[Java/字节流/BytesReader] 核心源码精讲: ByteArrayInputStream(字节数组输入流)
概述 : ByteArrayInputStream(字节数组输入流)
简介
- 字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。
java.io.ByteArrayInputStream
创建对象的方式
创建字节数组输入流对象有以下几种方式。
- 方式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
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 implements MySourceReader {
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{}", offset, BytesUtils.bytesToHexString( readBytes ) );//eg: "bytes[offset=0] : 0x010203"
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
out
[2025/06/26 14:08:25.668] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=0] : 0x010203
[2025/06/26 14:08:25.676] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=3] : 0x040506
[2025/06/26 14:08:25.677] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=6] : 0x070809
[2025/06/26 14:08:25.678] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=9] : 0x101112
[2025/06/26 14:08:25.679] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=12] : 0x131415
[2025/06/26 14:08:25.680] [INFO ] [main] [com.xxx.sdk.utils.bytes.BytesReaderTest :27 readBytesTest] bytes[offset=15] : 0x160000
Y 推荐文献
- [Java/HexStringReader] 核心源码精讲:java.io.StringReader类(JDK1.1-) - 博客园/千千寰宇
- [Java SE] 基础工具类:ByteUtils(字节操作) - 博客园/千千寰宇
X 参考文献
[Java/字节流/BytesReader] 核心源码精讲: ByteArrayInputStream(字节数组输入流)的更多相关文章
- java中ReentrantLock核心源码详解
ReentrantLock简介 ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活. ...
- 3 手写Java HashMap核心源码
手写Java HashMap核心源码 上一章手写LinkedList核心源码,本章我们来手写Java HashMap的核心源码. 我们来先了解一下HashMap的原理.HashMap 字面意思 has ...
- 手写 Java HashMap 核心源码
手写 Java HashMap 核心源码 手写 Java HashMap 核心源码 上一章手写 LinkedList 核心源码,本章我们来手写 Java HashMap 的核心源码. 我们来先了解一下 ...
- Java内存管理-掌握类加载器的核心源码和设计模式(六)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇文章介绍了类加载器分类以及类加载器的双亲委派模型,让我们能够从整体上对类加载器有 ...
- 6 手写Java LinkedHashMap 核心源码
概述 LinkedHashMap是Java中常用的数据结构之一,安卓中的LruCache缓存,底层使用的就是LinkedHashMap,LRU(Least Recently Used)算法,即最近最少 ...
- 2 手写Java LinkedList核心源码
上一章我们手写了ArrayList的核心源码,ArrayList底层是用了一个数组来保存数据,数组保存数据的优点就是查找效率高,但是删除效率特别低,最坏的情况下需要移动所有的元素.在查找需求比较重要的 ...
- 1 手写Java ArrayList核心源码
手写ArrayList核心源码 ArrayList是Java中常用的数据结构,不光有ArrayList,还有LinkedList,HashMap,LinkedHashMap,HashSet,Queue ...
- Android版数据结构与算法(五):LinkedHashMap核心源码彻底分析
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 上一篇基于哈希表实现HashMap核心源码彻底分析 分析了HashMap的源码,主要分析了扩容机制,如果感兴趣的可以去看看,扩容机制那几行最难懂的 ...
- 并发编程之 SynchronousQueue 核心源码分析
前言 SynchronousQueue 是一个普通用户不怎么常用的队列,通常在创建无界线程池(Executors.newCachedThreadPool())的时候使用,也就是那个非常危险的线程池 ^ ...
- HashMap的结构以及核心源码分析
摘要 对于Java开发人员来说,能够熟练地掌握java的集合类是必须的,本节想要跟大家共同学习一下JDK1.8中HashMap的底层实现与源码分析.HashMap是开发中使用频率最高的用于映射(键值对 ...
随机推荐
- 一文彻底拿下HarmonyOS NEXT开发实战调试技巧
> 程序员Feri一名12年+的程序员,做过开发带过团队创过业,擅长Java.嵌入式.鸿蒙.人工智能等,专注于程序员成长那点儿事,希望在成长的路上有你相伴!君志所向,一往无前! --- # 1. ...
- 图像处理中的 Gaussina Blur 和 SIFT 算法
Gaussina Blur 高斯模糊 高斯模糊的数学定义 高斯模糊是通过 高斯核(Gaussian Kernel) 对图像进行卷积操作实现的. 二维高斯函数定义为 \[G(x, y, \sigma) ...
- Unity资源打包之Asset Bundle
Asset Bundle的作用: 1.AssetBundle是一个压缩包包含模型.贴图.预制体.声音.甚至整个场景,可以在游戏运行的时候被加载: 2.AssetBundle自身保存着互相的依赖关系: ...
- Sa-Token v1.42.0 发布 🚀,新增 API Key、TOTP 验证码、RefreshToken 反查等能力
Sa-Token 是一款 免费.开源 的轻量级 Java 权限认证框架,主要解决:登录认证.权限认证.单点登录.OAuth2.0.微服务网关鉴权 等一系列权限相关问题. 目前最新版本 v1.42.0 ...
- Mybatis的关联关系的配置
例子1. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC &qu ...
- DeepSeekV3:写代码很强了
以前,我是不信AI能最先替代程序员的. DeepSeek的热度虽然在降,但是能力在悄摸的迭代. 在今年2月中旬测试DeepSeekR1的时候,虽然被它的文本处理能力惊艳到,但是当时吐槽过几句它的编程水 ...
- 没错,Go 语言的函数参数没有引用传递方式
这篇文章想浅浅地讲解 Go 语言函数参数传递的值拷贝. 一句话观点 Go语言中所有传递都是值传递,严格来说并不存在引用传递的概念.传递指针只是传递指针的值,并不是引用传递,只不过通过指针可以间接修改变 ...
- 前端传字符串,需要转List对象
前端传字符串,需要转List对象 import com.alibaba.fastjson.JSONObject; List<LogySbjsJdsbqxxxAccount> param = ...
- 【经验】博客|Windows下,一键安装和部署 hexo-admin 插件(Hexo 静态博客)
1. 在博客根目录下运行下列指令 npm install --save hexo-admin -y echo "hexo clean && hexo g -d"&g ...
- 【记录】Python3|用百度语音 API 朗读你的小说TXT
百度语音合成官方教程_AI开放平台 百度语音合成官方demo_github.com 简单地写了一个按段落朗读文本的demo:DEMO链接_gitee.com. 有时候会请求不到数据,不知道是网络原因还 ...