JAVA NIO系列(二) Channel解读

Channel就是一个通道,用于传输数据,两端分别是缓冲区和实体(文件或者套接字),通道的特点(也是NIO的特点):通道中的数据总是要先读到一个缓冲区,或者总是要从一个缓冲区中读入。

Channel的分类

1) FileChannel:从文件中读写数据

2) SocketChannel:通过TCP协议读写网络中的数据

3) ServerSocketChannel:在服务器端可以监听新进来的TCP连接,像WEB服务器那样,对每一个新进来的请求创建一个SocketChannel

4) DatagramChannel:通过UDP协议读写网络中的数据

上面众多的分类,是对应了不同的实体,这些通道包括了文件IO、TCP和UDP网络IO。

下面来看看Channel的源码:

 1 public interface Channel extends Closeable {
 2
 8     public boolean isOpen();
 9
27     public void close() throws IOException;
28
29 }

从这里我们可以看到,Channel接口只提供了关闭通道和检测通道是否打开这两个方法,剩下方法的都是由子接口和实现类来定义提供。

我们选择其中几个来看看这些接口的源码:

1 public interface WritableByteChannel
2     extends Channel
3 {
4
5     public int write(ByteBuffer src) throws IOException;
6
7 }
public interface ReadableByteChannel extends Channel {
    public int read(ByteBuffer dst) throws IOException;

}
public interface ByteChannel
    extends ReadableByteChannel, WritableByteChannel
{

}

前面我提到过:通道可以只读、只写或者同时读写,因为Channel类可以只实现只读接口ReadableByteChannel或者只实现只写接口WritableByteChannel,而我们常用的Channel类FileChannel、SocketChannel、DatagramChannel是双向通信的, 因为实现了ByteChannel接口。

Channel的获取

IO在广义上可以分为:文件IO和网络IO。文件IO对应的通道为FileChannel,而网络IO对应的通道则有三个:SocketChannel、ServerSoketChannel和DatagramChannel。

一、文件通道

FileChannel对象不能直接创建,只能通过FileInputStream、OutputStream、RandomAccessFile对象的getChannel()来获取,如:

FileInputStream fis = new FileInputStream("c:/in.txt");
FileChannel fic = fis.getChannel();

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

1)使用通道读取文件

 1 public class NIOFileReadTest
 2 {
 3     public static void main(String[] args) throws IOException
 4     {
 5         RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw");
 6         FileChannel fis = raf.getChannel();
 7         ByteBuffer buffer = ByteBuffer.allocate(1024);
 8         fis.read(buffer);
 9         buffer.flip();
10         while(buffer.hasRemaining())
11         {
12             System.out.print((char)buffer.get());
13         }
14         buffer.clear();
15         fis.close();
16     }
17 }

执行结果:

FileChannel
ByteBuffer
SelectorPicked

2)使用通道写入文件

public class NIOFileWriteTest
{
    public static void main(String[] args) throws Exception
    {
        FileOutputStream fos = new FileOutputStream("d:/out.txt");
        FileChannel fc = fos.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.clear();
        String str = "Channel";
        buffer.put(str.getBytes());
        buffer.flip();
        while(buffer.hasRemaining())
        {
            fc.write(buffer);
        }
        fc.close();
        fos.close();
    }
}

在这里总是要记住channel是要关闭的。

ByteBuffer中的方法我在下一章再详细介绍,这里只要注意这点即可:通道只能使用ByteBuffer,不管是读还是写,通道都要对接缓冲区

3)通道的常用方法

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

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

将上面读文件的程序修改下,来观察这几个方法:

 1 public class NIOFileReadTest
 2 {
 3     public static void main(String[] args) throws IOException
 4     {
 5         RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw");
 6         FileChannel fis = raf.getChannel();
 7         System.out.println("此通道文件的总长度:" +fis.size());
 8         //当前通道的文件位置
 9         long position = fis.position();
10         System.out.println("通道当前的位置:" + position);
11         //设置新的通道文件位置,从这个位置开始读取
12         fis.position(position + 8);
13         ByteBuffer buffer = ByteBuffer.allocate(50);
14         fis.read(buffer);
15         buffer.flip();
16         while(buffer.hasRemaining())
17         {
18             System.out.print((char)buffer.get());
19         }
20         buffer.clear();
21         fis.close();
22     }
23 }

执行结果:

此通道文件的总长度:33
通道当前的位置:0
nel
ByteBuffer
Selector

