前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty入门之基础篇二

Netty入门之基础篇二

作者头像
@派大星
发布2023-06-28 14:24:57
1060
发布2023-06-28 14:24:57
举报
文章被收录于专栏:码上遇见你

Buffer的线程安全问题?

Buffer是非线程安全的

Scattering Reads(大致了解即可)

分散读取。简单来讲是就是将文件进行拆分,写入不同的Buffer中。

  • 代码实例
代码语言:javascript
复制
public class TestScatteringReads {
    public static void main(String[] args) {
        try {
            FileChannel channel = new RandomAccessFile("word.txt", "r").getChannel();
            ByteBuffer b1 = ByteBuffer.allocate(3);
            ByteBuffer b2 = ByteBuffer.allocate(3);
            ByteBuffer b3 = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{b1,b2,b3});
            b1.flip();
            b2.flip();
            b3.flip();
            debugAll(b1);
            debugAll(b2);
            debugAll(b3);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Gathering Writes

集中写入,简单理解就是将多个buffer的数据组合到一起填充到channel中。减少数据的拷贝。

  • 代码实例
代码语言:javascript
复制
public class TestGatheringWrites {
    public static void main(String[] args) {
        ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
        ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
        ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
        try (FileChannel channel = new RandomAccessFile("words2.txt","rw").getChannel()) {
            channel.write(new ByteBuffer[]{b1,b2,b3});
        } catch (IOException e) {
        }
    }
}

接下来给大家简单讲解一下什么是黏包、半包现象 假设网络上又多条数据要发送给服务端,数据之间使用了\n进行换行,但是由于某种原因导致服务器接收的时候,数据进行了重新组合(这里的组合只是截断的点不一样,而不是字符串的顺序进行了重新排列),假设原始数据有3条:

  • Hello,world\n
  • I'm paidaxing\n
  • How are you?\n 结果到了服务器端变成了2个ByteBuffer
  • Hello,world\nI'm paidaxing\nHo
  • w are you?\n

现在你需要将数据进行恢复

  • 代码演示
代码语言:javascript
复制
public static void main(String[] args) {
        ByteBuffer source = ByteBuffer.allocate(32);
        source.put("Hello,world\nI'm paidaxing\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') {
                // 存入新的bytebuffer
                int lenth = i + 1 - source.position(); //消息长度
                ByteBuffer target = ByteBuffer.allocate(lenth);
                // 从soure读  向target写
                for (int j = 0; j < lenth; j++) {
                    byte b = source.get();
                    target.put(b);

                }
                debugAll(target);
            }
        }
        /*这里不能使用clear  compact*会把未写入的向前压缩*/
        source.compact();
    }

以上关于ByteBuffer的相关知识点就这些,接下来给大家讲解FileChannel,

FileChannel
FileChannel的工作模式

FileChannel只能工作在阻塞模式下 不能和selector一起使用

FileChannel的获取方式:

💡 注意:不能直接获取FileChannel,必须要通过FileInputStreamFileOutputStreamRandomAccessFile来获取FileChannel,它们都有getChannel()方法; ⚠️

  • FileInputStream获取的Channel只能读
  • FileOutputStream获取的Channel只能写
  • 通过RandomAccessFile获取的channel是否可以读写根据RandomAccessFile时的读写模式决定
读取

会从channel读取数据填充之ByteBuffer,返回值代表读到了多少个字节,-1表示到达了文件的末尾

demo

代码语言:javascript
复制
  int readBytes = channel.read(buffer);
写入

这里需要注意一下,当是FileChannel时,channel是可以不遵守写入的正确的写入方式,但是要是SocketChannel时一定要遵守下面的写入方式,因为在while中调用channel.write()的write方法并不能保证一次江buffer中的内容全部写入到channel中,因为channel的读写能力是有限制的。

demo

代码语言:javascript
复制
  ByteBuffer buffer = ...;
  buffer.put(...); // 存入数据
  buffer.flip();   // 切换读模式

  while(buffer.hasRemaining()) {
      channel.write(buffer);
  }
关闭

channel必须关闭,不过调用了FileInputStream、FileOutputStream和RandomAccessFile的close方法会间接调用channel的close方法

Channel的位置

获取当前位置

代码语言:javascript
复制
  long pos = channel.position();

设置当前位置

代码语言:javascript
复制
  long newPos = ...;
  channel.position(newPos);

💡 设置当前位置时,如果设置为文件的末尾

