Java小白一文讲清Java中集合相关的知识点(八)

HashMap底层机制及源码剖析

在这里插入图片描述

  • HashMap底层维护了Node类型的数组table,默认为null

  • 当创建对象时,将加载因子初始化为0.75;

  • 当添加key-value时,通过key的哈希值得到在table的索引,然后判断该索引处是否有元素,如果没有元素直接添加,如果该索引处有

    元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理,如果添加时发现容量不够,则需要扩容 resize();

  • 第一次添加,则需要扩容table容量为16,临界值threshold为12

  • 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推;

  • 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>= MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树);

HashMap源码解读

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //如果table的索引位置的key的hash和新的key的hash值相同,并
            //满足(table 现有的结点的key和准备添加的key是同一个对象  || equals返回真,就认为不能加入新的k-v)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果当前的table的已有的Node是红黑树,就按照红黑树的方式来处理
            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) //>= (8-1=7)
                            //树化,当然这里不是立即树化哈,深挖进去,会发现还要满足table的长度>=64
                            //才会树化,反之不符合条件就选择扩容,不急着树化
                            treeifyBin(tab, hash);
                        //我找了一圈,发现没有找到和我一样的,就把我加到屁股后面,然后退出
                        break;
                    }
                    //循环比较的过程中,我发现有一个是相同的,即hash值也相同||值相同,我就退出
                    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;//替换找到的重复key对应的value
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//每成功增加一个,就++
        if (++size > threshold)//12--24--48,一旦发现到了threshold临界值,就进入分支开始扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }




//树化部分的源码
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //如果table暂时为null,或者大小还没有到 64,暂时不树化,而是进行扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if (
            .......
        }
    }

HashTable

  • 存放的元素是键值对: 即 k-v
  • hashtable的键和值都不能为null,否则会抛出空指针异常
  • hashTable使用方法基本上和HashMap一样
  • hashTable是线程安全的(因为有synchronized修饰),hashMap是线程不安全的
  • 底层源码剖析
//简单说明一下HashTable的底层
/**
1.底层有数组Hashtable$Entry[]  初始化大小为11
2.临界值 threshold 为 8--> 11*0.75;
3.扩容:按照自己的扩容机制来进行
4.By  执行 addEntry(hash, key, value, index);
	添加K-V ,将其封装到Entry中去
5.当if (count >= threshold) 满足时,就进行扩容
6.按照int newCapacity = (oldCapacity << 1) + 1; 的大小扩容
*/

public synchronized V put(K key, V value) {
    if (value == null) {
            throw new NullPointerException();
        }
    // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
//hashtable的底层是entry数组,通过以下的add方法将key、value放进entry中,然后添加到数组中去
        addEntry(hash, key, value, index);
        return null;
    }

}


//addEntry方法底层
    private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        //当存放的元素数量大于临界值
        if (count >= threshold) {
           //进入这个方法
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }


//rehash()底层源码
    @SuppressWarnings("unchecked")
    protected void rehash() {
        //oldCapacity起初为11嘛
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // 扩容后新的容量=oldCapacity乘以2,再+1;
        //这就是起初容量为11,之后扩容后变为11*2+1=23的原因
        int newCapacity = (oldCapacity << 1) + 1;
        //如果新容量-数组最大值2147483639仍然大于0,则进入分支
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

HashMap和HashTable之间的对比

线程安全(同步)效率允许null键 null值
HashMap不安全可以
HashTable安全较低不可以

Properities

  • Properities类继承自HashTable类并且实现了Map接口,也是使用了一种键值对的形式来保存数据
  • 它的使用特点和HashTable类似
  • Properities还可以用于从xxx.properities文件中,加载数据到Properties类对象,并进行读取和修改
  • 说明:工作后,xxxx.properities文件通常作为配置文件,这个知识点在IO流举例;

TreeSet

  • 自动排序,TreeSet中的元素会自动按照自然顺序,例如数字从小到大,字母按照字典顺序,或者通过自定义的比较器排列;
  • 不允许重复元素,TreeSet会检查插入的元素是否已经存在,如果存在相同的元素,新的元素将不会被添加
      public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        //1.当我们使用无参构造器,创建TreeSet时,仍然是无序的
        treeSet.add("jason");
        treeSet.add("R");
        treeSet.add("K");
        treeSet.add("AAA");

        System.out.println(treeSet);
        //[AAA, K, R, jason]

        //2.我希望添加的元素,可以按照字符串大小来排序
        //3.我们可以使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)
        //  并指定排序规则
        TreeSet treeSetNB = new TreeSet(new Comparator() {

//源码解读,当传给TreeSet一个比较器时,深挖底层,我们会发现其将传入的比较器对象 赋给了TreeSet底层的
//TreeMap的属性this.comparator,也就是将匿名对象,传给了底层TreeMap的comparator属性
            /**
	public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }*/
//在调用treeSet.add("AAA")时,在底层会执行到
            /**
            //此中的cpr就是我们的匿名内部类(对象)
            	Comparator<? super K> cpr = comparator;
                    if (cpr != null) {
                        do {
                            parent = t;
                            //动态绑定到我们的匿名内部类(对象) compare
                            cmp = cpr.compare(key, t.key);
                            if (cmp < 0)
                                t = t.left;
                            else if (cmp > 0)
                                t = t.right;
                                //如果相等,即返回0,那么这个key就没有加入
                            else
                                return t.setValue(value);
                        } while (t != null);
       		    }
            */
            @Override
            public int compare(Object o1, Object o2) {
                //这里的这句话调用字符串的compareTo方法,默认按照字符串来比较大小
                //这里实现从大到小的比较规则
                return ((String)o2).compareTo((String)o1);
            }
        });
        treeSetNB.add("jason");
        treeSetNB.add("K");
        treeSetNB.add("R");
        treeSetNB.add("AAA");

        System.out.println("添加了比较器后的treeSet:"+treeSetNB);



    }



