前言
这里是对NIO学习后的个人整理,留作记录,如果帮助了你,万分开心。
文章内容若有错误,请指正。学习内容来自尚硅谷。
正文
简介
JavaNIO是从Java1.4版本开始引入的一个新的IO API,可以代替标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓冲区、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
Java NIO与IO的主要区别:
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO |
无 | 选择器(Selectors) |
IO是面向流的,也就是说程序通过IO向文件、磁盘或网络中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方,而且是单向的,只能读或者只能写。
NIO是面向缓冲区的包含两个概念,通道和缓冲区:通道表示打开IO设备的连接(相当于铁路),缓冲区是用来存取数据的(相当于火车吧)。NIO相当于两个城市通过铁路连接(Channel),火车(Buffer)双向运输货物。
使用NIO系统,需要获取用于连接IO设备的通道Channel以及用于容纳数据的缓冲区Buffer,然后操作缓冲区数据进行处理。简而言之,Channel负责传输,Buffer负责存储。
缓冲区Buffer
缓冲区就是数组,用于存储不同数据类型的数据,根据数据类型的不同(boolean除外),提供了相应类型的缓冲区(ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer)。各缓冲区的管理方式几乎一致,通过allocate()获取缓冲区。
1)缓冲区存取数据的两个核心方法:
put(): 存入数据到缓冲区中
get(): 获取缓冲区中的数据
2)缓冲区的四个核心属性:
capacity: 容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变。
limit:界限,表示缓冲区中可以操作数据的大小。(limit后的数据不能读写)
position:位置,表示缓冲区中正在操作数据的位置。
mark:标记,表示记录当前position的位置,可以通过reset()恢复到mark的位置。
在缓冲区中:
0 <= mark <= position <= limit <= capacity。
初始化10个字节缓冲区,并向缓冲区put5个byte,position指向的就是内容结尾5,此时limit和capacity都为10,当调用flip()模式切换为读的时候,这里filp()做了一些工作:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
flip函数将limit指向position,令position为0,mark=-1。因为limit后面将不可读取,所以如果只是想要重头开始读取buffer,可以使用rewind()方法。
下面举例说明:
@Test
public void test1() {
String str = "abcde";//写入缓冲区的字符串
//1. 分配一个指定大小为1024byte的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("-----------------allocate()----------------"); //1024
System.out.println(buf.position()); //0
System.out.println(buf.limit()); //1024
System.out.println(buf.capacity()); //1024
//2. 利用 put() 存入数据到缓冲区中
buf.put(str.getBytes());
System.out.println("-----------------put()----------------"); //1024
System.out.println(buf.position()); //5 当前缓冲区正在操作数据的位置为5
System.out.println(buf.limit()); //1024
System.out.println(buf.capacity()); //1024
//3. 通过flip方法切换读取数据模式, 读模式时当前位置指向0,limit指向内容的末尾即5
buf.flip();
System.out.println("-----------------flip()----------------"); //1024
System.out.println(buf.position()); //0
System.out.println(buf.limit()); //5
System.out.println(buf.capacity()); //1024
//4. 利用 get() 读取缓冲区中的数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
System.out.println(new String(dst, 0, dst.length)); //abcde
System.out.println("-----------------get()----------------"); //1024
System.out.println(buf.position()); //5
System.out.println(buf.limit()); //5
System.out.println(buf.capacity()); //1024
//5. rewind() : 可重复读,恢复position位置
buf.rewind();
System.out.println("-----------------rewind()----------------");
System.out.println(buf.position()); //0
System.out.println(buf.limit()); //5
System.out.println(buf.capacity()); //1024
//6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态
buf.clear();
System.out.println("-----------------clear()----------------");
System.out.println(buf.position()); //0
System.out.println(buf.limit()); //1024
System.out.println(buf.capacity()); //1024
System.out.println((char) buf.get()); //a 表明数据依旧存在
}
@Test
public void test2(){
String str = "abcde";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(str.getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst, 0, 2);//将读取数据到dst数组下标0长度2的位置。
System.out.println(new String(dst, 0, 2)); //ab
System.out.println(buf.position()); //2
//mark() : 标记
buf.mark(); //当前mark标记位置为2
buf.get(dst, 2, 2);
System.out.println(new String(dst, 2, 2));//cd
System.out.println(buf.position());//4
//reset() : 恢复到 mark 的位置
buf.reset();
System.out.println(buf.position());//2
//判断缓冲区中是否还有剩余数据
if(buf.hasRemaining()){
//获取缓冲区中可以操作的数量
System.out.println(buf.remaining());//3
}
}
非直接缓冲区和直接缓冲区
- 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
- 直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。
和非直接缓冲区不同,直接缓冲区是直接建立在物理内存上的,提高了效率但进行分配和取消分配所需成本通常也比非直接缓冲区高。
@Test
public void test3(){
//分配直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
//判断是否是直接缓冲区
System.out.println(buf.isDirect());
}
通道(Channel)
通道用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。Channel 本身不存储数据,因此需要配合缓冲区进行传输。
通道的主要实现类:
java.nio.channels.Channel 接口:
|–FileChannel 本地使用
|–SocketChannel 网络传输TCP使用
|–ServerSocketChannel 网络传输TCP使用
|–DatagramChannel 网络传输UDP使用
获取通道的方法:
Java 针对支持通道的类提供了 getChannel() 方法
本地 IO:
FileInputStream/FileOutputStream
RandomAccessFile
网络IO:
Socket
ServerSocket
DatagramSocket
在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()来建立通道
//指定通道建立的文件,并指定读写等属性。
FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE
, StandardOpenOption.READ, StandardOpenOption.CREATE);
关于通道的使用,下面写了本地使用几个例子:
//利用通道完成文件的复制(非直接缓冲区)
@Test
public void test1(){//10874-10953
long start = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
//①获取通道
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("d:/1.mkv");
fos = new FileOutputStream("d:/2.mkv");
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//②分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//③将通道中的数据存入缓冲区中
while(inChannel.read(buf) != -1){
buf.flip(); //切换读取数据的模式
//④将缓冲区中的数据写入通道中
outChannel.write(buf);
buf.clear(); //清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//通道的关闭channel.close()
...
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
//使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void test2() throws IOException{//2127-1902-1777
long start = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
//通道之间的数据传输(直接缓冲区)
@Test
public void test3() throws IOException{
long start = System.currentTimeMillis();
FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
// inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
//分散和聚集
@Test
public void test4() throws IOException{
RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
//1. 获取通道
FileChannel channel1 = raf1.getChannel();
//2. 分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3. 分散读取
ByteBuffer[] bufs = {buf1, buf2};
channel1.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println("-----------------");
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
//4. 聚集写入
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
}
在对视频文件使用通道读写时,直接缓冲区的读写速度比非直接缓冲区读写速度明显要快很多。NIO中还提供了字符集工具Charset,可以使用进行一些编码和解码。
JavaNIO的学习(二)