Java I/O 工做机制(一) —— Java 的 I/O 类库的基本架构

Java 的 I/O 类库的基本架构

Java 的 I/O 操做类在包 java.io 下,有将近 80 个类。css

按数据格式分类:前端

  • 面向字节(Byte)操做的 I/O 接口:InputStream 和 OutputStream
  • 面向字符(Character)操做的 I/O 接口:Writer 和 Reader

按做用位置分类:java

  • 基于磁盘操做的 I/O 接口:File
  • 基于网络操做的 I/O 接口:Socket(不在java.io中)

1. IO数据格式

(1)面向字节:操做以8位为单位对二进制数据进行操做,不对数据进行转换。这些类都是InputStream 和 OutputStream的子类。以InputStream/OutputStream为后缀的类都是字节流,能够处理全部类型的数据。数据库

(2)面向字符:操做以字符为单位,读时将二进制数据转换为字符,写时将字符转换为二进制数据Writer 和 Reader的子类,以Writer/Reader为后缀的都是字符流。后端

硬盘上全部的文件都是以字节形式保存,字符只在内存中才会造成。即只在处理纯文本文件时,优先考虑使用字符流,除此以外都用字节流。浏览器

 

InputStream 相关类层次结构:(OutputStream相似)

 

Writer 相关类层次结构:(Reader相似)

其中:缓存

字符流:服务器

  • InputStreamReader/OutputStreamWriter 是字节流转化为字符流的桥转换器。
  • BufferReader/BufferWriter 逐行读写流,可用于较大的文本文件。是过滤流,须要用其余的节点流作参数构造对象。

字节流:网络

  • FileInputStream/FileOutputStream 文件输入输出流。
  • PipedInputStream/PipedOutputStream 管道里,线程交互时使用。
  • ObjectInputStream/ObjectOutputStream 对象流,实现对象序列化

读写操做实例:数据结构

/**
 * 使用FileReader进行读取文件,而后FileWriter写入另外一个文件
 */
@Test
public void testFileReaderAndFileWriter() throws IOException {
    FileReader fileReader = new FileReader("h:\\haha.txt");
    char[] buff = new char[512];
    StringBuffer stringBuffer = new StringBuffer();

    while (fileReader.read(buff) > 0) {
        stringBuffer.append(buff);
    }
    System.out.println(stringBuffer.toString());

    FileWriter fileWriter = new FileWriter("h:\\haha2.txt");
    fileWriter.write(stringBuffer.toString().trim());

    fileWriter.close();
    System.out.println("写入文件成功");
}
/**
 * 使用InputStreamReader进行读取文件,而后用OutputStreamWriter写入文件
 */
@Test
public void testInputStreamReader() throws IOException {
    //操做数据的方式是能够组合的,此处FileInputStream读出的字节流用InputStreamReader转化为了字符流对象
    InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("h:\\haha.txt"), "utf-8");
    char[] buff = new char[512];
    StringBuffer stringBuffer = new StringBuffer();
    while (inputStreamReader.read(buff) > 0) {
        stringBuffer.append(buff);
    }
    System.out.println(stringBuffer.toString());
  
  //写文件时,要指定写入的地方(网络或本地)、路径   OutputStreamWriter outputStreamWriter
= new OutputStreamWriter(new FileOutputStream("h:\\haha2.txt"), "utf-8");   outputStreamWriter.write(stringBuffer.toString().trim());   outputStreamWriter.close(); } @Test public void testIntputStream2() throws IOException { InputStreamReader inputStreamReader = new InputStreamReader(new StringBufferInputStream("hello world")); char[] buff = new char[512]; int n = inputStreamReader.read(buff); System.out.println(n); System.out.println(buff); }

FileReader类继承了InputStreamReader,FileReader读取文件流,经过StreamDecoder解码成char,其解码字符集使用的是默认字符集。在Java中,咱们应该使用File对象来判断某个文件是否存在,若是咱们用FileOutputStream或者FileWriter打开,那么它确定会被覆盖。

2. IO发生位置

