Java-nio

一、NIO三大組件

NIO的三大組件分別是Channel,Buffer與Selector

Java NIO系統的核心在于:通道(Channel)和緩沖區(Buffer)。通道表示打開到 IO 設備(例如:文件、套接字)的連接。若需要使用 NIO 系統,需要獲取用于連接 IO 設備的通道以及用于容納數據的緩沖區。然后操作緩沖區,對數據進行處理

簡而言之,通道負責傳輸,緩沖區負責存儲

常見的Channel有以下四種,其中FileChannel主要用于文件傳輸,其余三種用于網絡通信

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

Buffer有以下幾種,其中使用較多的是ByteBuffer

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

1、Selector

在使用Selector之前,處理socket連接還有以下兩種方法

1.使用多線程技術

為每個連接分別開辟一個線程,分別去處理對應的socke連接

這種方法存在以下幾個問題

  • 內存占用高
    • 每個線程都需要占用一定的內存,當連接較多時,會開辟大量線程,導致占用大量內存
  • 線程上下文切換成本高
  • 只適合連接數少的場景
    • 連接數過多,會導致創建很多線程,從而出現問題

2.使用線程池技術

使用線程池,讓線程池中的線程去處理連接

這種方法存在以下幾個問題

  • 阻塞模式下,線程僅能處理一個連接
    • 線程池中的線程獲取任務(task)后,只有當其執行完任務之后(斷開連接后),才會去獲取并執行下一個任務
    • 若socke連接一直未斷開,則其對應的線程無法處理其他socke連接
  • 僅適合短連接場景
    • 短連接即建立連接發送請求并響應后就立即斷開,使得線程池中的線程可以快速處理其他連接

3.使用選擇器

selector 的作用就是配合一個線程來管理多個 channel(fileChannel因為是阻塞式的,所以無法使用selector),獲取這些 channel 上發生的事件,這些 channel 工作在非阻塞模式下,當一個channel中沒有執行任務時,可以去執行其他channel中的任務。適合連接數多,但流量較少的場景

若事件未就緒,調用 selector 的 select() 方法會阻塞線程,直到 channel 發生了就緒事件。這些事件就緒后,select 方法就會返回這些事件交給 thread 來處理

2、ByteBuffer

使用案例

使用方式
  • 向 buffer 寫入數據,例如調用 channel.read(buffer)
  • 調用 flip() 切換至讀模式
    • flip會使得buffer中的limit變為position,position變為0
  • 從 buffer 讀取數據,例如調用 buffer.get()
  • 調用 clear() 或者compact()切換至寫模式
    • 調用clear()方法時position=0,limit變為capacity
    • 調用compact()方法時,會將緩沖區中的未讀數據壓縮到緩沖區前面
  • 重復以上步驟

使用ByteBuffer讀取文件中的內容

public class TestByteBuffer {public static void main(String[] args) {// 獲得FileChanneltry (FileChannel channel = new FileInputStream("stu.txt").getChannel()) {// 獲得緩沖區ByteBuffer buffer = ByteBuffer.allocate(10);int hasNext = 0;StringBuilder builder = new StringBuilder();while((hasNext = channel.read(buffer)) > 0) {// 切換模式 limit=position, position=0buffer.flip();// 當buffer中還有數據時,獲取其中的數據while(buffer.hasRemaining()) {builder.append((char)buffer.get());}// 切換模式 position=0, limit=capacitybuffer.clear();}System.out.println(builder.toString());} catch (IOException e) {}}
}

打印結果

核心屬性

0123456789abcdef

字節緩沖區的父類Buffer中有幾個核心屬性,如下

// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
  • capacity:緩沖區的容量。通過構造函數賦予,一旦設置,無法更改
  • limit:緩沖區的界限。位于limit 后的數據不可讀寫。緩沖區的限制不能為負,并且不能大于其容量
  • position下一個讀寫位置的索引(類似PC)。緩沖區的位置不能為負,并且不能大于limit
  • mark:記錄當前position的值。position被改變后,可以通過調用reset() 方法恢復到mark的位置。

以上四個屬性必須滿足以下要求

mark <= position <= limit <= capacity

核心方法

ut()方法
  • put()方法可以將一個數據放入到緩沖區中。
  • 進行該操作后,postition的值會+1,指向下一個可以放入的位置。capacity = limit ,為緩沖區容量的值。

flip()方法
  • flip()方法會切換對緩沖區的操作模式,由寫->讀 / 讀->寫
  • 進行該操作后
    • 如果是寫模式->讀模式,position = 0 , limit 指向最后一個元素的下一個位置,capacity不變
    • 如果是讀->寫,則恢復為put()方法中的值

get()方法
  • get()方法會讀取緩沖區中的一個值
  • 進行該操作后,position會+1,如果超過了limit則會拋出異常
  • 注意:get(i)方法不會改變position的值

如果想通過get方法重復讀取數據

