代码+画图分析,彻底搞懂nio中channel和buffer

本文的重点不在各种I/O的区别,重点在nio中channel和buffer(提供给大家个工具,可以直观的动态的研究,点击跳转下载链接)的理解,帮助处于懵逼中的小伙伴脱离苦海。

Buffer(缓冲区)

  • 常用ByteBuffer 和 CharBuffer,还有一系列值类型Buffer,可以应用于不同类型。
  • 所有Buffer都是抽象类,无法直接实例化。创建缓冲区要调用XxxBuffer allocate(),参数是缓冲区容量
  • 缓冲区数据存放在内存中,读写效率高。缓冲区有记录指针,能改变读写的起始点,根据不同需求,灵活处理数据。

Buffer参数说明

  • capacity(容量):缓冲区支持的最大容量。
  • position(记录指针位置):是缓冲区读写数据的起始点,初始值为0。position随着数据的加入而改变,例如读取2个数据到Buffer中,则position = 2。
  • limit(界限):是缓冲区读写数据的终止点,limit之后的区域无法访问。
  • mark(标记):mark在0~position之间,设置该值就会把position移动到mark处。

Buffer的常用方法:

  • flip():确定缓冲区数据的起始点和终止点,为输出数据做准备(即写入通道)。此时:limit = position,position = 0。
  • clear():缓冲区初始化,准备再次接收新数据到缓冲区。position = 0,limit = capacity。
  • hasRemaining():判断postion到limit之间是否还有元素。
  • rewind():postion设为0,则mark值无效。
  • limit(int newLt):设置界限值,并返回一个缓冲区,该缓冲区的界限和limit()设置的一样。
  • get()和put():获取元素和存放元素。使用clear()之后,无法直接使用get()获取元素,需要使用get(int index)根据索引值来获取相应元素。

图片理解Buffer读写数据的流程变化

     

 

Channel(通道)

Channel通过Buffer(缓冲区)进行读写操作。read()表示读取通道数据到缓冲区,write()表示把缓冲区数据写入到通道。

Channel需要节点流作为创建基础,例如FileInputStream和FileOutputStream()的getChannel()。RandomAccessFile也能创建文件通道,支持读写模式。通过IO流创建的通道是单向的,使用RandomAccessFile创建的通道支持双向。

通道可以异步读写,异步读写表示通道执行读写操作时,也能做别的事情,解决线程阻塞。如果使用文件管道(FileChannel),建议用RandomAccessFile来创建管道,因为该类支持读写模式以及有大量处理文件的方法。

Channel实现类

FileChannel //读写文件通道
SocketChannel //通过TCP读写网络数据通道
ServerSocketChannel //监听多个TCP连接
DataChannel //通过UDP读写网络数据通道
Pipe.SinkChannel、Pipe.SourceChannel //线程通信管道传输数据

Channel常用方法

read()  //从Buffer中读取数据。
write() //写入数据到Buffer中。
map()   //把管道中部分数据或者全部数据映射成MappedByteBuffer,本质也是一个ByteBuffer。map()方法参数(读写模式,映射起始位置,数据长度)。
force() //强制将此通道的元数据也写入包含该文件的存储设备。

这里先跟大家说下,我们通常操作的channel类型的对象,其实都是不同类型的抽象对象,有些方法最终是调用的不同类型的channel的实现。我们拿file类型的filechannel来说明,关系如下:

java.nio.channels.Channel--------------是个接口interface(继承了Closeable接口)

java.nio.channels.FileChannel----------抽象类(继承自抽象类java.nio.channels.spi.AbstractInterruptibleChannel,而后者抽象类又实现了java.nio.channels.Channel接口),该类的很多方法都是抽象方法,比如:(基础差的同学好好理解下position)

