哦,原来字符串常量池是这样存储数据的
关于字符串常量池中的StringTable看了一下,网上很少有对这里做一个完整梳理的,有的说StringTable只存引用,有的说存的是字符串对象,到底存了什么,搞得我也是很头大,还是自己来动手看一下吧。
ps:本人水平有限,如果您发现了文章中的错误,欢迎私信我,我会及时改正。
揭开神秘面纱
HashtableBucket的结构示意图
java中String的intern()方法我就不过多介绍了,既然你能看到本篇博客,证明你对它有了一定的了解,直接来看他的底层代码。
文件位置:
openjdk8-master->hotspot->src->share->vm->classfile->symbolTable.cpp
oop StringTable::intern(Handle string_or_null, jchar* name,
int len, TRAPS) {
//计算出字符串的哈希值
unsigned int hashValue = hash_string(name, len);
//根据哈希值计算出索引,和HashMap有点像
int index = the_table()->hash_to_index(hashValue);
//在字符串常量池中寻找是否有相同的字符串
oop found_string = the_table()->lookup(index, name, len, hashValue);
// Found
//如果找到了就直接返回字符串,这也就和String的intern方法的api说明对应上了
if (found_string != NULL) return found_string;
debug_only(StableMemoryChecker smc(name, len * sizeof(name[0])));
assert(!Universe::heap()->is_in_reserved(name),
"proposed name of symbol must be stable");
Handle string;
// try to reuse the string if possible
if (!string_or_null.is_null()) {
string = string_or_null;
} else {
string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
}
// Grab the StringTable_lock before getting the_table() because it could
// change at safepoint.
MutexLocker ml(StringTable_lock, THREAD);
// Otherwise, add to symbol to table
//英文注释已经写得很清楚了,如果找不到,就将字面量添加到Hashtable
return the_table()->basic_add(index, string, name, len,
hashValue, CHECK_NULL);
}
oop StringTable::basic_add(int index_arg, Handle string, jchar* name,
int len, unsigned int hashValue_arg, TRAPS) {
assert(java_lang_String::equals(string(), name, len),
"string must be properly initialized");
// Cannot hit a safepoint in this function because the "this" pointer can move.
No_Safepoint_Verifier nsv;
// Check if the symbol table has been rehashed, if so, need to recalculate
// the hash value and index before second lookup.
unsigned int hashValue;
int index;
if (use_alternate_hashcode()) {
hashValue = hash_string(name, len);
index = hash_to_index(hashValue);
} else {
hashValue = hashValue_arg;
index = index_arg;
}
// Since look-up was done lock-free, we need to check if another
// thread beat us in the race to insert the symbol.
oop test = lookup(index, name, len, hashValue); // calls lookup(u1*, int)
if (test != NULL) {
// Entry already added
return test;
}
//上面的英文注释也得很清楚了,我就不再赘述了,主要看一下这里
//使用new_entry将字面量设置到Hashtable中,并返回entry
HashtableEntry<oop, mtSymbol>* entry = new_entry(hashValue, string());
//将entry添加到桶中
add_entry(index, entry);
return string();
}
StringTable使用的就是Hashtable我们先打开openjdk8-master->hotspot->src->share->vm->utilities->hashtable.hpp来看一下Hashtable的大致结构
template <class T, MEMFLAGS F> class HashtableEntry : public BasicHashtableEntry<F> {
friend class VMStructs;
private:
T _literal; // ref to item in table.
public:
// Literal
//字面量,例如String str = "hello",这部分就是hello
T literal() const { return _literal; }
//字面量的地址
T* literal_addr() { return &_literal; }
//set方法,为Hashtable添加字面量及引用值(赋值了字面量,自然也就有了引用值)
void set_literal(T s) { _literal = s; }
HashtableEntry* next() const {
return (HashtableEntry*)BasicHashtableEntry<F>::next();
}
HashtableEntry** next_addr() {
return (HashtableEntry**)BasicHashtableEntry<F>::next_addr();
}
};
回到new_entry()方法,在hashtable.cpp文件中
template <class T, MEMFLAGS F> HashtableEntry<T, F>* Hashtable<T, F>::new_entry(unsigned int hashValue, T obj) {
HashtableEntry<T, F>* entry;
//调用了下面的new_entry方法
entry = (HashtableEntry<T, F>*)BasicHashtable<F>::new_entry(hashValue);
//再将字面量设置到entry
entry->set_literal(obj);
return entry;
}
template <MEMFLAGS F> BasicHashtableEntry<F>* BasicHashtable<F>::new_entry(unsigned int hashValue) {
BasicHashtableEntry<F>* entry;
if (_free_list) {
entry = _free_list;
_free_list = _free_list->next();
} else {
if (_first_free_entry + _entry_size >= _end_block) {
int block_size = MIN2(512, MAX2((int)_table_size / 2, (int)_number_of_entries));
int len = _entry_size * block_size;
len = 1 << log2_intptr(len); // round down to power of 2
assert(len >= _entry_size, "");
_first_free_entry = NEW_C_HEAP_ARRAY2(char, len, F, CURRENT_PC);
_end_block = _first_free_entry + len;
}
entry = (BasicHashtableEntry<F>*)_first_free_entry;
_first_free_entry += _entry_size;
}
assert(_entry_size % HeapWordSize == 0, "");
//重点看这里,将字面量的hash值设置后返回entry
entry->set_hash(hashValue);
return entry;
}
此时,完成了对entry的设置,我们将字面量和地址值设置好了,接下来要将entry添加到桶中。
hashtable.cpp
public:
int table_size() { return _table_size; }
void set_entry(int index, BasicHashtableEntry<F>* entry);
//对HashMap有了解的看这些代码很容易理解
//看这里看这里,index为桶中位置,entry是刚刚设置好的
void add_entry(int index, BasicHashtableEntry<F>* entry);
void free_entry(BasicHashtableEntry<F>* entry);
int number_of_entries() { return _number_of_entries; }
void verify() PRODUCT_RETURN;
};
add_entry方法的具体实现在hashtable.inline.hpp中
template <MEMFLAGS F> inline void BasicHashtable<F>::add_entry(int index, BasicHashtableEntry<F>* entry) {
//个人理解,这里的entry和HashMap中的Node比较类似,都是链表
entry->set_next(bucket(index));
//在桶中对应的位置添加entry
_buckets[index].set_entry(entry);
++_number_of_entries;
}
总结
到这里,大概的流程已经走完了,通过intern()方法可以看出,Hashtable的key是字面量,value是地址值,如果按照网上的说法,Hashtable存的是引用是针对value来说的。Hashtable存的是字符串对象是把key和value看做一个整体来说的,如果在面试中被问到 ,需要向面试官解释一下答案的角度。