  • 可以調用rewind方法講position重新置為0
  • 或者調用get(int i)方法獲取索引 i 的內容,它不會移動讀指針

rewind()方法
  • 該方法只能在讀模式下使用
  • rewind()方法后,會恢復position、limit和capacity的值,變為進行get()前的值

clean()方法
  • clean()方法會將緩沖區中的各個屬性恢復為最初的狀態,position = 0, capacity = limit
  • 此時緩沖區的數據依然存在,處于“被遺忘”狀態,下次進行寫操作時會覆蓋這些數據

mark()和reset()方法
  • mark()方法會將postion的值保存到mark屬性中
  • reset()方法會將position的值改為mark中保存的值
compact()方法

此方法為ByteBuffer的方法,而不是Buffer的方法

  • compact會把未讀完的數據向前壓縮,然后切換到寫模式
  • 數據前移后,原位置的值并未清零,寫時會覆蓋之前的值

clear() VS compact()

clear只是對position、limit、mark進行重置,而compact在對position進行設置,以及limit、mark進行重置的同時,還涉及到數據在內存中拷貝(會調用arraycopy)。所以compact比clear更耗性能。但compact能保存你未讀取的數據,將新數據追加到為讀取的數據之后;而clear則不行,若你調用了clear,則未讀取的數據就無法再讀取到了

ByteBuffer.allocate() 與 ByteBuffer.allocateDirect()?

allocate()方法返回類型是? class java.nio .HeapByteBuffer

HeapByteBuffer?使用的是?java 堆內存,讀寫效率較低,受到GC的影響

allocateDirect()?方法返回類型是? class java.nio .DirectByteBuffer
DirectByteBuffer?使用的是直接內存, 讀寫效率高(少一次拷貝),不會受GC影響,但是分配的效率低(因為需要調用系統的分配內存相關接口),而且使用不當會造成內存泄漏

所以需要根據情況來判斷使用哪種方法進行模式切換

方法調用及演示

ByteBuffer調試工具類

需要先導入netty依賴

<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.51.Final</version>
</dependency>

創建一個工具類,便于觀察ByteBuffer的內部結構

import java.nio.ByteBuffer;import io.netty.util.internal.MathUtil;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.MathUtil.*;/*** @author qingm* @date 2024/1/12 15:59*/
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(StringUtil.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 (MathUtil.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("         +-------------------------------------------------+" +StringUtil.NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +StringUtil.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(StringUtil.NEWLINE +"+--------+-------------------------------------------------+----------------+");}private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {if (row < HEXDUMP_ROWPREFIXES.length) {dump.append(HEXDUMP_ROWPREFIXES[row]);} else {dump.append(StringUtil.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);}
}
調用ByteBuffer的方法
public class TestByteBuffer {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(10);// 向buffer中寫入1個字節的數據buffer.put((byte)97);// 使用工具類,查看buffer狀態ByteBufferUtil.debugAll(buffer);// 向buffer中寫入4個字節的數據buffer.put(new byte[]{98, 99, 100, 101});ByteBufferUtil.debugAll(buffer);// 獲取數據buffer.flip();ByteBufferUtil.debugAll(buffer);System.out.println(buffer.get());System.out.println(buffer.get());ByteBufferUtil.debugAll(buffer);// 使用compact切換模式buffer.compact();ByteBufferUtil.debugAll(buffer);// 再次寫入buffer.put((byte)102);buffer.put((byte)103);ByteBufferUtil.debugAll(buffer);}
}

運行結果

// 向緩沖區寫入了一個字節的數據,此時postition為1
+--------+-------------------- 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.........      |
+--------+-------------------------------------------------+----------------+// 向緩沖區寫入四個字節的數據,此時position為5
+--------+-------------------- all ------------------------+----------------+
position: [5], limit: [10]+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 00 00 00 00 00                   |abcde.....      |
+--------+-------------------------------------------------+----------------+// 調用flip切換模式,此時position為0,表示從第0個數據開始讀取
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [5]+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 00 00 00 00 00                   |abcde.....      |
+--------+-------------------------------------------------+----------------+
// 讀取兩個字節的數據             
97
98// position變為2             
+--------+-------------------- all ------------------------+----------------+
position: [2], limit: [5]+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 00 00 00 00 00                   |abcde.....      |
+--------+-------------------------------------------------+----------------+// 調用compact切換模式,此時position及其后面的數據被壓縮到ByteBuffer前面去了
// 此時position為3,會覆蓋之前的數據             
+--------+-------------------- all ------------------------+----------------+
position: [3], limit: [10]+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 64 65 64 65 00 00 00 00 00                   |cdede.....      |
+--------+-------------------------------------------------+----------------+// 再次寫入兩個字節的數據,之前的 0x64 0x65 被覆蓋         
+--------+-------------------- all ------------------------+----------------+
position: [5], limit: [10]+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 64 65 66 67 00 00 00 00 00                   |cdefg.....      |
+--------+-------------------------------------------------+----------------+

字符串與ByteBuffer的相互轉換

方法一

編碼:字符串調用getByte方法獲得byte數組,將byte數組放入ByteBuffer中

解碼先調用ByteBuffer的flip方法,然后通過StandardCharsets的decoder方法解碼

public class Translate {public static void main(String[] args) {// 準備兩個字符串String str1 = "hello";String str2 = "";ByteBuffer buffer1 = ByteBuffer.allocate(16);// 通過字符串的getByte方法獲得字節數組,放入緩沖區中buffer1.put(str1.getBytes());ByteBufferUtil.debugAll(buffer1);// 將緩沖區中的數據轉化為字符串// 切換模式buffer1.flip();// 通過StandardCharsets解碼,獲得CharBuffer,再通過toString獲得字符串str2 = StandardCharsets.UTF_8.decode(buffer1).toString();System.out.println(str2);ByteBufferUtil.debugAll(buffer1);}
}

運行結果

+--------+-------------------- 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...........|
+--------+-------------------------------------------------+----------------+
hello
+--------+-------------------- all ------------------------+----------------+
position: [5], limit: [5]+-------------------------------------------------+|  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...........|
+--------+-------------------------------------------------+----------------+Copy
方法二

編碼:通過StandardCharsets的encode方法獲得ByteBuffer,此時獲得的ByteBuffer為讀模式,無需通過flip切換模式

解碼:通過StandardCharsets的decoder方法解碼

public class Translate {public static void main(String[] args) {// 準備兩個字符串String str1 = "hello";String str2 = "";// 通過StandardCharsets的encode方法獲得ByteBuffer// 此時獲得的ByteBuffer為讀模式,無需通過flip切換模式ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(str1);ByteBufferUtil.debugAll(buffer1);// 將緩沖區中的數據轉化為字符串// 通過StandardCharsets解碼,獲得CharBuffer,再通過toString獲得字符串str2 = StandardCharsets.UTF_8.decode(buffer1).toString();System.out.println(str2);ByteBufferUtil.debugAll(buffer1);}
}

運行結果

+--------+-------------------- 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
+--------+-------------------- all ------------------------+----------------+
position: [5], 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           |
+--------+-------------------------------------------------+----------------+Copy
方法三

編碼:字符串調用getByte()方法獲得字節數組,將字節數組傳給ByteBuffer的wrap()方法,通過該方法獲得ByteBuffer。同樣無需調用flip方法切換為讀模式

解碼:通過StandardCharsets的decoder方法解碼

public class Translate {public static void main(String[] args) {// 準備兩個字符串String str1 = "hello";String str2 = "";// 通過StandardCharsets的encode方法獲得ByteBuffer// 此時獲得的ByteBuffer為讀模式,無需通過flip切換模式ByteBuffer buffer1 = ByteBuffer.wrap(str1.getBytes());ByteBufferUtil.debugAll(buffer1);// 將緩沖區中的數據轉化為字符串// 通過StandardCharsets解碼,獲得CharBuffer,再通過toString獲得字符串str2 = StandardCharsets.UTF_8.decode(buffer1).toString();System.out.println(str2);ByteBufferUtil.debugAll(buffer1);}
}

運行結果

+--------+-------------------- 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
+--------+-------------------- all ------------------------+----------------+
position: [5], 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           |
+--------+-------------------------------------------------+----------------+

粘包與半包

現象

網絡上有多條數據發送給服務端,數據之間使用 \n 進行分隔
但由于某種原因這些數據在接收時,被進行了重新組合,例如原始數據有3條為

  • Hello,world\n
  • I’m Nyima\n
  • How are you?\n

變成了下面的兩個 byteBuffer (粘包,半包)

  • Hello,world\nI’m Nyima\nHo
  • w are you?\n
出現原因

粘包

發送方在發送數據時,并不是一條一條地發送數據,而是將數據整合在一起,當數據達到一定的數量后再一起發送。這就會導致多條信息被放在一個緩沖區中被一起發送出去

半包

接收方的緩沖區的大小是有限的,當接收方的緩沖區滿了以后,就需要將信息截斷,等緩沖區空了以后再繼續放入數據。這就會發生一段完整的數據最后被截斷的現象

解決辦法
  • 通過get(index)方法遍歷ByteBuffer,遇到分隔符時進行處理。注意:get(index)不會改變position的值
    • 記錄該段數據長度,以便于申請對應大小的緩沖區
    • 將緩沖區的數據通過get()方法寫入到target中
  • 調用compact方法切換模式,因為緩沖區中可能還有未讀的數據
public class ByteBufferDemo {public static void main(String[] args) {ByteBuffer buffer = ByteBuffer.allocate(32);// 模擬粘包+半包buffer.put("Hello,world\nI'm Nyima\nHo".getBytes());// 調用split函數處理split(buffer);buffer.put("w are you?\n".getBytes());split(buffer);}private static void split(ByteBuffer buffer) {// 切換為讀模式buffer.flip();for(int i = 0; i < buffer.limit(); i++) {// 遍歷尋找分隔符// get(i)不會移動positionif (buffer.get(i) == '\n') {// 緩沖區長度int length = i+1-buffer.position();ByteBuffer target = ByteBuffer.allocate(length);// 將前面的內容寫入target緩沖區for(int j = 0; j < length; j++) {// 將buffer中的數據寫入target中target.put(buffer.get());}// 打印查看結果ByteBufferUtil.debugAll(target);}}// 切換為寫模式,但是緩沖區可能未讀完,這里需要使用compactbuffer.compact();}
}

運行結果

+--------+-------------------- all ------------------------+----------------+
position: [12], limit: [12]+-------------------------------------------------+|  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 6c 64 0a             |Hello,world.    |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [10], limit: [10]+-------------------------------------------------+|  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 49 27 6d 20 4e 79 69 6d 61 0a                   |I'm Nyima.      |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- 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?.   |
+--------+-------------------------------------------------+----------------+

二、文件編程

1、FileChannel

工作模式

FileChannel只能在阻塞模式下工作,所以無法搭配Selector

獲取

不能直接打開 FileChannel,必須通過 FileInputStream、FileOutputStream 或者 RandomAccessFile 來獲取 FileChannel,它們都有 getChannel 方法

  • 通過 FileInputStream 獲取的 channel?只能讀
  • 通過 FileOutputStream 獲取的 channel?只能寫
  • 通過 RandomAccessFile 是否能讀寫根據構造 RandomAccessFile 時的讀寫模式決定

讀取

通過 FileInputStream 獲取channel,通過read方法將數據寫入到ByteBuffer中

read方法的返回值表示讀到了多少字節,若讀到了文件末尾則返回-1

int readBytes = channel.read(buffer);Copy

可根據返回值判斷是否讀取完畢

while(channel.read(buffer) > 0) {// 進行對應操作...
}

寫入

因為channel也是有大小的,所以 write 方法并不能保證一次將 buffer 中的內容全部寫入 channel。必須需要按照以下規則進行寫入
?

// 通過hasRemaining()方法查看緩沖區中是否還有數據未寫入到通道中
while(buffer.hasRemaining()) {channel.write(buffer);
}

關閉

通道需要close,一般情況通過try-with-resource進行關閉,最好使用以下方法獲取strea以及channel,避免某些原因使得資源未被關閉
?

public class TestChannel {public static void main(String[] args) throws IOException {try (FileInputStream fis = new FileInputStream("stu.txt");FileOutputStream fos = new FileOutputStream("student.txt");FileChannel inputChannel = fis.getChannel();FileChannel outputChannel = fos.getChannel()) {// 執行對應操作...}}
}

位置

position

channel也擁有一個保存讀取數據位置的屬性,即position

long pos = channel.position();

可以通過position(int pos)設置channel中position的值

long newPos = ...;
channel.position(newPos);

設置當前位置時,如果設置為文件的末尾

  • 這時讀取會返回 -1
  • 這時寫入,會追加內容,但要注意如果 position 超過了文件末尾,再寫入時在新內容和原末尾之間會有空洞(00)

強制寫入

操作系統出于性能的考慮,會將數據緩存,不是立刻寫入磁盤,而是等到緩存滿了以后將所有數據一次性的寫入磁盤。可以調用?force(true)?方法將文件內容和元數據(文件的權限等信息)立刻寫入磁盤

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/711292.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/711292.shtml
英文地址,請注明出處:http://en.pswp.cn/news/711292.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring的簡單使用及內部實現原理

在現代的Java應用程序開發中&#xff0c;Spring Framework已經成為了不可或缺的工具之一。它提供了一種輕量級的、基于Java的解決方案&#xff0c;用于構建企業級應用程序和服務。本文將介紹Spring的簡單使用方法&#xff0c;并深入探討其內部實現原理。 首先&#xff0c;讓我們…

mysql8.0使用MGR實現高可用

一、三節點MGR集群的安裝部署 1. 安裝準備 準備好下面三臺服務器&#xff1a; IP端口角色192.168.150.213306mgr1192.168.150.223306mgr2192.168.150.233306mgr3 配置hosts解析 # cat >> /etc/hosts << EOF 192.168.150.21 mgr1 192.168.150.22 mgr2 192.168…

Windows環境下的調試器探究——硬件斷點

與軟件斷點與內存斷點不同&#xff0c;硬件斷點不依賴被調試程序&#xff0c;而是依賴于CPU中的調試寄存器。 調試寄存器有7個&#xff0c;分別為Dr0~Dr7。 用戶最多能夠設置4個硬件斷點&#xff0c;這是由于只有Dr0~Dr3用于存儲線性地址。 其中&#xff0c;Dr4和Dr5是保留的…

java中容器繼承體系

首先上圖 源碼解析 打開Collection接口源碼&#xff0c;能夠看到Collection接口是繼承了Iterable接口。 public interface Collection<E> extends Iterable<E> { /** * ...... */ } 以下是Iterable接口源碼及注釋 /** * Implementing this inte…

makefileGDB使用

一、makefile 1、make && makefile makefile帶來的好處就是——自動化編譯&#xff0c;一旦寫好&#xff0c;只需要一個make命令&#xff0c;整個工程完全自動編譯&#xff0c;極大的提高了軟件開發的效率 下面我們通過如下示例來進一步體會它們的作用&#xff1a; ①…

使用 Python 實現一個飛書/微信記賬機器人,酷B了!

Python飛書文檔機器人 今天的主題是&#xff1a;使用Python聯動飛書文檔機器人&#xff0c;實現一個專屬的記賬助手&#xff0c;這篇文章如果對你幫助極大&#xff0c;歡迎你分享給你的朋友、她、他&#xff0c;一起成長。 也歡迎大家留言&#xff0c;說說自己想看什么主題的…

代碼隨想錄第天 78.子集 90.子集II

LeetCode 78 子集 題目描述 給你一個整數數組 nums &#xff0c;數組中的元素 互不相同 。返回該數組所有可能的子集&#xff08;冪集&#xff09;。 解集 不能 包含重復的子集。你可以按 任意順序 返回解集。 示例 1&#xff1a; 輸入&#xff1a;nums [1,2,3] 輸出&…

LeetCode 2581.統計可能的樹根數目:換根DP(樹形DP)

【LetMeFly】2581.統計可能的樹根數目&#xff1a;換根DP(樹形DP) 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/count-number-of-possible-root-nodes/ Alice 有一棵 n 個節點的樹&#xff0c;節點編號為 0 到 n - 1 。樹用一個長度為 n - 1 的二維整數數組 edges…

debian/ubuntu 編譯安裝nginx php

debian/ubuntu 編譯安裝nginx php tar -zxvf nginx-1.9.9.tar.gz apt-get install libpcre3 libpcre3-dev ./configure --prefix/work/nginx-1.9.9 --with-pcre make make install service iptables stop #關閉防火墻, 可能不需要 修改nginx運行用戶為tboqi 抱著log目錄可…

【通信基礎知識】完整通信系統的流程圖及各模塊功能詳解

2024.2.29 抱歉最近在寫畢設大論文&#xff0c;因此沒有太多時間更新。然而&#xff0c;在寫論文的過程中&#xff0c;發現自己對通信系統的了解還不夠全明白&#xff0c;因此差了一些碩博論文總結了一個完整的通信系統流程圖。若有不對的地方請多多指正//部分內容有參考ChatGP…

【Elasticsearch管理】網絡配置

文章目錄 HTTP高級網絡設置高級TCP設置 TransportTCP傳輸概要文件Transport跟蹤 線程池fixed線程池fixed_auto_queue_sizescaling處理器設置 HTTP Elasticsearch只在默認情況下綁定到本地主機。對于運行本地開發服務器(如果在同一臺機器上啟動多個節點&#xff0c;甚至可以運行…

YOLOv7基礎 | 第2種方式:簡化網絡結構之yolov7.yaml(由104層簡化為30層)

前言:Hello大家好,我是小哥談。通過下載YOLOv7源碼可知,原始的yolov7.yaml文件是拆開寫的,比較混亂,也不好理解,并且為后續改進增添了很多困難。基于此種情況,筆者就給大家介紹一種將yolov7.yaml文件簡化的方法,將104層簡化為30層,并且參數量和計算量和原來是一致的,…

內存占用構造方法

#使用虛擬內存構造內存消耗 mkdir /tmp/memory mount -t tmpfs -o size5G tmpfs /tmp/memory dd if/dev/zero of/tmp/memory/block #釋放消耗的虛擬內存 rm -rf /tmp/memory/block umount /tmp/memory rmdir /tmp/memory #內存占用可直接在/dev/shm目錄下寫文件

《極客時間 - 左耳聽風》【文章筆記個人思考】

《極客時間 - 左耳聽風》原文鏈接&#xff1a;https://time.geekbang.org/column/intro/100002201?tabcatalog 10 | 如何成為一個大家愿意追隨的Leader&#xff1f; 10 | 如何成為一個大家愿意追隨的Leader&#xff1f; 這里的Leader是在技術上取得優勢&#xff0c;而不是行政…

2024年2月個人工作生活總結

本文為 2024年2月工作生活總結。 研發編碼 一些警告修正記錄 這個月修正了個人所負責的工程警告&#xff0c;這些警告其實是前人的代碼遺留的&#xff0c;我續寫的代碼&#xff0c;除printf函數的%d、%ld格式&#xff0c;都在寫的過程中改了。 下面記錄一些典型的警告及應對…

NLP(一)——概述

參考書: 《speech and language processing》《統計自然語言處理》 宗成慶 語言是思維的載體&#xff0c;自然語言處理相比其他信號較為特別 word2vec用到c語言 Question 預訓練語言模型和其他模型的區別? 預訓練模型是指在大規模數據上進行預訓練的模型&#xff0c;通常…

測試環境搭建整套大數據系統(七:集群搭建kafka(2.13)+flink(1.13.6)+dinky(0.6)+iceberg)

一&#xff1a;搭建kafka。 1. 三臺機器執行以下命令。 cd /opt wget wget https://dlcdn.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz tar zxvf kafka_2.13-3.6.1.tgz cd kafka_2.13-3.6.1/config vim server.properties修改以下倆內容 1.三臺機器分別給予各自的broker_id…

git操作學習記錄,簡單易上手

配置git 的賬戶郵箱 $ git config --global user.name "Firstname Lastname" $ git config --global user.email "your_emailexample.com"代碼回溯 git rest --hard [commit哈希值]git log命令只能查看以當前狀態為終點的歷史日志 git reflog命令&#x…

Python+neo4j構建豆瓣電影知識圖譜

文章目錄 數據來源數據整理導入節點和關系導入使用Subgraph批量導入節點和關系 多標簽實體和實體去重 數據來源 http://www.openkg.cn/dataset/douban-movie-kg 該網址擁有豐富的中文知識圖譜數據集&#xff0c;OpenKG(Open Knowledge Graph)&#xff0c;可供研究人員使用研究…

【golang】25、圖片操作

用 “github.com/fogleman/gg” 可以畫線, 框 用 “github.com/disintegration/imaging” 可以變換顏色 一、渲染 1.1 框和字 import "github.com/fogleman/gg"func DrawRectangles(inPath string, cRects []ColorTextRect, fnImgNameChange FnImgNameChange) (st…