集合

21 阅读9分钟

单列集合

image-20230823135449191

双列集合(key-value)

image-20230823135701836

Collection接口的常用方法

Collection接口是Java集合框架的根接口,它定义了集合的一些基本功能。Collection接口的常用方法包括:

  • add(E e) - 添加一个元素到集合中
  • remove(Object o) - 从集合中删除一个元素
  • contains(Object o) - 判断集合是否包含指定的元素
  • containsAll 查找多个元素是否存在
  • isEmpty() - 判断集合是否为空
  • size() - 返回集合中的元素个数
  • iterator() - 返回在集合上的迭代器
  • toArray() - 将集合转换为数组
  • clear() - 清空集合中的所有元素
  • equals(Object o) - 判断两个集合是否相等
  • hashCode() - 返回集合的哈希值

例如:

List<String> list = new ArrayList<>();
list.add("a"); // 添加元素
list.remove("a"); // 删除元素 
System.out.println(list.size()); // 获取元素个数
for(String s : list) { // 通过迭代器遍历
  System.out.println(s); 
}

Collection接口规定了集合的基本操作和属性,它的实现类提供了各种不同性能tradeoff的集合实现,如List,Set等。

Collection接口的主要特点:

  • Collection接口继承了Iterable接口,意味着所有Collection集合都可以通过for-each循环来遍历。
  • Collection不是一个真正的接口,它也提供了一些默认方法的实现,如addAll(), removeAll()等。
  • Collection表示一组object,可以通过iterator()获得迭代器对元素进行遍历。
  • Collection接口没有直接提供任何方法修改自身的长度,是通过子类自己实现如add()和remove()。
  • Collection实现了equals()和hashCode()方法来判断相等性。
  • 主要子接口有List,Set和Queue。
  • 主要实现类有ArrayList,LinkedList,HashSet,LinkedHashSet和TreeSet等。

使用Collection主要涉及:元素添加,删除,迭代遍历,检查包含关系等操作。选择合适的Collection实现类可以有效地存储和操作对象集合。

  1. collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复的元素,有些不可以
  3. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接的实现子类, 是通过它的子接口Set和List 来 实现的

List接口和常用方法

List接口是Collection接口的子接口

  1. List集合类中元素有序(即添加顺序和取出顺序一致)、 且可重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引
  3. List容器中的元素都对应一 个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

List接口常用的方法包括:

  • add(E e) - 添加元素到列表末尾
  • add(int index, E element) - 在指定位置插入元素
  • addAll(Collection c) - 将一个集合的所有元素添加到列表末尾
  • get(int index) - 返回指定位置的元素
  • set(int index, E element) - 修改指定位置的元素
  • remove(int index) - 删除并返回指定位置的元素
  • remove(Object o) - 删除第一个满足o==element的元素
  • clear() - 删除列表中的所有元素
  • contains(Object o) - 判断列表是否包含某元素
  • indexOf(Object o) - 返回元素在列表中首次出现的索引
  • isEmpty() - 判断列表是否为空
  • iterator() - 返回列表的迭代器
  • size() - 返回列表中的元素个数
  • subList(int from, int to) - 返回从from到to索引范围的子列表
  • sort(Comparator c) - 对列表元素进行排序

List接口继承自Collection,也可以使用所有Collection通用方法来操作列表。

List接口有三种常见的遍历方式:

  1. 通过索引遍历
List<String> list = new ArrayList<>();
for(int i=0; i<list.size(); i++) {
  String str = list.get(i);
  //...
}
  1. 通过迭代器遍历
List<String> list = new ArrayList<>();
Iterator<String> it = list.iterator();
while(it.hasNext()) {
  String str = it.next();
  //...  
}
  1. 通过增强for循环遍历
List<String> list = new ArrayList<>();
for(String str : list) {
  //...
}

增强for循环和迭代器遍历是Java 5后引入的新特性,可以简化代码。

索引遍历可以灵活访问任意索引位置,但代码繁琐。

迭代器遍历更加简洁,可以同时进行删除操作,但不能存取随机索引。

增强for循环最简洁,但只能遍历不能删除。

ArrayList底层结构和源码分析

permits all elements, including null , ArrayList可以加入null,并且多个

