继上篇的Netty入门之基础篇,为大家继续整理网络编程之Netty的相关入门知识点。上次讲到了ByteBuffer与字符串的相互转换。接下来继续
Buffer的线程安全问题?
Buffer是非线程安全的
Scattering Reads(大致了解即可)
分散读取。简单来讲是就是将文件进行拆分,写入不同的Buffer中。
-
代码实例
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中。减少数据的拷贝。
-
代码实例
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,worldn I’m paidaxingn How are you?n 结果到了服务器端变成了2个ByteBuffer Hello,worldnI’m paidaxingnHo w are you?n
现在你需要将数据进行恢复
-
代码演示
public static void main(String[] args) {
ByteBuffer source = ByteBuffer.allocate(32);
source.put("Hello,worldnI'm paidaxingnHo".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,必须要通过FileInputStream
、FileOutputStream
、RandomAccessFile
来获取FileChannel,它们都有getChannel()
方法;
⚠️
-
FileInputStream获取的Channel只能读 -
FileOutputStream获取的Channel只能写 -
通过RandomAccessFile获取的channel是否可以读写根据RandomAccessFile时的读写模式决定
读取
会从channel读取数据填充之ByteBuffer,返回值代表读到了多少个字节,
-1
表示到达了文件的末尾
demo
int readBytes = channel.read(buffer);
写入
这里需要注意一下,当是FileChannel
时,channel是可以不遵守写入的正确的写入方式,但是要是SocketChannel
时一定要遵守下面的写入方式,因为在while中调用channel.write()的write方法并不能保证一次江buffer中的内容全部写入到channel中,因为channel的读写能力是有限制的。
demo
ByteBuffer buffer = ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式
while(buffer.hasRemaining()) {
channel.write(buffer);
}
关闭
channel必须关闭
,不过调用了FileInputStream、FileOutputStream和RandomAccessFile的close方法会间接调用channel的close方法
Channel的位置
获取当前位置
long pos = channel.position();
设置当前位置
long newPos = ...;
channel.position(newPos);
💡 设置当前位置时,如果设置为文件的末尾
-
这时读取会返回 -1 -
这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)
Channel的大小(针对FileChannel)
使用 size 方法获取文件的大小
强制写入(对性能有所影响)
操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘。
测试案例:传输数据
-
使用transferTo的方法 -
优点:效率高,比起自己写文件输入输出流要高 底层会利用操作系统的零拷贝进行优化 -
该方法的传输内容有大小限制 2G
;可以多次传输
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 实例
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:dataprojects
-
.
代表了当前路径 -
..
代表了上一级路径
例如目录结构如下
d:
|- data
|- projects
|- a
|- b
代码
Path path = Paths.get("d:\data\projects\a\..\b");
System.out.println(path);
System.out.println(path.normalize()); // 正常化路径
以上代码会输出
d:dataprojectsa..b
d:dataprojectsb //正常化路径
Files
相关API
-
检查文件是否存在
Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path));
-
创建一级目录
注意事项:
-
如果目录已存在,会抛异常 FileAlreadyExistsException -
不能一次创建多级目录,否则会抛异常 NoSuchFileException
Path path = Paths.get("helloword/d1");
Files.createDirectory(path);
-
创建多级目录用
Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path);
-
拷贝文件
效率也比较高,使用操作系统底层实现,与transtionTo方法各有好处
如果文件已存在,会抛异常 FileAlreadyExistsException 如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
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 保证文件移动的原子性
Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
-
删除目录
注意: -
如果目录还有内容,会抛异常 DirectoryNotEmptyException
Path target = Paths.get("helloword/d1");
Files.delete(target);
-
遍历目录文件 (1.7之后可以使用上面讲到的Files) 如果要删除的文件目录里面还有内容我们就需要遍历一下:
demo
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
}
-
统计 指定文件 的数目
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
-
删除多级目录
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
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);
关于Netty入门暂时结束。如果有帮助,欢迎点赞关注。
微信搜索【
码上遇见你
】获取更多精彩内容
原文始发于微信公众号(码上遇见你):Netty入门之基础篇二
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/78781.html