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。或者RandomAccessFiletry (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是空的,是写入模式,只能往里写。

  1. 向buffer中写入数据,例如调用channel.read(buffer)
  2. 调用flip()切换至**【读模式】**
  3. 从buffer读取数据,例如调用buffer.get()
  4. 调用clear()或compact切换至**【写模式】**
  5. 重复步骤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); // adebugAll(buffer);buffer.put(new byte[]{98,99,100});// b c ddebugAll(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 paddingsfor (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 conversionfor (i = 0; i < BYTE2HEX.length; i++) {BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);}// Generate the lookup table for byte dump paddingsfor (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 conversionfor (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 dumpint rowEndIndex = rowStartIndex + 16;for (int j = rowStartIndex; j < rowEndIndex; j++) {dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);}dump.append(" |");// ASCII dumpfor (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 dumpint rowEndIndex = rowStartIndex + remainder;for (int j = rowStartIndex; j < rowEndIndex; j++) {dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);}dump.append(HEXPADDING[remainder]);dump.append(" |");// Ascii dumpfor (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()); // 读取 aSystem.out.println((char) byteBuffer.get()); // 读取 bbyteBuffer.mark(); // 加标记  索引为2 的位置System.out.println((char) byteBuffer.get()); // 读取 cSystem.out.println((char) byteBuffer.get()); // 读取  dbyteBuffer.reset(); // 将position 重置到索引为2的位置System.out.println((char) byteBuffer.get()); // 读取 cSystem.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. 字符串转为 ByteBufferByteBuffer byteBuffer1 = ByteBuffer.allocate(16);// put 方式在写入之后,还是写模式  position不为0byteBuffer1.put("hello".getBytes());
//        byteBuffer1.put("hello".getBytes(StandardCharsets.UTF_8));debugAll(byteBuffer1);// 2. Charset  在写入之后 自动转为读模式  position 为 0ByteBuffer byteBuffer2 = StandardCharsets.UTF_8.encode("hello");debugAll(byteBuffer2);// 3. wrap         // 2. Charset  在写入之后 自动转为读模式  position 为 0ByteBuffer byteBuffer3 = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));debugAll(byteBuffer3);// ByteBuffer转为StringCharBuffer decode = StandardCharsets.UTF_8.decode(byteBuffer2); // 返回的是一个CharBufferSystem.out.println(decode.toString());//  读取byteBuffer1 需要转为读模式byteBuffer1.flip();CharBuffer decode1 = StandardCharsets.UTF_8.decode(byteBuffer1); // 返回的是一个CharBufferSystem.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); //  oneByteBuffer b2 = ByteBuffer.allocate(3); //  twoByteBuffer b3 = ByteBuffer.allocate(5); //  threechannel.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();// 把这条完整消息存入新的ByteBufferByteBuffer 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.txtSystem.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.txtSystem.out.println(source3.normalize());Path projects = Paths.get("d:\\data", "projects"); //  代表了d"\data\projectsSystem.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>(){@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {System.out.println("=======>" + dir);dirCount.increment();return super.preVisitDirectory(dir, attrs);}@Overridepublic 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>(){@Overridepublic 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:\jjjat 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>(){@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {// 可以不用重写此方法,只是为了便于理解System.out.println("=====> 进入" + dir);return super.preVisitDirectory(dir, attrs);}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {// 先删除文件夹中的文件,然后再删除外层文件夹System.out.println("删除文件---> " + file);Files.delete(file);return super.visitFile(file, attrs);}@Overridepublic 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>(){@Overridepublic 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);}@Overridepublic 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();}});}
}

Netty学习笔记一NIO基础相关推荐

  1. Netty学习笔记(1) NIO基础-3

    文章目录 1. 前言 2. 网络编程(多线程) 1. 多线程优化(单个worker) 2. 解决多线程的问题 1. Run内部执行 2. 更简便的方法 3. 多线程优化(多个worker) 4. CP ...

  2. Netty学习笔记:二、NIO网络应用实例-群聊系统

    实例要求: 编写一个NIO群聊系统,实现服务器端和多个客户端之间的数据简单通讯(非阻塞): 实现多人群聊: 服务器端:可以监测用户上线.离线,并实现消息转发功能: 客户端:通过channel可以无阻塞 ...

  3. Netty学习笔记(二) 实现服务端和客户端

    在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...

  4. Netty学习笔记(六)Pipeline的传播机制

    前面简单提到了下Pipeline的传播机制,这里再详细分析下 Pipeline的传播机制中有两个非常重要的属性inbound和outbound(AbstractChannelHandlerContex ...

  5. Netty学习笔记(二)Netty服务端流程启动分析

    先贴下在NIO和Netty里启动服务端的代码 public class NioServer { /*** 指定端口号启动服务* */public boolean startServer(int por ...

  6. Netty学习笔记二网络编程

    Netty学习笔记二 二. 网络编程 1. 阻塞模式 阻塞主要表现为: 连接时阻塞 读取数据时阻塞 缺点: 阻塞单线程在没有连接时会阻塞等待连接的到达,连接到了以后,要进行读取数据,如果没有数据,还要 ...

  7. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

  8. JavaScript学习笔记02【基础——对象(Function、Array、Date、Math)】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  9. JavaScript学习笔记01【基础——简介、基础语法、运算符、特殊语法、流程控制语句】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

最新文章

  1. Bzoj2037: [Sdoi2008]Sue的小球
  2. jmeter(三)参数化
  3. 目前市场上用于个人计算机的硬盘尺寸是,第5章-硬盘(计算机组装与维护).docx
  4. OpenCV--CvMemStorage
  5. 【原创】MySQL Proxy - 协议(部分摘录)
  6. Win7下如何破解Visual Studio2008 90天试用版
  7. 一只潜力十足的专业电竞游戏鼠标——HyperX巨浪RGB电竞鼠标
  8. JAVA类似ABP框架_【Net】ABP框架学习之它并不那么好用
  9. Codeigniter 升级
  10. html 单元格拆分及合并,一键轻松搞定合并和拆分单元格-excel拆分单元格
  11. 新猿木子李:0基础学python培训教程 Python操作Excel之读取数据
  12. 无论夫妻还是情人,能陪你一生的男人,都有这个特征
  13. 进制转换--(2-8)为什么2的3次方=8,所以三位变一位
  14. 硬盘 主分区 和 逻辑分区 区别
  15. Maven:你还在手动导包吗?带你了解Maven的前世今生(尚硅谷详细笔记)
  16. c35是什么意思_混凝土标号怎么来的?C30_C25_C35_都是什么意思
  17. 一文带你清晰弄明白线程池的原理
  18. 模块九:mouse、key、joystick模块
  19. 什么是RUN CARD?
  20. pygame.surface.blit()方法4个参数的使用方法

热门文章

  1. 新版springcloud使用gateway+nacos,服务报错503 Service Unavailable
  2. Struts2框架概述、Struts简介、Struts环境搭建、Struts执行流程、Struts文档、Struts配置文件的加载顺序-day01
  3. 本科生出国留学? 看这里!
  4. vagrant + vitrulbox + centos8 部署单机 k8s
  5. JS日常开发小技巧(持续更新)
  6. 招聘 | 小红书-内容安全算法团队专家-内推
  7. STC15F2K60S2----1
  8. 成都拓嘉启远电商:拼多多库存数量能随便改吗
  9. Python基于easyocr和fitz实现的pdf转文字
  10. 程序员面试防坑宝典,助你秋招一臂之力(建议收藏,文末有彩蛋)