当前位置:   article > 正文

Java学习笔记(19)

Java学习笔记(19)

双列集合

键值对 一一对应

键值对对象 entry

Map

Put第一次给不存在的key,会把键值对添加到map中,返回null

Put给同一个key是会覆盖value的,返回被覆盖的值value

Remove根据key删除键值对,返回被删除的值value

Map遍历

键找值

把所有键放到单列集合中map.keyset()方法,获取每一个键,再调用map.get(key)获取每一个值

键值对

Map.entryset()获得每一键值对,放到一个set集合

Map.Entry<string,string>:因为Entry是Map里的一个接口,所以在用Entry接口的时候,要用Map调用

Entry.getkey()  Entry.getvalue() 分别获取key和value

Lambda遍历

Foreach

写一个匿名内部类实现biconsumer接口,创建这个匿名内部类的对象

Hashmap

但是计算哈希值的时候,只会利用键计算哈希值,和值无关

键是自定义对象,要重写hashcode和equals方法,值的话就不用

  1. package exercise;
  2. import java.util.*;
  3. public class exercise3 {
  4. public static void main(String[] args) {
  5. String[] arr = {"A", "B", "C", "D"};
  6. ArrayList<String> list = new ArrayList<>();
  7. Random r = new Random();
  8. for (int i = 0; i < 80; i++) {
  9. list.add(arr[r.nextInt(arr.length)]);
  10. }
  11. HashMap<String,Integer> hm = new HashMap<>();
  12. for (String s : list) {
  13. if (hm.containsKey(s)) {
  14. Integer i = hm.get(s);
  15. i++;
  16. hm.put(s, i);
  17. } else {
  18. hm.put(s, 1);
  19. }
  20. }
  21. System.out.println(hm);
  22. //求最大值
  23. int max = 0;
  24. Set<Map.Entry<String, Integer>> entries = hm.entrySet();
  25. for (Map.Entry<String, Integer> entry : entries) {
  26. if (entry.getValue() > max) {
  27. max = entry.getValue();
  28. }
  29. }
  30. //把和最大值相同的景点打印
  31. for (Map.Entry<String, Integer> entry : entries) {
  32. if (entry.getValue() == max) {
  33. System.out.println(entry.getKey());
  34. }
  35. }
  36. }
  37. }

Linkedhashmap

Treemap

按照键进行排序

  1. package exercise;
  2. import java.util.TreeMap;
  3. public class exercise4 {
  4. public static void main(String[] args) {
  5. String str = "aababcabcdabcde";
  6. TreeMap<Character, Integer> ts = new TreeMap<>();
  7. for (int i = 0; i < str.length(); i++) {
  8. char c = str.charAt(i);
  9. if (ts.containsKey(c)) {
  10. Integer j = ts.get(c);
  11. j++;
  12. ts.put(c, j);
  13. } else {
  14. ts.put(c, 1);
  15. }
  16. }
  17. StringBuilder sb = new StringBuilder();
  18. ts.forEach((character, integer) -> sb.append(character).append(" (").append(integer).append(") "));
  19. System.out.print(sb);
  20. }
  21. }

Hashmap的底层源码

→:是继承自Map的方法

↑:重写Map里的方法

m:是方法(同名就是构造方法,不同名就是成员方法)

f:是成员属性,可能是变量或常量

i:接口

c:内部类

Hashmap中每一个元素都是Node对象,实现entry接口

Hash:记录哈希值

Key:键

Value:值

Next:记录下一个node的地址值

Hashmap中的红黑树

Parent:父节点

Left:左子结点

Right:右子节点

Red:ture就是红色,false就是黑

Table:记录数组的地址值,装的就是每一个node对象

默认数组初始容量16

默认加载因子0.75

Hashmap最大容量2^30

Hashmap在调用空参构造的时候,底层的数组还没有创建,默认null

只是指定了加载因子为0.75

直到put的时候才真正建立数组

Hash(key):根据键计算出哈希值

onlyifAbsent:当前的数据是否保留,false就是不保留(当新的元素添加,键相同时,值就会覆盖掉原来的)

Hashmap添加元素源码分析

Hashmap添加第一个元素

数组位置为null

Putval

Resize():第一次添加元素,就会在这个方法里创建新的数组

第二种情况

数组位置不为null,键不重复,挂在下面形成链表或者红黑树

第三种情况

数组位置不为null,键重复,元素覆盖

这里会直接break

走这里

把新元素的值赋值给老元素的键值对的值

Treemap源码

添加第一个元素

//表示两个元素的键比较之后的结果

    int cmp;

//表示当前要添加节点的父节点

    Entry<K,V> parent;

//表示当前的比较规则

//如果我们是采取默认的自然排序,那么此时comparator记录的是null,cpr记录的也是null

//如果我们是采取比较去排序方式,那么此时comparator记录的是就是比较器

    Comparator<? super K> cpr = comparator;

//表示判断当前是否有比较器对象

//如果传递了比较器对象,就执行if里面的代码,此时以比较器的规则为准

