26 Java学习之NIO Channel(一)(待补充)

 一. Channel

在标准的IO当中,都是基于字节流/字符流进行操作的,而在NIO中则是是基于Channel和Buffer进行操作,其中的Channel的虽然模拟了流的概念,实则大不相同。

 

1.1 概念

Channel(通道)用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效的传输数据。有点抽象,不过我们可以根据它的用途来理解:

通道主要用于传输数据,从缓冲区的一侧传到另一侧的实体(如文件、套接字...),反之亦然;

通道是访问IO服务的导管,通过通道,我们可以以最小的开销来访问操作系统的I/O服务;

顺便说下,缓冲区是通道内部发送数据和接收数据的端点,

基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点像流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示: 
                                                      

下面是JAVA NIO中的一些主要Channel的实现: java.nio.channels包中的Channel接口的已知实现类

  • FileChannel:从文件中读写数据。
  • DatagramChannel:能通过UDP读写网络中的数据。
  • SocketChannel:能通过TCP读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

该接口如下所示:另外,关于通道Channel接口的定义,很简单,只有两个方法,判断通道是否打开和关闭通道;

1 public interface Channel extends Closeable {
2 
3     public boolean isOpen();
4 
5     public void close() throws IOException;
6 
7 }
View Code

与缓冲区不同,通道API主要由接口指定。不同的操作系统上通道实现(Channel Implementation)会有根本性的差异,所以通道API仅仅描述了可以做什么。因此很自然地,通道实现经常使用操作系统的本地代码。通道接口允许您以一种受控且可移植的方式来访问底层的I/O服务。

1.2  创建通道

通道主要分为两大类,文件(File)通道和套接字(socket)通道;下面分别看看这几个通道是如何创建的:

(1)创建FileChannel通道

FileChannel通道只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取,比如:

RandomAccessFile accessFile=new RandomAccessFile("C:\\Users\\hermioner\\Desktop\\test.txt", "rw");
FileChannel fileChannel=accessFile.getChannel();

(2)创建SocketChannel通道

SocketChannel socketChannel=SocketChannel.open();
socketChannel.connect(new InetSocketAddress("somehost", someport));

(3)创建ServerSocketChannel通道

 ServerSocketChannel ssc = ServerSocketChannel.open();
 ssc.socket().bind(new InetSocketAddress(somelocalport));

(4)创建DatagramChannel通道

DatagramChannel dc = DatagramChannel.open( );

1.3 单向或者双向通道

(1)读

public interface ReadableByteChannel extends Channel {
    public int read(ByteBuffer dst) throws IOException;
}

(2)写

public interface WritableByteChannel
    extends Channel
{
    public int write(ByteBuffer src) throws IOException;

}

(3)读写

public interface ByteChannel
    extends ReadableByteChannel, WritableByteChannel
{

}     

        通道既可以是单向的也可以是双向的。只实现ReadableByteChannel接口中的read()方法或者只实现WriteableByteChannel接口中的write()方法的通道皆为单向通道,同时实现ReadableByteChannel和WriteableByteChannel为双向通道,比如ByteChannel。对于socket通道来说,它们一直是双向的,而对于FileChannel来说,它同样实现了ByteChannel,但是我们知道通过FileInputStream的getChannel()获取的FileChannel只具有文件的只读权限,那此时的在该通道调用write()会出现什么情况?不出意外的抛出了NonWriteChannelException异常。 
通过以上,我们得出结论:通道都与特定的I/O服务挂钩,并且通道的性能受限于所连接的I/O服务的性质。

1.4 Scatter(分散)和Gather(聚集)

分散读取(Scattering Reads)是指从 Channel 中读取的数据“分散” 到多个 Buffer 中

注意:按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满。

聚集写入(Gathering Writes)是指将多个 Buffer 中的数据“聚集”到 Channel。

注意:按照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel 。

1.5 transferFrom & transferTo

代表了将数据从源通道传输到其它Channel中

1.6 通道的工作模式

通道的工作模式有两种:阻塞或非阻塞。在非阻塞模式下,调用的线程不会休眠,请求的操作会立刻返回结果;在阻塞模式下,调用的线程会产生休眠。另外除FileChannel不能运行在非阻塞模式下,其余的通道都可阻塞运行也可以以非阻塞的方式运行。

 

二. FileChannel

FileChannel和RandomAccessFile的比较:

