@lemonguge
2015-07-01T08:13:16.000000Z
字数 12466
阅读 365
I/O
笔者在这一篇将会对Java I/O系统的其他流类进行介绍,虽然可能并不常用,但是还是希望大家了解一下,同时这也是对笔者所掌握的知识进行温习。
在Java I/O系统中有一个流可以方便我们,将多个字节读取流和并成一个流(读取读取),将多个源合并成一个源,操作起来方便。这个流就是SequenceInputStream合并流,同样也是装饰器设计模式的一种体现。首先,我们看看怎么创建这个流。
SequenceInputStream合并流的构造函数 SequenceInputStream(Enumeration<? extends InputStream> e)参数必须是生成运行时类型为InputStream对象的Enumeration 型参数。SequenceInputStream(InputStream s1, InputStream s2)将按顺序读取这两个参数,先读取s1,然后读取s2。
import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintStream;import java.io.SequenceInputStream;public class Sequence {public static void main(String[] args) throws IOException {Sequence1();}// 合并两个文件public static void Sequence1() throws IOException {FileInputStream fis1 = new FileInputStream("file1.txt");FileInputStream fis2 = new FileInputStream("file2.txt");SequenceInputStream sis = new SequenceInputStream(fis1, fis2);InputStreamReader isr = new InputStreamReader(sis);BufferedReader br = new BufferedReader(isr);PrintStream ps = new PrintStream("file1&2.txt");String line = null;while ((line = br.readLine()) != null) {ps.println(line);ps.flush();}br.close();ps.close();}} ///~
import java.io.BufferedReader;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintStream;import java.io.SequenceInputStream;import java.util.ArrayList;import java.util.Collections;import java.util.Enumeration;import java.util.List;public class Sequence {public static void main(String[] args) throws IOException {Sequence2();}// 合并多个个文件public static void Sequence2() throws IOException {FileInputStream fis1 = new FileInputStream("file1.txt");FileInputStream fis2 = new FileInputStream("file2.txt");FileInputStream fis3 = new FileInputStream("file3.txt");List<FileInputStream> fisArr = new ArrayList<FileInputStream>();fisArr.add(fis1);fisArr.add(fis2);fisArr.add(fis3);// 使用集合工具类的方法生成枚举类型Enumeration<FileInputStream> eum = Collections.enumeration(fisArr);SequenceInputStream sis = new SequenceInputStream(eum);BufferedReader br = new BufferedReader(new InputStreamReader(sis));FileOutputStream fos = new FileOutputStream("file1&2&3.txt");PrintWriter pw = new PrintWriter(fos, true); // 自动刷新String line = null;while ((line = br.readLine()) != null) {pw.println(line);}br.close();pw.close();}} ///~
这种流可以用来操作对象,要操作对象的那个类必须实现Serializable接口,这接口是Java序列化的意思。什么是序列化呢,用于给被序列化的类加入ID号,用于判断类和对象是否是同一个版本。对象流可以将对象从堆内存中存到硬盘,使将对象持久化。
可以发现Serializable接口中没有任何需要实现的方法,该接口只是一个标记接口。虽然可以使用默认的序列化版本号,不过笔者推荐自定义序列号版本号,因为编译器的不同可能会导致反系列化时导致意外的InvalidClassException异常。
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;class Person implements Serializable{private static final long serialVersionUID = 1L;private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }@Overridepublic String toString() { return name+":"+age; }}public class ObjectStream {public static void main(String[] args) throws IOException, ClassNotFoundException {outputObj();inputObj();}// 反序列化public static void inputObj() throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person&since1L.obj"));Person _p = (Person) ois.readObject();System.out.println(_p);ois.close();}// 序列化public static void outputObj() throws IOException {Person p = new Person("小雨", 17);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person&since1L.obj"));oos.writeObject(p);oos.close();}} /* Output:小雨:17*///:~
当执行上面的文件时,可以在当前项目下发现有一个文件为“person&since1L.obj”。如果对Person类进行改变时,再来读取该文件,就会出现异常。
import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;class Person implements Serializable{private static final long serialVersionUID = 2L; // 修改序列化版本号private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }@Overridepublic String toString() { return name+":"+age; }}public class ObjectStream {public static void main(String[] args) throws IOException, ClassNotFoundException {try {inputObj();} catch (InvalidClassException e) {System.out.println(e.getMessage());}}public static void inputObj() throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person&since1L.obj"));Person _p = (Person) ois.readObject();System.out.println(_p);ois.close();}} /* Output:Person; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2*///:~
对于对象系列化,有一点需要注意的地方是:“静态的数据存储在方法区中不在堆中,系列化时不能保存该值。”如果我们想要非静态数据不被序列化,可以给成员变量前面加上transient关键字。
import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;class Person implements Serializable{private static final long serialVersionUID = 12345L;private transient String name;private static int age;public Person(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }public static int getAge() { return age; }public static void setAge(int age) { Person.age = age; }@Overridepublic String toString() { return name+":"+age; }}public class ObjectStream {public static void main(String[] args) throws IOException {outputObj();}public static void outputObj() throws IOException {Person.setAge(17);Person p = new Person("小雨");System.out.println(p);ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.obj"));oos.writeObject(p);oos.close();}} /* Output:小雨:17*///:~
可以在当前项目下发现“person.obj”文件,此时我们对该文件进行反序列号。
import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;class Person implements Serializable{private static final long serialVersionUID = 12345L;private transient String name;private static int age;public Person(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }public static int getAge() { return age; }public static void setAge(int age) { Person.age = age; }@Overridepublic String toString() { return name+":"+age; }}public class ObjectStream {public static void main(String[] args) throws IOException, ClassNotFoundException {inputObj();}public static void inputObj() throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.obj"));Person _p = (Person) ois.readObject();System.out.println(_p);ois.close();}} /* Output:null:0*///:~
之前介绍了操作对象的流,现在要接触新的流,用于操作基本数据类型。DataInputStream和DataOutputStream这两个流也是用来装饰其他流,下面会进行介绍:
import java.io.DataOutputStream;import java.io.FileReader;import java.io.IOException;import java.io.PrintStream;public class DataStream {public static void main(String[] args) throws IOException {writeByte();readDataFile();}public static void writeByte() throws IOException {DataOutputStream dos = new DataOutputStream(new PrintStream("data.txt"));// 0110-0001dos.writeByte(97);dos.write(97);// 0000-0001-0110-0001dos.writeByte(353);dos.write(353);dos.close();}public static void readDataFile() throws IOException {FileReader fr = new FileReader("data.txt");int ch = 0;while ((ch = fr.read()) != -1) {System.out.println((char) ch);}System.out.println("--end--");fr.close();}} /* Output:aaaa--end--*///:~
当前项目下的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布尔值。
import java.io.DataOutputStream;import java.io.FileReader;import java.io.IOException;import java.io.PrintStream;// 写入字符public class DataStream {public static void main(String[] args) throws IOException {writeChar();readDataFile();}public static void writeChar() throws IOException {DataOutputStream dos = new DataOutputStream(new PrintStream("data.txt"));// 0000-0000-0110-0001dos.writeChar(97);// 0000-0000-0110-0001-0110-0010-0110-0011dos.writeChar(6382179);dos.close();}public static void readDataFile() throws IOException {FileReader fr = new FileReader("data.txt");int ch = 0;while ((ch = fr.read()) != -1) {System.out.println((char) ch);}System.out.println("--end--");fr.close();}} /* Output:abc--end--*///:~
当前项目下的data.txt文件中显示的是“愀换”两个字,这只是对字节查询了GBK编码表显示的结果。writeChar()将一个char值以2-byte值形式写入基础输出流中,先写入高字节。因为要先从高位写2-byte,实际写入6382179时的显示应该是这样的:高位2-byte为一个空格和"a",接着低位覆盖写入了"b"和"c"。
import java.io.DataOutputStream;import java.io.FileReader;import java.io.IOException;import java.io.PrintStream;// 写入字符串public class DataStream {public static void main(String[] args) throws IOException {writeChar();readDataFile();}public static void writeString() throws FileNotFoundException, IOException {DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));dos.writeBytes("abc");dos.writeChars("abc");dos.close();}public static void readDataFile() throws IOException {FileReader fr = new FileReader("data.txt");int ch = 0;while ((ch = fr.read()) != -1) {System.out.println((char) ch);}System.out.println("--end--");fr.close();}} /* Output:abcabc--end--*///:~
当前项目下的data.txt文件中显示的是"abc a b c",至于中间的空格,因为writeChars()方法写入的是字符,即2-byte的数据,而读数据的时候中文以2-byte,而英文以1-byte来读再查询编码表,0对应的是空字符。这种现象在我们写int类型的基本数据时尤为明显。
import java.io.DataOutputStream;import java.io.FileReader;import java.io.IOException;import java.io.PrintStream;// int类型写入4-bytepublic class DataStream {public static void main(String[] args) throws IOException {writeInt();readDataFile();}public static void writeInt() throws FileNotFoundException, IOException {DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));dos.writeInt(97);dos.close();}public static void readDataFile() throws IOException {FileReader fr = new FileReader("data.txt");int ch = 0;while ((ch = fr.read()) != -1) {System.out.println((char) ch);}System.out.println("--end--");fr.close();}} /* Output:a--end--*///:~
当前项目下的data.txt文件中显示的是" a",a前面有3个空格。对于DataOutputStream写入的基本数据数据,我们可以使用DataInputStream获取基本数据类型。
import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.FileInputStream;import java.io.IOException;// int类型写入4-bytepublic class DataStream {public static void main(String[] args) throws IOException {DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));dos.write(97);dos.writeByte(97);dos.writeChar(97);dos.writeInt(97);dos.writeBoolean(true);dos.close();DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));System.out.print(dis.read());System.out.print(dis.readByte());System.out.print(dis.readChar());System.out.print(dis.readInt());System.out.print(dis.readBoolean());dis.close();}} /* Output:9797a97true*///:~
对于DataOutputStream流中,有一个方法值得注意,void writeUTF(String str)以与机器无关方式使用UTF-8修改版编码将一个字符串写入基础输出流,所以使用转换率无法指定码表。(如果写到文本文件中看到的也是乱码)
import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.FileInputStream;import java.io.IOException;// int类型写入4-bytepublic class DataStream {public static void main(String[] args) throws IOException {writeData();readData();}public static void readData() throws IOException {DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));String str = dis.readUTF();System.out.println(str);}public static void writeData() throws IOException {DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));dos.writeUTF("你好");// 使用了UTF-8修改版编码写入dos.close();}} /* Output:你好*///:~
操作内存的流有四个,内存字节输入输出流和内存字符输入输出流。因为这种类型的流只是操作内存,并不调用底层资源,所以并不需要调用close()方法,如果关闭该流后,还是可以调用此类中的各个方法,而不会产生任何IOException。
输入流的构造器都必须接受一个数组,内存字符输入流CharArrayReader的构造函数接受一个字符数组为其缓冲区数组,内存字节输入流ByteArrayInputStream的构造函数接受一个字节数组为其缓冲区数组。输入流的读方法都是从该缓存数组中进行读取的。
输出流的构造器可以有默认的,还可以指定大小的缓冲区容量,缓冲区会随着数据的不断写入而自动增长。可使用toByteArray()或toCharArray()和toString()获取数据,输出流都有一个writeTo()方法,将缓冲区的内容写入另一个流中。
import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;public class Array {public static void main(String[] args) {ByteArrayInputStream bi = new ByteArrayInputStream("abc你好".getBytes());ByteArrayOutputStream bo = new ByteArrayOutputStream();int ch = 0;while((ch=bi.read())!=-1){bo.write(ch);}System.out.println(bo.toString()); // 获取内存缓冲区的数据}} /* Output:abc你好*///:~
其中字符串缓冲字符流StringReader和StringWriter的使用和其非常类型,只不过不是缓冲数组,而会字符串缓冲。下面进行介绍一下:
import java.io.IOException;import java.io.StringReader;import java.io.StringWriter;public class StringIO {public static void main(String[] args) throws IOException {StringReader sr = new StringReader("abc你好");StringWriter sw = new StringWriter();char[] chs = new char[4];int length = 0;while((length=sr.read(chs))!=-1){sw.write(chs, 0, length);}System.out.println(sw.toString());}} /* Output:abc你好*///:~