NIO学习-03通道

版权声明:本文为博主原创文章,转载请说明出处。 https://blog.csdn.net/weixin_43549578/article/details/85110815

    通道(Channel):由 java.nio.channels 包定义的。Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据,Channel 只能与Buffer 进行交互

 

使用了通道进行连接,缓存使用了非直接缓存区形式。 

Java为  Channel  接口提供的最主要实现类如下:

java.nio.channels.Channel 接口:
    |--FileChannel  用于读取、写入、映射和操作文件的通道。
    |--SocketChannel  通过 TCP 读写网络中的数据。
    |--ServerSocketChannel 可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
    |--DatagramChannel  通过 UDP 读写网络中的数据通道。

获取通道:

1. Java 针对支持通道的类提供了 getChannel() 方法
    本地 IO:
          FileInputStream/FileOutputStream
          RandomAccessFile
 
    网络IO:
          Socket
          ServerSocket
          DatagramSocket
2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()
3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()

数据传输:

   1.将 Buffer 中数据写入 Channel:getChannel().write(bufs);

   2.从 Channel 读取数据到 Buffer:getChannel().read(bufs)

   3.transferFrom():将数据从源通道传输到其他 Channel 中。

   4.transferTo():将数据从源通道传输他 到其他  Channel中。

通道模式:分散(Scatter)与聚集(Gather)

分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中。

注意:按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。

 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中。

注意:按照缓冲区的顺序写入 position 和 limit 之间的数据到 Channel

 FileChannel  的常用方法:

int read(ByteBuffer dst) 从 从  Channel 到 中读取数据到  ByteBuffer
long  read(ByteBuffer[] dsts) 将 将  Channel 到 中的数据“分散”到  ByteBuffer[]
int  write(ByteBuffer src) 将 将  ByteBuffer 到 中的数据写入到  Channel
long write(ByteBuffer[] srcs) 将 将  ByteBuffer[] 到 中的数据“聚集”到  Channel
long position() 返回此通道的文件位置
FileChannel position(long p) 设置此通道的文件位置
long size() 返回此通道的文件的当前大小
FileChannel truncate(long s) 将此通道的文件截取为给定大小
void force(boolean metaData) 强制将所有对此通道的文件更新写入到存储设备中

 字符集:Charset 也支持通道。

   编码:字符串 -> 字节数组

       解码:字节数组 -> 字符串

案例:

  FileChannel中的transferTo方法并不一定能完整传输所有数据。在文档中解释如下:

/**
	 * 将字节从此通道的文件传输到给定的可写入字节通道。
	 * 试图读取从此通道的文件中给定 position 处开始的 count 个字节,并将其写入目标通道。
	 * 此方法的调用不一定传输所有请求的字节;是否传输取决于通道的性质和状态。
	 * 如果此通道的文件从给定的 position 处开始所包含的字节数小于 count 个字节,
	 * 或者如果目标通道是非阻塞的并且其输出缓冲区中的自由空间少于 count 个字节,则所传输的字节数要小于请求的字节数。
	 * 此方法不修改此通道的位置。如果给定的位置大于该文件的当前大小,则不传输任何字节
	 * 。如果目标通道中有该位置,则从该位置开始写入各字节,然后将该位置增加写入的字节数。
	 * 与从此通道读取并将内容写入目标通道的简单循环语句相比,此方法可能高效得多。
	 * 很多操作系统可将字节直接从文件系统缓存传输到目标通道,而无需实际复制各字节。
	 * 参数:
	 * position - 文件中的位置,从此位置开始传输;必须为非负数
	 * count - 要传输的最大字节数;必须为非负数
	 * target - 目标通道
	 * 返回:
	 * 实际已传输的字节数,可能为零
	 */
public abstract long transferTo(long position, long count,
                                    WritableByteChannel target)
        throws IOException;

 transferTo/transferFrom测试:

@Test
	public void test3() throws IOException{
		FileChannel inChannel = FileChannel.open(Paths.get("d:/1.txt"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("d:/2.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

//		inChannel.transferTo(0, inChannel.size(), outChannel);
		outChannel.transferFrom(inChannel, 0, inChannel.size());

		inChannel.close();
		outChannel.close();
	}

  

执行结果:

  

利用通道完成文件的复制(非直接缓冲区):
@Test
	public void test1(){
		long start = System.currentTimeMillis();

		FileInputStream fis = null;
		FileOutputStream fos = null;
		//①获取通道
		FileChannel inChannel = null;
		FileChannel outChannel = null;
		try {
			fis = new FileInputStream("d:/1.txt");
			fos = new FileOutputStream("d:/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(); //清空缓冲区
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(outChannel != null){
				try {
					outChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if(inChannel != null){
				try {
					inChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if(fos != null){
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if(fis != null){
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		long end = System.currentTimeMillis();
		System.out.println("耗费时间为:" + (end - start));

	}
使用直接缓冲区完成文件的复制(内存映射文件):
@Test
	public void test2() throws IOException{
		long start = System.currentTimeMillis();

		FileChannel inChannel = FileChannel.open(Paths.get("d:/1.txt"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("d:/2.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);

		inChannel.close();
		outChannel.close();

		long end = System.currentTimeMillis();
		System.out.println("耗费时间为:" + (end - start));
	}
分散和聚集:
@Test
	public void test4() throws IOException{
		RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");

		//1. 获取通道
		FileChannel channel1 = raf1.getChannel();

		//2. 分配指定大小的缓冲区
		ByteBuffer buf1 = ByteBuffer.allocate(100);
		ByteBuffer buf2 = ByteBuffer.allocate(1024);

		//3. 分散读取
		ByteBuffer[] bufs = {buf1, buf2};
		channel1.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()));

		//4. 聚集写入
		RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
		FileChannel channel2 = raf2.getChannel();

		channel2.write(bufs);
	}

获取当前所有的编码格式:

Map<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());
}

编码与解码:

@Test
	public void test6() throws IOException{
		Charset cs1 = Charset.forName("GBK");

		//获取编码器
		CharsetEncoder ce = cs1.newEncoder();

		//获取解码器
		CharsetDecoder cd = cs1.newDecoder();
        //创建缓存
		CharBuffer cBuf = CharBuffer.allocate(1024);
		cBuf.put("大吉大利,今晚吃鸡!");
		cBuf.flip();

		//编码
		ByteBuffer bBuf = ce.encode(cBuf);

		for (int i = 0; i < bBuf.array().length; i++) {
			System.out.println(bBuf.get());
		}
		//解码
		bBuf.flip();
		CharBuffer cBuf2 = cd.decode(bBuf);
		System.out.println(cBuf2.toString());

		System.out.println("------------------------------------------------------");
		//第二种解码:按照GBK解码 使用当前解码器(GBK)
		Charset cs2 = Charset.forName("GBK");
		bBuf.flip();
		CharBuffer cBuf3 = cs2.decode(bBuf);
		System.out.println(cBuf3.toString());
	}

猜你喜欢

转载自blog.csdn.net/weixin_43549578/article/details/85110815