《深入分析Java Web技术内幕》学习(二)

本章概览

  本书第二章突然跳到了分析Java I/O的机制,可能作者希望我们把I/O搞清楚,和第一章一样为之后的Java web机制打下基础。本章围绕了四组I/O接口:

类型接口
基于字节操作的I/O接口InputStream和OutpurStream
基于字符操作的I/O接口Writer和Reader
基于磁盘操作的I/O接口File
基于网络操作的I/O接口Socket

  
  Socket(Java.net)这个类本身不在 java.io 包下,但这里作者认为影响I/O操作只有两种影响因素:数据格式、传输方式,也就是将什么样的数据写到什么地方,所以本章的分析也由这两个因素展开。


基于字节的I/O操作接口

  输入流InputStream和输出流OutpurStream作为两个字节操作的大类,根据数据类型以及操作方式又被划分成若干个子类,每个子类各自处理不同类型。

类名父类介绍
InputStreamjava.lang.Object抽象类,是表示字节输入流的所有类的超类
ByteArrayInputStreamInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节
FileInputStreamInputStream从文件系统中的某个文件中获得输入字节
FilterInputStreamInputStream包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能
ObjectInputStreamInputStream用来实现反序列化的
PipedInputStreamInputStream管道输入流,读取管道内容。和PipedOutputStream用于多线程通信
InflaterInputStreamFilterInputStream此类为解压缩 “deflate” 压缩格式的数据实现流过滤器
BufferedInputStreamFilterInputStream为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力
DataInputStreamFilterInputStream数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型
ZipInputStreamInflaterInputStream为读取 ZIP 文件格式的文件实现输入流过滤器

  
(这里有一点问题,书中的类层次结构还提到了一个SocketInputStream,但是我在api中没有找到这个类,包括对应的SocketOutputStream也没有找到)
   

类名父类介绍
OutputStreamjava.lang.Object抽象类,是表示输出字节流的所有类的超类
ByteArrayOutputStreamOutputStream一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长
FileOutputStreamOutputStream用于将数据写入 File 或 FileDescriptor 的输出流
FilterOutputStreamOutputStream是过滤输出流的所有类的超类
ObjectOutputStreamOutputStream将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储
PipedOutputStreamOutputStream将管道输出流连接到管道输入流来创建通信管道
DataOutputStreamFilterOutputStream允许应用程序以适当方式将基本 Java 数据类型写入输出流中
PrintStreamFilterOutputStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
ZipOutputStreamInflaterOutputStream为以 ZIP 文件格式写入文件实现输出流过滤器

  
   作者提到了两点,操作数据的方式可以组合使用

OutputStream out = new BufferedOutputStream(
                new ObjectOutputStream(new FileOutputStream("fileName")));

            
流最终写到什么地方必须要指定,磁盘或网络,写到网络其实也是写文件,只不过还需要一些处理。


基于字符的I/O操作接口

  因为程序中通常操作的数据都是字符形式的,所以提供了直接对应字符的I/O接口,字符转字节的时候会涉及到编码转换。
Writer中有一个抽象方法write(char cbuf[], int off, int len),相对应的Reader中的抽象方法int read(char cbuf[], int off, int len)。这两个方法在开始之前已经用得很多了。

类名父类介绍
Writerjava.lang.Object写入字符流的抽象类。多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能
OutputStreamWriterWriter字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。
FileWriterOutputStreamWriter用来写入字符文件的便捷类。
BufferedWriterWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
StringWriterWriter一个字符流,可以用其回收在字符串缓冲区中的输出来构造字符串。
PipedWriterWriter传送的字符输出流
PrintWriterWriter向文本输出流打印对象的格式化表示形式
CharArrayWriterWriter实现一个可用作 Writer 的字符缓冲区。缓冲区会随向流中写入数据而自动增长。
Readerjava.lang.Object用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
InputStreamReaderReader字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。
FileReaderInputStreamReader用来读取字符文件的便捷类。
BufferedReaderReader从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
StringReaderReader其源为一个字符串的字符流。
PipedReaderReader传送的字符输入流。
FilterReaderReader用于读取已过滤的字符流的抽象类。
CharArrayReaderReader实现一个可用作字符输入流的字符缓冲区。

字节与字符的转化接口

  说了字节与字符各自的I/O接口,他们之间的转化也必须要说一下,上面的表格中提到了InputStreamReader和OutputStreamWriter,他们中最主要的就是StreamDecoder以及StreamEncoder类。Java字节流和字符流的转换器:StreamDecoder这个博客详细介绍了StreamDecoder的实现方式。


磁盘I/O工作机制

  • 标准访问文件方式:应用调用read()接口,先检查内核高速缓存,有就返回,没有就从磁盘读取,然后再缓存在高速缓存。调用write()接口,将数据复制到内核地址空间的缓存中,写到磁盘的时间由操作系统自己决定。
  • 直接I/O方式:应用直接访问磁盘数据,不经过内核数据缓冲。但是每次数据都从磁盘加载,非常缓慢。
  • 同步访问文件方式:数据的访问与写入都是同步操作,性能较差。
  • 异步访问文件方式:访问数据的线程发出请求,线程处理其他事情,当数据返回后继续之后的操作,提高效率。
  • 内存映射:操作系统将某一块内存区域与磁盘文件关联起来,当访问内存中数据时,转换为访问文件的某一段数据。

Java访问磁盘文件

从磁盘读取文件

Java序列化技术

  Java序列化技术就是将一个对象转化成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的。序列化需要继承java.io.Serializable接口,同时也有反序列化,以原始类作为模板,将字节数组重新构造成对象。这里我找了一篇别人的博客《Java序列化与反序列化》,写的很通俗易懂。当然在书中,作者还解析了序列化后的文件二进制数据的每一部分,在这里我觉得其实能实现序列化与反序列化就可以,等到你使用到了一定深度,你自然而然就会去探究其中。