FileChannel是线程安全的,可以多个线程在同一个实例上并发操作,但是其中有些方法(改变文件通道位置或者文件大小的方法)必须是单线程操作。

二、网络通道

SocketChannel是一个连接到TCP套接字的通道,获取的方式有两种:

1、打开一个SocketChannel并连接到互联网上某台服务器。

2、一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

上面这两种模式跟IO的Socket、ServerSocket类似,下面分别来看看客户端和服务器端:

一、SocketChannel

从通道中读取数据

 1 public class SocketChannelTest
 2 {
 3     public static void main(String[] args) throws Exception
 4     {
 5         //获取socket通道
 6         SocketChannel sc = SocketChannel.open();
 7         //设置为非阻塞模式
 8         sc.configureBlocking(false);
 9         //建立连接,非阻塞模式下,该方法可能在连接建立之前就返回了
10         sc.connect(new InetSocketAddress("wap.cmread.com",80));
11         //判断连接是否建立
12         while(!sc.finishConnect())
13         {
14             System.out.println("连接未建立");
15             Thread.sleep(5);
16         }
17         ByteBuffer buffer = ByteBuffer.allocate(48);
18         int byteRead = sc.read(buffer);
19         System.out.println(byteRead);
20         sc.close();
21         buffer.clear();
22     }
23 }

执行结果;

连接未建立
连接未建立
0

1、第6、7行是获取一个socket通道,并且设置为非阻塞模式。

2、由于是非阻塞模式,通道在调用方法connect/read/writer这三个方法时,会出现这些情况:连接未建立,connect方法就返回了;尚未读取任何数据时,read方法就返回;尚未写出任何内容时,writer就返回

3、在12行的循环代码中,是判断连接是否建立,从执行结果来看,循环执行了两次连接才建立(在循环里线程还有休眠)。

4、由于只是建立了连接,通道里面其实没有任何的数据。

5、第18行调用read方法,由于是非阻塞模式,所以在并未读取任何数据的情况下就返回0(尽管通道里面没有数据)。

将数据写入通道

 1 public class SocketChannelTest
 2 {
 3     public static void main(String[] args) throws Exception
 4     {
 5         SocketChannel sc = SocketChannel.open();
 6         String str = "non-blocking socket channel";
 7         ByteBuffer buffer = ByteBuffer.allocate(100);
 8         buffer.put(str.getBytes());
 9         buffer.flip();
10         while(buffer.hasRemaining())
11         {
12             sc.write(buffer);
13         }
14         sc.close();
15         buffer.clear();
16     }
17 }

1、SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

二、ServerSocketChannel

ServerSocketChannel是一个可以监听新进来的TCP连接的通道。

 1 public class ServerSocketChannelTest
 2 {
 3     public static void main(String[] args) throws Exception
 4     {
 5         ServerSocketChannel ssc = ServerSocketChannel.open();
 6         ssc.socket().bind(new InetSocketAddress(80));
 7         ssc.configureBlocking(false);
 8         while(true)
 9         {
10             SocketChannel sc = ssc.accept();
11             if(null != sc)
12             {
13                 //do something;
14             }
15         }
16     }
17 }

1、第5、6、7行,获取一个ServerSocketChannel,并且监听80端口,设置为非阻塞模式。

2、通过accept方法监听新接入进来的连接,这个方法会返回一个包含新进来的连接的SocketChannel(服务器端的通道的获取方式)。如果是阻塞模式,该方法会一直阻塞直到有新的连接进来。如果是非阻塞模式,则accept方法会立刻返回,返回值是null。

3、第11行,是因为在非阻塞模式下,需要检查SocketChannel是否为null。

三、socket通道与socket

1 ServerSocketChannel ssc = ServerSocketChannel.open();
2 ServerSocket socket = ssc.socket();
3 ServerSocketChannel ssc1 = socket.getChannel();

1、从这代码片段可以大概看到这样一种关系:所有socket通道(SocketChannel/ServerSocketChanne/DatagramSocketChannel)在被实例化之后,都是伴随生成对应的socket对象,就是前面IO章节介绍的java.net类(Socket/ServerSocket/DatagramSocket)。通过通道类的socket方法来获取。

2、java.net类(Socket/ServerSocket/DatagramSocket)现在可以通过getChannel方法来获取对应的通道。前提是这些socket对象不是使用传统方式(直接实例化)创建的。否则它就没有关联的socket通道,调用getChannel方法返回总是null。

