0%

【Java基础】集合

学习集合整理的学习笔记

一、Collection(集合)

1.1 集合框架的概述

  • 1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器
    • 此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt)
  • 2.数组在存储多个数据方面的特点:
    • 一旦初始化以后,其长度就确定了
    • 数组一旦定义好,其元素的类型也就确定了,我们也只能操作指定类型的数据了
  • 3.数组在存储多个数据方面的缺点:
    • 一旦初始化以后,其长度就不可以修改
    • 数组中提供的方法非常有限,对于添加,删除,插入数据等操作,非常不便,同时效率不高
    • 获取数组中实际元素的个数,数组没有现成的属性或方法可用
    • 数组存储数据的特点:有序、可重复。对于无序,不可重复的需求,不能满足

1.2 分类

  • Collection接口: 单列数据,定义了一组对象的方法的集合
    • List: 元素有序、可重复的集合(动态数组)
      • ArrayList、LinkedList、Vector
    • Set: 元素无序,不可重复的集合
      • HashSet、LinkedHashSet、TreeSet
  • Map接口: 双列数据,保存具有映射关系“key-value”的集合【类似python的字典】
    • HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

1.3 Collection中定义的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
add(Object e):集合里添加数据

size():返回一个int型,获取集合里数据的个数

addAll(Collection coll):将一个集合的元素,全部添加到另一个集合里

clear():清空集合里的元素

isEmpty():返回一个boolean型,判断当前集合是否为空

contains(Object obj):返回一个boolean型,判断当前集合中是否包含obj
注意:contains判断对象时,是调用该对象内equals方法,若此方法没重写,默认比较地址值。

containsAll(Collection coll):返回一个boolean型,判断当前集合里的数据是否全部包含了另一个集合的数据

remove(Object obj):返回一个boolean型,移除某个元素,移除成功true,否者false
注意:remove也是调用该对象的equals方法进行匹配,相同就进行移除

removeAll(Collection coll):返回一个boolean型,将一个集合里的元素从另一个集合里全部移除(差集)

retainAll(Collection coll):返回一个boolean型,保留两个集合的共同数据(交集)

equals(Object obj):返回一个boolean型,比较两个集合是否相同

hashCode():返回一个int型,计算哈希值并返回

toArray():返回一个Object[],将一个集合转变成数组

额外拓展:
Arrays.asList(数组):将一个数组变成一个集合

1.4 集合遍历

  • (1)迭代器
    • 迭代器是Collocetion的其中一个方法。iterator(),返回一个迭代器对象。用于遍历数组
    • 迭代器操作:
      • 1.内部的方法:hasNext() 和 next()
      • 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
      • 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
1
2
3
while(iterator.hasNext()){//是否有下一个,有就true,无就false
System.out.println(iterator.next());
}
  • (2)增强for循环
    • 格式: for(集合中元素的类型 局部变量 :集合对象),类似python的for xx in xx
    • 内部仍然调用了迭代器
1
2
3
for(Object obj : coll){
System.out.println(obj);
}

二、List

2.1 分类

|—-Collection接口
  |—-List接口:存储有序的,可重复的数据
    |—-ArrayList:作为List接口的主要实现类(1.2出现),线程不安全,效率高;底层使用Object[] elementData存储
    |—-LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList效率高。底层使用双向链表存储
    |—-Vector:作为List接口的古老实现类(1.0出现),线程安全,效率低;底层使用Object[] elementData存储


2.2 ArrayList源码分析

  • JDK 7情况下:
1
2
3
4
5
6
7
8
9
ArrayList list = new ArrayList();
//底层创建了长度为10的Object[]数组elementData

list.add(123);
//list[0] = new Integer(123);
...
list.add(11);
//如果此次的添加导致底层elementData数组容量不够,则扩容。
//默认情况下扩容为原来容量的1.5倍,同时将原有数组中的数组赋值到新的数组中
    • 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
  • JDK8情况下:
1
2
3
4
5
6
7
ArrayList list = new ArrayList();
//底层Object[] elementData初始化为{},并没有创建

list.add(123);
//第一次调用add()时,底层才创建长度为10的数组,并将数据添加到数组中
...
后续的添加和扩容操作与JDK7一样

总结:
JDK7中的ArrayList的对象创建,类似于单例模式的饿汉式
JDK8中的ArrayList的对象创建,类似于单例模式的懒汉式,延迟了数组的创建,节省内存


2.3 LinkedList源码分析

