0%

Java集合学习笔记

前言

这个是个人在学习Java集合时所记录的笔记;

1. Java集合概述

1.1 集合与数组的比较

一、数组在存储多个数据方面的特点:

  • 一旦初始化以后,其长度就确定了;
  • 数组一旦定义好,其元素的类型也就确定了,我们也就只能操作指定类型的数据了;

二、数组在存储多个数据方面的缺点:

  • 一旦初始化以后,其长度就不可修改了;
  • 数组中提供的方法有限,不便于添加、删除、插入数据等操作。同时效率不高;
  • 数组没有现成的属性或方法获取数组中实际元素的个数;
  • 数组存储对于无序、不可重复的需求不能满足;

三、集合存储的优点:

  • 可以解决数组存储数据方面的以上弊端;

2. Collection接口(重点)

2.1 常用方法

注意:Collection接口的实现类的对象中添加数据obj时,要求obj所在类必须要重写equals( )方法;

常用方法如下:

  1. add(Object e):将元素e添加到集合中
  2. size( ):获取添加的元素的个数
  3. addAll(Collection coll):将coll集合中的元素添加到当前的集合中
  4. isEmpty( ):判断当前集合是否为空
  5. clear( ):清空结合元素
  6. contains(Object obj):判断当前集合中是否包含obj,在判断氏会调用obj对象所在类的equals( )方法
  7. containsAll(Collection coll):判断形参coll中的所有元素是否都存在于当前集合中
  8. remove(Object obj):从当前集合中移除obj元素
  9. removeAll(Collection coll):从当前集合中移除coll中的所有的元素
  10. retainAll(Collection coll):获取当前集合和coll集合的交集,并返回给当前集合
  11. equals(Object obj):判断两个集合的元素是否相同
  12. hasjCode( ):返回当前对象的哈希值
  13. toAttay( ):将集合变为数组(将数组变为集合:调用Arrays类的静态方法asList( );)
  14. iterator( ):返回Iterator接口的实例,用于遍历集合元素

2.2 Iterator迭代器接口

Iterator迭代器的作用:进行集合元素的遍历操作;

2.2.1 Iterator接口使用方法

  1. hasNext( ):判断是否还有下一个元素;
  2. next( ):①指针下移;②将下移以后集合位置上的元素返回;
  3. remove( ):Iterator内部定义的方法,可以在遍历的时候删除集合中的元素,注意此方法不同于集合直接调用的remove( );

2.2.2 遍历集合的几种方式

1. 遍历方式一:使用iterator迭代器
1
2
3
4
5
6
7
8
9
10
11
Collection coll = new ArrayList( );
Iterator iterator = coll.iterator( );
while(iterator.hasNext( )) {
System.out.println(iterator.next( ));
}

//注意以下错误方式:
while(coll.iterator( ).hasNext( )) {
System.out.println(coll.iterator( ).next( ));
}
//错误原因:集合对象每次调用iterator( )方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前;
2. 遍历方式二:使用foreach循环

注意:foreach循环用于遍历集合和数组,内部其实仍然调用了迭代器;

1
2
3
4
Collection coll = new ArrayList( );
for(Object obj : coll) {
System.out.println(obj);
}
3. 遍历方式三:使用普通循环
1
2
3
4
Collection coll = new ArrayList( );
for(int i=0; i < coll.size(); i++) {
System.out.println(list.get(i))
}

2.3 List接口

特点:存储有序的、可重复的数据;可理解为动态数组,用于替换原有的数组;

2.3.1 ArrayList(常用)

ArrayList 继承自 AbstractList,实现了 List 接口,是一个可以动态修改的数组,与普通数组的区别就是它没有固定大小的限制

常用方法:

  • add():将元素插入到指定位置的ArrayList中;
  • contains():判断元素是否在ArrayList;
  • get():通过索引值获取ArrayList中的元素;
  • subList(int fromIndex, int toIndex):用于截取并返回动态数组中的一部分;
  • set(int index, E element):用于替换动态数组中指定索引的元素;
  • sort(Comparator c):根据指定的顺序对动态数组中的元素进行排序;
  • removeIf(Predicate<E> filter):用于删除所有满足特定条件的数组元素;
  • forEach(Consumer<E> action):用于遍历动态数组中每一个元素并执行特定操作

2.3.2 LinkedList(常用)

与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低;

注意:LinkedList 实现了 Queue接口 ,可作为单向队列使用;同时实现了Deque接口,可作为双端队列和栈使用;

  • 队列(Queue):限定所有的插入只能在表的一端进行,而所有的删除都在表的另一端进行的线性表。表中允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。队列的操作是按先进先出(FIFO)的原则进行的。
  • 栈(Stack):限定仅在表尾进行插入和删除运算的线性表,表尾称为栈顶(top),表头称为栈底(bottom)。也是一种特殊的线性表,是一种后进先出(LIFO)的结构。

