内存优化篇-String/char[]/byte[]的选择

简介: 在缓存大量的字符串数据的场景下,String/char数组/byte数组应该如何选择?

Java基本数据类型的大小

type

size(bits)

bytes

boolean

8

1

byte

8

1

char

16

2

short

16

2

int

32

4

long

64

8

float

32

4

double

64

8

Java引用的大小

在 32 位的 JVM 上,一个对象引用占用 4 个字节;在 64 位JVM上,占用 8 个字节。

使用 8 个字节是为了能够管理大于 4G 的内存,如果你的程序不需要访问大于 4G 的内存,

可通过 -XX:+UseCompressedOops 选项,开启指针压缩。从 Java 1.6.0_23 开始,这个选项默认是开的。


Java对象头的大小

在32位JVM中,对象头的大小为8个字节(4字节的Mark Word+4字节的Klass Pointer).

在64位JVM上,占用16个字节(8字节的Mark Word+8字节的Klass Pointer,因为开启UseCompressedOops,所以实际占用12个字节(8字节的Mark Word+4字节的Klass Pointer) 。参考klass pointer


接下来的内容都基于64位的JVM来展开


Java对象的大小

1、任意Java对象都包含至少12个字节的Object Header。

2、JVM分配内存以8字节为基本单位,如果不满小于8字节,则向8字节的倍数补齐。参考8 byte alignment


思考   

Object object = new Object(); 占用多少内存?

数组的大小如何计算?


验证

添加Maven依赖


<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>


import org.openjdk.jol.info.ClassLayout;


/**
 * Created by jianpingpan on 2019/1/17.
 */
public class BasicClass {

    public static void main(String[] args) throws Exception {
        System.out.println(ClassLayout.parseClass(Object.class).toPrintable());
        System.out.println(ClassLayout.parseClass(String.class).toPrintable());
        System.out.println(ClassLayout.parseClass(byte[].class).toPrintable());
        System.out.println(ClassLayout.parseClass(char[].class).toPrintable());
    }

}





1547711148456-a226b348-168e-45ed-9265-52

byte[] 和char[]的 object header为16个字节是因为有4个字节的数组长度。



String /  char[]   / byte[] 内存大小计算


    String a = new String("abc");
    String b = new String("abcd");
    String c = new String("abc");



1547713532558-2389799f-3057-4ebd-bae0-11

第一行占用JVM内存的大小:

对象大小 = 12字节(object header)+

                   4字节 (hash)+

                   4字节(数组引用vlaue[]) +

                   4字节 (padding)

                   16字节+3*2字节+2字节padding   (数组value[])

              =   48字节


假设要缓存的字符个数为N。


String的内存大小计算公式 = 40+N*2  +padding

char数组的内存大小计算公式 = 16+N*2+padding


如果用byte数组来存储字符串数据,占用的内存大小X需要分2种情况讨论:


1、如果需要存储的字符全在ASCII码中,一个字符用一个byte就可以存储 (编码方式可选ISO-8859-1/GBK/UTF-8):


X = 16+N+padding


2、如果需要存储的字符范围不能被ASCII码覆盖,则需要根据字符范围确定合适的存储方式。

如需要要存储字符集为ASCII+中文字符,则可使用GBK编码:


16+N+padding <X <  16+N*2+padding


如果字符集不能被ASCII码覆盖,并且包含非中文字符,则使用UTF-8编码:


16+N+padding<X<16+6*N+padding


结论:

由此可见,char数组占用的内存大小小于String占用的内存大小。

若存储的字符范围以ASCII码为主,使用byte数组存储优于char数组。



实际使用场景

那么在缓存中可以直接用char[]或byte[]替换String么?          

Set<String> set = new HashSet<>();

替换成

Set<byte[]> set = new HashSet<>();

会怎样呢?


很明显,contains方法、get方法都会失效。因为每个byte[]的hashCode不一样。


我们用下面的这个ByteArray/CharArray封装byte[],再用ByteArray替换String。


/**
 * Created by jianpingpan on 2019/1/23.
 */
public class ByteArray {
    byte[] bytes;

    public ByteArray(byte[] bytes){
        this.bytes = bytes;
    }

    @Override
    public int hashCode() {

        if(null == bytes){
            return 0;
        }
        return new String(bytes).hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null){
            return false;
        }
        return hashCode()==obj.hashCode();
    }
}



