image.png
1、java集合框架概述
2、Collection接口方法
3、Iterator迭代器接口
4、Collection子接口一:List
5、Collection子接口二:Set
6、Map接口
7、Collections工具类

一、Java集合框架概述

· 由于面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而java集合就像容器,可以动态地把多个对象的引用放入容器中。
image.png
· java集合类可以用户存储数量不等地多个对象,还可用于保存具有映射关系地关联数组

1、java集合地分类

java集合可以分为Collection和Map两种体系
Collection接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序,可重复的集合
Set:元素无序,不可重复的集合
Map接口:双列数据,保存具有映射关系”key-value对”的集合

2、Collection接口继承树

image.png

3、Map接口继承树

image.png

二、Collection 接口方法

· Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可以用于操作Set集合,也可用于操作List和Queue集合
· JDK不提供此接口的直接实现,二十提供更具体地子接口(如:Set和List)实现。
· 在java5之前,java集合会丢失容器中所有对象地数据类型,把所有对象都当成Object类型处理:从JDK5.0增加了泛型以后,java集合可以记住容器中对象地数据类型
1、添加
add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数
int size()
3、清空集合
void clear()
4、是否空集合
boolean isEmpty()
5、是否包含某个元素
boolean contain(Object obj) 通过元素的equals来判断此是否为同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
boolean equals(Object obj)
9、转成对象数组
Object[] toArray()
10、获取对象集合的哈希值
hashCode()
11、遍历
iterator() 返回迭代器对象,用于遍历集合

三、Iterator迭代器接口

1、使用Iterator接口遍历集合元素概述

· Iterator称位迭代器(设计模式的一种)主要用于遍历Collection集合中的元素
· GOF给迭代器模式的定义为 提供一种方法访问一个容器(container)对象中各个元素,而不是暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
· Collection接口继承了java.lang.Iterator接口,该接口有一个iterator()方法,那么所有实现了Collection接口的结合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象。
· Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
· 集合对象每次调用Iterator()方法都得到一个全新的迭代器对象,默认游标集合都在集合的第一个元素之前。

2、Iterator接口的方法及遍历实现

1、hasNext() 判断是否还有下一个元素
2、next() 指针下移,将下一后集合位置上的元素返回
注:在调用it.next()方法之前须要调用hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSushElementException异常
3、remove() 删除集合的元素
4、使用举例
(1)生成Iterator对象
(2)判断是否还有下一个元素
(3)指针下移,取出后移后位置上的元素
image.png

3、使用foreach循环遍历集合元素

· java5.0提供了循环foreach循环迭代访问Collection和数组
· 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素
· 遍历集合的底层调用Iterator完成操作
· foreach还可以用来遍历数组
image.png

四、Collection子接口之一:List接口

1、List接口概述

· 鉴于java中数组用来存储数据的局限性,我们通常使用List替代数组
· List集合类中,元素有序,且可重复,集合中的每个元素都有对应的顺序索引。
· List容器中的元素都对应一个整数型的序号记载在其容器中的位置,可以根据序号存取容器中的元素。
· JDK API中接口的实现类常用的有:ArrayList、LinkedList和Vector。

2、List接口方法

· List除了从Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的方法。
void add(int index, Object ele) 在Index位置插入元素
boolean addAll(int index, Collection eles) 从index位置开始将eles中所有元素添加进来
Object get(int index) 获取指定index位置的元素
int indexOf(Object obj) 返回obj在集合中首次出现的位置
int lastIndexOf(Object obj) 返回obj在当前集合中末次出现的位置
Object remove(int index) 移除指定index位置的元素,并返回此元素
Object set(int index, Object ele) 设置指定index位置的元素为ele
List SubList(int fromIndex, int toIndex) 返回fromIndex到toIndex位置的子集合。

3、List实现类之一:ArrayList(动态数据)

· ArrayList是List接口的典型实现类
· 本质上,ArrayList是对象引用的一个“变长”数组,既动态数据
· ArrayList的JDK1.8之前与之后的区别
|- JDK1.7:ArrayList像饿汉式,直接创建一个初始化容量为10的数组
|- JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组
· Arrays.asList(…)方法返回的List集合,既不是ArrayList实例,也不是Vector实例。Arrays.asList(…)返回值是一个固定长度的List集合
image.png