常用方法:

  • add(int index, E element):向指定位置插入元素;
  • addFirst() / addLast():把元素加到列表首部 / 尾部;
  • removeLast():删除并返回最后一个元素;
  • E poll():删除并返回第一个元素;
  • E get(int index):返回指定位置的元素;
  • int indexOf(Object o):查找指定元素从前往后第一次出现的索引;
  • E set(int index, E element):设置指定位置的元素;
  • Object[] toArray():返回一个由链表元素组成的数组;
  • int size():返回链表元素个数;

2.3.3 Vector(已过时)

2.3.4 三者的异同点

  • 相同点:两个类都实现了List接口,存储数据的特点相同,都是存储有序的、可重复的数据;
  • 不同点:
    • ArrayList:作为List接口的主要实现类:线程不安全,效率高,底层使用Object[ ] elementData存储;
    • LinkedList:底层使用双向循环链表存储,在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置),最后一个节点的后指针指向第一个节点的前指针,形成一个循环。线程不安全,对于频繁的插入和删除操作,使用此类效率比ArrayList效率高;
    • Vector:作为List接口的古老实现类:线程安全,效率低,底层使用Object[ ] elementData存储;

2.3.5 List接口使用场景推荐

如果涉及到“栈”、“队列”、“链表”等操作,应该考虑用List

对ArrayList和LinkedList的优缺点进行分析:

ArrayList

优点:ArrayList是实现了基于动态数组的数据结构,地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着的)。

缺点:因为地址连续,当要插入和删除时,Arraylist需要前后移动数据,所以插入和删除操作效率比较低。

LinkedList

优点:LinkedList是基于链表的数据结构,地址任意,所以开辟内存空间时不需要连续的地址,对于新增和删除操作比较占优势。

缺点:因为LinkedList要移动指针,所以查询操作性能比较低。

即对于ArrayList和LinkedList的使用场景推荐如下:

  1. 对于需要快速插入,删除元素,应该使用LinkedList。
  2. 对于需要快速随机访问元素,应该使用ArrayList。
  3. 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用ArrayList。对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用LinkedList。

2.4 Set接口

特点:存储无序的、不可重复的数据

  • 无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的;
  • 不可重复性:保证添加的元素按照equals( )判断时不能返回true,即相同的元素只能添加一个;

注意:

  1. 向Set中添加数据,其所在的类一定要重写hashCode( )和equals( );
    • 要同时重写equals()和hashCode()的原因:因为添加进集合的时候首先需要判断该集合中是否含有需要添加的元素,这个时候就要使用contains方法。contains方法内部调用equals方法,所以要重写**equals();而本来Object类中equals()相等,hashCode()必然相等,只有两个都相等才能说明是同一个对象,仅仅重写equals()就打破了这种逻辑,所以还需要重写hashCode()**;
  2. 重写的hashCode( )和equals( )尽可能保持一致:相等的对象必须具有相等的散列码;

2.4.1 HashSet(常用)

HashSet底层是数组+链表的结构;
元素添加例子:
向HashSet中添加元素a,首先调用元素a所在类的hashCode( )方法,计算元素a的哈希值,此哈希值通过某种算法计算出在HashSet底层数组中的存放位置(即索引位置),判断数组此位置上是否已经有元素:

  • 如果此位置上没有其他元素,则元素a添加成功;(情况1)
  • 如果此位置上有其他元素b(或以链表形式存放的多个元素),则比较元素a与元素b的hash值:
    • 如果hash值不相同,则元素a添加成功;(情况2)
    • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
      • 若equals()返回true,元素a添加失败;
      • 若返回false则元素a添加成功;(情况3)

注意:对于添加成功的情况2和情况3而言,元素a与已经存在指定索引位置上元素b以链表方式存储,元素b存储在数组中并指向元素a;

常用方法:

  • boolean contains(Object o):若此set包含指定的元素,则返回true;
  • boolean add(E e):如果指定的元素尚不存在,则将其添加到此集合中

2.4.2 LinkedHashSet

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据;

2.4.3 TreeSet

使用要求:

  1. 向TreeSet中添加的数据,要求是相同类的对象,不能添加不同类的对象;
  2. 两种排序方式:自然排序(实现Comparable接口)、定制排序(实现Comparator接口)
    • 自然排序中,比较两个对象是否相同的标准为:compareTo( )返回0,不再是equals( )
    • 定制排序中,比较两个对象是否相同的标准为:compare( )返回0,不再是equals( )