ArrayList是由数组来实现数据存储的

ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)在多线程情况下,不建议使用ArrayList

ArrayList中维护了一个Object类型的数组elementData.

transient表示瞬间,短暂的,表示该属性不会被序列化

当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。

如果使用的是指定大小的构造器,则初始elementData容量为指定大小, 如果需要扩容,则直接扩究elementData为1.5倍

Vector 底层结构和源码剖析

Vector底层也是一个对象数组,protected Object[] elementData;

Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized

在开发中,需要线程同步安全时,考虑使用Vector

image-20230826120217086

LinkedList

LinkedList底层实现了双向链表和双端队列特点

可以添加任意元素(元素可以重复),包括null

线程不安全,没有实现同步

LinkedList 的常用方法和遍历方式包括:

  1. add(E e) - 尾部加入元素
  2. add(int index, E element) - 在指定位置加入元素
  3. addFirst(E e)/addLast(E e) - 头部/尾部加入元素
  4. get(int index) - 获取指定位置的元素
  5. set(int index, E element) - 设置指定位置的元素
  6. remove(int index)/removeFirst()/removeLast() - 删除指定位置/头部/尾部的元素
  7. contains(Object o) - 判断是否包含某个元素
  8. size() - 获取元素个数

遍历方式:

  1. 通过索引遍历:
for (int i = 0; i < list.size(); i++) {
  System.out.println(list.get(i)); 
}
  1. 通过迭代器遍历:
Iterator iter = list.iterator();
while (iter.hasNext()) {
  System.out.println(iter.next());
}
  1. 通过 foreach 循环遍历:
for (Object obj : list) {
  System.out.println(obj); 
}
  1. 通过descendingIterator反向遍历:
Iterator descIter = list.descendingIterator();
while (descIter.hasNext()) {
  System.out.println(descIter.next());
}

ArrayList和LinkedList比较

image-20230829095209643

如何选择ArrayList和LinkedList:

1)如果我们改查的操作多,选择ArrayList

2)如果我们增删的操作多,选择LinkedList

3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList

4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList.

Set接口和常用方法

  1. 无序(添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null

和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.

Set接口的遍历方式同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。

  1. 可以使用迭代器
  2. 增强for
  3. 不能使用索引的方式来获取
package org.example.set_;

import java.util.HashSet;
import java.util.Iterator;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/8/29
 */
public class Set01_ {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        set.add("abc");
        set.add("123");
        set.add("def");
        set.add(null);

        //     遍历 方式一
        for (Object o : set) {
            System.out.println(o);
        }


        //     迭代器
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
package org.example.set_;

import java.util.HashSet;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/8/29
 */
public class Set02_ {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        //  会返回一个boolean值,如果添加成功,返回true,如果添加失败,返回false
        System.out.println(set.add("abc"));
        System.out.println(set.add("abc"));
        System.out.println(set.add("1"));
        System.out.println(set.add("2"));

        set.remove("abc");
    }
}
package org.example.set_;

import java.util.HashSet;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/8/29
 */
public class Set02_ {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        //  会返回一个boolean值,如果添加成功,返回true,如果添加失败,返回false
        System.out.println(set.add("abc"));
        System.out.println(set.add("abc"));
        System.out.println(set.add("1"));
        System.out.println(set.add("2"));

        set.remove("abc");

        set.add(new Dog("小黑"));
        set.add(new Dog("小黑"));

        System.out.println(set);

        set.add(new String("ade"));
        set.add(new String("ade")); // 加入不了,因为String类重写了equals方法,比较的是内容
        System.out.println(set);

    }
}


