本文转载自微信公众号《SH的全栈笔记》,作者SH的全栈笔记。转载本文请联系SH全栈笔记公众号。JavaNIO中的通道分类:FileChannelSocketChannelServerSocketChannelDatagramChannel通道分类FileChannel:主要用于读写文件,可以从磁盘读取文件,也可以将文件写入磁盘。SocketChannel:用于Socket的TCP连接的数据读写。它可以从Channel中读取数据,也可以向Channel中写入数据。ServerSocketChannel:通过ServerSocketChannel,可以监听TCP连接。服务器监听到连接后,会为每个请求创建一个套接字。一个SocketChannelDatagramChannel:用于UDP协议的数据读写。接下来我们分别介绍一下。FileChannel主要是用来操作文件的,废话不多说,直接看例子。准备文件test-file.txt,shDEQuanZhanBiJittest-file.txt文件内容输入FileInputStream从FileChannel中读取数据,例如将指定的文件输入到FileChannel中,我们可以得到文件的内容,然后写入FileChannelStream的输入核心代码:publicstaticvoidmain(String[]args)throwsIOException{//创建输入流FileInputStreamfileInputStream=newFileInputStream("test-file.txt");//通过输入流获取channelFileChannelfileChannel=fileInputStream.getChannel();//准备好ByteBufferByteBufferbuffer=ByteBuffer.allocate(16);//将输入流的channel的数据读入bufferfileChannel.read(buffer);//简单打印buffer的内容printBuffer(buffer);//shDEQuanZhanBiJi}里面的ByteBuffer是通道读写数据的中介。从通道中读取数据(即上面的例子),需要先将数据读入ByteBuffer;同样,如果要向通道写入数据,也需要先将数据写入ByteBuffer(下面关于输出流时间会讲)。如果你对ByteBuffer不熟悉,可以看看我之前写的《玩转 ByteBuffer》。printBuffer的代码还有一个输出FileOutputStream。顾名思义,就是一个输出数据的FileChannel,比如将数据写入磁盘文件。接下来,让我们看一下示例。作用:publicstaticvoidmain(String[]args)throwsIOException{//指定要生成的文件名StringgenerateFileName="generate-file.txt";//创建输出流FileOutputStreamfileOutputStream=newFileOutputStream(generateFileName);//通过获取channelFileChannelfileChanneloutputstream=fileOutputStream.getChannel();//准备好ByteBuffer并写入数据ByteBufferbuffer=ByteBuffer.allocate(16);buffer.put("shDEQuanZhanBiJi".getBytes(StandardCharsets.UTF_8));//Inputstream数据通道的读入缓冲区fileChannel.write(buffer);fileChannel.close();}相应的代码已经贴上相应的注释,这里不再赘述。唯一需要注意的是,调用write写入文件到磁盘时,也要先传入ByteBuffer。好吧,当你运行完代码后,你会发现虽然生成了文件,但是里面是空白的……这其实涉及到对ByteBuffer的熟悉程度,这是一个埋坑。如果不知道为什么文件为空,可以看上面关于ByteBuffer的文章,答案在下一篇。这是因为当我们创建一个ByteBuffer时,它默认是写模式。这个时候如果我们通过position和limit来读取数据,是读取不到的。所以在调用write之前,我们需要将ByteBuffer切换为read模式。完整代码如下:publicstaticvoidmain(String[]args)throwsIOException{//指定要生成的文件名StringgenerateFileName="generate-file.txt";//创建一个输出流FileOutputStreamfileOutputStream=newFileOutputStream(generateFileName);//通过输出stream获得channelFileChannelfileChannel=fileOutputStream.getChannel();//准备ByteBuffer并写入数据ByteBufferbuffer=ByteBuffer.allocate(16);buffer.put("shDEQuanZhanBiJi".getBytes(StandardCharsets.UTF_8));//将ByteBuffer切换为读模式buffer.flip();//将输入流的channel的数据读入bufferfileChannel.write(buffer);fileChannel.close();}可以看出文件生成并thecontentisavailable:但是,以上两种类型只能写或读。比如你用FileInputStream向通道写入数据,最后程序会抛出NonWritableChannelException,告诉你不能写入。有没有可以写、读、唱的实现?当然是RandomAccessFile。这里提一下,调用write后,并不是马上写入磁盘,也可以存放在操作系统的缓存中。如果您需要立即刷盘,只需调用channel.force(true);如何使用随机访问文件?其实和前面两个类似:publicstaticvoidmain(String[]args)throwsIOException{//指定要生成的文件名StringtargetFileName="target-file.txt";//创建RandomAccessFile并赋予其可读性(r),可写(w)权限RandomAccessFileaccessFile=newRandomAccessFile(targetFileName,"rw");FileChannelfileChannel=accessFile.getChannel();//创建ByteBuffer并写入数据ByteBufferbuffer=ByteBuffer.allocate(16);buffer.put("shDEQuanZhanBiJi".getBytes(StandardCharsets.UTF_8));//切换到缓冲区读取模式buffer.flip();//调用write将缓冲区数据写入通道,然后通道将数据写入磁盘文件fileChannel.write(buffer);//相当于清空bufferbuffer.clear();//将之前写入channel的数据读入bufferfileChannel.read(buffer);//打印buffer的内容printBuffer(buffer);fileChannel.close();}运行后的效果是会生成一个名为target-file.txt的文件,内容为isshDEQuanZhanBiJi.并且控制台会打印出之前写入通道的shDEQuanZhanBiJi。老规矩,详见评论。值得注意的是newRandomAccessFile(targetFileName,"rw");中的rw;.注释中也写了,意思是授予可读可写权限。还值得注意的是,您不能说将rw更改为w。不能这样玩,因为是简单的字符串匹配,只有这么多选项:可以看到模式类型,r是必不可少的。。。:r只能读rw,可以读写rws和rwd的作用和rw大致相同,都是可读可写的。唯一的区别是它们会强制每次更改到磁盘,而rws也会通过操作系统刷写文件的元数据,这意味着会更新文件的更新时间,而rwd不会刷写文件的元数据文件在磁盘上有两个SocketChannels,因为一个负责连接传输,一个负责连接监听,所以放在一起。在这一节中,我们大概会这样做:客户端将文件发送给服务器,但是为了让大家直接运行,客户端并没有从磁盘文件中读取,而是直接使用了ByteBuffer。可以运行后,尝试自己从磁盘加载它。先看代码,先看服务端:ServerSocketChannelpublicstaticvoidmain(String[]args)throwsIOException{//开启一个ServerSocketChannelServerSocketChannelserverSocketChannel=ServerSocketChannel.open();//绑定8080端口serverSocketChannel.bind(newInetSocketAddress);/8080开始接受客户端连接SocketChannelsocketChannel=serverSocketChannel.accept();//获取连接成功System.out.printf("socketChannel%sconnected\n",socketChannel);//准备ByteBuffer从socketChannel读取数据ByteBufferbuffer=ByteBuffer.allocate(16);//开始读取数据System.out.println("beforeread");intread=socketChannel.read(buffer);System.out.printf("readcomplete,readbyteslength:%s\n",read);printBuffer(buffer);}这里我们使用了JavaNIO中默认的阻塞模式,只是做个掩护,如果想让ServerSocketChannel进入非阻塞模式,可以调用:serverSocketChannel.configureBlocking(false);我们开启后是阻塞模式,所以当代码运行到serverSocketChannel.accept();时,会陷入阻塞状态,直到有客户端过来建立连接。同样,read方法也被阻塞。如果客户端还没有写入数据,服务端就会一直阻塞在读中。SocketChannel直接先给出代码:publicstaticvoidmain(String[]args)throwsIOException{//开启一个SocketChannelSocketChannelssocketChannel=SocketChannel.open();//连接到localhost的8080端口socketChannel.connect(newInetSocketAddress("localhost",8080));//准备ByteBufferByteBufferbuffer=ByteBuffer.allocate(16);buffer.put(Charset.defaultCharset().encode("test"));//切换buffer为读模式&写数据到channelbuffer.flip();socketChannel.write(buffer);}先启动服务端,再启动客户端。可以看到服务器端的控制台有如下输出:socketChanneljava.nio.channels.SocketChannel[connectedlocal=/127.0.0.1:8080remote=/127.0.0.1:64373]connectedbeforereadreadcomplete,readbyteslength:4BUFFERVALUE:testDatagram这个比较简单,首先是客户端的代码:publicstaticvoidmain(String[]args)throwsIOException{DatagramChanneldatagramChannel=DatagramChannel.open();//构建缓冲区数据ByteBufferbuffer=ByteBuffer.allocate(16);buffer.put(Charset.defaultCharset().encode("test"));//切换到缓冲区读取模式buffer.flip();datagramChannel.send(buffer,newInetSocketAddress("localhost",8080));}然后是服务器:publicstaticvoidmain(String[]args)throwsIOException{DatagramChanneldatagramChannel=DatagramChannel.open();datagramChannel.bind(newInetSocketAddress(8080));ByteBufferbuffer=ByteBuffer.allocate(16);datagramChannel.receive(缓冲区);printBuffer(缓冲区);}
