
由于字符集编码,不同字符占用内存空间不同的,Latin1(常见的西文编码)编码格式,一个字符占用一个字节(byte),而汉语,俄文等一个字符占用两个或以上字节(byte)。不是我们语言复杂,而是发明计算机语言的是西方,他们自然把自己的语言符号编码最前面的数字代码,当然我们确实有太多的汉字,ASCII自然是不够的,unicode码就出现了,说白了占用空间是这些代码数字的二进制占用内存,汉字存储的也就更占内存空间。但事实上在语言效率上,同样长度汉语携带的信息量是英语的好几倍。如果计算机是中国人发明那汉字就是占用空间(拆成五笔字根)比英语少的多,而且在现实空间中他也确实是这样。不要以为英文字幕占一个字节,就认为英文占内存少,能表达出意思必须是word,所以用英文单词所占内存和他相同意思的汉字所占内存比即使在当前编码模式下,英文也比中文多,word占4个字节,中文"字"占两个字节。因此出现了上面编码模式下,String如果用char[]来存储字符串,那么就会出现一些“空白”浪费,那我们把内部存储改为byte[]数组,这些“空白”就会被压缩,减小内存浪费。
![堆1/4被char数组占用,而绝大多数都是单字节char,多字节占比很少,所以可以使用byte[]代替String的char[]](https://developer.qcloudimg.com/http-save/yehe-10796478/fecf0faa0a4ea0c5fb91f96d971e8d04.png)
减少“空白占用”,直接char[]->byte[],但是又有字符不是单字节的。JDK1.6时在区分char[] 和byte[]的情况下,并尝试在构建字符串时将char[]压缩为byte[],但它对char[]执行的大多数字符串操作都需要解压缩字符串,消耗性能;alt-String.jar中保留一个完全不同的String实现,功能难以测试,维护成本很高。
JDK 中大量的charAt方法调用(约2000多),如果我们将char[]改为byte[]数组,你应该保证charAt方法不退化,因为这种改变
# find -iname \*.java -exec grep charAt {} \; | wc -l
2157
Simplest use case:
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
System.out.println(c);
}(char[]->byte[])对于String的长度length是线性的,此时在JDK中调用charAt方法,将变为一个二次循环。因此,这几乎排除了大多数可变长度字符编码方案,包括 UTF-8。
那我们底层就彻底使用byte[]数组,加一个开关coder,这样就可以区分Latin1和非Latin1编码,消除了压缩和解压消耗,并且不用独立包中重写全新String类,便于维护。java代码层面的实现
final class StringLatin1 {
public static char charAt(byte[] value, int index) {
checkIndex(index, value.length);
return (char)(value[index] & 0xff);
}
}
final class StringUTF16 {
// Return a new byte array for a UTF16-coded string for len chars
// Throw an exception if out of range
public static byte[] newBytesFor(int len) {
return new byte[newBytesLength(len)];
}
// Check the size of a UTF16-coded string
// Throw an exception if out of range
public static int newBytesLength(int len) {
if (len < 0) {
throw new NegativeArraySizeException();
}
if (len >= MAX_LENGTH) {
throw new OutOfMemoryError("UTF16 String size is " + len +
", should be less than " + MAX_LENGTH);
}
return len << 1;
}
static void putChar(byte[] val, int index, int c) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
val[index++] = (byte)(c >> HI_BYTE_SHIFT);
val[index] = (byte)(c >> LO_BYTE_SHIFT);
}
@IntrinsicCandidate
// intrinsic performs no bounds checks
static char getChar(byte[] val, int index) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
public static int length(byte[] value) {
return value.length >> 1;
}
}JVM中实现
//String类在JVM中的布局javaClass.hpp
class java_lang_String : AllStatic {
private:
static int _value_offset;
static int _hash_offset;
static int _hashIsZero_offset;
static int _coder_offset;
static int _flags_offset;
// Coders
enum Coder {
CODER_LATIN1 = 0,
CODER_UTF16 = 1
};
//javaClasses.inline.hpp
void java_lang_String::set_coder(oop string, jbyte coder) {
string->byte_field_put(_coder_offset, coder);
}
void java_lang_String::set_value(oop string, typeArrayOop buffer) {
string->obj_field_put(_value_offset, buffer);
}
bool java_lang_String::is_latin1(oop java_string) {
assert(is_instance(java_string), "must be java_string");
jbyte coder = java_string->byte_field(_coder_offset);
assert(CompactStrings || coder == CODER_UTF16, "Must be UTF16 without CompactStrings");
return coder == CODER_LATIN1;
}
//jni.cpp
JNI_ENTRY(jstring, jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len))
HOTSPOT_JNI_NEWSTRING_ENTRY(env, (uint16_t *) unicodeChars, len);
jstring ret = NULL;
DT_RETURN_MARK(NewString, jstring, (const jstring&)ret);
oop string=java_lang_String::create_oop_from_unicode((jchar*) unicodeChars, len, CHECK_NULL);
ret = (jstring) JNIHandles::make_local(THREAD, string);
return ret;
JNI_END
//创建String,javaClasses.cpp
Handle java_lang_String::basic_create(int length, bool is_latin1, TRAPS) {
assert(_initialized, "Must be initialized");
assert(CompactStrings || !is_latin1, "Must be UTF16 without CompactStrings");
// Create the String object first, so there's a chance that the String
// and the char array it points to end up in the same cache line.
oop obj;
obj = vmClasses::String_klass()->allocate_instance(CHECK_NH);
// Create the char array. The String object must be handlized here
// because GC can happen as a result of the allocation attempt.
Handle h_obj(THREAD, obj);
int arr_length = is_latin1 ? length : length << 1; // 2 bytes per UTF16.
typeArrayOop buffer = oopFactory::new_byteArray(arr_length, CHECK_NH);;
// Point the String at the char array
obj = h_obj();
set_value(obj, buffer);
// No need to zero the offset, allocation zero'ed the entire String object
set_coder(obj, is_latin1 ? CODER_LATIN1 : CODER_UTF16);
return h_obj;
}
oop java_lang_String::create_oop_from_unicode(const jchar* unicode, int length, TRAPS) {
Handle h_obj = create_from_unicode(unicode, length, CHECK_NULL);
return h_obj();
}
Handle java_lang_String::create_from_unicode(const jchar* unicode, int length, TRAPS) {
bool is_latin1 = CompactStrings && UNICODE::is_latin1(unicode, length);
Handle h_obj = basic_create(length, is_latin1, CHECK_NH);
typeArrayOop buffer = value(h_obj());
assert(TypeArrayKlass::cast(buffer->klass())->element_type() == T_BYTE, "only byte[]");
if (is_latin1) {
for (int index = 0; index < length; index++) {
buffer->byte_at_put(index, (jbyte)unicode[index]);
}
} else {
for (int index = 0; index < length; index++) {
buffer->char_at_put(index, unicode[index]);
}
}
//utf8.cpp 检查给定的字符数组(base)是否只包含Latin-1字符
bool UNICODE::is_latin1(const jchar* base, int length) {
for (int index = 0; index < length; index++) {
if (base[index] > 0x00FF) { //只要有字符编码大于255,就包含非Latin1
return false;
}
}
return true;
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。