5. NIO三核心(Buffer、Channel)

本章内容:

  1.Buffer

  2.Channel


一、Buffer

1.基本介绍

  缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer。

2.BUffer及其字类

  ByteBuffer,ShortBuffer,CharBuffer,IntBuffer,LongBuffer,DoubleBuffer,FloatBuffer:存储…到缓冲区。


3.Buffer类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:

1 // Invariants: mark <= position <= limit <= capacity
2     private int mark = -1;
3     private int position = 0;
4     private int limit;
5     private int capacity;

4.Buffer类方法

 1 public abstract class Buffer {
 2     //JDK1.4时,引入的api
 3     public final int capacity( )//返回此缓冲区的容量
 4     public final int position( )//返回此缓冲区的位置
 5     public final Buffer position (int newPositio)//设置此缓冲区的位置
 6     public final int limit( )//返回此缓冲区的限制
 7     public final Buffer limit (int newLimit)//设置此缓冲区的限制
 8     public final Buffer mark( )//在此缓冲区的位置设置标记
 9     public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置
10     public final Buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
11     public final Buffer flip( )//反转此缓冲区
12     public final Buffer rewind( )//重绕此缓冲区
13     public final int remaining( )//返回当前位置与限制之间的元素数
14     public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素
15     public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区
16  
17     //JDK1.6时引入的api
18     public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
19     public abstract Object array();//返回此缓冲区的底层实现数组
20     public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
21     public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
22 }

 代码示例:

 1 import java.nio.IntBuffer;
 2 public class BasicBuffer {
 3     public static void main(String[] args) {
 4 
 5         //举例说明Buffer 的使用 (简单说明)
 6         //创建一个Buffer, 大小为 5, 即可以存放5个int
 7         IntBuffer intBuffer = IntBuffer.allocate(5);
 8 
 9         //向buffer 存放数据
10 //        intBuffer.put(10);
11 //        intBuffer.put(11);
12 //        intBuffer.put(12);
13 //        intBuffer.put(13);
14 //        intBuffer.put(14);
15         for(int i = 0; i < intBuffer.capacity(); i++) {
16             intBuffer.put( i * 2);
17         }
18 
19         //如何从buffer读取数据
20         //将buffer转换,读写切换(!!!)
21         /*
22         public final Buffer flip() {
23             limit = position; //读数据不能超过5
24             position = 0;
25             mark = -1;
26             return this;
27         }
28          */
29         //buffer:0 2 4 6 8
30         intBuffer.flip();//进行读写切换★★★★★
31         intBuffer.position(1);//指定读取1位置的数字
32         System.out.println(intBuffer.get());//2
33         intBuffer.limit(3);//表示缓冲区的终点是位置3
34         while (intBuffer.hasRemaining()) {
35             //get()每次取完就往后移,上面已经读取完1位置上的数据,并且限制在3之前
36             System.out.println(intBuffer.get());//4
37         }
38     }
39 }

