1、NIO详解之标准NIO
前言
Github:https://github.com/HealerJean
NIO即新的输入输出,这个库是在JDK1.4中才引入的。它在标准java代码中提供了高速的面向块的IO操作。NIO即New IO,NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。
在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO(异步Io),
本篇主要介绍 标准NIO__
Channel 是通过 Buffer 来读写数据的
1、Buffer
Buffer是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行
当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。
flip() 方法将 Buffer切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear()
或 compact()
方法。
clear()
方法会清空整个缓冲区。
compact()
方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
1.1、控制buffer状态的三个变量
position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节的位置
limit:代表还有多少数据可以取出或还有多少空间可以写入(其实是一回事),它的值小于等于capacity。
capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。
1、图文分析读写过程
1、初始化缓冲区状态
ByteBuffer.allocate(capacity)
// 创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
(limit=capacity ,position=0)
2、写入缓存区数据
2.1、从channel中读取数据到缓冲区
将数据从channel读入buffer,返回的是缓冲区里面的字节数,如果里面没有数据,则返回-1,如果数据量小一下子就写进来了
channel.read( buffer );
2、2、自定义数据到写入缓冲区
// 2、创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 3、缓冲区中放入数据
byte[] text = "ABCDEFG".getBytes() ;
for (int i=0; i<text.length; ++i) {
buffer.put( text[i] );
}
3、缓冲区变成可读取模式
3.2、buffer.flip() 切换到读取模式
只有切换到读模式才能被其他人使用
写入数据之前我们调用了buffer.flip();方法,这个方法把当前的指针位置position设置成了limit。再将当前指针position指向数据的最开始端
position 被设置为 0,这意味着我们得到的下一个字节是第一个字节。
limit 已被设置为原来的 position,这意味着它包括以前读到的所有字节,并且一个字节也不多。 这样就是读取模式
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0的字符。(所以我们在其他地方使用的时候,就注意了,也就是说看情况,不是非它不可以读取的,因为还有其他的方法啊,笨笨,比如)
ByteBuffer.array() 返回的 array 长度为 ByteBuffer allocate的长度,并不是里面所含的内容的长度
注意后面的trim,没有的话,就会打印所有buffer缓冲区长度的内容,打印出来的结果就是正常数据+类似乱码
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println(msg);
4、全部读完
buffer.get() 按照缓冲区字节读取
//5、按照缓冲区字节读取
while (buffer.remaining()>0) {
byte b = buffer.get();
System.out.print(((char)b)); //读取中文会乱码哦
}
public final int remaining() {
return limit - position;
}
此时 position = limit
5、读完清空缓冲区
5.1、buffer.clear() 清除缓冲区
清空缓冲区内容,相当于重置初始化缓冲区
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
3、Channel
Channel是一个对象(通道),可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:
1、Channel是双向的,既可以读又可以写,而流是单向的
2、Channel可以进行异步的读写
3、对Channel的读写必须通过buffer对象
正如上面提到的,所有数据都通过Buffer对象处理,所以,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;同样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。
3.1、 Channel类型
在Java NIO中Channel主要有如下几种类型:
`FileChannel`:从文件读取数据的(不可以异步的)
`DatagramChannel`:读写UDP网络协议数据
`ServerSocketChannel`:可以监听TCP连接
`SocketChannel`:读写TCP网络协议数据(可以异步)
4、读取数据
@Test
public void readFileByChannelToBuffer(){
try {
FileInputStream fileInputStream = new FileInputStream("src/main/java/com/hlj/nio/D01Nio/file.txt") ;
// 1、获取通道
FileChannel channel = fileInputStream.getChannel();
// 2、创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3、读取数据到缓冲区
int n = channel.read(buffer); //返回的是缓冲区里面的字节数
//4、读取模式
buffer.flip();
//5、按照缓冲区字节读取
while (buffer.remaining()>0) {
byte b = buffer.get();
System.out.print(((char)b)); //读取中文会乱码哦
}
//6、读取完成清空缓冲区
buffer.clear();
//7、关闭channel
channel.close();
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2、写入数据
@Test
public void writeFileByChannelToBuffer(){
try {
FileOutputStream fileOutputStream = new FileOutputStream("src/main/java/com/hlj/nio/D01Nio/D02写入数据/write.txt") ;
// 1、获取通道
FileChannel channel = fileOutputStream.getChannel();
// 2、创建缓冲区,就表示创建了一个容量为1024字节的ByteBuffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 3、缓冲区中放入数据
byte[] text = "ABCDEFG".getBytes() ;
for (int i=0; i<text.length; ++i) {
buffer.put( text[i] );
}
// 4、切换buffe到读模式,这样才能被channel读取并且写入到文件中
buffer.flip() ;
//5、将缓冲区的写入到管道中去
channel.write(buffer);
//6、关闭channel
channel.close();
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3、读写结合
package com.hlj.nio.D01Nio.D03读写结合;
import org.apache.tomcat.jni.Buffer;
import org.junit.Test;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @Description
* @Author HealerJean
* @Date 2018/3/27 下午6:41.
*/
public class ReadAndWriteStudy {
public static void main(String[] args) {
}
@Test
public void readToWrite(){
String read = "src/main/java/com/hlj/nio/D01Nio/D03读写结合/read.txt";
String write = "src/main/java/com/hlj/nio/D01Nio/D03读写结合/write.txt";
copyFileUseNIO(read,write);
}
/**
* 用java NIO api拷贝文件
* @param src
* @param dst
* @throws IOException
*/
public void copyFileUseNIO(String src,String dst){
try {
FileInputStream fileInputStream = new FileInputStream(new File(src));
FileOutputStream fileOutputStream=new FileOutputStream(new File(dst));
//1、获得传输通道channel
FileChannel readChannel=fileInputStream.getChannel();
FileChannel wrieteChannel=fileOutputStream.getChannel();
//2、获得容器buffer,代表每次可以最多写入1024个字节
ByteBuffer buffer=ByteBuffer.allocate(1024);
while (true){
//3、读取数据到缓冲区,每次最多1024个字节
int n = readChannel.read(buffer);
if(n==-1){
break;
}
//3、切换到读模式,这样才能被被channel读取,写入到文件中去
buffer.flip() ;
wrieteChannel.write(buffer);
//4、清空缓冲区,用于 readChannel.read(buffer) 进行写入
buffer.clear() ;
}
readChannel.close();
wrieteChannel.close();
fileOutputStream.close();
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}