v8源码解析之HashTable(v8 0.1.5)

HashTable是v8中哈希表的实现,HashTable继承Array。HashTable提供一些公共的逻辑,供后续子类使用。我们看一下他 大内存布局。

然后看一下类的定义。HashTable是个模板类,prefix_size是,element_size是哈希表中每个元素的大小。

template<int prefix_size, int element_size>
class HashTable: public FixedArray {
 public:
  // 哈希表已使用的元素个数
  int NumberOfElements() {
    return Smi::cast(get(kNumberOfElementsIndex))->value();
  }
  // 哈希表容量
  int Capacity() {
    return Smi::cast(get(kCapacityIndex))->value();
  }

  // 新增一个元素,修改已使用元素个数字段的值
  void ElementAdded() { SetNumberOfElements(NumberOfElements() + 1); }
  // 删除一个元素或n个元素时,修改已使用元素个数字段的值
  void ElementRemoved() { SetNumberOfElements(NumberOfElements() - 1); }
  void ElementsRemoved(int n) { SetNumberOfElements(NumberOfElements() - n); }
  // 分配一个新的哈希表
  static Object* Allocate(int at_least_space_for);

  // 根据索引获取数组中对应的值
  Object* KeyAt(int entry) { return get(EntryToIndex(entry)); }

  // 是否是有效的键
  bool IsKey(Object* k) {
    return !k->IsNull() && !k->IsUndefined();
  }
  // 类型转换
  static inline HashTable* cast(Object* obj);
  // 管理哈希表key的类,由子类实现
  class Key {
   public:
    // 判断key是否和Object中的相等相等
    virtual bool IsMatch(Object* other) = 0;
    // 哈希函数
    typedef uint32_t (*HashFunction)(Object* obj);
    // Returns the hash function used for this key.
    virtual HashFunction GetHashFunction() = 0;
    // 哈希值
    virtual uint32_t Hash() = 0;
    // 获取对应的值
    virtual Object* GetObject() = 0;
    virtual bool IsStringKey() = 0;
    // Required.
    virtual ~Key() {}
  };

  // 计算哈希冲突时,跳的步长
  INLINE(static uint32_t GetProbeOffset(uint32_t n)) {
    return (n + n * n) >> 1;
  }
  // 元素个数 索引
  static const int kNumberOfElementsIndex = 0;
  // 容量 索引
  static const int kCapacityIndex         = 1;
  // prefix 索引
  static const int kPrefixStartIndex      = 2;
  // 哈希表第一个元素的开始地址的索引
  static const int kElementsStartIndex    = kPrefixStartIndex + prefix_size;
  // 元素大小
  static const int kElementSize           = element_size;
  // 第一个元素的相对内存偏移地址
  static const int kElementsStartOffset   =
      kHeaderSize + kElementsStartIndex * kPointerSize;

 protected:
  int FindEntry(Key* key);
  uint32_t FindInsertionEntry(Object* key, uint32_t hash);
  // 根据元素的索引算出在哈希表中的相对真正偏移
  static inline int EntryToIndex(int entry) {
    return (entry * kElementSize) + kElementsStartIndex;
  }
  // 设置哈希表中已使用元素的个数
  void SetNumberOfElements(int nof) {
    fast_set(this, kNumberOfElementsIndex, Smi::FromInt(nof));
  }
  // 设置哈希表容量
  void SetCapacity(int capacity) {
    ASSERT(capacity > 0);
    fast_set(this, kCapacityIndex, Smi::FromInt(capacity));
  }
  // 哈希冲突时,下一个索引的位置,取余保证位置再哈希表有效元素的范围
  static uint32_t GetProbe(uint32_t hash, uint32_t number, uint32_t size) {
    ASSERT(IsPowerOf2(size));
    return (hash + GetProbeOffset(number)) & (size - 1);
  }
  // 扩容
  Object* EnsureCapacity(int n, Key* key);
};

接下来我们看一下在类外定义的内容。

1 Allocate

Allocate分配一个新的哈希表。

template<int prefix_size, int element_size>
Object* HashTable<prefix_size, element_size>::Allocate(int at_least_space_for) {
  // 计算大于at_least_space_for的值,值是2的n次方中的最小值
  int capacity = NextPowerOf2(at_least_space_for);
  // 根据内存布局,不能小于4
  if (capacity < 4) capacity = 4;  
  // 算出最后一个元素在哈希表中的相对偏移,即哈希表(数组)的大小,然后分配一个新的数组
  Object* obj = Heap::AllocateHashTable(EntryToIndex(capacity));
  if (!obj->IsFailure()) {
    // 初始化属性
    HashTable::cast(obj)->SetNumberOfElements(0);
    HashTable::cast(obj)->SetCapacity(capacity);
  }
  return obj;
}