2.1 使用文件通道读数据

 1 package com.test.a;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.IOException;
 6 import java.nio.ByteBuffer;
 7 import java.nio.channels.FileChannel;
 8 
 9 public class Test {
10     public static void main(String[] args) throws IOException, IOException, ClassNotFoundException {
11         File file = new File("C:\\Users\\hermioner\\Desktop\\test.txt");
12         FileInputStream fis = new FileInputStream(file);
13         FileChannel fc = fis.getChannel();
14         ByteBuffer bb = ByteBuffer.allocate(35);
15         fc.read(bb);
16         bb.flip();
17         while (bb.hasRemaining()) {
18             System.out.print((char) bb.get());
19         }
20         bb.clear();
21         fc.close();
22 
23     }
24 }
25 I love china
26 I love my family
View Code

test.txt种的内容为:

说明:使用read方法将内容读取到缓冲区内即可(从通道种读取数据),缓冲区内有了数据,就可以使用前文对于缓冲区的操作读取数据了

2.2 使用文件通道写数据

 1 package com.test.a;
 2 
 3 import java.io.File;
 4 import java.io.IOException;
 5 import java.io.RandomAccessFile;
 6 import java.nio.ByteBuffer;
 7 import java.nio.channels.FileChannel;
 8 
 9 public class Test {
10     public static void main(String[] args) throws IOException, IOException, ClassNotFoundException {
11         File file = new File("C:\\Users\\hermioner\\Desktop\\test.txt");
12         RandomAccessFile raf = new RandomAccessFile(file, "rw");
13         FileChannel fc = raf.getChannel();
14         ByteBuffer bb = ByteBuffer.allocate(10);
15         String str = "hello";
16         bb.put(str.getBytes());//最多写10个一次,否则异常
17         bb.flip();
18         fc.write(bb);
19         bb.clear();
20         fc.close();
21     }
22 }
View Code

执行结果:hello写入了test.txt文件

说明:write方法写ByteBuffer中的内容至文件中,注意写之前还是要先把ByteBuffer给flip一下。可能有人觉得这种连续put的方法非常不方便,但是没有办法,之前已经提到过了:通道只能使用ByteBuffer

2.3 通道的常用方法

position();返回通道的文件位置

position(long newPosition):设置通道的文件位置

 1 package com.test.a;
 2 
 3 import java.io.File;
 4 import java.io.IOException;
 5 import java.io.RandomAccessFile;
 6 import java.nio.ByteBuffer;
 7 import java.nio.channels.FileChannel;
 8 
 9 public class Test {
10     public static void main(String[] args) throws IOException, IOException, ClassNotFoundException {
11         RandomAccessFile raf = new RandomAccessFile("C:\\Users\\hermioner\\Desktop\\test.txt","rw");
12         FileChannel fis = raf.getChannel();
13         System.out.println("此通道文件的总长度:" +fis.size());
14         //当前通道的文件位置
15         long position = fis.position();
16         System.out.println("通道当前的位置:" + position);
17         //设置新的通道文件位置,从这个位置开始读取
18         fis.position(position + 8);
19         long position2 = fis.position();
20         System.out.println("通道当前的位置:" + position2);
21         ByteBuffer buffer = ByteBuffer.allocate(50);
22         fis.read(buffer);
23         buffer.flip();
24         while(buffer.hasRemaining())
25         {
26             System.out.print((char)buffer.get());
27         }
28         buffer.clear();
29         fis.close();
30     }
31 }
32 
33 此通道文件的总长度:13
34 通道当前的位置:0
35 通道当前的位置:8
36 world
View Code

假设test.txt文件中的内容为:

2.4 FileChannel的一般使用规则

(1)打开FileChannel

     无法直接打开FileChannel(FileChannel是抽象类),需要通过 InputStream , OutputStream 或 RandomAccessFile 获取FileChannel。

(2)从FileChannel读取数据/写入数据

(3)关闭FileChannel

2.5 size方法

通过FileChannel实例的size()可获取FileChannel关联文件的大小

2.6 truncate方法

 

 三. SocketChannel

四. ServerSocketChannel

五. DatagramChannel

 六. 通道工具类

NIO通道提供了一个便捷的通道类Channels,其中定义了几种静态的工厂方法以便更容易的和流打交道。其中常用的方法如下:

 

 

 

 

 参考文献:

https://www.cnblogs.com/xiaoxi/p/6576588.html

https://www.cnblogs.com/chenpi/p/6481271.html

https://www.cnblogs.com/szlbm/p/5513155.html

https://www.cnblogs.com/pony1223/p/8179804.html

https://www.cnblogs.com/szlbm/p/5513155.html

https://blog.csdn.net/zcw4237256/article/details/78662762

 

posted @ 2018-10-21 16:04  Hermioner  阅读(192)  评论(0编辑  收藏  举报