上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel。上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始于一个Channel,一个Channel就相当于一个流,,可以从Channel中读取数据到Buffer,或者写数据到Channel中。

  Channel简介

  FileChannel

  SocketChannel

  ServerSocketChannel

  DatagramChannel

  总结

1. Channel简介

  Java NIO中的Channel类似流,但是有一些不同:

  • Channel既可以支持写也可以支持读,而流则是单向的,只能支持写或者读;
  • Channel支持异步读写;
  • Channel一般和Buffer配套使用,从Channel中读取数据到Buffer中,或从Buffer写入到Channel中;

  Channel的主要实现类有如下几种:

  • FileChannel,可以对文件读/写数据;
  • DatagramChannel,通过UDP从网络读/写数据;
  • SocketChannel,通过TCP从网络读/写数据;
  • ServerSocketChannel,允许你监听TCP连接,对于每个TCP连接都创建一个SocketChannel;

2. FileChannel

  Java NIO FileChannel是一类文件相连的channel,通过它可以从文件读取数据,或向文件写数据。FileChannel类是Java NIO类库提供的用于代替标准Java IO API来读写文件。FileChannel不能设置为非阻塞模式,只能工作在阻塞模式下。

2.1 打开FileChannel

  在使用FileChannel之前先要打开它,就I/O类库中有三个类被修改了,用以产生FileChannel,这三个类是:InputStream、OutputStream、RandomAccessFile,如下是如何从RandomAccessFile获取FileChannel:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

2.2 从FileChannel读取数据

  首先需要分配一个Buffer,从FileChannel读取的数据会读到Buffer中(是不是有点绕)。然后调用FileChannel的read()方法来读数据,这个方法会把数据读到Buffer中,返回的int代表读取了多少字节,返回-1代表文件末尾。

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

2.3 往FileChannel写数据

  通过调用FileChannel的write()方法可以往其中写数据,参数是一个Buffer:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}

  这里write()方法也没有保证一次写入多少数据,我们只是不断重复直到写完。

2.4 关闭FileChannel

  用完FileChannel之后记得要将其关闭:

channel.close();

2.5 FileChannel位置

  调用FileChannel对象的position()方法可以获取其position,也可以调用position(long pos)设置其position:

// 获取position
long pos channel.position();
// 设置position
channel.position(pos +123);

  如果将position设置到文件末尾后面,然后尝试读取文件,会返回-1;

  如果将position设置到文件末尾后面,然后尝试向文件中写数据,则文件会自动扩展,并且从设置的position位置处开始写数据,这会导致“file hole”,即物理文件会有间隙。

2.6 FileChannel尺寸

  size()方法返回filechannel连接的文件的尺寸大小:

long fileSize = channel.size(); 

2.7 截短FileChannel

  truncate()方法可以截短文件:

channel.truncate(1024);

  如上,将文件截取为1024字节长度。

2.8 FileChannel Force

  FileChannel.force()方法会刷新所有未写入到磁盘的数据到磁盘上。因为操作系统会先将数据缓存到内存中,再一次性写入磁盘,所以不能保证写到channel中的数据是否写入到磁盘上,所以可以调用flush()方法强制将数据写入磁盘。

  force()方法有一个boolean参数,代表是否要写入文件的元数据(比如权限):

channel.force(true);

3. SocketChannel

  Java NIO SocketChannel用于和TCP网络socket相连,等同于Java网络包中的Socket。可以通过两种方式来创建一个SocketChannel:

  • 打开一个SocketChannel并且将其和网络上的某台服务器相连;
  • 当有一个连接到达一个ServerSocketChannel时会自动创建一个SocketChannel;

3.1 打开Socket通道

  如下示例说明了如何打开一个SocketChannel:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

3.2 关闭Socket通道

  对于这种资源类的使用,是要记得及时关闭的:

socketChannel.close();

3.3 从Socket通道读数据

  调用read()方法可以读取数据:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

3.4 往Socket通道写数据

  调用其写方法write()可以向其中写数据,使用一个Buffer作为其参数:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}

3.5 工作在非阻塞模式下

  可以将SocketChannel设置为非阻塞模式,设置之后可以异步地调用其connect()、read()和write()方法。

