集合结构图

集合分为两大类,分别是 Collection、Map

Collection类集合的结构图如下:

Map类集合结构图如下:

接下来就是分别介绍两种集合的各个实现类。

重点是HashMap等源码分析

1.Collection接口和常用方法

1.1 Collecton接口实现类特点

  • collection实现子类可以存放多个元素,元素的类型是Object

  • collection下的List接口的实现类,可以存放相同元素 并且 元素有序

  • collection下的Set接口的实现类,不能存放相同元素 并且 元素无需

1.2 Collection接口常用方法

这里举例使用ArrayList来演示常用方法,因为对于这些实现类都是通过 List或Set 接口间接实现了Collection接口,所以Collection接口中定义的方法都是通用的。

package com.llh.learn1;

import java.util.ArrayList;
import java.util.List;

public class CollectionMethod {
    public static void main(String[] args) {
        // Collection接口常用的方法。这里主要以ArrayList来举例(这些方法Collection接口的实现类通用)
        ArrayList list = new ArrayList();

        // 添加元素 add(Object)
        list.add("a");
        // 在指定下标位置插入元素  (因为Set集合是无序的,即没有元素下标索引,所以这里在指定位置插入的方法对于Set集合是无法使用的。)
        list.add(0,"d");

        // 批量添加元素(将一个集合追加到集合后)
        list.addAll(List.of("a1","a2","a3"));
        System.out.println(list);

        // 删除元素
        list.remove("a2");
        list.remove(0);

        // 查找元素是否存在
        System.out.println(list.contains("a2"));
        System.out.println(list.containsAll(List.of("a1","a3")));

        // 获得元素的个数
        System.out.println(list.size());

        // 判断集合是否为空
        System.out.println(list.isEmpty());

        // 清空集合内的元素
        list.clear();

        // 如下代码可以看出来 remove(Object) 方法 一次只删除一个匹配的元素
        list.addAll(List.of("1","1","1","1","1"));
        System.out.println(list);
        list.remove("1");
        System.out.println(list);
    }
}

1.3 集合的遍历方法

遍历集合可以通过 Iterator迭代器 或者 增强for循环 两种方式来遍历,前提是这个集合类要继承或实现 Iteratorable 接口。

1.3.1 使用Iterator迭代器

Iterator称为迭代器,主要用来遍历Collection集合中的元素 ,也可以用来遍历数组。
从Collection接口继承自 Iteratorable 接口就可以看出来 Iterator会与Collection有关系。Iteratorable 表示可迭代的意思,而Colleciton又是Iteratorable接口的子接口,所有就可以使用 Iterator。

iterator迭代器工作原理

使用Iterator的方式进行遍历

package com.llh.learn1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionIterator {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        for (int x = 1;x<=10;x++){
            list.add("元素:" + x);
        }
        System.out.println(list);
        System.out.println("\n使用迭代器");
        useIterator(list);
    }
    // 定义使用迭代器来实现遍历的方法
    public static void useIterator(Collection c){
        // 获取关于该集合的iterator迭代器对象
        Iterator iterator = c.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

1.3.2 使用增强for循环

增强for循环是Iterator迭代器的简化,也是用来遍历 Collection集合、数组 使用的。
增强for循环

package com.llh.learn1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionIterator {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        for (int x = 1;x<=10;x++){
            list.add("元素:" + x);
        }
        System.out.println(list);

        System.out.println("\n使用增强for循环");
        useEnhanceFor(list);

        System.out.println("===================");
    }
  // 使用增强for循环来遍历Collection集合
    public static void useEnhanceFor(Collection c){
        for (Object o : c){
            System.out.println(o);
        }
    }
}

当使用增强 for 循环遍历实现了Iterable接口的对象(如集合类List、Set等)时,编译器会自动将其转换为使用迭代器的代码。

本质上,下面的增强 for 循环代码:

for (元素类型 变量名 : 可迭代对象) {
    // 循环体
}

会被编译器转换为类似这样的迭代器代码:

