深入浅出hashmap底层原理 && 制作一个简单的散列表

HashMap是什么

HashMap是通过键值对存储数据的一种数据类型。底层的基本单元是一个节点类,节点类中包含着键(key)和值(value)两种类型。存储的方式是无序的。是一种数组+链表存储方式。那相对于单纯的数组和链表,HashMap又有那些优势呢?

数组:

采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1),查找效率高,通过下标可以直接获取到数据。但是对于数组的增删效率就要低很多,向数组中插入一个数,需要就后面的每一个数进行位移,当标定数组超标是还需要新建一个更大的数组将原数组数据进行拷贝。可以说对于经常要添加和删除数据的集合,数组不是一个理想的存储方式。

链表

对于链表的新增,删除等操作,在我们找到需要插入或删除的节点后仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作则每次都需要遍历链表逐一进行比对,复杂度为O(n)。

HashMap

底层是数组加链表的形式如图所示
在这里插入图片描述
HashMap基于一个数组,当连续添加数据时势必会在同一个数组添加两次元素(哈希冲突),这时新添加的元素就会保存在之前节点的next地址,形成一个链表。对于HashMap存储结构,继承了数组和链表的各自优势,添加时通过hashcode快速定位到数组的某一节点,然后遍历该点上的链表,存在即覆盖,否则新增;对于查找操作来讲,同样需要遍历链表,然后通过key对象逐一比对equals的值。在JDK1.8之后对于每个数组上的链表个数在大于8之后,该链表会自动转化为红黑树,更加提高了效率,当黑红数数量小于6时,又会变成单向链表。

HashMap底层代码

HashMap默认的数组初始容量为16,默认负载因子为0.75
加载因子是底层数组容量达到75%时,底层数组开始扩容,扩容为原来的2倍

  /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

存储数据的Node节点

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

哈希算法
可以看出HashMap能取null值,默认code为0

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

方法

1、 void clear() :从这张地图中删除所有的映射。
2、boolean containsKey(Object key) :如果此映射包含指定键的映射,则返回 true 。
3、boolean containsValue(Object value) :如果此地图将一个或多个键映射到指定值,则返回 true 。
4、Set<Map.Entry<K,V>> entrySet() :返回此地图中包含的映射的Set视图。
5、V get(Object key) :返回到指定键所映射的值,或 null如果此映射包含该键的映射
6、V put(K key, V value) :将指定的值与此映射中的指定键相关联。
7、V remove(Object key) :从该地图中删除指定键的映射(如果存在)。
8、 int size() :返回此地图中键值映射的数量。

制作一个HashMap

创建一个Entry节点类,类中建立key、value、下一个节点next。

public class Entry {
    public Object key;
    public Object value;
    public Entry next;
    public Entry(Object k , Object v){
        this.key = k;
        this.value = v;
    }
    public Entry(){
    }
}

创建一个链表类,添加remove、add、print(打印value)和size方法

public class EntryList {
    private Entry root;
    private Entry last;
    private int size;
    public EntryList(){
        root = new Entry();
        size = 0;
    }
    public Entry remove(Object key){
        Entry att = new Entry();
        Entry head = root.next;
        if(head == null){
            return null;
        }
        if(head.key == key){
            if(head.next != null){
                root.next = head.next;
            }
            else{
                root.next = null;
            }
            size--;
            return head;
        }
        while(head.next != null){
            Entry per = head;
            head = head.next;
            if(head.next != null){
                Entry nex = head.next;
                if(head.key == key){
                    per.next = nex;
                    size--;
                    return head;
                }
            }
            else{
                if(head.key == key){
                    per.next = null;
                    size--;
                    return head;
                }
            }
        }
        return null;
    }
    public void add(Entry e){
        if(root.next == null){
            root.next = e;
            last = e;
            size++;
        }
        else{
            Entry head = root.next;
            while(head.key != e.key && head.next != null){
                head = head.next;
            }
            if(head.key == e.key){
                head.value = e.value;
            }
            else {
                last.next = e;
                last = e;
                size++;
            }
        }
    }
    public void printEntry(){
        Entry head = root.next;
        if(head == null){
            //System.out.println("null");
            return;
        }
        while(true){
            System.out.print("[ "+head.key+"   "+head.value+" ]");
            if(head.next == null){
                break;
            }
            head = head.next;
        }
        System.out.println();
    }
    public int size() {return size;}
}

创建一个HashMap类,默认数组长度为16,添加add、print、size和remove的方法。

public class HashMap<K,V> {
    private int defaut = 16;
    private EntryList[] list;
    private int size;
    public HashMap(){
        list = new EntryList[defaut];
    }
    public HashMap(int num){
        list = new EntryList[num];
        defaut = num;
        for(int i=0;i<num;i++){
           list[i] = new EntryList();
        }
    }
    public void add(K k ,V v){
        Entry entry = new Entry(k,v);
        int index = hash((K)entry.key);
        list[index].add(entry);
    }
    public int hash(K k){
        int nn = k.hashCode();
        return nn%defaut;
    }
    public void print(){
        for (int i = 0; i < list.length; i++) {
            list[i].printEntry();
        }
    }
    public int size(){
        int num = 0;
        for (int i = 0; i < list.length; i++) {
            num += list[i].size();
        }
        return (size=num);
    }

    public V remove(K k){
        int index = hash(k);
        Entry ee = list[index].remove(k);
        return ee == null ? null : (V)ee.value;
    }
}

测试,在同一个链表中的数据一行打印,不同链表分行打印显示

public class Test {
    public static void main(String[] args) {
        HashMap<String,String> hm = new HashMap<>(12);
        hm.add("a","a");
        hm.add("b","b");
        hm.add("c","c");
        hm.add("d","d");
        hm.add("f","f");
        hm.add("g","g");
        hm.add("a","h");
        hm.add("b","g");
        hm.add("c","d");
        hm.add("d","k");
        hm.add("f","l");
        hm.add("g","n");
        hm.add("z","g");
        hm.add("x","g");
        hm.add("y","g");
        hm.add("m","g");
        hm.add("l","g");
        hm.add("n","g");


        hm.add("偶买噶","g");
        hm.add("玛玛哈哈","g");
        hm.add("不知火舞","g");
        hm.add("橘右京","g");
        hm.add("露娜","g");
        hm.add("美特斯邦威","g");
        hm.add("美国队长" ,"g");

        hm.print();
        System.out.println(hm.size());
        System.out.println("=====================");
        hm.remove("n");
        hm.remove("b");
        hm.remove("z");
        hm.print();
        System.out.println(hm.size());

    }
}

测试结果如下
在这里插入图片描述
可以看到添加时当key值相同,value会被替换。value值可以相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值