通道(Channel)
通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。Channel 负责传输, Buffer 负责存储。通道是由 java.nio.channels 包定义的。 Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据, Channel 只能与Buffer 进行交互。
通道都是操作缓存区完成全部的功能的。
Java中所有已知 Channel 实现类:
最下面的4个类为具体实现类。共包括7个接口类、3个抽象类和4个实现类
-
Channel是最顶层的接口。
-
ReadableByteChannel和WritableByteChannel分别提供对通道读取和写入Byte数据的功能
-
ByteChannel用来将读取和写入的功能合并
-
ScatteringByteChannel和GatheringByteChannel分别提供了批量读取和写入ByteBuffer数组的能力
-
InterruptibleChannel提供了多线程异步关闭的能力
7个接口类的详细方法如下:
-
Channel代表一个可以进行IO操作的通道。定义了一下方法
boolean isOpen();//判断此通道是否处于打开状态
void close();//关闭这个Channel,相关资源被释放
-
ReadableByteChannel和WritableByteChannel分别定义了一个读取和写入byte数据的Channel接口。分别定义了read()和write()方法。
int read(ByteBuffer dst);//将字节序列从此通道中读入到给定的缓存区
int write(ByteBuffer src);//将字节序列从给定的缓存区写入此通道
-
ByteChannel没有定义新的方法,它的作用只是把ReadableByteChannel和WritableByteChannel合并在一起
-
ScatteringByteChannel可以一次将数据从通道读入多个ByteBuffer中。而GatheringByteBufferChannel可以一次将多个ByteBuffer中的数据写入通道。提供的方法如下
long read(ByteBuffer[] dsts);//将字节序列从此通道中读入给定的缓存区
long read(ByteBuffer[],int offset,int length);//讲字节序列从此通道中读入给定的缓存区,位置从offset开始到length
long write(ByteBuffer[] src);//将字节序列从给定的缓存区写入此通道
long write(ByteBuffer[] src,int offset,int length);//将字节序列从给定的缓存区写入此通道,位置从offset开始到length
-
InterruptibleChannel用来提供一个可以被异步关闭的Channel,它覆盖了Channel接口的关闭方法close()。
常用的有入下几个:
-
FileChannel:用于读取、写入、映射和操作文件的通道。
-
DatagramChannel:通过 UDP 读写网络中的数据通道。
-
SocketChannel:通过 TCP 读写网络中的数据。
-
ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
由此可以看出,nio 的实际应用侧重点还是网络数据读写。后续会介绍nio 在socket 中的应用。
获取通道
获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:
-
FileInputStream
-
FileOutputStream
-
RandomAccessFile
-
DatagramSocket
-
Socket
-
ServerSocket
获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道。
FileChannel
1 :FileChannel读写数据
-
准备以FileOutputStream类为准,这两个类都是支持通道操作的。
-
String info[] = {"hello","world"} ;
-
File file = new File("d:" + File.separator + "testfilechannel.txt") ;
-
FileOutputStream output = null ;
-
FileChannel fout = null;
-
try {
-
output = new FileOutputStream(file) ;
-
fout = null;
-
fout = output.getChannel() ; // 得到输出的通道
-
ByteBuffer buf = ByteBuffer.allocate(1024) ;
-
for(int i=0;i<info.length;i++){
-
buf.put(info[i].getBytes()) ; // 字符串变为字节数组放进缓冲区之中
-
}
-
buf.flip() ;
-
fout.write(buf) ; // 输出缓冲区的内容
-
} catch (Exception e) {
-
e.printStackTrace();
-
}finally{
-
if(fout!=null){
-
try {
-
fout.close() ;
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
if(output!=null){
-
try {
-
output.close() ;
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
2 文件锁(FileLock) (基于FileChannel)
文件锁和其他我们了解并发里面的锁很多概念类似,当多个人同时操作一个文件的时候,只有第一个人可以进行编辑,其他要么关闭(等第一个人操作完成之后可以操作),要么以只读的方式进行打开。
在java nio中提供了新的锁文件功能,当一个线程将文件锁定之后,其他线程无法操作此文件,文件的锁操作是使用FileLock类来进行完成的,此类对象需要依赖FileChannel进行实例化。
文件锁方式:
-
共享锁:允许多个线程进行文件读取。
-
独占锁:只允许一个线程进行文件的读写操作。
Java文件依赖FileChannel的主要涉及如下4个方法:
方法 | 说明 |
---|---|
lock() | 获取对此通道的文件的独占锁定。 |
lock(long position, long size, boolean shared) | 获取此通道的文件给定区域上的锁定。 |
tryLock() throws IOException | 试图获取对此通道的文件的独占锁定。 |
tryLock(long position, long size, boolean shared) throws IOException | 试图获取对此通道的文件给定区域的锁定。 |
无 | lock()等同于lock(0L, Long.MAX_VALUE, false) |
无 | tryLock()等同于tryLock(0L, Long.MAX_VALUE, false) |
lock()和tryLock()的区别:
-
lock()阻塞的方法,锁定范围可以随着文件的增大而增加。无参lock()默认为独占锁;有参lock(0L, Long.MAX_VALUE, true)为共享锁。
-
tryLock()非阻塞,当未获得锁时,返回null。无参tryLock()默认为独占锁;有参tryLock(0L, Long.MAX_VALUE, true)为共享锁。
简单实例代码:
-
File file = new File("d:" + File.separator + "test.txt") ;
-
FileOutputStream output = null ;
-
FileChannel fout = null ;
-
try {
-
output = new FileOutputStream(file,true) ;
-
fout = output.getChannel() ;// 得到通道
-
FileLock lock = fout.tryLock() ; // 进行独占锁的操作
-
if(lock!=null){
-
System.out.println(file.getName() + "文件锁定") ;
-
Thread.sleep(5000) ;
-
lock.release() ; // 释放
-
System.out.println(file.getName() + "文件解除锁定。") ;
-
}
-
} catch (IOException e) {
-
e.printStackTrace();
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
} finally {
-
if(fout!=null){
-
try {
-
fout.close();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
if(output!=null){
-
try {
-
output.close();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
3 MappedByteBuffer 内存映射 基于FileChannel
MappedByteBuffer是通过FileChannel创建的文件到内存的映射,是一个直接缓存区。
try {
File file = new File("D:\\DownloadFile\\test1.txt");
if(!file.exists()){
if(file.createNewFile()){
System.out.println("文件创建成功");
}
}
//使用内存映射复制文件
FileChannel channel = new FileInputStream("D:\\DownloadFile\\test.txt").getChannel();
FileChannel channel2 = new FileOutputStream(file).getChannel();
//映射第一个文件到内存
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
//写入到第二个文件
channel2.write(buffer);
channel.close();
channel2.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SelectableChannel
SelectableChannel在打开后,可以使用register()将它注册到特定的选择器,实现通道与选择器的事件绑定。
//注册到Selector
SelectableChannel socket = SocketChannle.open();
socket.configureBlocking(false);//设置为非阻塞模式才可以注册到选择器
socket.register(selector,SelectionKey.OP_CONNECT);