NIO技术-2-通道概论&文件通道

通道是NIO的一个主要创新,用于在Buffer与通道另一端之间进行有效的数据传输,这点在NIO技术-1-缓冲区有讲过,这里不在赘述。

 I/O可以分为文件IO和流IO,那么通道对应的就可以分为文件通道(FileChannel)和流通道(流通道就是套接字通道,SocketChannel),所以NIO中有四种通道实现类:

  1. FileChannel:文件通道,用于操作文件I/O
  2. ServerSocketChannel:服务器套接字通道,用于TCP连接响应客户端连接
  3. SocketChannel:套接字通道,用于TCP协议,客户端连接服务器后,服务器和客户端都会有一个
  4. SocketChannel,就可以互相发送数据了
  5. DatagramChannel:数据报通道,用于UDP协议

打开一个通道的方法如下:

//打开一个文件通道,指定为可读写
RandomAccessFile raf = new RandomAccessFile("d:/test.txt", "rw");
FileChannel fc = raf.getChannel();
// 打开一个服务器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 打开一个套接字通道,
SocketChannel sc = SocketChannel.open();
// 打开一个数据报通道
DatagramChannel dc = DatagramChannel.open();

文件通道还可以通过底层文件句柄的的方式获得,但是这样有可能导致不能读写文件

//不要使用这种方式获取通道实例
FileInputStream fis = new FileInputStream("d:/test.txt");
FileChannel fileChannel = fis.getChannel();
ByteBuffer buff = ByteBuffer.allocate(8192);
fileChannel.write(buff);

 还可以通过java.nio.channels.Channels这个工具类获取通道实例,下面是一个例子:

// 创建一个可读通道  
ReadableByteChannel rbc = Channels.newChannel(System.in);  
// 创建一个可写通道  
WritableByteChannel wbc = Channels.newChannel(System.out);  
// 创建一个大小为8192字节的字节缓冲区  
ByteBuffer buff = ByteBuffer.allocate(8192);  
// 轮询将可读通道的数据读到缓冲区  
while (rbc.read(buff) != -1) {  
    // 翻转缓冲区  
    buff.flip();  
    String str = new String(buff.array()).trim();  
    // 若输入"bye"则关闭通道  
    if (str.equals("bye")) {  
        rbc.close();  
        wbc.close();  
        break;  
    }  
    // 将缓冲区的数据写入到可写通道  
    wbc.write(buff);  
    // 轮询缓冲区是否还有剩余数据  
    while (buff.hasRemaining()) {  
        wbc.write(buff);  
    }  
    // 清空缓冲区  
    buff.clear();  
}  

通道可以以阻塞(blocking)或非阻塞(non-blocking)模式运行,阻塞模式会一直等待某个操作直到返回结果;非阻塞不会一直等待,要么返回null,要么返回执行完的结果。只有流通道才能已non-blocking模式运行,如Socket和Pipe。

Socket通道类继承SelectableChannel,只有SelectableChannel类才能与选择器(Selector)一起使用。

关闭通道使用close()方法,调用close()方法根据操作系统的网络实现不同可能会出现阻塞,可以在任何时候多次调用close();若出现阻塞,第一次调用close()后会一直等待;若第一次调用close()成功关闭后,之后再调用close()会立即返回,不会执行任何操作。

在一个已关闭的通道上进行I/O操作会抛出ClosedChannelException,可以通道isOpen()方法来检查通道时候为打开状态。

 NIO中的通道都实现了InterruptibleChannel,若某个线程上有一个处于阻塞状态的通道,线程被中断会抛出ClosedByInterruptException,并会关闭通道。可以调用isInterrupted()方法检查某个线程的interrupt状态。

一、分散读到多个缓冲区 & 从多个缓冲区聚集写入通道

