[关闭]
@lemonguge 2015-07-01T08:13:16.000000Z 字数 12466 阅读 365

Java I/O系统(四)

I/O


笔者在这一篇将会对Java I/O系统的其他流类进行介绍,虽然可能并不常用,但是还是希望大家了解一下,同时这也是对笔者所掌握的知识进行温习。


合并流

在Java I/O系统中有一个流可以方便我们,将多个字节读取流和并成一个流(读取读取),将多个源合并成一个源,操作起来方便。这个流就是SequenceInputStream合并流,同样也是装饰器设计模式的一种体现。首先,我们看看怎么创建这个流。

  1. import java.io.BufferedReader;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.PrintStream;
  6. import java.io.SequenceInputStream;
  7. public class Sequence {
  8. public static void main(String[] args) throws IOException {
  9. Sequence1();
  10. }
  11. // 合并两个文件
  12. public static void Sequence1() throws IOException {
  13. FileInputStream fis1 = new FileInputStream("file1.txt");
  14. FileInputStream fis2 = new FileInputStream("file2.txt");
  15. SequenceInputStream sis = new SequenceInputStream(fis1, fis2);
  16. InputStreamReader isr = new InputStreamReader(sis);
  17. BufferedReader br = new BufferedReader(isr);
  18. PrintStream ps = new PrintStream("file1&2.txt");
  19. String line = null;
  20. while ((line = br.readLine()) != null) {
  21. ps.println(line);
  22. ps.flush();
  23. }
  24. br.close();
  25. ps.close();
  26. }
  27. } ///~
  1. import java.io.BufferedReader;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.io.PrintStream;
  7. import java.io.SequenceInputStream;
  8. import java.util.ArrayList;
  9. import java.util.Collections;
  10. import java.util.Enumeration;
  11. import java.util.List;
  12. public class Sequence {
  13. public static void main(String[] args) throws IOException {
  14. Sequence2();
  15. }
  16. // 合并多个个文件
  17. public static void Sequence2() throws IOException {
  18. FileInputStream fis1 = new FileInputStream("file1.txt");
  19. FileInputStream fis2 = new FileInputStream("file2.txt");
  20. FileInputStream fis3 = new FileInputStream("file3.txt");
  21. List<FileInputStream> fisArr = new ArrayList<FileInputStream>();
  22. fisArr.add(fis1);
  23. fisArr.add(fis2);
  24. fisArr.add(fis3);
  25. // 使用集合工具类的方法生成枚举类型
  26. Enumeration<FileInputStream> eum = Collections.enumeration(fisArr);
  27. SequenceInputStream sis = new SequenceInputStream(eum);
  28. BufferedReader br = new BufferedReader(new InputStreamReader(sis));
  29. FileOutputStream fos = new FileOutputStream("file1&2&3.txt");
  30. PrintWriter pw = new PrintWriter(fos, true); // 自动刷新
  31. String line = null;
  32. while ((line = br.readLine()) != null) {
  33. pw.println(line);
  34. }
  35. br.close();
  36. pw.close();
  37. }
  38. } ///~

对象流

这种流可以用来操作对象,要操作对象的那个类必须实现Serializable接口,这接口是Java序列化的意思。什么是序列化呢,用于给被序列化的类加入ID号,用于判断类和对象是否是同一个版本。对象流可以将对象从堆内存中存到硬盘,使将对象持久化。

