-
allocate(int capacity)
: 分配指定大小的缓冲区(非直接缓冲区) -
allocateDirect(int capacity)
: 分配指定大小的缓冲区(直接缓冲区) -
put()
:向缓冲区中存储数据 -
get(byte[] dst)
:从缓冲区获取数据,这里的dst的容量必须和缓冲区的大小一致 -
get(byte[] dst,int offest,int length)
:读取指定长度的内容到dst中,这里的dst容量没有要求 -
flip()
: 缓冲区从写模式切换到读模式 -
clear()
:清空缓冲区,数据依然存在,只是处于一个“被遗忘”状态,改变的只是limit
和position
-
array()
:返回实现此缓冲区的 byte 数组 -
mark()
: 标记当前位置(position) -
reset()
:恢复到mark的位置
核心属性
capacity limit position position<=limit<=capacity
@Test public void test1(){ String str="abcd"; ByteBuffer buffer=ByteBuffer.allocate(1024);//分配1024个字节大小的缓冲区 buffer.put(str.getBytes()); //写入数据 System.out.println(buffer.capacity()); //容量 1024 System.out.println(buffer.limit()); //界限,1024 System.out.println(buffer.position()); //正在操作数据的位置 0 buffer.flip(); //切换到读模式,读取数据的时候一定要切换,否则将会没有界限 System.out.println(buffer.capacity()); //容量 1024 System.out.println(buffer.limit()); //界限,4,允许读取的位置只能到4,因为就存储了这么多的数据 System.out.println(buffer.position()); //正在操作数据的位置 0 System.err.println(buffer.get(4)); //超出界限了,下标记从0开始,0<=index<limit }
- 实例
/** * 读取缓冲区中的数据到指定的字节数组中 * 1、字节数组的大小一定要和buffer.limit()一样大小,否则会报错 */ @Test public void test2(){ String str="abcdefg"; ByteBuffer buffer=ByteBuffer.allocate(1024); //申请空间大小 buffer.put(str.getBytes()); //存入数据 buffer.flip(); //切换到读模式 //申请一个字节数组和实际数据一样大,这里必须和缓冲区的实际数据大小一样,否则将会报错 byte[] dst=new byte[buffer.limit()]; buffer.get(dst); //读取缓冲区的数据到dst字节数组中 System.out.println(new String(dst)); } /** * 读取一个字节 */ @Test public void test3(){ String str="abcdefg"; ByteBuffer buffer=ByteBuffer.allocate(10); //申请空间大小 buffer.put(str.getBytes()); //存入数据 buffer.flip(); //切换到读模式 System.out.println((char)buffer.get()); }
- 测试remark和reset
/** * 测试remark和rest */ @Test public void test1(){ ByteBuffer buffer=ByteBuffer.allocate(1024); String str="abcdcdscdscds"; buffer.put(str.getBytes()); //向缓冲区中写入数据 buffer.flip(); //切换到读的模式 byte[] dst=new byte[1024]; //创建byte数组 System.out.println("---------------------读取两个字节的数据----------------------------"); buffer.get(dst,0,2); //读取两个字节长度的数据到dst中,此时的position的位置位2 System.out.println(new String(dst)); System.out.println("----------------------标记此时的位置------------------------------"); buffer.mark(); //标记位置,此时的position的位置位2 System.out.println("---------------------继续读取两个字节的数据----------------------------"); buffer.get(dst,buffer.position(),2); //继续从当前位置读取两个字节到dst中 System.out.println(new String(dst)); System.out.println(buffer.position()); //此时的position的位置为4 System.out.println("---------------------重置缓冲区到remark的位置----------------------------"); buffer.reset(); //重置缓冲区到rmark的位置 System.out.println(buffer.position()); //此时的position为2 }
直接缓冲区
- 直接字节缓冲区可以通过调用此类的
allocateDirect()
工厂方法 来创建。此方法返回的 缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区 。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的机 本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
非直接缓冲区
- 在JVM中内存中创建,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在JVM内,因此销毁容易,但是占用JVM内存开销,处理过程中有复制操作。
-
- 写入步骤如下:
- 创建一个临时的直接ByteBuffer对象。
- 将非直接缓冲区的内容复制到临时缓冲中。
- 使用临时缓冲区执行低层次I/O操作。
- 临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
- 写入步骤如下:
通道(Channel)
- 通道是双向的,流是单向的
- 通道相当于输出和输入流
- 主要的实现类如下:
FileChannel SocketChannel ServerSocketChannel DatagramChannel
获取通道
- 本地IO,提供了
getChannel()
方法获取通道FileInputStream FileOutputStram RandomAccessFile
- 在JDK1.7中的NIO,针对各个通道提供了静态方法
open()
- 在JDK1.7中的NIO的Files工具类的
newByteChannel()
实例
- 利用通道实现文件的复制(非直接缓冲区)
/** * 使用getChannel获取通道,实现文件的复制 * @throws IOException */ @Test public void test1()throws IOException{ FileInputStream inputStream=new FileInputStream(new File("C:/images/lifecrystal.png")); FileOutputStream outputStream=new FileOutputStream(new File("C:/images/2.png")); FileChannel inchannel = inputStream.getChannel(); //获取通道,用于读取 FileChannel outchannel=outputStream.getChannel(); //获取通道,用于写入 ByteBuffer buffer=ByteBuffer.allocate(1024); //申请缓冲区 //将通道中的数据写入缓冲区 while (inchannel.read(buffer)!=-1) { buffer.flip(); //切换到读模式 //将缓冲区中的数据写入通道 outchannel.write(buffer); buffer.clear(); //清空缓冲区,继续读取数据 } //关闭通道 inchannel.close(); outchannel.close(); inputStream.close(); outchannel.close(); }
- 使用直接缓冲区完成文件的复制,使用open()的方法获取通道
/** * 使用直接缓冲区完成文件的复制 * 使用open()的方法获取通道 * @throws IOException */ @Test public void test2()throws IOException{ //获取一个读取数据的通道,使用的读模式 FileChannel inchannel=FileChannel.open(Paths.get("C:/images/2.png"), StandardOpenOption.READ); /** * StandardOpenOption.CREATE : 如果文件不存在,那么就创建,如果存在将会覆盖,不报错 * StandardOpenOption.CREATE_NEW : 如果不存在就创建,如果存在,将会报错 */ FileChannel outchannel=FileChannel.open(Paths.get("C:/images/3.png"), StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.READ); //创建一个内存映射文件,操作直接缓冲区,和allocatDirect()一样,MapMode.READ_ONLY表示只读的模式,用于读取 MappedByteBuffer inMappedBuff = inchannel.map(MapMode.READ_ONLY, 0, inchannel.size()); //创建一个内容映射文件,MapMode.READ_WRITE表示读写模式,可以读写 MappedByteBuffer outMappedBuffer = outchannel.map(MapMode.READ_WRITE, 0, inchannel.size()); byte[] dst=new byte[inMappedBuff.limit()]; //将数据读入到dst中 inMappedBuff.get(dst); //将数据从dst中读取到outMappedBuffer outMappedBuffer.put(dst); }
通道之间指定进行数据传输
transferTo(long position,long count,WritableByteChannel target) transferFrom(ReadableByteChannel from,long position,long count)
/** * 通道之间直接进行传输 * 1、transferTo(long position,long count,WritableByteChannel target):将数据从通道写入可写的通道target中 * 2、transferFrom(ReadableByteChannel from,long position,long count):将数据从通道from中读取到通道中 * @throws IOException */ @Test public void test3()throws IOException{ //获取一个读取数据的通道,使用的读模式 FileChannel inchannel = FileChannel.open(Paths.get("C:/images/2.png"), StandardOpenOption.READ); /** * StandardOpenOption.CREATE : 如果文件不存在,那么就创建,如果存在将会覆盖,不报错 * StandardOpenOption.CREATE_NEW : 如果不存在就创建,如果存在,将会报错 */ FileChannel outchannel=FileChannel.open(Paths.get("C:/images/4.png"), StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.READ); //将通道inchannel中的数据直接写入outchannel中 inchannel.transferTo(0, inchannel.size(), outchannel); //和上面一样的效果 // outchannel.transferFrom(inchannel, 0, inchannel.size()); inchannel.close(); outchannel.close(); }
分散读取
- 将通道中的数据分散到各个缓冲区中
/** * 分散读取:将通道中的数据写入各个缓冲区中,是按照顺序写入的,第一个缓冲区写满才会写入第二个缓冲区 * @throws IOException */ @Test public void test4()throws IOException{ //创建读写模式的RandomAccessFile RandomAccessFile accessFile=new RandomAccessFile(new File("C:/images/2.png"), "rw"); FileChannel inchannel=accessFile.getChannel(); //读取 ByteBuffer buffer1=ByteBuffer.allocate(10); //第一个缓冲区,10个字节大小 ByteBuffer buffer2=ByteBuffer.allocate(1024);//第二个缓冲区 ByteBuffer[] dst={buffer1,buffer2}; //分散读取 inchannel.read(dst); for (ByteBuffer byteBuffer : dst) { byteBuffer.flip(); //切换到读的模式 } //输出第一个缓冲区的数据 System.out.println(new String(buffer1.array())); //输出第二个缓冲区中的数据 System.out.println(new String(buffer2.array())); }
聚集写入
- 将各个缓冲区的数据读入到通道中
@Test public void test4()throws IOException{ //创建读写模式的RandomAccessFile RandomAccessFile accessFile=new RandomAccessFile(new File("C:/images/2.png"), "rw"); FileChannel inchannel=accessFile.getChannel(); //读取 ByteBuffer buffer1=ByteBuffer.allocate(10); //第一个缓冲区,10个字节大小 ByteBuffer buffer2=ByteBuffer.allocate(1024);//第二个缓冲区 ByteBuffer[] dst={buffer1,buffer2}; //分散读取 inchannel.read(dst); for (ByteBuffer byteBuffer : dst) { byteBuffer.flip(); //切换到读的模式 } //输出第一个缓冲区的数据 System.out.println(new String(buffer1.array())); //输出第二个缓冲区中的数据 System.out.println(new String(buffer2.array())); System.out.println("---------------------聚集写入-------------------"); RandomAccessFile accessFile2=new RandomAccessFile(new File("C:/images/6.png"), "rw"); FileChannel outChannel=accessFile2.getChannel(); //写入数据的通道 //聚集写入,将数据从各个缓冲区中写入到通道中 outChannel.write(dst); inchannel.close(); outChannel.close(); }
NIO阻塞式
- 阻塞或者不阻塞是针对
SocketChannel
,ServerSocketChannel
- NIO中的套接字可以轻松在阻塞和非阻塞之间切换,这里我们使用NIO实现阻塞式的TCP数据传输
/** * 客户端使用SocketChannel * 客户端使用SocketChannel中的write()方法向服务端发送数据,使用read()读取服务端返回的反馈 * 在数据发送完成之后如果不调用shutdownOutput告知服务端数据已传送完成,那么将会一直阻塞下去 * @throws Exception */ @Test public void testClient()throws Exception{ //获取通道 SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898)); FileChannel inchannel = FileChannel.open(Paths.get("C:/images/2.png"), StandardOpenOption.READ); ByteBuffer buffer=ByteBuffer.allocate(1024); //循环读取本地图片,并且发送到服务端 //1、先使用FileChannel将数据读取到缓冲区中 //2、再使用SocketChannel的write方法将缓冲区的数据发送到服务端 while(inchannel.read(buffer)!=-1){ buffer.flip(); //切换读模式 clientChannel.write(buffer); //发送数据 buffer.clear(); //清空缓冲区 } //告诉服务端数据已经传送完成,否则将会一直阻塞 clientChannel.shutdownOutput(); //接收服务端的反馈 //使用read()方法接收服务端的反馈,将其读入到缓冲区中 while(clientChannel.read(buffer)>0){ buffer.flip(); //切换读模式 System.out.println("服务端:"+new String(buffer.array())); buffer.clear(); } //关闭通道 inchannel.close(); clientChannel.close(); } /** * 服务端使用ServerSocketChannel * 服务端使用SocketChannel的read()方法读取客户端发送的数据,使用write()方法向客户端返回数据 * @throws Exception */ @Test public void testServer()throws Exception{ //获取服务端的通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); //绑定连接 serverChannel.bind(new InetSocketAddress(9898)); //获取客户端的连接通道 SocketChannel clientChannel = serverChannel.accept(); //申请缓冲区 ByteBuffer byteBuffer=ByteBuffer.allocate(1024); FileChannel outChannel = FileChannel.open(Paths.get("C:/images/12.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE); //循环接收客户端发送过来的数据,并且将其保存在本地 while(clientChannel.read(byteBuffer)>0){ byteBuffer.flip(); //切换读模式 outChannel.write(byteBuffer); //写入到本地 byteBuffer.clear(); //清空缓冲区 } //服务端发送反馈信息给客户端,使用的还是SocketChannel的write方法 byteBuffer.put("服务端接收数据成功".getBytes()); byteBuffer.flip(); //切换模式 clientChannel.write(byteBuffer); clientChannel.shutdownOutput(); //告知客户端传输完成 //关闭通道 outChannel.close(); clientChannel.close(); serverChannel.close(); }
Selector(选择器)
- 总的来说,选择器是对通道进行监听,这样就会避免阻塞的发生,实现了多路复用
SelectionKey
-
某个Channel成功连接到另一个服务器称为“ 连接就绪 ”。一个Server Socket Channel准备好接收新进入的连接称为“ 接收就绪 ”。一个有数据可读的通道可以说是“ 读就绪 ”。等待写数据的通道可以说是“ 写就绪 ”。
-
选择器是用来轮询监听通道的状态,其中有四种状态如下:
SelectionKey.OP_CONNECT SelectionKey.OP_ACCEPT SelectionKey.OP_READ SelectionKey.OP_WRITE
NIO非阻塞式
/** * 客户端需要使用configureBlocking(false)设置成非阻塞模式的 * @throws Exception */ @Test public void testClient()throws Exception{ //获取通道 SocketChannel client = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898)); //切换成非阻塞模式 client.configureBlocking(false); ByteBuffer buf=ByteBuffer.allocate(1024); //申请缓冲区 Scanner scanner=new Scanner(System.in); while(scanner.hasNext()){ String line=scanner.next(); //读取控制台输入的内容 buf.put((new Date().toString()+"\n"+line).getBytes()); //向缓冲区写入数据 buf.flip(); //切换到读模式 client.write(buf); //向服务端发送数据 buf.clear(); //清空缓存区 } scanner.close(); client.close(); } /** * 服务端 * 1、将通道注册到选择器中,并且指定监听的事件 * 2、程序每次都会轮询的从选择器中选择事件,可以选择不同状态的通道进行操作 * @throws Exception */ @Test public void testServer()throws Exception{ //获取通道 ServerSocketChannel server = ServerSocketChannel.open(); //绑定端口 server.bind(new InetSocketAddress(9898)); //配置非阻塞 server.configureBlocking(false); //获取选择器 Selector selector = Selector.open(); //将通道注册到选择器上,并且知道指定监听的事件为"接收就绪" server.register(selector, SelectionKey.OP_ACCEPT); //轮询式获取选择器上已经准备就绪的事件 while(selector.select()>0){ //获取当前选择器中所有的选择键(已经准备就绪的) Set<SelectionKey> keys = selector.selectedKeys(); //获取迭代器 Iterator<SelectionKey> iterator = keys.iterator(); //迭代器遍历所有的选择键 while(iterator.hasNext()){ //获取当前选择键 SelectionKey key = iterator.next(); iterator.remove(); //删除选择键 if (key.isAcceptable()) { //如果接收就绪了 SocketChannel client = server.accept(); //获取SocketChannel client.configureBlocking(false); //设置非阻塞模式 client.register(selector, SelectionKey.OP_READ); //将此通道注册到选择器中,指定监听的事件是读就绪 }else if(key.isReadable()){ //如果读就绪 SocketChannel client = (SocketChannel) key.channel(); //读就绪了,那么可以获取通道直接读取数据 ByteBuffer buf=ByteBuffer.allocate(1024); //声明一个缓冲区 //循环接收客户端的到缓冲区中 int len=0; while((len=client.read(buf))>0){ buf.flip(); System.out.println(new String(buf.array(),0,len)); buf.clear(); } }else if (key.isWritable()) { //如果写就绪 }else if (key.isConnectable()) { //如果连接就绪 } } } server.close(); }
参考文章
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。