InputStream和OutputStream构成了输入/输出类层次结构的基础。用于按字节进行读写。而与之处在同一等级的Reader/Writer同样作为抽象类定义了用于对字符进行读取的类层次结构,是基于两个字节的Unicode码元的读取。

  inputStream的抽象方法abstract int read(),每次读入一个字节并返回一个int值,在遇到文件尾的时候返回-1.所以可以用其子类FileInputStream进行文件字符读取:

  1. FileInputStream f = new FileInputStream("C:\\Users\\Loki\\Desktop\\io.txt");
  2. int i;
  3. while((i=f.read()) != -1){
  4. System.out.print((char)i);
  5. }

  同样OutputStream的抽象方法abstract void write()每次写入一个字节,比如用其子类FileOutputStream进行写入:

  1. FileOutputStream out = new FileOutputStream("C:\\Users\\Loki\\Desktop\\output.txt");
  2. char[] chars = {'a','b','c','d','e'};    //java中char是两个字节表示。
  3. for(int j = 0; j < chars.length; j++){ //如果用write写入int类型变量,则低8位被写入,高24位被忽略。
  4. out.write(chars[j]);
  5. }

  read和write方法在执行时都将阻塞,直至字节被读入或写出。此外可以通过available()方法得知可读入字节数,也可以以数组的形式进行读取。

  同样的,InputStreamReader类也是从文件中读取字节,并以int的形式返回,遇到文件尾则返回-1。但是它与上面的InputStream有什么不同呢?不同点就在于InputStream没有指定解析字节的编码方式,只是纯粹地读取一个字节的内容并原原本本地返回。而InputStreamReader类的构造器是以输入流为参数并且可以指定编码方式,比如可以以InputStream实例对象为参数并指定“GBK”编码方式,以“GBK”编码方式解析从InputStream输入流返回的字节,解析完了之后再返回对应字符的Unicode对应的代码值(代码点)。java核心技术上说  “InputStreamReader 类将包含字节(用某种字符编码方式表示的字符)的输入流转换为转换为可以产生Unicode码元的读入器”。以下是实例代码和输出,文件中的文字是“小岳岳是河南人”,第一行是InputStream的输出,第二行是InputStreamReader的输出:

  1. FileInputStream f2 = new FileInputStream("C:\\Users\\Loki\\Desktop\\io.txt");
  2. InputStreamReader inputStreamReader = new InputStreamReader(f2,"gbk");
  3. while((i = inputStreamReader.read()) != -1)
  4. System.out.print((char)i);
  5.  
  6. СÔÀÔÀÊǺÓÄÏÈË
  7. 小岳岳是河南人
  8. Process finished with exit code 0

根据以上描述,相应的OutpStreamReader类在输出的时候要指定一个输出流,并且可选地选定编码方式。

  当然,如果要是像文件写入文本的话,最方便的还是PrintWriter类,对于这个类的使用是跟System.out是一样的,它把数字、字符、boolean值以“utf-8"的编码格式编码成的字符串的形式输出到指定位置。关于编码格式,可以通过上面的InPuStreamReader验证。以下例程:

  1. PrintWriter printWriter = new PrintWriter("C:\\Users\\Loki\\Desktop\\output.txt");
  2. printWriter.println();
  3. printWriter.println("今天天气不错");
  4. printWriter.println(24);
  5. printWriter.println(true);
  6. printWriter.println('c');
  7. printWriter.flush();
  8.  
  9. 文档内容:
  10.  
  11. 今天天气不错
  12. 24
  13. true
  14. c

  对应文本的读入之前是用BufferedReader,不过建议用Scanner。在下面的例程中,刚开始没有用System.out把in.nextLine()输出,导致程序一直没有输出。所以以后看Api的时候一定要好好看函数的返回类型:

  1. Scanner in = new Scanner(System.in);
  2. while(in.hasNextLine()) //对应于单个字符有.hasNext()和.next()方法。具体查看Api
  3. {
  4. System.out.println(in.nextLine());
  5. }

