精华内容
下载资源
问答
  • hashmap排序原理
    多人点赞
    2022-05-21 20:46:19

    原文网址:Java--HashMap--排序--方法/实例_IT利刃出鞘的博客-CSDN博客

    简介

            本文用示例介绍HashMap排序的方法。

    排序已有数据

    按key排序

    使用stream进行排序(按key升序/降序)

    package org.example.a;
    
    import java.util.*;
    
    public class Demo {
        public static void main(String[] args) {
            Map<String, String> map = new HashMap<>();
            map.put("ad", "dd");
            map.put("bc", "ee");
            map.put("cb", "ff");
            for (Map.Entry<String, String> entry : map.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
    
            Map<String, String> linkedHashMap = new LinkedHashMap<>();
            // 默认按照升序排列
            map.entrySet().stream().sorted(Map.Entry.comparingByKey())
                    .forEach(o -> linkedHashMap.put(o.getKey(), o.getValue()));
            for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
    
            Map<String, String> linkedHashMap1 = new LinkedHashMap<>();
            // 自定义排序(降序)
            map.entrySet().stream().sorted(Map.Entry.comparingByKey(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return o2.compareTo(o1);
                }
            })).forEach(o -> linkedHashMap1.put(o.getKey(), o.getValue()));
            for (Map.Entry<String, String> entry : linkedHashMap1.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }

    执行结果

    bc:ee
    ad:dd
    cb:ff
    
    ad:dd
    bc:ee
    cb:ff
    
    cb:ff
    bc:ee
    ad:dd

    HashMap转TreeMap自定义排序(按key升序/降序)

    package org.example.a;
    
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.TreeMap;
    
    public class Demo {
        public static void main(String[] args) {
            Map<String, String> map = new HashMap<>();
            map.put("ad", "dd");
            map.put("bc", "ee");
            map.put("cb", "ff");
            for (Map.Entry<String, String> entry : map.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
            // 默认按照升序排序
            Map<String, String> map1 = new TreeMap<>();
            map.forEach(map1::put);
            for (Map.Entry<String, String> entry : map1.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
    
            // 自定义排序(降序)
            Map<String, String> map2 = new TreeMap<>(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return o2.compareTo(o1);
                }
            });
            map.forEach(map2::put);
            for (Map.Entry<String, String> entry : map2.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }
    

    执行结果

    bc:ee
    ad:dd
    cb:ff
    
    ad:dd
    bc:ee
    cb:ff
    
    cb:ff
    bc:ee
    ad:dd

    按value排序

    使用stream进行排序(按value升序/降序)

    package org.example.a;
    
    import java.util.*;
    
    public class Demo {
        public static void main(String[] args) {
            Map<String, String> map = new HashMap<>();
            map.put("ad", "dd");
            map.put("bc", "ee");
            map.put("cb", "ff");
            for (Map.Entry<String, String> entry : map.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
    
            Map<String, String> linkedHashMap = new LinkedHashMap<>();
            // 默认按照升序排列
            map.entrySet().stream().sorted(Map.Entry.comparingByValue())
                    .forEach(o -> linkedHashMap.put(o.getKey(), o.getValue()));
            for (Map.Entry<String, String> entry : linkedHashMap.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
    
            Map<String, String> linkedHashMap1 = new LinkedHashMap<>();
            // 自定义排序(降序)
            map.entrySet().stream().sorted(Map.Entry.comparingByValue(new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return o2.compareTo(o1);
                }
            })).forEach(o -> linkedHashMap1.put(o.getKey(), o.getValue()));
            for (Map.Entry<String, String> entry : linkedHashMap1.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }

    执行结果

    bc:ee
    ad:dd
    cb:ff
    
    ad:dd
    bc:ee
    cb:ff
    
    cb:ff
    bc:ee
    ad:dd

     借助List进行排序(按value升序/降序)

            原理:将待排序Map中的所有元素置于一个列表中,接着使用Collections的一个静态方法 sort(List<T> list, Comparator<? super T> c) 来排序列表,同样是用比较器定义比较规则。排序后的列表中的元素再依次装入Map,为了肯定的保证Map中元素与排序后的List中的元素的顺序一致,使用了LinkedHashMap数据类型。

            本处只写升序代码,降序只是调换个顺序而已。

    package org.example.a;
    
    import java.util.*;
    
    public class Demo {
        public static void main(String[] args) {
            Map<String, String> map = new HashMap<>();
            map.put("ad", "dd");
            map.put("bc", "ee");
            map.put("cb", "ff");
            for (Map.Entry<String, String> entry : map.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            System.out.println();
    
            Map<String, String> sortedMap = new LinkedHashMap<>();
            List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(
                    map.entrySet());
            Collections.sort(entryList, new Comparator<Map.Entry<String, String>>() {
                @Override
                public int compare(Map.Entry<String, String> me1, Map.Entry<String, String> me2) {
                    return me1.getValue().compareTo(me2.getValue());
                }
            });
            for (Map.Entry<String, String> stringStringEntry : entryList) {
                sortedMap.put(stringStringEntry.getKey(), stringStringEntry.getValue());
            }
    
            for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }

    运行结果

    bc:ee
    ad:dd
    cb:ff
    
    ad:dd
    bc:ee
    cb:ff

    按插入顺序存放

    HashMap不按插入顺序存放

    package org.example.a;
    
    import java.util.*;
    
    public class Demo{
        public static List arrayList = new ArrayList();
        public static void main(String[] args) {
            Map<String, String> hashMap = new HashMap<String, String>();
            hashMap.put("name1", "josan1");
            hashMap.put("name2", "josan2");
            hashMap.put("name3", "josan3");
            
            Set<Map.Entry<String, String>> set = hashMap.entrySet();
            Iterator<Map.Entry<String, String>> iterator = set.iterator();
            while(iterator.hasNext()) {
                Map.Entry entry = iterator.next();
                String key = (String) entry.getKey();
                String value = (String) entry.getValue();
                System.out.println("key:" + key + ", value:" + value);
            }
        }
    }

    执行结果(未按照插入顺序输出)

    key:name3, value:josan3
    key:name2, value:josan2
    key:name1, value:josan1

    LinkedHashMap会按照插入顺序存放

    package org.example.a;
    
    import java.util.*;
    
    public class Demo{
        public static List arrayList = new ArrayList();
        public static void main(String[] args) {
            Map<String, String> hashMap = new LinkedHashMap<String, String>();
            hashMap.put("name1", "josan1");
            hashMap.put("name2", "josan2");
            hashMap.put("name3", "josan3");
    
            Set<Map.Entry<String, String>> set = hashMap.entrySet();
            Iterator<Map.Entry<String, String>> iterator = set.iterator();
            while(iterator.hasNext()) {
                Map.Entry entry = iterator.next();
                String key = (String) entry.getKey();
                String value = (String) entry.getValue();
                System.out.println("key:" + key + ", value:" + value);
            }
        }
    }

    执行结果(按照插入顺序输出)

    key:name1, value:josan1
    key:name2, value:josan2
    key:name3, value:josan3 

    更多相关内容
  • 这篇文章主要介绍了Java HashMap两种简便排序方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下HashMap的储存是没有顺序的,而是按照key的HashCode实现....

    这篇文章主要介绍了Java HashMap两种简便排序方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    HashMap的储存是没有顺序的,而是按照key的HashCode实现.

    key=手机品牌,value=价格,这里以这个例子实现按名称排序和按价格排序.

    Map phone=new HashMap();

    phone.put("Apple",8899);

    phone.put("SAMSUNG",7000);

    phone.put("Meizu",2698);

    phone.put("Xiaomi",1800);

    System.out.println(phone);

    直接输出HashMap得到的是一个无序Map(不是Arraylist那种顺序型储存)

    b5502e7a5806082443f09ecacefff7c0.png

    1. 按key排序

    对名称进行排序,首先要得到HashMap中键的集合(keySet),并转换为数组,这样才能用Arrays.sort()进行排序

    Set set=phone.keySet();

    Object[] arr=set.toArray();

    Arrays.sort(arr);

    for(Object key:arr){

    System.out.println(key);

    }

    得到排序好的键值

    5ed8b25ec212cbbe4e72ae8704fb0cce.png

    最后利用HashMap.get(key)得到键对应的值即可

    for(Object key:arr){

    System.out.println(key+": "+phone.get(key));

    }

    得到的打印的结果

    6ab79d50983282c0bc69ea23a2189847.png

    2.按value排序

    对价格进行排序,首先需要得到HashMap中的包含映射关系的视图(entrySet),

    如图:

    b5502e7a5806082443f09ecacefff7c0.png

    将entrySet转换为List,然后重写比较器比较即可.这里可以使用List.sort(comparator),也可以使用Collections.sort(list,comparator)

    转换为list

    List> list = new ArrayList>(phone.entrySet()); //转换为list

    使用list.sort()排序

    list.sort(new Comparator>() {

    @Override

    public int compare(Map.Entry o1, Map.Entry o2) {

    return o2.getValue().compareTo(o1.getValue());

    }

    });

    使用Collections.sort()排序

    Collections.sort(list, new Comparator>() {

    @Override

    public int compare(Map.Entry o1, Map.Entry o2) {

    return o2.getValue().compareTo(o1.getValue());

    }

    });

    两种方式结果输出

    //for循环

    for (int i = 0; i < list.size(); i++) {

    System.out.println(list.get(i).getKey() + ": " + list.get(i).getValue());

    }

    //for-each循环

    for (Map.Entry mapping : list){

    System.out.println(mapping.getKey()+": "+mapping.getValue());

    }

    遍历打印输出

    //for

    for (int i = 0; i < list.size(); i++) {

    System.out.println(list.get(i).getKey() + ": " +list.get(i).getValue());

    }

    System.out.println();

    //for-each

    for (Map.Entry mapping : list) {

    System.out.println(mapping.getKey() + ": " +mapping.getValue());

    }

    结果

    e85f5e6345e1df48ab76acc92c3584ff.png

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    展开全文
  • HashMap自动排序的简单剖析

    千次阅读 2020-09-10 11:16:53
    当尝试向HashMap中存入int类型的key,可以看到在输出的时候会自动排序 HashMap<Integer, String> map = new HashMap<>(); map.put(3, "asdf"); map.put(2, "asdf"); map.put(1, "asdf"); map.p

    1.HashMap概述

    HashMap是无序的,这里无序的意思是你取出数据的顺序与你存入数据的顺序不同

    2.发现问题

    当尝试向HashMap中存入int类型的key,可以看到在输出的时候会自动排序

    HashMap<Integer, String> map = new HashMap<>();
            map.put(3, "asdf");
            map.put(2, "asdf");
            map.put(1, "asdf");
            map.put(6, "asdf");
            map.put(5, "asdf");
            map.put(4, "asdf");
            map.put(8, "asdf");
            map.put(9, "asdf");
            map.put(7, "asdf");
            map.put(0, "asdf");
    

    输出
    在这里插入图片描述

    3.实现原理

    我们都知道,HashMap是数组加链表实现的,在链表长度大于8的时候将链表转化为红黑树
    数组加链表画一下模型图是这样的,黑色的是数组,橙色的是链表,遍历HashMap的key的时候,先遍历第一列,然后第二列。。。
    在这里插入图片描述

    4.翻看源码

    HashMap的默认数组长度为16,默认负载因子是0.75,意思就是当数组内不为null的元素大于(数组长度*负载因子)的时候就会拓容数组
    如果数组长度和负载因子都是默认值,那当在数组中存入第13个元素后就会拓容
    16*0.75=12

        /**
         * 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;
    

    再读一下put方法和hash方法

    	public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
        //获取key的hash,当key为int类型的时候hash=key的值
        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            //tab就是HashMap的数组,这句话就是初始化数组
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //如果根据hash值来判断将此元素放在什么位置,如果数组当前位置
            //为空直接存放,成为一个长度为一的链表
            if ((p = tab[i = (n - 1) & hash]) == null)//==================
                tab[i] = newNode(hash, key, value, null);
            //如果不是,则将当前元素放在当前位置下元素的后边形成链表
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
            //拓容数组
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    

    5.分析

    17行:if ((p = tab[i = (n - 1) & hash]) == null)
    (n - 1) & hash
    n-1就是数组的length-1,现在数组长度是16,
    15&hash,
    例如,现在存入key为1,
    15转成二进制为1111
    1 转成二进制为0001,
    所以i=15&1=1;
    现在1就存放在数组下标为1的位置
    如果放2,那就放在数组下标为2的位置,
    如果再存放17的话,
    15 01111
    17 10001
    15&17=1;因为数组下标1的位置有上一次存放的key为1的元素,所以就将key=17的元素挂在key=1的下边,
    这是遍历HashMap的key就会变成1,17,2
    顺序就会乱掉,现在数组的长度是16,已使用的是2,还没有达到拓容那一步,

    6.验证

    下边的代码是存放11个数据,拓容要存入第13个数据时进行拓容

    HashMap<Integer, String> map = new HashMap<>();
            map.put(3, "asdf");
            map.put(2, "asdf");
            map.put(1, "asdf");
            map.put(6, "asdf");
            map.put(5, "asdf");
            map.put(4, "asdf");
            map.put(8, "asdf");
            map.put(9, "asdf");
            map.put(7, "asdf");
            map.put(0, "asdf");
    
            map.put(17,"saf");
    //        map.put(10,"saf");
    //        map.put(11,"saf");
            for (int i : map.keySet()) {
                System.out.println("key=" + i);
            }
            System.out.println("map.size()===============" + map.size());
    

    下边这段代码的输出结果就是
    在这里插入图片描述
    和分析的一样,
    如果再添加两个数据,使其拓容

    HashMap<Integer, String> map = new HashMap<>();
            map.put(3, "asdf");
            map.put(2, "asdf");
            map.put(1, "asdf");
            map.put(6, "asdf");
            map.put(5, "asdf");
            map.put(4, "asdf");
            map.put(8, "asdf");
            map.put(9, "asdf");
            map.put(7, "asdf");
            map.put(0, "asdf");
    
            map.put(17,"saf");
            map.put(10,"saf");
            map.put(11,"saf");
            for (int i : map.keySet()) {
                System.out.println("key=" + i);
            }
            System.out.println("map.size()===============" + map.size());
    

    输出是
    在这里插入图片描述
    又排好了顺序

    7.结论

    当所有key的hash的最大值<数组的长度-1时HashMap可以将存入的元素按照key的hash从小到大排序
    不过这个发现没有什么用就是了,不过看了一天源码收获不少,还看到好几种没见过的写法

    展开全文
  • HashMap 的工作原理

    2020-10-16 16:03:00
    2:HashMap 的工作原理HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry接口)实现,HashMap 通过 put & get 方法存储和获取。 存储对象时,将 K/V 键值...

    1:HashMap 的数据结构?
    A:哈希表结构(链表散列:数组+链表)实现,结合数组和链表的优点。当链表长度超过 8 时,链表转换为红黑树。

    transient Node<K,V>[] table;

    2:HashMap 的工作原理?

    HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry接口)实现,HashMap 通过 put & get 方法存储和获取。

    存储对象时,将 K/V 键值传给 put() 方法:

    ①、调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;

    ②、调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n);

    ③、i.如果 K 的 hash 值在 HashMap 中不存在,则执行插入,若存在,则发生碰撞;

    ii.如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 true,则更新键值对;

    iii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。

    (JDK 1.7 之前使用头插法、JDK 1.8 使用尾插法)(注意:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树)

    获取对象时,将 K 传给 get() 方法:①、调用 hash(K) 方法(计算 K 的 hash 值)从而获取该键值所在链表的数组下标;②、顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。

    hashCode 是定位的,存储位置;equals是定性的,比较两者是否相等。

    3.当两个对象的 hashCode 相同会发生什么?

    因为 hashCode 相同,不一定就是相等的(equals方法比较),所以两个对象所在数组的下标相同,"碰撞"就此发生。又因为 HashMap 使用链表存储对象,这个 Node 会存储到链表中。为什么要重写 hashcode 和 equals 方法?推荐看下。

    4.你知道 hash 的实现吗?为什么要这样实现?

    JDK 1.8 中,是通过 hashCode() 的高 16 位异或低 16 位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。

    5.为什么要用异或运算符?

    保证了对象的 hashCode 的 32 位值只要有一位发生改变,整个 hash() 返回值就会改变。尽可能的减少碰撞。

    6.HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题?

    ①、table 数组大小是由 capacity 这个参数确定的,默认是16,也可以构造时传入,最大限制是1<<30;](ht

    ②、loadFactor 是装载因子,主要目的是用来确认table 数组是否需要动态扩展,默认值是0.75,比如table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时,table就需要动态扩容;

    ③、扩容时,调用 resize() 方法,将 table 长度变为原来的两倍(注意是 table 长度,而不是 threshold)](h

    ④、如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。

    7.HashMap中put方法的过程?

    答:“调用哈希函数获取Key对应的hash值,再计算其数组下标;

    如果没有出现哈希冲突,则直接放入数组;如果出现哈希冲突,则以链表的方式放在链表后面;

    如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;

    如果结点的key已经存在,则替换其value即可;

    如果集合中的键值对大于12,调用resize方法进行数组扩容。”

    8.数组扩容的过程?

    创建一个新的数组,其容量为旧数组的两倍,并重新计算旧数组中结点的存储位置。结点在新数组中的位置只有两种,原下标位置或原下标+旧数组的大小。

    9.拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

    之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。推荐:面试问红黑树,我脸都绿了。

    [而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

    10.说说你对红黑树的见解?

    • 每个节点非红即黑
    • 根节点总是黑色的
    • 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
    • 每个叶子节点都是黑色的空节点(NIL节点)
    • 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

    11.jdk8中对HashMap做了哪些改变?

    在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)

    发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入

    在java 1.8中,Entry被Node替代(换了一个马甲。

    12.HashMap,LinkedHashMap,TreeMap 有什么区别?

    HashMap 参考其他问题;

    LinkedHashMap 保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录肯定是先插入的;遍历比 HashMap 慢;

    TreeMap 实现 SortMap 接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器)

    13.HashMap & TreeMap & LinkedHashMap 使用场景?

    一般情况下,使用最多的是 HashMap。

    HashMap:在 Map 中插入、删除和定位元素时;

    TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下;

    LinkedHashMap:在需要输出的顺序和输入的顺序相同的情况下。

    14.HashMap 和 HashTable 有什么区别?

    ①、HashMap 是线程不安全的,HashTable 是线程安全的;

    ②、由于线程安全,所以 HashTable 的效率比不上 HashMap;

    ③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;

    ④、HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;

    ⑤、HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode

    15.Java 中的另一个线程安全的与 HashMap 极其类似的类是什么?同样是线程安全,它与 HashTable 在线程同步上有什么不同?

    ConcurrentHashMap 类(是 Java并发包 java.util.concurrent 中提供的一个线程安全且高效的 HashMap 实现)。

    HashTable 是使用 synchronize 关键字加锁的原理(就是对对象加锁);

    而针对 ConcurrentHashMap,在 JDK 1.7 中采用 分段锁的方式;JDK 1.8 中直接采用了CAS(无锁算法)+ synchronized。

    16.HashMap & ConcurrentHashMap 的区别?

    除了加锁,原理上无太大区别。另外,HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许。

    17.为什么 ConcurrentHashMap 比 HashTable 效率要高?

    HashTable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞;

    ConcurrentHashMap

    • JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。

    • JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结

      点)(实现 Map.Entry)。锁粒度降低了。

    18.针对 ConcurrentHashMap 锁机制具体分析(JDK 1.7 VS JDK 1.8)

    JDK 1.7 中,采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构,包括两个核心静态内部类 Segment 和 HashEntry。

    ①、Segment 继承 ReentrantLock(重入锁) 用来充当锁的角色,每个 Segment 对象守护每个散列映射表的若干个桶;

    ②、HashEntry 用来封装映射表的键-值对;

    ③、每个桶是由若干个 HashEntry 对象链接起来的链表

    在这里插入图片描述

    JDK 1.8 中,采用Node + CAS + Synchronized来保证并发安全。取消类 Segment,直接用 table 数组存储键值对;当 HashEntry 对象组成的链表长度超过 TREEIFY_THRESHOLD 时,链表转换为红黑树,提升性能。底层变更为数组 + 链表 + 红黑树。
    在这里插入图片描述
    ]

    19.ConcurrentHashMap 在 JDK 1.8 中,为什么要使用内置锁 synchronized 来代替重入锁 ReentrantLock?

    ①、粒度降低了;

    ②、JVM 开发团队没有放弃 synchronized,而且基于 JVM 的 synchronized 优化空间更大,更加自然。

    ③、在大量的数据操作下,对于 JVM 的内存压力,基于 API 的 ReentrantLock 会开销更多的内存。

    20.ConcurrentHashMap 简单介绍?

    ①、重要的常量:

    private transient volatile int sizeCtl;

    当为负数时,-1 表示正在初始化,-N 表示 N - 1 个线程正在进行扩容;

    当为 0 时,表示 table 还没有初始化;

    当为其他正数时,表示初始化或者下一次进行扩容的大小。

    ②、数据结构:

    Node 是存储结构的基本单元,继承 HashMap 中的 Entry,用于存储数据;

    TreeNode 继承 Node,但是数据结构换成了二叉树结构,是红黑树的存储结构,用于红黑树中存储数据;

    TreeBin 是封装 TreeNode 的容器,提供转换红黑树的一些条件和锁的控制。

    ③、存储对象时(put() 方法):

    如果没有初始化,就调用 initTable() 方法来进行初始化;

    如果没有 hash 冲突就直接 CAS 无锁插入;

    如果需要扩容,就先进行扩容;

    如果存在 hash 冲突,就加锁来保证线程安全,两种情况:一种是链表形式就直接遍历

    到尾端插入,一种是红黑树就按照红黑树结构插入;

    如果该链表的数量大于阀值 8,就要先转换成红黑树的结构,break 再一次进入循环

    如果添加成功就调用 addCount() 方法统计 size,并且检查是否需要扩容。

    ④、扩容方法 transfer():默认容量为 16,扩容时,容量变为原来的两倍。

    helpTransfer():调用多个工作线程一起帮助进行扩容,这样的效率就会更高。

    ⑤、获取对象时(get()方法):

    计算 hash 值,定位到该 table 索引位置,如果是首结点符合就返回;

    如果遇到扩容时,会调用标记正在扩容结点 ForwardingNode.find()方法,查找该结点,匹配就返回;

    以上都不符合的话,就往下遍历结点,匹配就返回,否则最后就返回 null。

    21.ConcurrentHashMap 的并发度是什么?

    程序运行时能够同时更新 ConccurentHashMap 且不产生锁竞争的最大线程数。默认为 16,且可以在构造函数中设置。

    当用户设置并发度时,ConcurrentHashMap 会使用大于等于该值的最小2幂指数作为实际并发度(假如用户设置并发度为17,实际并发度则为32)

    展开全文
  • HashMap底层实现原理详解

    万次阅读 多人点赞 2021-02-13 22:41:16
    提示:以下是本篇文章对HashMap的实现原理内容,下面案例可供参考 提示:以下是本篇文章正文内容,下面案例可供参考 一、快速入门 示例:有一定基础的小伙伴们可以选择性的跳过该步骤 HashMap是Java程序员使用频率...
  • Hashmap实现原理及扩容机制详解

    万次阅读 多人点赞 2019-04-25 19:04:32
    HashMap实现原理 Node和Node链 拉链法 关于Node数组 table 散列算法 HashMap和红黑树 关于TreeNode 红黑树基础 HashMap扩容机制 JDK1.7下的扩容机制 JDK1.8下的扩容机制 HashMap基础 HashMap继承了...
  • HashMap 源码和底层原理在现在面试中是必问的。因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合。文章较长,介绍了很多原理性的问题,希望对你有所帮助~ 目录 本篇文章...
  • 面试题1:说一下 HashMap 的实现原理? 追问1:如何实现HashMap的有序? 追问2:那TreeMap怎么实现有序的? 追问3:put方法原理是怎么实现的? 追问4:HashMap扩容机制原理 追问5:HashMap在JDK1.8都做了哪些...
  • HashMap扩容原理

    万次阅读 多人点赞 2021-07-08 14:34:07
    本篇文章分别讲解JDK1.7和JDK1.8下的HashMap底层实现原理 文章目录一、什么是HashMap?二、为什么要使用HashMap?三、HashMap扩容为什么总是2的次幂四、JDk1.7扩容死循环问题五、JDK1.8的新结构1.为什么非要使用红黑...
  • 二:HashMap的工作原理【put()/get()方法工作原理】 三:在多线程的情况重新调整HashMap大小存在什么问题吗? 四:HashMap是非线程安全【非synchronized】 转发文章地址:...
  • HashMap原理和实现

    2022-03-31 09:47:38
    Map和Set 使用场景: 课程主要讲的是HashMap和HashSet
  • HashMap底层原理

    2019-08-14 20:18:29
    HashMap线程不安全,Hashtable线程安全;HashMap允许K/V都为null;后者K/V都不允许为null;HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类; ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的...
  • 文章目录 一、快速入门 1.HashMap的常用方法 2.HashMap的几个重要知识点 ... 五、HashMap存储原理与存储流程 1.HashMap存储原理 2.HashMap存储流程 六、jdk8中HashMap为什么要引入红黑树? 七、扩容后...
  • ArrayList、LinkedList 和 HashMap 底层原理

    千次阅读 2020-03-22 18:23:01
    ArrayList、LinkedList 和 HashMap 底层原理 1.LinkedList 和 ArrayList底层源码分析 ArrayList:作为List的主要实现类;线程不安全,效率高;底层使用数组实现。 LinkedList:对于频繁的插入、删除操作,建议使用...
  • 发现问题当尝试向HashMap中存入int类型的key,可以看到在输出的时候会自动排序HashMap map = new HashMap<>();map.put(3, "asdf");map.put(2, "asdf");map.put(1, "asdf");map.put(6, "...
  • 1.HashMap底层实现 JDK1.8中HashMap的put()和get()操作的过程 put操作: ①首先判断数组是否为空,如果数组为空则进行第一次扩容(resize) ②根据key计算hash值并与上数组的长度-1(int index = key.hashCode...
  • 单列集合:Collection List、Set、Queue List--> ArrayList、LinkedList、Vector Set--> HashSet、LinkedHashSet、TreeSet 单列集合:Map ...Hashtable、LinkedHashMap、HashMap...
  • 浅析HashMap底层原理

    2022-03-28 20:43:40
    HashMap底层是基于数组+链表+红黑树。 默认初始容量为(数组长度为16),默认负载系数为0.75(这个表示的意思是扩容机制当容量达到75%的时候自动进行扩容,当扩容的时候,会创建新的数组,以前存放的数组将会重新...
  • HashMap底层原理—个人理解 需再深入 jdk1.7 底层的数据结构是 数组+ 链表 put 存值和取值 entry对象 根据 entry对象利用hash算法 算出该hash值.然后再取余,可以得到该对象再数组的位置,(k,v,hash,指针) 因为不同的...
  • HashMap底层原理实现

    2019-03-27 19:41:19
    HashMap底层原理实现 1.Map的遍历方式 Map遍历方式一般分两种: Entryset():返回带有映射关系的Set视图;拿到视图之后可用foreach遍历输出打印出key–value。 Keyset(): 返回只带Key的Set视图;拿到视图之后可用...
  • HashMap底层实现原理

    千次阅读 2021-01-12 19:32:24
    1 HashMap底层实现原理 1.1 底层使用的数据结构 JDK8之前:数组、链表 JDK8之后:数组、链表、红黑树 1.2 重要字段 transient int size; //当前存储的键值对总数 int threshold; //阈值,默认为16 final float ...
  • HashMap如何按值value排序

    万次阅读 多人点赞 2019-01-24 17:06:15
    HashMap如何按值value排序简单介绍相关组件HashMapComparatorCollections.sort方法实现原理简介实现代码tips 简单介绍相关组件 HashMap 我们知道,HashMap是一个数组和链表组成的一种链表散列结构,存储方式是根据...
  • HashMap原理分析数据存储方式和查询效率 内存数据的组织方式有很多如 数组 采用开辟连续空间的方式存储 优点通过下标查询的时候可以计算出偏移量立即定位到元素查询速度很快 缺点 1插入或删除元素时往往需要移动元素...
  • HashMap与Hashtable区别及HashMap实现原理

    千次阅读 2016-12-04 16:52:06
    Map是编码过程中喜欢经,常使用到的容器,而HashMap和Hashtable都实现了Map的,所以我们往往会把两者进行对比。 HashMap和Hashtable区别 Hashtable是线程安全的,HashMap是非线程安全的。Hashtable是基于老的...
  • hashMap的实现原理

    2020-09-15 19:54:44
    HashMap的实现原理 HashMap的数据结构 在看Hashmap的数据结构之前先来看看数组和链表的特点 数组:寻址容易插入和删除的时候比较困难(数组有下表寻址,但是插入删除的时候下表要移动,扩容的时候也很麻烦) 链表:...
  • Java中HashMap的实现原理

    千次阅读 2019-02-03 22:21:28
    hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode...
  • 4.HashMap底层原理: 4.1HashMap的put(k,v)方法 1)先将k/v封装进Node对象中 2)通过hash()方法得出key的hash码 3)这时得出map的下标值,通过equals方法拿下标找是否有链表,拿key和链表上的每个节点的k进行...
  • hashMap底层原理

    2017-06-03 21:27:37
    之前去大华面试,问道了hashMap底层的一些东西,对于我这种毕业一年不到的人来说,就输的很惨,所以一些底层的,...这里简单的讲一下,hashTable、hashMap的实现原理都是一样的,都是基于哈希表实现的,区别就是hashT

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 67,168
精华内容 26,867
关键字:

hashmap排序原理