Eugene的答案解释了为什么你观察到大量数组的内存消耗如此增加的原因。标题中的问题“如何在Java中有效地存储小字节数组?”,可以回答为:完全没有。1 个
但是,可能有一些方法可以实现您的目标。像往常一样,这里的“最佳”解决方案将取决于如何使用这些数据。一个非常务实的方法是:为数据结构定义一个。interface
在最简单的情况下,这个接口可能只是
interface ByteArray2D
{
int getNumRows();
int getNumColumns();
byte get(int r, int c);
void set(int r, int c, byte b);
}
提供“2D 字节数组”的基本抽象。根据应用情况,在此处提供其他方法可能会有所帮助。这里可以采用的模式通常与矩阵库相关,矩阵库处理“2D矩阵”(通常是值),并且它们通常提供如下方法:float
interface Matrix {
Vector getRow(int row);
Vector getColumn(int column);
...
}
但是,当这里的主要目的是处理一组数组时,访问每个数组(即2D数组的每一行)的方法可能就足够了:byte[]
ByteBuffer getRow(int row);
给定此接口,创建不同的实现非常简单。例如,您可以创建一个简单的实现,该实现仅在内部存储 2D 数组:byte[][]
class SimpleByteArray2D implements ByteArray2D
{
private final byte array[][];
...
}
或者,您可以创建一个实现来存储一维数组,或者类似地,在内部存储:byte[]
ByteBuffer
class CompactByteArray2D implements ByteArray2D
{
private final ByteBuffer buffer;
...
}
然后,此实现只需在调用用于访问 2D 数组的某个行/列的方法之一时计算 (1D) 索引。
下面您将找到一个MCVE,其中显示了此接口和两个实现,接口的基本用法,并使用JOL进行内存占用分析。
该程序的输出是:
For 10 rows and 1000 columns:
Total size for SimpleByteArray2D : 10240
Total size for CompactByteArray2D: 10088
For 100 rows and 100 columns:
Total size for SimpleByteArray2D : 12440
Total size for CompactByteArray2D: 10088
For 1000 rows and 10 columns:
Total size for SimpleByteArray2D : 36040
Total size for CompactByteArray2D: 10088
表明
整个程序:
package stackoverflow;
import java.nio.ByteBuffer;
import org.openjdk.jol.info.GraphLayout;
public class EfficientByteArrayStorage
{
public static void main(String[] args)
{
showExampleUsage();
anaylyzeMemoryFootprint();
}
private static void anaylyzeMemoryFootprint()
{
testMemoryFootprint(10, 1000);
testMemoryFootprint(100, 100);
testMemoryFootprint(1000, 10);
}
private static void testMemoryFootprint(int rows, int cols)
{
System.out.println("For " + rows + " rows and " + cols + " columns:");
ByteArray2D b0 = new SimpleByteArray2D(rows, cols);
GraphLayout g0 = GraphLayout.parseInstance(b0);
System.out.println("Total size for SimpleByteArray2D : " + g0.totalSize());
//System.out.println(g0.toFootprint());
ByteArray2D b1 = new CompactByteArray2D(rows, cols);
GraphLayout g1 = GraphLayout.parseInstance(b1);
System.out.println("Total size for CompactByteArray2D: " + g1.totalSize());
//System.out.println(g1.toFootprint());
}
// Shows an example of how to use the different implementations
private static void showExampleUsage()
{
System.out.println("Using a SimpleByteArray2D");
ByteArray2D b0 = new SimpleByteArray2D(10, 10);
exampleUsage(b0);
System.out.println("Using a CompactByteArray2D");
ByteArray2D b1 = new CompactByteArray2D(10, 10);
exampleUsage(b1);
}
private static void exampleUsage(ByteArray2D byteArray2D)
{
// Reading elements of the array
System.out.println(byteArray2D.get(2, 4));
// Writing elements of the array
byteArray2D.set(2, 4, (byte)123);
System.out.println(byteArray2D.get(2, 4));
// Bulk access to rows
ByteBuffer row = byteArray2D.getRow(2);
for (int c = 0; c < row.capacity(); c++)
{
System.out.println(row.get(c));
}
// (Commented out for this MCVE: Writing one row to a file)
/*/
try (FileChannel fileChannel =
new FileOutputStream(new File("example.dat")).getChannel())
{
fileChannel.write(byteArray2D.getRow(2));
}
catch (IOException e)
{
e.printStackTrace();
}
//*/
}
}
interface ByteArray2D
{
int getNumRows();
int getNumColumns();
byte get(int r, int c);
void set(int r, int c, byte b);
// Bulk access to rows, for convenience and efficiency
ByteBuffer getRow(int row);
}
class SimpleByteArray2D implements ByteArray2D
{
private final int rows;
private final int cols;
private final byte array[][];
public SimpleByteArray2D(int rows, int cols)
{
this.rows = rows;
this.cols = cols;
this.array = new byte[rows][cols];
}
@Override
public int getNumRows()
{
return rows;
}
@Override
public int getNumColumns()
{
return cols;
}
@Override
public byte get(int r, int c)
{
return array[r][c];
}
@Override
public void set(int r, int c, byte b)
{
array[r][c] = b;
}
@Override
public ByteBuffer getRow(int row)
{
return ByteBuffer.wrap(array[row]);
}
}
class CompactByteArray2D implements ByteArray2D
{
private final int rows;
private final int cols;
private final ByteBuffer buffer;
public CompactByteArray2D(int rows, int cols)
{
this.rows = rows;
this.cols = cols;
this.buffer = ByteBuffer.allocate(rows * cols);
}
@Override
public int getNumRows()
{
return rows;
}
@Override
public int getNumColumns()
{
return cols;
}
@Override
public byte get(int r, int c)
{
return buffer.get(r * cols + c);
}
@Override
public void set(int r, int c, byte b)
{
buffer.put(r * cols + c, b);
}
@Override
public ByteBuffer getRow(int row)
{
ByteBuffer r = buffer.slice();
r.position(row * cols);
r.limit(row * cols + cols);
return r.slice();
}
}
同样,这主要是作为草图,以显示一种可能的方法。接口的详细信息将取决于预期的应用程序模式。
1 附注:
内存开销的问题在其他语言中是类似的。例如,在 C/C++ 中,与“2D Java 数组”最相似的结构是手动分配的指针数组:
char** array;
array = new (char*)[numRows];
array[0] = new char[numCols];
...
在这种情况下,您还有一个与行数成比例的开销 - 即每行一个(通常为 4 字节)指针。