参考文档:http://tutorials.jenkov.com/java-nio/channels.html
和传统的IO中的 InputStream
、OutputStream
相比,主要区别就是传统IO中的stream是单向的。但是在NIO中的channel
是可读可写的。并且读写是可以异步进行的。另外channel
都是从buffer
中读取或者写入数据的
上一篇学习笔记中的主要实现的管道类型
FileChannel 从文件和文件读取数据
DatagramChannel 可以通过UDP在网络上读写数据。
SocketChannel 可以通过TCP在网络上读写数据。
ServerSocketChannel 允许您侦听传入的TCP连接,就像web服务器一样。为每个传入连接创建SocketChannel。
这4个类在idea中可以查看相应的UML图
先看下面的程序
读取文件数据:
public class FileChannelPrc {
public static void main(String[] args) throws IOException {
// 创建一个rw模式的随机文件
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
// 获取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// 设定缓冲区大小48
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = fileChannel.read(buf);
while(bytesRead !=-1){
System.out.println("\nRead " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = fileChannel.read(buf);
}
randomAccessFile.close();
}
}
// 运行结果:
Hi, I am jimmy.
上面程序要正确执行需要,现在本地对应文件夹中创建相应文件。我在文件中写入的是Hi, I am jimmy.
这里就可以看到读取出来并且打印了。
然后我们将文件内容修改为Hi, I am jimmy. I am learning Java NIO. Hope I can hold it.
(大于48字符)。在运行程序,此时返回值
Read 48
Hi, I am jimmy. I am learning Java NIO. Hope I
Read 13
can hold it.
通过上面的测试我们大概可以猜测。
// 通过fileChannel将数据读取到缓冲区,返回值是缓冲区读取的字节数
int bytesRead = fileChannel.read(buf);
// 判断缓冲区是否还有数据
buf.hasRemaining()
// 获取当前缓冲区的第一个元素
buf.get()
// 切换缓冲区读写模式
buf.flip();
// 清空缓存区
buf.clear();
我们现在先只来看channel
相关的猜测。改造程序(改造部分如下)
// 设定缓冲区大小
ByteBuffer buf = ByteBuffer.allocate(48);
ByteBuffer buf2 = ByteBuffer.allocate(48);
int bytesRead = fileChannel.read(buf);
int bytesRead2 = fileChannel.read(buf);
System.out.println("bytesRead" + bytesRead + " bytesRead2" + bytesRead2);
此时走debug或者查看打印语句就可以看出来,bytesRead = 16 bytesRead2 = -1
(文件信息Hi, I am jimmy.
)。那么这样就可以得出一个结论
fileChannel.read(buf);会将channel
的数据flush
到缓冲区,而不是复制。
下面是程序则是使用fileChannel写信息到文件中
public class FileChannelPrc {
public static void main(String[] args) throws IOException {
...
// 写文件
// ByteBuffer buf2 = ByteBuffer.allocate(48);
String newData = "do not worry, be happy 时间:" + System.currentTimeMillis();
buf.clear();
buf.put(newData.getBytes());
while(buf.hasRemaining()) {
buf.flip();
fileChannel.write(buf);
}
fileChannel.close();
randomAccessFile.close();
}
}
查看文件,文件文本显示
Hi, I am jimmy. do not worry, be happy 时间:1591970658685
看这样的结果可以看出来,fileChannel
写数据的时候是在文件结尾处增加写入数据,而非覆盖。
那么根据上面的例子我们先简单的总结下fileChannel
简单使用方法。
public abstract class FileChannel
这是个抽象类),获得方式可以使用RandomAccessFile
的实例来获取
read
将内容读取到ByteBuffer
,当然这里也设计ByteBuffer
的使用,这个后面在学习总结write
方法向文件写入ByteBuffer
的信息。close
来关闭fileChannel
接下来稍微看下fileChannel
的源码(这里也算是写一个备忘录,以后遇到相关问题的时候可以回来看下,或许会有帮助)
// ---- read方法 -------
public abstract int read(ByteBuffer dst) throws IOException;
// 从指定位置开始读取数据
public abstract int read(ByteBuffer dst, long position) throws IOException;
public final long read(ByteBuffer[] dsts) throws IOException {
return read(dsts, 0, dsts.length);
}
public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
// ------ write方法 ------
public abstract int write(ByteBuffer src) throws IOException;
public abstract int write(ByteBuffer src, long position) throws IOException;
public final long write(ByteBuffer[] srcs) throws IOException {
return write(srcs, 0, srcs.length);
}
public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
// ---- open方法 ------ @since 1.7 这个方法可以获取fileChannel
public static FileChannel open(Path path, OpenOption... options) throws IOException
{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return open(path, set, NO_ATTRIBUTES);
}
public static FileChannel open(Path path, Set<? extends OpenOption> options,
FileAttribute<?>... attrs) throws IOException
{
FileSystemProvider provider = path.getFileSystem().provider();
return provider.newFileChannel(path, options, attrs);
}
// --- position --
// 获取一个新的fileChannel
public abstract FileChannel position(long newPosition) throws IOException;
// 返回buffer读取的位置
public abstract long position() throws IOException;
// --- force ----
// 强制将channel的数据写入,根据metaData决定是否写入本地磁盘存储
public abstract void force(boolean metaData) throws IOException;
// Returns the current size of this channel's file. 返回当前通道中的文件大小
public abstract long size() throws IOException;
// 截取指定长度的数据,从开始进行截取长度为 size的
public abstract FileChannel truncate(long size) throws IOException;
当然还有一些其他方法,这些等以后涉及使用的时候在补充吧。这里附上测试的时候的代码(非全部,有的API直接可使用idea的片段执行查看结果。)
public class FileChannelPrc {
public static void main(String[] args) throws IOException {
// 创建一个rw模式的随机文件
RandomAccessFile randomAccessFile =new RandomAccessFile("D:\\nioFile.txt", "rw");
// 获取fileChinnel
FileChannel fileChannel = randomAccessFile.getChannel();
// fileChannel.position();
FileChannel fileChannel2 = fileChannel.truncate(10);
// 设定缓冲区大小
ByteBuffer buf = ByteBuffer.allocate(48);
// int bytesRead = fileChannel.read(buf,10L);
int bytesRead = fileChannel2.read(buf);
// ByteBuffer buf2 = ByteBuffer.allocate(48);
// int bytesRead = fileChannel.read(buf);
/*int bytesRead2 = fileChannel.read(buf);
System.out.println("bytesRead" + bytesRead + " bytesRead2" + bytesRead2);*/
while(bytesRead !=-1){
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
// bytesRead = fileChannel.read(buf);
bytesRead = fileChannel2.read(buf);
}
// 写文件
// ByteBuffer buf2 = ByteBuffer.allocate(48);
/* String newData = "do not worry, be happy 时间:" + System.currentTimeMillis();
buf.clear();
buf.put(newData.getBytes());
while(buf.hasRemaining()) {
buf.flip();
fileChannel.write(buf);
}*/
fileChannel.close();
randomAccessFile.close();
}
}
socketChannel
是一个用来处理TCP网络套接字的通道。有俩种方式来获取SocketChannel
ServerSocketChannel
来获取SocketChannel socketChannel = SocketChannel.open();
ServerSocketChannel
类似于标准Java网络中的ServerSocket
一样,是一个可以侦听传入TCP连接的通道.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
no talk,show code
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 15:03
* @Description:
*
* 理解为server
*/
public class ServerSocketChannelPrc {
public static void main(String[] args) throws IOException {
// 获取实例
ServerSocketChannel server = ServerSocketChannel.open();
// 绑定ip和端口
server.socket().bind(new InetSocketAddress("127.0.0.1",2999));
//设置为非阻塞模式
server.configureBlocking(false);
// 获取 SocketChannel
while(true){
SocketChannel serverChannel = server.accept();
if(serverChannel != null) {
ByteBuffer buf = ByteBuffer.allocate(512);
int byteRead = serverChannel.read(buf);
StringBuilder req = new StringBuilder();
buf.flip();
while(buf.hasRemaining()){
req.append((char) buf.get());
}
buf.clear();
System.out.println("客户端发来消息" + req);
buf.put("hi,this is jimmy. be happy ".getBytes());
buf.flip();
serverChannel.write(buf);
// serverChannel.close();
}
}
}
}
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 12:04
* @Description:
*
* 可以理解为client
*/
public class SocketChannelPrc {
/**
* 为了方便,这里直接将异常抛出,实际开发要进行异常捕捉
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// 获取 SocketChannel 的实例
SocketChannel client = SocketChannel.open();
/**
* 连接服务端,这里面传入的参数对象为 SocketAddress
* 查看源码可以找到一个实现类 InetSocketAddress 构造方法 public InetSocketAddress(String hostname, int port)
* 看到这个构造就很熟悉了,
*
* 连接服务 127.0.0.1 和 2999 端口
*/
client.connect(new InetSocketAddress("127.0.0.1",2999));
// 有channel就需要有缓冲区buffer
ByteBuffer buf = ByteBuffer.allocate(512);
buf.put("hi, jimmy,this is client.".getBytes());
// 切换读写模式
buf.flip();
// 写数据去服务器
client.write(buf);
// 清空buffer,此时相当于将buffer重置为读模式
buf.clear();
int byteRead = client.read(buf);
buf.flip();
StringBuilder stringBuffer=new StringBuilder();
while (buf.hasRemaining()){
stringBuffer.append((char) buf.get());
}
buf.clear();
System.out.println("从服务端接收到的数据:"+stringBuffer);
// 关闭 socketChannel
client.close();
}
}
先启动服务端ServerSocketChannel
,然后U启动SocketChannel
.返回结果如下:
客户端发来消息hi, jimmy,this is client.
从服务端接收到的数据:hi,this is jimmy. be happy
整体看来和fileChannel
差不太多,基本都是要先有一个打开的channel
(有一个方法是isOpen()可以获取是否打开)。然后使用相应的缓冲区来将数据从通道写入缓冲区或者从缓冲区读取数据。(关于buffer相关的方法后面在学习)。不过要注意一些特别的地方SocketChannel``ServerSocketChannel
的一个方法configureBlocking()
这个方法表示这个SocketChannel``ServerSocketChannel
是阻塞还是非阻塞的,具体使用方式等学习了selector
的时候在进行补充。当然上面程序是不完善的,接受放缓冲区不够大的时候会报错。具体可以改下代码看看。
DatagramChannel
类似于java 网络编程的DatagramSocket类;使用UDP进行网络传输
同样的可以使用open方法获取一个实例
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 17:32
* @Description:
* server
*/
public class DatagramChannelPrcS {
public static void main(String[] args) throws IOException {
DatagramChannel s = DatagramChannel.open();
s.bind(new InetSocketAddress("127.0.0.1",3999));
ByteBuffer buf = ByteBuffer.allocate(48);
s.receive(buf);
buf.clear();
while(buf.hasRemaining()){
System.out.print((char)buf.get());
}
}
}
package jniolearn.channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @Author: jimmy
* @Date: 2020/6/13 17:26
* @Description:
* client
*/
public class DatagramChannelPrc {
public static void main(String[] args) throws IOException {
DatagramChannel channel = DatagramChannel.open();
// 绑定端口
// channel.bind(new InetSocketAddress(3999));
ByteBuffer buf = ByteBuffer.allocate(48);
buf.put("datagrameChannel info".getBytes());
// datagrameChannel不需要使用这个方法
// channel.write(buf);
buf.flip();
int a = channel.send(buf,new InetSocketAddress("127.0.0.1",3999));
channel.close();
}
}
使用起来比较简单,服务端可以直接使用bind方法绑定端口、ip。客户端从缓冲区直接将数据发哦是那个至服务器。是否发送成功或者成功被接收到是没有保证的;发送消息通过send方法发出,改方法返回一个int值,表示成功发送的字节数:
那么这学习留下一个尾巴就是关于阻塞模式和非阻塞模式的区别以及应用。这个在学习完其他类之后在进行补充学习
buf = ByteBuffer.allocate(48);
buf.put(“datagrameChannel info”.getBytes());
// datagrameChannel不需要使用这个方法
// channel.write(buf);
buf.flip();
int a = channel.send(buf,new InetSocketAddress(“127.0.0.1”,3999));
channel.close();
}
}
使用起来比较简单,服务端可以直接使用bind方法绑定端口、ip。客户端从缓冲区直接将数据发哦是那个至服务器。是否发送成功或者成功被接收到是没有保证的;发送消息通过send方法发出,改方法返回一个int值,表示成功发送的字节数:
那么这学习留下一个尾巴就是关于阻塞模式和非阻塞模式的区别以及应用。这个在学习完其他类之后在进行补充学习