深入分析 Java I/O 工作机制
作者: egg
博客: http://blog.csdn.net/zhangerqing
论坛: http://www.qtlife.net (随便建了一个,方便大家一起交流!)
IO 是 Java 及众多编程语言很重要的一块,同时很多程序的瓶颈和耗时操作也都在 IO 这块。所以能够很好的解决 IO 问题对提高程序性能有很大的帮助!本章我们将要系统的对 Java IO 做个分析,通过理论加实践,希望读者朋友们能真正彻底的理解并且掌握了它。本章系 Java 之美 [ 从菜鸟到高手演变 ] 系列 之 Java IO ,通过本章的学习,读者朋友们能基本了解到关于 IO 的很多知识。日后加以理解、分析、在项目中实践,定能灵活运用!
一、简介
IO 操作面临很多问题,信息量的巨大,网络的环境等等,因为 IO 不仅仅是对本地文件、目录的操作,有时对二进制流、还有一部分是网络方面的资源,所以多种原因直接造成 IO 操作无疑是耗时且复杂多变的。 Java 对 IO 的支持是个不断的演变过程,经过了很多的优化,直到 JDK1.4 以后,才趋于稳定,在 JDK1.4 中,加入了 nio 类,解决了很多性能问题,虽然我们有足够的理由不去了解关于 Java IO 以前的情况,但是为了学好现在的类,我们还是打算去研究下,通过掌握类的优化情况来彻底理解 IO 的机制! Java IO 主要主要在 java.io 包下,分为四大块近 80 个类:
1 、基于字节操作的 I/O 接口: InputStream 和 OutputStream
2 、基于字符操作的 I/O 接口: Writer 和 Reader
3 、基于磁盘操作的 I/O 接口: File
4 、基于网络操作的 I/O 接口: Socket (不在 java.io 包下)
影响 IO 性能的无非就是两大因素:数据的格式及存储的方式,前两类主要是数据格式方面的,后两个类是存储方式方面的:本地和网络。所以策划好这两个方面的活动,有助于我们合理使用 IO 。
二、基于字节的 I/O 操作 (InputStream 和 OutputStream)
我们先来看看类图:
图1
图2
二者类似,我只详细讲解 InputStream 类, OutputStream 留给大家自己去学习。 InputStream 类是个抽象类,里面核心的方法就是 read()、read(byte b[])、read(byte b[], int off, int len),这三个方法是用于读取数据的底层的方法,他们可以用来读取一下这些类型的数据:
A. 字节数组
B. String 对象
C. 文件
D. 管道,从一端进入,从另一端输出
E. 流
F. internet 资源
每一种数据源都有相应的 InputStream 子类,因为 InputStream 是个处于顶层的类,用来处理各种数据源的类都继承了 InputStream 类,我们来看看这些类:
ByteArrayInputStream:处理字节数组的类,允许将内存的缓冲区当做 InputStream 使用。
StringBufferInputStream:将 String 转换成 InputStream ,内部实现用的是 StringBuffer 。
FileInputStream:从文件中读取数据。
PipedInputStream :用于从管道中读取数据。
SequenceInputStream :将多个流对象转化成一个 InputStream 。
FilterInputStream :装饰器类,为其它 InputStream 类提供功能。
做过关于 IO 操作的读者知道,我们很少单独使用哪个类来实现 IO 操作,平时都是几个类合起来使用,这其实体现了一种 装饰器模式 (详见: http://blog.csdn.net/zhangerqing )的思想,在后面的分析中我们会详细的分析。从上面的图 1 中我们可以看出, FilterInputStream 虽说是 Inputstream 的子类,但它依然是 BufferedInputStream 、 DataInputStream 、 LineNumberInputStream 、 PushbackInputStream 类的父类,这四个类分别提供了最贴近我们程序员使用的方法,如: readInt() 、readInt()、readInt()等等。对于 IO 操作,不管是磁盘还是网络,最终都是对字节的操作,而我们平时写的程序都是字符形式的,所以在传输的过程中需要进行转换。在 字符到字节 的转换过程中,我们需要用到一个类: InputStreamReader 。
三、基于字符的 I/O 操作 (Writer 和 Reader)
图3
图4
Writer 和 Reader 操作的目的就是操作字符和不是字节,和 InputStream 和 OutputStream 配合增加 IO 效果。通过 InputStreamReader 和 OutputStreamReader 可以进行字节和字符的转换,设计 Writer 和 Reader 的目的是国际化,使 IO 操作支持 16 位的 Unicode 。 我把它们单独的画出来,因为要是全画的话,太大了放不下,有兴趣的 TX 可以在 rational rose 中导入其带的 JDK 类图看看,很过瘾的!
四、基于磁盘的 I/O 操作 (File)
五、基于网络的 I/O 操作 (Socket)
六、 NIO
四-六部分由于时间关系,还没有整理完,后续会补出来!
七、经典 IO 操作
1 、缓冲输入文件。
import java.io.BufferedReader; import java.io.FileReader; public class InputStreamTest { public static String read(String filename) throws Exception { BufferedReader br = new BufferedReader(new FileReader(filename)); String s; StringBuffer sb = new StringBuffer(); while ((s = br.readLine()) != null) { sb.append(s + " "); } br.close(); return sb.toString(); } public static void main(String[] args) throws Exception { System.out.println(read("src/InputStreamTest.java")); } }
这段代码是从磁盘读入 InputStreamTest.java 文件,然后转换成字符串。输出就是将源文件原样输出。
2 、从内存中读取。
import java.io.StringReader; public class MemoryInput { public static void main(String[] args) throws Exception { StringReader in = new StringReader( InputStreamTest.read("src/MemoryInput.java")); int c; while ((c = in.read()) != -1) System.out.println((char) c); } }
read 返回的是 int 类型的数据,所以在输出语句中用 char 做了强类型转换。该程序将一个一个的输出字符。
3 、基本的文件输出。
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.PrintWriter; import java.io.StringReader; public class BasicFileOutput { static String file = "basie.out"; public static void main(String[] args) throws Exception { BufferedReader in = new BufferedReader(new StringReader( InputStreamTest.read("src/BasicFileOutput.java"))); PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( file))); int lineCount = 1; String s; while ((s = in.readLine()) != null) { out.println(lineCount++ + ": " + s); } out.close(); System.out.println(InputStreamTest.read(file)); } }
输出:
1: import java.io.BufferedReader;
2: import java.io.BufferedWriter;
3: import java.io.FileWriter;
…
4 、 RandomAccessFile
RandomAccessFile 被我们称为 ”自我独立的类”,因为它独立于我们前面说的 IO 类,与 InputStream 和 OutputStream 没什么关系,除了实现了 DataOutput, DataInput两个接口外。所有方法都是重新编写,而且很多都是 native 方法,我们来看个例子,了解下这个类:
5 、管道流
八、标准 I/O
就是我们最原始的使用的从控制台输入或者输出的那些类和方法,如 System.in 、 System.out 等。
public class StandardIO { public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String s; while ((s = in.readLine()) != null && s.length() != 0) System.out.println(s); } }
System.in 返回的是未经包装的 InputStream 对象,所以需要进行装饰,经 InputStreamReader转换为 Reader 对象,放入 BufferedReader 的构造方法中。除此之外, System.out 和 System.err 都是直接的 PriintStream 对象,可直接使用。我们也可以使用 java.util 包下的 Scanner 类来代替上述程序:
public class StandardIO { public static void main(String[] args) throws IOException { Scanner in = new Scanner(System.in); String s; while((s = in.next()) != null && s.length() != 0){ System.out.println(s); } } }
九、性能分析及总结
1 、一个文件读写工具类。
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; /** * 一个非常实用的文件操作类 . 2012-12-19 * * @author Bruce Eckel , edited by erqing * */ public class TextFile extends ArrayList<String> { private static final long serialVersionUID = -1942855619975438512L; // Read a file as a String public static String read(String filename) { StringBuilder sb = new StringBuilder(); try { BufferedReader in = new BufferedReader(new FileReader(new File( filename).getAbsoluteFile())); String s; try { while ((s = in.readLine()) != null) { sb.append(s); sb.append(" "); } } finally { in.close(); } } catch (IOException e) { throw new RuntimeException(e); } return sb.toString(); } // Write a single file in one method call public static void write(String fileName, String text) { try { PrintWriter out = new PrintWriter( new File(fileName).getAbsoluteFile()); try { out.print(text); } finally { out.close(); } } catch (IOException e) { throw new RuntimeException(e); } } // Read a file,spilt by any regular expression public TextFile(String fileName, String splitter) { super(Arrays.asList(read(fileName).split(splitter))); if (get(0).equals("")) remove(0); } // Normally read by lines public TextFile(String fileName) { this(fileName, " "); } public void write(String fileName) { try { PrintWriter out = new PrintWriter( new File(fileName).getAbsoluteFile()); try { for (String item : this) out.println(item); } finally { out.close(); } } catch (IOException e) { throw new RuntimeException(e); } } // test,I have generated a file named data.d at the root public static void main(String[] args) { /* read() test */ System.out.println(read("data.d")); // testing is OK! /* write() test */ write("out.d", "helloworld egg"); // testing is OK! /* constractor test */ TextFile tf = new TextFile("data.d"); // testing is OK! } }
2 、读取二进制文件。
import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * to read the binary file * * @author erqing * */ public class BinaryFile { /* the parametre is a file */ public static byte[] read(File file) throws IOException { BufferedInputStream bf = new BufferedInputStream(new FileInputStream( file)); try { byte[] data = new byte[bf.available()]; bf.read(data); return data; } finally { bf.close(); } } /* the param is the path of a file */ public static byte[] read(String file) throws IOException { return read(new File(file).getAbsoluteFile()); } }
本章节未完,因为最近较忙,所以先发出一部分,后续会将缺少的补齐!