3.1、个人对ArrayList的总结:

  • ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组
  • 扩容时,扩容大小是原长度 + 原长度的一半,使用右移一位实现(二进制)
  • 元素复制时使用Arrays.copy()

    1. private void grow(int minCapacity) {
    2. // overflow-conscious code
    3. int oldCapacity = elementData.length;
    4. int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容
    5. if (newCapacity - minCapacity < 0)
    6. newCapacity = minCapacity;
    7. if (newCapacity - MAX_ARRAY_SIZE > 0)
    8. newCapacity = hugeCapacity(minCapacity);
    9. // minCapacity is usually close to size, so this is a win:
    10. elementData = Arrays.copyOf(elementData, newCapacity);
    11. }
  • ArrayList线程不安全,如果要使用线程安全的List则使用Vector

参考博客:https://blog.csdn.net/zengsao/article/details/118857418?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~aggregatepage~first_rank_ecpm_v1~rank_v31_ecpm-2-118857418.pc_agg_new_rank&utm_term=arraylist%E7%BA%BF%E7%A8%8B%E4%B8%8D%E5%AE%89%E5%85%A8%E5%8E%9F%E5%9B%A0%E4%B8%BA%E4%BB%80%E4%B9%88&spm=1000.2123.3001.4430

线程不安全的三种表现 解决办法
空指针异常(size不达标)(size达标)

使用Vector、Collections.synchronizedList()、使用copyOnwriteArrayList()
数组越界异常
并发修改异常

4、List实现类之二:LinkedList(双向链表)

· 对于频繁的插入或删除操作,建议使用LinkedList类,效率较高
· 新增方法
void addFirst(Object obj)
void addKast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
· LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first与last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义两个变量;
|- prev变量记录前一个元素的位置
|- next变量记录下一个元素的位置

5、List实现类之三:Vector

· vector是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
· 在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
· 新增方法
void addElement(Object obj)
void insertElementAt(Object obj, int index)
void removeElement(Object obj)
void removeAllElements()

6、Collection子接口的面试题

请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?
· ArrayList和LinkedList的异同
二者都是线程不安全,相对线程安全的Vector,执行效率高。此外,ArrayList实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。对于新增和删除,LinkedList要优于ArrayList,因为ArrayList要移动数据
· ArrayList和Vector的异同
ArrayList与Vector几乎相同,唯一的区别在于Vector是同步类(sysChronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全由程序员自己控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

注:ArrayList扩容机制详解:https://blog.csdn.net/qq_41813208/article/details/107777539

五、Collection子接口之二:Set接口

1、Set接口概述

· Set接口是Collection的子接口,set接口没有提供额外的方法
· Set集合不允许包含相同的元素,如果试把两个相同的元素加入到同一个Set集合中,则添加操作失败
· Set判断两个对象是否相同不是使用 == 运算符,而是根据equals()方法

2、Set实现类之一:HashSet

· HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类
· HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
·HashSet具有以下特点
不能保证元素的排列顺序
HashSet不是线程安全的
集合元素可以是null
· HashSet集合判断两个元素的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
· 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等的规则。即:“相等的对象必须具有相等的散列码”。
· 向HashSet中首次添加元素的过程:
1、HashSet在构造函数中,实现了HashMap,实际的是存储在HashMap的结构中,其中key值就是add方法中存入的数据,而value值则是一个Object常量。
2、调用add实际上是调用HashMap的put方法,其中key值就是add方法中存入的数据,而value值则是一个Object常量。
3、先判断table是否为空,为空则创建一个大小为16的数组
4、计算该值存储在数组中的对应下标,存入数据
扩展:如果存入相同的元素,则在else中判断该值是否已经存在
5、判断是否需要数组扩容
6、返回null,再返回true

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  2. boolean evict) {
  3. Node<K,V>[] tab; Node<K,V> p; int n, i;
  4. if ((tab = table) == null || (n = tab.length) == 0)
  5. // 数组创建
  6. n = (tab = resize()).length;
  7. if ((p = tab[i = (n - 1) & hash]) == null)
  8. tab[i] = newNode(hash, key, value, null);
  9. else {
  10. Node<K,V> e; K k;
  11. if (p.hash == hash &&
  12. // 判断传进的值是否与数组中获取出来的值相等
  13. ((k = p.key) == key || (key != null && key.equals(k))))
  14. e = p;
  15. else if (p instanceof TreeNode)
  16. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  17. else {
  18. for (int binCount = 0; ; ++binCount) {
  19. if ((e = p.next) == null) {
  20. p.next = newNode(hash, key, value, null);
  21. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  22. treeifyBin(tab, hash);
  23. break;
  24. }
  25. if (e.hash == hash &&
  26. ((k = e.key) == key || (key != null && key.equals(k))))
  27. break;
  28. p = e;
  29. }
  30. }
  31. if (e != null) { // existing mapping for key
  32. V oldValue = e.value;
  33. if (!onlyIfAbsent || oldValue == null)
  34. e.value = value;
  35. afterNodeAccess(e);
  36. return oldValue;
  37. }
  38. }
  39. ++modCount;
  40. // 判断是否需要扩容
  41. if (++size > threshold)
  42. resize();
  43. afterNodeInsertion(evict);
  44. return null;
  45. }