class Dog {
    private String name;


    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + ''' +
                '}';
    }
}

HastSet源码

  1. HashSet底层是HashMap
  2. 添加一个元素时,先得到hash值会转成->索引值
  3. 找到存储数据表table ,看这个索引位置是否已经存放的有元素
  4. 如果没有,直接加入
  5. 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
  6. 在Java8中,如果一条链表的元素个数超过TREEIFY THRESHOL
  7. D(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)

image-20230831195228091

第一步

image-20230902145918837

第二步

image-20230902153512208

image-20230902150047757

第三步

image-20230902150209568

第四步

求出 hash值 不完全等价于hashCode值

image-20230902154013936

第五步

    /**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; // 辅助变量
        // table 是一个haspMap的一个属性, 类型是 Node[]
        // 第一次扩容,到16个空间
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 根据key,得到hash值 去计算key因该存放到table表的那个索引位置
        // 并把这个位置的对象,赋给 p
        // 判断p是否为空  key "abc" value = PERSENT
        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 && 
                // 如果当前索引位置对应的链表的第-一个元素和准备添加的key的hash值一样
                // 并且满足 下面两个条件之一 :
                    //(1) 准备加入的key和p指向的Node 结点的key是同一个对象
                    //(2) p指向的Node结点的key 的equals() 和准备加入的key比较后相同
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 再判断p是不是一颗红黑树,
		   // 如果是 颗红黑树,就调用 putTreeVal ,来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            // 如果tabLe对应索引位置,已经是一个链表, 就使用for循环比较
            // (1) 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
            // 注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点
            // 就调用treeifyBin() 对当前这个链表进行树化(转成红黑树)
            // 注意,在转成红黑树时,要进行判断,判断条件
            // if (tab == nu1l || (n = tab.Length) < MIN_ TREEIFY_ CAPACITY(64))
            // resize() ;
            // 如果上面条件成立,先tabLe扩 容.
            // 只有上面条件不成立时,才进行转成红黑树
            // (2)依次和该链表的每个元素比较过程中,如果有相同情况,就直接break

                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;
    }

扩容机制

  • HashSet底层是HashMap,第一次添加时,table 数组扩容到16,临界值(threshold)是16加载因子(loadFactor)是0.75 = 12
  • 如果table数组使用到了临界值12,就会扩容到16* 2 = 32,新的临界值就是32*0.75 = 24,依次类推
  • 在Java8中,如果一条链表的元素个数到达TREEIFY THRESHOLD(默认是8 ),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

LinkedHashSet

  1. LinkedHashSet是HashSet的子类
  2. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
  3. LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  4. LinkedHashSet不允许添重复元素

源码剖析

  1. LinkedHashSet加入顺序和取出元素,数据的顺序致
  2. LinkedHashSet 底层维护的是一个L inkedHashMap (是HashMap的子类)
  3. LinkedHashSet 底层结构(数组table+双向链表)
  4. 添加第一次时,直接将数组tabLe扩容到16 ,存放的结点类型是LinkedHashMap$Entry
  5. 数组是HashMapNode[]存放的元素/数据是LinkedHashMapNode[] 存放的元素/数据是L inkedHashMapEntry类型

Map

  • Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
  • Map中的key和value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
  • Map中的key不允许重复,原因和HashSet 一样,前面分析过源码.
  • Map中的value可以重复
  • Map的key可以为null, value也可以为null ,注意key为null,只能有一个,value为null ,可以多个.常用String类作为Map的key
  • key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
package org.example.map;

import java.util.HashMap;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/9/4
 */
public class Map01 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("no1", 10);
        hashMap.put("no2", 20);
        hashMap.put("no3", 10);
        hashMap.put("no4", 20);

        System.out.println(hashMap.get("no1"));
        // System.out.println(hashMap);
    }
}
  • Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的, 有 因为Node实现了Entry 接口,有些书上也说一对k-v就是一个Entry

Map接口常用方法

  1. put:添加
  2. remove:根据键删除映射关系
  3. get:根据键获取值
  4. size:获取元素个数
  5. isEmpty:判断个数是否为0
  6. clear:清除
  7. containsKey:查找键是否存在
package org.example.map;

import java.util.HashMap;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/9/5
 */
public class MapMethod {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("邓超", "孙俪");
        map.put("李晨", "范冰冰");
        map.put("刘德华", "柳岩");

        System.out.println(map);

        map.remove("刘德华");

        Object val = map.get("邓超");
        System.out.println(val);

        System.out.println(map.size());


        System.out.println(map.containsKey("李晨"));
        System.out.println(map.containsValue("范冰冰"));

        System.out.println(map.isEmpty());

        map.clear();

    }
}

Map遍历方式

  1. containsKey:查找键是否存在
  2. keySet:获取所有的键
  3. entrySet:获取所有关系
  4. values:获取所有的值
package org.example.map;

import java.util.*;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/9/5
 */


public class MapMethod {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("邓超", "孙俪");
        map.put("李晨", "范冰冰");
        map.put("刘德华", "柳岩");

        // 第一组   keySet()方法
        for (Object o : map.keySet()) {
            System.out.println(o + " " + map.get(o));
        }

        // 迭代器  快捷键 itit
        Iterator iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            System.out.println(key + " " + map.get(key));
        }

        //   第二组  values()方法
        Collection values = map.values();
        for (Object value : values) {
            System.out.println(value);
        }

        //     迭代器
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object value = iterator1.next();
            System.out.println(value);
        }

        // 第三组     EntrySet 获取
        Set entrySet = map.entrySet();
        for (Object entry : entrySet) {
            Map.Entry entry1 = (Map.Entry) entry;
            System.out.println(entry1.getKey() + " " + entry1.getValue());
        }

        Iterator iterator2 = entrySet.iterator();
        while (iterator2.hasNext()) {
            Object next = iterator2.next();
            Map.Entry entry = (Map.Entry) next;
            System.out.println(entry.getKey() + " " + entry.getValue());

        }
    }
}

Map底层

  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75.
  3. 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
  4. 第1次添加,则需要扩容table容量为16,临界值(threshold)为12.
  5. 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推.
  6. 在Java8中,如果一条链表的元素个数超过TREEIFY THRESHOLD(默认是8 ),并且table的大小>= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)

HashTable

  1. 存放的元素是键值对:即K-V
  2. hashtable的键和值都不能为null, 否则会抛出NullPointerException\
  3. hashTable使用方法基本上和HashMap-样
  4. hashTable是线程安全的,hashMap是线程不安全的

Properties

  1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形 式来保存数据。
  2. 他的使用特点和Hashtable类似
  3. Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象, 并进行读取和修改
  4. 说明:工作后xxx.properties 文件通常作为配置文件,这个知识点在IO流举例
  1. 先判断存储的类型(一 组对象或一组键值对)

  2. 一组对象: Collection接口

    允许重复: List 增删多: LinkedList [底层维护了-个双向链表] 改查多: ArrayList [底层维护Object类型的可变数组] 不允许重复: Set 无序: HashSet [底层是HashMap ,维护了一个哈希表即(数组+链表+红黑树)] 排序: TreeSet 插入和取出顺序一致: LinkedHashSet , 维护数组+双向链表

  3. 一组键值对: Map 键无序: HashMap [底层是:哈希表jdk7:数组+链表,jdk8: 数组+链表+红黑树] 键排序: TreeMap 键插入和取出顺序一致: LinkedHashMap 读取文件Properties

Collections工具类

  • Collections是一个操作Set、List 和Map等集合的工具类
  • Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

排序操作

  1. reverse(List):反转List中元素的顺序
  2. shuffle(List):对List集合元素进行随机排序
  3. sort(List):根据元素的自然顺序对指定List集合元素按升序排序
  4. sort(List, Comparator): 根据指定的Comparator产生的顺序对List 集合元素进行排序
  5. swap(List, int, int): 将指定list集合中的i处元素和j处元素进行交换
  6. 应用案例演示Collections java
package org.example.collections;

import java.util.ArrayList;
import java.util.Collections;

/**
 * @author adekang
 * @version 1.0
 * @date 2023/9/7
 */
public class Collections_ {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);

        Collections.reverse(arrayList);
        System.out.println(arrayList);
    }
}
package org.example.collections;

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

/**
 * @author adekang
 * @version 1.0
 * @date 2023/9/7
 */
public class Collections_ {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("张三");
        arrayList.add("李四二");
        arrayList.add("王二麻子");

        Collections.reverse(arrayList);
        System.out.println(arrayList);

        Collections.sort(arrayList, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).length() - ((String) o2).length();
            }
        });
    }
}

查找、替换

  1. Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  2. Object max(Collection, Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元素
  3. Object min(Collection)
  4. Object min(Collection, Comparator)
  5. int frequency(Collection, Object): 返回指定集合中指定元素的出现次数
  6. void copy(List dest,List src):将src中的内容复制到dest中
  7. boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值