5.Bytebuffer(最为常用的Buffer字类)

 1 public abstract class ByteBuffer {
 2     //缓冲区创建相关api
 3     public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
 4     public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量
 5     public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
 6     //构造初始化位置offset和上界length的缓冲区
 7     public static ByteBuffer wrap(byte[] array,int offset, int length)
 8      //缓存区存取相关API
 9     public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
10     public abstract byte get (int index);//从绝对位置get
11     public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
12     public abstract ByteBuffer put (int index, byte b);//从绝对位置上put

二、Channel

 1. 基本介绍

① NIO的通道类似于流,但有些区别如下:
  通道可以同时进行读写,而流只能读或者只能写。
  通道可以实现异步读写数据。
  通道可以从缓冲读数据,也可以写数据到缓冲。

② BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
③ Channel在NIO中是一个接口 public interface Channel extends Closeable{}
④ 常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】
FileChannel 用于文件的数据读写DatagramChannel 用于 UDP 的数据读写ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写


 2. FileChannel类

 FileChannel主要用来对本地文件进行 IO 操作,常见的方法有:

1 public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
2 public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
3 public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
4 public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道

3. FileChannel应用实例1
  应用实例1-本地文件写数据:使用前面学习后的ByteBuffer(缓冲) 和 FileChannel(通道), 将 "hello,Java" 写入到file01.txt 中,文件不存在就创建。

 1 import java.io.FileOutputStream;
 2 import java.nio.ByteBuffer;
 3 import java.nio.channels.FileChannel;
 4 
 5 public class NIOFileChannel01 {
 6     public static void main(String[] args) throws Exception{
 7         String str = "hello.Java";
 8         //1.创建一个输出流->channel
 9         FileOutputStream fileOutputStream = new FileOutputStream("d:\file01.txt");
10         //2.通过 fileOutputStream 获取 对应的 FileChannel
11         //这个 fileChannel 真实 类型是  FileChannelImpl
12         FileChannel fileChannel = fileOutputStream.getChannel();
13         //3.创建一个缓冲区 ByteBuffer
14         ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
15         //4.将 str 放入 byteBuffer
16         byteBuffer.put(str.getBytes());
17         //5.对byteBuffer 进行flip
18         byteBuffer.flip();
19         //6.将byteBuffer 数据写入到 fileChannel
20         fileChannel.write(byteBuffer);
21         fileOutputStream.close();
22     }
23 }

 

4. FileChannel应用实例2

  应用实例2-本地文件读数据: 使用前面学习后的ByteBuffer(缓冲) 和 FileChannel(通道), 将 file01.txt 中的数据读入到程序,并显示在控制台屏幕,假定文件已经存在。

 1 import java.io.File;
 2 import java.io.FileInputStream;
 3 import java.nio.ByteBuffer;
 4 import java.nio.channels.FileChannel;
 5 
 6 public class NIOFileChannel02 {
 7     public static void main(String[] args) throws Exception {
 8         //1.创建文件的输入流
 9         File file = new File("d:\file01.txt");
10         FileInputStream fileInputStream = new FileInputStream(file);
11         //2.通过fileInputStream 获取对应的FileChannel -> 实际类型  FileChannelImpl
12         FileChannel fileChannel = fileInputStream.getChannel();
13         //3.创建缓冲区
14         ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
15         //4.将通道的数据读入到Buffer
16         fileChannel.read(byteBuffer);
17         //5.将byteBuffer 的字节数据 转成String
18         System.out.println(new String(byteBuffer.array()));
19         fileInputStream.close();
20     }
21 }

 

 

5. FileChannel应用实例3

应用实例3-使用一个Buffer完成文件读取:使用 FileChannel(通道) 和 方法  read , write,完成文件的拷贝,拷贝一个文本文件 1.txt  , 放在项目下即可。

 1 import java.io.FileInputStream;
 2 import java.io.FileOutputStream;
 3 import java.nio.ByteBuffer;
 4 import java.nio.channels.FileChannel;
 5 
 6 public class NIOFileChannel03 {
 7     public static void main(String[] args) throws Exception {
 8         FileInputStream fileInputStream = new FileInputStream("1.txt");
 9         FileChannel fileChannel01 = fileInputStream.getChannel();
10         FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
11         FileChannel fileChannel02 = fileOutputStream.getChannel();
12         ByteBuffer byteBuffer = ByteBuffer.allocate(512);
13         while (true) { //循环读取
14             //这里有一个重要的操作,一定不要忘了
15             /*
16              public final Buffer clear() {
17                 position = 0;
18                 limit = capacity;
19                 mark = -1;
20                 return this;
21             }
22              */
23             byteBuffer.clear(); //清空buffer
24             int read = fileChannel01.read(byteBuffer);
25             System.out.println("read =" + read);
26             if(read == -1) { //表示读完
27                 break;
28             }
29             //将buffer 中的数据写入到 fileChannel02 -- 2.txt
30             byteBuffer.flip();
31             fileChannel02.write(byteBuffer);
32         }
33         //关闭相关的流
34         fileInputStream.close();
35         fileOutputStream.close();
36     }
37 }

6. FileChannel应用实例4

应用实例4-拷贝文件transferFrom 方法(从目标通道中复制数据到当前通道):使用 FileChannel(通道) 和 方法  transferFrom ,完成文件的拷贝,拷贝一张图片。

 1 import java.io.FileInputStream;
 2 import java.io.FileOutputStream;
 3 import java.nio.channels.FileChannel;
 4 
 5 public class NIOFileChannel04 {
 6     public static void main(String[] args)  throws Exception {
 7         //创建相关流
 8         FileInputStream fileInputStream = new FileInputStream("d:\a1.jpg");
 9         FileOutputStream fileOutputStream = new FileOutputStream("d:\a2.jpg");
10         //获取各个流对应的filechannel
11         FileChannel sourceCh = fileInputStream.getChannel();
12         FileChannel destCh = fileOutputStream.getChannel();
13         //使用transferForm完成拷贝
14         destCh.transferFrom(sourceCh,0,sourceCh.size());
15         //关闭相关通道和流
16         sourceCh.close();
17         destCh.close();
18         fileInputStream.close();
19         fileOutputStream.close();
20     }
21 }

7. 关于Buffer 和 Channel的注意事项和细节
① ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能BufferUnderflowException 异常。
② 可以将一个普通Buffer 转成只读Buffer。

1 ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();

③ NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成。

 1 import java.io.RandomAccessFile;
 2 import java.nio.MappedByteBuffer;
 3 import java.nio.channels.FileChannel;
 4 /*
 5 说明
 6 1. MappedByteBuffer 可让文件直接在内存(堆外内存)修改, 操作系统不需要拷贝一次
 7  */
 8 public class MappedByteBufferTest {
 9     public static void main(String[] args) throws Exception {
10         RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
11         //获取对应的通道
12         FileChannel channel = randomAccessFile.getChannel();
13 
14         /**
15          * 参数1: FileChannel.MapMode.READ_WRITE 使用的读写模式
16          * 参数2: 0 : 可以直接修改的起始位置
17          * 参数3:  5: 是映射到内存的大小(不是索引位置) ,即将 1.txt 的多少个字节映射到内存
18          * 可以直接修改的范围就是 0-5
19          * 实际类型 DirectByteBuffer
20          */
21         MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
22         mappedByteBuffer.put(0, (byte) 'H');
23         mappedByteBuffer.put(3, (byte) '9');
24         mappedByteBuffer.put(5, (byte) 'Y');//IndexOutOfBoundsException
25         randomAccessFile.close();
26         System.out.println("修改成功~~");
27     }
28 }

④ 前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO 还支持 通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering 。

 1 import java.net.InetSocketAddress;
 2 import java.nio.ByteBuffer;
 3 import java.nio.channels.ServerSocketChannel;
 4 import java.nio.channels.SocketChannel;
 5 import java.util.Arrays;
 6 
 7 /**
 8  * Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入  [分散]
 9  * Gathering: 从buffer读取数据时,可以采用buffer数组,依次读
10  */
11 public class ScatteringAndGatheringTest {
12     public static void main(String[] args) throws Exception {
13         //使用 ServerSocketChannel 和 SocketChannel 网络
14         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
15         InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
16         //绑定端口到socket ,并启动
17         serverSocketChannel.socket().bind(inetSocketAddress);
18         //创建buffer数组
19         ByteBuffer[] byteBuffers = new ByteBuffer[2];
20         byteBuffers[0] = ByteBuffer.allocate(5);
21         byteBuffers[1] = ByteBuffer.allocate(3);
22         //等客户端连接(telnet)
23         SocketChannel socketChannel = serverSocketChannel.accept();
24         int messageLength = 8;   //假定从客户端接收8个字节
25         //循环的读取
26         while (true) {
27             int byteRead = 0;
28             while (byteRead < messageLength ) {
29                 long l = socketChannel.read(byteBuffers);
30                 byteRead += l; //累计读取的字节数
31                 System.out.println("byteRead=" + byteRead);
32                 //使用流打印, 看看当前的这个buffer的position 和 limit
33                 Arrays.asList(byteBuffers).stream().map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);
34             }
35             //将所有的buffer进行flip
36             Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
37             //将数据读出显示到客户端
38             long byteWirte = 0;
39             while (byteWirte < messageLength) {
40                 long l = socketChannel.write(byteBuffers); //
41                 byteWirte += l;
42             }
43             //将所有的buffer 进行clear
44             Arrays.asList(byteBuffers).forEach(buffer-> {
45                 buffer.clear();
46             });
47             System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);
48         }
49     }
50 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/qmillet/p/12142453.html