最近再看I/O这一块,故作为总结记录于此。JDK1.4引入NIO后,原来的I/O方法都基于NIO进行了优化,提高了性能。I/O操作类都在java.io下,大概将近80个,大致可以分为4类:
- 基于字节操作的I/O接口:以InputStream和OutputStream为基类,也是I/O操作的基础。
- 基于字符操作的I/O接口:以Reader和Writer为基类,字符的读写是基于字节进行的,中间进行了转换。
- 基于磁盘操作的I/O接口:主要是File,代表目录下的所有文件。
- 基于网络操作的I/O接口:主要是Socket,实现网络数据的传输。
本文大致总结一下基于字节和字符的I/O操作,主要理清JAVA I/O中的类关系。
摘自《Java编程思想》:类库中常使用“流”这个抽象的概念,代表任何有能力产出数据的数据源对象或有能力接收数据的接收端对象。其屏蔽了I/O设备中数据处理的细节。I/O类分为输入和输出两类。通过 继承,任何InputStream或Reader的派生类都含有read()方法,用于读取单个字节或字符,任何OutputStream或Writer的派生类都含有write()方法,用于写单个字节或字符。通常不会使用单一的类创建流对象,而是通过叠合多个对象提供期望的功能(即采用装饰器模式)。
一、基于字节的I/O操作
1. InputStream类型
InputStream的作用表示那些从不同数据源产生输入的类,即其派生类多是不同数据源对应的流对象。如下:
ByteArrayInputStream:从内存缓冲区读取字节数组
FileInputStream:从文件中读取字节,其构造参数可以是文件名、File对象或FileDescriptor
ObjectInputStream:主要用于反序列化,读取基本数据类型或对象
PipedInputStream:产生用于写入相关PipedOutputStream的数据,实现“管道化”概念,多用于多线程中。
FilterInputStream:作为装饰器类,其子类与上述不同流对象叠合使用,以控制特定输入流。
其中,FilterInputStream的子类通过添加属性或有用的接口控制字节输入流,其构造函数为InputStream,常见的几个如下:
DataInputStream:与DataOutputStream搭配使用,读取基本类型数据及String对象。
BufferdInputStream:使用缓冲区的概念,避免每次都进行实际读操作,提升I/O性能。(不是减少磁盘IO操作次数(这个OS已经帮我们做了),而是通过减少系统调用次数来提高性能的)
InflaterInputStream:其子类GZIPInputStream和ZipInputStream可以读取GZIP和ZIP格式的数据。
2. OutputStream类型
与InputStream相对应,OutputStream的作用表示将数据写入不同的数据源,常用的输出流对象如下:
ByteArrayOutputStream:在内存中创建缓冲区,写入字节数组
FileOutputStream:将字节数据写入文件中,其构造参数可以是文件名、File对象或FileDescriptor
ObjectOutputStream:主要用于序列化,作用于基本数据类型或对象
PipedOutputStream:任何写入其中的数据,都会自动作为相关PipedInputStream的输出,实现“管道化”概念,多用于多线程中。
FilterOutputStream:作为装饰器类,其子类与上述不同流对象叠合使用,以控制特定输出流。
其中,FilterOutputStream的子类通过添加属性或有用的接口控制字节输入流,其构造函数为InputStream,常见的几个如下:
DataOutputStream:与DataInputStream搭配使用,写入基本类型数据及String对象。
PrintStream:用于格式化输出显示。
BufferdOutputStream:使用缓冲区的概念,避免每次都进行实际写操作,提升I/O性能。
DeflaterOutputStream:其子类GZIPOutputStream和ZipOutputStream可以写GZIP和ZIP格式的数据。
二、基于字符的I/O操作
不管是磁盘还是网络传输,数据处理的最小单元都是字节,而不是字符。故所有I/O操作的都是字节而不是字符。为了方便引入了字符操作,其中涉及字节到字符的转换适配,InputStreamReader可以把InputStream转为Reader,OutputStreamWriter可以把OutputStream转为Writer。对上述按字节操作的流对象,可以采用FilterInputStream和FilterOutputStream的装饰器子类控制流。Reader和Writer沿用相似的思想,但不完全相同。
1. Reader类型
继承自Reader类的,字符型数据来源常用类,如下:
InputStreamReader:字节与字符适配器,子类包含FileReader(以字符形式读取文件)
CharArrayReader:读取字符数组
StringReader:数据源是字符串
BufferedReader:读取字符输入流,并进行缓存,常用法:BufferedReader in = new BufferedReader(new FileReader("foo.in")); 表示采用缓存的方式从文件读取数据
PipedReader:管道形式读取字符
FilterReader:对Reader装饰,直接使用的不多,如PushbackReader
2. Writer类型
继承自Writer类的,字符型数据来源常用类,如下:
OutputStreamReader:字节与字符适配器,子类包含FileWriter(以字符形式写文件)
CharArrayWriter:写字符数组
StringWriter:内部有StringBuffer,用于缓存构造字符串
BufferedWriter:字符输出流,常用法:PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out"))); 表示将数据格式化并用缓存的方式写入文件
PipedWriter:管道形式输出字符
FilterWriter:对Writer装饰,如XMLWriter
三、自我独立的类RandomAccessFile
该类可以随机访问文件,实现了DataOutput, DataInput,不是InputStream或OutputStream继承层次结构的一部分。与其他I/O类本质有所不同,可以在一个文件内向前或向后移动。
工作方式类似与DataOutputStream和 DataInputStream,用法如下:
RandomAccessFile randomAccessFile = new RandomAccessFile("data.dat", "rw")
其中,r代表读,w代表写。
四、常用实例
1. 缓存输入文件
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * 缓存输入文件,防止频繁与文件交互 * 1.采用装饰器模式,BufferedReader从FileReader中读取字符,FileReader为字符数据源 * 2.FileReader继承InputStreamReader,实例化一个FileInputStream对象作为字节数据源, * 3.InputStreamReader继承Reader,包含StreamDecoder,将字节数据转换为字符;编码格式没有指定时采用默认编码。 * 4.Reader可以实现对FileInputStream加锁*/ public class BufferedInputFile { public static String read(String filename) throws IOException { BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); String s; StringBuilder sb = new StringBuilder(); while((s = bufferedReader.readLine()) != null) { sb.append(s + " "); } bufferedReader.close(); return sb.toString(); } public static void main(String[] args) throws IOException { System.out.println(read("src/com/test/io/BufferedInputFile.java")); } }
//输出类文件到控制台
2.从内存输入
import java.io.IOException; import java.io.StringReader; /** * 将文件读入内存 * 具体形式:new StringReader(new BufferdReader(new FileReader(filename))) * 通过缓存读文件,防止每读一个字节,都与文件直接交互*/ public class MemoryINput { static String filename = "src/com/test/io/BufferedInputFile.java"; public static void main(String[] args) throws IOException{ StringReader in = new StringReader(BufferedInputFile.read(filename)); int c; while((c = in.read()) != -1) { System.out.println((char)c); } } }
3.格式化内存输入
import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; /** * 格式化的内存输入 * 1.in.readByte()读取字节,无法判断字节是否有效合法,因此无法判断结尾,报java.io.EOFException * 2.采用available()方法预估还有多少字节可存取*/ public class FormattedMemoryInput { public static void main(String[] args) throws IOException { DataInputStream in = new DataInputStream( new ByteArrayInputStream(BufferedInputFile.read("src/com/test/io/BufferedInputFile.java").getBytes())); // byte c; // while((c = in.readByte()) !=-1) { // System.out.print((char) c); // } while(in.available() != 0) { System.out.print((char) in.readByte()); } } }
4.基本文件输出
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; /** * 基本文件输出 * 1.先利用BufferedInputFile和StringReader将数据读到内存,记住输入流已经关闭 * 2.new PrintWriter(new BufferedWriter(new FileWriter(outfile)))输出字符到文件 * 注意,此处使用BufferedWriter进行缓冲,防止每个字节都与文件交互 * 3.文本文件输出快捷方式 PrintWriter out = new PrintWriter(outfile); * 底层实现了缓存new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName)))*/ public class BasicFIleOutput { static String filename = "src/com/test/io/BufferedInputFile.java"; static String outfile = "BasicFIleOutput.out"; public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new StringReader(BufferedInputFile.read(filename))); // PrintWriter out = new PrintWriter(new FileWriter(outfile)); // PrintWriter out = new PrintWriter(outfile); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outfile))); int lineCount = 1; String s; while((s = in.readLine()) != null) { out.println(lineCount++ + ":" +s); } out.close(); } }
5.存储与恢复数据
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * 1.DataInputStream能从DataOutputStream中准确读取数据 * 2.读数据时,必须知道数据精确的位置,否则会报错 * 3.writeUTF与readUTF采用UTF-8的变体进行编码 * */ public class StoringAndRecoveringData { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt"))); out.writeDouble(3.12159); out.writeUTF("this is pi"); out.writeDouble(1.4414); out.writeUTF("this is root of 2"); out.close(); DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("Data.txt"))); System.out.println(in.readDouble()); System.out.println(in.readUTF()); System.out.println(in.readDouble()); in.close(); } }
五、总结
1. I/O操作本质是基于字节流的操作,InputStream和OutputStream对输入和输出源进行了抽象,其子类代表不同的数据源。
2. FilterInputStream和FilterOutputStream采用装饰器模式,对输入和输出流进行控制,如采用缓冲器、读基本数据类型等。
3. Reader和Writer代表基于字符的操作,底层是基于字节操作,经过InputStreamReader和OutputStreamWriter,采用StreamEncoder和StreamDecoder,将输入输出流,按Charset进行转换
4. 所有基于字节或字符的操作,基本都采用叠合的方式。如输入流采用缓存的方式从文件中读取,输出流采用缓存的方式按格式输出到文件。
5. 理清他们之间的关系,有利于了解I/O的操作过程。