时间: 2024-10-21 05:13:58

JAVA NIO系列(二) Channel解读的相关文章

Java NIO系列(三) - Channel

前言 上文讲到Java NIO一些基本概念.在标准的IO中,都是基于字节流/字符流进行数据操作的,而在NIO中则是是基于Channel和Buffer进行操作,其中的Channel的虽然模拟了流的概念,实则大不相同. 本文将详细阐述NIO中的通道Channel的概念和具体的用法. Channel和Stream的区别 区别 Stream Channel 是否支持异步 不支持 支持 是否支持双向数据传输 不支持,只能单向 支持,既可以从通道读取数据,也可以向通道写入数据 是否结合Buffer使用 不

Java NIO系列(二) - Buffer

前言 在Java NIO中,缓冲区用来临时存储数据,可以理解为是I/O操作中数据暂存的中转站.缓冲区直接为通道(Channel)服务,数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问这块内存. 正文 Buffer的类型 Java NIO提供以下几种Buffer类型: ByteBuffer MappedByteBuffer ShortBuffer LongBuff

Java NIO系列教程(二) Channel

原文地址:http://ifeve.com/channels/ 声明:Java NIO系列教材并非本人原创,只因阅读原文之后有感于文章之精妙,意欲与诸位共享,故而出此下策,忘原作者见谅.另附上原文地址. Java NIO的通道类似流,但又有些不同: 既可以从通道中读取数据,又可以写数据到通道.但流的读写通常是单向的. 通道可以异步地读写. 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入. 正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道.如下图所示: C

5. 彤哥说netty系列之Java NIO核心组件之Channel

你好,我是彤哥,本篇是netty系列的第五篇. 简介 上一章我们一起学习了如何使用Java原生NIO实现群聊系统,这章我们一起来看看Java NIO的核心组件之一--Channel. 思维转变 首先,我想说的最重要的一个点是,学习NIO思维一定要从BIO那种一个连接一个线程的模式转变成多个连接(Channel)共用一个线程来处理的这种思维. 1个Connection = 1个Socket = 1个Channel,这几个概念可以看作是等价的,都表示一个连接,只不过是用在不同的场景中. 如果单从阻塞

java NIO系列教程1

ava NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式. Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中. Java NIO: Asynchronous IO(异步IO) Java NIO可以让你

Java NIO系列教程(三) Buffer

原文链接:http://ifeve.com/buffers/ 声明:Java NIO系列教材并非本人原创,只因阅读原文之后有感于文章之精妙,意欲与诸位共享,故而出此下策,忘原作者见谅.另附上原文地址. Java NIO的通道类似流,但又有些不同: Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问

java NIO系列教程2

7.FileChannel Java NIO中的FileChannel是一个连接到文件的通道.可以通过文件通道读写文件. FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下. 打开FileChannel 在使用FileChannel之前,必须先打开它.但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream.OutputStream或RandomAccessFile来获取一个FileChannel实例.下面是通过RandomAccessFile打开

Java NIO系列教程(五) 通道之间的数据传输

原文地址:http://tutorials.jenkov.com/java-nio/scatter-gather.html 作者:Jakob Jenkov   译者:郭蕾     校对:周泰 在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel. transferFrom() FileChannel的transferFrom()方法可以将数据从源通道传输到FileChanne

java nio 通道(二)

本文章来源于我的个人博客: java nio 通道(二) 一,文件通道 文件通道总是堵塞式的,因此不能被置于非堵塞模式. FileChannel对象是线程安全的.多个进程能够在同一个实例上并发调用方法而不会引起不论什么问题,只是非全部的操作都是多线程的.影响通道位置或者影响文件大小的操作都是单线程的. 通过FileChannel实例看到的某个文件的视图同通过一个外部的非java进程看到的该文件的视图可能一致也可能不一致. 创建文件通道: RandomAccessFile randomAccess

java nio 缓冲区(二)

本文章来自于本人个人博客:java nio 缓冲区(二) 一,创建缓冲区 1.缓冲区的创建有两种方式,分别是ByteBuffer.allocate([int])或者ByteBuffer.wrap(byte[]),第一种方式是创建一个分配了int个字节的缓冲区,而第二种方式是在现有字节数组之上创建一个缓冲区,这个缓冲区的capacity就是数组的长度. 2.Buffer类的其它子类创建缓冲区也是一样的:CharBuffer.allocate(int)或者CharBuffer.wrap(byte[]