IO简介
IO:应用程序调用操作系统进行数据的读取和写入。
同步:被调用方在得到最终结果之后才会返回
异步: 被调用方先响应,然后再去计算结果,结果出来之后再通知调用方
阻塞:调用方在调用之后一直等到结果返回
非阻塞:调用方在调用之后先去执行其他的任务
阻塞、非阻塞和同步、异步的区别(阻塞、非阻塞和同步、异步其实针对的对象是不一样的):
- 1)阻塞、非阻塞的讨论对象是调用者;
- 2)同步、异步的讨论对象是被调用者。
JAVA普通IO与NIO
普通IO:基于流Stream来实现,一次读取一个字节,可添加缓存区,是同步阻塞的;
NIO:基于块实现,一次读取一个块,是非阻塞的;
NIO组成
NIO主要有通道和缓冲区两部分,到任何一个地方的数据(或者来自任何地方的数据)都需要经过通道(Channel),缓冲区(Buffer)是一个容器对象,发送到一个通道的所有数据都必须先放到缓冲区;类似的,从通道中读取的所有数据都要先放到缓冲区。
通道
通道类似IO中的流,但是通道是双向的,同一个通道既可以读也可以写还可以一边读一边写。
缓冲区
缓冲区本质是一个数组,还提供了对数据的结构化访问,可以跟踪系统的读写进程。
缓冲区有多种类型的数组,最常见的是ByteBuffer,是byte类型的。每一个Java的基本类型都有着相应的类型的缓冲区。
状态变量
NIO通过状态变量来实现缓冲区的内部统计机制,缓冲区的每一次读写都会改变状态变量。
可以使用三个值表示缓冲区在任意时刻的状态:
读取过程(从通道读取到缓冲区)
读取之前需要先调用clear()方法,将limit设置与capacity相同,将position设为0。
1.初始状态,limit与capacity相等,position是从0开始的

2.读取数据到缓存区,position增加,limit不变

写入过程
在写入数据之前调用flip()方法将limit设置为当前的position,将position设置为0;

2.从缓存区写入数据到通道,position增加

访问方法
访问方法是用来直接访问缓冲区中的数据的方法。
方法分为绝对的和相对的,相对的方法会造成缓冲区三个变量的改变,绝对方法不会改变变量的值。
get()方法
指定位置的方法是绝对的。
put()方法
指定位置的方法是绝对的。
NIO中的数据读写
读写概述:
从通道中读取数据:只需要创建一个缓冲区,然后让通道将数据读到缓冲区。
写入数据:创建一个缓冲区,用数据填充它,然后让通道来执行相应的写入操作。
通道的读和写都是从缓存区的角度来定义的,缓存区读入数据的时候就用的通道的read方法,写数据的时候就调用通道的写方法。
读取文件:
1 2 3 4 5 6 7
| FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
fc.read( buffer );
|
写入文件:
1 2 3 4 5 6 7 8 9 10 11 12
| FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel(); ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int ii=0; ii<message.length; ++ii) { buffer.put( message[ii] ); }
buffer.flip();
fc.write( buffer );
|
一边读一遍写
1 2 3 4 5 6 7 8 9 10 11 12 13
| while (true) {
buffer.clear(); int r = fcin.read( buffer ); if (r==-1) { break; } buffer.flip(); fcout.write( buffer ); }
|
缓冲区的更多操作
包装和分配
1.使用静态方法allocate()分配缓冲区;
1 2
| ByteBuffer buffer = ByteBuffer.allocate( 1024 );
|
2.现有数组包装为缓冲区;
1 2
| byte array[] = new byte[1024]; ByteBuffer buffer = ByteBuffer.wrap( array );
|
缓存分区
slice() 方法根据现有的缓冲区创建一种 子缓冲区 。也就是说,它创建一个新的缓冲区,新缓冲区与原来的缓冲区的一部分共享数据。(子缓冲区与主缓冲区的数据用的是同一份)
子缓冲区的初始位置和结束位置通过position和limit来进行指定,最后调用slice方法。
1 2 3
| buffer.position( 3 ); buffer.limit( 7 ); ByteBuffer slice = buffer.slice();
|
只读缓冲区
通过asReadOnlyBuffer() 方法将普通的缓冲区转换为只读缓冲区。但是不能将只读缓冲区转换为普通缓冲区。
文件锁定
锁定文件
获取文件一部分的锁。需要使用FileChannel,如果需要一个排他锁,则需要以写的方式打开文件。
1 2 3 4 5 6 7 8
| RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" ); FileChannel fc = raf.getChannel(); FileLock lock = fc.lock( start, end, false );
lock.release();
|
联网与异步IO
异步IO是没有阻塞的读写数据的方法。通过注册一系列事件,当事件发生的时候通知处理即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set;
public class MultiPortEcho {
private int[] ports; private ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
public MultiPortEcho(int[] ports) throws IOException { this.ports = ports; go(); }
private void go() throws IOException { Selector selector = Selector.open();
for (int port : ports) { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); serverSocket.bind(address);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Listening on port " + port); }
while (true) { int num = selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) { SelectionKey key = iterator.next();
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) { ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("Accepted connection from " + socketChannel); } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { SocketChannel socketChannel = (SocketChannel) key.channel();
int bytesEchoed = 0;
while (true) { echoBuffer.clear();
int bytesRead = socketChannel.read(echoBuffer);
if (bytesRead == -1) { socketChannel.close(); System.out.println("Connection closed by client: " + socketChannel); break; }
if (bytesRead > 0) { echoBuffer.flip();
while (echoBuffer.hasRemaining()) { socketChannel.write(echoBuffer); bytesEchoed += bytesRead; } } }
System.out.println("Echoed " + bytesEchoed + " bytes from " + socketChannel); }
iterator.remove(); } } }
public static void main(String[] args) throws IOException { if (args.length == 0) { System.err.println("Usage: java MultiPortEcho port [port port ...]"); System.exit(1); }
int[] ports = new int[args.length]; for (int i = 0; i < args.length; i++) { ports[i] = Integer.parseInt(args[i]); }
new MultiPortEcho(ports); } }
|
字符处理
可以直接把缓冲区丢入编码和解码器中,将会返回对应格式的缓冲区;
1 2 3 4 5 6 7
| Charset latin1 = Charset.forName( "ISO-8859-1" ); CharsetDecoder decoder = latin1.newDecoder(); CharsetEncoder encoder = latin1.newEncoder(); CharBuffer cb = decoder.decode( inputData ); ByteBuffer outputData = encoder.encode( cb );
|