字符集中的编码方式可以在java中的unicode字符和编码而成的字节之间进行转换。获取字符集对象的方式为Charset.forName("utf-8");这与反射中通过名字构造类实例一样(Class cl = Class.forName("java.util.Scanner");)。

  下面是如何编码java字符串:

    String str = ...;

    ByteBuffer buffer = Charset.forName("...");

    byte[] bytes = buffer.array();

  解码字节序列:

    byte[] bytes = ...;

    ByteBuffer bbuf = ByteBuffer.wrap(bytes,offset,length);  

    CharBuffer cbuf = cset.decode(bbuf);

    String str = cbuf.toString();

  DataOutput接口定义了一些方法,这些方法以二进制的格式写数组、字符、boolean值。比如writeInt总是把一个整数写出为4字节的二进制整数量。而DataInput在输入的时候需要用一个输入流初始化。

  随机访问文件类RandomAccessFile类的思想与C语言中的文件读取思想是一致的,可以选择只读(r)或可读可写(rw),还可以获得获得文件指针,也能够设置文件指针的位置。它同时实现了DataOutput和DateInput的方法,所以可以用这两个接口中的方法对文件进行读取。

  读取Zip文档的时候要用到ZipInputStream类,以下是两种读取方法,但是注意,Scanner方法要比直接用read方法的效果要好:

  1. ZipInputStream zin = new ZipInputStream(new FileInputStream("C:\\Users\\Loki\\Desktop\\Desktop.zip"));
  2. ZipEntry entry;
  3. while((entry = zin.getNextEntry()) != null){
  4. Scanner scanner = new Scanner(zin);
  5. while (scanner.hasNextLine())
  6. System.out.println(scanner.nextLine());
  7. zin.closeEntry();
  8. }
  9.  
  10. /*
  11. while((entry = zin.getNextEntry()) != null){
  12. int i;
  13. while((i = zin.read()) != -1)
  14. System.out.print((char)i);
  15. zin.closeEntry();
  16. }
  17. */
  18. zin.close();

序列化:

  序列化是一种传递对象的方式,以序列号代替内存地址标记不同的对象。为什么要用序列号而不是内存地址呢,因为虚拟机会对内存机进行整理从而改变对象的内存地址,所以用内存地址标记一个对象是不可靠的。

  先对序列化有一个直观的认识。序列化是将对象写出到流中、之后再将其读回。为了写出和读回,我们需要用到ObjectInputStream和ObjectOutputStream类,顾名思义,对象输入和输出流。如下程序所示:

  1. public static void main(String[] args) throws IOException,ClassNotFoundException{
  2.  
  3. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Loki\\Desktop\\io.txt"));
  4. Student li = new Student("LiSi", 24,2);
  5. Student zhang = new Student("ZhangSan", 24,3);
  6. out.writeObject(li);
  7. out.writeObject(zhang);
  8. out.close();
  9.  
  10. ObjectInputStream in = new ObjectInputStream(new FileInputStream("C:\\Users\\Loki\\Desktop\\io.txt"));
  11. Student copyLi = (Student)in.readObject(); //要抛出ClassNoteFountException
  12. Student copyZhang = (Student)in.readObject();
  13.  
  14. System.out.println(copyLi.getName()); //注意这里面的copyLi和li是数据域相同的不同的对象。
  15. System.out.println(copyZhang.getName());
  16.  
  17. }
  18. }
  19.  
  20. class Student implements Serializable{
  21. private String name;
  22. private int age;
  23. private int grade;
  24.  
  25. public Student(){
  26.  
  27. }
  28. public Student(String aName, int aAge, int aGrade){
  29. name = aName;
  30. age = aAge;
  31. grade = aGrade;
  32. }
  33. ...
  34. ...
  35. ...
  36. }

需要注意的有两点,其一:要抛出ClassNotFountException异常;其二:要实现Serializable接口,这是一个标记接口,没有任何方法。

也可以序列化基本类型,用到的方法是writeInt这些方法,因为ObjectInputStream和ObjectOutputStream类实现了DataInput和DataOutput接口。

因为每个对象都是用序列号保存的,所以这种机制被称为序列化,以下是其算法:

  1. 遇到的每一个对象都关联一个序列号;

  2. 对于每一个对象,第一次遇到时将其对象保存到流中(保存的是域以及类信息);

  3. 如果遇到的对象已经被保存过,那么只写"与之前保存过的序列号为x的对象相同”;

  4. 读取对象的时候过程相反。

  如果类中有不可序列化的域,则应该标记为transient,在序列化时会被跳过。为了存储这些不可序列化的域,可序列化的类可以定义具有以下签名的私有方法:

    

  1. private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;
  2. private void writeObject(ObjectOutputStream out) throws IOException;

之后数据域就不会再被自动序列化,而是被用反射机制调用来序列化对象,在这种私有方法的内部实现类似于下面所示:

  1. private void writeObject(ObjectOutputStream out){
  2. out.defaultWriteObject();
  3. out.writeDouble(x);
  4. out.writeDouble(y);
  5. }

  其中通过defaultWriteObject方法序列化可被序列化的域,然后用标砖的DataOutput调用写出不可被序列化的域。

外部化(Externalizable):

  除了使用序列化机制来保存和恢复对象数据之外,类还可以定义自己的机制,那就是外部化。外部话可以弥补序列化不能记录超类中数据的缺点。使用外部化时类要实现Exteinalizable接口,并实现readExternal和writeExternal方法, 这两个方法是公共方法。

  1. void writeExternal(ObjectOutput out) throws IOException;
  2. void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

在方法的具体实现中,可以通过调用DataOutput和DataInput的标准方法来输入和输出数据。