参考链接:链接
image.png
注:HashSet实际上是一个HashMap实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用null元素。HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象private static final Object PRESENT = new Object();
HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。

3、Set实现类之二:LinkedHashSet

· LinkedHashSet是HashSet的子类
· LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
· L inkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
· LinkedHashSet不允许集合元素重复。
image.png

4、Set实现类之三:TreeSet

· TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
· TreeSet底层使用红黑树结构存储数据
· 新增的方法如下:
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
· TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
· TreeSet和后面要讲的TreeMap采用红黑树的存储结构
· 特点:有序,查询速度比List块

(1)排序——自然排序

· 自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
· 如果试图把一个对象添加到TreeSet时,则对象的类必须实现Comparable接口。
实现Comparable的类必须实现compareTo(Object obj)方法,两个对象即通过compareTo(Object obj)方法的返回值来比较大小。
· ComParable的典型实现:
BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们的数值大小进行比较
Character:按字符的unicode值来进行比较
Boolean:true对应的包装类实例大于false对应的包装类实例
String:按字符串中字符的unicode值进行比较
Date、Time:后边的时间、日期比前面的时间、日期大
· 向TreeSet中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
· 因为只有相同类的两个实例才会比较大小,所以向TreeSet中添加的应该是同一个类的对象。
· 对于TreeSet集合而言,他判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较返回值。
· 当需要把一个对象放入TreeSet中,重写该对象对应的equals()方法时,应保证该方法的equals()方法比较返回true,则通过compareTo(Object obj)方法比较应返回0。否则让人难以理解。

(2)排序——定制排序

· TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
· 利用int compare(T o1,T o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。)要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
· 此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。
· 使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。

六、Map接口

1、Map接口概述

· Map与Collection并列存在。用于保存具有映射关系的数据:key-value
· Map中的key和value都可以是任何引用类型的数据
· Map中的key用Set来存放,不允许重复,即同一个Map对象所对应的类,需重写hashCode()和equals()方法。
· 常用String类作为Map的“键”
· key和value之间存在单项一对一关系,即通过指定的key总能找到唯一确定的value
· Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map接口使用频率最高的实现类。
image.png

2、Map接口方法

(1)添加、删除、修改操作:

  • Object put(Object key,Object value):指定key-value添加到(或修改)当前map对象中
  • void putAll(Map m):将m中的所有key-value对存放到当前map中
  • Object remove(Object key):移除指定key的key-value对,并返回value
  • void clear():清空当前map中所有的数据

(2)元素查询的操作

  • Object get(Object key):获取指定key的value
  • boolean containsKey(Object key):是否包含指定的key
  • boolean containsValue(Object value):是否包含指定的value
  • int size():返回map中key-value对的个数
  • boolean isEmpty():判断当前map是否为空
  • boolean equals(Object obj):判断当前map和参数对象Obj是否相等

(3)元视图操作方法

  • Set keySet():返回所有key构成的Set集合
  • Collection values():返回所有value构成的Collection集合
  • Set entrySet():返回所有key-value对构成的Set集合

image.png

