解析NIO
NIO 是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
NIO有三大组件:
1、channel:数据传输的通道,读写数据的双向通道
2、buffer:内存缓冲区,暂存channel中的数据
3、selector:选择器
下面先简单解释一些selector:
一开始我们是采用的多线程设计解决多个socket连接的,一个子线程只能管理一个socket
缺点是:1、内存占用高;2、线程上下文切换成本高;3、只适合连接数较少的情况
后面改进了多线程设计,有了线程池设计:一个线程可以管理多个socket
缺点是:1、堵塞模式下,线程仅能处理一个socket的连接;2、仅适用于短连接的情况下。
所以就有了selector来解决上述问题
selector作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非堵塞模式下,不会让线程吊死在一个channel上,适合连接数特别多,但流量低的场景。
下面再来看看Buffer中的byteBUffer
byteBuffer有三个重要属性:1、capaticy容量;2、position 读写指针位置;3、limit 读写限制(限制的字节数)
写模式下,limit默认与capacity保持一致,读模式下,position与实际存的字节数一致
创建ByteBuffer实例有俩种方法,不用new创建:
//准备缓冲区 划分一块内存作为缓冲区 固定大小为10字节,不可改动
//使用的堆内存 ,读写效率较低,受到GC影响,GC为虚拟机的垃圾回收机制
ByteBuffer buffer=ByteBuffer.allocate(10);
//或者 直接内存,读写效率较高(少一次数据的拷贝),系统内存不受GC影响;分配内存效率较低,使用不当可能造成内存泄漏
ByteBuffer buffer=ByteBuffer.allocateDirect(10);
byteBuffer的各种方法:
package org.example;
import java.nio.ByteBuffer;
import static org.example.ByteBufferUtil.debugAll;
//各种方法
public class TestByteBufferReadWrite {
public static void main(String[] args) {
/*ByteBuffer buffer = ByteBuffer.allocate(10);
//写模式
buffer.put((byte) 0x61);//'a'
buffer.put(new byte[]{0x62,0x62,0x63}); //b c d
//读模式
buffer.flip();
System.out.println(buffer.get());*/
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a','b','c','d'});
buffer.flip();
//get方法会让position指针向后移,
// 如果想读重复数据,可以用rewind方法使position重置为0,或者调用get(int i)获取索引i处数据,但不会移动指针
/*buffer.get(new byte[4]);
debugAll(buffer);
buffer.rewind();
System.out.println((char)buffer.get());*/
//mark & reset
//mark 做一个标记,记录position的位置,reset是将position重置到mark的位置。
// System.out.println((char) buffer.get());
// System.out.println((char) buffer.get());
// buffer.mark();//加标记,于索引为2的位置
// System.out.println((char) buffer.get());
// System.out.println((char) buffer.get());
// buffer.reset();//将position重置到索引2
// System.out.println((char) buffer.get());
// System.out.println((char) buffer.get());
//get(i)但不会移动指针位置
System.out.println((char) buffer.get(3));
}
}
下面了解一下文本编程FileChannel :只能工作在堵塞模式下
不能直接打开FileChannel,必须通过FileInputStream、FileOutputStream或者RandomAccessFile来获取FileChannel,它们都有getChannel方法。
1、通过FileInputStream获取的channel只能读
2、通过FileOutputStream获取的channel只能写
3、通过RandomAccrssFile获取的channel是否能读写根据构造RandomAccessFile时的读写模式确定。
package org.example;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class byteBuffer {
public static void main(String[] args) {
//因为分配给buffer的内存是有限的,所以一般要读多次才能读完。
ByteBuffer buffer=ByteBuffer.allocate(10);
//从channel中读取数据,向buffer中写入
while(true) {//读多次
int len = channel.read(buffer); //返回读取的字节数,如果返回值为-1 表示已经读完了 buffer.put();
if(len==-1)
break;
//打印buffer
buffer.flip();//切换为读模式
while (buffer.hasRemaining()) {//是否还有剩余
byte b = buffer.get();//一次读一个字节 channel.write(buffer);
System.out.println((char) b);
}
//切换为写模式
buffer.clear();
}
} catch (IOException e) {
}
}
}
Path 用来表示文件路径,Paths是工具类,用来获取Path实例
Path source = Paths.get("d:\\1.txt");//绝对路径
Path source = Paths.get("d:/1.txt");//绝对路径
Path source = Paths.get("d:\\data","projects");//代表d://data/projects
//创建一级目录
Path path = Paths.get("data.txt");
Files.createDirectory(path);
//如果目录存在,会抛异常FileAlreadyExistException
//创建多级目录同样会抛异常
//创建多级目录
Path path = Paths.get("data.txt");
Files.createDirectories(path);
拷贝文件
Path source = Paths.get("1/data.txt");
Path target = Paths.get("2/data.txt");
Files.copy(source,target);
//如果target文件已存在,会抛异常
//如果想覆盖了已存在target文件的内容
Files.copy(source,target,StandardCopyOption.REPALCE_EXISTING);
1、transferTo 多次拷贝文件内容
package org.example;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
//数据传输
public class TestTransferTo {
public static void main(String[] args) {
try (
FileChannel from = new FileInputStream("data.txt").getChannel();
FileChannel to = new FileOutputStream("to.txt").getChannel();
) {
//比用文件输入输出流效率要高,底层会利用操作系统的零拷贝进行优化,单次最大传输量为2g,所以我们可以传输多次。
//单次传输
//from.transferTo(0,from.size(),to);
//多次传输
long size =from.size();
for(long length=size;length>0;){
length-=from.transferTo(size-length,length,to);
}
} catch (IOException e) {
e.printStackTrace();
};
}
}
2、copy拷贝多级目录
package org.example;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class TestFilesCopy {
public static void main(String[] args) throws IOException {
//拷贝多级目录
String source="...1";//源文件
String target="...2";//目标文件
Files.walk(Paths.get(source)).forEach(path -> { //path 即为遍历的源文件的各个子文件
try {
String targetName=path.toString().replace(source,target);//声明要创建的目录名或文件名
//判断当前路径是否为目录
if(Files.isDirectory(path)){ //为目录
Files.createDirectory(Paths.get(targetName));
}else{ //为文件
Files.copy(path,Paths.get(targetName));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
拷贝文件用transferTo或copy,这俩个效率高.
分散读写:将一个channel中的内容分散读取到多个ByteBuffer中,这多个buffer存的内容共同组成一个channel的内容
集中写入:将多个ByteBuffer中的内容写入到同一个channel中
package org.example;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
public class TestScatteringReads {
public static void main(String[] args) {
//分散读取
/*try (FileChannel channel = new RandomAccessFile("words.txt", "r").getChannel()) {
ByteBuffer b1 = ByteBuffer.allocate(3);
ByteBuffer b2 = ByteBuffer.allocate(3);
ByteBuffer b3 = ByteBuffer.allocate(5);
channel.read(new ByteBuffer[]{b1,b2,b3});
} catch (IOException e) {
}*/
//集中写入
ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
try (FileChannel channel = new RandomAccessFile("words.txt", "rw").getChannel()) {
channel.write(new ByteBuffer[]{b1,b2,b3});
} catch (IOException e) {
};
}
}
删除文件/目录
Path target = Paths.get("1.txt");
Files.delete("target");//该方法也可以删目录,如果目录中还有内存,会报错
//如果文件不存在会报错
所以当要删除一整个文件夹时,需要我们对整个文件夹遍历一遍
walkFileTree用于遍历文件夹
package org.example;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
// 遍历文件夹
public class TestFilesWalkFileTree {
public static void main(String[] args) throws IOException {
//删除多级目录
//Files.delete(Paths.get("")); //只能删文件夹为空的目录
//需要我们先删子文件夹,在删本文件夹,从里到外
Files.walkFileTree(Paths.get(""),new SimpleFileVisitor<Path>(){
@Override //visitFile方法为访问到文件时触发
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file);//打印文件名称
Files.delete(file);
return super.visitFile(file, attrs);
}
@Override //访问完文件后触发
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("<===退出"+dir);
Files.delete(dir);
return super.postVisitDirectory(dir, exc);
}
});
}