connect()

  对于处于非阻塞模式下的SocketChannel,调用其connect()方法之后会立即返回,即使没有成功建立连接。可以调用finishConnect()方法来获知是否成功建立连接:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(! socketChannel.finishConnect() ){
//wait, or do something else...
}

write()

  对于处于非阻塞模式的SocketChannel,调用其write()也会返回,但是可能还没有写入数据。因此你需要在一个循环中调用它,就像前面的例子中看到的那样。

read()

  对于处于非阻塞模式的SocketChannel,调用其read()有可能出现返回int值,但是还没有任何数据读入到buffer中。因此需要关注返回int值,这个可以告诉我们有多少数据是已经读取的。

非阻塞模式下和Selectors一起工作

  非阻塞模式下的SocketChannel适合和Selector一起搭配使用。通过往Selector中注册一个或多个SocketChannel,可以通过Selector选择已经就绪的Channel。具体使用稍后会详述。

4. ServerSocketChannel

  Java NIO ServerSocketChannel可以监听TCP连接,就像标准Java网络库中的ServerSocket。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
//do something with socketChannel...
}

4.1 打开ServerSocketChannel

  很简单,调用其open()方法就可以开启一个ServerSocketChannel:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

4.2 关闭ServerSocketChannel

serverSocketChannel.close();

4.3 监听连接

  调用其accept()方法之后可以监听连接。在一个新的连接到来之前,都处于阻塞状态,一旦新的连接到来,accept()方法将返回一个和这个连接对应的SocketChannel。

  将其放在一个while循环中就可以不断监听新的连接:

while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
//do something with socketChannel...
}

  当然实际中while循环应该使用其他的判断条件而不是true。

4.4 工作在非阻塞模式下

  ServerSocketChannel同样可设置为非阻塞模式,此时调用其accept()会立即返回,如果没有新的连接可能返回null,需要对返回值是否为空进行校验:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
//do something with socketChannel...
}
}

5. DatagramChannel

  DatagramChannel用于发送和接收UDP包。因为UDP是一个无连接协议,不是像其他channel一样进行读写操作,而是通过数据包来交换数据。

5.1 打开DatagramChannel

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

  这个例子中打开了一个DatagramChannel,可以通过端口9999接收UDP数据包。

5.2 接收数据

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

  调用DatagramChannel的receive()方法可以将接收到的数据包中的数据复制到指定的Buffer中。如果收到的数据大于Buffer容量,默认将其抛弃。

5.3 发送数据

String newData = "New String to write to file..."
+ System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

  在这个例子中是向“jenkov.com”服务器的80端口发送UDP数据包。因为UDP不保证数据传输,所以也不会知道发送的数据包是否被收到。

5.4 连接到指定地址

  可以将DatagramChannel“连接”到一个指定的网络地址。因为UDP协议是无连接的,所以通过这种方式创建连接并不会像基于TCP的channel那样创建一个真实的连接。但是,这样可以“锁定”这个DatagramChannel,使得它只能和一个指定的ip地址交换数据。

channel.connect(new InetSocketAddress("jenkov.com", 80)); 

  在这种情况下(锁定),也可以像其他Channel那样调用read()和write()方法,只不过不能够保证数据一定能够收到。

int bytesRead = channel.read(buf);
int bytesWritten = channel.write(buf);

6. 总结

  本文主要总结了Channel的相关知识,Channel是通道,和Buffer进行交互数据,可以读数据到Buffer中,也可以从Buffer往Channel写数据。Channel主要有下面几种:

  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel

  其中FileChannel是和文件交互、SocketChannel和ServerSocketChannel是基于TCP的网络Channel,DatagramChannel是基于UDP的网络Channel。

