Netty学习笔记一NIO基础

8 篇文章 0 订阅
8 篇文章 0 订阅

Netty学习笔记一

一. NIO 基础

non-blocking io 非阻塞IO (也可称为new IO, 因为是JDK1.4加入的)

1. 三大组件

1.1 Channel

通道:数据的传输通道。我们之前使用的Stream也是数据的传输通道,只不过他们是单向的数据传输通道(输出(流)通道,输入(流)通道),但是Channel是读写数据的**【双向通道】**,可以将数据读入buffer,也可以将buffer的数据写入channel,而之前的stream要么是输入,要么是输出,channel比stream更为底层。

image-20210816102541401

常见的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进行服务。

多线程版缺点:

image-20210816111515972

  • 内存占用高(windows下默认一个线程,大概占用1M内存,一千个线程就是一个G,多了内存溢出)
    • 餐馆,一个客人配备一个服务员专属服务,但是餐馆的地方有限,一千个客人一千个服务员,有可能屋子就放不下了(内存溢出)并且假设餐馆只有16个张桌子和座位(16核CPU)那同时能并行工作的服务员(线程)就只有16个,剩下的要进行轮班(就是线程的上下文切换)
  • 线程上下文切换成本高,因为进行上下文切换要保护现场和恢复现场,需要时间,这要如果线程数过多,那么可能导致,线程真正用于处理的执行时间很短很短,都用在了上下文且换上
    • 就假设这个餐馆为了让照顾每个顾客都能被提供服务(吃饭)就提出了一种策略,
    • 每个客人可以轮流吃饭,吃两口换下一个客人(线程的上下文切换,这个就是并发操作),
    • 【保护现场】:而在一个客人和下一个客人进行轮换时,你就要整理餐桌,将这个客人桌上的菜品和餐具保存好,并且将这个客人的菜品点了什么菜(对应于CPU指令执),还有什么菜没上(CPU指令执行到了哪里,)
    • **【恢复现场】**等一下在轮到这个客人的时候如果菜做好了(执行指令需要的数据准备好了),你得知道上哪道菜(执行哪个CPU指令),还得把刚刚保存的 餐桌状态给恢复(恢复现场)。
    • 但是这样轮班吃饭(并发执行)就有一个问题,人少的时候还行,可能一个客人一次轮换能吃一个小时,但是如果客人多了,你要保护现场和恢复现场,那么一次轮换可能就只吃一分钟,客人体验感就会很差(性能就会很差)。
  • 所以只适合连接数比较少的场景。高连接数就会瘫痪。

线程上下文切换,可能导致线程根本没执行,时间片就用完了

线程池版

image-20210816132413834

  • 也就是这个餐馆固定只有16个服务员(线程池中有16个线程根据CPU核心数自己确定最佳的CPU利用率),这样就可以减少创建线程及线程上下文切换造成的资源浪费问题。

  • 但是规定一个服务员必须将这桌客人送走,才能服务下一桌的客人,因为一桌客人可能要点菜,上菜,吃菜,最后结账走人。所以即使这桌客人在等待上菜的时候,那么这个服务员即使什么都不干也不能去服务其他客人(socket),

  • 那么其他客人就得等待(陷入阻塞)直到这桌客人就餐完毕(业务执行完毕)。

  • 也就是说,如果来了1000个客人,那么我同一时间有16个服务员可以为客人提供服务,那么同一时间只能有16个客人可以吃饭。其他客人(socket)阻塞(等待).

  • 这样就不用创建那么多线程,浪费内存资源,又因为没有那么多线程在并发执行任务,所以减少了上下文切换。

  • 但是这样的方案只适合每个客人的吃饭时间很短的场景,如果一个客人一顿饭吃了4个小时。那其他客人得等待 多久能吃上饭。所以只适合短连接

线程池版缺点

  • 阻塞模式下,线程只能处理一个socket连接
  • 仅适合短连接场景,
  • 服务器早期都设计成短连接,早期的tomcat服务器,阻塞式的IO,适合于Http请求:连接上-发请求-返回结果就断开连接。
  • 线程的利用率不高。
selector版设计

image-20210816132354320

selector的作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件(可连接,可读,可写事件…),这些channel工作在**【非阻塞模式】**下,不会让线程吊死在一个channel上。适合连接数特别多,但流量低(读写数据量少)的场景,因为这样占用这个线程的时间就短。

调用selector的select()会阻塞直到channel发生了读写就绪事件,这些事件发生,select()方法就会返回这些事件交给thread处理。

  • 这次只需要一个服务员(线程)来服务所有的客人(channel也就是socket)。
  • 只不过是中间加了一个机器人(selector选择器)他能监听所有客人的需求,客人的一举一动都在他的监视下,一旦客人有什么请求她都能知道,然后派出服务员去提供的服务。
  • 例如:三个客人(channel)都进来了,都在看菜单,突然有一个客人要点菜(发送数据)那么就告诉机器人我要点菜(有一些数据要发给服务器,服务器给我处理)
  • 然后机器人就认为发生了一个事件(selector就认为发生了一个事件,会将事件处理权交给线程,)
  • 让服务员去处理请求(让线程去处理socket的请求)
  • 但是在处理请求的过程中,来了其他线程的请求,这时候依然是阻塞的,依然需要排队等待上一个socket处理完成。
  • image-20210816134423162

线程利用率得到了提高,在一个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是空的,是写入模式,只能往里写。

  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的位置,

  • image-20210816161439094

写模式下,position是写入的位置,limit等于容量,下图表示写入了4个字节后的状态

  • image-20210816161704175

flip()动作发生后,position切换为读取位置,limit切换为读取限制

  • image-20210816161817559

读取的四个字节后的状态

  • image-20210816161848055

clear()动作发生后的状态,注意:这里的abcd并没有被清空,只是position指针重新回到了0的位置,但是数据还在(见代码示例)

  • image-20210816163013842

  • 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的限制。

  • image-20210816171728843

  • 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();
            }

        });
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值