默认的ByteArrayOutputStream似乎是一个相当浪费的实现,我想知道是否有任何具体原因。首先,它在后端保留1个固定阵列。如果它已满,它会创建一个新数组并将旧数组复制到其中(更多内存+更多开销)。然后,如果你执行toByteArray(),它实际上会再次复制数组。
字节缓冲区很好但是也固定了大小,它们只在单个阵列上提供了一些,仅此而已。
我想知道创建一个使用一个或多个支持数组的类(或者它是否已存在,请指向我)是否有趣。它不是每次都要重复扩展数组,而是添加一个新的后备数组。要阅读您可以轻松创建一个类似输入流的界面,同时您可以公开像输出流这样的界面来编写
关于这样的事情是否已经存在的任何反馈,如果没有:为什么?它有一些我不会看到的缺点吗?
答案 0 :(得分:2)
这实际上是一个好主意,特别是对于大数据。
在堆上分配大型数组时,您可以快速遇到内存问题,因为它们需要分配连续的可用内存。当我们经常分配大小为10-50MB的字节数组并且遇到OutOfMemoryException
时,我们曾经遇到过这样的情况,不是因为可用的内存太少(我们通常有90%,或900MB免费),但是因为由于堆碎片,没有一个连续的内存块可用于此阵列。
我们最终创建了一个Blob
类,它在内部将数据存储为链式(List)较小的数组的块。这些数组具有固定的大小(对于快速查找至关重要,因此您可以快速计算给定索引所涉及的数组和偏移量),并为此Blob创建InputStream
和OutputStream
类。后来我们将它扩展为可以从磁盘上交换。
我只能鼓励你试一试!
答案 1 :(得分:0)
C ++标准库既有一个向量类(如Java ArrayList),也有一个deque类(另一个像类一样的List)。后者提供有效的前置和有效的附加。我见过的实现维护了固定长度数组的列表。所以,有点像你感兴趣的情况。所以,这当然是可能的。
缺点是代码的复杂性大大增加。我想可以改变JRE中的实现来做你所建议的,使用toByteArray方法从片段中收集数据。但是这样做的优先级非常低,因为简单的实现速度非常快。执行IO的任何代码都应该假设读取和写入操作很慢,这可能会阻塞。 ByteArrayOutputStream非常快,因为它在内存操作中执行而不是在真正的外部IO中执行。复制这些字节数组可能比外部IO快得多。当前实现的缺点是在用于大输出流时创建大型垃圾数组。但是这个类的用例是针对小流;如果要临时存储大输出流的字节,则应使用临时文件。因此,提案的复杂性可能在实践中没有多大帮助
答案 2 :(得分:0)
看起来你已经知道了好处。与单个缓冲区相比,缓冲区列表的缺点包括:
如果对您的应用程序有意义,您可以自由编写这样的数据结构
答案 3 :(得分:0)
由于似乎没有真正的实现,我已经快速编写了一个初始实现来测试速度:
public class Buffer {
private int size;
private int writeIndex, writeOffset,
readIndex, readOffset;
private List<byte[]> backingArrays = new ArrayList<byte[]>();
public Buffer() {
this(10240);
}
public Buffer(int size) {
this.size = size;
}
public int read(byte [] bytes) {
return read(bytes, 0, bytes.length);
}
public int read(byte [] bytes, int offset, int length) {
int read = 0;
while(length > 0) {
byte [] input = getInput();
// no more data
if (input == null) {
if (read == 0)
return -1;
else
return read;
}
int readLength = Math.min(length, (readIndex == writeIndex ? writeOffset : size) - readOffset);
System.arraycopy(input, readOffset, bytes, offset, readLength);
length -= readLength;
offset += readLength;
readOffset += readLength;
read += readLength;
}
return read;
}
public void write(byte [] bytes) {
write(bytes, 0, bytes.length);
}
public void write(byte [] bytes, int offset, int length) {
while (length > 0) {
byte [] output = getOutput();
int writeLength = Math.min(length, output.length - writeOffset);
System.arraycopy(bytes, offset, output, writeOffset, writeLength);
length -= writeLength;
offset += writeLength;
writeOffset += writeLength;
}
}
private byte[] getOutput() {
// if we have filled an array, move to the next one
if (writeOffset >= size) {
writeIndex++;
writeOffset = 0;
}
// create it if it doesn't exist yet
if (backingArrays.size() <= writeIndex)
backingArrays.add(new byte[size]);
return backingArrays.get(writeIndex);
}
private byte [] getInput() {
// nothing written yet
if (backingArrays.size() == 0)
return null;
if (readOffset >= size) {
readIndex++;
readOffset = 0;
}
// can not read past where it is written
if (readIndex > writeIndex || (readIndex == writeIndex && readOffset >= writeOffset))
return null;
else
return backingArrays.get(readIndex);
}
public long size() {
return (long) size * (long) writeIndex + writeOffset;
}
}
我正在通过复制36 meg文件来测试它。很多当然取决于文件交互,但一般来说,读取速度比bytearrayinputstream略快快40%(徘徊在5-20%左右)
我很快把它放在一起,所以如果你发现任何错误,请告诉我。
修改强>:
添加了一项功能,默认情况下,为gc
发布已读取的数组