1. 通道的概念
通道(Channel):由 java.nio.channels 包定义的。 Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据, Channel 只能与Buffer 进行交互。
2. 主要实现类
Java 为 Channel 接口提供的最主要实现类如下:
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:通过 UDP 读写网络中的数据通道。
- SocketChannel:通过 TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
3. 获取通道
获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:
FileInputStream、FileOutputStream、RandomAccessFile、DatagramSocket、Socket 和 ServerSocket。
获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道。
4. FileChannel 通道的数据传输
下图所示的是FileChannel类的一些常用方法:
4.1 文件复制
示例一:下面演示一下使用通道和非直接缓冲区实现文件的复制:
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
| @Test public void fileCopy() { FileInputStream fis = null; FileOutputStream fos = null; FileChannel inChannel = null; FileChannel outChannel = null; try { try { fis = new FileInputStream("1.txt"); fos = new FileOutputStream("2.txt");
inChannel = fis.getChannel(); outChannel = fos.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
while (inChannel.read(buf) != -1) { buf.flip(); outChannel.write(buf); buf.clear(); } } finally { if (outChannel != null) outChannel.close(); if (inChannel != null) inChannel.close(); if (fos != null) fos.close(); if (fis != null) fis.close(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
|
示例二:下面演示一下使用通道和直接缓冲区实现文件的复制:
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
| @Test public void fileCopyDirect() { try { FileChannel inChannel = null; FileChannel outChannel = null; try { inChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get("3.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); byte[] dst = new byte[inMappedBuf.limit()]; inMappedBuf.get(dst); outMappedBuf.put(dst); } finally { if (inChannel != null) inChannel.close(); if (outChannel != null) outChannel.close(); } } catch (IOException e) { e.printStackTrace(); } }
|
示例三:下面演示一下使用通道之间的数据传输来实现文件的复制:
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
| @Test public void fileCopyDirect2() { try { FileChannel inChannel = null; FileChannel outChannel = null; try { inChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get("5.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
outChannel.transferFrom(inChannel, 0, inChannel.size()); } finally { inChannel.close(); outChannel.close(); }
} catch (IOException e) { e.printStackTrace(); } }
|
4.2 分散(Scatter)和聚集(Gather)
分散读取(Scattering Reads)是指从 Channel 中读取的数据“分散” 到多个 Buffer 中。需要按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。而聚集写入(Gathering Writes)是指将多个 Buffer 中的数据“聚集”到 Channel。按照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel 。如下图示:
下面演示分散读取和聚集写入的示例,具体代码如下所示:
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
| @Test public void ScatteringRead() { try { RandomAccessFile raf = null; FileChannel channel = null; RandomAccessFile raf2 = null; FileChannel channel2 = null; try { raf = new RandomAccessFile("1.txt", "rw"); channel = raf.getChannel(); ByteBuffer buf1 = ByteBuffer.allocate(10); ByteBuffer buf2 = ByteBuffer.allocate(100); ByteBuffer[] bufs = {buf1, buf2}; channel.read(bufs); for (ByteBuffer byteBuffer : bufs) { byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("----------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); raf2 = new RandomAccessFile("2.txt", "rw"); channel2 = raf2.getChannel(); channel2.write(bufs); } finally { if (channel != null) channel.close(); if (raf != null) raf.close(); if (channel2 != null) channel2.close(); if (raf2 != null) raf2.close(); } } catch (IOException e) { e.printStackTrace(); } }
|
控制台输出如下结果:
1 2 3 4 5
| asfasfasfa ---------------- sf‘sa 阿是大师大师的 啊实打实的看见爱好
|
文件 2.txt保存有文件1.txt中的如下数据,同时也是从多个缓冲区中读到的数据。
1 2 3 4
| asfasfasfa sf‘sa 阿是大师大师的 啊实打实的看见爱好
|
5. 字符集 Charset
Charset类提供了一个availableCharsets静态方法来获取当前JDK所支持的所有字符集。下面代码用于获取输出该JDK所支持的所有字符集:
1 2 3 4 5 6 7 8 9 10
| @Test public void getCharsets() { SortedMap<String,Charset> map = Charset.availableCharsets(); Set<Entry<String,Charset>> set = map.entrySet(); for (Entry<String, Charset> entry : set) { System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
运行结果:
1 2 3 4 5 6 7 8
| Big5=Big5 Big5-HKSCS=Big5-HKSCS CESU-8=CESU-8 EUC-JP=EUC-JP EUC-KR=EUC-KR GB18030=GB18030 GB2312=GB2312 ...
|
下面演示一下对缓冲区的数据进行指定编码和解码的操作:
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
| @Test public void testEncoding() throws CharacterCodingException { Charset cs = Charset.forName("GBK"); CharsetEncoder encoder = cs.newEncoder(); CharsetDecoder decoder = cs.newDecoder(); CharBuffer cBuf = CharBuffer.allocate(1024); cBuf.put("你好"); cBuf.flip(); ByteBuffer bBuf = encoder.encode(cBuf); for (int i = 0; i < bBuf.limit(); i++) { System.out.print(bBuf.get()); } bBuf.flip(); CharBuffer cBuf2 = decoder.decode(bBuf); System.out.println(cBuf2.toString()); bBuf.flip(); Charset csUTF8 = Charset.forName("ISO-8859-1"); CharsetDecoder decoderUTF8 = csUTF8.newDecoder(); CharBuffer cBufUTF8 = decoderUTF8.decode(bBuf); System.out.println(cBufUTF8.toString()); }
|
注意:要时刻注意缓冲区中position的位置!无论是get方法、put方法还是encode方法和decode方法都会改变缓冲区中的position位置。
对于 DatagramChannel、SocketChannel 和 ServerSocketChannel 这三个通道类将放于下一篇博客讲。