我们有这样一个应用:维护一个二维byte大数组,一维的长度是固定的,第二维的数组
可能会修改。
byte[][] bytes=new byte[M][];
for(int i=0;i<bytes.length;i++){
bytes[i]=new byte[N];
}
我需要访问每个数组,比如bytes[10]
我也需要修改某个数组,比如bytes[10]=new byte[N];
这是一种实现方式。
另一种方式是用一维数组来模拟
long[] pointers=new long[M];
byte[] data=new byte[M*N];
这种方式的优点是节省内存,并且访问速度快。
缺点是无法修改其中的某个数组,只能修改整个data数组。
我做实验测试了一下,
第一种方法使用内存 1.3g,时间11626ms
第二种方法使用内存 740M,随机初始化时间1829ms
==========================一维数组================================
public static void main(String[] args) {
if(args.length!=2){
System.out.println("Usage: java Test2 docNum len");
}
int docNum=Integer.valueOf(args[0]);
int len=Integer.valueOf(args[1]);
System.out.println("docNum="+docNum+",len="+len);
long start=System.currentTimeMillis();
long id=0;
byte[][] bytes=new byte[docNum][];
for(int i=0;i<docNum;i++){
bytes[i]=new byte[len];
for(int j=0;j<len;j++){
bytes[i][j]=(byte)(id%128);
id++;
}
}
System.out.println(id);
long end=System.currentTimeMillis();
System.out.println("Test2 timeUsed: "+(end-start)+" ms");
System.out.println("Create timeUsed: "+(end-start)+" ms");
start=System.currentTimeMillis();
for(int i=0;i<bytes.length;i++){
for(int j=0;j<bytes[i].length;j++){
id=bytes[i][j];
}
}
end=System.currentTimeMillis();
System.out.println("Read timeUsed: "+(end-start)+" ms");
System.gc();
try {
Thread.sleep(300000);
} catch (InterruptedException e) {
}
System.out.println(bytes.length);
}
}
=================================== 二维数组=====================================
public static void main(String[] args) {
if(args.length!=2){
System.out.println("Usage: java Test docNum len");
}
int docNum=Integer.valueOf(args[0]);
int len=Integer.valueOf(args[1]);
System.out.println("docNum="+docNum+",len="+len);
long start=System.currentTimeMillis();
long[] pointers=new long[docNum];
byte[] data=new byte[len*docNum];
for(int i=0;i<pointers.length;i++){
pointers[i]=1L*i*len;
}
long id=0;
for(int i=0;i<data.length;i++){
data[i]=(byte)(id%128);
id++;
}
System.out.println(id);
long end=System.currentTimeMillis();
System.out.println("Create timeUsed: "+(end-start)+" ms");
start=System.currentTimeMillis();
for(int i=0;i<data.length;i++){
id=data[i];
}
end=System.currentTimeMillis();
System.out.println("Read timeUsed: "+(end-start)+" ms");
try {
Thread.sleep(300000);
} catch (InterruptedException e) {
}
System.out.println(data.length);
}
}
读取的时候第二种稍微比第一种快一些,但是比较难以理解的是内存的差别为什么这么大。
一维数组jmap看的结果为:
num #instances #bytes class name
----------------------------------------------
1: 413 600079280 [B
2: 2 160000064 [J
3: 5204 522520 <constMethodKlass>
4: 8516 453200 <symbolKlass>
5: 5204 419104 <methodKlass>
二维数组的结果为:
num #instances #bytes class name
----------------------------------------------
1: 20000411 960079232 [B
2: 1 80000016 [[B
3: 5204 522520 <constMethodKlass>
4: 8518 453240 <symbolKlass>
5: 5204 419104 <methodKlass>
6: 343 200296 <constantPoolKlass>
7: 1505 198464 [C
一维数组的大小比较好解释:20M*30=600M的byte数组,然后加上20M*8(Long)
二维数组在java里是20M个一维数组,除了数据的20M*30外,多了20M个引用,也就是80M的空间(在64bit机器160M)
怎么会多出这么多呢? 1: 20000411 960079232 [B 平均一个一维数组时48个字节,但是我们的大小只有30个字节,然后加上数组会保持数组的长度4个字节。也就34个字节,怎么会这么多呢?
后来查了一下,原来每个对象都会保持8个字节的头部信息,jvm用它来管理内存。这样就是42个字节,也不是48个字节啊。后来又搜索了一些,发现HotSpots会对对象的空间做字节对齐,
这样就变成48个字节了。为了验证这个想法,把二维数组从30个byte变成34个byte,使用的空间没有变化。