java中的NIO基础
java.nio.*在jdk1.4中开始引入的新的非阻塞IO库,目的是为了提高IO速度。
nio的核心概念:channels(通道),buffers(缓存)。
理解channels,buffers:我们可以把它想象成一个煤矿,通道时包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤矿。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获取数据,要么向缓冲器发送数据。——来自《JAVA变成思想》
理解channels,buffers:我们可以把它想象成一个煤矿,通道时包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤矿。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获取数据,要么向缓冲器发送数据。——来自《JAVA变成思想》
理解nio的设计模式:一个故事讲清楚NIO
- jdk中Buffer主要实现
父类Buffer,提供Buffer的基本操作
abstract Object array() 返回此缓冲区的底层实现数组(可选操作)。
abstract int arrayOffset() 返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量(可选操作)。
int capacity() 返回此缓冲区的容量。
Buffer clear() 清除此缓冲区。
Buffer flip() 反转此缓冲区。
abstract boolean hasArray() 告知此缓冲区是否具有可访问的底层实现数组。
boolean hasRemaining() 告知在当前位置和限制之间是否有元素。
abstract boolean isDirect() 告知此缓冲区是否为直接缓冲区。
abstract boolean isReadOnly() 告知此缓冲区是否为只读缓冲区。
int limit() 返回此缓冲区的限制。
Buffer limit(int newLimit) 设置此缓冲区的限制。
Buffer mark() 在此缓冲区的位置设置标记。
int position() 返回此缓冲区的位置。
Buffer position(int newPosition) 设置此缓冲区的位置。
int remaining() 返回当前位置与限制之间的元素数。
Buffer reset() 将此缓冲区的位置重置为以前标记的位置。
Buffer rewind() 重绕此缓冲区。
理解Buffer的设计和读写基本操作 通俗编程——白话NIO之Buffer
直接已知子类
ByteBuffer:字节缓冲区
CharBuffer:字符缓冲区
DoubleBuffer:double缓冲区
FloatBuffer:float缓冲区
IntBuffer:int缓冲区
LongBuffer:long缓冲区
ShortBuffer:short缓冲区
MappedByteBuffer:直接字节缓冲区
子类一般创建方法
通过 allocation 方法创建,此方法为该缓冲区的内容分配空间
通过 wrapping 方法将现有的基本类型数组包装到缓冲区中来创建
jdk对这些子类缓冲区中主要定义了以下操作
读写单个数据(字节,int,short...)的 get 和 put 方法
批量的将缓冲区的连续数据(字节,int,short...)序列传输到数组中或者将数组或其他缓冲区中的连续数据(字节,int,short...)序列传输到此缓冲区的put/get方法
对字节缓冲区的基本操作,比如压缩compacting,创建共享此缓冲区内容的新的字节缓冲区duplicate,创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列slice等
另外ByteBuffer还提供了另外两个常用操作
读写基本数据类型的put/get方法,getInt(),getShort()...
创建视图缓冲区的方法,允许将字节缓冲区视为包含其他基本类型值的缓冲区,asCharBuffer(),asIntBuffer()...
两个常用比较方法
boolean equals(Object ob)
判断此缓冲区是否与另一个对象相同。
当且仅当满足以下条件时两个字节缓冲区相同:
它们具有相同的元素类型,
它们具有相同数量的剩余元素,
并且两个剩余元素序列(从position到limit之间的元素,与它们的起始位置无关)逐点相同。
字节缓冲区与任何其他类型的对象都不同。
int compareTo(ByteBuffer that)将此缓冲区与另一个缓冲区进行比较。
比较两个字节缓冲区的方法是按字典顺序比较它们的剩余元素序列,而不考虑每个序列在其对应缓冲区中的起始位置。
字节缓冲区不能与任何其他类型的对象进行比较。
- jdk中的Channel主要实现
DatagramChannel:是一个能收发UDP包的通道。可以使用DatagramChannel.open()打开通道。
SocketChannel:是一个连接到TCP网络套接字的通道。可以使用SocketChannel.open()打开通道。
ServerSocketChannel:是一个针对面向流的侦听套接字的可选择通道,可以使用ServerSocketChannel.open()打开通道。
- 代码示例
FileChannel以及一些基本的常用操作
package me.hays.JavaStudy.nio; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileChannelTest { public static final int BUFFER_SIZE = 2048; public static void main(String[] args) throws IOException { String fileName = "fileChannelTest.txt"; String fileNameTo = "fileChannelTestDest.txt"; writeFile(fileName); readFile(fileName); copyFile(fileName, fileNameTo); } /*** * 写文件*基本用法 * @param fileName 文件路径 * @throws IOException * 使用buffer读写数据的一般套路: * 1、写入数据到Buffer * 2、调用flip()方法 * 3、从Buffer中读取数据 * 4、调用clear()方法或者compact()方法 * clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。 * 任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。 */ public static void writeFile(String fileName) throws IOException { FileChannel wFileChannel = null; RandomAccessFile randomAccessFile = null; try { randomAccessFile = new RandomAccessFile(fileName, "rw"); wFileChannel = randomAccessFile.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);// 分配新的字节缓存区 buffer.put("hello world!!".getBytes());// 往缓冲器中写入数据// putChar,putDobule... buffer.flip();// 反转此缓冲区,因为刚刚是放入数据,现在要取出来 while (buffer.hasRemaining()) {// 查看是否有元素,如有返回true wFileChannel.write(buffer);// write方法是FileChannel提供的 } } finally { if (randomAccessFile != null) { randomAccessFile.close();//randomAccessFile关闭的时候,通道会自动关闭 } } } /*** * 读文件,基本用法 * @param fileName 文件路径 * @throws IOException */ public static void readFile(String fileName) throws IOException{ FileChannel rFileChannel = null; RandomAccessFile randomAccessFile = null; try { randomAccessFile = new RandomAccessFile(fileName, "r"); rFileChannel = randomAccessFile.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); int readBytes = rFileChannel.read(buffer); while(readBytes != -1){//如果该通道已到达流的末尾,则返回 -1 buffer.flip(); while(buffer.hasRemaining()){ System.out.print((char) buffer.get()); } buffer.clear();//读完后清空,给下次继续读 readBytes = rFileChannel.read(buffer); } } finally { if (randomAccessFile != null) { randomAccessFile.close();//randomAccessFile关闭的时候,通道会自动关闭 } } } /*** * 文件复制**transferTo和transferFrom的使用 * @param srcFileName 源文件 * @param destFileName 目标文件 * @throws IOException */ public static void copyFile(String srcFileName, String destFileName) throws IOException{ RandomAccessFile srcR = null; RandomAccessFile destR = null; try { srcR = new RandomAccessFile(srcFileName, "r"); FileChannel srcChannel = srcR.getChannel(); destR = new RandomAccessFile(destFileName, "rw"); FileChannel destChannel = destR.getChannel(); srcChannel.transferTo(0, srcChannel.size(), destChannel); //destChannel.transferFrom(destChannel, 0, srcChannel.size());//和上面语句实现效果一致 srcChannel.close(); destChannel.close(); } finally { if (srcR != null) { srcR.close();//randomAccessFile关闭的时候,通道会自动关闭 } if (destR != null) { destR.close(); } } } }
ServerSocketChannel和SocketChannel以及Selector的使用
package me.hays.JavaStudy.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; /**** * @author hays * 客户端代码 */ public class SocketClientTest { public static void main(String[] args) throws IOException { createClient("localhost", 8000); } public static void createClient(String hostname, int port) throws IOException{ //套路1、获取通道2、设置非阻塞3、监听端口4、注册连接 SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); socketChannel.connect(new InetSocketAddress(hostname, 8000)); socketChannel.register(selector, SelectionKey.OP_CONNECT); //轮询处理感兴趣的事件 while(selector.select() > 0){ Iterator<SelectionKey> it = selector.selectedKeys().iterator(); if(it.hasNext()){ SelectionKey selectionKey = it.next(); it.remove(); if(selectionKey.isConnectable()){ socketChannel = (SocketChannel) selectionKey.channel(); if(socketChannel.isConnectionPending()){ // 如果正在连接,则完成连接 //没有的话会报错 socketChannel.finishConnect(); } socketChannel.configureBlocking(false); socketChannel.write(ByteBuffer.wrap(new String("向服务端发送信息:hello server").getBytes("utf-8"))); socketChannel.register(selector, SelectionKey.OP_READ); }else if(selectionKey.isReadable()){ System.out.println("read......"); socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer readBuffer = ByteBuffer.allocate(1024); socketChannel.read(readBuffer); byte[] data = readBuffer.array(); String msg = new String(data, "utf-8").trim(); System.out.println("客户端接收到消息:" + msg); }else{ System.out.println("????"); } } } } }
package me.hays.JavaStudy.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; /**** * @author hays * 服务端代码 */ public class ServerSocketServerTest { public static void main(String[] args) throws IOException { createServer(8000); } public static void createServer(int port) throws IOException{ //使用ServerSocketChannel的静态方法打开一个ServerSocket的通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(port));//监听端口 serverSocketChannel.configureBlocking(false);//设置非阻塞 Selector selector = Selector.open();//使用Selector //注册感兴趣的事件,当接收到请求时才进行处理,其他情况下阻塞 //Selector一起使用时,Channel必须处于非阻塞模式下,所以不能将FileChannel与Selector一起使用 //SelectionKey.OP_ACCEPT; SelectionKey.OP_CONNECT; SelectionKey.OP_READ; SelectionKey.OP_WRITE //一般ServerSocketChannel只注册accept事件,read和write事件是注册到accept的SocketChannel中 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(selector.select() > 0){//轮询//已更新其准备就绪操作集的键的数目,该数目可能为零 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey selectionKey = (SelectionKey) it.next(); it.remove(); if(selectionKey.isAcceptable()){ System.out.println("accept......"); SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); ByteBuffer sendBuffer = ByteBuffer.wrap("向客户端发送消息:hello client!".getBytes("utf-8")); socketChannel.write(sendBuffer); socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("registe read and write......"); }else if(selectionKey.isReadable()){ System.out.println("read......"); SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer readBuffer = ByteBuffer.allocate(1024); socketChannel.read(readBuffer); byte[] data = readBuffer.array(); String msg = new String(data, "utf-8").trim(); System.out.println("服务端接收到消息:" + msg); }else{ System.out.println("????"); } } } } }