通道(Channel)

基本介绍

  1. NIO的通道类似于流,但有些区别如下:
    • 通道可以同时进行读写,而流只能读或者只能写
    • 通道可以实现异步读写数据
    • 通道可以从缓冲读数据,也可以写数据到缓冲
    在这里插入图片描述
  2. BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道
    (Channel)是双向的,可以读操作,也可以写操作。
  3. Channel在NIO中是一个接口public interface Channel extends Closeable{}
  4. 常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和SocketChannel。【ServerSocketChanne 类似ServerSocket , SocketChannel 类似 Socket】
  5. FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP的数据读写。

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),把数据从当前通道复制给目标通道

文件写数据

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
package com.jhj.channel;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NioFileChannel {

private static ByteBuffer allocate;

public static void main(String[] args) throws IOException {

String hello = "hello";
//从创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream("F:\\1.txt");;

//通过fileoutpotstream 获取对应的FileChannel

FileChannel channel = fileOutputStream.getChannel();

//创建一个缓冲区bytebuffer
ByteBuffer allocate = ByteBuffer.allocate(1024);

//将str放入bytebuff
allocate.put(hello.getBytes());

//对bytebuff flip反转
allocate.flip();

//将byteBuffer 数据写入filechannel
channel.write(allocate);
//关闭输出流
fileOutputStream.close();
}
}

文件读数据

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
package com.jhj.channel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NioFileChannel02 {

private static ByteBuffer allocate;

public static void main(String[] args) throws IOException {


//创建一个文件
File file = new File("F:\\1.txt");
//创建输入流
FileInputStream fileInputStream = new FileInputStream(file);
//获取channel
FileChannel channel = fileInputStream.getChannel();
//创建缓冲区
ByteBuffer allocate1 = ByteBuffer.allocate((int) file.length());
//将通道数据读取到buffer
channel.read(allocate1);

//将字节数据转化为string

System.out.println(new String(allocate1.array()));

fileInputStream.close();


}
}

一个buffer 完成读写

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
46
47
48
package com.jhj.channel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NioFileChannel03 {

private static ByteBuffer allocate;

public static void main(String[] args) throws IOException {


//输入流
FileInputStream fileInputStream = new FileInputStream("F://1.txt");
//获取channel
FileChannel channel = fileInputStream.getChannel();

//输出流
FileOutputStream fileOutputStream = new FileOutputStream("F://2.txt");
//获取channel
FileChannel channel1 = fileOutputStream.getChannel();

//缓冲区
ByteBuffer allocate1 = ByteBuffer.allocate(1024);

//读到缓冲区
while(true){

//将标志位重置 清空buffer 如果不clear position limit 会相同 读取为0
allocate1.clear();

int read = channel.read(allocate1);
if (read == -1) {

break;
}
//将读取到的数据 写入
allocate1.flip();
channel1.write(allocate1);
}
fileInputStream.close();
fileOutputStream.close();
}
}

文件拷贝

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
package com.jhj.channel;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NioFileChannel04 {

private static ByteBuffer allocate;

public static void main(String[] args) throws IOException {


//输入流
FileInputStream fileInputStream = new FileInputStream("F://1.txt");
//获取channel
FileChannel channel = fileInputStream.getChannel();

//输出流
FileOutputStream fileOutputStream = new FileOutputStream("F://2.txt");
//获取channel
FileChannel channel1 = fileOutputStream.getChannel();

//拷贝
channel1.transferFrom(channel,0,channel.size());

fileInputStream.close();
fileOutputStream.close();
}
}

关于Buffer 和 Channel的注意事项和细节

  1. ByteBuffer 支持类型化的put 和 get, put 放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常。顺序要一致
  2. 可以将一个普通Buffer 转成只读Buffer buffer.asReaadOnlyBuffer()
  3. NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO 来完成.
  4. 前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO 还支持 通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering

MappedByteBuffer

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
package com.jhj.channel;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
* MappedByteBuffer 可以让文件直接在内存(堆外内存)修改,操作系统不需要拷贝一次
*/


public class NioFileChannel05 {

public static void main(String[] args) throws Exception {

RandomAccessFile rw = new RandomAccessFile("F://1.txt", "rw");
//获取通道
FileChannel channel = rw.getChannel();
/**
* 参数1 读写模式
* 参数2 可以直接修改的起始位置
* 参数3 映射到内存的大小 即将文件 F://1.txt 的多少个字节映射到内存
* 即可以直接修改的范围为0-5 不包括索引为5
* 实际类型是DirectByteBuffer
*/
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

map.put(0,(byte)'J');
map.put(3,(byte)'J');
rw.close();
System.out.println("修改成功");

}
}

buffer数组

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.jhj.channel;

import java.io.RandomAccessFile;
import java.lang.reflect.Array;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;

/**
* Scattering :将数据写入到buffer时,可以采用buffer数组,依次写入 分散
* Gathering :从buffer读数据时,可以采用buffer数组,依次读 聚合
*/


public class NioFileChannel06 {

public static void main(String[] args) throws Exception {

//使用serverSockerChannel 和SocketChannel 网络
ServerSocketChannel open = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//绑定端口到socket并启动
open.socket().bind(inetSocketAddress);
//创建buffer数组
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0]=ByteBuffer.allocate(5);
byteBuffers[1]=ByteBuffer.allocate(3);
//等待客户端连接
SocketChannel accept = open.accept();

//假定从客户端接受8个字节
int messageLength=8;
//循环读取
while (true){

int byteRead=0;
while (byteRead<messageLength){

long read = accept.read(byteBuffers);
byteRead+=read;
System.out.println("byteRead="+byteRead);
//使用流打印 看看当前buffer的position 和limit
Arrays.asList(byteBuffers).stream().map(buffer->
"position"+buffer.position()+"limit="+buffer.limit()
).forEach(System.out::println);

//将所有buffer反转
Arrays.asList(byteBuffers).forEach(byteBuffer -> byteBuffer.flip());

//将数据显示在客户端
long byteWrite=0;
while (byteWrite<messageLength){

long write = accept.write(byteBuffers);
byteWrite+=write;
}
//将所有的buffer进行clear
Arrays.asList(byteBuffers).forEach(byteBuffer -> byteBuffer.clear());

System.out.println("byteRead:="+byteRead+"byteWrite:="+byteWrite+"messageLength:="+messageLength);
}
}

}
}

作者声明

1
如有问题,欢迎指正!