2 FindInsertionEntry

FindInsertionEntry通过key计算插入位置的索引,该索引是相对偏移位置。因为哈希表(数组)的内存布局中,前面几个元素是用于存储其他数据,所以从第n个元素起才是真正的元素。FindInsertionEntry返回的索引还需要转成真正的偏移。

template<int prefix_size, int element_size>
uint32_t HashTable<prefix_size, element_size>::FindInsertionEntry(
      Object* key,
      uint32_t hash) {
  uint32_t capacity = Capacity();
  // 通过key找到一个插入的位置
  uint32_t entry = GetProbe(hash, 0, capacity);
  // 该位置是否有值,有值说明发生冲突
  Object* element = KeyAt(entry);
  // 非空则说明冲突了,找下一个可用的索引,否则直接返回(如果都有值了,这里会死循环)
  for (uint32_t i = 1; !(element->IsUndefined() || element->IsNull()); i++) {
    entry = GetProbe(hash, i, capacity);
    element = KeyAt(entry);
  }

  return entry;
}

3 EnsureCapacity

EnsureCapacity实现哈希表的扩容

template<int prefix_size, int element_size>
Object* HashTable<prefix_size, element_size>::EnsureCapacity(int n, Key* key) {
  // 旧的容量
  int capacity = Capacity();
  // 已使用加上待添加的元素个数
  int nof = NumberOfElements() + n;
  // Make sure 20% is free
  // n+n/4如果小于容量则说明剩余空间小于20%了。假设是等于,说明n/4==capacity/5
  if (nof + (nof >> 2) <= capacity) return this;
  // 分配一个新的数组
  Object* obj = Allocate(nof * 2);
  if (obj->IsFailure()) return obj;
  HashTable* dict = HashTable::cast(obj);
  WriteBarrierMode mode = dict->GetWriteBarrierMode();
  // 把prefix先复制过去
  for (int i = kPrefixStartIndex; i < kPrefixStartIndex + prefix_size; i++) {
    dict->set(i, get(i), mode);
  }
  // 把数据从原来的哈希表复制到新的哈希表
  uint32_t (*Hash)(Object* key) = key->GetHashFunction();
  // 遍历之前的哈希表(数组)
  for (int i = 0; i < capacity; i++) {
    // 根据逻辑索引算出真正的索引 
    uint32_t from_index = EntryToIndex(i);
    // 拿到索引的对应的元素
    Object* key = get(from_index);
    // 有值
    if (IsKey(key)) {
      // 重新哈希,并且找到在新哈希表的逻辑位置,再算出实际的位置
      uint32_t insertion_index =
          EntryToIndex(dict->FindInsertionEntry(key, Hash(key)));
      // 写入新的哈希表,需要复制的大小由element_size决定,可能需要复制多个指针的值
      for (int j = 0; j < element_size; j++) {
        dict->set(insertion_index + j, get(from_index + j), mode);
      }
    }
  }
  // 设置已使用元素的个数
  dict->SetNumberOfElements(NumberOfElements());
  return dict;
}

2 FindEntry

FindEntry是通过key找到对应的位置。该位置是相对位置,存取的时候还需要计算出真正的位置。

template <int prefix_size, int element_size>
int HashTable<prefix_size, element_size>::FindEntry(Key* key) {
  // 已使用的元素个数
  uint32_t nof = NumberOfElements();
  if (nof == 0) return -1;  // Bail out if empty.

  uint32_t capacity = Capacity();
  uint32_t hash = key->Hash();
  // 获取在哈希表中的逻辑索引,还需要转成内部真正的索引,因为哈希表的数组前几项用来存储其他数据了
  uint32_t entry = GetProbe(hash, 0, capacity);
  // 获取对应的键
  Object* element = KeyAt(entry);
  uint32_t passed_elements = 0;
  // 有值
  if (!element->IsNull()) {
    // 匹配则直接返回
    if (!element->IsUndefined() && key->IsMatch(element)) return entry;
    // 不匹配则判断是不是所有元素都找过了,是则直接返回找不到
    if (++passed_elements == nof) return -1;
  }
  // 继续找...
  /*
    因为哈希表解决冲突方式是开发地址法,所以继续找下一个位置的元素,
    直到undefined,说明到底了,极端的情况下,遍历的每个元素都有值了,
    但都不等于想要找的值,所以下面passed_elements那里也需要判断,
    判断是不是遍历了所有值了,否则死循环
  */
  for (uint32_t i = 1; !element->IsUndefined(); i++) {
    entry = GetProbe(hash, i, capacity);
    element = KeyAt(entry);
    if (!element->IsNull()) {
      if (!element->IsUndefined() && key->IsMatch(element)) return entry;
      // todo
      if (++passed_elements == nof) return -1;
    }
  }
  return -1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值