Java学习日记2022.7.11

集合

一、集合的概念

集合在高中我们已经知道他是可以包含很多元素的一个容器,java中也不例外,集合与数组都是容器,我们在不了解集合时保存一组类型相同的元素时会用数组,但是数组一旦定义,长度将不能再变化。但是在开发时我们通常需要长度能改变的容器,因为可能随时添加删除元素。此时我们就需要一个能动态增长长度的容器去存放数据。我们需要对数据的保存的逻辑可能各种各样,于是就有了各种各样的数据结构。Java中对于各种数据结构的实现,就是我们用到的集合。

二、集合API

Java的集合框架是由很多接口、抽象类、具体类组成的,都位于java.util包中,我们画出集合的体系图如下
Java学习日记2022.7.11_第1张图片

根据体系图从上往下去理解各个接口,类的作用。

首先集合分为单列集合和双列集合

单列集合类实现接口是Collection,Collection继承Iterable接口,Iterable为集合体系最顶层接口。

Collection接口里定义了存取一组对象的方法和集合中的共有方法,在List和Set接口里被重写发挥各自的作用,所以被继承的方法在子接口里的实现是不一样的。

List接口和Set接口是继承Collection接口,用于区分容器内元素是否有序是否可重复。

List接口的实现类:

1.ArrayList

底层实现是数组,可以变长的 ,存储元素有顺序,地址是连续的,所以查询的效率很高,但是因为删除添加元素需要移动其他元素的位置,所以中间增删的效率稍微慢一点。

2.LinkedList

底层是链表实现,存储元素同样有顺序且可以重复,但地址不一定连续,查询慢(必须从头或尾开始查找,直到找到为止),中间增删快,只改变后继节点位置即可。

3.Vector
底层是数组实现,是线程安全的。

List接口集合的遍历4种方式:

​ for循环遍历
​ 增强for循环
​ Iterator迭代器

        List<String> a=new ArrayList<>();
        a.add("a");
        a.add("b");
        a.add("c");
        a.add("d");
        Iterator<String> b=a.iterator();/*使用ArrayList中的方法返回一个ArrayList类之外一个类的对象,该类继承Iterator接口,HashSet和TreeSet能使用此迭代器遍历*/
        while (b.hasNext()) { //hashNext方法从数组索引0开始遍历判断是否有元素存在
            System.out.println(b.next());//输出该索引对应的元素
        }

​ ListIterator迭代器(只支持List接口下的实现类)

        ArrayList a=new ArrayList<>();
        a.add("a");
        a.add("b");
        a.add("c");
        a.add("d");
        ListIterator b=a.listIterator();//HashSet和TreeSet不能使用此迭代器遍历
        while (b.hasNext()) {
            System.out.println(b.next());
        }
Set接口的实现类:

1.HashSet

HashSet是Set接口的一个实现类,Set接口的特点是无序可以重复,所以使用起来比较简单,但是唯一性的体现在底层是怎么实现的呢,点进去HashSet的源码会发现它的构造方法是创建了一个HashMap类的对象,HashMap这里我也提一下,后面就不大篇幅的概括了,然后再点进去它的add方法会发现HashSet的add方法就是调用了HashMap的put方法,插入的对象为key,参数PRESENT为value实际是一个空的Object对象。因为在HashMap中的put方法,会调用hashcode()方法计算出哈希值,然后再通过按位与运算(length-1)&hashcode计算出数组的下标,然后会通过p(e).hash==hash判断哈希值,再通过equals方法判断key是否存在且为同样的值最终确定是否为完全相同的key,如果相同则替换key对应的value值,不同则插入不同的key,才会增加一个结点,链接在链表上,所以key不会有相同的节点。所以HashSet的底层其实也就是HashMap,通过哈希表(数组和链表)实现。

2.TreeSet

TreeSet的底层其实就是红黑树数据结构,也就是一颗自平衡的排序二叉树。它底层实现的是Map接口下的一个实现类TreeMap,添加的数据也是存入了key的位置,value同上是一个空Object对象,不同的是TreeMap是通过哈希表(数组和红黑树)实现,TreeMap中的key值是有序且不重复的,所以TreeSet中的元素也就是有序且不重复的。

Map接口的实现类:

Map接口实现类的数据对象是双列集合 键–>值(“a”=“aaa”)键不能重复 ,值可以重复

1.HashMap

HashMap底层实现当然是要扒一扒源码了,put方法点进去,首先我们会看见下面这个返回值,所以说我们的put方法是可以用输出语句输出的,至于输出什么我们接着点进去看
在这里插入图片描述首先点进去hash(key)这个方法,发现参数key被传进去计算了一个哈希值并返回作为putVal方法的参数
Java学习日记2022.7.11_第2张图片

接着点进去putVal方法看到如下源码

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;//扩容并把数组长度赋给n
        if ((p = tab[i = (n - 1) & hash]) == null)//判断哈希值计算出的数组下标位置上有没有元素,不论有没有都将该位置的结点对象地址传给引用p。
            tab[i] = newNode(hash, key, value, null);//没有元素直接插入传进来的key和value存入一个结点对象,此时都是链表,有元素存在执行else
        else {
            Node<K,V> e; K k;//声明一个Node类的泛型引用,和key值的一个泛型定义,用于下面的判断
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))//判断该结点对象key值是否和传进来的key值完全相等
                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地址传递给e
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st判断链表长度是否大于等于8
                            treeifyBin(tab, hash);//扩容或转换为红黑树
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))//有后续结点判断key是否重复此时e已经是p.next,并把该结点的key值给k(k=e.key)如果不相等则需要往后继续检索,执行p=e,将e的地址再给p让p作为这个正在检索的结点,所以p永远都是上一个结点,继续进行循环,如果相等则跳出循环通过后续代码替换该被检索结点的value的值
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key//和传进来的key值不相等直接替换value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

实现过程已在代码内解释。

2.TreeMap就是HashMap哈希表上的排列从链表转化成了红黑树。

Map接口集合的遍历方式:

方式1:根据键找值
获取所有键的集合
遍历键的集合,获取到每一个键
根据键找值

       HashMap<String,String> a = new HashMap<>();
        a.put("a","1");
        a.put("b","3");
        a.put("c","2");
        Set<String> key= a.keySet();/*HashMap类中一个内部类KeySet间接继承了Set接口,调用HashMap里重写的keySet方法new一个KeySet对象,该类和其他集合类一样将key值存在一个新的集合里*/
        for (String b:key){
            System.out.println(b+"="+a.get(b));
        }

方式2:根据键值对对象找键和值
获取所有键值对对象的集合
遍历键值对对象的集合,获取到每一个键值对对象
根据键值对对象找键和值

HashMap<String,String> a = new HashMap<>();
        a.put("a","1");
        a.put("b","3");
        a.put("c","2");
        Set<Map.Entry<String, String>> entrySet = a.entrySet();
        for(Map.Entry<String,String> b:entrySet){
            System.out.println(b.getKey()+"="+b.getValue());
        }

Node结点类实现了Entry接口,该接口提供了获取键值对的抽象方法,
Java学习日记2022.7.11_第3张图片
调用HashMap里的entrySet()方法将键值对以对象的形式存在Set集合里
Java学习日记2022.7.11_第4张图片

你可能感兴趣的:(java,学习,开发语言)