Java集合(容器)框架

image.png
所有的集合框架都包含如下内容:接口、实现(类)、算法。接口下面是一些抽象类,最后是具体实现类。

Collection 与 Map

Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。
Collection 接口又有 3 种子类型,List、Set 和 Queue,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet。
Map常用的实现类有HashMap、LinkedHashMap 等等。
尽管 Map 不是集合,但是它们完全整合在集合中。

ListSet

  1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素;
  2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>;
  3. List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> | 元素 | List | Set | | :—-: | :—-: | :—-: | | 排序 | 有序 | 无序 | | 重复 | 可重复 | 不重复 | | null | 允许 | 允许 | | 插入效率 | 低 | 高 | | 删除效率 | 低 | 高 | | 查找效率 | 高 | 低 | | 线程安全 | Collections.synchronizedList与CopyOnWriteArrayList | Collections.synchronizedSet与CopyOnWriteArraySet | | 特性 | 动态增长 | 不允许添加重复元素(相同则覆盖原有元素) |

CopyOnWrite适用于读多写少的场景,是以空间换时间的思想

Queue

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作(先进先出)。
常见的有单队列、循环队列…
一般用于缓冲、并发访问。

Map

迭代 Map 中的元素不存在直接了当的方法。如果要查询某个 Map 以了解其哪些元素满足特定查询,或如果要迭代其所有元素(无论原因如何),则您首先需要获取该 Map 的“视图”。有三种可能的视图:

  • 所有键值对 — 参见 entrySet()
  • 所有键 — 参见 keySet()
  • 所有值 — 参见 values()

前两个视图均返回 Set 对象,第三个视图返回 Collection 对象。
调整 Map 大小和选择负载因子

监测您的应用程序。如果发现某个 Map 造成瓶颈,则分析造成瓶颈的原因,并部分或全部更改该 Map 的以下内容:Map 类;Map 大小;负载因子;关键对象 equals() 方法实现。专用的 Map 的基本上都需要特殊用途的定制 Map 实现,否则通用 Map 将实现您所需的性能目标。

Doug Lea 是纽约州立大学奥斯威戈分校计算机科学系的教授。他创建了一组公共领域的程序包(统称 util.concurrent),该程序包包含许多可以简化高性能并行编程的实用程序类。这些类中包含两个 Map,即 ConcurrentReaderHashMap 和 ConcurrentHashMap。这些 Map 实现是线程安全的,并且不需要对并发访问或更新进行同步,同时还适用于大多数需要 Map 的情况。它们还远比同步的 Map(如 Hashtable)或使用同步的包装器更具伸缩性,并且与 HashMap 相比,它们对性能的破坏很小。util.concurrent 程序包构成了 JSR166 的基础;JSR166 已经开发了一个包含在 Java 1.5 版中的并发实用程序,而 Java 1.5 版将把这些 Map 包含在一个新的 java.util.concurrent 程序包中。 —https://www.oracle.com/cn/database/technology/maps.html

迭代器 - Iterator

Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
image.png
迭代器的基本操作是 next 、hasNext 和 remove
调用 next() 会返回迭代器的下一个元素,并且更新迭代器的状态
调用 hasNext() 用于检测集合中是否还有元素
调用 remove() 将迭代器返回的元素删除

  1. public class IteratorUsage {
  2. public static void main(String[] args) {
  3. // 创建集合
  4. ArrayList<String> list = new ArrayList<>();
  5. list.add("1");
  6. list.add("2");
  7. list.add("3");
  8. list.add("4");
  9. // 获取迭代器
  10. Iterator<String> it = list.iterator();
  11. // 输出集合中的所有元素
  12. while (it.hasNext()) {
  13. // 需要把当前元素读取出来进行操作
  14. String item = it.next();
  15. if ("3".equals(item)) {
  16. //用迭代器 的 remove() 方法后,达到了在删除元素时不破坏遍历
  17. it.remove();
  18. } else {
  19. System.out.println(item);
  20. }
  21. }
  22. }
  23. }

image.png

