Java ByteArrayInputStream 原理用法源码分析
应用场景和存在意义
ByteArrayInputStream
是 Java 中的一个输入流类,它将字节数组作为数据源。ByteArrayInputStream
的存在意义在于提供了一种将字节数组转换为输入流的方式,以便于在程序中对字节数组进行读取操作。
以下是 ByteArrayInputStream
的一些应用场景和存在意义:
-
内存中读取数据:
ByteArrayInputStream
允许从内存中的字节数组读取数据,而无需借助磁盘或网络等外部存储设备。这在某些情况下可以提高读取速度和效率,特别是当数据已经存在于内存中时。 -
方便数据传输: 使用
ByteArrayInputStream
,可以方便地将字节数组传递给需要输入流的方法或组件,而无需将字节数组写入磁盘或进行网络传输。这样可以简化代码,并避免不必要的数据复制和存储开销。 -
测试和调试: 在测试和调试过程中,可以使用
ByteArrayInputStream
来模拟输入流的行为,以便更容易对代码进行单元测试和调试。可以使用预定义的字节数组作为输入数据,并通过ByteArrayInputStream
提供给待测试的方法。 -
数据解析和处理: 有时,需要对二进制数据进行解析和处理。使用
ByteArrayInputStream
可以将字节数组转换为输入流,然后使用相应的读取方法从中读取数据。这对于处理二进制协议、解析图像或音频数据等场景非常有用。
总之,ByteArrayInputStream
的存在意义在于提供了一种将字节数组转换为输入流的方式,方便在程序中对字节数组进行读取操作。它适用于内存中读取数据、方便数据传输、测试和调试以及数据解析和处理等场景。
原理
ByteArrayInputStream
是 Java 中的一个输入流实现类,它继承自 InputStream
。它的原理如下:
-
内部缓冲区:
-
ByteArrayInputStream
使用一个字节数组作为内部缓冲区来存储数据。 - 在创建
ByteArrayInputStream
对象时,需要将字节数组作为参数传递给它,并指定起始位置和长度。
-
-
读取操作:
- 当调用
read()
方法时,ByteArrayInputStream
会从内部缓冲区中逐个字节地读取数据,并返回下一个可用的字节数据。 - 如果已经读取到了数组末尾,即所有字节都已被读取,
read()
方法将返回 -1,表示已达到流的末尾。
- 当调用
-
指针位置管理:
-
ByteArrayInputStream
使用一个指针来记录当前读取位置。 - 在初始状态下,指针位于起始位置,通过每次读取一个字节后,指针向前移动一个位置。
- 通过
mark()
和reset()
方法,可以在某个位置设置标记并恢复到该位置,以支持重复读取或跳过部分数据。
-
-
关闭操作:
- 当不再需要使用
ByteArrayInputStream
对象时,应该调用close()
方法关闭流释放资源。
- 当不再需要使用
通过以上原理,ByteArrayInputStream
提供了从字节数组中读取数据的功能,方便快捷地读取字节数组中的数据。需要注意的是,在使用完毕后,应及时关闭流以释放资源。
用法示例
以下是 ByteArrayInputStream
在不同场景下的完整代码示例:
1. 内存中读取数据:
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class ByteArrayInputStreamExample {
public static void main(String[] args) {
byte[] data = { 65, 66, 67, 68, 69 }; // 字节数组
try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
int byteRead;
while ((byteRead = bais.read()) != -1) {
System.out.print((char) byteRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个示例创建了一个字节数组 data
,然后使用 ByteArrayInputStream
将其作为输入流进行读取。通过循环读取每个字节,并将其转换为字符输出到控制台。
2. 方便数据传输:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayTransferExample {
public static void main(String[] args) {
byte[] sourceData = "Hello, World!".getBytes(); // 源字节数组
// 将源字节数组写入 ByteArrayOutputStream
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
baos.write(sourceData);
// 从 ByteArrayOutputStream 读取数据并存储到目标字节数组
byte[] targetData = baos.toByteArray();
// 使用目标字节数组创建 ByteArrayInputStream
try (ByteArrayInputStream bais = new ByteArrayInputStream(targetData)) {
int byteRead;
while ((byteRead = bais.read()) != -1) {
System.out.print((char) byteRead);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个示例将源字节数组写入 ByteArrayOutputStream
,然后从中读取数据并存储到目标字节数组。最后,使用目标字节数组创建 ByteArrayInputStream
并进行读取操作。
3. 测试和调试:
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class ByteArrayTestingExample {
public static void processData(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
int byteRead;
while ((byteRead = bais.read()) != -1) {
System.out.println("Processing byte: " + byteRead);
// 在此处进行处理逻辑...
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
byte[] testData = { 10, 20, 30, 40, 50 }; // 测试数据
processData(testData);
}
}
在这个示例中,processData()
方法接收一个字节数组作为参数,并使用 ByteArrayInputStream
进行读取操作。可以在处理逻辑中对每个字节进行进一步的处理或分析。在 main()
方法中,通过传递测试数据调用 processData()
方法进行测试和调试。
4. 数据解析和处理:
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class DataParsingExample {
public static void parseData(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
byte[] buffer = new byte[4];
int bytesRead;
while ((bytesRead = bais.read(buffer)) != -1) {
// 对读取到的数据进行解析和处理
System.out.println("Parsed data: " + new String(buffer, 0, bytesRead));
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
byte[] binaryData = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 }; // 二进制数据
parseData(binaryData);
}
}
在这个示例中,parseData()
方法接收一个二进制数据的字节数组作为参数,并使用 ByteArrayInputStream
进行分块读取操作。可以根据实际需求对每个读取到的数据块进行解析和处理。
以上是针对不同场景下 ByteArrayInputStream
的完整代码示例,用于展示如何在Java程序中使用 ByteArrayInputStream
进行内存中数据读取、方便数据传输、测试和调试、以及数据解析和处理。
方法总结
ByteArrayInputStream
是 Java I/O 库中的一个类,它继承自 InputStream
抽象类。该类实现了一个字节数组输入流,即将一个字节数组作为输入源,并提供了相应的读取方法。
该类的主要特点和方法如下:
- 内部缓冲区:
ByteArrayInputStream
包含一个内部的字节数组缓冲区buf
,通过该缓冲区存储待读取的字节数据。 - 索引指针:
pos
表示当前待读取的字节在缓冲区中的位置。 - 标记:
mark
表示当前标记的位置,用于支持reset()
方法。 - 字节总数:
count
表示缓冲区中有效的字节总数。
主要方法如下:
-
read()
:从输入流中读取下一个字节的数据。返回值是一个范围在 0 到 255 之间的整数,如果已达到流的末尾,则返回 -1。 -
read(byte[] b, int off, int len)
:从输入流中最多一次性读取len
个字节,并将其写入到字节数组b
中。返回实际读取的字节数,如果已达到流的末尾,则返回 -1。 -
skip(long n)
:跳过输入流中的n
个字节。返回实际跳过的字节数。 -
available()
:返回当前可从输入流中读取的字节数。 -
mark(int readAheadLimit)
:将当前位置设置为标记位置,并指定在标记失效之前可以读取的最大字节数。 -
reset()
:将流重置到最后一次调用 mark 方法时的位置。 -
markSupported()
:测试输入流是否支持 mark 和 reset 操作。
此外,ByteArrayInputStream
还提供了构造函数用于初始化内部缓冲区和索引指针。在使用完毕后,调用 close()
方法可以关闭流,但实际上对于 ByteArrayInputStream
来说,关闭流没有任何效果。
总体而言,ByteArrayInputStream
提供了一种方便的方式来将字节数组作为输入源进行读取操作,它是一个简单、轻量级的输入流实现。
BufferedInputStream比较
ByteArrayInputStream
和 BufferedInputStream
是 Java 中两种不同的输入流实现类,它们有以下区别:
-
数据来源:
-
ByteArrayInputStream
:从字节数组中读取数据。 -
BufferedInputStream
:可以包装其他输入流,提供缓冲功能,从底层输入流中读取数据。
-
-
缓冲功能:
-
ByteArrayInputStream
:没有缓冲功能,直接从字节数组中逐个字节地读取数据。 -
BufferedInputStream
:提供了缓冲功能,使用内部缓冲区存储从底层输入流读取的数据,减少实际的 I/O 操作次数。
-
-
读取方式:
-
ByteArrayInputStream
:支持逐个字节或批量读取字节数组。 -
BufferedInputStream
:支持逐个字节或批量读取字节数组。
-
-
性能影响:
-
ByteArrayInputStream
:由于直接读取字节数组,无需进行额外的 I/O 操作,所以性能较高。 -
BufferedInputStream
:通过使用内部缓冲区减少实际的 I/O 操作次数,可以提高读取性能。
-
根据需要选择适合的输入流类。如果已经有一个字节数组并且需要从中读取数据,可以选择使用 ByteArrayInputStream
。如果需要对其他输入流进行缓冲以提高读取性能,可以选择使用 BufferedInputStream
。
中文源码
/**
* ByteArrayInputStream 是一个包含内部缓冲区的输入流,缓冲区中包含可以从流中读取的字节。
* 一个内部计数器跟踪下一个由 read 方法提供的字节。
* <p>
* 关闭 ByteArrayInputStream 没有任何效果。在流关闭后,仍然可以调用该类的方法而不会生成 IOException。
*
* @author Arthur van Hoff
* @see
* @since JDK1.0
*/
public class ByteArrayInputStream extends InputStream {
/**
* 由流的创建者提供的字节数组。buf[0] 到 buf[count-1] 是唯一可从流中读取的字节;
* buf[pos] 是将要读取的下一个字节。
*/
protected byte buf[];
/**
* 从输入流缓冲区读取的下一个字符的索引。
* 此值应始终为非负数且不大于 count 的值。
* 从输入流缓冲区读取的下一个字节是 buf[pos]。
*/
protected int pos;
/**
* 流中当前标记的位置。
* ByteArrayInputStream 对象在构造时默认标记为零位置。
* 它们可以通过 mark() 方法在缓冲区中的另一个位置进行标记。
* reset() 方法将当前缓冲区位置设置为此点。
* <p>
* 如果没有设置标记,则 mark 的值是构造函数传递的偏移量(如果未提供偏移量,则为 0)。
*
* @since JDK1.1
*/
protected int mark = 0;
/**
* 输入流缓冲区中最后一个有效字符的索引加一。
* 此值应始终为非负数且不大于 buf 的长度。
* 它是可从输入流缓冲区中读取的最后一个字节之后的位置。
*/
protected int count;
/**
* 创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
* 缓冲区数组不会被复制。
* pos 的初始值为 0,count 的初始值为 buf 的长度。
*
* @param buf 输入缓冲区。
*/
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
/**
* 创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
* pos 的初始值为 offset,count 的初始值为 offset+length 和 的较小值。
* 缓冲区数组不会被复制。缓冲区的标记设置为指定的偏移量。
*
* @param buf 输入缓冲区。
* @param offset 要读取的第一个字节在缓冲区中的偏移量。
* @param length 从缓冲区中读取的最大字节数。
*/
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
/**
* 从输入流中读取下一个字节的数据。将字节的值作为范围在 0 到 255 之间的 int 值返回。
* 如果没有可用的字节(因为已达到流的末尾),则返回 -1。
* <p>
* 此 read 方法不会阻塞。
*
* @return 下一个字节的数据,如果已达到流的末尾,则返回 -1。
*/
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
/**
* 从输入流中最多一次性读取 len 个字节,并将其读入字节数组 b 中。
* 如果 pos 等于 count,则返回 -1,表示已达到文件末尾。
* 否则,读取的字节数 k 等于 len 和 count-pos 的较小值。
* 如果 k 是正数,则将字节 buf[pos] 到 buf[pos+k-1] 复制到 b[off] 到 b[off+k-1],
* 这相当于使用 执行的操作。
* 将 k 添加到 pos,并返回 k。
* <p>
* 此 read 方法不会阻塞。
*
* @param b 存储读取数据的缓冲区。
* @param off 目标数组中的起始偏移量。
* @param len 最多读取的字节数。
* @return 实际读取到缓冲区中的总字节数,如果没有更多数据则返回 -1。
* @throws NullPointerException 如果 b 为 null。
* @throws IndexOutOfBoundsException 如果 off 为负数,len 为负数,或 len 大于 - off。
*/
public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
if (pos >= count) {
return -1;
}
int avail = count - pos;
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}
/**
* 跳过输入流中的 n 个字节。如果到达了输入流的末尾,则跳过的字节数可能较少。
* 实际跳过的字节数 k 等于 n 和 count-pos 的较小值。
* 将 k 添加到 pos,并返回 k。
*
* @param n 要跳过的字节数。
* @return 实际跳过的字节数。
*/
public synchronized long skip(long n) {
long k = count - pos;
if (n < k) {
k = n < 0 ? 0 : n;
}
pos += k;
return k;
}
/**
* 返回可以从输入流中读取(或跳过)的剩余字节数。
* <p>
* 返回的值是 count - pos,即从输入缓冲区中读取而不阻塞的剩余字节数。
*
* @return 可从输入流中读取(或跳过)的剩余字节数。
*/
public synchronized int available() {
return count - pos;
}
/**
* 测试此 InputStream 是否支持 mark/reset 操作。
* ByteArrayInputStream 的 markSupported 方法始终返回 true。
*
* @since JDK1.1
*/
public boolean markSupported() {
return true;
}
/**
* 设置流的当前标记位置。
* ByteArrayInputStream 对象在构造时默认标记为零位置。
* 它们可以通过此方法在缓冲区的其他位置进行标记。
* <p>
* 如果没有设置标记,则 mark 的值是构造函数传递的偏移量(如果未提供偏移量,则为 0)。
*
* @since JDK1.1
*/
public void mark(int readAheadLimit) {
mark = pos;
}
/**
* 将缓冲区重置为标记位置。除非另一个位置被标记或者在构造函数中指定了偏移量,否则标记位置为 0。
*/
public synchronized void reset() {
pos = mark;
}
/**
* 关闭 ByteArrayInputStream 没有任何效果。
* 在流关闭后,仍然可以调用该类的方法而不会生成 IOException。
*/
public void close() throws IOException {
}
}