NIO简介
什么是Java NIO,nio在java1.4时新增,叫做new I/O,就是新的I/O,既是在基于1.0出现的I/O Stream操作之上的新改变,
包括,新的 I/O通信模型,如Buffer,Channels,多路复用(Selector);基于Perl样式正则表达式的模式匹配工具。
java.nio.Buffer
一个特点原始数据类型(并不包括如String等类)的集合,提供了方便的put,get,数据批量移动等操作,就像我们用数组来缓存每次读取的数据一样,但提供了更加方便的操作,还有一个特殊的基于直接内存来分配的ByteBuffer.allocateDirect()的操作,基于直接内存来操作,虽然在gc中并不回收这部分内存,但大数据的操作效率更高。
Buffer内部维护0<=mark<=position<=limit<=capacity的指针,来操作缓存数据,对外提供各种类型的put,get,批量移动;对缓存容器进行创建,翻转,释放,压缩,标记
缓冲区的使用实例
public static void main(String[] args) {
//1.基于allocate方法调用得到的heap(JVM管理的堆)上的缓存
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put((byte) 'a');
byteBuffer.put((byte) 'b');
byteBuffer.put((byte) 'c');
//翻转函数,设置limit = position,position = 0,
byteBuffer.flip();
//hasRemaining = limit - position>0
while (byteBuffer.hasRemaining()) {
//position = position+1*(1byte)
System.out.println((char) byteBuffer.get());
}
//直接返回当前这个Buffer底层的数组,和position,limit等下标操纵无关
byte[] bytes = byteBuffer.array();
for (byte aByte : bytes) {
System.out.println(aByte);
}
//int类型的Buffer,同理其他的Buffer也是如此
IntBuffer intBuffer = IntBuffer.allocate(8);
intBuffer.put(1);
intBuffer.put(2);
intBuffer.put(3);
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
//2.基于Wrap()方法得到heap(JVM管理的堆)上的缓存
//基础类型的Wrap(),直接创建一个数组长度的缓冲区,position=0,limit =capacity = length;
LongBuffer longBuffer = LongBuffer.wrap(new long[]{1L, 2L, 3L});
//因为offset=position =0,所以不用再次flip
while (longBuffer.hasRemaining()) {
System.out.println("long:" + longBuffer.get());
}
//已经到结尾了,所以要重置一下指针,内部的数据并未发生改变
//position = 0;
//limit = capacity;
//mark = -1;
longBuffer.clear();
longBuffer.put(4L);
longBuffer.flip();
while (longBuffer.hasRemaining()) {
System.out.println("long:" + longBuffer.get());
}
// 这是为了方便处理字符串处理而实现的特例,包装CharSequence,如String,StringBuffer,StringBuilder
CharSequence charBuffer = CharBuffer.wrap("hello world");
while (((CharBuffer) charBuffer).hasRemaining()) {
System.out.println("char:" + ((CharBuffer) charBuffer).get());
}
//批量移动,把float[]的数批量put到floatBuffer中,然后再get出floatBuffer的数据,放在tmpBuffer中
FloatBuffer floatBuffer = FloatBuffer.allocate(8);
floatBuffer.put(new float[]{0.4f, 0.2f, 0.3f});
float[] tmpBuffer = new float[4];
floatBuffer.flip();
floatBuffer.get(tmpBuffer, 0, floatBuffer.remaining());
for (float v : tmpBuffer) {
System.out.println("float:" + v);
}
DoubleBuffer doubleBuffer = DoubleBuffer.allocate(8);
doubleBuffer.put(new double[]{0.5d, 0.6d, 0.7d});
//mark一下position,mark = position
doubleBuffer.mark();
doubleBuffer.put(new double[]{0.8d, 0.9d, 0.11d});
//position = mark;
doubleBuffer.reset();
//position = mark,limit = capacity,所以打印出来了最后两个没有实际赋值的0.0
while (doubleBuffer.hasRemaining()) {
System.out.println("double:" + doubleBuffer.get());
}
ShortBuffer shortBuffer = ShortBuffer.allocate(8);
shortBuffer.put(new short[]{1, 2, 3, 4, 5, 6});
System.out.println("shortBuffer.position:" + shortBuffer.position());
//手动设置position
shortBuffer.position(3);
//剩余position个数据,limit=capacity
shortBuffer.compact();
while (shortBuffer.hasRemaining()) {
System.out.println("short:" + shortBuffer.get());
}
//直接内存缓冲区,1char = 2byte,1short=2byte,1int = 4byte,1long = 8byte,1float = 4byte,1double = 8byte
//不会被gc的区域
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(8);
directByteBuffer.putInt(10).putInt(11);
directByteBuffer.flip();
//1.直接类型映射
/*while (directByteBuffer.hasRemaining()) {
System.out.println("directByteBuffer:" + directByteBuffer.getInt());
}*/
//2.buffer类型转换
IntBuffer intBuffer1 = directByteBuffer.asIntBuffer();
while(intBuffer1.hasRemaining()){
System.out.println("directByteBuffer:" + intBuffer1.get());
}
}
java.nio.channels.Channel
什么是channel,channel是(A nexus for I/O operations.),一个i/o操作的连接。
FileChannel文件拷贝实例
/**
* 利用transfer直接拷贝
*/
private static void copyFile2(String source, String target) throws IOException {
File fileRead = new File(source);
File fileWrite = new File(target);
RandomAccessFile randomAccessFileRead = new RandomAccessFile(fileRead, "r");
RandomAccessFile randomAccessFileWrite = new RandomAccessFile(fileWrite, "rw");
FileChannel fileChannelRead = randomAccessFileRead.getChannel();
FileChannel fileChannelWrite = randomAccessFileWrite.getChannel();
fileChannelRead.transferTo(0, fileChannelRead.size(), fileChannelWrite);
fileChannelRead.close();
fileChannelWrite.close();
}
/**
* 手工拷贝
* @param source
* @param target
* @throws IOException
*/
private static void copyFile1(String source, String target) throws IOException {
File fileRead = new File(source);
File fileWrite = new File(target);
RandomAccessFile randomAccessFileRead = new RandomAccessFile(fileRead, "r");
RandomAccessFile randomAccessFileWrite = new RandomAccessFile(fileWrite, "rw");
//16k
ByteBuffer buffer = ByteBuffer.allocateDirect(1 << 14);
FileChannel fileChannelRead = randomAccessFileRead.getChannel();
FileChannel fileChannelWrite = randomAccessFileWrite.getChannel();
while (fileChannelRead.read(buffer) != 0) {
buffer.flip();
fileChannelWrite.write(buffer);
}
fileChannelRead.close();
fileChannelWrite.close();
}
不带Selector的NIO网络通信实例
server,服务端监听端口8888,等待服务端连接,连接完成后,直接在当前线程(main线程)中,接受来自客户端的文件,并写出到文件中。
private static void server(String filename) throws IOException, InterruptedException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel == null) {
System.out.println("等待客户端连接.");
TimeUnit.SECONDS.sleep(2);
continue;
}
//16k
ByteBuffer buffer = ByteBuffer.allocate(1 << 14);
RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "rw");
while (socketChannel.read(buffer) != 0) {
buffer.flip();
randomAccessFile.getChannel().write(buffer);
}
if (socketChannel.isOpen()) {
socketChannel.close();
}
break;
}
serverSocketChannel.close();
}
client
客户端连接127.0.0.1端口为8888的服务,然后发送数据,等待服务端接受。
private static void client(String filename) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "r");
//16k
ByteBuffer buffer = ByteBuffer.allocateDirect(1 << 14);
while (randomAccessFile.getChannel().read(buffer) != 0) {
buffer.flip();
socketChannel.write(buffer);
}
randomAccessFile.close();
socketChannel.close();
}
java.nio.channels.Selector(对象的多路复用器)
多个channel可以同时注册到同一个Selector中,Selector通过唯一的一个SelectionKey与一个通道建立联系,而每个SelectionKey都设置了一个关注事件类型,包括, OP_ACCEPT、OP_READ、OP_WRITE、OP_CONNECT四种事件类型。
使用Selector选择器的服务端实例(客户端可以使用上面的客户端)
private static void server(String filename) throws IOException, InterruptedException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//阻塞等待连接
int complement = selector.select();
if (complement == 0) {
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
System.out.println("接受連接。");
SocketChannel socketChannel = serverSocketChannel1.accept();
if(socketChannel==null){
continue;
}
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//16k
ByteBuffer byteBuffer = ByteBuffer.allocate(1 << 14);
FileChannel fileChannel = new RandomAccessFile(filename, "rw").getChannel();
while (socketChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
fileChannel.write(byteBuffer);
byteBuffer.clear();
}
fileChannel.close();
socketChannel.close();
}
iterator.remove();
}
}
}
这一篇写了对Nio的理解,主要是Buffer、Channel、Selector的类,下一篇讲解在Java7中Nio2。