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

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

按数据格式分类:

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

按作用位置分类:

  • 基于磁盘操作的 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/

Java I/O 工作机制(一) —— Java 的 I/O 类库的基本架构的更多相关文章

  1. 2 深入分析 Java IO的工作机制(一)

    大部分Web应用系统的瓶颈都是I/O瓶颈 2.1 Java的I/O类库的基本架构 Java的I/O操作类在包java.io下,大概有将近80个类,这些类大概可以分成如下4组. 基于字节操作的I/O接口 ...

  2. 深入分析Java I/O 工作机制

    前言 :  I/O 问题是Web 应用中所面临的主要问题之一.而且是任何编程语言都无法回避的问题,是整个人机交互的核心. java 的I/O类操作在java.io 包下,将近80个子类, 大概可以分成 ...

  3. java I/O工作机制

    java I/O 的基本架构: 1:基于字节操作的I/O接口 InputStream OutputStream 2:基于字符操作的I/O接口 Writer 和Reader 3:基于磁盘操作的I/O接口 ...

  4. Java I/O 工作机制(二) —— Java 的 I/O 的交互方式分析

    简介: BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善.  ...

  5. Java Web ClassLoader工作机制

    一.ClassLoader的作用: 1.类加载机制:父优先的等级加载机制 2.类加载过程 3.将Class字节码重新解析成JVM统一要求的对象格式 二.ClassLoader常用方法 1.define ...

  6. Java Socket 的工作机制

    转载,请加上原文链接: 目录 socket 对象的创建时间 socket 通信可能会造成死锁 socket 对象的创建时间 这里需要一点TCP的知识, TCP状态分析请看 --> TCP转态转换 ...

  7. 2 深入分析 Java IO的工作机制(二)

    2.5 I/O调优 下面总结一些磁盘I/O和网络I/O的常用优化技巧. 2.5.1 磁盘I/O优化 1. 性能检测 应用程序通常都需要访问磁盘来读取数据,而磁盘I/O通常都很耗时,要判断I/O是否是一 ...

  8. 深入分析 Java I/O 的工作机制--转载

    Java 的 I/O 类库的基本架构 I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取和交换信息的主要渠道.在当今这个数据大爆炸时代, ...

  9. 深入分析 Java I/O 的工作机制

    I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这个海量数据时代,数据在网络中随处流动.这个流动的过程中都涉及到 I/O 问题,可以说大部分 Web 应用系统的瓶颈都是 ...

随机推荐

  1. 4 Values whose Sum is 0(枚举+二分)

    The SUM problem can be formulated as follows: given four lists A, B, C, D of integer values, compute ...

  2. Codeforces 852D

    数目规模大,尝试转换为判定性问题,那么E就不影响到网络流的复杂度了 SPFA码歪了,WA了好几发气死了 #include<bits/stdc++.h> using namespace st ...

  3. Flask&&人工智能AI -- 8 HTML5+ 初识,HBuilder,夜神模拟器,Webview

    昨日内容回顾 1.增删改查: 增: db.collections.insert({a:1}) // 官方不推荐了 db.collections.insertMany([{a:1},{b:1}]) in ...

  4. django文件配置

    先是 staticfile 文件配制 STATTCFILE=(os.path.join(BASE_DIR,'static'),) 然后是数据库配置  : DATABASES = { 'default' ...

  5. http协议&接口规范&接口测试入门

    http协议 请求: 请求行:请求方法.url(协议名://ip;端口/工程名/资源路径).协议版本 请求头 :键值对 请求正文 响应: 响应行:协议版本.响应状态码.响应状态码描述 响应头 :键值对 ...

  6. Jenkins安全配置详解

    一.进入安全配置界面 首页后点击进入系统管理(Manage Jenkins) ——下拉下方看到安全配置(Configure Global Security) ——进入安全配置界面 二,详解安全配置的选 ...

  7. Jenkins安装过程

    1.安装环境 配置java环境 安装Tomcat 2.将Jenkins.war 包放入Tomcat的webapps目录 3.启动tomcat后,tomcat会解压war包,生成一个jenkins文件夹 ...

  8. java——newInstance()方法和new关键字

    https://www.cnblogs.com/liuyanmin/p/5146557.html 这两个都可以创建一个对象,那么这样个东西有什么不一样呢?什么时候用new,什么时候用newInstan ...

  9. Jmeter录制pc脚本

    1.打开jmeter后可以看到左边窗口有个“测试计划”和“工作台”,右键“测试计划”,添加 Threads(Users) →线程组,再右键 线程组→添加 配置元件→Http请求默认值 Http请求默认 ...

  10. python_字典 学习

    一.创建字典(关联数组或hash表) 字典由键(key)和对应的值(values)组成. 代码: dic = { ‘ name‘:1 , ‘ zhang ’:2 , ‘ age‘ :3 , ‘ sex ...