通道(Channel)
NIO的通道类似于流,但有些区别如下:
- 通道可以同时进行读写,而流只能读或者只能写
- 通道可以实现异步读写数据
- 通道可以从缓冲区读数据,也可以写数据到缓冲区
Channel在NIO中是一个接口,常用的Channel类有:FileChannel
、DatagramChannel
、ServerSocketChannel
、SocketChannel
(ServerSocketChannel类似ServerSocket、SocketChannel类似Socket)
FileChannel
用于文件的数据读写,DatagramChannel
用于UDP的数据读写,ServerSocketChannel
和SocketChannel
用于TCP的数据读写。
FileChannel类
FileChannel主要用来对本地文件进行IO操作的,常用方法如下:
- public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
- public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
- public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
- public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道
FileChannel例子
Demo1:将随机字符串写入到某文件中
public class FileChannelWriteDemo {
public static void main(String[] args) throws IOException {
//使用这种方式不需要进行buffer读写转换
// ByteBuffer byteBuffer=ByteBuffer.wrap("hello".getBytes());
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("hello".getBytes());
//使用流来构造FileChannel
FileOutputStream fos = new FileOutputStream("1.txt");
FileChannel channel = fos.getChannel();
byteBuffer.flip();
channel.write(byteBuffer);
channel.close();
fos.close();
}
}
Demo2:将上面1.txt中的数据读入程序,并显示出来
public class FileChannelReadDemo {
public static void main(String[] args) throws Exception {
//从文件中读取内容 输出出来
FileInputStream fis = new FileInputStream("1.txt");
FileChannel channel = fis.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(fis.available());
//返回读取的字节数,可能为0(position与limit相等时).如果当前channel已经读取到流的末尾 则返回-1
int read = channel.read(buffer);
// buffer.flip();
//buffer.array()直接获取了buffer内部的数组 不需要读写转换,如果采用原来遍历的方式
//需要buffer.flip() 否则报错BufferUnderflowException
System.out.println(new String(buffer.array()).trim());
channel.close();
fis.close();
}
}
Demo3:使用一个ByteBuffer完成文件的读取、写入
/**
* 2020/1/7 下午3:30
* 使用buffer和channel 实现文件的拷贝。使用一个channel将文件数据读取到buffer中,
* 再使用另一个channel从buffer中读取数据
*/
public class FileChannelCopyDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
FileChannel readChannel = fis.getChannel();
FileOutputStream fos = new FileOutputStream("2.txt");
FileChannel writeChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(2);
while (true) {
//循环读取、写入数据时 记得clear
//否则 buffer中内容被读取之后position=limit,下面的read会读不到数据
//read会变成0 造成在这里死循环
buffer.clear();
int read = readChannel.read(buffer);
if (read == -1) {
break;
}
buffer.flip();
writeChannel.write(buffer);
}
writeChannel.close();
fos.close();
readChannel.close();
fis.close();
}
}
Demo4:使用FileChannel和transferFrom 完成文件的拷贝
public class FileChannelTransFromDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
FileChannel readChannel = fis.getChannel();
FileOutputStream fos = new FileOutputStream("3.txt");
FileChannel writeChannel = fos.getChannel();
//将文件1.txt拷贝到3.txt中
long l = writeChannel.transferFrom(readChannel, 0, fis.available());
System.out.println(l);
writeChannel.close();
fos.close();
readChannel.close();
fis.close();
}
}
Demo5:使用 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进行修改
/**
* 2020/1/7 下午4:27
* MappedByteBuffer可以实现数据在堆外内存中直接修改 而不需要拷贝一次
*/
public class MappedByteBufferDemo {
public static void main(String[] args) throws Exception{
//使用MapMode.READ_WRITE 必须保证读取的文件是可读写的,否则抛出异常
// FileInputStream fis=new FileInputStream("1.txt");
// FileChannel channel = fis.getChannel();
RandomAccessFile accessFile=new RandomAccessFile("1.txt","rw");
FileChannel channel = accessFile.getChannel();
/**
* FileChannel.MapMode.READ_WRITE 使用的读写模式
* 0 : 可以直接修改的起始位置
* 5: 是映射到内存的大小(不是索引位置) ,即将 1.txt 的多少个字节映射到内存
* 可以直接修改的范围就是 0-5
*/
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
mappedByteBuffer.put(0,(byte)'A');
channel.close();
accessFile.close();
}
}
Demo6:使用多个Buffer(buffer数组)来完成读写操作
/**
* 2020/1/7 下午8:03
* 从buffer中读取数据,可以是buffer数组,会依次读取buffer数组中的元素
*/
public class GatheringDemo {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("4.txt");
FileChannel channel = fos.getChannel();
ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0] = ByteBuffer.allocate(4);
buffers[1] = ByteBuffer.allocate(8);
for (int i = 0; i < 4; i++) {
buffers[0].put((byte) (65 + i));
}
for (int i = 0; i < 8; i++) {
buffers[1].put((byte) (65 + i));
}
Stream.of(buffers).forEach(buffer->buffer.flip());
channel.write(buffers);
channel.close();
fos.close();
}
}
/**
* 2020/1/7 下午7:46
* 将数据写入buffer时,可以指定buffer数组,数据会自动依次写入到buffer数组的每个元素里
*/
public class ScatteringDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0]=ByteBuffer.allocate(4);
buffers[1]=ByteBuffer.allocate(32);
FileChannel channel = fis.getChannel();
channel.read(buffers);
Stream.of(buffers)
.forEach(buffer -> buffer.flip());
Stream.of(buffers).forEach(buffer -> {
while (buffer.hasRemaining()) {
System.out.println((char)buffer.get());
}
});
}
}