可以发现Serializable接口中没有任何需要实现的方法,该接口只是一个标记接口。虽然可以使用默认的序列化版本号,不过笔者推荐自定义序列号版本号,因为编译器的不同可能会导致反系列化时导致意外的InvalidClassException异常。

  1. import java.io.FileInputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import java.io.ObjectInputStream;
  5. import java.io.ObjectOutputStream;
  6. import java.io.Serializable;
  7. class Person implements Serializable{
  8. private static final long serialVersionUID = 1L;
  9. private String name;
  10. private int age;
  11. public Person(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. public String getName() { return name; }
  16. public void setName(String name) { this.name = name; }
  17. public int getAge() { return age; }
  18. public void setAge(int age) { this.age = age; }
  19. @Override
  20. public String toString() { return name+":"+age; }
  21. }
  22. public class ObjectStream {
  23. public static void main(String[] args) throws IOException, ClassNotFoundException {
  24. outputObj();
  25. inputObj();
  26. }
  27. // 反序列化
  28. public static void inputObj() throws IOException, ClassNotFoundException {
  29. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person&since1L.obj"));
  30. Person _p = (Person) ois.readObject();
  31. System.out.println(_p);
  32. ois.close();
  33. }
  34. // 序列化
  35. public static void outputObj() throws IOException {
  36. Person p = new Person("小雨", 17);
  37. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person&since1L.obj"));
  38. oos.writeObject(p);
  39. oos.close();
  40. }
  41. } /* Output:
  42. 小雨:17
  43. *///:~

当执行上面的文件时,可以在当前项目下发现有一个文件为“person&since1L.obj”。如果对Person类进行改变时,再来读取该文件,就会出现异常。

  1. import java.io.FileInputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import java.io.ObjectInputStream;
  5. import java.io.ObjectOutputStream;
  6. import java.io.Serializable;
  7. class Person implements Serializable{
  8. private static final long serialVersionUID = 2L; // 修改序列化版本号
  9. private String name;
  10. private int age;
  11. public Person(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. public String getName() { return name; }
  16. public void setName(String name) { this.name = name; }
  17. public int getAge() { return age; }
  18. public void setAge(int age) { this.age = age; }
  19. @Override
  20. public String toString() { return name+":"+age; }
  21. }
  22. public class ObjectStream {
  23. public static void main(String[] args) throws IOException, ClassNotFoundException {
  24. try {
  25. inputObj();
  26. } catch (InvalidClassException e) {
  27. System.out.println(e.getMessage());
  28. }
  29. }
  30. public static void inputObj() throws IOException, ClassNotFoundException {
  31. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person&since1L.obj"));
  32. Person _p = (Person) ois.readObject();
  33. System.out.println(_p);
  34. ois.close();
  35. }
  36. } /* Output:
  37. Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
  38. *///:~

对于对象系列化,有一点需要注意的地方是:“静态的数据存储在方法区中不在堆中,系列化时不能保存该值。”如果我们想要非静态数据不被序列化,可以给成员变量前面加上transient关键字。

  1. import java.io.FileOutputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectOutputStream;
  4. import java.io.Serializable;
  5. class Person implements Serializable{
  6. private static final long serialVersionUID = 12345L;
  7. private transient String name;
  8. private static int age;
  9. public Person(String name) {
  10. this.name = name;
  11. }
  12. public String getName() { return name; }
  13. public void setName(String name) { this.name = name; }
  14. public static int getAge() { return age; }
  15. public static void setAge(int age) { Person.age = age; }
  16. @Override
  17. public String toString() { return name+":"+age; }
  18. }
  19. public class ObjectStream {
  20. public static void main(String[] args) throws IOException {
  21. outputObj();
  22. }
  23. public static void outputObj() throws IOException {
  24. Person.setAge(17);
  25. Person p = new Person("小雨");
  26. System.out.println(p);
  27. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.obj"));
  28. oos.writeObject(p);
  29. oos.close();
  30. }
  31. } /* Output:
  32. 小雨:17
  33. *///:~

可以在当前项目下发现“person.obj”文件,此时我们对该文件进行反序列号。

  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. import java.io.Serializable;
  5. class Person implements Serializable{
  6. private static final long serialVersionUID = 12345L;
  7. private transient String name;
  8. private static int age;
  9. public Person(String name) {
  10. this.name = name;
  11. }
  12. public String getName() { return name; }
  13. public void setName(String name) { this.name = name; }
  14. public static int getAge() { return age; }
  15. public static void setAge(int age) { Person.age = age; }
  16. @Override
  17. public String toString() { return name+":"+age; }
  18. }
  19. public class ObjectStream {
  20. public static void main(String[] args) throws IOException, ClassNotFoundException {
  21. inputObj();
  22. }
  23. public static void inputObj() throws IOException, ClassNotFoundException {
  24. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.obj"));
  25. Person _p = (Person) ois.readObject();
  26. System.out.println(_p);
  27. ois.close();
  28. }
  29. } /* Output:
  30. null:0
  31. *///:~

操作基本数据类型流

之前介绍了操作对象的流,现在要接触新的流,用于操作基本数据类型。DataInputStreamDataOutputStream这两个流也是用来装饰其他流,下面会进行介绍:

  1. import java.io.DataOutputStream;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.PrintStream;
  5. public class DataStream {
  6. public static void main(String[] args) throws IOException {
  7. writeByte();
  8. readDataFile();
  9. }
  10. public static void writeByte() throws IOException {
  11. DataOutputStream dos = new DataOutputStream(new PrintStream("data.txt"));
  12. // 0110-0001
  13. dos.writeByte(97);
  14. dos.write(97);
  15. // 0000-0001-0110-0001
  16. dos.writeByte(353);
  17. dos.write(353);
  18. dos.close();
  19. }
  20. public static void readDataFile() throws IOException {
  21. FileReader fr = new FileReader("data.txt");
  22. int ch = 0;
  23. while ((ch = fr.read()) != -1) {
  24. System.out.println((char) ch);
  25. }
  26. System.out.println("--end--");
  27. fr.close();
  28. }
  29. } /* Output:
  30. a
  31. a
  32. a
  33. a
  34. --end--
  35. *///:~

当前项目下的data.txt文件中显示的也是"aaaa",writeByte()方法的效果如同write(),即写入指定参数的8个低位。实际上是有区别的,writeByte()(写入1-byte)方法将会先写入高位,只是后面的低位将其覆盖,write()是直接就写入低8位(写入1-byte)。不止是writeByte()方法,其他的writeInt()(写入4-byte)、writeChar()(写入2-byte)、writeShort()(写入2-byte)等等其他方法都是先写入高位。值得注意的是:writeByte()writeChar()writeShort()接受的都是int类型参数,writeBoolean()接受的是一个boolean布尔值。

  1. import java.io.DataOutputStream;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.PrintStream;
  5. // 写入字符
  6. public class DataStream {
  7. public static void main(String[] args) throws IOException {
  8. writeChar();
  9. readDataFile();
  10. }
  11. public static void writeChar() throws IOException {
  12. DataOutputStream dos = new DataOutputStream(new PrintStream("data.txt"));
  13. // 0000-0000-0110-0001
  14. dos.writeChar(97);
  15. // 0000-0000-0110-0001-0110-0010-0110-0011
  16. dos.writeChar(6382179);
  17. dos.close();
  18. }
  19. public static void readDataFile() throws IOException {
  20. FileReader fr = new FileReader("data.txt");
  21. int ch = 0;
  22. while ((ch = fr.read()) != -1) {
  23. System.out.println((char) ch);
  24. }
  25. System.out.println("--end--");
  26. fr.close();
  27. }
  28. } /* Output:
  29. a
  30. b
  31. c
  32. --end--
  33. *///:~

当前项目下的data.txt文件中显示的是“愀换”两个字,这只是对字节查询了GBK编码表显示的结果。writeChar()将一个char值以2-byte值形式写入基础输出流中,先写入高字节。因为要先从高位写2-byte,实际写入6382179时的显示应该是这样的:高位2-byte为一个空格和"a",接着低位覆盖写入了"b"和"c"。

  1. import java.io.DataOutputStream;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.PrintStream;
  5. // 写入字符串
  6. public class DataStream {
  7. public static void main(String[] args) throws IOException {
  8. writeChar();
  9. readDataFile();
  10. }
  11. public static void writeString() throws FileNotFoundException, IOException {
  12. DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
  13. dos.writeBytes("abc");
  14. dos.writeChars("abc");
  15. dos.close();
  16. }
  17. public static void readDataFile() throws IOException {
  18. FileReader fr = new FileReader("data.txt");
  19. int ch = 0;
  20. while ((ch = fr.read()) != -1) {
  21. System.out.println((char) ch);
  22. }
  23. System.out.println("--end--");
  24. fr.close();
  25. }
  26. } /* Output:
  27. a
  28. b
  29. c
  30. a
  31. b
  32. c
  33. --end--
  34. *///:~

当前项目下的data.txt文件中显示的是"abc a b c",至于中间的空格,因为writeChars()方法写入的是字符,即2-byte的数据,而读数据的时候中文以2-byte,而英文以1-byte来读再查询编码表,0对应的是空字符。这种现象在我们写int类型的基本数据时尤为明显。

  1. import java.io.DataOutputStream;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.PrintStream;
  5. // int类型写入4-byte
  6. public class DataStream {
  7. public static void main(String[] args) throws IOException {
  8. writeInt();
  9. readDataFile();
  10. }
  11. public static void writeInt() throws FileNotFoundException, IOException {
  12. DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
  13. dos.writeInt(97);
  14. dos.close();
  15. }
  16. public static void readDataFile() throws IOException {
  17. FileReader fr = new FileReader("data.txt");
  18. int ch = 0;
  19. while ((ch = fr.read()) != -1) {
  20. System.out.println((char) ch);
  21. }
  22. System.out.println("--end--");
  23. fr.close();
  24. }
  25. } /* Output:
  26. a
  27. --end--
  28. *///:~

当前项目下的data.txt文件中显示的是" a",a前面有3个空格。对于DataOutputStream写入的基本数据数据,我们可以使用DataInputStream获取基本数据类型。

  1. import java.io.DataInputStream;
  2. import java.io.DataOutputStream;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. // int类型写入4-byte
  6. public class DataStream {
  7. public static void main(String[] args) throws IOException {
  8. DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
  9. dos.write(97);
  10. dos.writeByte(97);
  11. dos.writeChar(97);
  12. dos.writeInt(97);
  13. dos.writeBoolean(true);
  14. dos.close();
  15. DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
  16. System.out.print(dis.read());
  17. System.out.print(dis.readByte());
  18. System.out.print(dis.readChar());
  19. System.out.print(dis.readInt());
  20. System.out.print(dis.readBoolean());
  21. dis.close();
  22. }
  23. } /* Output:
  24. 9797a97true
  25. *///:~

对于DataOutputStream流中,有一个方法值得注意,void writeUTF(String str)以与机器无关方式使用UTF-8修改版编码将一个字符串写入基础输出流,所以使用转换率无法指定码表。(如果写到文本文件中看到的也是乱码)

  1. import java.io.DataInputStream;
  2. import java.io.DataOutputStream;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. // int类型写入4-byte
  6. public class DataStream {
  7. public static void main(String[] args) throws IOException {
  8. writeData();
  9. readData();
  10. }
  11. public static void readData() throws IOException {
  12. DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
  13. String str = dis.readUTF();
  14. System.out.println(str);
  15. }
  16. public static void writeData() throws IOException {
  17. DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
  18. dos.writeUTF("你好");// 使用了UTF-8修改版编码写入
  19. dos.close();
  20. }
  21. } /* Output:
  22. 你好
  23. *///:~

操作内存的流

操作内存的流有四个,内存字节输入输出流和内存字符输入输出流。因为这种类型的流只是操作内存,并不调用底层资源,所以并不需要调用close()方法,如果关闭该流后,还是可以调用此类中的各个方法,而不会产生任何IOException

输入流的构造器都必须接受一个数组,内存字符输入流CharArrayReader的构造函数接受一个字符数组为其缓冲区数组,内存字节输入流ByteArrayInputStream的构造函数接受一个字节数组为其缓冲区数组。输入流的读方法都是从该缓存数组中进行读取的。

输出流的构造器可以有默认的,还可以指定大小的缓冲区容量,缓冲区会随着数据的不断写入而自动增长。可使用toByteArray()toCharArray()toString()获取数据,输出流都有一个writeTo()方法,将缓冲区的内容写入另一个流中。

  1. import java.io.ByteArrayInputStream;
  2. import java.io.ByteArrayOutputStream;
  3. public class Array {
  4. public static void main(String[] args) {
  5. ByteArrayInputStream bi = new ByteArrayInputStream("abc你好".getBytes());
  6. ByteArrayOutputStream bo = new ByteArrayOutputStream();
  7. int ch = 0;
  8. while((ch=bi.read())!=-1){
  9. bo.write(ch);
  10. }
  11. System.out.println(bo.toString()); // 获取内存缓冲区的数据
  12. }
  13. } /* Output:
  14. abc你好
  15. *///:~

其中字符串缓冲字符流StringReaderStringWriter的使用和其非常类型,只不过不是缓冲数组,而会字符串缓冲。下面进行介绍一下:

  1. import java.io.IOException;
  2. import java.io.StringReader;
  3. import java.io.StringWriter;
  4. public class StringIO {
  5. public static void main(String[] args) throws IOException {
  6. StringReader sr = new StringReader("abc你好");
  7. StringWriter sw = new StringWriter();
  8. char[] chs = new char[4];
  9. int length = 0;
  10. while((length=sr.read(chs))!=-1){
  11. sw.write(chs, 0, length);
  12. }
  13. System.out.println(sw.toString());
  14. }
  15. } /* Output:
  16. abc你好
  17. *///:~
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注