[Java/字节流/BytesReader] 核心源码精讲: ByteArrayInputStream(字节数组输入流,JDK 1.0-)
序
- 在物联网领域、通信领域,时常涉及直接从二进制字节数据中读取指定区域的字节数据,完成通信协议报文的解析/反序列化等操作。
直接读取字节数据,能节约诸多资源、提高程序处理性能。
而Java idk io模块内自带的 ByteArrayInputStream 是一个很好的字节流处理组件。(本文聚焦的对象)
-
最近半年,笔者约摸70-80%的时间都花在了研究、设计、处理【端云通信协议】及其二进制报文数据。
此篇,是用到jdk中的一个较为底层的组件。感兴趣的朋友可以阅读一二。 -
也推荐阅读: java io模块的字符流处理组件 StringReader。
(参见文末的推荐阅读文献)
概述 : ByteArrayInputStream(字节数组输入流)
简介
- 字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。
java.io.ByteArrayInputStream: 自 JDK 1.0 起即有此类
创建对象的方式
创建字节数组输入流对象有以下几种方式。
- 方式1 接收字节数组作为参数创建:
ByteArrayInputStream bArray = new ByteArrayInputStream(byte [] a);
- 方式2 接收一个字节数组,和两个整形变量 off、len,off表示第一个读取的字节,len表示读取字节的长度。
ByteArrayInputStream bArray = new ByteArrayInputStream(byte []a, int off, int len)
成功创建字节数组输入流对象后,可以参见以下列表中的方法,对流进行读操作或其他操作。
常用API
| 序号 | 方法描述 |
|---|---|
| 1 | public int read() 从此输入流中读取下一个数据字节。 |
| 2 | public int read(byte[] r, int off, int len) 将最多 len 个数据字节从此输入流读入字节数组。 |
| 3 | public int available() 返回可不发生阻塞地从此输入流读取的字节数。 |
| 4 | public void mark(int read) 设置流中的当前标记位置。 |
| 5 | public long skip(long n) 从此输入流中跳过 n 个输入字节。 |
示例
下面的例子演示了ByteArrayInputStream 和 ByteArrayOutputStream的使用:
import java.io.*;
public class ByteStreamTest {
public static void main(String args[])throws IOException {
ByteArrayOutputStream bOutput = new ByteArrayOutputStream(12);
while( bOutput.size()!= 10 ) {
// 获取用户输入值
bOutput.write(System.in.read());
}
byte b [] = bOutput.toByteArray();
System.out.println("Print the content");
for(int x= 0 ; x < b.length; x++) {
// 打印字符
System.out.print((char)b[x] + " ");
}
System.out.println(" ");
int c;
ByteArrayInputStream bInput = new ByteArrayInputStream(b);
System.out.println("Converting characters to Upper case " );
for(int y = 0 ; y < 1; y++ ) {
while(( c= bInput.read())!= -1) {
System.out.println(Character.toUpperCase((char)c));
}
bInput.reset();
}
}
}
out
asdfghjkly
Print the content
a s d f g h j k l y
Converting characters to Upper case
A
S
D
F
G
H
J
K
L
Y
最佳实践
基于封装 ByteArrayInputStream 的 BytesReader
前置依赖: DataTypeEnum / IBytesReader
DataTypeEnum / IBytesReader / HexStringReader
BytesReader
package com.xxx.sdk.utils.bytes;
import com.xxx.sdk.pojo.bytes.DataTypeEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.Map;
/**
* 以字节数据为源的读取器
* @note 核心原理: 基于字节流工具类 {@link ByteArrayInputStream } 读取字节数据
* @updateTime 2025.8.12 09:32 AM
*/
@Slf4j
public class BytesReader implements IBytesReader<byte []> {
//初始的字节数组
private byte [] source;
//初始的偏移量
private int initOffset;
//初始的长度
private int initLength;
private ByteArrayInputStream byteArrayInputStream;
/**
* 字符集
* @note 解析 String 类型的字节数据时使用
*/
private Charset charset = CHARSET_DEFAULT;
/**
* 最近一次操作读取到的字节数据
*/
private byte[] latestReadData = null;
public BytesReader(byte [] bytes, int offset, int length) {
this.byteArrayInputStream = new ByteArrayInputStream(bytes, offset, length);
this.byteArrayInputStream.reset();
this.source = bytes;
this.initOffset = offset;
this.initLength = length;
}
public BytesReader(byte [] bytes) {
//this.byteArrayInputStream = new ByteArrayInputStream(bytes);
this(bytes, 0 ,bytes.length);//与上一行代码等效
}
public int read(){
return this.byteArrayInputStream.read();
}
/**
* 读取1个字节
* @return
*/
public byte readByte(){
int byteSize = 1;
byte [] targetBuffer = new byte[byteSize];
int off = 0;//目标字节数组的起始位置
int readSize = read(targetBuffer, off, byteSize);
if(log.isDebugEnabled()){
log.debug("off:{}, readSize:{}", off, readSize);
}
return targetBuffer[0];
}
public Map.Entry<Integer, byte[]> readBytes(int length){
byte [] destinationBuffer = new byte[ length];
int destinationBufferOffset = 0;
int destinationBufferLength = destinationBuffer.length;
int actualLength = this.byteArrayInputStream.readNBytes(destinationBuffer, destinationBufferOffset, destinationBufferLength);
final Map.Entry<Integer, byte[]> result = new Map.Entry<Integer, byte[]>() {
@Override
public Integer getKey() {
return actualLength;
}
@Override
public byte[] getValue() {
return destinationBuffer;
}
@Override
public byte[] setValue(byte[] value) {
throw new RuntimeException("Not support set value method!");
}
};
return result;
}
@Override
public Object parseBytes(int readBytesLength, DataTypeEnum dataType, Charset charset, String comment) throws IOException {
comment = (comment == null || comment.length() == 0) ?"":comment;
//step1 判断长度
if (readBytesLength > this.getSource().length) {
String errorMessage = String.format(
"%s | raw data's bytes length is abnormal!readBytesLength=%d,dataType=%s, sourceHex=%s", comment, readBytesLength, dataType, BytesUtils.bytesToHexString( this.getSource() )
);
log.error(errorMessage );//报文长度异常
throw new IOException(errorMessage);
}
//step2 从 reader 读取数据,存入 data:byte []
byte[] rawData = new byte[readBytesLength];
this.read(rawData);
this.latestReadData = rawData;
//step3 data:char [] 转 String
// [demo] length = 2, dataType = "L", hexString = "0014", next = 4( StringReader 已读取 4 个 char,待读取第 1+4 个(即 下标为 4) 的 char )
// [demo] length = 4, dataType = "L", hexString = "64E85504", next = xx
if(log.isDebugEnabled()){//原则上不建议打开 | 注: next() 是基于反射机制获取的
String rawDataHex = BytesUtils.bytesToHexString( rawData );
log.debug("{}| rawDataHex:{}, dataType:{}, next:{}", comment, rawDataHex, dataType, this.next());
}
//step4 按照不同类型解析数据
Object result = null;
try {
result = bytesConvert( rawData, dataType, charset );
} catch (Exception exception) {//先捕获异常,打印具体的日志信息(便于事后定位);再上抛异常
String rawDataHex = BytesUtils.bytesToHexString( rawData );
log.error("{}| Fail to convert the raw data({}) to data type({})!next:{}, exception.message:{}", comment, rawDataHex, dataType, this.next(), exception.getMessage() , this.next(), exception);
if( log.isDebugEnabled() ) {//仅调试模式下,打印该内容
log.error( "{}| raw data:{} | data-type:{} | next:{} | source-string:{}", comment, rawDataHex, dataType, this.next(), this.getSource());
}
throw new RuntimeException(exception);
}
return result;
}
private static Object bytesConvert(byte [] rawData, DataTypeEnum dataType, Charset charset) {
Object result = null;
String errorMessage = null;
try {
switch (dataType) {
//Long 类型解析
case LONG_TYPE:
/**
* 16进制的字符串转换 Long 型
* Long.parseLong( "01", 16 ) = 1L
* Long.parseLong( "0101", 16 ) = 257L
* Long.parseLong( "010101", 16 ) = 65793L
*/
result = Long.parseLong( BytesUtils.bytesToHexString( rawData ), 16 );//支持 1-8 个字节
break;
//Float 类型解析
case FLOAT_TYPE:
result = BytesUtils.bytesToFloat( rawData );//eg: "1.23"
break;
//Double 类型解析
case DOUBLE_TYPE:
result = BytesUtils.bytesToDouble( rawData );
break;
//String类型解析
case STRING_TYPE:
//builder.append(hexStringToString(hexString));
//builder.append( BytesUtils.hexStringToTextString(hexString, charset) );
result = BytesUtils.bytesToTextString( rawData, charset );
break;
//IntArray 类型解析
//case INT_ARRAY_TYPE:
//s.append( BytesUtils.hexStringToIntegerArrayString( s1 , BytesUtils.INTEGER_ARRAY_STRING_SEPARATOR ) );
//break;
//原始类型,不需要解析
case RAW_TYPE:
//builder.append(hexString);
//String rawDataHex = BytesUtils.bytesToHexString(rawData);
result = rawData;
break;
default:
break;
}
} catch (Exception exception) {
String rawDataHex = BytesUtils.bytesToHexString(rawData);
errorMessage = String.format("Fail to convert the raw data(hex:%s) to data type(%s)!", rawDataHex, dataType);
log.error(errorMessage + ", exception:", exception);
throw new RuntimeException(errorMessage, exception);
}
if(log.isDebugEnabled()){
String rawDataHex = BytesUtils.bytesToHexString(rawData);
log.debug("Convert the raw data (hex:{}) to data type({}) success!result:{}", rawDataHex, dataType, result);
}
return result;
}
/**
* 从指定的位置读取最多 length 个字节数据,并存放到 targetBuffer 中
* @param destinationBuffer 目标字节数组
* @param destinationBufferOffset 目标字节数组的起始位置 (容易理解错误,多注意)
* @param length 要读取的字节数
* @return
*/
public int read(byte destinationBuffer[], int destinationBufferOffset, int length){
return this.byteArrayInputStream.readNBytes(destinationBuffer, destinationBufferOffset, length);
}
public int read(byte destinationBuffer[], int length){
int destinationBufferOffset = 0;
return this.byteArrayInputStream.readNBytes(destinationBuffer, destinationBufferOffset, length);
}
public int read(byte destinationBuffer[]){
int destinationBufferOffset = 0;
int destinationBufferLength = destinationBuffer.length;
return this.byteArrayInputStream.readNBytes(destinationBuffer, destinationBufferOffset, destinationBufferLength);
}
public long skip(int length){
return byteArrayInputStream.skip(length);
}
/**
* 获取下一字节的位置
* @description
* 1. 利用反射原理,将 java.io.ByteArrayInputStream 的 private 属性 pos 读取出来
* 2. 不建议高频调用 (影响调用程序的性能)
* @return
*/
@SneakyThrows
@Override
public int next(){
int next = Integer.MIN_VALUE; //读取失败时,以此值为标志
//反射方法1 : Java 17 中需结合 VM Option 参数 : `--add-opens java.base/java.io=ALL-UNNAMED`
//java.lang.reflect.Field field = java.io.ByteArrayInputStream.class.getDeclaredField("pos");
//field.setAccessible(true);
//next = field.getInt( this );//读取 next 的值
////field.set(this, Integer.MIN_VALUE);//设置字段的值
//反射方法2: 基于 Unsafe
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
// 获取私有字段的偏移量
Field nextField = ByteArrayInputStream.class.getDeclaredField("pos");
long offset = unsafe.objectFieldOffset(nextField);
next = unsafe.getInt(this.byteArrayInputStream, offset);
//unsafe.putInt(byteArrayInputStream, offset, 10);// 设置字段值
return next;//next : 下标从 0 开始; 即将读取的下一个 char 的下标位置
}
@Override
public Boolean hasNext(){
return byteArrayInputStream.available() > 0;
}
@Override
public byte [] getSource(){
return this.source;
}
@Override
public Charset getCharset() {
return this.charset;
}
@Override
public void setCharset(Charset charset) {
this.charset = charset;
}
public int getInitOffset() {
return initOffset;
}
public int getInitLength() {
return initLength;
}
@Override
public byte[] getLatestReadData() {
return latestReadData;
}
}
Demo
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.Map;
@Slf4j
public class BytesReaderTest {
/**
* 读取字节数据
*/
@Test
public void readBytesTest(){
byte bytes [] = new byte [] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
BytesReader bytesReader = new BytesReader(bytes);
int offset = 0;
while (bytesReader.hasNext()) {
try {
offset = bytesReader.next();
//log.info("bytes[{}] : 0x{}", offset, BytesUtils.byteToHexString( bytesReader.readByte() ) );//每次读取1个字节
int readSize = 3;//每次读取的字节数
Map.Entry<Integer, byte[]> readResult = bytesReader.readBytes(readSize);
int actualLength = readResult.getKey();
byte [] readBytes = readResult.getValue();
log.info("bytes[offset={}] : 0x{}, actualLength:{}", offset, BytesUtils.bytesToHexString( readBytes ), actualLength );//eg: "bytes[offset=0] : 0x010203, actualLength:3" , "bytes[offset=15] : 0x160000, actualLength:1"
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test
public void parseBytesTest() throws IOException {
byte [] bytes = new byte [] { (byte) 0xef, (byte) 0xbb, (byte) 0xbf, 0x31, 0x00 };
String rawDataHex = BytesUtils.bytesToHexString( bytes );
BytesReader bytesReader = new BytesReader(bytes);
Charset charset = Charset.forName("UTF-8");
Object parseResult = bytesReader.parseBytes( bytes.length, DataTypeEnum.STRING_TYPE, charset, "" );
log.info("rawDataHex:{}, parseResult : {}", rawDataHex, parseResult);//"rawDataHex:efbbbf3100, parseResult : 1"
}
}
out : readBytesTest
[2025/06/26 14:12:54.921] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=0] : 0x010203, actualLength:3
[2025/06/26 14:12:54.927] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=3] : 0x040506, actualLength:3
[2025/06/26 14:12:54.928] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=6] : 0x070809, actualLength:3
[2025/06/26 14:12:54.929] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=9] : 0x101112, actualLength:3
[2025/06/26 14:12:54.929] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=12] : 0x131415, actualLength:3
[2025/06/26 14:12:54.930] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=15] : 0x160000, actualLength:1
Y 推荐文献
- [Java/HexStringReader] 核心源码精讲:java.io.StringReader类(JDK1.1-) - 博客园/千千寰宇
- [Java SE] 基础工具类:ByteUtils(字节操作) - 博客园/千千寰宇
X 参考文献
本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号