Java_IO字节流下(17)

FilterInputStream 和 FilterOutputStream

FilterInputStream 和 FilterOutputStream 使用装饰器模式,封装输入输出流并提供额外功能

,类似于接口的作用,具体的装饰功能由子类完成,常用的子类有 BufferedInputStream、DataInputStream、BufferedOutputStream、DataOutputStream、PrintStream

BufferedInputStream 和 BufferedOutputStream

BufferedInputStream 缓冲输入流,为另一个输入流添加缓冲功能、mark 标记和 reset 重置方法功能,通过内部缓冲区字节数组实现,读输入流数据时,BufferedInputStream 会将数据先保存到缓冲区,默认的缓冲大小是 8192 字节,内部定义了索引来标记 read 方法读取下一个字节的位置,read 方法会调用 fill 方法填充缓冲区,填充可分为 4 种情况

情况一,buffer 没有被装满,则继续填充,无论被标记与否

情况二,buffer 已经被装满,buffer 没有被标记,则读完 buffer 中的数据后,从 buffer 头覆盖填充

情况三,buffer 已经被装满,buffer 被标记且需要保留的数据小于 buffer,则读完 buffer 中的数据后,拷贝需要保留的数据,即被标记位置到 buffer 末尾之间的数据,到 buffer 开头,然后从拷贝数据的末尾继续覆盖填充

情况四,buffer 已经被装满,buffer 被标记且需要保留的数据等于 buffer,保留的数据长度如果小于等于 marklimit,则新建一个更大的字节数组,将需要保留的数据拷贝到新数组,然后从拷贝数据的末尾继续填充,但保留的数据长度如果大于 marklimit,则 mark 失效,直接清空 buffer ,重新从 buffer 头开始填充,即 mark 失效并不是完全由 marklimit 决定,而是取 marklimit 和 buffer 两者较大的值

