NIO基础
NIO 非阻塞IO
三大组件之Channel & Buffer
channel类似于stream,是读写数据的双向通道,可以从channel将数据读入buffer,也可以把buffer的数据写入channel,而之前的stream要么是输入,要么是输出,channel比stream更为底层
常见Channel
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
常见buffer
buffer用来缓冲读写数据
-
ByteBuffer
- MappedByteBuffer
- DirectByteBuffer
- HeapByteBuffer
-
ShortBuffer
-
IntBuffer
-
LongBuffer
-
FloatBuffer
-
DoubleBuffer
-
CharBuffer
读取文件数据
package org.example.bytebuffer;
import lombok.extern.slf4j.Slf4j;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@Slf4j
public class TestByteBuffer {
public static void main(String[] args) {
// FileChannel
// 1.输入流输出流 2.RandomAccessFile
try (FileChannel fileChannel = new FileInputStream("data.txt").getChannel()) {
// 创建ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
while (true) {
// 读取到buffer里
int len = fileChannel.read(byteBuffer);
log.debug("读取到的字节数: {}", len);
if (len == -1) { // 没有内容了
break;
}
byteBuffer.flip(); // 切换读模式
// 获取byteBuffer
while (byteBuffer.hasRemaining()) { //是否还有剩余未读数据
char ch = (char) byteBuffer.get();
log.debug("读取到的字节: {}", ch);
}
// 读完一次以后,buffer切换为写模式
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ByteBuffer内部结构
有下面重要属性:
- capacity
- position
- limit
ByteBufferUtil
package org.example.util;
import java.nio.ByteBuffer;
public class ByteBufferUtil {
public static void debug(ByteBuffer buffer) {
int pos = buffer.position();
int limit = buffer.limit();
int cap = buffer.capacity();
// 定义列宽
int offsetWidth = 8;
int hexWidth = 49; // 16字节 * 2 + 15个空格 + 1个中间分隔符 = 49
int charWidth = 16;
// 计算表格总宽度
int totalTableWidth = offsetWidth + hexWidth + charWidth + 8; // 8是边框和分隔符的宽度
// 居中标题
String titleText = " ByteBuffer Debug View ";
printCenteredBorder("┌", "─", "┐", titleText, totalTableWidth);
// 显示缓冲区信息
String infoText = String.format("Position: %d │ Limit: %d │ Capacity: %d", pos, limit, cap);
printCenteredLine(infoText, totalTableWidth);
// 显示标记说明
String legendText = "Arrows: ↑P = Position ↑L = Limit-1 ↑* = Both";
printCenteredLine(legendText, totalTableWidth);
// 列分隔符
printSeparator("├", "┼", "┤", offsetWidth, hexWidth, charWidth);
// 列标题
String offsetHeader = centerText("Offset", offsetWidth);
String hexHeader = centerText("Hex Dump", hexWidth);
String charHeader = centerText("ASCII", charWidth);
System.out.printf("│%s│%s│%s│%n", offsetHeader, hexHeader, charHeader);
printSeparator("├", "┼", "┤", offsetWidth, hexWidth, charWidth);
// 创建一个临时的只读副本来安全地读取所有位置的数据
ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
readBuffer.clear(); // 重置position=0, limit=capacity,这样可以读取整个缓冲区
// 数据行
for (int i = 0; i < cap; i += 16) {
StringBuilder hexPart = new StringBuilder();
StringBuilder charPart = new StringBuilder();
StringBuilder arrowPart = new StringBuilder(); // 新增:箭头行
String offset = String.format("%08X", i);
// 处理每行的16个字节
for (int j = 0; j < 16; j++) {
int idx = i + j;
if (idx < cap) {
// 使用绝对位置读取,不影响原缓冲区的position
byte b = readBuffer.get(idx);
// 格式化十六进制值(不再添加标记)
String hexValue = String.format("%02X", b & 0xFF);
hexPart.append(hexValue);
// 添加ASCII字符
char c = (b >= 32 && b <= 126) ? (char) b : '·';
charPart.append(c);
// 添加箭头标记
String arrow = getArrow(idx, pos, limit);
arrowPart.append(arrow.isEmpty() ? " " : (" " + arrow));
} else {
// 填充空白
hexPart.append(" ");
charPart.append(" ");
arrowPart.append(" ");
}
// 添加间隔(第8个字节后添加额外空格)
if (j < 15) {
hexPart.append(j == 7 ? " " : " ");
arrowPart.append(j == 7 ? " " : " ");
}
}
// 确保字符部分长度正确
while (charPart.length() < charWidth) {
charPart.append(" ");
}
// 确保十六进制部分长度正确
String hexStr = hexPart.toString();
if (hexStr.length() < hexWidth) {
hexStr = String.format("%-" + hexWidth + "s", hexStr);
}
// 确保箭头部分长度正确
String arrowStr = arrowPart.toString();
if (arrowStr.length() < hexWidth) {
arrowStr = String.format("%-" + hexWidth + "s", arrowStr);
}
// 输出数据行
System.out.printf("│%s│%s│%s│%n", offset, hexStr, charPart.toString());
// 检查这一行是否需要显示箭头
boolean hasArrows = false;
for (int j = 0; j < 16 && i + j < cap; j++) {
int idx = i + j;
if (!getArrow(idx, pos, limit).isEmpty()) {
hasArrows = true;
break;
}
}
// 如果有箭头,输出箭头行
if (hasArrows) {
System.out.printf("│%s│%s│%s│%n",
" ".repeat(offsetWidth),
arrowStr,
" ".repeat(charWidth));
}
}
// 底部边框
printSeparator("└", "┴", "┘", offsetWidth, hexWidth, charWidth);
// 添加额外信息
if (cap > 0) {
System.out.println();
System.out.printf("Buffer Info: %d bytes total, %d bytes readable (pos=%d to limit=%d)%n",
cap, Math.max(0, limit - pos), pos, limit);
}
}
private static String getArrow(int index, int position, int limit) {
boolean isPosition = (index == position);
boolean isLimitMinus1 = (index == limit - 1 && limit > 0);
if (isPosition && isLimitMinus1) {
return "↑*"; // 同时是position和limit-1
} else if (isPosition) {
return "↑P"; // position箭头
} else if (isLimitMinus1) {
return "↑L"; // limit-1箭头
}
return "";
}
private static String getMarker(int index, int position, int limit) {
boolean isPosition = (index == position);
boolean isLimitMinus1 = (index == limit - 1 && limit > 0);
if (isPosition && isLimitMinus1) {
return "*"; // 同时是position和limit-1
} else if (isPosition) {
return "P"; // position标记
} else if (isLimitMinus1) {
return "L"; // limit-1标记
}
return "";
}
private static void printCenteredBorder(String left, String fill, String right, String text, int width) {
int textLen = text.length();
int fillLen = width - textLen - 2; // -2 for left and right borders
int leftFill = fillLen / 2;
int rightFill = fillLen - leftFill;
System.out.print(left);
System.out.print(fill.repeat(leftFill));
System.out.print(text);
System.out.print(fill.repeat(rightFill));
System.out.println(right);
}
private static void printCenteredLine(String text, int width) {
String centeredText = centerText(text, width - 2); // -2 for borders
System.out.println("│" + centeredText + "│");
}
private static void printSeparator(String left, String middle, String right, int... widths) {
System.out.print(left);
for (int i = 0; i < widths.length; i++) {
System.out.print("─".repeat(widths[i]));
if (i < widths.length - 1) {
System.out.print(middle);
}
}
System.out.println(right);
}
private static String centerText(String text, int width) {
if (text.length() >= width) {
return text.substring(0, width);
}
int paddingTotal = width - text.length();
int padLeft = paddingTotal / 2;
int padRight = paddingTotal - padLeft;
return " ".repeat(padLeft) + text + " ".repeat(padRight);
}
// 示例用法
public static void main(String[] args) {
System.out.println("=".repeat(80));
System.out.println("ByteBuffer Debug Tool - Test Cases");
System.out.println("=".repeat(80));
// 测试案例1:正常缓冲区
System.out.println("\n【测试1】正常缓冲区,包含可打印字符");
ByteBuffer buffer1 = ByteBuffer.allocate(32);
String testStr = "Hello, ByteBuffer World!";
buffer1.put(testStr.getBytes());
buffer1.flip();
buffer1.position(7); // 设置position到逗号后
buffer1.limit(20); // 限制到World的d
debug(buffer1);
// 测试案例2:小缓冲区,position和limit-1重合
System.out.println("\n【测试2】position和limit-1重合的情况");
ByteBuffer buffer2 = ByteBuffer.allocate(10);
buffer2.put(new byte[]{0x41, 0x42, 0x43, 0x44, 0x45}); // ABCDE
buffer2.flip();
buffer2.position(2); // C
buffer2.limit(3); // limit在D,所以limit-1是C
debug(buffer2);
// 测试案例3:包含不可打印字符
System.out.println("\n【测试3】包含不可打印字符和二进制数据");
ByteBuffer buffer3 = ByteBuffer.allocate(24);
for (int i = 0; i < 24; i++) {
buffer3.put((byte) (i + 1)); // 1-24的字节值
}
buffer3.flip();
buffer3.position(5);
buffer3.limit(18);
debug(buffer3);
// 测试案例4:空缓冲区
System.out.println("\n【测试4】空缓冲区");
ByteBuffer buffer4 = ByteBuffer.allocate(0);
debug(buffer4);
// 测试案例5:单字节缓冲区
System.out.println("\n【测试5】单字节缓冲区");
ByteBuffer buffer5 = ByteBuffer.allocate(1);
buffer5.put((byte) 'X');
buffer5.flip();
debug(buffer5);
// 测试案例6:大缓冲区截断显示
System.out.println("\n【测试6】较大缓冲区");
ByteBuffer buffer6 = ByteBuffer.allocate(50);
String longStr = "The quick brown fox jumps over the lazy dog. 1234567890";
buffer6.put(longStr.substring(0, Math.min(longStr.length(), 50)).getBytes());
buffer6.flip();
buffer6.position(10);
buffer6.limit(35);
debug(buffer6);
}
}
看看效果
package org.example.bytebuffer;
import lombok.extern.slf4j.Slf4j;
import java.nio.ByteBuffer;
import static org.example.util.ByteBufferUtil.debug;
@Slf4j
public class TestByteBuffer {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
debug(byteBuffer);
byteBuffer.put((byte) 'a');
byteBuffer.put((byte) 'b');
byteBuffer.put((byte) 'c');
debug(byteBuffer);
byteBuffer.flip();
debug(byteBuffer);
for (int i = 0; i < 3; i++) {
byteBuffer.get();
}
debug(byteBuffer);
}
}
ByteBuffer常用方法
分配空间
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
package org.example.bytebuffer;
import lombok.extern.slf4j.Slf4j;
import java.nio.ByteBuffer;
@Slf4j
public class TestByteBuffer {
public static void main(String[] args) {
System.out.println(ByteBuffer.allocate(1024).getClass());
System.out.println(ByteBuffer.allocateDirect(1024).getClass());
}
}
向buffer写入数据
从buffer读取数据
读取数据的mark和reset
package org.example.bytebuffer;
import lombok.extern.slf4j.Slf4j; // Lombok annotation for logging, though not used in this main example
import java.nio.ByteBuffer;
import static org.example.util.ByteBufferUtil.debug; // Assuming ByteBufferUtil.debug is your custom utility
/**
* Demonstrates common operations and state transitions of a Java NIO ByteBuffer.
* This class showcases:
* 1. Allocation of a ByteBuffer.
* 2. Writing data (put) into the buffer.
* 3. Switching to read mode (flip).
* 4. Reading data (get) from the buffer.
* 5. Marking a position (mark) and returning to it (reset).
*/
@Slf4j // Lombok annotation, provides a 'log' field automatically
public class TestByteBuffer {
public static void main(String[] args) {
System.out.println("========== TestByteBuffer Execution Start ==========");
// 1. Allocate ByteBuffer
// ---------------------------------------------------------------------
System.out.println("\n--- 1. Allocating ByteBuffer (capacity 10) ---");
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// State after allocation:
// position = 0 (current position for writing/reading)
// limit = 10 (how much can be written/read, initially capacity)
// capacity = 10 (total size of the buffer)
// mark = undefined
// 2. Put data into the buffer (Write Mode)
// ---------------------------------------------------------------------
System.out.println("\n--- 2. Putting \"helloyyds\" (9 bytes) into buffer ---");
byteBuffer.put("helloyyds".getBytes()); // "helloyyds" is 9 bytes long
// After put:
// position advances by the number of bytes written (position = 9)
// limit remains capacity (limit = 10)
debug(byteBuffer); // Display buffer state and content
// 3. Flip the buffer (Switch to Read Mode)
// ---------------------------------------------------------------------
System.out.println("\n--- 3. Flipping buffer (to prepare for reading) ---");
byteBuffer.flip();
// After flip:
// limit is set to current position (limit = 9, the extent of written data)
// position is reset to 0 (to start reading from the beginning of written data)
// mark is discarded if set
debug(byteBuffer);
// 4. Get one byte (Read Mode)
// ---------------------------------------------------------------------
System.out.println("\n--- 4. Getting one byte from buffer ---");
byteBuffer.get(); // Reads the byte at position 0 ('h') and increments position
// After get():
// position advances by 1 (position = 1)
// limit remains 9
debug(byteBuffer);
// 5. Mark current position and read some bytes
// ---------------------------------------------------------------------
System.out.println("\n--- 5. Marking position and reading 4 bytes ---");
byteBuffer.mark(); // Sets mark = current position (mark = 1)
System.out.print("Read characters: ");
for (int i = 0; i < 4; i++) {
char c = (char) byteBuffer.get(); // Reads 'e', 'l', 'l', 'o'
System.out.print(c + " ");
}
System.out.println();
// After reading 4 bytes:
// position advances by 4 (position = 1 + 4 = 5)
// limit remains 9
// mark remains 1
debug(byteBuffer);
// 6. Reset position to the mark
// ---------------------------------------------------------------------
System.out.println("\n--- 6. Resetting position to the mark ---");
byteBuffer.reset(); // Sets position = mark (position = 1)
// After reset:
// position is now 1
// limit remains 9
debug(byteBuffer);
// 7. Read the same bytes again after reset
// ---------------------------------------------------------------------
System.out.println("\n--- 7. Reading 4 bytes again after reset ---");
System.out.print("Read characters again: ");
for (int i = 0; i < 4; i++) {
char c = (char) byteBuffer.get(); // Reads 'e', 'l', 'l', 'o' again
System.out.print(c + " ");
}
System.out.println();
// After reading 4 bytes again:
// position advances by 4 (position = 1 + 4 = 5)
// limit remains 9
debug(byteBuffer); // Final state
System.out.println("\n========== TestByteBuffer Execution End ==========");
}
}
字符串与bytebuffer的互相转换
package org.example.bytebuffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class StringToByteBuffer {
public static void main(String[] args) {
ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("helloworld");
ByteBuffer byteBuffer1 = Charset.forName("UTF-8").encode("hello");
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);
System.out.println(charBuffer.toString());
CharBuffer charBuffer1 = Charset.forName("UTF-8").decode(byteBuffer1);
System.out.println(charBuffer1.toString());
ByteBuffer byteBuffer2 = ByteBuffer.wrap("helloworld".getBytes(StandardCharsets.UTF_8));
CharBuffer charBuffer2 = Charset.forName("UTF-8").decode(byteBuffer2);
System.out.println(charBuffer2);
}
}
分散读集中写
package org.example.bytebuffer;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import static org.example.util.ByteBufferUtil.debug;
public class MultiPartByteBuffer {
public static void main(String[] args) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile("3parts.txt", "r")) {
FileChannel fileChannel = randomAccessFile.getChannel();
// 分配三个 ByteBuffer
ByteBuffer a1 = ByteBuffer.allocate(3);
ByteBuffer a2 = ByteBuffer.allocate(3);
ByteBuffer a3 = ByteBuffer.allocate(5);
// 分散读取
fileChannel.read(new ByteBuffer[]{a1, a2, a3});
// 切换到读模式
a1.flip();
a2.flip();
a3.flip();
// 调试输出
debug(a1);
debug(a2);
debug(a3);
// Process a1 fully
while (a1.hasRemaining()) {
System.out.print((char) a1.get());
}
// Process a2 fully
while (a2.hasRemaining()) {
System.out.print((char) a2.get());
}
// Process a3 fully
while (a3.hasRemaining()) {
System.out.print((char) a3.get());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MultiPartFileChannelWriter {
public static void main(String[] args) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile("multi.txt","rw")) {
FileChannel fileChannel = randomAccessFile.getChannel();
ByteBuffer a1 = StandardCharsets.UTF_8.encode("meowrain");
ByteBuffer a2 = StandardCharsets.UTF_8.encode("yyds");
ByteBuffer a3 = StandardCharsets.UTF_8.encode("hooo");
fileChannel.write(new ByteBuffer[]{a1,a2,a3});
} catch (IOException e) {
e.printStackTrace();
}
}
}