Netty学习笔记一
一. NIO 基础
non-blocking io 非阻塞IO (也可称为new IO, 因为是JDK1.4加入的)
1. 三大组件
1.1 Channel
通道:数据的传输通道。我们之前使用的Stream也是数据的传输通道,只不过他们是单向的数据传输通道(输出(流)通道,输入(流)通道),但是Channel是读写数据的**【双向通道】**,可以将数据读入buffer,也可以将buffer的数据写入channel,而之前的stream要么是输入,要么是输出,channel比stream更为底层。
常见的Channel 有
- FileChannel (文件数据传输通道)
- DatagramChannel(UDP的数据传输通道)
- SocketChannel(TCP的数据传输通道,客户端服务器端均可)
- ServerSocketChannel(TCP的数据传输通道,专用于服务器端)
1.2 Buffer
内存缓冲区(数据缓冲区):暂存从Channel读入的数据或者暂存写出的数据,在应用程序和磁盘文件或网络之间的桥梁。
buffer则用来缓冲读写数据,创建的buffer有
- ByteBuffer(字节缓冲区)
- MappedByteBuffer
- DirectByteBuffer
- HeapByteBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
- CharBuffer
1.3 Selector
选择器:饥饿和服务器端代码设计理解这个词。
多线程版
在没有NIO之前,处理多个客户端请求,采用的是多线程,一个客户端和服务器端建立一个socket连接(一个服务器,一个网卡最多可以建立65535连接),针对这个socket就可以进行读写操作,然后创建一个新的线程专门为这个socket进行服务。
多线程版缺点:
- 内存占用高(windows下默认一个线程,大概占用1M内存,一千个线程就是一个G,多了内存溢出)
- 餐馆,一个客人配备一个服务员专属服务,但是餐馆的地方有限,一千个客人一千个服务员,有可能屋子就放不下了(内存溢出)并且假设餐馆只有16个张桌子和座位(16核CPU)那同时能并行工作的服务员(线程)就只有16个,剩下的要进行轮班(就是线程的上下文切换)
- 线程上下文切换成本高,因为进行上下文切换要保护现场和恢复现场,需要时间,这要如果线程数过多,那么可能导致,线程真正用于处理的执行时间很短很短,都用在了上下文且换上
- 就假设这个餐馆为了让照顾每个顾客都能被提供服务(吃饭)就提出了一种策略,
- 每个客人可以轮流吃饭,吃两口换下一个客人(线程的上下文切换,这个就是并发操作),
- 【保护现场】:而在一个客人和下一个客人进行轮换时,你就要整理餐桌,将这个客人桌上的菜品和餐具保存好,并且将这个客人的菜品点了什么菜(对应于CPU指令执),还有什么菜没上(CPU指令执行到了哪里,)
- **【恢复现场】**等一下在轮到这个客人的时候如果菜做好了(执行指令需要的数据准备好了),你得知道上哪道菜(执行哪个CPU指令),还得把刚刚保存的 餐桌状态给恢复(恢复现场)。
- 但是这样轮班吃饭(并发执行)就有一个问题,人少的时候还行,可能一个客人一次轮换能吃一个小时,但是如果客人多了,你要保护现场和恢复现场,那么一次轮换可能就只吃一分钟,客人体验感就会很差(性能就会很差)。
- 所以只适合连接数比较少的场景。高连接数就会瘫痪。
线程上下文切换,可能导致线程根本没执行,时间片就用完了
线程池版
-
也就是这个餐馆固定只有16个服务员(线程池中有16个线程根据CPU核心数自己确定最佳的CPU利用率),这样就可以减少创建线程及线程上下文切换造成的资源浪费问题。
-
但是规定一个服务员必须将这桌客人送走,才能服务下一桌的客人,因为一桌客人可能要点菜,上菜,吃菜,最后结账走人。所以即使这桌客人在等待上菜的时候,那么这个服务员即使什么都不干也不能去服务其他客人(socket),
-
那么其他客人就得等待(陷入阻塞)直到这桌客人就餐完毕(业务执行完毕)。
-
也就是说,如果来了1000个客人,那么我同一时间有16个服务员可以为客人提供服务,那么同一时间只能有16个客人可以吃饭。其他客人(socket)阻塞(等待).
-
这样就不用创建那么多线程,浪费内存资源,又因为没有那么多线程在并发执行任务,所以减少了上下文切换。
-
但是这样的方案只适合每个客人的吃饭时间很短的场景,如果一个客人一顿饭吃了4个小时。那其他客人得等待 多久能吃上饭。所以只适合短连接
线程池版缺点
- 阻塞模式下,线程只能处理一个socket连接
- 仅适合短连接场景,
- 服务器早期都设计成短连接,早期的tomcat服务器,阻塞式的IO,适合于Http请求:连接上-发请求-返回结果就断开连接。
- 线程的利用率不高。
selector版设计
selector的作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件(可连接,可读,可写事件…),这些channel工作在**【非阻塞模式】**下,不会让线程吊死在一个channel上。适合连接数特别多,但流量低(读写数据量少)的场景,因为这样占用这个线程的时间就短。
调用selector的select()会阻塞直到channel发生了读写就绪事件,这些事件发生,select()方法就会返回这些事件交给thread处理。
- 这次只需要一个服务员(线程)来服务所有的客人(channel也就是socket)。
- 只不过是中间加了一个机器人(selector选择器)他能监听所有客人的需求,客人的一举一动都在他的监视下,一旦客人有什么请求她都能知道,然后派出服务员去提供的服务。
- 例如:三个客人(channel)都进来了,都在看菜单,突然有一个客人要点菜(发送数据)那么就告诉机器人我要点菜(有一些数据要发给服务器,服务器给我处理)
- 然后机器人就认为发生了一个事件(selector就认为发生了一个事件,会将事件处理权交给线程,)
- 让服务员去处理请求(让线程去处理socket的请求)
- 但是在处理请求的过程中,来了其他线程的请求,这时候依然是阻塞的,依然需要排队等待上一个socket处理完成。
线程利用率得到了提高,在一个socket没有请求事件发生时,可以处理其他socket的请求。
2. ByteBuffer
2.1 基本使用
代码示例
-
package com.sunyang.netty.study; import lombok.extern.slf4j.Slf4j; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @Author: sunyang * @Date: 2021/8/16 * @Description: */ @Slf4j(topic = "c.Demo") public class ByteBufferDemo { /* 文件读取 */ public static void main(String[] args) { // FileChannel // 获得FileChannel可以通过输入输出流间接的获得FileChannel。或者RandomAccessFile try (FileChannel channel = new FileInputStream("data.txt").getChannel()) { // 创建一个大小为10 的字节缓冲区,这样就不能一次读取所有的文件,要通过两次读取(为了测试举例),因为有时候你也不知道要读取的文件有多大,不可能直接定义好。 ByteBuffer byteBuffer = ByteBuffer.allocate(10); int len; while ((len = channel.read(byteBuffer)) != -1) { // 从channel中读取数据,写入到bytebuffer中.从此通道读取字节序列到给定缓冲区 log.debug("读取到的字节长度是{}", len); // 打印buffer的内容 byteBuffer.flip(); // 切换至读模式 while (byteBuffer.hasRemaining()) { // 是否还有剩余的未读数据 // 不带参数的get()是一次读取一个字节 byte b = byteBuffer.get(); log.debug("读取到实际的字符是{}", (char) b); } // 切换至写模式 byteBuffer.clear(); } } catch (IOException e) { } } }
-
// 输出结果 15:35:18.964 [main] c.Demo - 读取到的字节长度是10 15:35:18.968 [main] c.Demo - 读取到实际的字符是1 15:35:18.968 [main] c.Demo - 读取到实际的字符是2 15:35:18.968 [main] c.Demo - 读取到实际的字符是3 15:35:18.968 [main] c.Demo - 读取到实际的字符是4 15:35:18.968 [main] c.Demo - 读取到实际的字符是5 15:35:18.968 [main] c.Demo - 读取到实际的字符是6 15:35:18.968 [main] c.Demo - 读取到实际的字符是7 15:35:18.969 [main] c.Demo - 读取到实际的字符是8 15:35:18.969 [main] c.Demo - 读取到实际的字符是9 15:35:18.969 [main] c.Demo - 读取到实际的字符是0 15:35:18.969 [main] c.Demo - 读取到的字节长度是3 15:35:18.969 [main] c.Demo - 读取到实际的字符是a 15:35:18.969 [main] c.Demo - 读取到实际的字符是b 15:35:18.969 [main] c.Demo - 读取到实际的字符是c
正确使用姿势
初始时buffer是空的,是写入模式,只能往里写。
- 向buffer中写入数据,例如调用channel.read(buffer)
- 调用flip()切换至**【读模式】**
- 从buffer读取数据,例如调用buffer.get()
- 调用clear()或compact切换至**【写模式】**
- 重复步骤1~4直至读取完成
2.2 ByteBuffer内部结构
2.2.1 ByteBuffer重要属性
- capacity - 容量,buffer能装多少数据
- position - 定位 读写指针(索引指针)
- 调用flip(),clear()会将指针重新指向0的位置,等待读取或写入
- 调用compact()会将指针指向压缩后最后一个字节数据的位置。然后再这之后进行写入新数据
- limit - 限制 能读多少字节,能写多少字节
- 写入时的limit大小为容量大小。
- 读取时的limit大小为缓冲区中数据的大小
2.2.2 读写流程
刚开始时,等待写入
-
buffer内容为空,Position指针在0的位置,
-
写模式下,position是写入的位置,limit等于容量,下图表示写入了4个字节后的状态
flip()动作发生后,position切换为读取位置,limit切换为读取限制
读取的四个字节后的状态
clear()动作发生后的状态,注意:这里的abcd并没有被清空,只是position指针重新回到了0的位置,但是数据还在(见代码示例)
-
-
package com.sunyang.netty.study; import lombok.extern.slf4j.Slf4j; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * @Author: sunyang * @Date: 2021/8/16 * @Description: */ @Slf4j(topic = "c.Demo") public class ByteBufferDemo { /* 文件读取 */ public static void main(String[] args) { // FileChannel // 获得FileChannel可以通过输入输出流间接的获得FileChannel。 try (FileChannel channel = new FileInputStream("data.txt").getChannel()) { // 创建一个大小为10 的字节缓冲区,这样就不能一次读取所有的文件,要通过两次读取(为了测试举例),因为有时候你也不知道要读取的文件有多大,不可能直接定义好。 ByteBuffer byteBuffer = ByteBuffer.allocate(10); int len; while ((len = channel.read(byteBuffer)) != -1) { // 从channel中读取数据,写入到bytebuffer中.从此通道读取字节序列到给定缓冲区 log.debug("读取到的字节长度是{}", len); // 打印buffer的内容 byteBuffer.flip(); // 切换至读模式 while (byteBuffer.hasRemaining()) { // 是否还有剩余的未读数据 // 不带参数的get()是一次读取一个字节 byte b = byteBuffer.get(); log.debug("读取到字符是{}", (char) b); } // 切换至写模式 但是不会清空缓冲区,只是指针便回到了0的位置。 byteBuffer.clear(); byte b = byteBuffer.get(); // 而因为读取完成之后这个指针在1的位置,并没有调用 byteBuffer.clear()将指针重置为0, // 所以下一次写入的时候是从指针为1的位置开始写入的,也就是1被保存了下了,在1之后又写入了abc // 所以从channel读取到的字节长度为3,但是从buffer读出来的字节长度为4,包括1,a, b, c,但是之后的数据在从channel读取写入到buffer时被清掉了。 log.debug("读取到实际的字符是{}", (char) b); } } catch (IOException e) { } } }
-
// 输出 ------------第一次循环------------------------ 15:53:35.715 [main] c.Demo - 读取到的字节长度是10 15:53:35.720 [main] c.Demo - 读取到实际的字符是1 15:53:35.720 [main] c.Demo - 读取到实际的字符是2 15:53:35.720 [main] c.Demo - 读取到实际的字符是3 15:53:35.720 [main] c.Demo - 读取到实际的字符是4 15:53:35.720 [main] c.Demo - 读取到实际的字符是5 15:53:35.720 [main] c.Demo - 读取到实际的字符是6 15:53:35.720 [main] c.Demo - 读取到实际的字符是7 15:53:35.720 [main] c.Demo - 读取到实际的字符是8 15:53:35.720 [main] c.Demo - 读取到实际的字符是9 15:53:35.720 [main] c.Demo - 读取到实际的字符是0 15:53:35.720 [main] c.Demo - 读取到实际的字符是1 -------------- 第二次循环------------------------ 15:53:35.721 [main] c.Demo - 读取到的字节长度是3 15:53:35.721 [main] c.Demo - 读取到实际的字符是1 15:53:35.721 [main] c.Demo - 读取到实际的字符是a 15:53:35.721 [main] c.Demo - 读取到实际的字符是b 15:53:35.721 [main] c.Demo - 读取到实际的字符是c 15:53:35.721 [main] c.Demo - 读取到实际的字符是1 // 这是第二次循环时 byteBuffer.clear();后读取到的数据
compact方法,是把未读完的部分向前压缩,然后切换至写模式,但是如果不写入数据再次进行读,也不会读到两个cd,因为有limit的限制。
-
-
package com.sunyang.netty.study; import java.nio.ByteBuffer; import static com.sunyang.netty.study.ByteBufferUtil.debugAll; /** * @Author: sunyang * @Date: 2021/8/16 * @Description: */ public class ByteBufferReadWriteDemo { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put((byte) 97); // a debugAll(buffer); buffer.put(new byte[]{98,99,100});// b c d debugAll(buffer); // System.out.println(buffer.get()); buffer.flip(); System.out.println(buffer.get()); debugAll(buffer); buffer.compact(); debugAll(buffer); } }
-
+--------+-------------------- all ------------------------+----------------+ position: [1], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 61 00 00 00 00 00 00 00 00 00 |a......... | +--------+-------------------------------------------------+----------------+ +--------+-------------------- all ------------------------+----------------+ position: [4], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... | +--------+-------------------------------------------------+----------------+ 97 +--------+-------------------- all ------------------------+----------------+ position: [1], limit: [4] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... | +--------+-------------------------------------------------+----------------+ +--------+-------------------- all ------------------------+----------------+ position: [3], limit: [10] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 62 63 64 64 00 00 00 00 00 00 |bcdd...... | +--------+-------------------------------------------------+----------------+
2.3 工具类
-
package com.sunyang.netty.study; import io.netty.util.internal.StringUtil; import java.nio.ByteBuffer; import static io.netty.util.internal.MathUtil.isOutOfBounds; import static io.netty.util.internal.StringUtil.NEWLINE; /** * @Author: sunyang * @Date: 2021/8/16 * @Description: */ public class ByteBufferUtil { private static final char[] BYTE2CHAR = new char[256]; private static final char[] HEXDUMP_TABLE = new char[256 * 4]; private static final String[] HEXPADDING = new String[16]; private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4]; private static final String[] BYTE2HEX = new String[256]; private static final String[] BYTEPADDING = new String[16]; static { final char[] DIGITS = "0123456789abcdef".toCharArray(); for (int i = 0; i < 256; i++) { HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F]; HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F]; } int i; // Generate the lookup table for hex dump paddings for (i = 0; i < HEXPADDING.length; i++) { int padding = HEXPADDING.length - i; StringBuilder buf = new StringBuilder(padding * 3); for (int j = 0; j < padding; j++) { buf.append(" "); } HEXPADDING[i] = buf.toString(); } // Generate the lookup table for the start-offset header in each row (up to 64KiB). for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) { StringBuilder buf = new StringBuilder(12); buf.append(NEWLINE); buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L)); buf.setCharAt(buf.length() - 9, '|'); buf.append('|'); HEXDUMP_ROWPREFIXES[i] = buf.toString(); } // Generate the lookup table for byte-to-hex-dump conversion for (i = 0; i < BYTE2HEX.length; i++) { BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i); } // Generate the lookup table for byte dump paddings for (i = 0; i < BYTEPADDING.length; i++) { int padding = BYTEPADDING.length - i; StringBuilder buf = new StringBuilder(padding); for (int j = 0; j < padding; j++) { buf.append(' '); } BYTEPADDING[i] = buf.toString(); } // Generate the lookup table for byte-to-char conversion for (i = 0; i < BYTE2CHAR.length; i++) { if (i <= 0x1f || i >= 0x7f) { BYTE2CHAR[i] = '.'; } else { BYTE2CHAR[i] = (char) i; } } } /** * 打印所有内容 * @param buffer */ public static void debugAll(ByteBuffer buffer) { int oldlimit = buffer.limit(); buffer.limit(buffer.capacity()); StringBuilder origin = new StringBuilder(256); appendPrettyHexDump(origin, buffer, 0, buffer.capacity()); System.out.println("+--------+-------------------- all ------------------------+----------------+"); System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit); System.out.println(origin); buffer.limit(oldlimit); } /** * 打印可读取内容 * @param buffer */ public static void debugRead(ByteBuffer buffer) { StringBuilder builder = new StringBuilder(256); appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position()); System.out.println("+--------+-------------------- read -----------------------+----------------+"); System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit()); System.out.println(builder); } private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) { if (isOutOfBounds(offset, length, buf.capacity())) { throw new IndexOutOfBoundsException( "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length + ") <= " + "buf.capacity(" + buf.capacity() + ')'); } if (length == 0) { return; } dump.append( " +-------------------------------------------------+" + NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" + NEWLINE + "+--------+-------------------------------------------------+----------------+"); final int startIndex = offset; final int fullRows = length >>> 4; final int remainder = length & 0xF; // Dump the rows which have 16 bytes. for (int row = 0; row < fullRows; row++) { int rowStartIndex = (row << 4) + startIndex; // Per-row prefix. appendHexDumpRowPrefix(dump, row, rowStartIndex); // Hex dump int rowEndIndex = rowStartIndex + 16; for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2HEX[getUnsignedByte(buf, j)]); } dump.append(" |"); // ASCII dump for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]); } dump.append('|'); } // Dump the last row which has less than 16 bytes. if (remainder != 0) { int rowStartIndex = (fullRows << 4) + startIndex; appendHexDumpRowPrefix(dump, fullRows, rowStartIndex); // Hex dump int rowEndIndex = rowStartIndex + remainder; for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2HEX[getUnsignedByte(buf, j)]); } dump.append(HEXPADDING[remainder]); dump.append(" |"); // Ascii dump for (int j = rowStartIndex; j < rowEndIndex; j++) { dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]); } dump.append(BYTEPADDING[remainder]); dump.append('|'); } dump.append(NEWLINE + "+--------+-------------------------------------------------+----------------+"); } private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) { if (row < HEXDUMP_ROWPREFIXES.length) { dump.append(HEXDUMP_ROWPREFIXES[row]); } else { dump.append(NEWLINE); dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L)); dump.setCharAt(dump.length() - 9, '|'); dump.append('|'); } } public static short getUnsignedByte(ByteBuffer buffer, int index) { return (short) (buffer.get(index) & 0xFF); } }
2.4 ByteBuffer常见方法
分配空间
可以使用allocate() 和 allocateDirect()方法为ByteBuffer分配空间,其他buffer类也有该方法
package com.sunyang.netty.study;
import java.nio.ByteBuffer;
/**
* @program: netty-study
* @description: Dmeo
* @author: SunYang
* @create: 2021-08-16 19:43
**/
public class ByteBufferAllocateDemo {
public static void main(String[] args) {
System.out.println(ByteBuffer.allocate(16).getClass());
System.out.println(ByteBuffer.allocateDirect(16).getClass());
}
}
class java.nio.HeapByteBuffer // 使用的是java的堆内存,堆内字节缓冲区,读写效率低,会受到GC的影响。
class java.nio.DirectByteBuffer // 使用的是直接内存,直接内存字节缓冲区,读写效率高(零拷贝),不会受GC影响,因为是系统直接内存,所以分配内存要调用操作系统函数,所以分配内存的速度较慢,如果使用不当(资源没得到合理释放),会造成内存泄漏,但是Netty对其进行了优化。
分配时大小固定,不能动态改变。(Netty可以)
向Buffer写入数据
有两种办法:
- 调用channel的read方法 channel.read(byteBuffer) 从channel读,往buffer写
- 调用buffer自己的put方法 buffer.put((byte) 97)
从Buffer读取数据
有三种办法:
- 调用channel的write方法 channel.write(byteBuffer); 从Buffer读,往Channel写
- 调用自己的get()方法 byteBuffer.get();byteBuffer.get();
get() 方法会让position读指针向后走,如果向重复读取数据
-
可以调用rewind方法将position重新置为0
-
package com.sunyang.netty.study; import java.nio.ByteBuffer; import static com.sunyang.netty.study.ByteBufferUtil.debugAll; /** * @program: netty-study * @description: * @author: SunYang * @create: 2021-08-16 20:02 **/ public class ByteBufferReadDemo { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put(new byte[]{'a', 'b', 'c', 'd'}); byteBuffer.flip(); byteBuffer.get(new byte[4]); debugAll(byteBuffer); // rewind 从头开始读 byteBuffer.rewind(); System.out.println((char)byteBuffer.get()); } }
-
+--------+-------------------- all ------------------------+----------------+ position: [4], limit: [4] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... | +--------+-------------------------------------------------+----------------+ a
-
-
或者调用get(int i) 方法获取索引i 的内容,他不会移动读指针。
-
package com.sunyang.netty.study; import java.nio.ByteBuffer; import static com.sunyang.netty.study.ByteBufferUtil.debugAll; /** * @program: netty-study * @description: * @author: SunYang * @create: 2021-08-16 20:02 **/ public class ByteBufferReadDemo { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put(new byte[]{'a', 'b', 'c', 'd'}); byteBuffer.flip(); // get(i) 不会改变读索引的位置 System.out.println((char) byteBuffer.get(3)); debugAll(byteBuffer); } }
-
d +--------+-------------------- all ------------------------+----------------+ position: [0], limit: [4] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... | +--------+-------------------------------------------------+----------------+
-
-
mark & reset
-
mark做一个标记,记录position位置,reset是将position重置到mark的位置
-
package com.sunyang.netty.study; import java.nio.ByteBuffer; import static com.sunyang.netty.study.ByteBufferUtil.debugAll; /** * @program: netty-study * @description: * @author: SunYang * @create: 2021-08-16 20:02 **/ public class ByteBufferReadDemo { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put(new byte[]{'a', 'b', 'c', 'd'}); byteBuffer.flip(); System.out.println((char) byteBuffer.get()); // 读取 a System.out.println((char) byteBuffer.get()); // 读取 b byteBuffer.mark(); // 加标记 索引为2 的位置 System.out.println((char) byteBuffer.get()); // 读取 c System.out.println((char) byteBuffer.get()); // 读取 d byteBuffer.reset(); // 将position 重置到索引为2的位置 System.out.println((char) byteBuffer.get()); // 读取 c System.out.println((char) byteBuffer.get()); // 读取 d } }
-
a b c d c d
-
字符串与ByteBuffer互转
package com.sunyang.netty.study;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static com.sunyang.netty.study.ByteBufferUtil.debugAll;
/**
* @program: netty-study
* @description: Demo
* @author: SunYang
* @create: 2021-08-16 20:30
**/
public class StringAndByteBuffer {
public static void main(String[] args) {
// 1. 字符串转为 ByteBuffer
ByteBuffer byteBuffer1 = ByteBuffer.allocate(16);
// put 方式在写入之后,还是写模式 position不为0
byteBuffer1.put("hello".getBytes());
// byteBuffer1.put("hello".getBytes(StandardCharsets.UTF_8));
debugAll(byteBuffer1);
// 2. Charset 在写入之后 自动转为读模式 position 为 0
ByteBuffer byteBuffer2 = StandardCharsets.UTF_8.encode("hello");
debugAll(byteBuffer2);
// 3. wrap // 2. Charset 在写入之后 自动转为读模式 position 为 0
ByteBuffer byteBuffer3 = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
debugAll(byteBuffer3);
// ByteBuffer转为String
CharBuffer decode = StandardCharsets.UTF_8.decode(byteBuffer2); // 返回的是一个CharBuffer
System.out.println(decode.toString());
// 读取byteBuffer1 需要转为读模式
byteBuffer1.flip();
CharBuffer decode1 = StandardCharsets.UTF_8.decode(byteBuffer1); // 返回的是一个CharBuffer
System.out.println(decode.toString());
}
}
+--------+-------------------- all ------------------------+----------------+
position: [5], limit: [16]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 |hello...........|
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [5]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f |hello |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [5]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f |hello |
+--------+-------------------------------------------------+----------------+
hello
hello
2.5 Scattering Reads
分散读取集中写的方法不重要,重要的是思想,可以减少在ByteBuffer之间的拷贝,减少数据的复制次数,提高效率。
分散读取
package com.sunyang.netty.study;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import static com.sunyang.netty.study.ByteBufferUtil.debugAll;
/**
* @program: netty-study
* @description: 分散读
* @author: SunYang
* @create: 2021-08-16 20:59
**/
public class ScatteringReadsDemo {
public static void main(String[] args) {
try (FileChannel channel = new RandomAccessFile("words.txt", "r").getChannel()) {
ByteBuffer b1 = ByteBuffer.allocate(3); // one
ByteBuffer b2 = ByteBuffer.allocate(3); // two
ByteBuffer b3 = ByteBuffer.allocate(5); // three
channel.read(new ByteBuffer[]{b1, b2, b3});
b1.flip();
b2.flip();
b3.flip();
debugAll(b1);
debugAll(b2);
debugAll(b3);
} catch (IOException e) {
};
}
}
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [3]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 6f 6e 65 |one |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [3]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 74 77 6f |two |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [5]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 74 68 72 65 65 |three |
+--------+-------------------------------------------------+----------------+
2.6 Gathering Write
集中写入
package com.sunyang.netty.study;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import static com.sunyang.netty.study.ByteBufferUtil.debugAll;
/**
* @program: netty-study
* @description: 集中写
* @author: SunYang
* @create: 2021-08-16 21:06
**/
public class GatheringWritesDemo {
public static void main(String[] args) {
ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
ByteBuffer b2 = StandardCharsets.UTF_8.encode("word");
ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
try (FileChannel channel = new RandomAccessFile("words2.txt", "rw").getChannel()) {
channel.write(new ByteBuffer[]{b1, b2, b3});
} catch (IOException e) {
};
}
}
2.7 粘包半包
待补充
package com.sunyang.netty.study;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import static com.sunyang.netty.study.ByteBufferUtil.debugAll;
/**
* @program: netty-study
* @description: 粘包半包问题
* @author: SunYang
* @create: 2021-08-16 21:47
**/
public class ByteBufferExam {
public static void main(String[] args) {
/**
* 网络上有多条数据发送给服务器端,为了进行区分在数据之间加了\n 进行区分
* 但由于某种原因(ByteBuffer大小等等。)这些数据在接收时,别进行了重新组合,例如原始数据有三条为
* Hello,word\n
* I`m zhangsan\n
* How are you?\n
* 变成了下面的两个ByteBuffer(粘包,半包)
* Hello,word\nI`m zhangsan\nHo
* w are you?\n
* 现在编写程序,将错乱的数据恢复成原始的按\n 分隔数据
* **/
ByteBuffer source = ByteBuffer.allocate(32);
source.put("Hello,word\nI`m zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\n".getBytes());
split(source);
}
private static void split(ByteBuffer source) {
// 切换成读模式
source.flip();
for (int i = 0; i < source.limit(); i++) {
// 找到一条完整消息 以后会有更高效的方法,这里要一个字节一个字节去遍历一条消息的结束。浪费时间和资源
if (source.get(i) == '\n') {
int length = i + 1 - source.position();
// 把这条完整消息存入新的ByteBuffer
ByteBuffer target = ByteBuffer.allocate(length);
// 从source读,向target写
for (int j = 0; j < length; j++) {
target.put(source.get());
}
debugAll(target);
}
}
// 切换成写模式 但是不能用clear 因为clear会从头写,那么未读取完的部分就会被丢弃,所以得用compacct()
source.compact();
}
}
+--------+-------------------- all ------------------------+----------------+
position: [11], limit: [11]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 2c 77 6f 72 64 0a |Hello,word. |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [13], limit: [13]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 49 60 6d 20 7a 68 61 6e 67 73 61 6e 0a |I`m zhangsan. |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [13], limit: [13]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 6f 77 20 61 72 65 20 79 6f 75 3f 0a |How are you?. |
+--------+-------------------------------------------------+----------------+
3.文件编程
3.1 FileChannel
⚠ 注意
FileChannel只能工作在非阻塞模式下,只有和网络相关的Channel才能和selector()配合使用,工作在非阻塞模式下。
获取
不能直接打开FileChannel,必须通过FileInputStream,FileOutputStream或者RandomAccessFile来获取FileChannel,他们都有getChannel方法
- 通过FileInputStream获取的channel只能读
- 通过FileOutPutStream获取的channel只能写
- 通过RandomAccessFile是否能读写根据构造RandomAccessFile时的读写模式决定。读写为(rw)读为(r)
虽然FileChannel是双向通道,但是他获取的源头决定了他是读取通道,还是写入通道,
读取
会从channel读取数据填充ByteBuffer,返回值表示读到了多少字节,-1表示到达了文件的末尾
int readBytes = channel.read(buffer);
写入
写入的正确姿势:
- FileChannel对写入是没有限制的,可以无限写入数据,也就是可以一次将Buffer中的数据全部写入到Channel中,但是SocketChannel传输数据的能力是有限的,并不是说Buffer中有多少数据,一次性就能全部写入到Channel中去,所以正确的写入模式应该是先去检查Buffer中还有没有数据,如果还有就要再次调用write去写,所以尽量这样去写。
ByteBuffer buffer = ......;
buffer.put(); // 写入数据
buffer.flip() // 切换读模式
while(buffer.hasRemaining()){
channel.write(buffer);
}
关闭
channel必须关闭,要么用try,要么调用FileInputStream,FileOutputStream或者RandomAccessFile的close()方法会间接的调用channel的close方法,也可以直接调用channel的close()方法,两个close调用一个就可以
位置
获取当前位置 和ByteBuffer中的position一样
long pos = channel.position(); // 读写指针
设置当前位置
long newpos = ....;
channel.position(newPos);// 指定新的位置
设置当前位置时,如果设置为文件的末尾
- 这时读取会返回-1
- 这时写入,会追加内容,但要注意如果position超过了文件末尾,再写入时在新内容和原末尾之间为会有空洞(00)
大小
使用size方法获取文件的大小
long size = channel.size();
强制写入
这个缓存是指在内存中与外存映射的一些内存块,也叫页缓存,目的是减少真正的块IO
操作系统出于性能的考虑,并不是说每次调用write方法就一定将数据写入到磁盘中去,而是将数据写入到操作系统的缓存中去,只有当你关闭了channel,才会将缓存中的数据同步到磁盘中去,但是可以主动调用force(true)方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘,但是会对性能有所影响。
3.2 两个Channel传输数据
transferTo()
不需要缓冲区,效率高,0拷贝。只要JDK中带transferTo的底层都会使用操作系统的0拷贝进行优化。
package com.sunyang.netty.study;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description:
*/
public class FileChannelTransferTo {
public static void main(String[] args) {
try (FileChannel from = new FileInputStream("data.txt").getChannel();
FileChannel to = new FileOutputStream("to.txt").getChannel();
) {
from.transferTo(0, from.size(), to);
} catch (IOException e) {
e.printStackTrace();
};
}
}
因为transferTo()一次最多传输2G的数据,所以如果数据量特别大,就需要分段传输
package com.sunyang.netty.study;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description:
*/
public class FileChannelTransferTo {
public static void main(String[] args) {
try (FileChannel from = new FileInputStream("data.txt").getChannel();
FileChannel to = new FileOutputStream("to.txt").getChannel();
) {
// 创数数据上限,最多2G,那么如果数据过大,就需要分段传输。
for (long left = from.size(); left > 0;) {
left -= from.transferTo(from.size() - left, left, to);
}
} catch (IOException e) {
e.printStackTrace();
};
}
}
3.3 Path
JDK7引入了Path和Paths类
- Path用来表示文件路径
- Paths是工具类,用来获取Path实例
package com.sunyang.netty.study;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description:
*/
public class FileChannelTransferTo {
public static void main(String[] args) {
Path source = Paths.get("1.txt"); // 相对路径 使用user.dir环境变量来定位1.txt
System.out.println(source.normalize());
Path source1 = Paths.get("d:\\1.txt"); // 绝对路径 代表了 d:\1.txt 用\ 需要转义
System.out.println(source1.normalize());
Path source3 = Paths.get("d:/1.txt"); // 绝对路径 同样代表了 d:\1.txt
System.out.println(source3.normalize());
Path projects = Paths.get("d:\\data", "projects"); // 代表了d"\data\projects
System.out.println(projects.normalize());
}
}
1.txt
d:\1.txt
d:\1.txt
d:\data\projects
- 【.】代表了当前路径
- 【…】代表了上一级路径
例如目录结构如下:
d:
|- data
|- projects
|- a
|- b
代码示例
package com.sunyang.netty.study;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description:
*/
public class FileChannelTransferTo {
public static void main(String[] args) {
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径
}
}
d:\data\projects\a\..\b
d:\data\projects\b
3.4 Files
检查文件是否存在
Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));
创建一级目录
Path path = Paths.get("helloword/d1");
Files.createDirectory(path);
- 如果目录已存在,会抛异常 FileAlreadyExistsException
- 不能一次创建多级目录,否则会抛异常 NoSuchFileException
创建多级目录用
Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);
拷贝文件
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);
- 如果文件已存在,会抛异常 FileAlreadyExistsException
如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
移动文件
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
- StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
删除文件
Path target = Paths.get("helloword/target.txt");
Files.delete(target);
- 如果文件不存在,会抛异常 NoSuchFileException
删除目录
Path target = Paths.get("helloword/d1");
Files.delete(target);
- 如果目录还有内容,会抛异常 DirectoryNotEmptyException
3.4.1 WalkFileTree()
遍历目录文件
package com.sunyang.netty.study;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.LongAdder;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description: 遍历文件夹树
*/
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
LongAdder fileCount = new LongAdder();
LongAdder dirCount = new LongAdder();
Files.walkFileTree(Paths.get("E:\\Java\\jdk1.8.0_261"), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("=======>" + dir);
dirCount.increment();
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file);
fileCount.increment();
return super.visitFile(file, attrs);
}
});
System.out.println("dir count: " + dirCount);
System.out.println("file count: " + fileCount);
}
}
=======>E:\Java\jdk1.8.0_261
=======>E:\Java\jdk1.8.0_261\bin
E:\Java\jdk1.8.0_261\bin\appletviewer.exe
E:\Java\jdk1.8.0_261\bin\extcheck.exe
E:\Java\jdk1.8.0_261\bin\idlj.exe
=======>E:\Java\jdk1.8.0_261\lib\visualvm\visualvm\modules\locale
E:\Java\jdk1.8.0_261\lib\visualvm\visualvm\modules\locale\com-sun-tools-visualvm-api-caching_ja.jar
.......
=======>E:\Java\jdk1.8.0_261\lib\visualvm\visualvm\update_tracking
E:\Java\jdk1.8.0_261\lib\visualvm\visualvm\update_tracking\com-sun-tools-visualvm-api-caching.xml
.......
dir count: 68
file count: 935
Disconnected from the target VM, address: '127.0.0.1:60354', transport: 'socket'
Process finished with exit code 0
查找某个类型的文件
package com.sunyang.netty.study;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description: 遍历文件夹树
*/
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
// fileTree();
AtomicInteger jarCount = new AtomicInteger();
Files.walkFileTree(Paths.get("E:\\Java\\jdk1.8.0_261"), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".jar")) {
System.out.println(file);
jarCount.incrementAndGet();
}
return super.visitFile(file, attrs);
}
});
System.out.println("jar count: " + jarCount);
}
}
E:\Java\jdk1.8.0_261\lib\visualvm\visualvm\modules\locale\com-sun-tools-visualvm-uisupport_ja.jar
.......
E:\Java\jdk1.8.0_261\lib\visualvm\visualvm\modules\locale\org-netbeans-modules-profiler_visualvm.jar
jar count: 337
删除多级目录
如果文件夹不为空,直接删除是不可以的
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
Files.delete(Paths.get("E:\\lianxi"));
}
Exception in thread "main" java.nio.file.DirectoryNotEmptyException: E:\jjj
at sun.nio.fs.WindowsFileSystemProvider.implDelete(WindowsFileSystemProvider.java:266)
at sun.nio.fs.AbstractFileSystemProvider.delete(AbstractFileSystemProvider.java:103)
at java.nio.file.Files.delete(Files.java:1126)
at com.sunyang.netty.study.FilesWalkFileTree.main(FilesWalkFileTree.java:22)
层级处理,从里到外
package com.sunyang.netty.study;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description: 遍历文件夹树
*/
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
// fileTree();
// findFile();
// 不走回收站,删除之后在回收站看不到。
Files.walkFileTree(Paths.get("E:\\lianxi"), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// 可以不用重写此方法,只是为了便于理解
System.out.println("=====> 进入" + dir);
return super.preVisitDirectory(dir, attrs);
}
@Override
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);
System.out.println("开始删除文件夹--->" + dir);
Files.delete(dir);
return super.postVisitDirectory(dir, exc);
}
});
}
}
=====> 进入E:\lianxi
=====> 进入E:\lianxi\lain
删除文件---> E:\lianxi\lain\weee.txt
<======== 退出E:\lianxi\lain
开始删除文件夹--->E:\lianxi\lain
删除文件---> E:\lianxi\wenjian.txt
<======== 退出E:\lianxi
开始删除文件夹--->E:\lianxi
拷贝多级目录
方法一:
package com.sunyang.netty.study;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description: 遍历文件夹树
*/
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
// fileTree();
// findFile();
// deleteFile();
String source = "E:\\Git";
String target = "E:\\GitTest";
Files.walkFileTree(Paths.get("E:\\Git"), new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
String targetName = dir.toString().replace(source, target);
Files.createDirectory(Paths.get(targetName));
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String targetName = file.toString().replace(source, target);
Files.copy(file, Paths.get(targetName));
return super.visitFile(file, attrs);
}
});
}
}
方法二:
package com.sunyang.netty.study;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description: 遍历文件夹树
*/
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
// fileTree();
// findFile();
// deleteFile();
// createFile();
String source = "E:\\Git";
String target = "E:\\GitTest";
Files.walk(Paths.get(source)).forEach(path -> {
String targetName = path.toString().replace(source, target);
try {
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();
}
});
}
}
ile(Path file, BasicFileAttributes attrs) throws IOException {
String targetName = file.toString().replace(source, target);
Files.copy(file, Paths.get(targetName));
return super.visitFile(file, attrs);
}
});
}
}
方法二:
```java
package com.sunyang.netty.study;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
/**
* @Author: sunyang
* @Date: 2021/8/17
* @Description: 遍历文件夹树
*/
public class FilesWalkFileTree {
public static void main(String[] args) throws IOException {
// fileTree();
// findFile();
// deleteFile();
// createFile();
String source = "E:\\Git";
String target = "E:\\GitTest";
Files.walk(Paths.get(source)).forEach(path -> {
String targetName = path.toString().replace(source, target);
try {
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();
}
});
}
}