哦?原来字符串常量池是这样存储数据的!

哦,原来字符串常量池是这样存储数据的

关于字符串常量池中的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看做一个整体来说的,如果在面试中被问到 ,需要向面试官解释一下答案的角度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值