七、Map实现类之一:HashMap

  • HashMap是Map接口使用频率最高的实现类之一
  • 允许使用null键和null值,和HashSet一样,不保证映射的顺序
  • 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
  • 所有的value构成的集合是Collection:无序、可重复的。所以value所在的类要重写:equals()
  • 一个key-value构成一个entry
  • 所有的entry构成的集合是Set:无序的、不可重复的
  • HashMap 判断两个key相等的标准是:两个key通过equals()方法返回true,hashCode值也相等
  • HashMap 判断两个value值得标准是:两个value通过equals()方法返回true
  • jdk8中hashmap 数组 + 链表 + 红黑树。每一个数据单元都是一个Node结构,Node中包含 key字段、value字段、next字段、hash字段。
  • hsahmap散列表数组初始长度默认是16。散列表不是 new HashMap()时创建的,散列表是懒加载机制,只有第一次put数据的时候才创建。
  • 链表转化为红黑树主要是有两个指标:

1)是链表长度达到8.
2)是当前散列表数组长度已经达到64.否则,就算内部链表长度达到8了,也不会发生链转树;而是仅仅发生一次resize,散列表扩容。

  • Node对象内部有一个hash字段,这个hash字段的值是key对象的hashCode()返回值吗?不是。这个hash值是key的hashCode()二次加工得到的·。高16位异或低16位

高16位异或低16位,主要解决均匀分布的问题。

  • hashmap的put方法流程 4种情况:

寻址算法都一样:都是根据 key的hashCode 经过高低位异或之后的值,然后再按位与 & (table.length - 1),得到一个槽位(slot)下标。这个下标,槽内状况不同,情况也不同。四种状态。
1)slot == null :直接put占用这个slot(槽)。把当前方法传进来的key和value封装成一个node对象,放到这个slot中。
2) slot != null,并且它引用的Node 还没有链化。
这种情况,需要先对比一下,Node对象的key与当前put对象的key是否完全相等。若相等,这就是replace操作,替换value就可以。若不相等,这个put操作就是hash冲突,在slot->node 后面插入一个新的节点,采用尾插法。
3)slot != null && slot内部的node已经链化。与2)类似。迭代查找node,看链表上元素的key,与当前传来的key是否完全一致。一致的话,则replace。 put之后,还需要检查当前链表的长度,有没有达到树化阈值。若达到阈值,就调用一个树化方法,具体的树化操作都在这个树化方法里面完成。
4)冲突很严重的情况,链已经转化为红黑树。

1、HashMap的存储结构

image.png

image.png

八、Map实现类之二:LinkedHahsMap

  • LinkedHashMap是HashMap的子类
  • 在HashMap存储结构的基础上,使用了一对双向链表记录添加元素的顺序
  • 与LinkedHashSet类似,LinkedHashMap可以维护Map的迭代顺序:迭代顺序与Key-value对的插入顺序一致

image.png

九、Map实现类之三:TreeMap

  • TreeMap存储Key-value对时,需要根据key-value对进行排序。TreeMap可以保证所有的key-value对处于有序状态
  • TreeSet底层使用红黑树结构存储数据
  • TreeMap的key的排序

-自然排序:TreeMap的所有的key必须实现Comparable接口,而且所有的key应该时同一个类的对象,否则将会抛出ClassCastExpetion
-定制排序:创建TreeMap的所有的Key必须实现Comparator对象,该对象对TreeMap中的所有key进行排序。此时不需要Map的Key实现Comparable接口

  • TreeMap判断两个key先等的标准:两个key通过compareTo()方法或者compare()方法返回0

  • 总结HashMap、LinkedHashMap、TreeMap,参考以下链接:https://blog.csdn.net/weixin_44018338/article/details/104973706

    十、Map实现类之四:Hashtable

  • Hashtable是个古老的Map实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。

  • Hashtable实现原理和HashMap相同,功能相同,底层使用哈希表结构,查询速度快,很多情况下可以互用。
  • 与HashMap不同:Hashtable不允许使用null作为key和value
  • 与HashMap一样,Hashtable也不能保证其中key-value对的顺序
  • Hashtable判断两个key相等,两个value相等的标准,与HashMap一致

    十一、Map实现类之五:Properties

  • Properties类是Hashtable的子类,该对象用于处理属性文件

  • 由于属性文件里的key、value都是字符串,所以Properties里的key和value都是字符串类型
  • 存取数据时,建议使用setPrpperty(String key, String value)方法和getProperty(String key)方法

image.png

十二、Collections工具类

image.png