Iterator<元素类型> iterator = 可迭代对象.iterator();while (iterator.hasNext()) {
    元素类型 变量名 = iterator.next();
    // 循环体
}

所以,当你使用 增强for循环来遍历集合的时候,底层其实还是会创建出一个关于该集合的Iterator对象,这个是需要注意的。

1.4 List集合的源码实现

1.4.1 ArrayList底层结构和源码分析

ArrayList底层机制:

  • ArrayList底层实际上基于一个Object类型的数组 elementData。 transient Object[] elementdata; // 这个transient表示短暂的、瞬间,表示该修饰符修饰的属性不会被序列化

  • 当创建ArrayList的时候,如果使用的是空参构造器,则初始化elementData的容量为0,添加第一个元素时elementData容量会扩容到10。如果需要再次扩容,那么就扩容当当前容量的1.5倍

  • 如果使用指定大小的构造器,则初始化elementData的容量为构造器中指定的大小,如果需要再次扩容,同样扩容到当前容量的1.5倍

ArrayList的源码分析:

public class ArrayListSource {
    public static void main(String[] args) {
        // 使用空参构造器
        /** private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
           public ArrayList() {
                    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;       // 把一个空数组赋值给elementData
                }
         */
        ArrayList list = new ArrayList();
        // 开始添加操作
        /**     public boolean add(E e) {
                    modCount++;                             // 修改更改记录数
                    add(e, elementData, size);              // 添加操作
                    return true;
                }
            // add方法的代码
                private void add(E e, Object[] elementData, int s) {       // 这个s是该类的一个变量,默认值为0 (作用是用来指定元素要在elementData数组中的下标)
                    if (s == elementData.length)                           // elementData数组的长度为0,结果为true
                        elementData = grow();                              // 进行扩容,grow()方法的具体代码如下
                    elementData[s] = e;                                    // elementData[0] = e; 把刚才要添加的元素赋值给了elementData[0],完成添加操作
                    size = s + 1;                                          // 把size进行加1,用于指定下一次要添加元素到数组的下标
                }
            // grow()       方法
                private Object[] grow() {
                    return grow(size + 1);                                 // grow() 调用 grow(int i)方法,这个i用来表示当前数组所需要的最小容量
                }
            // grow(int i) 方法
                private Object[] grow(int minCapacity) {
                    int oldCapacity = elementData.length;                  // 把elementData数组当前的容量赋值给 oldCapacity
                    // 判断,如果elementData的容量>=1 的情况(也就是说elementData不为空,有值的情况)
                    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {  
                        // 这段是用来求出 elementData容量当前的 1.5 并赋值给 newCapacity
                        int newCapacity = ArraysSupport.newLength(oldCapacity,                  
                                minCapacity - oldCapacity, // minimum growth 
                                oldCapacity >> 1           // preferred growth );
                        // 使用数组的赋值方法,把当前的elementData赋值给一个新的扩容为自己1.5倍容量的新数组
                        return elementData = Arrays.copyOf(elementData, newCapacity);           
                    } else {
                        // 如果原来elementData为空的话就返回一个容量为10的空数组并赋值给elementData,这里DEFAULT_CAPACITY是常量,值为10
                        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
                    }
                }
         * 
        */
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        // 此时list的容量size已经为10,所以根据源码执行下边的添加操作就会进行扩容了,扩容为当前数组容量的1.5倍
        list.add(1);

        // 虽然这里输入当前集合的容量为11,这是因为这个方法实际上是返回的size变量,这个变量就是用来记录elementData中真是添加元素的个数,而不是
        // elementData的真实容量,这个地方elementData数组的真实容量就是15,但是因为我们值添加11个元素,所以size变量就是11,所以这里就返回11。
        // 这个地方可以通过debug来进行查看。
        System.out.println(list.size());
    }
}

1.4.2 Vector分析

Vector和ArrayList基本相同,只是有一点细微差别。Vector底层也是基于Object类型的数组进行实现的。只不过是Vector是线程安全的。同时在数组的初始化和扩容相较于ArrayList有点不同。
Vector示例代码:

