Java IO流

IO框架

流是一种抽象的数据总称,本质是能够进行数据的传输。按流的方向分为:输入流和输出流。按处理数据的单位分为:字节流和字符流。字节就是1个Byte,8位,字符是占2个Byte,16位,字节是有符号类型,字符是无符号类型的。

字节输入流:InputStream,字节输出流:OutputStream

字符输入流:Reader,字符输出流:Writer

 

一、以字节为单位的输入流的框架图:


(1)InputStream是以字节为单位的输入流的超类,InputStream提供了接口从输入流中读取字节数据。

(2)ByteArrayInputStream是字节数组输入流,它的内部缓冲区是一个字节数组,本质是通过字节数组来是实现的。

(3)PipedInputStream是管道输入流,能够实现多线程间的管道通信。

(4)FilterInputStream是过滤输入流,它是DataInputStream和BufferedInputStream的超类。

(5)DataInputStream是数据输入流,用来装饰其他输入流,允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。

(6)BufferedInputStream是缓冲输入流,作用是为另一个输入流添加缓冲功能。

(7)FileInputStream是文件输入流,通常用于对文件进行读取操作。

(8)File是文件和目录路径名的抽象表示形式,File不仅仅表示文件,也可以表示目录,它的超类是Object。

(9)FileDescriptor是文件描述符,用来表示开放文件,开放套接字等。

(10)ObjectInputStream是对象输入流,和ObjectOutputStream一起来对基本数据或对象进行持久存储。

二、以字节为单位的输出流的框架图:


(1)OutputStream是以字节为单位的输出流的超类,提供了write()函数从输出流中读取字节数据。

(2)ByteArrayOutputStream是字节数组输出流,数据被写入到一个byte数组,缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()获取数据。

(3)PipedOutputStream是管道输出流,和PipedInputStream一起使用,能实现多线程间的管道通信。

(4)FilterOutputStream是过滤输出流,是DataOutputStream,BufferedOutputStream和PrintStream的超类

(5)DataOutputStream是数据输出流,用来装饰其他的输出流,允许应用程序以与机器无关方式向底层写入基本Java数据类型。

(6)BufferedOutputStream是缓冲输出流,它的作用是为另一个输出流添加缓冲功能。

(7)PrintStream是打印输出流,用来装饰其他输出流,为其他输出流添加功能,方便的打印各种数据值

(8)FileOutputStream是文件输出流,通常用于向文件进行写入操作。

(9)ObjectOutputStream是对象输出流,它和ObjectInputStream一起对基本数据或者对象的持久存储。

三、以字符为单位的输入流的框架图:


(1)Reader是以字符为单位的输入流的超类,它提供了read()接口来取字符数据。

(2)CharArrayReader 是字符数组输入流,用于读取字符数组,继承于Reader。

(3)PipedReader 是字符类型的管道输入流,它和PipedWriter一起可以通过管道进行进程间的通讯,在使用管道通信时,必须将PipedWriter和PipedReader配套使用。

(4)FilterReader是字符类型的过滤输入流。

(5)BufferedReader是字符缓冲输入流,作用是为另一个输入流添加缓冲功能。

(6)InputStreamReader是字节转字符的输入流,它是字节流通向字符流的桥梁。

(7)FileReader是字符类型的文件输入流,通常用于对文件进行读取操作。

四、以字符为单位的输出流的框架图:


(1)Writer是以字符为单位的输出流的超类,它提供了writer()函数对字符的写。

(2)CharArrayWriter是字符数组输出流,用于读取字符数组,继承于Writer。

(3)PipedWriter是字符类型的管道输出流,它和PipedReader一起是可以通过管道进行线程间的通讯,在使用管道通信时,必须将PipedReader和PipedWriter配套使用。

(4)FilterWriter是字符类型的过滤输出流。

(5)BufferedWriter是字符缓冲输出流,作用是为另一个输出流添加缓冲功能。

(6)OutputStreamWriter是字节转字符的输出流,是字节通向字符流的桥梁。

(7)FileWriter 是字符类型的文件输出流。

(8)PrintWriter是字符类型的打印输出,用来装饰其他输出流,能为其他的输出流添加功能。

 

字节流与字符流的不同在于字符流由字节流包装而来,在IO读入之后经过JVM处理,把字节流转换成字符流。

 

流使用完必须关闭close(),流输出前必须刷新flush()。

 