Java NIO学习系列二:Channel的更多相关文章

  1. Java NIO学习系列一:Buffer

    前面三篇文章中分别总结了标准Java IO系统中的File.RandomAccessFile.I/O流系统,对于I/O系统从其继承体系入手,力求对类数量繁多的的I/O系统有一个清晰的认识,然后结合一些 ...

  2. Java NIO学习系列三:Selector

    前面的两篇文章中总结了Java NIO中的两大基础组件Buffer和Channel的相关知识点,在NIO中都是通过Channel和Buffer的协作来读写数据的,在这个基础上通过selector来协调 ...

  3. Java NIO学习系列四:NIO和IO对比

    前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...

  4. Java NIO学习系列七:Path、Files、AsynchronousFileChannel

    相对于标准Java IO中通过File来指向文件和目录,Java NIO中提供了更丰富的类来支持对文件和目录的操作,不仅仅支持更多操作,还支持诸如异步读写等特性,本文我们就来学习一些Java NIO提 ...

  5. Java NIO学习系列六:Java中的IO模型

    前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...

  6. JAVA NIO学习笔记二 频道和缓冲区

    Java NIO 频道 Java NIO渠道类似于流,他们之间具有一些区别的: 您可以读取和写入频道.流通常是单向(读或写). 通道可以异步读取和写入数据. 通道常常是读取或写入缓冲区. 如上所述,您 ...

  7. Java NIO学习系列五:I/O模型

    前面总结了很多IO.NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型.什么是IO模型?对于不同人.在不同场景下给出的答案是不同的,所以 ...

  8. java基础学习系列二

    循环语句 1,for(){} 2,while(){} 3,do{}while() continue和break用法 break是结束循环 continue结束本次循环

  9. Java命令学习系列(二)——Jstack

    Java命令学习系列(二)——Jstack 2015-04-18 分类:Java 阅读(512) 评论(0) jstack是java虚拟机自带的一种堆栈跟踪工具. 功能 jstack用于生成java虚 ...

随机推荐

  1. Linux 获得了其首款基于 RISC-V 的多核开源处理器

    去年,硅谷创业公司 SiFive 发布了首款开源 SoC(片上系统 System on a Chip),命名为 Freeform Everywhere 310.现在,该公司从嵌入式系统领先一步,发布了 ...

  2. 伪随机数与采样(sampling)

    计算机本身是无法产生真正的随机数的,但是可以根据一定的算法产生伪随机数(pseudo-random numbers).最古老最简单的莫过于 Linear congruential generator: ...

  3. Matlab随笔之矩阵入门知识

    原文:Matlab随笔之矩阵入门知识 直接输入法创建矩阵 – 矩阵的所有元素必须放在方括号“[ ]”内: – 矩阵列元素之间必须用逗号“,”或空格隔开,每行必须用“;”隔开 – 矩阵元素可以是任何不含 ...

  4. 大数据_zookeeper环境搭建中的几个坑

    文章目录 [] Zookeeper简介 关于zk的介绍, zk的paxos算法, 网上已经有各位大神在写了, 本文主要写我在搭建过程中的几个极有可能遇到的坑. Zookeeper部署中的坑 坑之一 E ...

  5. CountDownLatch和CyclicBarrier 专题

    4.Runnable接口和Callable接口的区别 有点深的问题了,也看出一个Java程序员学习知识的广度. Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行ru ...

  6. 新秀翻译(一个)——Java在继承和组合

    阅读英文的程序猿的能力,这是非常重要的.过去的几年中一直在学习英语,今天心血来潮,在网上找什么鲍文简要翻译. 普通级,能力有限,看官还请大家多多指点. 译文: 本文将会举例说明Java中继承和组合的概 ...

  7. linux没有 conio.h解决的方式

    conio.h不是C标准库中的头文件,在ISO和POSIX标准中均未定义. conio是Console Input/Output(控制台输入输出)的简写,当中定义了通过控制台进行数据输入和数据输出的函 ...

  8. go与java互用的AES实现

    终于实现了go与java互用的AES算法实现.基于go可以编译windows与linux下的命令行工具,十分方便. Java源码 import java.security.GeneralSecurit ...

  9. SAP HR工资配置项1---工资计算周期配置

    对于工资计算,三个方面需要配置:工资计算期.工资类型.工资. 下面是工资期内的配置: 1.在定义参数 在参数指示工资的频率. 主题 设置期间參数 菜单路径 SAP 用户化实施指南→工资核算→工资核算: ...

  10. android 获取 cpu 频率信息

    cpu的频率信息可以在/sys/devices/system/cpu/cpu0/cpufreq/路径下读取 比如最高频率路径为:/sys/devices/system/cpu/cpu0/cpufreq ...