(1) 磁盘IO工做机制

前面介绍了基本的 Java I/O 的操做接口,这些接口主要定义了如何操做数据,以及介绍了操做两种数据结构:字节和字符的方式。还有一个关键问题就是数据写到何处,其中一个主要方式就是将数据持久化到物理磁盘,下面将介绍如何将数据持久化到物理磁盘的过程。

数据在磁盘的惟一最小描述是文件,也就是说上层应用程序只能经过文件来操做磁盘上的数据,文件也是操做系统和磁盘驱动器交互的一个最小单元。值得注意的是 Java 中一般的 File 并不表明一个真实存在的文件对象,当你经过指定一个路径描述符时,它就会返回一个表明这个路径相关联的一个虚拟对象,这个多是一个真实存在的文件或者是一个包含多个文件的目录。

什么时候真正会要检查一个文件存不存?就是在真正要读取这个文件时,例如 FileInputStream 类都是操做一个文件的接口,注意到在建立一个 FileInputStream 对象时,会建立一个 FileDescriptor 对象,其实这个对象就是真正表明一个存在的文件对象的描述,当咱们在操做一个文件对象时能够经过 getFD() 方法获取真正操做的与底层操做系统关联的文件描述。例如能够调用 FileDescriptor.sync() 方法将操做系统缓存中的数据强制刷新到物理磁盘中。

从磁盘读取文件过程:

当传入一个文件路径,将会根据这个路径建立一个 File 对象来标识这个文件,而后将会根据这个 File 对象建立真正读取文件的操做对象,这时将会真正建立一个关联真实存在的磁盘文件的文件描述符 FileDescriptor,经过这个对象能够直接控制这个磁盘文件。因为咱们须要读取的是字符格式,因此须要 StreamDecoder 类将 byte 解码为 char 格式,至于如何从磁盘驱动器上读取一段数据,由操做系统帮咱们完成。

 

(2)网络IO工做机制(Socket)

Socket 描述计算机之间完成相互通讯一种抽象功能。Socket 也同样有多种,大部分状况下咱们使用的都是基于 TCP/IP 的流套接字,它是一种稳定的通讯协议。

下典型的基于 Socket 的通讯的场景:

主机 A 的应用程序要能和主机 B 的应用程序通讯,必须经过 Socket 创建链接,而创建 Socket 链接必须须要底层 TCP/IP 协议来创建 TCP 链接。创建 TCP 链接须要底层 IP 协议来寻址网络中的主机。咱们知道网络层使用的 IP 协议能够帮助咱们根据 IP 地址来找到目标主机,可是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通讯就要经过 TCP 或 UPD 的地址也就是端口号来指定。这样就能够经过一个 Socket 实例惟一表明一个主机上的一个应用程序的通讯链路了。

(TCP/UDP:找端口号,从而与应用程序通讯。IP:找主机)

创建通讯链路

当客户端要与服务端通讯,客户端首先要建立一个 Socket 实例,操做系统将为这个 Socket 实例分配一个没有被使用的本地端口号,并建立一个包含本地和远程地址和端口号的套接字数据结构,这个数据结构将一直保存在系统中直到这个链接关闭。在建立 Socket 实例的构造函数正确返回以前,将要进行 TCP 的三次握手协议,TCP 握手协议完成后,Socket 实例对象将建立完成,不然将抛出 IOException 错误。

与之对应的服务端将建立一个 ServerSocket 实例,ServerSocket 建立比较简单只要指定的端口号没有被占用,通常实例建立都会成功,同时操做系统也会为 ServerSocket 实例建立一个底层数据结构,这个数据结构中包含指定监听的端口号和包含监听地址的通配符,一般状况下都是“*”即监听全部地址。以后当调用 accept() 方法时,将进入阻塞状态,等待客户端的请求。当一个新的请求到来时,将为这个链接建立一个新的套接字数据结构,该套接字数据的信息包含的地址和端口信息正是请求源地址和端口。这个新建立的数据结构将会关联到 ServerSocket 实例的一个未完成的链接数据结构列表中,注意这时服务端与之对应的 Socket 实例并无完成建立,而要等到与客户端的三次握手完成后,这个服务端的 Socket 实例才会返回,并将这个 Socket 实例对应的数据结构从未完成列表中移到已完成列表中。因此 ServerSocket 所关联的列表中每一个数据结构,都表明与一个客户端的创建的 TCP 链接