(CharArray的实现方式同ByteArray,只是把byte[] bytes 替换成 char[] chars即可)


ByteArray占用的内存大小 = 

                  12字节(object header+

                   4字节(数组引用bytes[]) +

                   16字节+N字节+padding   (数组bytes[]

              =   32字节+N字节+padding


CharArray占用的内存大小=

                   12字节(object header+

                   4字节(数组引用bytes[]) +

                   16字节+2*N字节+padding   (数组bytes[]

              =   32字节+2*N字节+padding


其中,N为数组中元素的个数。


例子

以存储100万条长度为32位的MD5字符串为例且内容互不相同字符串为例(假设字符串中的字符均为字母、数字、下划线)。

 

可以用classmexer来计算内存使用量 。

  

import com.javamex.classmexer.MemoryUtil;


/**
 * Created by jianpingpan on 2019/1/25.
 */
public class StringTest {


    public static void main(String[] args){
        
        String s="cfcd208495d565ef66e7dff9f98764da";
        ByteArray b = new ByteArray(s.getBytes());
        CharArray c = new CharArray(s.toCharArray());

        long stringBytes = MemoryUtil.deepMemoryUsageOf(s);
        long byteArrayBytes = MemoryUtil.deepMemoryUsageOf(b);
        long charArrayBytes = MemoryUtil.deepMemoryUsageOf(c);

        System.out.println("stringBytes:"+stringBytes);
        System.out.println("byteArrayBytes:"+byteArrayBytes);
        System.out.println("charArrayBytes:"+charArrayBytes);
    }
}


resize,w_2000

 

用String存储,每条记录占用的空间为 40+32*2 = 104字节

用ByteArray存储,每条记录占用的空间为 32+32 = 64字节

用CharArray存储,每条记录占用的空间为 32+32*2 = 96字节




                  



参考文档:

http://btoddb-java-sizing.blogspot.com/2012/01/object-sizes.html

https://stackoverflow.com/questions/26357186/what-is-in-java-object-header/26416983

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

目录
相关文章
|
3月前
|
存储 Go 索引
Golang 中的 String、rune 和 byte
Golang 中的 String、rune 和 byte
|
3月前
|
测试技术 Go API
golang []byte和string的高性能转换
golang []byte和string的高性能转换
38 1
new String()定义字符串为空,char[] chs = {‘a‘,‘b‘,‘c‘} String s2 = new String(chs) 输出abc,byte定99为a
new String()定义字符串为空,char[] chs = {‘a‘,‘b‘,‘c‘} String s2 = new String(chs) 输出abc,byte定99为a
|
6月前
|
C++
【C++】std::string 转换成非const类型 char* 的三种方法记录
【C++】std::string 转换成非const类型 char* 的三种方法记录
164 0
char[] 转String时的陷阱
char[] 转String时的陷阱
39 1
byte[]转换成String
byte[]转换成String
|
存储 小程序 程序员
8k字详解整型(int)/字符型(char)/浮点型(float)/有符号(signed)/无符号(unsigned)数据在内存中的存储【程序员内功修炼/C语言】
8k字详解整型(int)/字符型(char)/浮点型(float)/有符号(signed)/无符号(unsigned)数据在内存中的存储【程序员内功修炼/C语言】
147 0
|
存储 自然语言处理 Java
Java_9_为何要将_String_的底层实现由_char[]_改成了_byte[]_?
Java_9_为何要将_String_的底层实现由_char[]_改成了_byte[]_?
|
2月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
38 0
java基础(13)String类
|
1月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
55 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性