四个顶层类都没有实现Serializable接口。java.io.Serializable虽然属于IO框架,但其并不是直接作用于IO框架四个顶层IO类及其子类,而是作用于其它对象,使其可被序列化,再通过IO类对其进行输入输出。Java对象的序列化指将一个java对象写入IO流中,对象的反序列化则从IO流中恢复该java对象。

序列化注意事项:

1、如果子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,否则会抛InvalidClassException异常。

2、静态变量不会被序列化,那是类的,不是对象的。串行化保存的是对象的状态,即非静态的属性,即实例变量。不能保存类变量。

3、transient关键字修饰变量可以限制序列化。对于不需要或不应该保存的属性,应加上transient修饰符。要串行化的对象的类必须是公开的(public)。

4、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致,就是 private static final longserialVersionUID = 1L。

5、Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。反序列化时,恢复引用关系。

6、序列化到同一个文件时,如第二次修改了相同对象属性值再次保存时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。

 

 

Java IO 框架中主要应用了两个设计模式:装饰模式和适配器模式。

装饰器通过包装一个装饰对象来扩展其功能,而又不改变其接口,动态增强性能。

适配器将一个类的接口转换成期望的另一个接口,让原本不兼容的接口可以合作无间(继承了旧接口,组合了新接口)。

两者区别:相同点:都拥有一个目标对象。不同点:适配器模式需要实现旧接口,而装饰器模式必须实现相同接口。

典型的TCP客户端要经过下面三步操作:

1、创建一个Socket实例:构造函数向指定的远程主机和端口建立一个TCP连接;

2、通过套接字的I/O流与服务端通信;

3、使用Socket类的close方法关闭连接。

服务端的工作是建立一个通信终端,并被动地等待客户端的连接。

典型的TCP服务端执行如下两步操作:

1、创建一个ServerSocket实例并指定本地端口,用来监听客户端在该端口发送的TCP连接请求;

2、重复执行:

1)调用ServerSocket的accept()方法以获取客户端连接,并通过其返回值创建一个Socket实例;

2)为返回的Socket实例开启新的线程,并使用返回的Socket实例的I/O流与客户端通信;

3)通信完成后,使用Socket类的close()方法关闭该客户端的套接字连接。

 

NIO

Channel 和 buffer 是 NIO 是两个最基本的数据类型抽象。

Buffer:  是一块连续的内存块。是 NIO 数据读或写的中转地。

Channel:  数据的源头或者数据的目的地。用于向 buffer 提供数据或者读取 buffer 数据 ,buffer 对象的唯一接口。

一个 buffer 主要由 position,limit,capacity 三个变量来控制读写的过程

position:数据位置, limit:最多多少单位数据, capacity:buffer 容量

 

NIO有效解决了多线程服务器存在的线程开销问题。在NIO中使用多线程主要目的不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程充分利用多个CPU的处理能力和处理中的等待时间,达到提高服务能力的目的。

 

同步:函数调用在没得到结果之前,不返回任何结果。

异步:函数调用在没得到结果之前,返回状态信息。

阻塞:函数调用在没得到结果之前,当前线程挂起。得到结果后才返回。

非阻塞:函数调用在没得到结果之前,当前线程不会挂起,立即返回结果。

阻塞的结果就是会带来大量的进程上下文切换。

非阻塞的原理:

把整个过程切换成小的任务,通过任务间协作完成;由一个专门的线程来处理所有的 IO 事件,并负责分发(Reactor );事件驱动机制:事件到的时候触发,而不是同步的去监视事件;线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的进程切换。

选择器(Selector):异步IO的核心类,它能检测一个或多个通道 (channel)上的事件,并将事件分发出去。

 

Java NIO与OIO的区别

面向流与面向缓冲

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。

阻塞与非阻塞IO

Java IO的各种流是阻塞的。当一个线程调用read()或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。Java NIO的非阻塞模式,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

 

NIO和IO如何影响应用程序的设计,可能会影响您应用程序设计的以下几个方面:

对NIO或IO类的API调用。

数据处理。(IO: 从一个阻塞的流中读数据, NIO:从一个通道里读数据,直到所有的数据都读到缓冲区里)

用来处理数据的线程数。(NIO:单线程管理多个连接, 聊天服务器/P2P;IO: 一个连接通过一个线,一次发送大量的数据)

 

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据.

一个完整的三次握手也就是请求---应答---再次确认

位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)

 

四次分手:

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

 

1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你未必会马上关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的.

2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值