  • 这时读取会返回 -1
  • 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
Channel的大小(针对FileChannel)

使用 size 方法获取文件的大小

强制写入(对性能有所影响)

操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘。

测试案例:传输数据
  • 使用transferTo的方法
    • 优点:效率高,比起自己写文件输入输出流要高 底层会利用操作系统的零拷贝进行优化
    • 该方法的传输内容有大小限制 2G;可以多次传输
代码语言:javascript
复制
public static void main(String[] args) {
        try (
                FileChannel channelFrom = new FileInputStream("data.txt").getChannel();
                FileChannel channelTo = new FileOutputStream("to.txt").getChannel()
        ) {
            long size = channelFrom.size();
            // left 变量代表还剩多少个字节
            for(long left = size; left>0;){
                left -= channelFrom.transferTo((size-left),channelFrom.size(),channelTo);// 返回实际传输的字节数
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
Path

jdk7 引入了 Path 和 Paths 类

  • Path 用来表示文件路径
  • Paths 是工具类,用来获取 Path 实例
代码语言:javascript
复制
Path source = Paths.get("1.txt"); // 相对路径 使用 user.dir 环境变量来定位 1.txt

Path source = Paths.get("d:\\1.txt"); // 绝对路径 代表了  d:\1.txt

Path source = Paths.get("d:/1.txt"); // 绝对路径 同样代表了  d:\1.txt

Path projects = Paths.get("d:\\data", "projects"); // 代表了  d:\data\projects
  • . 代表了当前路径
  • .. 代表了上一级路径

例如目录结构如下

代码语言:javascript
复制
d:
	|- data
		|- projects
			|- a
			|- b

代码

代码语言:javascript
复制
Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径

以上代码会输出

代码语言:javascript
复制
d:\data\projects\a\..\b
d:\data\projects\b //正常化路径
Files

相关API

  • 检查文件是否存在
代码语言:javascript
复制
  Path path = Paths.get("helloword/data.txt");
  System.out.println(Files.exists(path));
  • 创建一级目录

注意事项:

  1. 如果目录已存在,会抛异常 FileAlreadyExistsException
  2. 不能一次创建多级目录,否则会抛异常 NoSuchFileException
代码语言:javascript
复制
  Path path = Paths.get("helloword/d1");
  Files.createDirectory(path);
  • 创建多级目录用
代码语言:javascript
复制
  Path path = Paths.get("helloword/d1/d2");
  Files.createDirectories(path);
  • 拷贝文件 效率也比较高,使用操作系统底层实现,与transtionTo方法各有好处

  1. 如果文件已存在,会抛异常 FileAlreadyExistsException
  2. 如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
代码语言:javascript
复制
  Path source = Paths.get("helloword/data.txt");
  Path target = Paths.get("helloword/target.txt");

  Files.copy(source, target);
  Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
  • 移动文件 注意:
    • StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性
代码语言:javascript
复制
  Path source = Paths.get("helloword/data.txt");
  Path target = Paths.get("helloword/data.txt");

  Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
  • 删除目录 注意:
    • 如果目录还有内容,会抛异常 DirectoryNotEmptyException
代码语言:javascript
复制
  Path target = Paths.get("helloword/d1");

  Files.delete(target);
  • 遍历目录文件 (1.7之后可以使用上面讲到的Files) 如果要删除的文件目录里面还有内容我们就需要遍历一下: demo
代码语言:javascript
复制
public static void main(String[] args) throws IOException {
    Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
    AtomicInteger dirCount = new AtomicInteger();
    AtomicInteger fileCount = new AtomicInteger();
    Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
        //遍历文件之前的方法
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
            throws IOException {
            System.out.println(dir);
            dirCount.incrementAndGet();
            return super.preVisitDirectory(dir, attrs);
        }
        // 遍历文件时的方法
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
            throws IOException {
            System.out.println(file);
            fileCount.incrementAndGet();
            return super.visitFile(file, attrs);
        }
    });
    System.out.println(dirCount); // 133
    System.out.println(fileCount); // 1479
}
  • 统计 指定文件 的数目
代码语言:javascript
复制
  Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
  AtomicInteger fileCount = new AtomicInteger();
  Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
          throws IOException {
          if (file.toFile().getName().endsWith(".jar")) {
              fileCount.incrementAndGet();
          }
          return super.visitFile(file, attrs);
      }
  });
  System.out.println(fileCount); // 724
  • 删除多级目录
代码语言:javascript
复制
Path path = Paths.get("d:\\a");
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
        throws IOException {
        Files.delete(file);
        return super.visitFile(file, attrs);
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) 
        throws IOException {
        Files.delete(dir);
        return super.postVisitDirectory(dir, exc);
    }
});

⚠️ 删除很危险

删除是危险操作,确保要递归删除的文件夹没有重要内容

  • 拷贝多级目录demo
代码语言:javascript
复制
long start = System.currentTimeMillis();
String source = "D:\\Snipaste-1.16.2-x64";
String target = "D:\\Snipaste-1.16.2-x64aaa";

Files.walk(Paths.get(source)).forEach(path -> {
    try {
        String targetName = path.toString().replace(source, target);
        // 是目录
        if (Files.isDirectory(path)) {
            Files.createDirectory(Paths.get(targetName));
        }
        // 是普通文件
        else if (Files.isRegularFile(path)) {
            Files.copy(path, Paths.get(targetName));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});
long end = System.currentTimeMillis();
System.out.println(end - start);
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上遇见你 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Scattering Reads(大致了解即可)
  • Gathering Writes
  • FileChannel
    • FileChannel的工作模式
      • FileChannel的获取方式:
        • 读取
          • 写入
            • 关闭
              • Channel的位置
                • Channel的大小(针对FileChannel)
                  • 强制写入(对性能有所影响)
                    • 测试案例:传输数据
                      • Path
                        • Files
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档