数据传输

传输数据是咱们创建链接的主要目的,如何经过 Socket 传输数据:

当链接已经创建成功,服务端和客户端都会拥有一个 Socket 实例,每一个 Socket 实例都有一个 InputStream 和 OutputStream,正是经过这两个对象来交换数据。同时咱们也知道网络 I/O 都是以字节流传输的。当 Socket 对象建立时,操做系统将会为 InputStream 和 OutputStream 分别分配必定大小的缓冲区,数据的写入和读取都是经过这个缓存区完成的。写入端将数据写到 OutputStream 对应的 SendQ 队列中,当队列填满时,数据将被发送到另外一端 InputStream 的 RecvQ 队列中,若是这时 RecvQ 已经满了,那么 OutputStream 的 write 方法将会阻塞直到 RecvQ 队列有足够的空间容纳 SendQ 发送的数据。值得特别注意的是,这个缓存区的大小以及写入端的速度和读取端的速度很是影响这个链接的数据传输效率,因为可能会发生阻塞,因此网络 I/O 与磁盘 I/O 在数据的写入和读取还要有一个协调的过程,若是两边同时传送数据时可能会产生死锁,在后面 NIO 部分将介绍避免这种状况。

3. IO调优

提高磁盘 I/O 性能一般的方法:

  1. 增长缓存,减小磁盘访问次数
  2. 优化磁盘的管理系统,设计最优的磁盘访问策略,以及磁盘的寻址策略(在底层操做系统层面考虑)
  3. 设计合理的磁盘存储数据块,以及访问这些数据块的策略(在应用层面考虑)。如咱们能够给存放的数据设计索引,经过寻址索引来加快和减小磁盘的访问,还有能够采用异步和非阻塞的方式加快磁盘的访问效率。
  4. 应用合理的 RAID 策略提高磁盘 IO

网络 I/O 优化一般有一些基本处理原则:

  1. 减小网络交互次数:1)在须要网络交互的两端会设置缓存,好比 Oracle 的 JDBC 驱动程序提供了对查询的 SQL 结果的缓存,在客户端和数据库端都有,能够有效的减小对数据库的访问。2)合并访问请求:如在查询数据库时,咱们要查 10 个 id,我能够每次查一个 id,也能够一次查 10 个 id。再好比在访问一个页面时经过会有多个 js 或 css 的文件,咱们能够将多个 js 文件合并在一个 HTTP 连接中,每一个文件用逗号隔开,而后发送到后端 Web 服务器根据这个 URL 连接,再拆分出各个文件,而后打包再一并发回给前端浏览器。这些都是经常使用的减小网络 I/O 的办法。
  2. 减小网络传输数据量的大小:减小网络数据量的办法一般是将数据压缩后再传输,如 HTTP 请求中,一般 Web 服务器将请求的 Web 页面 gzip 压缩后在传输给浏览器。还有就是经过设计简单的协议,尽可能经过读取协议头来获取有用的价值信息。
  3. 尽可能减小编码:一般在网络 I/O 中数据传输都是以字节形式的,也就是一般要序列化。可是咱们发送要传输的数据都是字符形式的,从字符到字节必须编码。可是这个编码过程是比较耗时的,因此在要通过网络 I/O 传输时,尽可能直接以字节形式发送。也就是尽可能提早将字符转化为字节,或者减小字符到字节的转化过程。
  4. 根据应用场景设计合适的交互方式:所谓的交互场景主要包括同步与异步、阻塞与非阻塞方式。

 

参考连接:https://www.ibm.com/developerworks/cn/java/j-lo-javaio/

相关文章
相关标签/搜索