[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

序号方法描述
1public int read() 从此输入流中读取下一个数据字节。
2public int read(byte[] r, int off, int len) 将最多 len 个数据字节从此输入流读入字节数组。
3public int available() 返回可不发生阻塞地从此输入流读取的字节数。
4public void mark(int read) 设置流中的当前标记位置。
5public 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

BytesReader

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import sun.misc.Unsafe;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Map;

/**
 * 以字节数据为源的读取器
 * @note 核心原理: 基于字节流工具类 {@link ByteArrayInputStream } 读取字节数据
 * @updateTime 2025.6.26 13:50
 */
@Slf4j
public class BytesReader {
    private ByteArrayInputStream byteArrayInputStream;

    public BytesReader(byte [] bytes) {
        this.byteArrayInputStream = new ByteArrayInputStream(bytes);
    }

    public BytesReader(byte [] bytes, int offset, int length) {
        this.byteArrayInputStream = new ByteArrayInputStream(bytes, offset, 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;
    }

    /**
     * 从指定的位置读取最多 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
    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 的下标位置
    }

    public Boolean hasNext(){
        return byteArrayInputStream.available() > 0;
    }
}

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();
            }
        }
    }
}

out

[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 推荐文献

X 参考文献

原创作者: johnnyzen 转载于: https://www.cnblogs.com/johnnyzen/p/18949896
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值