1
2
3
4
5
LinkedList list = new LinkedList();
//内部声明了Node类型的first和last属性,默认值为null

list.add(213);
//将213封装到Node中,创建Node对象。
1
2
3
4
5
6
7
8
9
10
11
12
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}

2.4 List接口中的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
除了Collection中的方法外:
void add(int index, Obejct else):在指定的位置中插入数组

boolean addAll(int index, Collection else):从index位置将else集合中所有数据插入

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位置的子集合
1
2
3
4
5
6
7
8
9
10
总结常用方法:
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object else)
查:get(int index)
插:add(int index, Object else)
长度:size()
遍历:(1)Iterator迭代器
2)增强for循环
3)普通循环

三、Set

3.1 分类

|—-Collection接口
  |—-Set接口:单列集合,用来存储一个一个的对象
    |—-HashSet:作为Set接口的主要实现类;线程不安全;可以存放nll值
      |—-LinkedSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
    |—-TreeSet:可以按照添加对象的指定属性,进行排序


3.2 无序、不可重复性理解

  • (1)无序性
    • (1)不等于随机性。
    • (2)存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值进行存储
  • (2)不可重复性
    • 保证添加的元素,按照equals()判断时,不能反回true。即:相同的元素只能添加一个
  • (3)以HashSet为例,来了解存储过程
    • 我们向HashSet中添加元素b,首先调用元素b所在类的hashCode()方法,计算元素b的哈希值
    • 此哈希值通过某种算法计算出HashSet底层数组中存放位置(即:索引位置),判断数组在此位置上是否已经有元素:
      • 如果此位置上没有其他元素,则元素b添加成功 (情况一)
      • 如果此位置上有其他元素b(或以链表形式存在多个元素),则比较元素a与元素b的hash值
        • 如果hash值不相同,以链表的形式将元素b添加(情况二)
        • 如果hash只相同,进而需要调用元素b所在类的equals()方法
          • equals()返回false,以链表的形式将元素b添加(情况三)
          • equals()返回true,则元素b添加失败
    • 注意:
      • JDK7以链表添加数据,是将新数据放进数组,并用指针指向原数组数据;
      • JDK8以链表添加数据,是数组里面的数据,用指针指向新添加的数据
      • 口诀:七上八下
  • (4)总结
    • 向Set中添加数据,其所在的类一定要重写hashCode() 和 equals()
    • 重写hashCode() 和 equals() 尽可能保持一致性:相等的对象必须具有相等的散列码
    • HashSet底层:数组 + 链表

3.3 LinkedHashSet

  • 说明
    • LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录次数据前一个数据和后一个数据。本质上和HashSet区别不大
    • 优点:对于频繁的遍历操作,LinkedHashSet的效率比HashSet高

3.4 TreeSet

  • (1)使用说明
    • 向TreeSet中添加的数据,要求是相同类的对象
    • 存储的形式类似于二叉树
    • 访问数据是从小到大访问,需要用到Java比较器
  • (2)比较方法
    • 自然排序
      • 类实现Comparable接口,重写compareTo()方法
      • TreeSet判断是否存储数据是否相同,使用的是compareTo()方法,不再是equals
    • 定制排序
      • 创建comparator实现类对象,该实现类重写compare()方法,将此对象作为参数传入TreeSet的创建中
      • TreeSet判断是否存储数据是否相同,使用的是compare()方法,不再是equals

四、Map

4.1 Map分类

|—-Map:双列数据,存储key-value对的数据 —类似于高中函数:y=f(x)
  |—-HashMap:作为主要实现类;线程不安全,效率高;存储null的key和value;
    |—-LinkedHashMap:保证在遍历map元素是,可以按照添加的顺序实现遍历
      原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素
      对于频繁的遍历操作,此类的执行效率高于HashMap
  |—-TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序、定制排序
    底层使用红黑树
  |—-Hashtable:作为古老实现类;线程安全,效率低;不能存储null的key和value;
    |—-Properties:常用来处理配置文件。key和value都是String类型


4.2 Map结构理解

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

4.3 底层实现原理

  • 基本与HashSet没有太大区别(以HashMap为例):
    • 比较key的hash值,看位置上是否有其他数据
      • 有就再比较该位置上链表的key的hash值,看是否相同
        • key的hash值相同,就调用equals方法
          • equals()返回false,添加成功
          • equals()返回true,修改该key的value值—不同点
    • 在不断添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)。默认的扩容方式:扩容到原来容量的2倍,并将原有的数据复制过来。