//如果没有传递比较器对象,就执行else里面的代码,此时以自然排序的规则为准

    if (cpr != null) {

        do {

            parent = t;

            cmp = cpr.compare(key, t.key);

            if (cmp < 0)

                t = t.left;

            else if (cmp > 0)

                t = t.right;

            else {

                V oldValue = t.value;

                if (replaceOld || oldValue == null) {

                    t.value = value;

                }

                return oldValue;

            }

        } while (t != null);

    } else {

//把键进行强转,强转成Comparable类型的

//要求:键必须要实现Comparable接口,如果没有实现这个接口

//此时在强转的时候,就会报错。

        Comparable<? super K> k = (Comparable<? super K>) key;

        do {

//把根节点当做当前节点的父节点

            parent = t;

//调用compareTo方法,比较根节点和当前要添加节点的大小关系

            cmp = k.compareTo(t.key);

            if (cmp < 0)

//如果比较的结果为负数

//那么继续到根节点的左边去找

                t = t.left;

            else if (cmp > 0)

//如果比较的结果为正数

//那么继续到根节点的右边去找

                t = t.right;

            else {

//如果比较的结果为0,会覆盖

                V oldValue = t.value;

                if (replaceOld || oldValue == null) {

                    t.value = value;

                }

                return oldValue;

            }

        } while (t != null);

    }

//就会把当前节点按照指定的规则进行添加

    addEntry(key, value, parent, cmp < 0);

return null;

 private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {

    Entry<K,V> e = new Entry<>(key, value, parent);

    if (addToLeft)

        parent.left = e;

    else

        parent.right = e;

//添加完毕之后,需要按照红黑树的规则进行调整

    fixAfterInsertion(e);

    size++;

    modCount++;

}

private void fixAfterInsertion(Entry<K,V> x) {

//因为红黑树的节点默认就是红色的

    x.color = RED;

//按照红黑规则进行调整

//parentOf:获取x的父节点

//parentOf(parentOf(x)):获取x的爷爷节点

//leftOf:获取左子节点

    while (x != null && x != root && x.parent.color == RED) {

//判断当前节点的父节点是爷爷节点的左子节点还是右子节点

//目的:为了获取当前节点的叔叔节点

        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {

//表示当前节点的父节点是爷爷节点的左子节点

//那么下面就可以用rightOf获取到当前节点的叔叔节点

            Entry<K,V> y = rightOf(parentOf(parentOf(x)));

            if (colorOf(y) == RED) {

//叔叔节点为红色的处理方案

//把父节点设置为黑色

                setColor(parentOf(x), BLACK);

//把叔叔节点设置为黑色

                setColor(y, BLACK);

//把爷爷节点设置为红色

                setColor(parentOf(parentOf(x)), RED);

//把爷爷节点设置为当前节点

                x = parentOf(parentOf(x));

            } else {

//叔叔节点为黑色的处理方案

//表示判断当前节点是否为父节点的右子节点

                if (x == rightOf(parentOf(x))) {

//表示当前节点是父节点的右子节点

                    x = parentOf(x);

//左旋

                    rotateLeft(x);

                }

                setColor(parentOf(x), BLACK);

                setColor(parentOf(parentOf(x)), RED);

                rotateRight(parentOf(parentOf(x)));

            }

        } else {

//表示当前节点的父节点是爷爷节点的右子节点

//那么下面就可以用leftOf获取到当前节点的叔叔节点

            Entry<K,V> y = leftOf(parentOf(parentOf(x)));

            if (colorOf(y) == RED) {

                setColor(parentOf(x), BLACK);

                setColor(y, BLACK);

                setColor(parentOf(parentOf(x)), RED);

                x = parentOf(parentOf(x));

            } else {

                if (x == leftOf(parentOf(x))) {

                    x = parentOf(x);

                    rotateRight(x);

                }

                setColor(parentOf(x), BLACK);

                setColor(parentOf(parentOf(x)), RED);

                rotateLeft(parentOf(parentOf(x)));

            }

        }

    }

//把根节点设置为黑色

    root.color = BLACK;

}

**************************

6.课堂思考问题:

6.1TreeMap添加元素的时候,键是否需要重写hashCode和equals方法?

此时是不需要重写的。

6.2HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的。

既然有红黑树,HashMap的键是否需要实现Compareable接口或者传递比较器对象呢?

不需要的。

因为在HashMap的底层,默认是利用哈希值的大小关系来创建红黑树的

6.3TreeMap和HashMap谁的效率更高?

如果是最坏情况,添加了8个元素,这8个元素形成了链表,此时TreeMap的效率要更高

但是这种情况出现的几率非常的少。

一般而言,还是HashMap的效率要更高。

6.4你觉得在Map集合中,java会提供一个如果键重复了,不会覆盖的put方法呢?

此时putIfAbsent本身不重要。

传递一个思想:

代码中的逻辑都有两面性,如果我们只知道了其中的A面,而且代码中还发现了有变量可以控制两面性的发生。

那么该逻辑一定会有B面。

习惯:

boolean类型的变量控制,一般只有AB两面,因为boolean只有两个值

int类型的变量控制,一般至少有三面,因为int可以取多个值。

6.5三种双列集合,以后如何选择?

HashMap LinkedHashMap TreeMap

默认:HashMap(效率最高)

如果要保证存取有序:LinkedHashMap

如果要进行排序:TreeMap

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/284883?site
推荐阅读
相关标签
  

闽ICP备14008679号