1.position()---返回filechannel调用read(ByteBuffer dst)后,当前指针指向的的位置,也就是说如果分配的缓冲区buffer < filechannel.size()的时候,返回的是本次往buffer里面存的数据所在filechannel的原始数据的位置。直接上代码:

	@Test
	public void testChannelPosition() throws IOException{
		//
		FileChannel inChannel = new FileInputStream("E:/nio-test-files/test.txt").getChannel();
		ByteBuffer buf = ByteBuffer.allocate(2);
		System.out.println("设置缓冲区buffer大小为2");
		int bytesRead = inChannel.read(buf);
		StringBuffer strBuf = new StringBuffer();
		while(bytesRead != -1){
			System.out.println("===========================本轮将channel内容按照buffer指定大小存入缓冲区开始====================");
			buf.flip();
			//全部读出来
			while (buf.hasRemaining()) {
				System.out.println("-------------遍历缓冲区strat-------------");
				System.out.println("解析缓冲区时position=" + inChannel.position());
				char c = (char)buf.get();
				System.out.println("此时读取的是:" + c);
				strBuf.append(c + "");
				System.out.println("-------------遍历缓冲区end-------------");
			}
			System.out.println("遍历完本轮缓冲区后的内容是:" + strBuf.toString()+"    此时position=" + inChannel.position());
			//因为不知道上面的文件有多少字符,所以继续读,直到本次往缓冲区读的字节数为-1才能读完,先clear缓冲区
			buf.clear();
			bytesRead = inChannel.read(buf);
			System.out.println("channel还可以继续read到缓冲区的大小为" + bytesRead);
			System.out.println("继续往缓冲区读指定大小的数据后position=" + inChannel.position());
			System.out.println("===========================本轮channel内容按照buffer指定大小存入缓冲区并解析结束=================");
		}
		System.out.println("最终知道文档内的内容是:" + strBuf.toString());
	} 

运行结果:(分析见底部)

2.size()------返回filechannel的大小;

3.truncate(long size);

4.force(boolean metaData),transferTo(long position, long count, WritableByteChannel target),

5.transferTo(long position, long count, WritableByteChannel target)----JDK解释:此方法不会改变filechannel对象的position;

6.transferFrom(ReadableByteChannel src,long position, long count)----JDK解释:此方法不会改变filechannel对象的position;

7.read(ByteBuffer dst, long position)----JDK解释:此方法不会改变filechannel对象的position;

8.write(ByteBuffer src, long position)----JDK解释:此方法不会改变filechannel对象的position;

sun.nio.ch.FileChannelImpl--------------java.nio.channels.FileChannel类的子类,实现了上面的抽象方法。

在NIO中,channel是数据的载体,需要操作的数据都要装在到channel中,但是!!!channel中的数据我们是无法直接操作,这时我们需要另一个东西,也就是buffer。除了通过channel到channel直接传递数据(transferTo和transferFrom)所有的数据肯定是也必须是通过buffer来获取内容。

下面贴两个类,自己研究下,看下注释就理解了

TestBuffer.java

package nio.day0;

import java.nio.ByteBuffer;

import org.junit.Test;

public class TestBuffer {

	/**
	 * 缓冲区存取的两个方法:
	 * 1.get():获取缓冲区的数据
	 * 2.put():存入数据到缓冲区
	 * 
	 * 缓冲区的4个核心属性:
	 * capacity :容量,缓冲区中最大存储数据的容量,一旦声明,不可改变
	 * limit : 界限,表示 缓冲区中可以操作数据的大小,limit后面的数我们不能读写:因为写(put())完数据之后调用flip()方法,limit的坐标就是刚刚写的结束位置
	 * position : 位置,表示缓冲区中正在操作的位置 ,就是当前所操作的位置
	 * 
	 * mark :标记,表示记录当前position的位置。可以通过reset()方法将position恢复到mark的位置
	 * 
	 * 		0 <= mark <= position <= limit <= capacity
	 * 
	 */
	@Test
	public void testIO1(){
		String str = "abcde";
		//1.分配一个指定大小的缓冲区。   此时,position=0,limit和capacity都是最后一个位置
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		System.out.println("============allocate()分配空间之后=============");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//2.利用put存储数据
		buf.put(str.getBytes());
		//当put写完之后position的位置到达了写的内容的结束位置,limit和capacity位置没变
		System.out.println("============put()写入数据之后=============");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//3.flip,切换读写模式
		buf.flip();
		//调用flip()方法,此缓冲区就进入读模式,position的位置又回到刚刚写的位置的开始,即0的位置,limit的位置到了刚刚写的结束的位置,调用flip之前position的位置
		System.out.println("============flip()让buf缓冲区从写模式变为读模式之后=============");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//4.利用get获取数据,当读完缓冲区数据的时候position又到了数据结束的位置
		byte[] dst = new byte[buf.limit()];//读取所有的数据
		buf.get(dst);
		System.out.println(new String(dst, 0, dst.length));
		System.out.println("============get()完buf缓冲区所有数据之后=============");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//5.rewind()方法,在读模式中,将position的位置重置到0的位置。即可重读状态
		buf.rewind();
		System.out.println("============rewind()在读模式中重置position位置,使缓冲区所有数据可重读=============");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//6.clear()清空缓冲区
		buf.clear();
		//但是clear()方法并不是真正的情况缓冲区,此时缓冲区的数据还在,只是数据处于“被遗忘”状态
		//	===》所谓“被遗忘”只是让position、limit、capacity的指针位置回到了allocate时的位置
		System.out.println("============clear()后只是让position、limit、capacity的指针位置回到了allocate时的位置=============");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		//测试数据还在
		System.out.println("============验证clear()之后数据还在=============");
		System.out.println((char)buf.getChar());
	}
	
	
	@Test
	public void testMark(){
		String str = "abcde";
		ByteBuffer buf = ByteBuffer.allocate(1024);
		buf.put(str.getBytes());
		buf.flip();
		byte[] dst = new byte[buf.limit()];
		//先读取两个字节
		buf.get(dst, 0, 2);
		System.out.println(new String(dst,0,2));
		//打印此时position的位置
		System.out.println(buf.position());
		//mark()记录当前position的位置
		buf.mark();
		//继续读取两个字节
		buf.get(dst, 2, 2);
		System.out.println(new String(dst,2,2));
		//打印此时position的位置
		System.out.println(buf.position());
		//reset()将position恢复到mark()的位置
		buf.reset();
		//打印此时position的位置
		System.out.println(buf.position());
	}
}

