一、ByteBuffer
1、基本使用代码
- 向buffer写入数据,例如调用channelread(buffer)
- 调用flip()切换至读模式
- 从buffer读取数据,例如调用buffer.get()
- 调用clear() 或cornpact()切换至写模式
- 重复1-4
public static void main(String[] args) {
// FileChannel
// 1、输入输出流 2.RandomAccessFile data.txt
try(FileChannel channel = new FileInputStream("D:\\1zheng\\dai\\netty\\mode01\\data.txt").getChannel()){
// 准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10); //10个字节
while (true) {
// 从channel 读取数据,向buffer写入
int read = channel.read(buffer);
log.debug("读取到的字节数:{}", read);
if (read == -1) break; //没有内容时退出
// 打印内容
buffer.flip(); //切换读模式
while (buffer.hasRemaining()) { //是否还有剩余数据
byte b = buffer.get();
log.debug("实际字节:{}", (char) b);
}
buffer.clear(); //切换写模式
}
}catch (IOException e){
}
}
ByteBuffer有以下重要属性
- capacity :容量
- position : 读写下标
- limit :读写限制
流程
2、ByteBuffer常见方法
分配空间
可以使用 allocate
方法为ByteBuffer分配内存,也可以使用*allocateDirect
方法*
allocate
:分配的是是java堆内存,读写效率低,受到GC的影响
allocateDirect
:直接内存,读写效率高(少一次拷贝),不会受到GC的影响,分配的效率低
读写操作
clear()
:从0下标开始写
cornpact()
:压缩已有的空间,从写入到的位置继续写
get()
:从position 位置开始读;
get(0)
:读取0下标的值;
rewind():把
position 移动到0下标;
mark()
:标记当前position 下标
reset()
:把
position移动到mark()标记的下标去;
字符串转换
put("hello".getBytes());
StandardCharsets.UTF_8.encode("hello");
ByteBuffer.wrap("hello".getBytes());
public static void main(String[] args) {
// 1、字符串转为ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
buffer.put("hello".getBytes()); //该方法需要去手动转为读模式
buffer.flip();
System.out.println(buffer);
System.out.println((char) buffer.get());
// 下面两种方法可以自动转为读模式
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
System.out.println(buffer2);
System.out.println((char) buffer2.get());
ByteBuffer buffer1 = ByteBuffer.wrap("hello".getBytes());
System.out.println(buffer1);
System.out.println((char) buffer1.get());
// 2、把ByteBuffer转为字符串
String s = StandardCharsets.UTF_8.decode(buffer2).toString();
System.out.println(s);
}
分散读取,集中写入到磁盘
public static void main(String[] args) {
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
ByteBuffer buffer3 = StandardCharsets.UTF_8.encode("hello");
ByteBuffer buffer4 = StandardCharsets.UTF_8.encode("你好");
try(FileChannel channel = new RandomAccessFile("words2.txt","rw").getChannel()){
channel.write(new ByteBuffer[]{buffer2,buffer3,buffer4});
}catch (Exception e){}
}
解决网络编程中ByteBuffer
常见的粘包,半包问题
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.put("Hello,world\nI'm zhangsan\nHo".getBytes());
split(buffer); //会先把粘包的读取完
buffer.put("w are you?\n".getBytes());
split(buffer); //最后在把半包的拼接读取掉
}
private static void split(ByteBuffer buffer) {
buffer.flip();
for (int i = 0; i < buffer.limit(); i++) {
// 通过\n找到一条完整消息
if (buffer.get(i)=='\n'){
int length = i+1-buffer.position();
// 创建指定大小的缓冲区
ByteBuffer target = ByteBuffer.allocate(length);
// 把完整数据写入新的ByteBuffer中
for (int j = 0; j < length; j++) {
target.put(buffer.get());
}
target.flip();
String s = StandardCharsets.UTF_8.decode(target).toString();
System.out.println(s);
System.out.println(buffer);
}
}
buffer.compact();
}
二、FileChannel
⚠️注意
FileChannel只能工作在阻塞模式下
文件拷贝:通过FileChannel把文件内容拷贝到新的位置
该方式有个缺点一次性只能传输2G的大小
transferTo
():该方法采用的是一种零拷贝的技术,直接再内核缓冲区把数据发送到目标通道,减少了从内核态切换到用户态再切换到内核态的上下文切换操作,且直接发送数据到目标通道。效率会高很多。
public static void main(String[] args) {
try (FileChannel from = new FileInputStream("D:\\1zheng\\dai\\netty\\mode01\\data.txt").getChannel();
FileChannel to = new FileOutputStream("D:\\1zheng\\dai\\netty\\mode01\\tt01.txt").getChannel()
){
// 效率比传统io输入输出的效率高底层会利用操作系统的零拷贝进行优化
/*
* from读取文件通道,(开始读取下标,结束下标,读取到通道)
* 一次最多传输 2G 的数据
*/
from.transferTo(0,from.size(),to);
}catch (Exception e){
e.printStackTrace();
}
}
如果文件比较大时,可以循环传输
public static void main(String[] args) {
try (FileChannel from = new FileInputStream("D:\\1zheng\\dai\\netty\\mode01\\data.txt").getChannel();
FileChannel to = new FileOutputStream("D:\\1zheng\\dai\\netty\\mode01\\tt01.txt").getChannel()
){
// 效率比传统io输入输出的效率高底层会利用操作系统的零拷贝进行优化
/*
* 一次最多传输 2G 的数据
*/
long size = from.size();
// left 变量代表还剩下多少字节
for (long left = size; left>0;){
System.out.println("position:"+(size - left) + " left:" +left);
// from读取文件通道,(开始读取下标,结束下标,读取到通道)
left -= from.transferTo((size - left), left, to); //方法返回值是本次传输的大小
}
}catch (Exception e){
e.printStackTrace();
}
}
Path
- Path用来表示文件路径
- Paths是工具类,用来获取Path实例
路径正常化
Files
检查文件是否存在,创建目录
创建多级
拷贝文件
public static void main(String[] args) {
Path path1 = Paths.get("D:\\1zheng\\dai\\netty\\mode01\\data.txt");
Path path2 = Paths.get("D:\\1zheng\\dai\\netty\\mode01\\tt02.txt");
try{
// 直接拷贝,但如果已有,则报错 FileAlreadyExistsException
Files.copy(path1,path2);
// 拷贝,如果已经有,则覆盖
Files.copy(path1,path2, StandardCopyOption.REPLACE_EXISTING);
}catch (Exception e){
e.printStackTrace();
}
}
移动文件
public static void main(String[] args) throws IOException {
Path path1 = Paths.get("D:\\1zheng\\dai\\netty\\mode01\\data.txt");
Path path2 = Paths.get("data.txt");
// StandardCopyOption.ATOMIC_MOVE用于保证文件的原子性
Files.move(path2,path1, StandardCopyOption.ATOMIC_MOVE);
}
获取文件属性:readAttributes
- 具体属性名称:
🅾️ 获取文件目录:Files.*walkFileTree
()*
AtomicInteger
:计数器
public static void test01(Path path) throws IOException {
// 计数器
final AtomicInteger dirCount = new AtomicInteger();
final AtomicInteger fileCount = new AtomicInteger();
// 获取文件夹目录
Files.walkFileTree(path,new SimpleFileVisitor<Path>(){
// 遍历文件夹
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("====>"+dir);
dirCount.incrementAndGet();
return super.preVisitDirectory(dir, attrs);
}
// 遍历文件
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file);
fileCount.incrementAndGet();
return super.visitFile(file, attrs);
}
});
System.out.println("dir count: "+dirCount);
System.out.println("file count: "+fileCount);
}
文件夹拷贝
public static void main(String[] args) throws IOException {
String source = "D:\\data";
String target = "D:\\data202";
Files.walk(Paths.get(source)).forEach(path->{
try {
String targetName = path.toString().replace(source, target);
// 判断该路径是否是文件夹
if (Files.isDirectory(path)) {
// 新建文件夹
Files.createDirectory(Paths.get(targetName));
}
// 判断该路径是否是文件
else if (Files.isRegularFile(path)){
// 拷贝文件
Files.copy(path , Paths.get(targetName));
}
} catch (IOException e) {
e.printStackTrace();
}
});
}