Java NIO(6):通道和文件通道详解编程语言

一、通道是什么

     通道式(Channel)是java.nio的第二个主要创新。通道既不是一个扩展也不是一项增强,而是全新的、极好的Java I/O示例,提供与I/O服务的直接连接。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据

     通常情况下,通道与操作系统的文件描述符(FileDescriptor)和文件句柄(FileHandler)有着一对一的关系。虽然通道比文件描述符更广义,但开发者经常使用到的多数通道都是连接到开放的文件描述符的。Channel类提供维持平台独立性所需的抽象过程,不然仍然会模拟现代操作系统本身的I/O性能。

    通道是一种途径,借助该途径,可以用最小的总开销来访问操作系统本身的I/O服务。缓冲区则是通道内部用来发送和接收数据的端点,如下图:

Java NIO(6):通道和文件通道详解编程语言

二、通道基础

首先,看一下基本的Channel接口,下面是Channel接口的完整源码:

public interface Channel extends Closeable { 
 
    /** 
     * Tells whether or not this channel is open.  </p> 
     * 
     * @return <tt>true</tt> if, and only if, this channel is open 
     */ 
    public boolean isOpen(); 
 
    /** 
     * Closes this channel. 
     * 
     * <p> After a channel is closed, any further attempt to invoke I/O 
     * operations upon it will cause a {@link ClosedChannelException} to be 
     * thrown. 
     * 
     * <p> If this channel is already closed then invoking this method has no 
     * effect. 
     * 
     * <p> This method may be invoked at any time.  If some other thread has 
     * already invoked it, however, then another invocation will block until 
     * the first invocation is complete, after which it will return without 
     * effect. </p> 
     * 
     * @throws  IOException  If an I/O error occurs 
     */ 
    public void close() throws IOException; 
 
}
    和缓冲区不同,通道API主要由接口指定。不同的操作系统上通道实现会有根本性的差异,所以通道API仅仅描述了可以做什么,因此很自然地,通道实现经常使用操作系统的本地代码,通道接口允许开发者以一种受控且可移植的方式来访问底层的I/O服务。

    可以从底层的Channel接口看到,对所有通道来说只有两种共同的操作:检查一个通道是否打开isOpen()和关闭一个打开的通道close(),其余所有的东西都是那些实现Channel接口以及它的子接口的类。

从Channel接口引申出的其他接口都是面向字节的子接口:

Java NIO(6):通道和文件通道详解编程语言

    包括WritableByteChannel和ReadableByteChannel。这也正好支持了我们之前的所学:通道只能在字节缓冲区上操作。层次接口表明其他数据类型的通道也可以从Channel接口引申而来。这是一种很好的镭射机,不过非字节实现是不可能的,因为操作系统都是以字节的形式实现底层I/O接口的。

三、认识通道

看一下基本的接口:

public interface ReadableByteChannel extends Channel { 
 
    public int read(ByteBuffer dst) throws IOException; 
 
}
public interface WritableByteChannel 
    extends Channel 
{ 
 
    public int write(ByteBuffer src) throws IOException; 
 
}
public interface ByteChannel 
    extends ReadableByteChannel, WritableByteChannel 
{ 
 
}

    通道可以是单向的也可以是双向的。一个Channel类可能实现定义read()方法的ReadableByteChannel接口,而另一个Channel类也许实现WritableByteChannel接口以提供write()方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据,就像上面的ByteChannel。

    通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行,非阻塞模式的通道永远不会让调用的线程休眠,请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式

比方说非阻塞的通道SocketChannel:

public abstract class SocketChannel 
    extends AbstractSelectableChannel 
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel 
{ 
    ... 
}
public abstract class AbstractSelectableChannel 
    extends SelectableChannel 
{ 
   ... 
}

    可以看出,socket通道类从SelectableChannel类引申而来,从SelectableChannel引申而来的类可以和支持有条件的选择的选择器(Selectors)一起使用。将非阻塞I/O和选择器组合起来可以使开发者的程序利用多路复用I/O。

四、认识文件通道

    通道是访问I/O服务的导管,I/O可以分为广义的两大类:File I/O和Stream I/O。那么相应的,通道也有两种类型,它们是文件(File)通道和套接字(Socket)通道。文件通道指的是FileChannel,套接字通道则有三个,分别是SocketChannel、ServerSocketChannel和DatagramChannel。

    通道可以以多种方式创建。Socket通道可以有直接创建Socket通道的工厂方法,但是一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取,开发者不能直接创建一个FileChannel

   文件I/O是我们最常使用的I/O,因此这部分先认识一下文件通道,下一部分再以代码形式演示如何使用文件通道高。用UML图表示一下文件通道的类层次关系:

Java NIO(6):通道和文件通道详解编程语言

    文件通道总是阻塞式的,因此不能被置于非阻塞模式下

    前面提到过了,FileChannel对象不能直接创建,一个FileChannel实例只能通过在一个打开的File对象(RandomAccessFile、FileInputStream或FileOutputStream)上调用getChannel()方法获取,调用getChannel()方法会返回一个连接到相同文件的FileChannel对象且该FileChannel对象具有与file对象相同的访问权限,然后就可以使用通道对象来利用强大的FileChannel API了。

    FileChannel对象是线程安全的多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的。影响通道位置或者影响文件的操作都是单线程的,如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或文件系统的影响。

五、使用文件通道读数据

讲了这么多理论,下面来看一下如何使用文件通道,首先是从文件中读出数据:

public static void main(String[] args) throws Exception 
{ 
    File file = new File("D:/files/readchannel.txt"); 
    FileInputStream fis = new FileInputStream(file); 
    FileChannel fc = fis.getChannel(); 
    ByteBuffer bb = ByteBuffer.allocate(35); 
    fc.read(bb); 
    bb.flip(); 
    while (bb.hasRemaining()) 
    { 
        System.out.print((char)bb.get()); 
    } 
    bb.clear(); 
    fc.close(); 
}

这是最简单的操作,前面讲过文件通道必须通过一个打开的RandomAccessFile、FileInputStream、FileOutputStream获取到,因此这里使用FileInputStream来获取FileChannel。接着只要使用read方法将内容读取到缓冲区内即可,缓冲区内有了数据,就可以使用前文对于缓冲区的操作读取数据了。文件里面的数据是:

Java NIO(6):通道和文件通道详解编程语言

控制台上打印出来的数据是:

channel1! 
channel2! 
channel3!

没有问题。

六、使用文件通道写数据

上面看了使用文件通道读数据,接着看一下使用文件通道写数据,差不多:

public static void main(String[] args) throws Exception 
{ 
    File file = new File("D:/files/writechannel.txt"); 
    RandomAccessFile raf = new RandomAccessFile(file, "rw"); 
    FileChannel fc = raf.getChannel(); 
    ByteBuffer bb = ByteBuffer.allocate(10); 
    String str = "abcdefghij"; 
    bb.put(str.getBytes()); 
    bb.flip(); 
    fc.write(bb); 
    bb.clear(); 
    fc.close(); 
}

这里使用了RandomAccessFile去获取FileChannel,然后操作其实差不多,write方法写ByteBuffer中的内容至文件中,注意写之前还是要先把ByteBuffer给flip一下。

可能有人觉得这种连续put的方法非常不方便,但是没有办法,之前已经提到过了:通道只能使用ByteBuffer

 

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/17227.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论