网络I/O工作机制

  本节重点介绍通信协议和如何完成数据传输。
  

TCP状态转化

TCP状态变迁图

影响网络传输的因素

  将一份数据从一个地方正确的传输到另一个地方所需要的时间我们称为响应时间。而影响这个响应时间的因素有很多。
- 网络带宽:带宽就是一秒钟一条物理链路最大能传输的比特数。这里是bit而不是字节Byte数。这是影响网络传输的一个关键环节,因为在当前网络环境中,平均网络带宽只有1.7Mb/s左右。
- 传输距离:数据在光纤中要走的距离,光在传播中有一个折射率,大概速度是光的2/3,这个时间也就是计算机网络中说的传输延迟。
- TCP拥塞控制:TCP传输是一个“停等停等”的协议,传输方和接受方的步调要一致,要达到这个步调一致就需要拥塞控制(拥塞控制主要算法)来调节。

Java Socket的工作机制

  Socket大部分使用的都是基于TCP/IP的流套接字,是一种稳定的通信协议。
  当客户端要与服务端通信时,客户端首先创建一个Socket实例,操作系统将为这个Socket实例分配一个没有被使用的本地端口号,并且创建一个包含本地地址、远程地址和端口号的套接字数据结构,一直保存到连接关闭。在经过TCP的3次握手协议后,Socket实例对象才创建完成,否则抛出IOException。
  与之对应得是服务端创建一个ServerSocket实例,并创建一个底层数据结构,包含监听端口号以及包含监听地址的通配符,通常为“*”,监听所有地址。然后调用accept()方法,进入阻塞状态,等待客户端请求。
  请求到来时,为该请求分配一个新的套接字数据结构,包含源地址与端口信息。然后将该数据结构关联到ServerSocket实例的一个连接数据结构列表中。并且等到客户端完成3次握手后,服务端的Socket实例才返回,并且将对应得数据结构从未完成转移到已完成列表。
  连接成功,想要传输数据,服务端和客户端都有一个Socket,每个Socket实例都有InputStream 和OutputStream,并通过这两个对象来交换数据。然后通过操作系统为他们分配的缓存区来进行数据的写入与读取。


JavaNIO工作方式

  书中先说了BIO,即阻塞I/O,数据在写入OutputStream或从InputStream 读取时都有可能阻塞,线程失去CPU的使用权,这对于大规模访问量和有性能要求的情况下是不能被接受的。虽然有一些解决办法,但是我们仍然需要一些新的I/O操作方式,就是NIO。
  NIO,即非阻塞I/O,由Channels、Buffers、Selectors三个核心部分组成,Selector可以比作车站的运行调度系统,Channels相当于汽车,Selector可以轮询每一张车(Channel)的状态,Buffer就相当于汽车上的座位,是一个具体的概念。和BIO的区别就在于,BIO的Stream是一个很抽象的东西,你并不知道自己上了什么车,也不知道坐到了座位还是被丢下去了。而NIO把这些过程具象化了,让人们能够控制它们。
  一个简单的JavaNIO聊天室

Buffer的工作方式

  Selector在检测通信信道I/O有数据传输时,通过select()取得SocketChannel,将数据读取或写入Buffer缓冲区。在这里Buffer可以理解为一组基本数据类型的元素列表,通过几个变量来保存数据的当前位置状态。

  • Capacity:当前缓冲区数组的总长度。
  • Position:下一个要操作的数据元素的位置。
  • Limit:缓冲区数组不可操作的下一个元素的位置,limit<=capacity。
  • mark:用于记录当前position的前一个位置或者默认是0。
      
    首先ByteBuffer.allocate(11)创建一个11个Byte的数组缓冲区,初始状态就为:
    这里写图片描述
    写入5个字节数据:
    这里写图片描述
    调用ByteBuffer.flip()方法:
    这里写图片描述
    这样操作系统就可以正确读取这5个字节数据并发送,调用clear()方法,这些变量又会被初始化。还有一个Mark,调用mark()方法时,它会记录position的前一个位置,调用reset()时,position将恢复mark记录的位置。

NIO数据访问方式

  NIO提供了两种优化的访问方法:

  • FileChannel.transferTo()/transferFrom():减少数据从内核到用户空间的复制,数据直接在内核空间移动。
  • FileChannel.map():将文件按照一定大小块映射为内存区域,访问这个内存区域将直接操作这个文件数据。

I/O调优

磁盘I/O优化的方法有:

  • 增加缓存,减少磁盘访问次数。
  • 设计合理的磁盘存储数据块,以及访问这些数据的策略。
  • 应用合理的RAID策略提升磁盘I/O。

网络I/O优化的方法有:

  • 减少网络交互次数。
  • 减少网络传输数据量的大小。
  • 尽量减少编码。

设计模式解析

  适配器模式就是把一个类的接口变换成客户端所能接受的另一种接口,从而使两个接口不匹配而无法工作的两个类一起工作。而在Java I/O中为了将InputStream和OutputStream适配到Reader与Writer。使用了InputStreamReader这个类来当适配器,将InputStream适配到目标接口Reader类,OutputStreamWriter也是一样。
  装饰器模式就是把某个类装饰一下,使得它在功能上更加强大。InputStream类以抽象组件存在,FileInputStream就是具体组件,实现抽象组件所有接口;FilterInputStream类就是装饰角色,也实现了InputStream的所有借口,并持有InputStream对象实例的引用,而最后的BufferedInputStream就是装饰器实现类,给InputStream类附加了功能,使得InputStream读取的数据保存在内存中,提高读取性能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值