//test.txt中只有一行数据:whoareu?  
//创建2个缓冲区,组成一个缓冲区数组  
//打开一个文件通道,将test.txt中的数据读到缓冲区数组中  
//缓冲区数组会自动填充buffA和buffB这两个缓冲区  
//buffA填满后,再继续填充buffB  
//达到分散读取数据到多个缓冲区中  
ByteBuffer buffA = ByteBuffer.allocate(6);  
ByteBuffer buffB = ByteBuffer.allocate(5);  
ByteBuffer[] buffArr = { buffA, buffB };  
RandomAccessFile raf = new RandomAccessFile("test.txt", "r");  
FileChannel fc = raf.getChannel();  
fc.read(buffArr);  
System.out.println(new String(buffA.array()));//输出who  
System.out.println(new String(buffB.array()));//输出areu?  
fc.close();  
raf.close();  
//创建两个Buffer,组成一个Buffer Array  
//将Buffer Array的数据写入到文件通道到  
//达到聚集缓冲区其中写入通道目的  
byte[] byteA = "hello ".getBytes("UTF-8");  
ByteBuffer buffC = ByteBuffer.wrap(byteA);  
byte[] byteB = "world!".getBytes("UTF-8");  
ByteBuffer buffD = ByteBuffer.wrap(byteB);  
ByteBuffer[] allBuff = {buffC,buffD};  
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");  
FileChannel fc = raf.getChannel();  
fc.write(allBuff);  
fc.close();  
raf.close();  

二、文件通道(FileChannel)

FileChannel不能直接创建,只能通过创建一个文件对象(RandAccessFile、FileInputStream、FileOutputStream)后调用其getChannel()方法获得。FileChannel是线程安全,多个进程并发操作同一文件不会引起任何问题;兵法行为受低层操作系统或文件系统影响。

FileChannel类保证同一个JVM上的所有FileChannel实例看到的文件内容是一致的,但不能保证外部的非Java进程看到的该文件视图一致,也可能一致,这取决于低层操作系统的实现。

打开一个文件通道可以使用下面方式:

//RandomAccessFile有2中构造方法,下面的构造方法等同于:  
//RandomAccessFile raf = new RandomAccessFile(new File("test.txt"), "rw");  
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");  
FileChannel fc = raf.getChannel();

RandomAccessFile构造方法的第二个参数含义如下:

  1. "rw":对文件可读可写,若文件不存在则会创建该文件
  2. "r":只读
  3. "rws":对文件可读写,并且文件中数据和元信息(若更新时间等)的每个更新都会写入到磁盘
  4. "rwd":对文件可读写,并且文件数据的每个更新会同步写入到磁盘

文件锁在Jdk1.4时才被提供,当多个程序并发操作同一个文件时,可以使用文件锁来锁定文件同一时刻只能接受一个程序的IO操作。文件锁分为独占锁和共享锁,FileChannel提供了文件锁的API,在FileChannel的文件锁方式中,锁的对象是文件而不是通道或线程,也就是说文件锁不适用于同一个JVM上多个线程并发访问文件的情况。同一个JVM中,一个线程获得了某个文件的独占锁,第二个线程也可以获得这个文件的独占锁;但是,在不同的JVM中,第一个JVM的线程获得的某个文件的独占所,第二个JVM的线程会被阻塞。导致这样的情况的原因是文件锁是由操作系统在进程级上来判优的,而不是在线程级上。

文件锁可以通过FileChannel的lock()或tryLock()方法获取,两者的区别如下:

  1. lock():阻塞,第一个线程获取文件锁后,第二个线程必须等待
  2. tryLock():非阻塞,若不能立即获得文件锁则返回null
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");  
FileChannel fc = raf.getChannel();  
//--------------------------------------------  
//阻塞获得文件独占锁,并锁定文件所有数据  
FileLock lock0 = fc.lock();  
//阻塞获得文件独占锁,并锁定文件指定数据  
FileLock lock1 = fc.lock(0, 8192, false);  
//阻塞获得文件共享锁,并锁定文件0 ~ 8192字节的数据  
FileLock lock2 = fc.lock(0, 8192, true);  
//--------------------------------------------  
//非阻塞获取文件独占所,等同于lock()  
FileLock lock3 = fc.tryLock();  
//非阻塞获取文件独占所,等同于fc.lock(0, 8192, false);  
FileLock lock4 = fc.tryLock(0, 8192, false);  
//非阻塞获取文件共享锁,等同于fc.lock(0, 8192, true);  
FileLock lock5 = fc.tryLock(0, 8192, true);  

FileLock对象关联FileChannel,FileLock API如下:

  1. channel():获取关联的FileChannel
  2. isShared():判断是共享锁还是独占锁,返回ture是共享锁,返回false是独占锁
  3. overlaps(long position, long size):判断当前文件锁锁定的区域是否有交叉,也就是是否也被别线程锁定了
  4. isValid():判断当前文件锁是否有效
  5. release():释放文件锁,通道被关闭或JVM关闭时也会释放文件锁。

实际应用中,一般使用共享锁读文件,使用独占锁写文件

转载于:https://my.oschina.net/u/1024107/blog/745986

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值