// vector分析
public class VectorSource {
    public static void main(String[] args) {
        /**     public Vector() {
                    this(10);        // 可以看到Vector在初始化的时候直接把一个容量为10的空数组赋值给elementData,这点与ArrayList不同
                }
    */
        Vector vector = new Vector();

        /**     public synchronized boolean add(E e) {    // 可以看到Vector类的方法都使用了 synchronized 来实现线程同步
                     modCount++;
                     add(e, elementData, elementCount);
                     return true;
                 }
         */
        vector.add("1");
    }
}

1.4.3 LinkedList底层结构和源码分析

LinkedList底层实现了 双向链表 双端队列 的特点。
LinkedList的底层操作机制:

上边的Node中的item属性就是用来存储真正添加的元素的。

① 简单模拟LinkedList

package com.llh.learn1;

/**
 * @Description: TODO
 * @Author lilinghao
 * @Date 2025/3/4
 **/
public class LinkedListMimic {
    public static void main(String[] args) {
        Node item1 = new Node("item1");
        Node item2 = new Node("item2");
        Node item3 = new Node("item3");

        // first节点指向第一个节点
        Node first = item1;
        // last节点执行最后一个节点           first、last是用来满足双端队列的特点
        Node last = item3;

        // 构建各个node之间的链接关系
        item1.setNext(item2);
        item2.setNext(item3);
        item3.setPrev(item2);
        item2.setPrev(item1);

        // 实现正向遍历
        System.out.println("==========正向遍历=============");
        while (first != null) {
            System.out.println(first.getItem());
            first = first.getNext();
        }
        first = item1;          // 遍历过后将头指针复位

        // 反向遍历
        System.out.println("==========反向遍历=============");
        while (last != null){
            System.out.println(last.getItem());
            last = last.getPrev();
        }
        // 遍历过后将尾指针复位

        // 模拟插入 (插入到 2-3 之间)
        System.out.println("==========插入============");
        Node item5 = new Node("插入");
        item2.setNext(item5);
        item3.setPrev(item5);
        item5.setPrev(item2);
        item5.setNext(item3);
        while (first != null) {
            System.out.println(first.getItem());
            first = first.getNext();
        }
        first = item1;

        // 模拟删除             (删除item5)
        System.out.println("========删除===========");
        item2.setNext(item3);
        item3.setPrev(item2);
        while (first != null) {
            System.out.println(first.getItem());
            first = first.getNext();
        }
        first = item1;

    }
}
class Node {
    private Node next;
    private Node prev;
    private Object item;

    public Node() {}

