Java NIO中一个socket连接使用一个Channel来表示。从更广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。
Java NIO的通道可以更加细化。例如,不同的网络传输协议类型,在Java中都有不同的NIO Channel实现。
其中最为重要的四种Channel实现:
@Test
public void getChannel(){
try {
// 创建一个文件输出流
FileOutputStream fos = new FileOutputStream("D:\\h.txt");
FileChannel channel1 = fos.getChannel();
// 创建一个文件输入流
FileInputStream fins = new FileInputStream("D:\\h.txt");
FileChannel channel2 = fins.getChannel();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
//创建RandomAccessFile随机访问对象
RandomAccessFile rFile = new RandomAccessFile("filename.txt","rw");
//获取文件流的通道(可读可写)
FileChannel channel = rFile.getChannel();
复制代码
在大部分应用场景中,从通道读取数据都会调用通道的int read(ByteBuffer buf)
方法,它把从通道读取的数据写入ByteBuffer缓冲区,并且返回读取的数据量。
public class FileChannelReadDemo01 {
public static void main(String[] args) {
testReadDataFromChannel();
}
public static void testReadDataFromChannel() {
// 创建一个文件输出流
RandomAccessFile fos = null;
FileChannel channel = null;
try {
fos = new RandomAccessFile ("D:\\h.txt","rw");
channel = fos.getChannel();
// 定义一个字节缓冲区域
ByteBuffer buffer = ByteBuffer.allocate(1024);
int length = -1;
while ((length = channel.read(buffer)) != -1) {
// 调用flip,buffer由写入变为读
buffer.flip();
byte[] array = buffer.array();
System.out.print(new String(array, 0, length,"utf-8"));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
closeQuietly(fos);
closeQuietly(channel);
}
}
public static void closeQuietly(java.io.Closeable o) {
if (null == o) {
return;
}
try {
o.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
把数据写入通道,在大部分应用场景中都会调用通道的write(ByteBuffer)
方法,此方法的参数是一个ByteBuffer缓冲区实例,是待写数据的来源。
write(ByteBuffer)
方法的作用是从ByteBuffer缓冲区中读取数据,然后写入通道自身,而返回值是写入成功的字节数。
对于入参buffer实例来说,需要从其中读取数据写入channel通道中,所以入参buffer必须处于读模式,不能处于写模式。
//如果buf处于写模式(如刚写完数据),需要翻转buf,使其变成读模式
buf.flip();
int outlength = 0;
//调用write()方法,将buf的数据写入通道
while ((outlength = outchannel.write(buf)) != 0) {
System.out.println("写入的字节数:" + outlength);
}
当通道使用完成后,必须将其关闭。关闭非常简单,调用close()方法即可。
在将缓冲区写入通道时,出于性能的原因,操作系统不可能每次都实时地将写入数据落地(或刷新)到磁盘,完成最终的数据保存。
在将缓冲区数据写入通道时,要保证数据能写入磁盘,可以在写入后调用一下FileChannel的force()方法。
public class FileNIOCopyDemo {
public static void main(String[] args) throws IOException {
// 源文件
File srcFile = new File("D:\\h.txt");
// 目标文件
File destFile = new File("D:\\hd.txt");
// 校验源文件是否存在
fileIsExists(srcFile);
// 创建目标文件
createFile(destFile);
long startTime = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outchannel = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
inChannel = fis.getChannel();
outchannel = fos.getChannel();
int length = -1;
// 创建一个ByteBuffer
ByteBuffer buf = ByteBuffer.allocate(1024);
// 读取文件
while ((length = inChannel.read(buf)) != -1) {
//翻转buf,变成成读模式
buf.flip();
int outlength = 0;
// 从buffer中读取数据,然后写入到channe中
while ((outlength = outchannel.write(buf)) != 0) {
System.out.println("写入字节数:" + outlength);
}
// 清除buf,变成写入模式
buf.clear();
}
//强制刷新磁盘
outchannel.force(true);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeQuietly(fis);
closeQuietly(fos);
closeQuietly(inChannel);
closeQuietly(outchannel);
}
long endTime = System.currentTimeMillis();
System.out.println("一共耗时:【" + (endTime - startTime) + "】 ms");
}
private static void createFile(File destFile) throws IOException {
if (!destFile.exists()) {
destFile.createNewFile();
}
}
private static void fileIsExists(File srcFile) throws FileNotFoundException {
if (!srcFile.exists()) {
throw new FileNotFoundException("source.file.not.exists");
}
}
private static void closeQuietly(java.io.Closeable o) {
if (null == o) {
return;
}
try {
o.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
一共耗时:【14】 ms
作为文件复制的程序来说,以上代码的效率不是最高的。更高效的文件复制可以调用文件通道的transferFrom()方法。
public class FileNIOFastCopyDemo {
public static void main(String[] args) throws IOException {
// 源文件
File srcFile = new File("D:\\h.txt");
// 目标文件
File destFile = new File("D:\\hd.txt");
// 校验源文件是否存在
fileIsExists(srcFile);
// 创建目标文件
createFile(destFile);
long startTime = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outchannel = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
inChannel = fis.getChannel();
outchannel = fos.getChannel();
long size = inChannel.size();
long pos = 0;
long count = 0;
while (pos < size) {
//每次复制最多1024个字节,没有就复制剩余的
count = size - pos > 1024 ? 1024 : size - pos;
//复制内存,偏移量pos + count长度
pos += outchannel.transferFrom(inChannel, pos, count);
}
//强制刷新磁盘
outchannel.force(true);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeQuietly(fis);
closeQuietly(fos);
closeQuietly(inChannel);
closeQuietly(outchannel);
}
long endTime = System.currentTimeMillis();
System.out.println("一共耗时:【" + (endTime - startTime) + "】 ms");
}
private static void createFile(File destFile) throws IOException {
if (!destFile.exists()) {
destFile.createNewFile();
}
}
private static void fileIsExists(File srcFile) throws FileNotFoundException {
if (!srcFile.exists()) {
throw new FileNotFoundException("source.file.not.exists");
}
}
private static void closeQuietly(Closeable o) {
if (null == o) {
return;
}
try {
o.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在NIO中,涉及网络连接的通道有两个:
其中,NIO中的SocketChannel传输通道与OIO中的Socket类对应,NIO中的ServerSocketChannel监听通道对应于OIO中的ServerSocket类。
ServerSocketChannel仅应用于服务端,而SocketChannel同时处于服务端和客户端。所以,对于一个连接,两端都有一个负责传输的SocketChannel。
无论是ServerSocketChannel还是SocketChannel,都支持阻塞和非阻塞两种模式。如何进行模式的设置呢?调用configureBlocking()方法,具体如下:
// 设置为非阻塞模式。
socketChannel.configureBlocking(false)
// 设置为阻塞模式。
socketChannel.configureBlocking(true)
在阻塞模式下,SocketChannel的连接、读、写操作都是同步阻塞式的,,在效率上与Java OIO面向流的阻塞式读写操作相同。
给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。
感谢阅读,文章对你有帮助的话,不妨 一键三连 支持一下吧。你们的支持是我最大的动力,祝大家万事胜意!