1、流的抽象基类:字节流:InputStream,OutputStream.字符流:Reader,Writer。
另外,RandomAccessFile类功能强大,实现对文件的随机位置读写,它没有继承以上四种抽象类。
2,常用的IO实现类
1)fileIO:读写文件,stream每次读一个字节8位,reader读一个char16位。有中文时用reader;节点流,内存---硬盘。
2)bufferedIO:内部有一个缓存区,可以等着读出的数据达到一定数量时集中度出,比如每次读一行BufferedRead的readLine方法,把一行当成字符串读出,该方法比较常用。该方法读文件时,到文件结尾会返回null,此时退出不阻塞。(line = br.readLine()) != null。 但封装socket的话,永远不会返回null,所以一直阻塞
3)转换流:InputStreamReader 是字节流通向字符流的桥梁;OutputStreamWriter 是字符流通向字节流的桥梁;
4)DataIO:DataInputStream和DataOutputStream可以输入输出各种基础数据类型,比如long,double。
5)ByteArrayIO:ByteArrayInputStream和OutputStream是在内存中都一个字节数组,通过刘往字节数组中读写,内存----内存。UDP的packet需要包装一个字节数组,通常在该流上面套DataIO进行UDP通信。
3、序列化:一个应用是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本,如画图软件存盘功能;另一个应用是通过值将对象从一个应用程序域发送到另一个应用程序域中,如socket网络传输。
序列化和反序列化:序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。
4、实现序列化的几种方式:常见的做法有两种:一是把对象包装成JSON或者XML传输,二是采用java对象的序列化和反序列化。
5、
1)概念:Serializable为标记型接口,没有方法,实现Serializable接口的类,其对象可以转化为字节序列保存到硬盘上或者实现网络传输。ObjectInputStream和ObjectOnputStream中的WriteObject和ReadObject方法,这两个方法成对存在,只有写入了多少个对象,才可以读出多少个对象。
2)UID:通过serialVersionUID来验证类版本一致性的。如果没有明确指定serialVersionUID,某个类实现Serializable接口的时候会根据字段和特定的算法生成一个serialVersionUID,当类发生任何变化时系统会自动升级这个ID,所以反序列化的时候就会失败。抛出异常。所以强烈建议所有可序列化类都显式声明 serialVersionUID 值,并且声明为private,不让子类继承。
private static final long serialVersionUID = -5726374138698742258L;
UID的更新存在两种情况:当改变类的方法或者static和transient类型的属性时,不需要更新类的UID;当增加或者减少属性时,不需要更新,只不过新增添的属性在反序列化是生成为0或null;当改变原有属性的数据类型时,需要升级UID。
3)引用类型的属性:当一个类中含有一个引用变量的属性,则这个属性所在的类也必须可序列化。
4)序列化编号:所有保存在磁盘中的对象都有一个序列化编号,而不同于类编号UID。当序列化一个对象时,先检查该对象是否已经被序列化过,如果没有则进行序列化,如果已经序列化,则第二次只保存第一次时的编号。由于该算法,对于一个属性可变对象,程序只有在第一次使用writeObject方法时才会将对象转化为二进制,即使之后把该对象属性改变进行write,输出时会发现属性并不变。
5)可以通过在需要被序列化的类中添加writeObjecr和readObject来实现序列化的自定义。在序列化该对象之前,会先调用该方法。
6、Externalizable
它是
Serializable的一个子接口,两个方法
readExternal
(ObjectInput
in)和
writeExternal
(ObjectOutput
out),程序员可以自己实现这两个方法控制序列化。
7、Json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,类似XML。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript 的一个子集。 JSON采用完全独立于语言的文本格式,可用于各种语言。这些特性使JSON成为理想的数据交换语言。详见JavaScript。
8,NIO
传统的IO底层实现都是通过对字节的处理,而且数据远没有数据时会阻塞该线程,这样效率不高。NIO将要读取的文件映射到内存中,这样读取文件就像读取内存的数据一样了,面向数据块。Channel和Buffer是核心对象,所需要读的数据是从通道读入缓冲区,等缓冲区满了读到内存;或从缓冲区写入到通道中的。
1)Channel:双向通道,可将指定文件部分或者全部映射到Buffer,内存只能通过Buffer获取和写入数据,不能直接访问Channel。Channel不应该直接创建,而应通过传统的流节点的getChannel获取,不同流节点产生的不同。
通道的实现类:
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
2)Buffer:
Buffer中的三个参数
capacity
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
position
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.
当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
使用Buffer的步骤:不管是读文件还是写文件,buffer都是先写后读
- 调用
clear()
- 写入数据到Buffer
- 调用
flip()
方法(写模式到读模式,当向buffer写入数据时,position会记录下写了多少数据,limit放到position,position放到开始处。在读模式下,可以读取之前写入到buffer的所有数据。) - 从Buffer中读取数据
- 调用
clear()
方法或者compact()
方法(写模式到读模式。两种方式:调用clear()或compact()方法。clear()方法position移到开始处,limit移到capacity处。compact()方法只会清除已经读过的数据。首先将未读的数据都被移到缓冲区的起始处,position移到数据末尾,这样新写入的数据将放到缓冲区未读数据的后面。)
3)例子
读文件
01 | RandomAccessFile aFile = new RandomAccessFile( "data/nio-data.txt" , "rw" ); |
02 | FileChannel inChannel = aFile.getChannel(); |
04 | //create buffer with capacity of 48 bytes |
05 | ByteBuffer buf = ByteBuffer.allocate( 48 ); |
08 | while (inChannel.read(buf)!= - 1 ) { // 读文件内容写入buffer,返回值为 读取的字节数,如果该通道已到达文件的末尾,则返回 -1 |
10 | buf.flip(); //make buffer ready for read |
12 | while (buf.hasRemaining()){ |
13 | System.out.print(( char ) buf.get()); //把刚才写入buffer的内容读出 |
16 | buf.clear(); //make buffer ready for writing |
17 | bytesRead = inChannel.read(buf); |
inChannel.read(buf)也可以换为
inChannel.map()一次性将文件的内容全部读出写入buffer。
写文件
1 | RandomAccessFile aFile = new RandomAccessFile( "data/nio-data.txt" , "rw" ); |
2 | FileChannel outChannel = aFile.getChannel(); |
01 | String newData = "New String to write to file..." + System.currentTimeMillis(); |
03 | ByteBuffer buf = ByteBuffer.allocate(48); |
05 | buf.put(newData.getBytes()); |
09 | while(buf.hasRemaining()) { |
4)Select
Selector允许单线程管理多个 Channel。与Selector一起使用时,Channel必须处于非阻塞模式下。
实例:打开一个Selector,注册一个通道注册到这个Selector上(通道的初始化过程略去),然后通过Selector监听Channel的当前状态(接受,连接,读,写)是否就绪。这样提高了NIO使用效率。
01 | Selector selector = Selector.open(); |
02 | channel.configureBlocking( false );//将通道设为非阻塞 |
03 | SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //注册通道,并写上你感兴趣的状态 ,当通道达到这个状态,通道就绪。 |
05 | int readyChannels = selector.select();//返回已就绪通道的数目 |
06 | if (readyChannels == 0 ) continue ; |
/*当
readyChannels不为0时,就可通过以下操作来访问那些已经达到感兴趣状态的通道了
*/
07 | Set selectedKeys = selector.selectedKeys(); |
08 | Iterator keyIterator = selectedKeys.iterator(); |
09 | while (keyIterator.hasNext()) { |
10 | SelectionKey selectionKey = keyIterator.next(); |
11 | if (key.isAcceptable()) { |
12 | // a connection was accepted by a ServerSocketChannel. |
13 | } else if (key.isConnectable()) { |
14 | // a connection was established with a remote server. |
15 | } else if (key.isReadable()) { |
16 | // a channel is ready for reading |
17 | } else if (key.isWritable()) { |
18 | // a channel is ready for writing |
这样的好处是可以用专门的IO线程与IO操作打交道,其他线程专注于自己的业务逻辑就可。例如当某个Channel到了某个状态时,Selector可以主动通知和调用该线程进行相应业务逻辑操作。
9,
Java NIO和IO的主要区别
http://ifeve.com/java-nio-vs-io/
下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器
IO的阻塞模型:
1 | BufferedReader reader = new BufferedReader( new InputStreamReader(input)); |
3 | String nameLine = reader.readLine(); |
4 | String ageLine = reader.readLine(); |
readline()会阻塞直到整行读完。这样必须使进程开辟另外的线程,线程转换耗时,所以使得进程效率降低。
NIO的非阻塞模型,方法调用完成后立刻返回,不管是否接收到数据。
假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。可以使用bufferFull()方法使得必须读满了buffer在对数据进行处理。
10,java7中的AIO
按照《Unix网络编程》的划分,IO模型可以分为:阻塞IO、非阻塞IO、IO复用(select)、信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO。如何区分呢?首先一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO服用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。
11,IO的事件触发
边缘触发 是指每当状态变化时发生一个io事件;
条件触发 是只要满足条件就发生一个io事件;
“举个读socket的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时水平触发的api会因为还有50个字节可读从 而立即返回用户一个read ready notification。而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。 因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则这个socket就算废了。而使用条件触发的api 时,如果应用程序不需要写就不要关注socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家常用的select就是属于水平触发这一类,长期关注socket写事件会出现CPU 100%的毛病。