十堰做网站的有哪些,wix网站做seo如何,商会网站建设方案,文山知名网站建设哪家好文章目录 三大组件Channel BufferSelector ByteBufferByteBuffer 正确使用姿势ByteBuffer 内部结构ByteBuffer 常见方法分配空间向 buffer 写入数据从 buffer 读取数据mark 和 reset 字符串与 ByteBuffer 互转Scattering ReadsGathering Writes粘包、半包分析 文件编程Fi… 文章目录 三大组件Channel BufferSelector ByteBufferByteBuffer 正确使用姿势ByteBuffer 内部结构ByteBuffer 常见方法分配空间向 buffer 写入数据从 buffer 读取数据mark 和 reset 字符串与 ByteBuffer 互转Scattering ReadsGathering Writes粘包、半包分析 文件编程FileChannel两个 Channel 传输数据PathFiles 附 ByteBuffer结构的调试工具 三大组件
Channel Buffer
channel 有一点类似于 stream它就是读写数据的双向通道可以从 channel 将数据读入 buffer也可以将 buffer 的数据写入 channel而之前的 stream 要么是输入要么是输出channel 比 stream 更为底层 #mermaid-svg-lkcUDkYzeiDNfwvr {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr .error-icon{fill:#552222;}#mermaid-svg-lkcUDkYzeiDNfwvr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lkcUDkYzeiDNfwvr .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-lkcUDkYzeiDNfwvr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lkcUDkYzeiDNfwvr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lkcUDkYzeiDNfwvr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lkcUDkYzeiDNfwvr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lkcUDkYzeiDNfwvr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lkcUDkYzeiDNfwvr .marker.cross{stroke:#333333;}#mermaid-svg-lkcUDkYzeiDNfwvr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lkcUDkYzeiDNfwvr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr .cluster-label text{fill:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr .cluster-label span{color:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr .label text,#mermaid-svg-lkcUDkYzeiDNfwvr span{fill:#333;color:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr .node rect,#mermaid-svg-lkcUDkYzeiDNfwvr .node circle,#mermaid-svg-lkcUDkYzeiDNfwvr .node ellipse,#mermaid-svg-lkcUDkYzeiDNfwvr .node polygon,#mermaid-svg-lkcUDkYzeiDNfwvr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lkcUDkYzeiDNfwvr .node .label{text-align:center;}#mermaid-svg-lkcUDkYzeiDNfwvr .node.clickable{cursor:pointer;}#mermaid-svg-lkcUDkYzeiDNfwvr .arrowheadPath{fill:#333333;}#mermaid-svg-lkcUDkYzeiDNfwvr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lkcUDkYzeiDNfwvr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lkcUDkYzeiDNfwvr .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-lkcUDkYzeiDNfwvr .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-lkcUDkYzeiDNfwvr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lkcUDkYzeiDNfwvr .cluster text{fill:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr .cluster span{color:#333;}#mermaid-svg-lkcUDkYzeiDNfwvr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lkcUDkYzeiDNfwvr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} channel buffer 常见的 Channel 有
FileChannelDatagramChannelSocketChannelServerSocketChannel
buffer 则用来缓冲读写数据常见的 buffer 有
ByteBuffer MappedByteBufferDirectByteBufferHeapByteBuffer ShortBufferIntBufferLongBufferFloatBufferDoubleBufferCharBuffer ⚠️Buffer 是非线程安全的 Selector
selector 单从字面意思不好理解需要结合服务器的设计演化来理解它的用途
多线程版设计 #mermaid-svg-EKcp0oDlw3AmLCMO {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO .error-icon{fill:#552222;}#mermaid-svg-EKcp0oDlw3AmLCMO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EKcp0oDlw3AmLCMO .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-EKcp0oDlw3AmLCMO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EKcp0oDlw3AmLCMO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EKcp0oDlw3AmLCMO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EKcp0oDlw3AmLCMO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EKcp0oDlw3AmLCMO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EKcp0oDlw3AmLCMO .marker.cross{stroke:#333333;}#mermaid-svg-EKcp0oDlw3AmLCMO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EKcp0oDlw3AmLCMO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO .cluster-label text{fill:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO .cluster-label span{color:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO .label text,#mermaid-svg-EKcp0oDlw3AmLCMO span{fill:#333;color:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO .node rect,#mermaid-svg-EKcp0oDlw3AmLCMO .node circle,#mermaid-svg-EKcp0oDlw3AmLCMO .node ellipse,#mermaid-svg-EKcp0oDlw3AmLCMO .node polygon,#mermaid-svg-EKcp0oDlw3AmLCMO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EKcp0oDlw3AmLCMO .node .label{text-align:center;}#mermaid-svg-EKcp0oDlw3AmLCMO .node.clickable{cursor:pointer;}#mermaid-svg-EKcp0oDlw3AmLCMO .arrowheadPath{fill:#333333;}#mermaid-svg-EKcp0oDlw3AmLCMO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EKcp0oDlw3AmLCMO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EKcp0oDlw3AmLCMO .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-EKcp0oDlw3AmLCMO .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-EKcp0oDlw3AmLCMO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EKcp0oDlw3AmLCMO .cluster text{fill:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO .cluster span{color:#333;}#mermaid-svg-EKcp0oDlw3AmLCMO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EKcp0oDlw3AmLCMO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 多线程版 socket1 thread socket2 thread socket3 thread 最早在NIO没有出来之前我们如何开发一个服务器端的程序呢
有一种思路就是使用多线程。因为服务端的应用程序开发肯定要处理多个用户的通信那么一个客户来了他在我们的代码里表现就是一个Socket。我们针对这个Socket就能进行一些读写的操作。为了执行这些操作我们服务器端就会启动一个新的线程来专门为这个Socket提供服务。如果有多个客户端那么就会有多个Socket服务端就要开多个线程进行处理。在客户比较少的时候这种方法是可行的但是一旦连接数变多这个方法就不适用了。因为一个客户端用一个线程去处理的话这个线程本身他就会占用一定的内存默认的情况下比如windows就会占用1m的内存如果来了1000个连接那么光线程占用的内存就有1G。
⚠️ 多线程版缺点
内存占用高线程上下文切换成本高只适合连接数少的场景
线程池版设计 #mermaid-svg-ImAtVlQZj13TI7eU {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ImAtVlQZj13TI7eU .error-icon{fill:#552222;}#mermaid-svg-ImAtVlQZj13TI7eU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ImAtVlQZj13TI7eU .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ImAtVlQZj13TI7eU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ImAtVlQZj13TI7eU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ImAtVlQZj13TI7eU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ImAtVlQZj13TI7eU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ImAtVlQZj13TI7eU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ImAtVlQZj13TI7eU .marker.cross{stroke:#333333;}#mermaid-svg-ImAtVlQZj13TI7eU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ImAtVlQZj13TI7eU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ImAtVlQZj13TI7eU .cluster-label text{fill:#333;}#mermaid-svg-ImAtVlQZj13TI7eU .cluster-label span{color:#333;}#mermaid-svg-ImAtVlQZj13TI7eU .label text,#mermaid-svg-ImAtVlQZj13TI7eU span{fill:#333;color:#333;}#mermaid-svg-ImAtVlQZj13TI7eU .node rect,#mermaid-svg-ImAtVlQZj13TI7eU .node circle,#mermaid-svg-ImAtVlQZj13TI7eU .node ellipse,#mermaid-svg-ImAtVlQZj13TI7eU .node polygon,#mermaid-svg-ImAtVlQZj13TI7eU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ImAtVlQZj13TI7eU .node .label{text-align:center;}#mermaid-svg-ImAtVlQZj13TI7eU .node.clickable{cursor:pointer;}#mermaid-svg-ImAtVlQZj13TI7eU .arrowheadPath{fill:#333333;}#mermaid-svg-ImAtVlQZj13TI7eU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ImAtVlQZj13TI7eU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ImAtVlQZj13TI7eU .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-ImAtVlQZj13TI7eU .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-ImAtVlQZj13TI7eU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ImAtVlQZj13TI7eU .cluster text{fill:#333;}#mermaid-svg-ImAtVlQZj13TI7eU .cluster span{color:#333;}#mermaid-svg-ImAtVlQZj13TI7eU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ImAtVlQZj13TI7eU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程池版 socket1 thread socket2 thread socket3 socket4 ⚠️ 线程池版缺点
阻塞模式下线程仅能处理一个 socket 连接 这个很好理解线程在等待数据的过程中是被阻塞的无法同时处理其他的连接。如果这个时候该线程去处理其他的socket了这个时候刚好数据来了那么这些数据就丢了。 仅适合短连接场景
selector 版设计
selector 的作用就是配合一个线程来管理多个 channel获取这些 channel 上发生的事件这些 channel 工作在非阻塞模式下不会让线程吊死在一个 channel 上。适合连接数特别多但流量低的场景low traffic就是一个channel不会频繁的发生事件 #mermaid-svg-Z8Fu0b5Fdv9jXR79 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .error-icon{fill:#552222;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .marker.cross{stroke:#333333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .cluster-label text{fill:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .cluster-label span{color:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .label text,#mermaid-svg-Z8Fu0b5Fdv9jXR79 span{fill:#333;color:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node rect,#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node circle,#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node ellipse,#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node polygon,#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node .label{text-align:center;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .node.clickable{cursor:pointer;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .arrowheadPath{fill:#333333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .cluster text{fill:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 .cluster span{color:#333;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Z8Fu0b5Fdv9jXR79 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} selector 版 selector thread channel channel channel 调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件这些事件发生select 方法就会返回这些事件交给 thread 来处理
ByteBuffer
有一普通文本文件 data.txt内容为
1234567890abcd使用 FileChannel 来读取文件内容
Slf4j
public class ChannelDemo1 {public static void main(String[] args) throws IOException {//首先获取channel,常见的有两种方式// 1输入输出流// 2随机文件流try (FileChannel fileChannel new FileInputStream(data.txt).getChannel()) {//分配字节缓冲一开始处于写模式ByteBuffer buffer ByteBuffer.allocate(10);//开始读取do {int read fileChannel.read(buffer);if (read -1) break;//切换到读模式buffer.flip();while (buffer.hasRemaining()) log.debug(String.valueOf((char)buffer.get()));//切换回写模式buffer.clear();} while (true);}}
}输出 ByteBuffer 正确使用姿势
向 buffer 写入数据例如调用 channel.read(buffer)调用 flip() 切换至读模式从 buffer 读取数据例如调用 buffer.get()调用 clear() 或 compact() 切换至写模式重复 1~4 步骤
ByteBuffer 内部结构
ByteBuffer 有以下重要属性
capacity容量position代表读写指针limit读写的限制
一开始 写模式下position 是写入位置limit 等于容量下图表示写入了 4 个字节后的状态 flip 动作发生后position 切换为读取位置limit 切换为读取限制 读取 4 个字节后状态 clear 动作发生后状态
compact 方法是把未读完的部分向前压缩然后切换至写模式 ByteBuffer 常见方法
分配空间
可以使用 allocate 方法为 ByteBuffer 分配空间其它 buffer 类也有该方法
Bytebuffer buf ByteBuffer.allocate(16);跟他相近的还有一种方法
ByteBuffer byteBuffer ByteBuffer.allocateDirect(16);那么这两种方法有什么区别呢
我们将这两个方法的返回值类型打印一下 Testpublic void bufferTest(){System.out.println(ByteBuffer.allocate(10).getClass()); //class java.nio.HeapByteBufferSystem.out.println(ByteBuffer.allocateDirect(10).getClass()); //class java.nio.DirectByteBuffer}这两者的区别在于
HeapByteBuffer使用的Java堆内存读写效率较低收到GC的影响DirectByteBuffer使用的直接内存读写效率高少一次拷贝不会受GC的影响操作系统来回收不在jvm的管辖范围分配的效率较低要调用操作系统的方法 要想了解堆和直接内存的更多内容可以看我的另一篇文章 JVM从跨平台到跨专业Ⅰ-- 内存结构与对象探秘【含思维导图】 向 buffer 写入数据
有两种办法
调用 channel 的 read 方法调用 buffer 自己的 put 方法
int readBytes channel.read(buf);和
buf.put((byte)127);从 buffer 读取数据
同样有两种办法
调用 channel 的 write 方法调用 buffer 自己的 get 方法
int writeBytes channel.write(buf);和
byte b buf.get();get 方法会让 position 读指针向后走如果想重复读取数据
可以调用 rewind 方法将 position 重新置为 0或者调用 get(int i) 方法获取索引 i 的内容它不会移动读指针
mark 和 reset
mark 是在读取时做一个标记即使 position 改变只要调用 reset 就能回到 mark 的位置 注意 rewind 和 flip 都会清除 mark 位置 字符串与 ByteBuffer 互转
public class BufferAndString {public static void main(String[] args) {//字符串转ByteBuffer//方法一注意这种情况下ByteBuffer还处于写模式//指针此时处于索引5在转化为String的时候要先切换为读模式byte[] bytes hello.getBytes();ByteBuffer buffer ByteBuffer.allocate(16);buffer.put(bytes);ByteBufferUtil.debugAll(buffer);//方法二使用CharSet//此时ByteBuffer已经切换回了读模式指针处于0号位ByteBuffer buffer1 StandardCharsets.UTF_8.encode(hello);ByteBufferUtil.debugAll(buffer1);//方法三wrap可以把一个字节数组包装成一个ByteBufferByteBuffer buffer2 ByteBuffer.wrap(hello.getBytes());ByteBufferUtil.debugAll(buffer2);//至于ByteBuffer转字符串直接相反方法即可}
}输出 Scattering Reads
分散读取
有一个文本文件 3parts.txt
onetwothree使用如下方式读取可以将数据填充至多个 buffer
try (RandomAccessFile file new RandomAccessFile(helloword/3parts.txt, rw)) {FileChannel channel file.getChannel();ByteBuffer a ByteBuffer.allocate(3);ByteBuffer b ByteBuffer.allocate(3);ByteBuffer c ByteBuffer.allocate(5);channel.read(new ByteBuffer[]{a, b, c});a.flip();b.flip();c.flip();debug(a);debug(b);debug(c);
} catch (IOException e) {e.printStackTrace();
}结果 -------------------------------------------------| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
-------------------------------------------------------------------------
|00000000| 6f 6e 65 |one |
--------------------------------------------------------------------------------------------------------------------------| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
-------------------------------------------------------------------------
|00000000| 74 77 6f |two |
--------------------------------------------------------------------------------------------------------------------------| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
-------------------------------------------------------------------------
|00000000| 74 68 72 65 65 |three |
-------------------------------------------------------------------------Gathering Writes
使用如下方式写入可以将多个 buffer 的数据填充至 channel也就是集中写入
try (RandomAccessFile file new RandomAccessFile(helloword/3parts.txt, rw)) {FileChannel channel file.getChannel();ByteBuffer d ByteBuffer.allocate(4);ByteBuffer e ByteBuffer.allocate(4);channel.position(11);d.put(new byte[]{f, o, u, r});e.put(new byte[]{f, i, v, e});d.flip();e.flip();debug(d);debug(e);channel.write(new ByteBuffer[]{d, e});
} catch (IOException e) {e.printStackTrace();
}输出 -------------------------------------------------| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
-------------------------------------------------------------------------
|00000000| 66 6f 75 72 |four |
--------------------------------------------------------------------------------------------------------------------------| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
-------------------------------------------------------------------------
|00000000| 66 69 76 65 |five |
-------------------------------------------------------------------------文件内容
onetwothreefourfive粘包、半包分析
网络上有多条数据发送给服务端数据之间使用\n进行分隔但由于某种原因这些数据在接收时被进行了重新组合。例如原始数据有3条为
Helloworld\nI’m zhangsan\nHow are you?\n
变成了下面2个ByteBuffer
Helloworld\nI’m zhangsan\nHo 两条消息黏在了一起这就属于粘包现象。发生原因效率太高 w are you?\n 消息被截断了这就属于半包现象。发生原因服务器缓冲区的大小限制
我们现在编写程序将错乱的数据恢复成原始的按\n分隔的数据
public class TestByteBufferExam {public static void main(String[] args) {//构造粘包、半包现象ByteBuffer source ByteBuffer.allocate(32);source.put(Helloworld\nIm zhangsan\nHo.getBytes());split(source);source.put(w are you?\n.getBytes());split(source);}public static void split(ByteBuffer source){//首先切换读模式source.flip();//遍历source找到分隔符for (int i 0; i source.limit(); i) {//找到了一个完整消息if (source.get(i) \n) {//创建一个新的ByteBuffer存储int length i - source.position() 1;ByteBuffer target ByteBuffer.allocate(length);for (int j 0 ; j length; j) {target.put(source.get());}//验证一下ByteBufferUtil.debugAll(target);}}//处理完之后切换回写模式,// 为了让残留的半截消息不丢失我们使用的compact方法切换为写模式source.compact();}
}结果 这是一种基本的解决粘包、半包的方法当然效率并不高后面会有更高效的处理方法。
文件编程
FileChannel
⚠️ FileChannel 工作模式 FileChannel 只能工作在阻塞模式下 获取
不能直接打开 FileChannel必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel它们都有 getChannel 方法
通过 FileInputStream 获取的 channel 只能读通过 FileOutputStream 获取的 channel 只能写通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定
读取
会从 channel 读取数据填充 ByteBuffer返回值表示读到了多少字节-1 表示到达了文件的末尾
int readBytes channel.read(buffer);写入
写入的正确姿势如下 SocketChannel
ByteBuffer buffer ...;
buffer.put(...); // 存入数据
buffer.flip(); // 切换读模式while(buffer.hasRemaining()) {channel.write(buffer);
}在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel
关闭
channel 必须关闭不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法
位置
获取当前位置
long pos channel.position();设置当前位置
long newPos ...;
channel.position(newPos);设置当前位置时如果设置为文件的末尾
这时读取会返回 -1这时写入会追加内容但要注意如果 position 超过了文件末尾再写入时在新内容和原末尾之间会有空洞00
大小
使用 size 方法获取文件的大小
强制写入
操作系统出于性能的考虑会将数据缓存不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据文件的权限等信息立刻写入磁盘
两个 Channel 传输数据
String FROM helloword/data.txt;
String TO helloword/to.txt;
long start System.nanoTime();
try (FileChannel from new FileInputStream(FROM).getChannel();FileChannel to new FileOutputStream(TO).getChannel();) {from.transferTo(0, from.size(), to);
} catch (IOException e) {e.printStackTrace();
}
long end System.nanoTime();
System.out.println(transferTo 用时 (end - start) / 1000_000.0);输出
transferTo 用时8.2011超过 2g 大小的文件传输
public class TestFileChannelTransferTo {public static void main(String[] args) {try (FileChannel from new FileInputStream(data.txt).getChannel();FileChannel to new FileOutputStream(to.txt).getChannel();) {// 效率高底层会利用操作系统的零拷贝进行优化long size from.size();// left 变量代表还剩余多少字节for (long left size; left 0; ) {System.out.println(position: (size - left) left: left);left - from.transferTo((size - left), left, to);}} catch (IOException e) {e.printStackTrace();}}
}实际传输一个超大文件
position:0 left:7769948160
position:2147483647 left:5622464513
position:4294967294 left:3474980866
position:6442450941 left:1327497219Path
jdk7 引入了 Path 和 Paths 类
Path 用来表示文件路径Paths 是工具类用来获取 Path 实例
Path source Paths.get(1.txt); // 相对路径 使用 user.dir 环境变量来定位 1.txtPath source Paths.get(d:\\1.txt); // 绝对路径 代表了 d:\1.txtPath source Paths.get(d:/1.txt); // 绝对路径 同样代表了 d:\1.txtPath projects Paths.get(d:\\data, projects); // 代表了 d:\data\projects. 代表了当前路径.. 代表了上一级路径
例如目录结构如下
d:|- data|- projects|- a|- b代码
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\bFiles
检查文件是否存在
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
遍历目录文件
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 SimpleFileVisitorPath(){Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {System.out.println(dir);dirCount.incrementAndGet();return super.preVisitDirectory(dir, attrs);}Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {System.out.println(file);fileCount.incrementAndGet();return super.visitFile(file, attrs);}});System.out.println(dirCount); // 133System.out.println(fileCount); // 1479
}统计 jar 的数目
Path path Paths.get(C:\\Program Files\\Java\\jdk1.8.0_91);
AtomicInteger fileCount new AtomicInteger();
Files.walkFileTree(path, new SimpleFileVisitorPath(){Overridepublic 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 SimpleFileVisitorPath(){Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {Files.delete(file);return super.visitFile(file, attrs);}Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {Files.delete(dir);return super.postVisitDirectory(dir, exc);}
});⚠️ 删除很危险 删除是危险操作确保要递归删除的文件夹没有重要内容 拷贝多级目录
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);附 ByteBuffer结构的调试工具
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;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);}
}