buffer 数组扩容,首先判断如果扩容 2 倍大于了 marklimit,则扩容到 marklimit 的大小,如果不超过 marklimit,则扩容 2 倍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Test {
  public static void main(String[] args) {
    try {
      //file的内容为abcdef
      File file = new File("file.txt");
      //设置缓冲区初始大小为4
      InputStream in = new BufferedInputStream(new FileInputStream(file), 4);
      byte[] buf = new byte[5];
      //标记当前位置,即第1个元素a,并设置marklimit为1,不超过缓冲区
      //理论上之后只能再读1个字节,超过1个mark将失效
      in.mark(1);
      //读2个字节,即到第3个元素c
      in.read(buf, 0, 2);
      //因为没有超过缓冲区,即使超过了marklimit的限制mark也不会失效
      in.reset(); //重置到mark标记的位置a处,

      in.mark(5); //设置marklimit为5,超过缓冲区一个字节
      //读5个字节,超出了buffer但不大于marklimit,则buffer自动扩容
      //4扩容2倍为8大于了marklimit的5,所以只扩容到5
      in.read(buf, 0, 5);
      in.reset();
      in.reset(); //只要mark没有失效则可以多次reset
      in.read(buf, 0, 5);
      //读完5个再读一个,使得超出缓冲区
      in.read();
      //共读了6个字节,超出了buffer且大于marklimit,则mark失效,清空buffer从头覆盖填充
      in.reset(); //mark失效,reset将会报IO异常Resetting to invalid mark
      in.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

BufferedOutputStream 缓冲输出流,同样内部维护了默认大小为 8192 字节的字节数组作为缓冲区,当缓冲区被装满或调用 flush 方法,才会将缓冲区的数据写入输出流,如果一次性输出大于缓冲区的数据,则直接输出不经过缓冲区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Test {
  public static void main(String[] args) {
    try {
      //abcdef
      byte[] byteArr = {0x61, 0x62, 0x63, 0x64, 0x65, 0x66};
      File file = new File("file.txt");
      //设置缓冲区初始大小为4
      OutputStream out = new BufferedOutputStream(
        new FileOutputStream(file), 4);
      //将byteArr前2个字节写入输出流
      out.write(byteArr, 0, 2);
      System.out.println("断点调试"); //此时缓冲区没有装满不会写入文件
      out.flush();
      System.out.println("断点调试"); //手动刷新缓冲区写入文件
      //再写入2个字节装满缓冲区
      out.write(byteArr, 2, 4);
      System.out.println("断点调试"); //缓冲区装满自动刷新
      out.write(byteArr, 0, 6);
      System.out.println("断点调试"); //如果一次性输出大于缓冲区的数据,则直接输出
      out.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
DataInputStream 和 DataOutputStream

FileOutputStream 不能根据 Java 基本类型读写数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Test {
  public static void main(String[] args) {
    try {
      File file = new File("file.txt");
      FileOutputStream out = new FileOutputStream(file);
      //997补码为00000000 00000000 00000011 11100101
      //写入byte类型数据会舍去前三个字节,则写入的数据为11100101,即229
      out.write(997);
      out.close();
      FileInputStream in = new FileInputStream(file);
      System.out.println(in.read()); //229
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
}

DataInputStream 和 DataOutputStream 可以根据 Java 基本类型从其包装流读写数据,屏蔽数据类型底层存储的字节信息,还提供有 readUTF 方法,可以从输入流读取 UTF-8 编码的数据,并返回 String 字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test {
  public static void main(String[] args) {
    try {
      File file = new File("file.txt");
      DataOutputStream out = new DataOutputStream(
        new FileOutputStream(file));
      out.writeBoolean(true);
      out.writeChar('a');
      out.writeByte((byte)12);
      out.writeShort((short)123);
      out.writeInt(997);
      out.writeLong(123456789L);
      out.writeUTF("张三");
      out.close();
      DataInputStream in = new DataInputStream(new FileInputStream(file));
      System.out.println(in.readBoolean()); //true
      System.out.println(in.readChar());    //a
      System.out.println(in.readByte());    //12
      System.out.println(in.readShort());   //123
      System.out.println(in.readInt());     //997
      System.out.println(in.readLong());    //123456789
      System.out.println(in.readUTF());     //张三
      in.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
PrintStream 和 System.out.println

PrintStream 打印输出流,装饰其它输出流方便打印数据,提供了自动 flush 和字符集设置功能,且产生的 IOException 都会在内部就被捕获并添加错误标记,可以通过 checkError() 返回错误标记查看 IOException,print 和 println 方法都是将传入的参数转换成字符串再写入输入流

PrintStream 使用指定的编码,如果没有指定则使用系统默认编码,而 DataOutputStream 则只使用UTF-8 编码, DataOutputStream 一般与 DataInputStream 配合使用屏蔽数据类型底层存储的字节信息,PrintStream 目的是使其装饰的输出流能方便地输出各种格式的数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Test {
  public static void main(String[] args) {
    final byte[] arr = {0x61, 0x62, 0x63, 0x64, 0x65};
    try {
      File file = new File("file.txt");
      PrintStream out = new PrintStream(new FileOutputStream(file));
      //默认装饰FileOutputStream
      //PrintStream out = new PrintStream(file);
      //PrintStream out = new PrintStream("file.txt");
      out.write(arr);
      out.write(0x41); //0x41对应ASCII码为'A'
      out.print(0x41); //等价于out.write(String.valueOf(0x41)); 即65
      out.append('B'); //将字符'B'追加到输出流中
      out.println("C");
      out.println(new Student("张三")); //自动调用toString方法
      out.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
class Student {
  private String name;
  public Student(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return name;
  }
}
//file.txt中为 abcdeA65BC 张三

System.out.println 就使用了 PrintStream

1
2
3
public final class System {
    public final static PrintStream out = null;
}

System 类定义了 PrintStream 对象 out,使用的 println 方法就是 PrintStream 定义的方法

1
2
3
4
5
6
private static void initializeSystemClass() {
  //通过标准输出out的FileDescriptor创建FileOutputStream
  FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
  //将FileOutputStream通过PrintStream包装,并设置编码方式
  setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
}

ObjectInputStream 和 ObjectOutputStream

ObjectInputStream 和 ObjectOutputStream 用于序列化操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class Test {
  public static void main(String[] args) {
    testWrite();
    testRead();
  }
  private static void testWrite() {
    try {
      //通过ObjectOutputStream序列化后再通过FileOutputStream写入文件
      ObjectOutputStream out = new ObjectOutputStream(
        new FileOutputStream("file.txt"));
      //序列化基本类型
      out.writeBoolean(true);
      out.writeByte((byte)65);
      out.writeChar('a');
      out.writeInt(12345);
      out.writeLong(123456789L);
      out.writeFloat(3.14F);
      out.writeDouble(3.14159D);
      //序列化HashMap对象
      HashMap map = new HashMap();
      map.put("one", "red");
      map.put("two", "green");
      map.put("three", "blue");
      out.writeObject(map);
      //序列化自定义对象
      Student stu = new Student("张三");
      out.writeObject(stu);
      out.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  private static void testRead() {
    try {
      //通过FileInputStream读取文件后再通过ObjectOutputStream反序列化
      ObjectInputStream in = new ObjectInputStream(
        new FileInputStream("file.txt"));
      //反序列化基本类型
      System.out.println(in.readBoolean());
      System.out.println((in.readByte() & 0xff)); //保证二进制数据一致性
      System.out.println(in.readChar());
      System.out.println(in.readInt());
      System.out.println(in.readLong());
      System.out.println(in.readFloat());
      System.out.println(in.readDouble());
      //反序列化HashMap对象
      HashMap map = (HashMap) in.readObject();
      Iterator iter = map.entrySet().iterator();
      while (iter.hasNext()) {
        Map.Entry entry = (Map.Entry)iter.next();
        System.out.println(entry.getKey() + ": " + entry.getValue());
      }
      //反序列化自定义对象
      Student stu = (Student) in.readObject();
      System.out.println(stu);
      in.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
class Student implements Serializable {
  private String name;
  public Student(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return name;
  }
}

RandomAccessFile

通过流的方式访问文件不能从文件中间开始,RandomAccessFile 不属于流,但能对任意文件进行读写,内部定义了一个记录指针,可以标识当前读写的位置,并且可以自由移动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test {
  public static void main(String[] args) {
    try {
      File file = new File("file.txt");
      RandomAccessFile raf = new RandomAccessFile(file, "rw");
      raf.writeChars("abc"); //字母间会自动加上空格
      //获取指针位置
      long end = raf.getFilePointer();
      System.out.println(end); //6
      //移动指针到开头
      raf.seek(0);
      //读取一个字符
      char c = raf.readChar();
      System.out.println(c);   //a
      //读取当前指针之后的全部数据
      byte[] buf = new byte[(int)end];
      raf.read(buf, 0, buf.length);
      System.out.println(new String(buf)); //空格b空格c
      //在末尾追加内容
      long fileLen = raf.length();
      //定位到文件末尾
      raf.seek(fileLen);
      raf.writeBoolean(true);
      raf.writeByte((byte)12);
      raf.writeShort((short)123);
      raf.writeInt(12345);
      raf.writeLong(1234567890123456L);
      raf.writeFloat(1.2F);
      raf.writeDouble(3.14159D);
      raf.writeChar('a');
      raf.writeUTF("张三"); //UTF-8
      raf.close();
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
}

以上内容是玉山整理的笔记,如有错误还请指出