TreeMap

  • 键的排序:TreeMap按照键的自然顺序,例如数字从小到大,字母按字典顺序,或者根据提供的Comparator 进行排序;

    ​ 因此,插入元素时会自动排序,而不是按照插入的顺序存储;

  • 不允许null键:TreeMap不允许null作为键,这与HashMap允许一个nulll键不同,但是TreeMap允许null值,可以有多个null值;

    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap();
        treeMap.put("1",null);
        treeMap.put("2",null);
        System.out.println(treeMap);
        //{1=null, 2=null}
    }

Collection工具类

  • Collection是一个操作Set、List和Map等集合的工具类
  • Collection中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
  • 排序操作
  • reverse(List):反转List中元素的顺序
  • shuffle(List):对List集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定List集合元素按升序排序
  • sort(List,Comparator):根据指定的Comparator 产生的顺序对List 集合元素进行排序
  • swap(List,int,int):将指定List集合中的 i 处元素和 j 处元素进行交换
    public static void main(String[] args) {
        //创建ArrayList 集合,用于测试。
        ArrayList list = new ArrayList();
        list.add("kerwin");
        list.add("h活在当下hhh");
        list.add("AA感受每一段旅程");
        list.add("moment");

        System.out.println(list);
        //[kerwin, h活在当下hhh, AA感受每一段旅程, moment]

        //reverse 反转
        Collections.reverse(list);
        System.out.println(list);
        //[moment, AA感受每一段旅程, h活在当下hhh, kerwin]

        //shuffle  随机排序
        Collections.shuffle(list);
        System.out.println(list);
        //[h活在当下hhh, AA感受每一段旅程, kerwin, moment]

        //sort  根据元素的自然顺序对指定的List集合元素按升序排序
        Collections.sort(list);
        System.out.println(list);
        //[AA感受每一段旅程, h活在当下hhh, kerwin, moment]

        //sort(list , Comparator) 传入比较器进行排序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String str1 = (String)o1;
                String str2 = (String)o2;
                return str2.compareToIgnoreCase(str1);
            }
        });
        System.out.println(list);
        //[moment, kerwin, h活在当下hhh, AA感受每一段旅程]

        //swap  将指定List集合中的i 处元素和 j 处元素进行交换;
        Collections.swap(list,0,1);b `	1N2QWA
        System.out.println(list);
        //[kerwin, moment, h活在当下hhh, AA感受每一段旅程]
    }

  • 查找、替换
  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection ,Comparator):根据Comparator指定的顺序返回给定集合中的最大元素
  • Object min(Collection)
  • Object max(Collection,Comparator)
  • int frequency(Collection ,Object):返回指定集合中指定元素的出现次数
  • void copy(List dest, List src):将src 中的内容复制到dest中
  • boolean replaceAll(List list ,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
    public static void main(String[] args) {
        //创建ArrayList 集合,用于测试。
        ArrayList list = new ArrayList();
        list.add("kerwin");
        list.add("hhhh");
        list.add("AAjourney");
        list.add("moment");

        System.out.println(list);
        //[kerwin, hhhh, AAjourney, moment]


        //Object max(Collection);  返回给定集合中的最大元素
        System.out.println("自然顺序下的最大元素");
        System.out.println(Collections.max(list));
        //moment

        //自定义比较器,然后返回自定义规则下的最大元素
        //比如,我要返回长度最大的元素
        Object maxObject = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String str1 = (String)o1;
                String str2 = (String)o2;

                return str1.length()-str2.length();
                /**
                 * return ((String)o1).length()-((String)o2).length();
                 */
            }
        });
        System.out.println("自定义比较器下的最长元素");
        System.out.println(maxObject);
        //AAjourney

        //min(Collection)
        System.out.println("自然顺序下的最小元素:");
        System.out.println(Collections.min(list));
        //AAjourney


        //同样,实现自定义比较器,然后返回长度最小的元素
        Object minObject = Collections.min(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                /**
                 * 这里想补充一点容易犯错的点:你要像下面这样写的时候,务必要留意括号的位置,
                 * ((String)o1)  这个对应的才是String,才会有length()API可以调用
                 * 如果不留意这一点,就会频频报错hhh
                 *
                 */
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        System.out.println("自定义比较器下的最短元素");
        System.out.println(minObject);
        //hhhh

        //  int  frequency(Collection Object) 返回指定集合中指定元素出现的次数
        list.add("hhhh");
        System.out.println(list);
        //[kerwin, hhhh, AAjourney, moment, hhhh]

        int hhhhFre = Collections.frequency(list, "hhhh");
        System.out.println(hhhhFre);
        //2  即"hhhh"这个对象在集合中出现了2次

        //void copy(List dest,List src)
        ArrayList testList = new ArrayList();
        testList.add("Bingo");
        testList.add("Bingo");
        //将testList集合中的元素,复制到list中去
        Collections.copy(list,testList);
        System.out.println(testList);
        //[Bingo, Bingo]

        System.out.println(list);
        //[Bingo, Bingo, AAjourney, moment, hhhh]


        //boolean replaceAll(List list, Object oldVal, Object newVal)
        Collections.replaceAll(list,"hhhh","hhhhhDamn");
        System.out.println(list);
        //[Bingo, Bingo, AAjourney, moment, hhhhhDamn]


    }
	

拓展:比较器专题练习

      public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("cherry");
        list.add("kiwi");

        //1.按照字符串的长度来 进行从短到长排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        });
        System.out.println("按照字符串长度,由短到长排序如下:");
        System.out.println(list);
        //[kiwi, apple, banana, cherry]

        //2.按照字符串的最后一个字符从大到小排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                /**
                 * 这样也行!!!
                 *  char lastChar1 = o1.charAt(o1.length() - 1);
                    char lastChar2 = o2.charAt(o2.length() - 1);
                    return Character.compare(lastChar2, lastChar1);  // 从大到小排序
                 */
                return o2.charAt(o2.length()-1)-o1.charAt(o1.length()-1);
            }
        });
        System.out.println("按照字符串的最后一个字符从大到小排序:");
        for(String obj : list){
            System.out.print(obj+"\t");
        }
        //cherry	kiwi	apple	banana

        //3.按字符串中的元音字母数量从多到少排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return countVowels(o2) - countVowels(o1);  // 从多到少排序
            }
            private int countVowels(String str) {
                int count = 0;
                for (char c : str.toLowerCase().toCharArray()) {
                    if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                        count++;
                    }
                }
                return count;
            }
        });
        System.out.println();
        System.out.println("按字符串中的元音字母数量从多到少排序,如下:");
        System.out.println(list);
        //[banana, kiwi, apple, cherry]

        //4.按字符串字母逆序排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.toCharArray()[0]-o1.toCharArray()[0];
            }
        });
        System.out.println("按字符串字母逆序排序");
        System.out.println(list);
        //[kiwi, cherry, banana, apple]

        //5.按字符串是否以元音字母开头排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                boolean startsWithVowel1 = startsWithVowel(o1);
                boolean startsWithVowel2 = startsWithVowel(o2);

                if (startsWithVowel1 && !startsWithVowel2) {
                    return -1;
                } else if (!startsWithVowel1 && startsWithVowel2) {
                    return 1;
                } else {
                    return o1.compareTo(o2);  // 如果开头情况相同,则按字母顺序排序
                }
            }

            private boolean startsWithVowel(String str) {
                char firstChar = Character.toLowerCase(str.charAt(0));
                return firstChar == 'a' || firstChar == 'e' || firstChar == 'i' || firstChar == 'o' || firstChar == 'u';
            }
        });
        System.out.println("按字符串是否以元音字母开头排序");
        System.out.println(list);
        //[apple, banana, cherry, kiwi]

    }

//输出结果如下:
按照字符串长度,由短到长排序如下:
[kiwi, apple, banana, cherry]
按照字符串的最后一个字符从大到小排序:
cherry	kiwi	apple	banana	
按字符串中的元音字母数量从多到少排序,如下:
[banana, kiwi, apple, cherry]
按字符串字母逆序排序
[kiwi, cherry, banana, apple]
按字符串是否以元音字母开头排序
[apple, banana, cherry, kiwi]

集合总结

开发中如何选择集合实现类

  • 先判断存储的类型(一组对象或一组键值对)
  • 一组对象(单列数据):Collection接口
    • 允许重复:List
      • 增删多:LinkedList[底层维护了一个双向链表]
      • 改查多:ArrayList[底层维护了Object 类型的可变数组]
    • 不允许重复:Set
      • 无序:HashSet [底层是HashMap,维护了一个哈希表,即数组+链表+红黑树]
      • 排序:TreeSet
      • 插入和取出顺序一致:LinkedHashSet 维护数组+ 双向链表
  • 一组键值对(双列):Map
    • 键无序:HashMap [ 底层是:哈希表 jdk7:数组+链表 jdk8:数组+链表+红黑树 ]
    • 键排序:TreeMap
    • 键插入和取出顺序一致:LinkedHashMap
    • 读取文件:Properties
  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值