TestChannel.java

package nio.day1;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

/**
 * 3种获取通道的方式:
 *    1.java针对支持通道的类提供了getChannel()方法
 *          本地IO:
 *          FileInputStream/FileOutputStream
 *          RandomAccessFile
 *          
 *          网络IO:
 *          Socket
 *          ServerSocket
 *          DatagramSocket
 *    2.JDK1.7中NIO2针对各个通道提供了静态方法open()
 *    3.JDK1.7中NIO2的Files工具类的newByteChannel()
 *    
 *    
 * 通道之间的数据传输
 *   transferFrom
 *   transferTo
 * 
 * @author Admin
 *
 */
public class TestChannel {
	
	
	//通道之间的数据传输(直接缓冲区)
	@Test
	public void test3() throws IOException{
		FileChannel inChannel = FileChannel.open(Paths.get("E:/nio-test-files/nio.jpg"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("E:/nio-test-files/nio-test3.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);//create_new如果已经存在会报错
	
		inChannel.transferTo(0, inChannel.size(), outChannel);
		//outChannel.transferFrom(inChannel, 0, inChannel.size());  //和上面的一样
		System.out.println(inChannel.position());
		inChannel.close();
		outChannel.close();
	}
	
	
	/**
	 * 利用直接缓冲区(os内存)完成文件的复制(内存映射文件)
	 * @throws IOException 
	 */
	@Test
	public void test2() throws IOException{
		FileChannel inChannel = FileChannel.open(Paths.get("E:/nio-test-files/nio.jpg"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("E:/nio-test-files/nio-test2.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);//create_new如果已经存在会报错
	
		//内存映射文件(这个文件和allocateDirect()一样)
		MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
		MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());//MapMode.READ_WRITE对应了open中的StandardOpenOption.READ, StandardOpenOption.WRITE
	
		//直接对缓冲区进行数据的读写操作
		byte[] dst = new byte[inMappedBuf.limit()];
		inMappedBuf.get(dst); //src.get(dst)表示This method transfers bytes from this buffer(src) into the given destination(dst) array
		outMappedBuf.put(dst);//This method transfers the entire content of the given sourcebyte array into this buffer
		
		inChannel.close();
		outChannel.close();
	}
	
	
	
	/**
	 * 利用通道完成文件的复制(利用非直接缓冲区buffer)
	 * @throws IOException 
	 */
	@Test
	public void test1() throws IOException{
		//1.通过打开与实际文件的连接创建一个 FileInputStream 
		FileInputStream fileInputStream = new FileInputStream("E:/nio-test-files/nio.jpg");
		FileOutputStream fileOutputStream = new FileOutputStream("E:/nio-test-files/nio-test1.jpg");
		
		//2.从流中获取channel
		FileChannel inChannel = fileInputStream.getChannel();
		FileChannel outChannel = fileOutputStream.getChannel();
		
		//3.分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//4.将通道中的数据存入缓冲区
		while(inChannel.read(buf) != -1){
			//5.将缓冲区的数据写入通道中
			buf.flip();
			outChannel.write(buf);
			buf.clear();
		}
		inChannel.close();
		outChannel.close();
		fileInputStream.close();
		fileOutputStream.close();
	}
	
	
	@Test
	public void test() throws IOException {
		//第一种可以通过new FileInputStream(path)(或者FileOutStream(path)).getChnanel()方式获取FileChannel
		//FileChannel inChannel = new FileInputStream("E:/nio-test-files/test.txt").getChannel();
		//第二种通过new RandomAccessFile("E:/nio-test-files/test.txt", "rw").getChnannel()获取FileChannel
		RandomAccessFile aFile = new RandomAccessFile("E:/nio-test-files/test.txt", "rw");
		FileChannel inChannel = aFile.getChannel();
		
		ByteBuffer buf = ByteBuffer.allocate(13);
		int bytesRead = inChannel.read(buf);//从channel往buf里面存,相当于buf的put
		while (bytesRead != -1) {
			System.out.println("Read " + bytesRead);
			buf.flip();//buf切换读写模式

			while (buf.hasRemaining()) {
				//System.out.println(inChannel.position());
				System.out.print((char) buf.get());//从buf里面取,get
			}
			//System.out.println(inChannel.position());
			buf.clear();//重新往buffer里面存数据必须先清空缓冲区,重置buffer的position,limit,size信息
			bytesRead = inChannel.read(buf);
		}
		/*while(inChannel.read(buf) != -1){
			System.out.println(inChannel.read(buf));
			buf.flip();
			while (buf.hasRemaining()) {
				System.out.print((char) buf.get());
			}
			buf.clear();
		}*/
		aFile.close();

	}
	
	
	@Test
	public void testWrite() throws IOException{
		String str = "ABDC";
		FileOutputStream fileOutputStream = new FileOutputStream("E:/nio-test-files/test-out.txt") ;
		ByteBuffer buf = ByteBuffer.allocate(1024);
		buf.put(str.getBytes());//buf的put
		buf.flip();//切换读写模式
		FileChannel channel = fileOutputStream.getChannel();
		channel.write(buf);//从buf中get数据,写入channel
		fileOutputStream.close();
		channel.close();
	}
	
	@Test
	public void testChannelPosition() throws IOException{
		//
		FileChannel inChannel = new FileInputStream("E:/nio-test-files/test.txt").getChannel();
		ByteBuffer buf = ByteBuffer.allocate(2);
		System.out.println("设置缓冲区buffer大小为2");
		int bytesRead = inChannel.read(buf);
		StringBuffer strBuf = new StringBuffer();
		while(bytesRead != -1){
			System.out.println("===========================本轮将channel内容按照buffer指定大小存入缓冲区开始====================");
			buf.flip();
			//全部读出来
			while (buf.hasRemaining()) {
				System.out.println("-------------遍历缓冲区strat-------------");
				System.out.println("解析缓冲区时position=" + inChannel.position());
				char c = (char)buf.get();
				System.out.println("此时读取的是:" + c);
				strBuf.append(c + "");
				System.out.println("-------------遍历缓冲区end-------------");
			}
			System.out.println("遍历完本轮缓冲区后的内容是:" + strBuf.toString()+"    此时position=" + inChannel.position());
			//因为不知道上面的文件有多少字符,所以继续读,直到本次往缓冲区读的字节数为-1才能读完,先clear缓冲区
			buf.clear();
			bytesRead = inChannel.read(buf);
			System.out.println("channel还可以继续read到缓冲区的大小为" + bytesRead);
			System.out.println("继续往缓冲区读指定大小的数据后position=" + inChannel.position());
			System.out.println("===========================本轮channel内容按照buffer指定大小存入缓冲区并解析结束=================");
		}
		System.out.println("最终知道文档内的内容是:" + strBuf.toString());
	} 

}

补上刚刚上面对于position的分析过程:

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值