一、java.io包概览
Java IO包主要可以分为如下4类:
基于字节操作的I/O接口:InputStream和OutputStream。
基于字符操作的I/O接口:Writer和Reader
基于磁盘操作的I/O接口:File。
基于网络操作的I/O接口:Socket(没在IO包下)。
前2种区分I/O操作中数据的格式,后2种主要是数据传输的方式。
二、基于字节的I/O操作
1、 InputStream介绍
InputStream是所有基于字节格式处理读数据的父类,其类层次结构如及大致介绍下:
1.1 ByteArrayInputStream
包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。 关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。实例代码如下:
1 public static void main(String[] args) throws IOException { 2 3 // TODO Auto-generated method stub 4 5 byte [] buf = {0x3A,0x22,0x33}; 6 7 InputStream is = new ByteArrayInputStream(buf); 8 9 int c = is.read(); 10 11 while(c != -1){ 12 13 System.out.println(c); 14 15 c = is.read(); 16 17 } 18 19 is.close(); 20 21 is.reset(); 22 23 System.out.println(is.read()); 24 25 26 27 }
1.2 FileInputStram
利用此类可以以字节方式读取文件内容,一般用于读取二进制文件,若读取文本文件,考虑使用FileReader,示例代码如下:
File file = new File("test.txt"); InputStream inputStream = new FileInputStream(file); byte [] content = new byte[10]; StringBuffer sb = new StringBuffer(); while(inputStream.read(content) != -1){ sb.append(new String(content)); content = new byte[10]; } inputStream.close(); System.out.println(sb.toString());
1.3 FilterInputStream
封装其它的输入流,并为它们提供额外的功能,它的常用的子类有BufferedInputStream和DataInputStream。BufferedInputStream的作用就是为“输入流提供缓冲功能,以及mark()和reset()功能”。
DataInputStream 是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。示例代码如下:
BufferedInputStream in = new BufferedInputStream(new FileInputStream("test.txt")); byte [] data = new byte[10]; StringBuffer sb = new StringBuffer(); while(in.read(data) != -1){ sb.append(new String(data)); data = new byte[10]; } in.close(); System.out.println(sb.toString());
2. OutputStream介绍
OutputStream是所有基于字节格式处理写数据的父类,其类层次结构如及大致介绍下:
2.1 ByteArrayOutputStream
此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()和 toString()获取数据,关闭后仍可使用。由于这个原因,ByteArrayOutputStream常用于存储数据以用于一次写入。
1 public static void main(String[] args) throws IOException { 2 // TODO Auto-generated method stub 3 ByteArrayOutputStream os = new ByteArrayOutputStream(); 4 Random random = new Random(); 5 for(int i=0;i<5;i++){ 6 int a = random.nextInt(999); 7 os.write(a); 8 System.out.println(a); 9 } 10 System.out.println(os.toByteArray().length); //length:5 11 for(int i=0;i<os.toByteArray().length;i++){ 12 System.out.println(os.toByteArray()[i]); 13 } 14 }
2.2 FileOutputStream
A file output stream is an output stream for writing data to a File or to a FileDescriptor. Whether or not a file is available or may be created depends upon the underlying platform. Some platforms, in particular, allow a file to be opened for writing by only one FileOutputStream (or other file-writing object) at a time. In such situations the constructors in this class will fail if the file involved is already open. 示例代码如下:
String content = " 今天没吃药 明天再吃药"; OutputStream os = new FileOutputStream("test_write.txt",true); os.write(content.getBytes()); os.close(); System.out.println("dddd");
2.3 FilterOutputStream
This class is the superclass of all classes that filter output streams. These streams sit on top of an already existing output stream (the underlying output stream) which it uses as its basic sink of data, but possibly transforming the data along the way or providing additional functionality.
The class FilterOutputStream itself simply overrides all methods of OutputStream with versions that pass all requests to the underlying output stream. Subclasses of FilterOutputStream may further override some of these methods as well as provide additional methods and fields.
与FilterInputStream用法类似,示例代码如下:
String data = "新年好 红包呢"; File file = new File("filter_output_stream.txt"); BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file, true)); os.write(data.getBytes("UTF-8")); os.close(); System.out.println("write over!!!");
三、基于字符的I/O操作
不管是磁盘还是网络操作,最小的存储单元都是字节, 而不是字符,所以I/O操作都是字节,而不是字符。但由于程序中通常操作的都是字符,为了方便使用,java提供了字符接口。
但字节到字符必须经过转码,而编码非常耗时,而且还会经常出现乱码。
1. Reader介绍
Reader类是所有字符操作的父类,Abstract class for reading character streams. The only methods that a subclass must implement are read(char[], int, int) and close(). Most subclasses, however, will override some of the methods defined here in order to provide higher efficiency, additional functionality, or both.
StringBuffer sb = new StringBuffer(); char [] buf = new char[10]; Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt"),"UTF-8"));//一般会包一层BufferedReader以提升性能 while(reader.read(buf) != -1){ sb.append(buf); } reader.close(); System.out.println(sb.toString());
2. Writer介绍
Abstract class for writing to character streams. The only methods that a subclass must implement are write(char[], int, int), flush(), and close(). Most subclasses, however, will override some of the methods defined here in order to provide higher efficiency, additional functionality, or both.
String data = "你好周杰伦 快来快来 顶顶顶顶"; Writer writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream("output_stream_writer.txt",true),"UTF-8")); writer.write(data); System.out.println("write over"); writer.close();
3. 字节与字符的转化接口
数据持久化或网络传输都是以字节进行的,所以必须有从字符到字节或字节到字符的转化接口,从字符到字节需要转化,其过程为:
InputStreamReader类是从字节到字符的转化的桥梁,从InputStream到Reader要制定编码字符集,否则会用系统某人字符集,很可能出现乱码问题。StreamDecoder是完成从字节到字符解码功能实现类。
FileReader类就是按上述方式读取文件内容的,FileReader继承自InputStreamReader类,实际上是读取文件,然后通过StreamDecoder解码成char,此处用的默认字符编码集。
写入过程类似,
通过OutputStreamWriter类 完成从字符到字节的编码过程,有StreamEncoder完成编码过程。
四、磁盘I/O机制分析
读取和写入文件需调用操作系统接口,分别对应read和write2个系统调用,而只要是系统调用,就可能存在内核地址空间和用户地址空间转换的问题,因为操作系统需保护自身运行的安全性。
但内核调用时存在数据从内核复制到用户空间的问题,若遇到非常耗时的I/O操作,性能会很差。
因此,操作系统在内核使用缓存机制,以减少I/O相应时间。下面是常见的文件访问方式。
1. 标准文件访问方式
read时,OS检查内核缓存是否有,有则返回缓存,无则从磁盘读取,并缓存。
write时,OS将用户空间数据复制到内核地址缓存,这是对用户程序来说,write已经完成,但写入磁盘的时机由OS决定,除非显示调用sync命令。
2. 直接I/O方式
直接I/O方式指应用程序直接访问磁盘数据,而不经过OS内核缓冲区,目的是为了减少从内核缓冲区到用户空间数据复制的时间,此种方式常用于数据库。
因为数据库明确知道应该缓存哪些数据,且可对热点数据提前预加载到内存,负面影响是若数据不在应用缓存中,则会直接去磁盘操作,此时耗时较多。
因此,直接I/O通常会跟异步I/O结合使用。
3. 同步访问文件的方式
数据写入和读取都是同步的,只有数据成功写到磁盘时才返回写成功,性能较差,用于对数据安全性要求较高的场合,且硬件通常是定制的。
4. 异步访问方式
发出访问数据请求后,线程会接着处理其他事情,而不是堵塞等待,请求数据返回后才处理下面的操作。
此方式明显提高应用程序的效率,但不会改变访问文件的效率。
5. 内存映射方式
将内存和硬盘映射,当访问内存时,转化到访问磁盘,目的是减少内核到用户空间的数据复制,因为此时这2个空间的数据是共享的。
五、 File和FileDescriptor类
1. File类
Java中的File类并不代表一个真实存在的文件对象,当创建一个File对象时,返回代表这个路径的虚拟对象
该路径本身可能是个文件或文件夹,不会检查改路径是否存在。
2. FileDescriptor类
当真正访问文件时,会创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,通过这个对象可以直接控制磁盘文件。
File对象可以通过getFD方法获取FileDescriptor对象,FileDescriptor.sync()方法将OS缓存数据强制刷新到磁盘中。
六、Java序列化技术
一般会用通用技术存储,如JSON或XML,且尽量存储通用的数据结构。