4.4 JDK7 和 JDK8 底层区别

  • 1.new HashMap():JDK7底层直接创建长度为16的数组;
    • JDK8底层没有创建一个长度为16的数组,添加数据时再创建
  • 2.JDK7底层数组是:Entry[];
    • JDK8底层数组是:Node[]
  • 3.JDK7底层结构只有:数组+链表
    • JDK8底层结构:数组+链表+红黑树
    • 当数组的某一个索引位置上的元素一链表形式存在的数组个数>8,且当前数组长度>64时,此时此索引位置上的所有数组改为使用红黑树

4.5 其他实现类

  • LinkedHashMap: 原理与LinkedHashSet无太大差异,就是在HashMap基础上,多两个指针指引数据前后顺序
  • TreeMap: 原理与TreeSet无太大差异,key存储的数据必须是同一对象,并提供Java比较器(自然排序,定制排序)来进行对数据的排序
  • Properties: 配置文件,涉及文件的读写操作(暂不细讲)
    • (1)创建Properties对象
    • (2)对象.load(流)//加载流对应的文件
    • (3)对象.getProperty(String key)//获取对应key相应的value
    • 注意: 配置文件里面的内容为:"key=value",不要在”=”之间添加空格

4.6 常用方法

Map接口定义方法

1
2
3
4
5
添加、删除、修改操作:
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中所有的数据
1
2
3
4
5
6
7
元素查询操作:
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是否相等
1
2
3
4
元视图操作(用于遍历):
Set KeySet():返回所有Key构成的Set集合
Collection value():返回所有value构成的Collection集合
Set entrySet():返回所有Key-value对构成的集合

总结

1
2
3
4
5
6
7
总结:常用方法
添加:Object put(Object key, Object value)
删除:Object remove(Object key)
修改:Object put(Object key, Object value)
查询:Object get(Object key)
长度:int size()
遍历:Set KeySet() / Collection value() / Set entrySet()

五、Collections工具类

5.1 常用方法

1
2
3
4
5
6
1)排序操作:
void reverse(List):反转List中元素的顺序
void shuffle(List):对List集合元素进行随机排序
void sort(List):根据元素的自然顺序对指定List集合元素按升序排序
void sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
void swap(List, int, int):将指定list集合中的i处元素和j处元素进行交换
1
2
3
4
5
6
7
8
2)查找、替换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
Object min(Collection):同理,找最小
Object min(Collection, Comparator):同理,找最小
int frequency(Collection, Object):返回指定集合中指定元素的出现次数
void copy(List dest, List src):将src中的内容赋值到dest中
boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值
1
2
3
3)Collections类提供了多个synchronizedXxx()方法,
该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
例:List list1 = Collections.synchronizedList(list);

5.2 注意点

  • copy方法的使用:
1
2
3
4
错误示范:
List list1 = new ArrayList(list.size());
Collections.copy(list1, list);
新建的List中并没有元素,不能进行赋值
1
2
3
4
正确操作:
List list2 = Arrays.asList(new Object[list.size()]);
Collections.copy(list2, list);
数组转变成集合,默认赋予null

六、面试题

  • 面试题:Collection 和 Collections 的区别?
    • Collection是集合的接口
    • Collections是操作Collection和Map的工具类
  • 面试题:ArrayList、LinkedList、Vector三者的异同?
    • 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的,可重复的数据
    • 异:
      • ArrayList:作为List接口的主要实现类(1.2出现),线程不安全,效率高;底层使用Object[] elementData存储
      • Vector:作为List接口的古老实现类(1.0出现),线程安全,效率低;底层使用Object[] elementData存储
      • LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList效率高。底层使用双向链表存储
  • 面试题:HashMap的底层实现原理?
    • 存储数据时,先通过获取该数据的key的hash值,通过某种算法计算在数组的位置,查看该位置是否有数据:
      • 若无,存储成功。若有,比较该位置上链表元素的hash值是否相同:
        • 若相同,链表形式存储(JDK7:头插法;JDK8:尾接法)。若不同,调用equals()方法:
          • 若equals()返回false,链表形式存储。若equals返回true,则修改该key上的value值。
  • 面试题:HashMap 和 Hashtable的异同?
    • 同:双列数据,存储key-value对的数据
    • 异:
      • HashMap:作为主要实现类;线程不安全,效率高;存储null的key和value
      • Hashtable:作为古老实现类;线程安全,效率低;不能存储null的key和value