比较器 - Comparator

  1. 排序,需要比较两个对象谁排在前谁排在后
  2. 分组,需要比较两个对象是否是属于同一组 … ```java public class Man { public String name; public String gander; public Integer age;

    public Man(String name, String gander, Integer age) {

    1. this.name = name;
    2. this.gander = gander;
    3. this.age = age;

    } @Override public String toString(){

     return name + "\t" + gander + "\t" + age;
    

    } }


public class ComparatorUsage {

public static <T> List<List<T>> group(List<T> dataList, Comparator<? super T> comparator) {
    List<List<T>> result = new ArrayList<>();
    for (T item : dataList) {
        boolean isSameGroup = false;
        for (int j = 0; j < result.size(); j++) {
            int ret = comparator.compare(item, result.get(j).get(0));
            if (ret == 0) {
                isSameGroup = true;
                result.get(j).add(item);
                break;
            }
        }
        if (!isSameGroup) {
            List<T> innerList = new ArrayList<T>();
            result.add(innerList);
            innerList.add(item);
        }
    }
    return result;
}

public static void main(String[] args) {
    List<Man> list = new ArrayList<>();
    list.add(new Man("Jack", "男", 32));
    list.add(new Man("Ross", "女", 12));
    list.add(new Man("Ben", "男", 62));
    list.add(new Man("Dive", "女", 22));

    Collections.sort(list, new Comparator<Man>() {
        @Override
        public int compare(Man a, Man b) {
            return a.age - b.age;
        }
    });

    System.out.println(list);

    List<List<Man>> groupByAge = group(list, new Comparator<Man>() {
        @Override
        public int compare(Man a, Man b) {
            // 按年龄分组
            if (a.age < 20) {
                return -1;
            } else if (a.age < 60) {
                return 0;
            } else {
                return 1;
            }
        }
    });
    System.out.println(groupByAge);
    List<List<Man>> groupByGander = group(list, new Comparator<Man>() {
        @Override
        public int compare(Man a, Man b) {
            // 按性别分组
            return a.gander.compareTo(b.gander);
        }
    });

    System.out.println(groupByGander);
}

}

![image.png](https://cdn.nlark.com/yuque/0/2021/png/12661604/1616393596941-6b5d6cef-9fd1-475a-9d69-4a99e4cb2087.png#align=left&display=inline&height=77&margin=%5Bobject%20Object%5D&name=image.png&originHeight=77&originWidth=625&size=40742&status=done&style=none&width=625)
<a name="SidKz"></a>
# ArrayList
> 底层数据结构:动态数组,默认容量10,负载因子0.75,特点:随机存取、自动扩容
> 使用场景:
> 1. 频繁访问列表中的某一个元素;
> 1. 只需要在列表末尾进行添加和删除元素操作

<a name="2Ixy8"></a>
## 扩容
**ArrayList的大小是如何自动增加的?**
> 1. 在arraylist中增加一个对象的时候,会先去检查arraylist数组,以确保已存在的数组中有足够的容量来存储这个新的对象。如果没有足够容量的话(加载因子的系数小于等于1,意指即当元素个数超过容量长度*加载因子的系数时,进行扩容),就会新建一个长度更长的数组,旧的数组就会使用Arrays.copyOf方法被复制到新的数组中去,现有的数组引用指向了新的数组
> 1. 默认大小10(可指定),在扩容时每次扩容都是原容量的1.5倍,newCapacity = (oldCapacity * 3)/2 + 1;

<a name="tzehf"></a>
## 序列化
**ArrayList 中 elementData 为什么使用 transient 修饰?**
```java
/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
  1. 使用了 transient 修饰,可以防止被自动序列化
  2. 由于 ArrayList 是基于动态数组实现的,所以并不是所有的空间都被使用,因此 ArrayList 自定义了序列化与反序列化,具体可以看 writeObject 和 readObject 两个方法
  3. 需要注意的一点是,当对象中自定义了 writeObject 和 readObject 方法时,JVM 会调用这两个自定义方法来实现序列化与反序列化

注:其他集合实现同样有这个特点

ArrayList 中的 elementData 为什么是 Object 而不是泛型T ?

  1. Java 中泛型运用的目的就是实现对象的重用,泛型T和Object类其实在编写时没有太大区别
  2. JVM中没有T这个概念,T只是存在于编写时,进入虚拟机运行时,虚拟机会对泛型标志进行类型擦除,也就是替换T会限定类型替换(根据运行时类型),如果没有限定就会用Object替换
  3. Object可以new Object(),就是说可以实例化,而T不能实例化。在反射方面来说,从运行时,返回一个T的实例时,不需要经过强制转换,然后Object则需要经过转换才能得到

???

源码

Vector

All Implemented Interfaces:Serializable, Cloneable, Iterable, Collection, List, RandomAccess 底层数据结构:动态数组 特点:支持同步、提供随机访问、可克隆、支持序列化、速度慢 扩容:newCapcity = oldCapcity << 1

LinkedList

底层数据结构:双向链表实现、保留了头尾两个指针(维护了first和last节点,可以使用其实现栈,队列,双向队列等),特点:查询慢、插入快、实现了Queue接口 使用场景:

  1. 需要通过循环迭代来访问列表中的某些元素
  2. 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作

HashSet

底层数据结构:HashMap,默认容量16,负载因子0.75 特点:无重复、允许null、无序、存取速度快

HashMap

底层数据结构:链表+数组+红黑树(JDK1.8增加了红黑树,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等),默认容量16,负载因子0.75 特点:允许重复、最多只允许一条记录的键为null(允许多条记录的值为null)、无序

Node类的基本属性有: hash:key的哈希值 key:节点的key,类型和定义HashMap时的key相同 value:节点的value,类型和定义HashMap时的value相同 next:该节点的下一节点

image.png

扩容

默认容量16,负载因子0.75,如果HashMap初始化的时候指定了容量,HashMap会把这个容量修改为2的幂等数,然后创建对应长度的table; 当容量超过oldCap*负载因子时 newCap = oldCap << 1 当链表中的节点数量大于TREEIFY_THRESHOLD(8)时,链表将会考虑改为红黑树

HashMap的数组长度为什么保持2的次幂?
减少之前已经散列良好的老数组的数据位置重新调换
保证低位全为1,而扩容后只有一位差异,也就是多出了最左位的1,这样在通过 h&(length-1)的时候,只要h对应的最左边的那一个差异位为0,就能保证得到的新的数组索引和老数组索引一致(大大减少了之前已经散列良好的老数组的数据位置重新调换)
数组长度保持2的次幂,length-1的低位都为1,会使得获得的数组索引index更加均匀

LinkedHashMap

底层数据结构:LinkedList+HashMap(Hash table and linked list implementation of the Map interface) 特点:有序(LinkedList定义了迭代顺序)

线程安全问题

ArrayList、LinkedList、HashSet、HashMap、LinkedHashMap不是线程安全的(可用Collections.synchronized*进行包装) Vector、ConcurrentArrayList、ConcurrentHashMap…是线程安全的

参考

https://www.runoob.com/java/java-collections.html
https://www.oracle.com/cn/database/technology/maps.html