    public Node(Object item) {
        this.item = item;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public Node getPrev() {
        return prev;
    }

    public void setPrev(Node prev) {
        this.prev = prev;
    }

    public Object getItem() {
        return item;
    }

    public void setItem(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Node{" +
                "next=" + next +
                ", prev=" + prev +
                ", item=" + item +
                '}';
    }
}

② LinkedList增删改查底层机制

这里主要分析一个add()方法执行流程,至于其它的 remove()、set()……方法的思想和add()方法的思想都差不多

// LinkedList源码分析
public class LinkedListCRUD {
    public static void main(String[] args) {
        // 使用空参构造器初始化LinkedList对象。此时list的属性 --> size=0、first=null、last=null、modCount=0
        LinkedList list = new LinkedList();
        /**    
              public boolean add(E e) {         
                    linkLast(e);                // 调用linkLast(e) 方法
                    return true;
                }
            // linkLast(e) 方法
                void linkLast(E e) {
                    final Node<E> l = last;                     // 使用final修饰的Node类型的局部变量来存储 last 的内存地址
                    final Node<E> newNode = new Node<>(l, e, null); // 正式将元素e放入一个新创建的Node节点
                    last = newNode;                             // 把last执行刚才新添加的node节点
                    // 如果原来的双向列表中的last属性为null,那么就说明原来的链表中没有元素(即为空链表),那么就把first也执行刚才添加的node节点上
                    if (l == null)                              
                        first = newNode;
                    // 如果原来的链表不为空的话,那么使用 l.next=newNode 来把原来链表的最后一个node的next属性赋值为新增node节点的地址。
                    // 因为这里 l 指向的是未添加新元素之前时链表的最后一个元素。并且这个l变量是被final进行修饰,所以l的值不会随着last变量的变化而变化
                    else
                        l.next = newNode;
                    // 通过上边的步骤,已经完成了新增节点的插入,以及 双向链表的链接以及first、last的指向问题。最后把list的size属性、modCount属性进行+1
                    size++;
                    modCount++;
                }
            // new Node(l,e,null) 方法
                    Node(Node<E> prev, E element, Node<E> next) {
                        this.item = element;                // 赋值item
                        // 赋值新增元素的next,这里传入为null,因为新增元素后边肯定没元素了,所以就把next赋值为null
                        this.next = next; 
                        // 帮当前双向链表的最后一个node节点赋值给该新增node节点的prev属性。                  
                        this.prev = prev;
                    }
    */
        list.add("item1");
        list.add("item2");
// 在双向链表的第一个位置插入元素(node)
        list.addFirst("itemFirst");
// 在双向链表的最后一个位置插入元素(node)
        list.addLast("itemLast");
// 根据元素内容移除元素
        list.remove("itemFirst");
        list.remove("item2");
        System.out.println(list);
// 移除第一个元素
        list.removeFirst();      // .remove()等效于 removeFirst(),因为remove方法就是调用的removeFirst()
// 移除最后一个元素
        list.removeLast();
// 修改指定下标的元素
        list.set(0,"item1-set");
        System.out.println(list);
// 获取指定下标的元素
        System.out.println(list.get(0));


// 两种遍历方法
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        for (Object o : list) {
            System.out.println(o);
        }
    }
}

③ ArrayList 和 LinkedList的比较

1.5 Set集合

1.5.1 Set集合介绍

①Set集合特点

  • 无序(添加和取出的顺序不一致),并且没有下标索引

  • 不能存储重复的值(null 也只能存储一个)

因为Set、List都是Collection的子接口,因此set和list接口的常用方法基本一致,这里对于Set集合的常用方法不做过多阐述。
并且因为Set集合是无序的,所以在不能进行下标之类的操作,例如通过下标来进行CRUD以

接下来就来介绍关于Set集合接口的实现类


1.5.2 HashSet底层结构和源码分析

① HashSet说明

② HashSet源码解析


public class HashSetSource {
    public static void main(String[] args) {
        // 调用空参构造器
        /**
        public HashSet() {
            map = new HashMap<>();          // 可以看到HashSet底层在调用HashMap的空参构造器,把hashMap对象赋值给自己的map属性
        }
        // HashMap的空参构造器
            public HashMap() {
                this.loadFactor = DEFAULT_LOAD_FACTOR; // 把DEFAULT_LOAD_FACTOR 这个常量是加载因子赋值给loadFactor,默认值为0.75
            }
    */
        HashSet set = new HashSet();
        /**    调用添加方法
                public boolean add(E e) {
         // 向map属性(即hashmap对象)添加元素 (因为set是单列,而map是双列:k-v,所以set这里把map的value填充为同一个常量PRESENT)
                    return map.put(e, PRESENT)==null;    
                }
            // map.put(e,PRESENT)方法
                public V put(K key, V value) {
                    // 又调用该方法。这里hash(key)对key进行hash计算,false表示的是onlyIfAbsent属性的值,true表示的是evict属性的值
                    return putVal(hash(key), key, value, false, true);      
                }
            // hash(key)方法
                static final int hash(Object key) {
                    int h;
                    // 如果key为null就直接返回值=0,如果不为null就根据key的hashCode方法并且进行再运算求出计算值
                    // 所以这里就决定了 null 会返回同一个值,就是0
                    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
                }

            // putVal(hash(key),key,value,false,true) 方法    =================== 重点=============================
             final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
                    // 定义临时局部变量。tab用来存储hashMap中的table索引数组。n用来存储hashMap中table数组的总长度
                    Node<K,V>[] tab; Node<K,V> p; int n, i;
                    // 如果hashMap中的table为空或者table数组的长度为0,那么就使用resize()方法进行扩容,并把扩容后的数组总长度赋值给n
                    if ((tab = table) == null || (n = tab.length) == 0)
                        n = (tab = resize()).length;
                    // 判断key进行hash后在hashMap的Node类型的table数组的对应下标位置是否为空
                    if ((p = tab[i = (n - 1) & hash]) == null)
                    // 为空就直接把新添加的node赋值在hashmap的table对应索引的位置
                        tab[i] = newNode(hash, key, value, null);
                    else {
                    // 如果hashmap的node类型的数组中下标等于hash操作后的key的位置不为空
                        Node<K,V> e; K k;
            // 如果table[hash(key)]中的元素跟新增元素的key相同,那么就把该位置的值赋值给e,用于配合下边的对相同key的value替换问题
                        if (p.hash == hash &&
                            ((k = p.key) == key || (key != null && key.equals(k))))
                            e = p;
                    // 如果table[hash(key)] 的值是树类型的话,那么就调用树相关的方法,目的也是来完成插入操作
                        else if (p instanceof TreeNode)
                            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                        else {
                        // 如果上边那种情况都不是,那就判断该索引的链表中是否有于该元素相同key的node
                            for (int binCount = 0; ; ++binCount) {
                                if ((e = p.next) == null) {
                        // 通过循环判断条件,可以走到该条件分支就表示该链表中没有与新增元素相同的key
                        // 那么就创建新的node,并让原来链表中最后一个node的next指向新node,新node的next置为空
                                    p.next = newNode(hash, key, value, null);
// 如果此时一个链表的数组超过了8,那么就考虑树化
                                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 这里进行树化的时候会判断hashmap的table数组总长度是否达到64,如果没达到就暂时不树化,而是对table表进行2倍扩容
                                        treeifyBin(tab, hash);
                                    break;
                                }


                                if (e.hash == hash &&
                                    ((k = e.key) == key || (key != null && key.equals(k))))
// 到这里就说明链表中有与新增元素相同key的node,那么就直接break循环。同时因为不断的e=p.next,此时 跳出循环后e只好指向的是链表中相同key的node
// 这个也是用来配合下边的判断 用来对相同key的value替换问题
                                    break;
                                p = e;
                            }
                        }

                        if (e != null) { 
// 用来进行在新增元素的时候 对于相同key的value替换(用新增的value来替换原来的value)
                            V oldValue = e.value;
                            if (!onlyIfAbsent || oldValue == null)
// 如果onlyIfAbsent为false(就是不使用putIfAbsent 来添加元素) 或者 原来node的value为null,那么就进行替换操作
                                e.value = value;
                            afterNodeAccess(e);
                            // 返回node原来旧的value
                            return oldValue;
                        }
                    }
                    ++modCount;                 // 增加一次修改次数
                    if (++size > threshold)     // 如果node的数量超过临界值,那就就对hashmap的table数组进行扩容
                        resize();
                    afterNodeInsertion(evict);
                    return null;
                }
        */
        set.add("java");
        set.add("linux");
        set.add("java");
        System.out.println(set);
    }
}

HashMap的扩容机制

从上边的源码可以看出来hashmap的扩容机制。


关于添加HashSet重复元素的判断

通过源代码中 (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) 可以看出来,判断key是否相同是 同时根据 hashCode(hash方法里面实际上调用hashCode,然后对hashCode进行再运算) 和 对应元素的 equals 方法。

那么根据这个我们可以自己定义对于HashSet来说什么情况才算是 元素相同。

下面是一个关于HashSet重复值的相关练习题

package com.llh.my_collection;

import java.util.HashSet;
import java.util.Objects;

/**
 * @Description: TODO
 * @Author lilinghao
 * @Date 2025/3/2
 **/
public class HashsetTest {
    public static void main(String[] args) {
        /** 练习要求: 要求创建一个hashset对象,创建三个Employee对象存放入hashset,要求如果employee的name和age相同,那么不能添加到hashset中
         * 分析: hashset(hashmap) 的去重机制是根据元素的 hashCode 和 equals 来进行实现的。所以这里要对Employee对象name和age同时相同是进行去重,那么就需要
         * 1. 重写Employee的hashCode()方法,要求其在 name、age相同时输出相同的hashCode
         * 2. 重写Employee的equals方法,比较其name和age相同时就认为两个对象相同
         */

        HashSet hashSet = new HashSet();
        Employee e1 = new Employee("llh", 22);
        Employee e2 = new Employee("lz", 22);
        Employee e3 = new Employee("llh", 22);
        hashSet.add(e1);
        hashSet.add(e2);
        hashSet.add(e3);
        System.out.println(e1.hashCode());
        System.out.println(e3.hashCode());
        System.out.println(hashSet);
    }
}
class Employee{
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Employee(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Employee() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        try {
            Employee e2 = (Employee) o;
            return this.getName().equals(e2.getName()) && this.getAge()==e2.getAge();
        }catch (Exception e){
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

1.5.3 LinkedHashSet

①LinkedHashSet说明


LinkedHashSet通过双向链表来确保了插入和遍历的顺序一致。

    public LinkedHashSet() {
        super(16, .75f, true);      //LinkedHashSet 初始化就直接初始化出容量为16的Node类型数组
    }

1.6 Collections工具类

Collections是一个操作 List、Set 集合的工具类,提供了一系列的静态方法,用来实现对List、Set集合的排序、查询、修改等操作。

方法概述如下:

具体的演示代码如下:

package com.llh.learn1;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;

/**
 * @Description: TODO
 * @Author lilinghao
 * @Date 2025/3/4
 **/
public class MyCollectionsUtil {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            list.add(random.nextInt(100));
        }
        System.out.println("======== 原List集合 ================");
        System.out.println(list);

        // 反转   Collections.reverse(List list) Collections.reverse(List list,Comparator comparator)
        System.out.println("=======反转===============");
        Collections.reverse(list);
        System.out.println(list);

        // 随机排序(洗牌) Collections.shuffle(List list)
        System.out.println("==============洗牌==================");
        Collections.shuffle(list);
        System.out.println(list);

        // 按顺序排序    Collections.sort()
        System.out.println("============按默认顺序排序(从小到大)============");
        Collections.sort(list);
        System.out.println(list);

        // 使用指定比较器进行排序          Collections.sort(List,Comparator)
        System.out.println("========= 指定规则排序 ==========");
        Comparator<Integer> comparator = (o1, o2) -> o1 - o2;
        Collections.sort(list, comparator);
        System.out.println(list);


        // 交换对应下标的位置的元素 Collections.swap(List list,index1,index2)
        System.out.println("====== 交换元素 ==========");
        Collections.swap(list, 0, 1);
        System.out.println(list);

        // 当前集合最大元素 Collections.swap(Collection collection)
        System.out.println("========当前集合最大元素=======");
        System.out.println(Collections.max(list));

        //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
        //比如,我们要返回长度最大的元素

        // min() ,min(Comparator)

        // 复制集合 copy(List desc,List src)
        ArrayList list1 = new ArrayList();
            // 因为ArrayList的初始长度为0,所以我们要先添加点元素让 ArrayList 扩容
        for (int i = 0; i < 10; i++) {list1.add(i);}
        Collections.copy(list1, list);
        System.out.println("===========复制list集合=============\n"+list1);

        // replaceAll(List,oldValue,newValue) 使用新值替换 List 对象的所有旧值
        Collections.replaceAll(list, 47, 50);

        // frequency(Collection,value)  统计元素在集合中出现的次数
        System.out.println("50出现的次数"+Collections.frequency(list, 50));
    }
}

2. Map接口和常用方法

2.1 Map接口实现类特点

key为null只能有一个 这个是因为通过hash和equals 对元素的key进行去重
前边的Set、List底层运用Map的时候都是对Map进行阉割使用,Set、List只需要使用Map中的key。所以可以理解Set、List是Map的阉割版。

Map的常用方法:




package com.llh.learn1;

import java.util.HashMap;

/**
 * @Description: TODO
 * @Author lilinghao
 * @Date 2025/3/4
 **/
public class HashMapTest {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        // 添加元素
        map.put("1", "item1");
        map.put("2", "item2");
        map.put("3", null);
        map.put(null, "null-hello");
        map.put(null, "null-world");     // 会发生替换,最多只能有一个key为null。因为key去重同样包括null
        System.out.println(map);
        System.out.println("===================");
        map.putIfAbsent("1", "hello");      // 如果不存在相同key就添加。(条件:原来元素的value不为null,如果为null则替换value)
        map.putIfAbsent("3", "world");       // 因为3对应的值为value,所以这里就发生替换,替换后为 3 - world
        System.out.println(map.put("2", "item2-替换"));    // 如果发生替换就会返回原来的旧value
        System.out.println("=====================");
        System.out.println(map);

        System.out.println(map.get("1"));
        System.out.println(map.containsKey("1")); // 判断是否存在某个key
        System.out.println(map.containsValue("world")); // 判断是否存在某个value

        Collection values = map.values();
        for (Object o : values){
            System.out.println(o);
        }

        HashMap map1 = new HashMap();
        for (int x = 1;x<=5;x++){
            map.put(""+x,"元素:" + x);
        }
        map.putAll(map1);
        System.out.println(map);
        map.clear();    // 清楚map内的元素
        System.out.println(map.isEmpty());
    }
}

1.6 Collections工具类

Collections类是一个用来操作 Collection接口(就是List、Set接口实现类)的工具类。其中提供了一系列静态方法,对集合中的元素进行排序、cha

2.2 Map实现类的遍历

遍历原理: 遍历主要是通过Map内的EntrySet对象,这个对象是对Map内table内的链表内的node数据进行整理,用来方便我们遍历Map。

所以遍历map就使用提供的内部属性 KeySet、ValueSet、EntrySet来完成。

    public static void useEntrySet(Map map){
        for (Object o : map.entrySet()){
            Map.Entry entry = (Map.Entry) o;
            System.out.println("key:"+entry.getKey()+" ---> value:"+entry.getValue());
        }
    }

    public static void useKeySet(Map map){
        for(Object o : map.keySet()){
            System.out.println("key:"+o+" ---> value:"+map.get(o));
        }
    }
    public static void useIterator(Map map){
        Iterator iterator = map.keySet().iterator();
        while (iterator.hasNext()){
            Object key = iterator.next();
            System.out.println("key:"+key+" ---> value:"+map.get(key));
        }
    }

2.3 HashMap

对于HashMap的底层源码在分析 HashSet的时候就已经分析过了,因为HashSet底层就是HashMap。所以这里就不再进行源码分析了,直接上结论。

HashMap的结构是 Node类型的数组 + 单向链表 + 红黑树

HashMap源码底层结论

2.4 LinkedHashMap

linkedHashMap就是在HashMap的基础上把链表改为了双向链表,并且LinkedHashMap添加了 head 、tail属性,分别用来指向双向链表的头和尾节点。

关于图解: LinkedHashSet底层就是用的LinkedHashMap,所以那个图解就是LinkedHashMap的图解。如下

2.5 HashTable


HashTable 和 HashMap对比

线程安全

效率

允许null建null值

HashMap

不安全

可以

Hashtable

安全

较低

不可以

其它方面Hashtable和HashMap基本相同。


关于Properties,该类是Hashtable的子类。所以Map集合的通用方法它也通用


package com.llh.learn1;

import java.util.Properties;

public class MyProperties {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("john", 100);            //k-v
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);              //如果有相同的 key , value 被替换
        System.out.println(properties);
        System.out.println(properties.get("john"));
        properties.remove("lic");
        properties.setProperty("john", "hello");
        properties.setProperty("ming","world");
        System.out.println(properties);
    }
}

3.集合总结

开发中如何选择集合实现类(记住)

文章作者: Administrator
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 寻梦
java
喜欢就支持一下吧