java nio
一、什么是java nio
java nio 是java new i/o的简称,也有叫java non-blocking i/o,在jdk1.4中引入。它是一种同步非阻塞的io模型,也是io多路复用的基础。
二、nio技术组成
java NIO主要由三部分组成①channels、②selecters、③Buffers
1、Channel(通道)类似于bio中的stream,数据可以从channel读取到buffer中,也可以从buffer写入channel中。channel和stream的不同点是stream是单向的,二channel是双向的。下面是一些常见的通道,
FileChannel 从文件中读写数据,不能设置为非阻塞模式,也就不能与selector在一起使用
DatagramChannel 通过UDP读写网络中的数据
SocketChannel 通过TCP读写网络中的数据
ServerSocketChannel 可以监听新进来的TCP链接,对每一个新进来的TCP链接都会创建一个SocketChannel
2.Buffers 缓存,用来缓存从channel中读取的数据,也可以将数据从缓存写入channel中。
ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 从命名可知分别对应了几种基本数据类型。
MappedByteBuffer 是ByteBuffer的子类,用于表示内存映射文件缓存,可用于复制大型文件。
Buffer的属性和一些常用的方法:
属性:mark <= position <= limit <= capacity
capacity :缓存区的总容量
position :缓冲区中,下一次发生读取或写入操作的地方,每次读写操作后,都会向后推进。
limit:在读模式下,limit和positon一相同;在写模式下,limit设置为上次读取时position的地方
mark:标记值,当调用了mark()方法后,mark就是当前的position位置;调用reset()之后,会将postion置为mark的位置。
方法:
allocate(n) 初始化缓冲区的大小,此时position=limit=0 ,capacity = n, mark= -1
put() 向缓冲区写数据
get() 向缓冲区读数据
filp() 将缓冲区从写模式切换到读模式
clear() 从读模式切换到写模式,不会清空数据,但后续写数据会覆盖原来的数据,即使有部分数据没有读,也会被遗忘;将position和limit都置为0,将mark置为-1
compact() 从读数据切换到写模式,数据不会被清空,会将所有未读的数据copy到缓冲区头部,后续写数据不会覆盖,而是在这些数据之后写数据
mark() 对position做出标记,配合reset使用
reset() 将position置为标记值
rewind() 将position置为0,mark置为-1
3.selector 选择器,用于监听在selector上注册了的channel的事件,只能用于非阻塞channel。通过调用它的select()或其重载方法,这个方法会一直阻塞,直到注册的通道中有事件就绪。
在selector上注册channel时需要指定监听的事件,总共有如下四种事件可供选择:
SelectionKey.OP_CONNECT 如果某个channel成功连接到另一个服务器称为“连接就绪”
SelectionKey.OP_ACCEPT 如果一个ServerSocketChannel准备好接收新进入的连接称为“接收就绪”
SelectionKey.OP_READ 如果一个有数据可读的通道可以说是“读就绪”
SelectionKey.OP_WRITE 等待写数据的通道可以说是“写就绪”
下面是一些nio方法操作文件的例子:
//NIO读写文件 public class Test3 { public static void main(String[] args) { String path = "E:/a.txt"; readFile(path); String path2 = "E:/b.txt"; writeFile(path2); } /** * * @Title: readFile * @Description:使用FileChannel读取文本文件 * @param @param path * @return void * @throws */ public static void readFile(String path){ FileInputStream fis = null; FileChannel fc = null; try { //Java.nio.charset.Charset处理了字符转换问题。它通过构造CharsetEncoder和CharsetDecoder将字符序列转换成字节和逆转换。 Charset charset = Charset.forName("utf-8"); CharsetDecoder decoder = charset.newDecoder(); fis = new FileInputStream(path); fc = fis.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); while (fc.read(byteBuffer) != -1) { byteBuffer.flip(); decoder.decode(byteBuffer, charBuffer, false);//如果不够一个字符不会进行解码 charBuffer.flip(); while (charBuffer.hasRemaining()) { System.out.print(charBuffer.get()); } byteBuffer.compact();//将缓冲区设置为读模式,并且上一次没有转换的字节会被保留下来,并放在最前端,不能用clear()代替 charBuffer.compact(); } fis.close(); fc.close(); } catch (Exception e) { e.printStackTrace(); }finally{ try { fis.close(); fc.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * * @Title: writeFile * @Description:使用FileChannel将文字写入文件中 * @param @param path * @return void * @throws */ public static void writeFile(String path){ FileOutputStream fos = null; FileChannel fileChannel = null; try { String source = "我要写入一个文件中使得发放卡号即可adda"; fos = new FileOutputStream(path); fileChannel = fos.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put(source.getBytes("utf-8")); buffer.flip(); fileChannel.write(buffer); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ try { fos.close(); fileChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
//nio复制文件 public class Test4 { public static void main(String[] args) throws IOException { String source = "E:/BaiduNetdisk_5.6.3.exe"; String target = "E:/BaiduNetdisk_5.6.3_copy.exe"; // bioCopyFile(source,target); // nioCopyFile(source,target); mappedCopyFile(source,target); } /** * * @Title: bioCopyFile * @Description: java 标准io流复制文件 * @param @throws IOException * @return void * @throws */ public static void bioCopyFile(String source,String target)throws IOException { long startTime = System.currentTimeMillis(); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target)); int len = 0; while((len = bis.read()) != -1){ bos.write(len); } long endTime = System.currentTimeMillis(); System.out.println("bio复制文件所用时间"+(endTime-startTime)+"毫秒"); bis.close(); bos.close(); } /** * * @Title: nioCopyFile * @Description: java nio复制文件 * @param @throws IOException * @return void * @throws */ public static void nioCopyFile(String source,String target)throws IOException { long startTime = System.currentTimeMillis(); RandomAccessFile raf = new RandomAccessFile(source,"rw"); RandomAccessFile raf2 = new RandomAccessFile(target,"rw"); FileChannel inChannel = raf.getChannel(); FileChannel outChannel = raf2.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); while(inChannel.read(buffer) != -1){ buffer.flip();//写模式切换到读模式 outChannel.write(buffer); buffer.clear();//读模式切换到写模式 } raf.close(); raf2.close(); inChannel.close(); outChannel.close(); long endTime = System.currentTimeMillis(); System.out.println("nio复制文件所用时间"+(endTime-startTime)+"毫秒"); } /** * * @Title: mappedCopyFile * @Description: 使用MappedByteBuffer来复制文件 * MappedByteBuffer的确快,但也存在一些问题,主要就是内存占用和文件关闭等不确定问题。 * 被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的 */ public static void mappedCopyFile(String source,String target) throws IOException{ long startTime = System.currentTimeMillis(); RandomAccessFile raf = new RandomAccessFile(source,"rw"); RandomAccessFile raf2 = new RandomAccessFile(target,"rw"); FileChannel inChannel = raf.getChannel(); FileChannel outChannel = raf2.getChannel(); MappedByteBuffer mappedByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size()); outChannel.write(mappedByteBuffer); raf.close(); raf2.close(); inChannel.close(); outChannel.close(); long endTime = System.currentTimeMillis(); System.out.println("MappedByteBuffer复制文件所用时间"+(endTime-startTime)+"毫秒"); } }
//nio服务器 public class Server { private Selector selector;//同一个选择器,用来监听是否有链接或者其他操作就绪 public static void main(String[] args) { Server server = new Server(); server.startServer();//启动服务器时监听端口是否有新的连接事件 server.listenChannel();//监听所有的channel有没有数据读取准备就绪 } public void startServer(){ try { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); ssc.socket().bind(new InetSocketAddress(8888));//监听8888端口 selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { e.printStackTrace(); } } public void listenChannel(){ while(true){ try { selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); handleKey(key); iterator.remove(); } } catch (IOException e) { e.printStackTrace(); } } } private void handleKey( SelectionKey key){ try { if(key.isAcceptable()){//有新的网络可以链接 ServerSocketChannel channel = (ServerSocketChannel)key.channel(); SocketChannel socketChannel = channel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } if(key.isReadable()){ SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int read = channel.read(buffer); if(read > 0){ String receiveText = new String(buffer.array(),0,read); System.out.println("服务器端接受到的数据---"+receiveText); channel.register(selector, SelectionKey.OP_WRITE); } } if(key.isWritable()){ SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.clear(); buffer.put("服务器响应信息".getBytes()); buffer.flip(); channel.write(buffer); channel.register(selector, SelectionKey.OP_READ); } } catch (IOException e) { e.printStackTrace(); } } } //nio客户端 public class Client { public static void main(String[] args) { clientStart(); } public static void clientStart(){ try { Selector selector = Selector.open(); SocketChannel sc = SocketChannel.open(); sc.configureBlocking(false); sc.connect(new InetSocketAddress("localhost", 8888)); sc.register(selector, SelectionKey.OP_CONNECT); while(true){ selector.select(); Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); //iterator.remove(); if(key.isConnectable()){ System.out.println("client connect"); SocketChannel socketChannel = (SocketChannel) key.channel(); // 判断此通道上是否正在进行连接操作。 // 完成套接字通道的连接过程。 ByteBuffer sendBuffer = ByteBuffer.allocate(1024); //完成连接的建立(TCP三次握手) if (socketChannel.isConnectionPending()) { socketChannel.finishConnect(); System.out.println("完成连接!"); sendBuffer.put("Hello,Server".getBytes()); sendBuffer.flip(); socketChannel.write(sendBuffer); } socketChannel.register(selector, SelectionKey.OP_READ); } if(key.isReadable()){ SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int read=channel.read(buffer); if(read>0){ String receiveText = new String( buffer.array(),0,read); System.out.println("客户端接受服务器端数据--:"+receiveText); channel.register(selector, SelectionKey.OP_WRITE); } } if(key.isWritable()){ SocketChannel socketChannel = (SocketChannel)key.channel(); ByteBuffer sendBuffer = ByteBuffer.allocate(1024); sendBuffer.clear(); sendBuffer.put("MSG from client".getBytes()); sendBuffer.flip(); socketChannel.write(sendBuffer); socketChannel.register(selector, SelectionKey.OP_READ); } } } } catch (IOException e) { e.printStackTrace(); } } }