读写文件:

  Files类可以使得对普通的文件的读写变得快捷。这个类主要用于处理中等长度文本文件,在读取的时候它可以全部按字节读取或者当作行序列读取,有如下方法:

  1. byte[] bytes = Files.readAllBytes(path);
  2. String content = new String(bytes, charset); //将上面读出的byte以特定的编码方式转换为字符串
  3. //也可以把文件当作行序列读出
  4. List<String> lines = Files.readAllLines(path, charset);
  5.  
  6. //写出到文件
  7. Files.write(path, content.getBytes(charset));
  8. //追加
  9. Files.write(path, content.getBytes(charset), StandardOpenOption.APPEND);
  10. //将一个行的集合写入到文件中
  11. Files.write(path, lines);

  对于长文本文件和二进制文件,还是需要用传统的读入器/写出器,Files类的静态方法提供一些方法来获取读入/写出器,避免了上述的繁琐方法:

  1. InputStream in = Files.newInputStream(path);
  2. OutputStream out = Files.newOutputStream(path);
  3.  
  4. Reader in = Files.newBufferedReader(path, charset);
  5. Writer out = Files.newBufferedWriter(path, charset);

  Files类提供了移动、删除、复制文件的一些方法。

参考:

  http://blog.163.com/fan_yishan/blog/static/47692213200821595727205/

  java核心技术卷二

java核心技术之流与文件的更多相关文章

  1. Java I/O流 复制文件速度对比

    Java I/O流 复制文件速度对比 首先来说明如何使用Java的IO流实现文件的复制: 第一步肯定是要获取文件 这里使用字节流,一会我们会对视频进行复制(视频为非文本文件,故使用之) FileInp ...

  2. Java之字符流读写文件、文件的拷贝

    字符流读数据 – 按单个字符读取 创建字符流读文件对象: Reader reader = new FileReader("readme.txt"); 调用方法读取数据: int d ...

  3. Java基础 IO流的文件和目录的五类主要操作

    笔记: /** IO流的 文件和目录的操作 * 1.路径需要 需要两个反斜杠 或者一个单斜杠! * 绝对路径:包括盘符在内的完整的路径名! * 相对路径:在当前目录文件下的路径! * 2.File 是 ...

  4. Java基础IO流 ,文件读取,由易至难

    最基础的读取文件 import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;imp ...

  5. java 输入输出IO流 RandomAccessFile文件的任意文件指针位置地方来读写数据

    RandomAccessFile的介绍: RandomAccessFile是Java输入输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出 ...

  6. java中io流实现文件上传下载

    新建io.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" page ...

  7. JAVA(IO流)文件复制

    package com.am; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOEx ...

  8. Java程序设计---io流读取文件内容并将其逆值输出到控制台

    import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileR ...

  9. java通过IO流复制文件

    package kimoji; import java.io.*; public class FileTest { public static void main(String[] args) thr ...

随机推荐

  1. Memcached在windows下的基本使用

    1.Memcached是什么 Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动 ...

  2. JavaWeb 后端 <五> 之 JSP 学习笔记

    一.JSP简介 1.也是SUN公司推出的开发动态web资源的技术,属于JavaEE技术之一.由于原理上是Servlet, 所以JSP/Servlet在一起. 二.HTML.Servlet和JSP 1. ...

  3. 是否使用安全模式启动word

          打开word,出现了一个提示,显示着“word遇到问题需要关闭.我们对此引起的不便表示抱歉.”下面有选项“恢复我的工作并重启word”,选中它.点下面的“不发送”.      在出现的提示 ...

  4. 深入理解javascript异步编程障眼法&&h5 web worker实现多线程

    0.从一道题说起 var t = true; setTimeout(function(){ t = false; }, 1000); while(t){ } alert('end'); 1 2 3 4 ...

  5. css 子div自适应父div高度

    <div class="out"> <div class="a"></div> <div class="b& ...

  6. centos/linux alternatives与update-alternatives详解与软件版本切换

    update-alternatives是linux系统中专门维护系统命令链接符的工具,通过它可以很方便的设置系统默认使用哪个命令.哪个软件版本,比如,我们在系统中同时安装了open jdk和sun j ...

  7. Ubuntu14.04安装有道词典

    Ubuntu14.04安装有道词典之前要更新系统: sudo apt-get update sudo apt-get upgrade sudo apt-get dist-upgrade 在有道官网下载 ...

  8. git创建版本库以及使用

    Git使用教程(摘自tugenhua0707) 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央 ...

  9. SpringMVC源码情操陶冶-FreeMarker之web配置

    前言:本文不讲解FreeMarkerView视图的相关配置,其配置基本由FreeMarkerViewResolver实现,具体可参考>>>SpringMVC源码情操陶冶-ViewRe ...

  10. JavaScript Base64加解密

    Base64加密算法是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045-RFC2049,上面有MIME的详细规范.Base64编码可用于在HTTP环境下传递较长的标识信 ...