NIO概述:
NIO在JDK1.4后引入的
NIO是面向块(缓冲区)编程,旧IO是面向流编程
Java中针对NIO的一些核心的包和接口、类
java.nio 主要包含了各种与Buffer相关的类
java.nio.channel 主要包含了与Channel和Selector相关的类和接口
java.nio.charset 主要包含了与编码相关的类接口
java.nio.channels.spi 主要包含了与Channel相关的服务提供者编程接口
javan.nio.charset.spi 主要包含了与charset相关的服务提供者编程接口
四个核心包
- Buffer
- Channel
- CharSet
- Selector
面向缓冲区编程:
数据的读写必须经过缓冲区
我们可以使用Buffer所对应的子类来数据从通道(Channel)流向缓冲区
从缓冲区写到通道叫做读取缓冲区
Buffer
Buffer是一个抽象类,针对缓冲区封装的一个类,提供相应的方法来操作这个缓冲区
子类有
ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer
核心类
ByteBuffer和CharBuffer
ByteBuffer有一个子类 MappedByteBuffer
MappedByteBuffer类能够将文件直接映射到内存中,那么这样我们就可以像访问内存一样访问文件,非常方便
获取Buffer
获取ByteBuffer
-
static ByteBuffer allocate(int capacity) 分配一个新的字节缓冲区。 static ByteBuffer allocateDirect(int capacity) 分配新的直接字节缓冲区。 二者获取Buffer的区别 1.创建普通Buffer成本低,读写的效率不高 2.因为创建直接Buffer成本高,所以我们一般用在Buffer生存周期较长的时候使用 3.只有ByteBuffer才能够创建直接Buffer,其他的Buffer对象是不能够创建 4.如果创建了直接Buffer但是我又想要使用其他Buffer的功能,可以将ByteBuffer转换成其他Buffer asIntBuffer()
四个非常重要的概念
- capacity: 缓冲区的容量,不可以为负数,一旦创建了就不能够改变
- limit: 无效缓冲区的第一个位置索引,limit后面的数据既不可读,也不可写 * **position:**下一个可以被读取或者写入的缓冲区位置索引
- mark: 标记索引,该索引能够用于下次读取或者写入,它只能够在0-position之间
四个系数的关系:
0 < mark < postion < limit < capacity
五个方法
flip(): 将写模式切换为读模式, 将limit的值改为postion的值,同时将postion归0
特点: 就是为下一次数据的读取做好准备
clear(): 将读模式切换为写模式,将limit改为capacity的值,同时将postion归0
特点: 就是为下一次数据的写入做好准备
put(): 相对读取,向Buffer中存储数据
get(): 相对读取,从Buffer中获取数据
mark(): 设置标记位
reset(): 重置
hasRemaining(): 判断当前位置和limit之间是否还有元素可处理
绝对读取: get(index) 不会影响position的位置
相对读取: put() get() 会影响,每次读取一次,指针后移
public class NIODemo02 {
public static void main(String[] args) {
// ByteBuffer buffer = ByteBuffer.allocate(5);
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
CharBuffer buffer = CharBuffer.allocate(8);
// 已经准备好了向Buffer中写数据 写模式
System.out.println("capacity:" + buffer.capacity()); // 8
System.out.println("limit:" + buffer.limit()); // 8
System.out.println("position:" + buffer.position()); // 0
buffer.put('x');
buffer.put('y');
buffer.put('r');
System.out.println("------------------------");
System.out.println("capacity:" + buffer.capacity()); // 8
System.out.println("limit:" + buffer.limit()); // 8
System.out.println("position:" + buffer.position()); // 3
System.out.println("------------------------");
// 切换模式 ,limit变为position的位置然后将position变为0
buffer.flip();
System.out.println("capacity:" + buffer.capacity()); // 8
System.out.println("limit:" + buffer.limit()); // 3
System.out.println("position:" + buffer.position()); // 0
System.out.println("------------------------");
char ch = 0;
ch = buffer.get();
System.out.println(ch);
buffer.mark();// mark: 1
ch = buffer.get();
System.out.println(ch);
System.out.println("------------------------");
System.out.println("capacity:" + buffer.capacity()); // 8
System.out.println("limit:" + buffer.limit()); // 3
System.out.println("position:" + buffer.position()); // 2
System.out.println("------------------------");
ch = buffer.get();
System.out.println(ch);
// ch = buffer.get();
// System.out.println(ch);
System.out.println("---------++---------------");
buffer.reset();
ch = 0;
while (buffer.hasRemaining()) {
ch = buffer.get();
System.out.println((char) ch);
}
System.out.println("------------------");
buffer.clear(); // 将postion 清 0 ,将limit = capacity
System.out.println("capacity:" + buffer.capacity()); // 8
System.out.println("limit:" + buffer.limit()); // 8
System.out.println("position:" + buffer.position()); // 0
// 注意: 调用clear方法只是将读模式改为写模式,并不会清空缓冲区的数据
System.out.println(buffer.get(1));
System.out.println("执行绝对读取之后Buffer的position位置:" + buffer.position());
}
}
Channel 通道
Channel原理类似于传统的流对象, FileInputStream FileOutputStream
但是有3个主要的区别
- 程序如果想要读取Channel中的数据,不能够直接读写,必须经过Buffer 【唯一性】
- 通过Channel通道既能够读取也能够写入数据 【双向性】
- Channel能够将指定的部分或者全部文件映射到内存中
全部映射
MappedByteBuffer
部分文件映射
Java中为Channel提供了如下常用的类
FileChannel 和文件相关的通道
DatagramChannel 和UDP协议传输数据相关的通道
SocketChannel 针对TCP协议客户端Socket提供的通道
ServerSocketChannel 针对TCP协议服务器端Socket提供的通道
获取FileChannel对象
和文件相关的普通流有哪些?
FileInputStream FileOutputStream RandomAccessFile
常用的方法
read() : 将Channel中的数据读取到Buffer中
write() : 向Buffer中写入数据
map(): 将channel中的数据全部或者部分映射到Buffer中
inChannel.map(mode, position, size)
MappedByteBuffer mappBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
public class NIODemo03 {
public static void main(String[] args) throws IOException {
File srcFile = new File("nio.txt");
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(new File("nio4.txt"));
// 获取Channel对象
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
// 获取Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(10);
// 将inChannel中的数据读取到Buffer中
// int len = inChannel.read(buffer);
// System.out.println(len);
// byte[] bys = buffer.array();
// System.out.println(new String(bys, 0, len));
// // 切换成读模式
// buffer.flip();
// len = inChannel.read(buffer);
// System.out.println(len);
// System.out.println(new String(buffer.array(), 0, len));
// buffer.flip();
//
// len = inChannel.read(buffer);
// System.out.println(len);
// System.out.println(new String(buffer.array(), 0, len));
// buffer.flip();
// int len = 0;
// while ((len = inChannel.read(buffer)) != -1) {
// buffer.flip();
// System.out.print(new String(buffer.array(), 0, len));
// }
// while ((inChannel.read(buffer)) != -1) {
// buffer.flip(); // 为取出数据做好准备
// outChannel.write(buffer);
// buffer.clear();
// }
// 3.Channel能够将指定的部分或者全部文件映射到内存中
// java.nio.channels.NonWritableChannelException
// java.nio.channels.NonReadableChannelException
// MappedByteBuffer mapBuffer = inChannel.map(MapMode.READ_WRITE, 0, srcFile.length());
// byte[] array = mapBuffer.array();
// System.out.println(new String(array));
}
}
利用NIO来遍历文件
基于事件驱动的方式遍历文件
public class NIODemo07 {
public static void main(String[] args) throws IOException {
FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException {
System.out.println("正准备访问" + path + "文件");
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
System.out.println("正在访问" + path + "文件");
if (path.endsWith("NIODemo03.java")) {
System.out.println("恭喜你找到了我想要的Java文件,你可以停止查找了");
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
return FileVisitResult.SKIP_SIBLINGS;
}
// @Override
// public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException {
// return FileVisitResult.CONTINUE;
// }
};
Files.walkFileTree(Paths.get("D:\\JavaSE"), visitor);
}
}
使用NIO的WatchService监控文件系统变化
public class NIODemo08 {
public static void main(String[] args) throws Exception {
// 获取到文件系统的WatchService对象
WatchService watchService = FileSystems.getDefault().newWatchService();
Paths.get("c:").register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.OVERFLOW);
// 通过wachService来监听文件系统
while (true) {
WatchKey key = watchService.take();
List<WatchEvent<?>> pollEvents = key.pollEvents();
for (WatchEvent<?> watchEvent : pollEvents) {
System.out.println(watchEvent.context() + "发生了" + watchEvent.kind() + "事件");
}
boolean reset = key.reset();
if (!reset) {
break;
}
}
}
}