食用前说明:
本文章内容来自B站韩顺平老师的课堂笔记,本人只是将其进行自我整理,内容有所删减,韩顺平老师的B站课程AV号:BV1fh411y7R8
本文章内容来自韩顺平老师的课堂笔记的 第14章 集合,重点学习:ArrayList,Vector,HashSet,HashMap等内容。
本章链接直达
名称 | 长度 | |
---|---|---|
数组 | 长度开始必须指定,且不能更改 | 1. 保存必须为同一类型元素 |
进行增加 / 删除元素,比较麻烦 | | 集合 | 动态保存任意多个对象 | 1. 提供了一系列方便的操作对象的方法:add、remove、set、get等
使用集合添加,删除新元素,简洁明了 |
Java 的集合类很多,主要分为两大类,如图 :[最好背下来]
14.3.1 Collection 接口实现类的特点
collection 实现子类可以存放多个元素,每个元素可以是 Object
Collection 接口没有直接的实现子类,是通过它的子接口 Set 和 List 来实现的
有些 Collection 的实现类,可以存放重复的元素,有些不可以
有些 Collection 的实现类,有些是有序的 (List),有些不是有序的 (Set)
14.3.2 Collection 接口遍历元素方式 1-使用 Iterator(迭代器)
Iterator对象称为迭代器,主要用于遍历 Collection 集合中的元素。
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
Iterator 仅用于遍历集合,Iterator 本身并不存放对象。
案例演示
public static void main(String[] args) { Collection col = new ArrayList(); col.add(new Book("三国演义", "罗贯中", 10.1)); col.add(new Book("小李飞刀", "古龙", 5.1)); col.add(new Book("红楼梦", "曹雪芹", 34.6)); //现在老师希望能够遍历 col 集合 //1. 先得到 col 对应的 迭代器 Iterator iterator = col.iterator(); //2. 使用 while 循环遍历 // while (iterator.hasNext()) {//判断是否还有数据 // //返回下一个元素,类型是 Object // Object obj = iterator.next(); // System.out.println("obj=" + obj); // } //老师教大家一个快捷键,快速生成 while => itit //显示所有的快捷键的的快捷键 ctrl + j while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println("obj=" + obj); } //3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素 // iterator.next();//NoSuchElementException //4. 如果希望再次遍历,需要重置我们的迭代器 iterator = col.iterator(); System.out.println("===第二次遍历==="); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println("obj=" + obj); } }
14.3.3 Collection 接口遍历对象方式 2-for 循环增强
增强for循环,可以代替 iterator 迭代器,特点:增强for就是简化版的 iterator ,本质一样。只能用于遍历集合或数组。
案例演示
public static void main(String[] args) { List list = new ArrayList(); list.add(new Dog("小黑", 3)); list.add(new Dog("大黄", 100)); list.add(new Dog("大壮", 8)); //先使用 for 增强 for (Object dog : list) { System.out.println("dog=" + dog); } //使用迭代器 System.out.println("===使用迭代器来遍历==="); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object dog = iterator.next(); System.out.println("dog=" + dog); } }
List 集合中元素有序 (即添加顺序和取出顺序一致)、且可重复
List 集合中的每个元素都有其对应的顺序素引,即支持索引。
List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
案例演示
//1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例] List list = new ArrayList(); list.add("jack"); list.add("tom"); list.add("mary"); list.add("hsp"); list.add("tom"); System.out.println("list=" + list); //2. List 集合中的每个元素都有其对应的顺序索引,即支持索引 // 索引是从 0 开始的 System.out.println(list.get(3));//hsp
14.4.1 List 的三种遍历方式 [ArrayList, LinkedList,Vector]
案例演示
//List 接口的实现子类 Vector LinkedList //List list = new ArrayList(); //List list = new Vector(); List list = new LinkedList(); list.add("jack"); list.add("tom"); list.add("鱼香肉丝"); list.add("北京烤鸭子"); //遍历 //1. 迭代器 Iterator iterator = list.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println(obj); } System.out.println("=====增强 for====="); //2. 增强 for for (Object o : list) { System.out.println("o=" + o); } System.out.println("=====普通 for===="); //3. 使用普通 for for (int i = 0; i < list.size(); i++) { System.out.println("对象=" + list.get(i)); }
14.5.1 ArrayList 的注意事项
permits all elements, including null,ArrayList可以加入null,并且多个
ArrayList 是由数组来实现数据存储的
ArrayList 基本等同于Vector,除了 ArrayList 是线程不安全 (执行效率高) 看源码。 在多线程情况下,不建议使用 ArrayList
14.5.2 ArrayList 的底层操作机制源码分析(重点,难点.)
ArrayList 中维护了一个 Object 类型的数 elementData。 transient Object[] elementData; //transient 表示瞬间,短暂的,表示该属性不会被序列号
当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容量为0,第1次添加,则扩容 elementData 为10,如需要再次扩容,则扩容elementData 为1.5倍。
如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小,如果需要扩容,则直接扩容 elementData 为1.5倍。
14.6.1 Vector 的基本介绍
Vector 类的定义说明
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
Vector 底层也是一个对象数组,protected Object[] elementData;
Vector 是线程同步的,即线程安全,Vector 类的操作方法带有 synchronized
public synchronized E get(int index) { f (index >= elementCount) throw new ArraylndexOutOfBoundsException(index); return elementData(index); }
在开发中,需要线程同步安全时,考虑使用Vector
14.6.2 Vector 和 ArrayList 的比较
底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 如果有参构造1.5倍 |
如果是无参 |
第一次 10
从第二次开始按1.5倍扩容 | | Vector | 可变数组Object[] | jdk1.0 | 安全,效率不高 | 如果是无参,默认10,满后,就按2倍扩容 如果指定大小,则每次直接按2倍扩容 |
14.7.1 LinkedList 的全面说明
LinkedList 底层实现了双向链表和双端队列特点
可以添加任意元素 (元素可以重复),包括 null
线程不安全,没有实现同步
14.7.2 LinkedList 的底层操作机制
LinkedList 底层维护了一个双向链表
LinkedList 中维护了两个属性 first 和 last 分别指向,首节点和尾节点
每个节点(Node对象),里面又维护了 prev、next、item 三个属性,其中通过 prev 指向前一个,通过 next 指向后一个节点。最终实现双向链表。
所以 LinkedList 的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
模拟一个简单的双向链表
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低,通过数组扩容 | 较高 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 |
如何选择 ArrayList 和 LinkedList :
如果我们改查的操作多,选择 ArrayList
如果我们增删的操作多,选择 LinkedList
一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择 ArrayList
在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是 ArrayList ,另外一个模块是 LinkedList , 也就是说,要根据业务来进行选择
14.9.1 Set 接口基本介绍
无序(添加和取出的顺序不一致),没有索引
不允许重复元素,所以最多包含一个 null
JDK API 中 Set 接口的实现类有:
14.9.2 Set 接口的遍历方式
同 Collection 的遍历方式一次,因为 Set 接口是 Collection 接口的子接口。
可以使用迭代器
增强for
不能使用索引的方式来获取
1. HashSet 实现了 Set 接口
2. HashSet 实际上是 HashMap,看下源码
public HashSetk() { map = new HashMap<>(); }3. 可以存放 null 值,但是只能有一个 null
4. HashSet 不保证元素是有序的,取决于hash后,再确定索引的结果。(即,不保证存放元素的顺序和取出顺序一致)
5. 不能有重复元素 / 对象. 在前面 Set 接口使用已经讲过
14.10.1 HashSet 底层机制说明
分析 HashSet 底层是 HashMap,HashMap 底层是 (数组+链表+红黑树)
分析 HashSet 的添加元素底层是如何实现 (hash() + equals())
HashSet 底层是 HashMap
添加一个元素时,先得到 hash 值 -会转成 -> 索引值
找到存储数据表 table,看这个索引位置是否已经存放的有元素
如果没有,直接加入
如果有,调用 equals 比较,如果相同,就放弃添加,如果不相同,则添加到最后
在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD (默认是 8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY (默认64),就会进行树化 (红黑树)
14.11.1 LinkedHashSet 的全面说明
LinkedHashSet 是 HashSet 的子类
LinkedHashSet 底层是一个 LinkedHashMap ,底层维护了一个 数组+ 双向链表 (hash表 和 双向链表)
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
LinkedHashSet 不允许添重复元素
14.12.1 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
14.13.1 HashMap 小结
Map 接口的常用实现类:HashMap、Hashtable 和 Properties。
HashMap 是 Map 接口使用频率最高的实现类。
HashMap 是以 key-val 对的方式来存储数据(HashMap$Node类型)
key 不能重复,但是值可以重复,允许使用 null 键和 null 值。
如果添加相同的 key ,则会覆盖原来的 key-val ,等同于修改。(key 不会替换,val 会替换)
与 HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的。(jdk8的 hashMap 底层 数组+链表+红黑树)
HashMap 没有实现同步,因此是线程不安全的,方法没有做到同步互斥的操作,没有 synchronized
14.13.2 HashMap 底层机制及源码剖析
(k,v)是一个Node 实现了 Map.Entry<K,V>,查看 HashMap 的源码可以看到。
jdk7.0 的 hashmap 底层实现 [数组+链表] ,jdk8.0 底层 [数组+ 链表+红黑树]
扩容机制 [和 HashSet 相同]
HashMap 底层维护了 Node 类型的数组 table ,默认为 null
当创建对象时,将加载因子 (loadfactor) 初始化为0.75。
当添加 key-val 时,通过 key 的哈希值得到在 table 的索引。然后判断该索引处是否有元素,如果没有元素直接添加。
如果该索引处有元素,继续判断该元素的 key 和准备加入的 key 是否相等,如果相等,则直接替换 val;
如果不相等,需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
第1次添加,则需要扩容 table 容量为16,临界值 (threshold) 为12(16*0.75)
以后再扩容,则需要扩容 table 容量为原来的2倍 (32),临界值为原来的2倍,即24,依次类推。
在Java8中,如果一条链表的元素个数超过 TREEIFY_THRESHOLD(默认是8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
14.14.1 HashTable 的基本介绍
存放的元素是 键值对 : 即 K-V
hashtable 的键和值都不能为 null,否则会抛出 NullPointerException
hashTable 使用方法基本上和 HashMap 一样
hashTable 是线程安全的(synchronized),hashMap 是线程不安全的
14.14.2 Hashtable 和 HashMap 对比
版本 | 线程安全(同步) | 效率 | 允许null键null值 | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hashtable | 1.0 | 安全 | 较低 | 不可以 |
14.15.1 基本介绍
Properties 类继承 Hashtable 类并且实现了 Map 接口,也是使用一种键值对的形式来保存数据。
他的使用特点和 Hashtable 类似
Properties 还可以用于 从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行读取和修改
说明:工作后 xxx.properties 文件通常作为配置文件,这个知识点在 IO流举例,有兴趣可先看文章
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
先判断存储的类型(一组对象 [单列] 或一组键值对 [双列] )
一组对象 [单列]:Collection 接口
允许重复:List
增删多:LinkedList [底层维护了一个双向链表]
改查多:ArrayList [底层维护 Object类型的可变数组]
不允许重复:Set
无序:HashSet [底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet [老韩举例说明]
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
一组键值对 [双列]:Map
键无序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap [老韩举例说明]
键插入和取出顺序一致:LinkedHashMap
读取文件: Properties
14.17.1 Collections 工具类介绍
Collections 是一个操作 Set、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
14.17.2 排序操作:(均为 static 方法)
功能 | |
---|---|
reverse(List) | 反转 List 中元素的顺序 |
shuffle(List) | 对 List 集合元素进行随机排序 |
sort(List) | 根据元素的自然顺序对指定 List 集合元素按升序排序 |
sort(List, Comparator) | 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 |
swap(List, int, int) | 将指定 list 集合中的 i 处元素和 j 处元素进行交换 |
14.17.3 查找、替换
功能 | |
---|---|
Object max(Collection) | 根据元素的自然顺序,返回给定集合中的最大元素 |
Object max(Collection,Comparator) | 根据 Comparator 指定的顺序,返回给定集合中的最大元素 |
Object min(Collection) | 根据元素的自然顺序,返回给定集合中的最小元素 |
Object min(Collection,Comparator) | 根据 Comparator 指定的顺序,返回给定集合中的最小元素 |
int frequency(CollectionObject) | 返回指定集合中指定元素的出现次数 |
void copy(List dest,List src) | 将src中的内容复制到dest中 |
boolean replaceAll (List list,Object oldVal,Object newVal) | 使用新值替换 List 对象的所有旧值 |