`
lorry1113
  • 浏览: 263209 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

再思考Java里的数据结构容器——hash容器:hashset hashmap hash容器的结构

    博客分类:
  • java
阅读更多
Hashtable跟hashmap差不多,不过HashTable强制同步,是线程安全的。

HashSet<E>是通过内置的HashMap<E,object>来实现的,HashSet<E>中指定的元素类型E,本身就是key,每个元素的value是一个常量object,HashSet仅作一个集合管理,只有add contains remove等接口,没有get接口。

重点讨论下HashMap的结构。

HashMap维护了capacity个数的hash bucket(hash桶),这个是用数组实现的,即table[capacity],每个非空的hash bucket其实是一个映射链表的表头,即list head of entry,链表里面的每个entry负责一个具体key到value的映射。

每个hash bucket里元素,他们key值的hashcode可能不一样,即hash散列时是容许冲突的,但一样的是hashcode&(capacity-1),这个hashcode&(capacity-1)即他们所在的hash bucket在table[capacity]里的index,其实这就是hash散列的过程,顺便说下这个hashcode是key的hashcode经过变换之后得到的,这个index就是所谓的hash bucket index。

当然capacity也可以认为是元素的容量,因为理想的hash散列是一对一的,即没有散列冲突,一个hash bucket固定存储一个key到value的映射,后面可以看到容器维护的时候也是这么努力的,是否扩充容量是由元素的个数是否到了负载极限来决定的,而不是已填充的hash bucket的个数,这是表现之一。

有了这个概念之后再看hashmap的常见操作:

为一个元素在table[bucketIndex]里添加一个映射entry,如果超过threshold(Capacity * loadFactor),则扩展一倍,默认的容量是16,loderfactor是0.75,所以当元素个数达到12的时候,table容量扩充为32,依次64,128等

    void addEntry(int hash, K key, V value, int bucketIndex) {

Entry<K,V> e = table[bucketIndex]; // 获取hash bucket里的entry链表表头
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
// new Entry<K,V>(hash, key, value, e) 构造里是这么写的:this.next = e,可见是前插法填充链表的。
        if (size++ >= threshold)
            resize(2 * table.length);
    }

查找是否包含某个key的映射:比如 hm.add("ab");则hm.containsKey("ab")则true

    public boolean containsKey(Object key) {
//空元素null特殊对待,貌似专门用一个object常量
        Object k = maskNull(key);
//把k的hashcode再转换
        int hash = hash(k);
//hashcode&(capacity-1)换的index
        int i = indexFor(hash, table.length);
        Entry e = table[i];
// 在entry链表里找key
        while (e != null) {
//先验证key的hashcode,然后就是key的equals值
            if (e.hash == hash && eq(k, e.key))
                return true;
            e = e.next;
        }
        return false;
    }

按照这个过程分析hm.containsKey("ab"),过程就是通过"ab"的hashcode值找到hashbucket的entry链表表头,然后在里面逐个与"ab"比较equals,找到的话则返回true,否则false。其他的查找操作与这个过程大同小异,比如get(key);put(key,value)其实也有个类似的查找映射enty的操作。

    public V put(K key, V value) {
K k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            if (e.hash == hash && eq(k, e.key)) {
//put另一个值得注意的地方:同一个key下增加,会把原来的value给覆盖掉
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(hash, k, value, i);
        return null;
    }

同样如果是查找某个value,得依次遍历所有的hash bucket,并逐个与给定值进行equals,这个源码略去。

原创,转载请标注http://hi.baidu.com/heelenyc 欢迎交流指正。

再看hashtable。
大致上和hashmap是一样的,但可以肯定他们两不是一个人写的,实现细节有差别,包括hash散列函数,空值处理、代码风格等。
最重要的区别是在关键操作方法前多了一个synchronized同步的,这是出于线程安全而考虑的。下面以put函数为例

    public synchronized V put(K key, V value) {
// Make sure the value is not null
//从这可以看出 hashtable不欢迎value==null的元素,hashmap对这个问题专门对待

if (value == null) {
     throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
//此处如果key为null,抛出异常
int hash = key.hashCode();
//跟hashmap的散列函数有区别
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
     if ((e.hash == hash) && e.key.equals(key)) {
   V old = e.value;
   e.value = value;
   return old;
     }
}

总结:
1、HashSet,HashMap,Hashtable 之所以"hash", 个人认为体现在:
首先、通过key的hashcde找到table[capability]里的hash bucket的过程,这是hash散列的过程,
其次、散列过程显然无法避免冲突,在元素个数不止一个的hash bucket通过链表查找key的过程,其实就是hash再探测的过程,显然java是通过链表来解决hash冲突的。

2、key的hashcode、key的equals方法和value的equals方法是hash容器操作的关键,所以当我们自定义hash容器的元素类的时候,特别要注意这两个方法的重写。用到的地方包括:
查找key是用key的hashcode找hash bucket,用key的equals找对应的entry;查找value是使用了value的equals来匹配的。

3、Hashtable和Hashmap在hash处理上大同小异,列举几点
区别一:hashtable不允许null的value,也没有考虑null的key,即在获取key的hashcode会抛出异常,hashmap碰到null的key都替换成一个常量object,它也可以容忍null的value
区别二:把key的hashcode散列到hash bucket的时候有区别
区别三:Hashtable强制同步,保证线程安全。

原创,转载请标注http://hi.baidu.com/heelenyc 欢迎交流指正。

待续,下次看下容器里的泛型编程和设计模式。

分享到:
评论

相关推荐

    源码解析jdk7.0集合:HashSet的底层实现原理.pdf

    HashSet作为Java集合框架中一个重要的非同步集合实现,它在JDK 7.0中的底层实现原理是基于HashMap来存储和操作数据的。下面就详细介绍HashSet的实现原理。 首先,HashSet是Set接口的一个实现类,它用于存储唯一性的...

    Java集合框架源码剖析:HashSet 和 HashMap

     之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也是说HashSet里面有一个HashMap(适配器模式)。因此本文将重点分析HashMap。  HashMap实现了Map...

    Java HashMap类详解

    也就是说:Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的 Java 对象。 4. HashMap 的存储实现 HashMap 的存储实现可以通过查看其 put(K key, V value) 方法的源代码来了解。该方法首先判断 ...

    Java数据结构和算法中文第二版

    - **数组(Array)**:Java中的数组是基本的数据结构,可以直接声明和使用。 - **链表(Linked List)**:`java.util.LinkedList`提供了链表的实现。 - **栈(Stack)**:`java.util.Stack`是一个继承自`Vector`的类,实现...

    Java集合专题总结:HashMap 和 HashTable 源码学习和面试总结

    Java集合专题总结:HashMap和HashTable源码...本文总结了HashMap和HashTable的源码学习和面试总结,涵盖了它们的存储结构、构造方法、get和put方法的源码分析、Hash表的特点和缺点、HashTable和HashMap的区别等内容。

    数据结构 课件java版本

    本课件“数据结构 课件java版本”是基于Java编程语言来讲解这一主题的,旨在帮助学生和开发者深入理解数据结构的基本概念,并掌握用Java实现这些数据结构的方法。 在Java中,数据结构主要分为以下几类: 1. **数组...

    Java面试题 从源码角度分析HashSet实现原理

    5. HashSet的实现机理:HashSet的实现机理可以总结为:HashSet是基于HashMap实现的,HashSet的元素不重复机制是通过HashMap的put方法实现的,HashSet的其他方法都是通过调用HashMap的对应方法来实现的。 通过源码...

    cpp-sparsemap一个高效hashmap和hashset的C实现

    哈希映射(HashMap)和哈希集合(HashSet)都是基于哈希表的数据结构。哈希表通过哈希函数将键(Key)映射到数组的索引位置,以此实现快速查找。哈希冲突是哈希表必须面对的问题,cpp-sparsemap通过开放寻址法或链...

    java数据结构源码

    6. **集合**(Collection):Java的`java.util.Collection`接口是所有单值容器的父接口,包括`Set`和`List`。其中`ArrayList`和`HashSet`是最常用的实现。 7. **集合的变种:Set**(Set):不允许重复元素,`...

    通过 HashMap、HashSet 的源代码分析其 Hash 存储机制1

    在Java编程语言中,HashMap和HashSet是两种常用的集合类,它们都依赖于哈希存储机制来提供高效的数据存取性能。这两个类分别实现了Map接口和Set接口,虽然它们的用途不同,但它们底层的实现原理有很强的关联性。本文...

    Java容器集合(equals 和 hashCode+基础数据结构+ArrayList+Vector和LinkedList)

    Java容器集合(equals和hashCode+基础数据结构+ArrayList+Vector和LinkedList) Java容器集合是Java中的一种基础数据结构,用于存储和管理数据。其中,equals和hashCode方法是Java容器集合中两个非常重要的方法,...

    Hashtable和HashMap的区别:

    在Java编程语言中,`Hashtable` 和 `HashMap` 都是用来存储键值对的数据结构。这两种数据结构虽然相似,但是在实现细节上存在显著差异。 1. **Hashtable**:作为 `Dictionary` 类的子类,`Hashtable` 是 Java 最早...

    JavaHashSet和HashMap源码剖析编程开发技术

    在Java编程语言中,HashSet和HashMap是两种非常重要的集合类,它们都位于`java.util`包下,分别用于存储不重复元素的集合和键值对的数据结构。本篇技术文档将深入剖析这两类数据结构的源码,帮助开发者理解其内部...

    java数据结构和算法

    5. **集合(Collection)框架**:Java提供了一整套集合框架,包括List(ArrayList, LinkedList)、Set(HashSet, TreeSet)和Queue(LinkedList实现的Deque)等接口及其实现类。 6. **映射(Map)**:键值对存储,如...

    Java集合容器面试题

    Java 集合容器是 Java 语言中的一种数据结构,用于存储和操作数据。集合容器框架是 Java 中的一个重要组件,提供了一种统一的标准来存储和操作数据。下面是关于 Java 集合容器的知识点: 集合容器概述 集合容器是...

    java中HashMap详解.pdf

    在Java编程语言中,HashMap是基于哈希表实现的数据结构,它是Map接口的一个具体实现,提供了高效的插入、删除和查找操作。HashMap不保证元素的顺序,允许null键和null值。HashMap的工作原理主要依赖于哈希算法,通过...

    HashMap 概述 精讲 .md

    - **数据结构**:解释HashMap的数据结构是什么? - **put过程**:详细描述HashMap的put过程。 - **线程安全性**:解释为什么HashMap不是线程安全的? - **哈希碰撞处理**:HashMap是如何处理哈希碰撞的? - **get...

    Java 最常见的 208 道面试题:第二模块答案

    【Java 容器详解】 Java 容器是 Java 核心库的重要组成部分,它们提供了存储和管理对象的方式。常见的容器包括以下几类: 1. **集合接口**:主要有 Collection 和 Map 两大接口。 - **Collection**:代表一组不...

Global site tag (gtag.js) - Google Analytics