磁盘 I/O 工做机制缓存
前面介绍了基本的 Java I/O 的操做接口,这些接口主要定义了如何操做数据,以及介绍了操做两种数据结构:字节和字符的方式。还有一个关键问题就是数据写到何处,其中一个主要方式就是将数据持久化到物理磁盘,下面将介绍如何将数据持久化到物理磁盘的过程。网络
咱们知道数据在磁盘的惟一最小描述就是文件,也就是说上层应用程序只能经过文件来操做磁盘上的数据,文件也是操做系统和磁盘驱动器交互的一个最小单 元。值得注意的是 Java 中一般的 File 并不表明一个真实存在的文件对象,当你经过指定一个路径描述符时,它就会返回一个表明这个路径相关联的一个虚拟对象,这个多是一个真实存在的文件或者是 一个包含多个文件的目录。为什么要这样设计?由于大部分状况下,咱们并不关心这个文件是否真的存在,而是关心这个文件到底如何操做。例如咱们手机里一般存了 几百个朋友的电话号码,可是咱们一般关心的是我有没有这个朋友的电话号码,或者这个电话号码是什么,可是这个电话号码到底能不能打通,咱们并非时时刻刻 都去检查,而只有在真正要给他打电话时才会看这个电话能不能用。也就是使用这个电话记录要比打这个电话的次数多不少。数据结构
什么时候真正会要检查一个文件存不存?就是在真正要读取这个文件时,例如 FileInputStream 类都是操做一个文件的接口,注意到在建立一个 FileInputStream 对象时,会建立一个 FileDescriptor 对象,其实这个对象就是真正表明一个存在的文件对象的描述,当咱们在操做一个文件对象时能够经过 getFD() 方法获取真正操做的与底层操做系统关联的文件描述。例如能够调用 FileDescriptor.sync() 方法将操做系统缓存中的数据强制刷新到物理磁盘中。函数
下面以清单 1 的程序为例,介绍下如何从磁盘读取一段文本字符。以下图所示:工具
图 7. 从磁盘读取文件 操作系统
当传入一个文件路径,将会根据这个路径建立一个 File 对象来标识这个文件,而后将会根据这个 File 对象建立真正读取文件的操做对象,这时将会真正建立一个关联真实存在的磁盘文件的文件描述符 FileDescriptor,经过这个对象能够直接控制这个磁盘文件。因为咱们须要读取的是字符格式,因此须要 StreamDecoder 类将 byte 解码为 char 格式,至于如何从磁盘驱动器上读取一段数据,由操做系统帮咱们完成。至于操做系统是如何将数据持久化到磁盘以及如何创建数据结构须要根据当前操做系统使用 何种文件系统来回答,至于文件系统的相关细节能够参考另外的文章。设计
Java Socket 的工做机制code
Socket 这个概念没有对应到一个具体的实体,它是描述计算机之间完成相互通讯一种抽象功能。打个比方,能够把 Socket 比做为两个城市之间的交通工具,有了它,就能够在城市之间来回穿梭了。交通工具备多种,每种交通工具也有相应的交通规则。Socket 也同样,也有多种。大部分状况下咱们使用的都是基于 TCP/IP 的流套接字,它是一种稳定的通讯协议。对象
下图是典型的基于 Socket 的通讯的场景:接口
图 8.Socket 通讯示例
主机 A 的应用程序要能和主机 B 的应用程序通讯,必须经过 Socket 创建链接,而创建 Socket 链接必须须要底层 TCP/IP 协议来创建 TCP 链接。创建 TCP 链接须要底层 IP 协议来寻址网络中的主机。咱们知道网络层使用的 IP 协议能够帮助咱们根据 IP 地址来找到目标主机,可是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通讯就要经过 TCP 或 UPD 的地址也就是端口号来指定。这样就能够经过一个 Socket 实例惟一表明一个主机上的一个应用程序的通讯链路了。
创建通讯链路
当客户端要与服务端通讯,客户端首先要建立一个 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 部分将介绍避免这种状况。