2.4.4 三者的异同点

  • 相同点:三者都实现了Set接口,存储无序的、不可重复的数据;

  • 不同点:

    • HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值;
    • LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet;
    • TreeSet:可以按照添加对象的指定属性进行排序,底层存储结构是红黑树;

3. Map接口(重点)

定义:双列集合,用来存储一对对(key-value)的数据

3.1 Map的特点

  • Map中的key:无序的、不可重复的,使用Set存储所有key(以HashMap类为例,key所在的类要重写equals()和hashCode()方法)
  • Map中的value:无序的、可重复的,使用Collection存储所有value(value所在的类要重写equals()方法)
    一个键值对:key-value构成了一个Entry对象
  • Map中的entry:无序的、不可重复的,key-value构成了一个Entry键值对对象,使用Set存储所有的entry

3.2 HashMap(常用)

1. HashMap概述

HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表后通过key对象的equals()逐一比对查找。

  • 注意:JDK7.0时底层结构只有数组+链表,JDK8.0后底层结构为数组+链表+红黑树;(当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储)

哈希冲突:当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了(即两个不同的元素,通过哈希函数得出的实际存储地址相同),其实这就是所谓的哈希冲突,也叫哈希碰撞。

2. HashMap在JDK7.0与JDK 8.0底层实现原理对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HashMap map =new HashMap( );
不同于像JDK7.0在实例化以后,底层创建了长度为16的一维数组Entry[ ] table,在JDK8.0后底层没有创建一个长度为16的数组,底层数组变为是Node[ ],而非Entry[ ]


map.put(key1, value1)
在JDK8.0,首次调用put( )方法时,底层才创建了长度为16的数组执行put()

JDK7.08.0时执行流程类似如下:
首先调用key1所在类的hashcode( )计算key1哈希值,哈希值经过某种算法计算以后得到在Entry数组中的存放位置
如果此位置上的数据为空,此时的key1-value1添加成功;(情况1
如果此位置上的数据不为空,比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功;(情况2
如果key1的哈希值与已经存在的某个数据(key2-value2)的哈希值相同,继续调用key1所在类的equals(key2)方法比较:
如果equals()返回false,则key1-value1添加成功;(情况3
如果equals()返回true,使用value1替换value2

注意:关于情况23,此时key1-value1和原来的数据以链表的方式存储;

常用方法:

  • size():计算 HashMap 中键/值对的数量;
  • put(K key,V value):将键/值对添加到 HashMap 中;
  • containsKey(Object key):检查 HashMap 中是否存在指定的 key 对应的映射关系;
  • replace(K key, V oldValue, V newValue):替换 HashMap 中是指定的 key 对应的 value;
  • forEach(BiConsumer<K, V> action):用于对 HashMap 中的每个映射执行指定的操作;
  • merge(key, value, remappingFunction):先判断指定的 key 是否存在,如果不存在,则添加键值对到 hashMap 中;
  • keySet() / values() / entrySet():分别是返回所有key / value / 键值对;
  • getOrDefault(Object key, V defaultValue):获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值;

3.3 LinkedHashMap

3.4 TreeMap

3.5 HashTable(已过时)

3.6 Properties

3.7 五者的异同点

  • 不同点:
    • HashMap:作为Map接口的主要实现类,线程不安全,效率高;可以存储null的key和value;
    • LinkedHashMap:保证在遍历Map元素时,可以按照添加的顺序实现遍历,原因在原有的HashMap底层结构基础上添加了一对指针,指向前一个和后一个元素;(对于频繁的遍历操作,此类执行效率高于HashMap)
    • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序或定制排序;
    • Hashtable:作为古老的实现类,线程安全,效率低,不可以存储null的key和value;
    • Properties:Hashtable的子类,常用来处理配置文件,key和value都是String类型的;

4. Collections工具类

Collections:是操作Set、、List和Map等集合的工具类

常用方法:

  1. 排序操作(均为static方法)

    • reverse(List):反转List中元素的顺序
    • shuffle(List):对List集合元素进行随机排序
    • sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素按升序排序
    • swap(List, int, int):将指定List集合中的i处元素和j处元素进行交换
  2. 查找替换

    • Object max(Collection):根据元素的自然排序,返回给定集合中的最大元素
    • int frequency(Collection, Object):返回指定集合中指定元素的出现次数
    • void copy(List dest, List src):将src中的内容复制到dest中
    • boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换旧值

    synchronizedList(List):将指定集合包装成线程同步的集合(用于解决ArrayList和HashMap线程安全问题)