type
status
date
slug
summary
tags
category
icon
password
数据结构分类

- Collection接口: 存储的是单列的数据
- ArrayList:
- LinkedList:
- Vector:
- HashSet:
- LinkedHashSet:
- TreeSet:
List接口: 存储的元素是有序的
Set接口: 存储的元素是无序且不重复的
Map接口:存储的元素是双列的数据 Key-Value形式存储的数据
- HashMap:
- LinkedHashMap:
- TreeMap:
集合和数组存储数据概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
数组存储的特点
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
比如:String[] arr;int[] arr1;Object[] arr2;
弊端:
- 一旦初始化以后,其长度就不可修改。
- 数组中提供的方法非常限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足(集合正是解决了数组的弊端)
Collection接口
该接口中没有提供通过下标操作元素的方法
List接口和Set接口都继承自 Collection接口,Collection接口里定义的方法,List和Set的实现类都可以调用
Collection接口的常用方法:
add(Object obj) addAll(Collection coll) size() isEmpty() clear();
contains(Object obj) containsAll(Collection coll) remove(Object obj)
removeAll(Collection coll) retainsAll(Collection coll) equals(Object obj);
hasCode() toArray() iterator();
其中retain方法可以看做是remove方法的反向操作,一个是只保留,一个是移除
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
Collection集合和数组间的转化
Iterator迭代器和foreach循环
遍历Collection的两种方式:
- 使用迭代器Iterator (定义在java.utils包下)
- foreach循环(或增强for循环)
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。

Remove的使用
如果要使用Iteratror中的remove()方法,如果在还未调用next()或在上一次调用next方法之后已经调用了next方法,再调用都会报IllegalStateException
迭代器 的Remove()方法和集合直接调用Remove的方法不同,要求参数也不同
使用迭代器删除元素时,必须用迭代器的remove方法进行元素删除,不能使用List的remove方法删除,因为前者会保证方法中的modCount以及expectedModCount值一致,但是后者只是自增了modCount,没有调整expectedModCount,会导致错误
增强for循环不能对元素进行增删操作,如果有必须使用循环删除元素的需求,更推荐用 fori 循环+list列表的方式删除
List接口
list存储的是有序、可重复的数据
该接口在其“父接口”Collection的基础上新增了很多通过下标来操作元素的方法,比如:
- 增 add(E e) /
- 删 remove(E e) / remove(int index)
- 改 set(int index,E e)
- 查 get(int index) / indexOf(E e) / lastIndexOf(E e):
- 插 add(int index,E e)
- 长度 size()
- 遍历Iterator迭代器方式、增强for循环、普通的循环
常用使用类
Collection接口:单列集合,用来存储一个一个的对象
- List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原的数组
- ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
- LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存

以上练习题,updateList方法执行的是删除列表中索引为2的元素,故删除的是数字3。list中存的数据其实是自动装箱后基本包装类数据,add没有重载方法,添加的是对象,但是remove既可以删除对象,又可以删除索引,这里删除的是索引。如果要删除元素2,则list.remove()方法里需要删除的是一堆Interger类型的对象,即
List.remove(new Integer(2))
要点:区分List中的remove(int index)和remove(Object obj)方法
泛型Generic
定义数组的时候,语法是 元素数据类型[] 数组名
定义 List时, List list = new ArrayList(),如果不使用泛型的话,无法指定List里存储的元素类型,换言之,list列表里可以添加任何类型的数据。
但是通常在实际业务需求中,我们需要往一个List里面添加同一类型数据,另外在数据操作中,同一个类型的数据更方便进行统一操作,因此我们引入了泛型的概念
泛型的使用: 就是规定 Set / List / Map 的元素类型
泛型只能使用引用数据类型,不能使用基本数据类型
泛型通配符
<?> 表示任意类型
<? extends Person >规定泛型的上限,可以传入Person以及Person的子类
<? super Person >规定泛型的下限,可以传入Person以及Person的父类
自定义泛型
杂记比较器
用来操作Object对象
数组对应的有一个 Arrays工具类,用来快速操作数组
Collection接口,表示单列集合 List和Set
Collections工具类,用来快速的操作 List 和 Set
ArrayList和LinkedArrayList
List接口里有两个常用的实现类: ArrayList LinkedList
add(E e) / add(int i,E e) / remove(int index) / remove(E e)/get(int index)
indexOf(E e) / lastIndexOf(E e) / size() / isEmpty() / clear()
只要和下标相关的方法,都是List特有的,Collection接口里没有的!
迭代器
iterator() 方法是 Collection接口里的方法,List和Set都能够调用的方法
可以用来对 List和Set里的元素进行遍历
增强for循环<?>
list无论是调用 add还是remove,只要对集合里的元素进行了增删操作,都会让modCount成员变量自增一次
增强for循环无法通过赋值操作能修改原有数据,是复制出了一个全新的数组,增强for循环的递增其实仍然调用了迭代器
List源码分析
- ArrayList:底层使用的是数组结构,查询快,增删慢,开发中最常使用的集合。
- 查询快是因为每个元素都有一个编号
- 增删慢当数组满了以后,扩充时会将原来数组的内容整个复制,插入以及删除数据时,会影响到其他的元素
- LinkedList: 底层使用的是双向链表结构,查询慢,增删快
- 增删快:在插入和删除时,最多只会涉及到三个节点
- 查询慢:元素没有编号,查询数据是通过 循环来实现
- Vector: 实现和 ArrayList基本相同。
- Vector是线程安全的,效率低,ArrayList是线程不安全,效率高类似StringBuilder和StringBuffer的区别
- ArrayList和LinkedList的选择:
- 如果是增删比较频繁的场景,推荐使用LinkedList;
- 如果是查询比较频繁的场景,推荐使用ArrayList
ArrayList源码分析
- 成员变量:
- Object[] elementData: Object类型的数组 elementData,用来保存所有的数据
- int size: 表示数组里已经存入的元素个
- 成员常量:
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 如果不指定数组的长度,elementData默认是空数组
- DEFAULT_CAPACITY = 10; 如果不指定数组的长度,当调用add方法添加元素时,elementData长度会被设置为10,回顾StringBuilder的初始长度是16
- 构造方法:
- ArrayList(): 空参的构造方法。会将 elementData数组设置为
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA 空数组
- ArrayList(int capacity): 会将elementData数组创建,并指定长度
- 常用方法:
- add(E e): 判断 elementData是否是空数组,如果是空,让指定数组的长度为默认值 DEFAULT_CAPACITY,如果添加的元素个数超出了 elementData 数组的长度,会将 elementData 数组扩容为 原来的 1.5倍
- add(int index,E e): 当插入元素时,影响到的元素会很多 [index,size()-1]都会受影响
jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2.2 jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
...
后续的添加和扩容操作与jdk 7 无异。
小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LikedArrayList源码
LinkedList底层使用的是双向链表,链表上存储的数据不再是元素,而是将元素包装成为了 Node类型的对象
构造方法:<待补充>
成员变量:
Node<E> first: 表示链表的第一个元素
Node<E> last: 表示链表的最后一个元素
int size:链表上元素的个数
Vector源码
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。,在扩容方面,默认扩容为原来的数组长度的2倍。Vector方法里采用了大量的同步代码块,相对ArrayList是效率低,安全性高的
Set接口
- 存储的元素是无序的,没有编号,不能通过下标来操作元素,不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
- 存储的元素不允许重复(判断元素是否重复的依据),保证添加的元素照equals()判断时,不能返回true。即:相同的元素只能添加一个。
Set在开发中的使用场景比较少,适用于无序以及不重复的数据组织场景。
元素添加过程(以hashSet为例)
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置,判断数组此位置上是否已经元素:
- 如果此位置上没其他元素,则元素a添加成功。
$情况1
- 如果此位置上其他元素b(或以链表形式存在的多个元素,则比较元素a与元素b的hash值,如果是链表的话, 得逐个比,直到比完或者得出结论):
- 如果hash值不相同,则元素a添加成功。
$情况2
- 如果hash值相同,进而需要调用元素a所在类的equals()方法:
(1) equals()返回true,元素a添加失败
$情况3
(2) equals()返回false,则元素a添加成功。
对于添加成功的情况2和情况3而言,元素a 与已经存在指定索引位置上数据以链表的方式存储。连接方向:
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下

Set接口中没有额外定义新的方法,用的都是Collection中声明过的方法。
Collection接口的常用方法:
add(Object obj) addAll(Collection coll) size() isEmpty() clear();
contains(Object obj) containsAll(Collection coll) remove(Object obj) removeAll(Collection coll) retainsAll(Collection coll) equals(Object obj);
hasCode() toArray() iterator();
Set分类
Collection接口:单列集合,用来存储一个一个的对象
- Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
- HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
- LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
- TreeSet:可以照添加对象的指定属性,进行排序。
在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
HashSet
HashSet底层使用的是 HashMap(本质)
HashSet判断元素是否重复的依据:先判断哈希值是否相同,如果哈希值不同,直接存入; 如果哈希值相同,再调用equals方法判断,如果equals方法如果是true,认为是统一个元素,不再存入,反之存入。
HashSet/LinkedHashSet:
要求:向HashSet、LinkedHashSet中添加的数据,其所在的类一定要重写hashCode()和equals(),HashSet里如果没有重写hashCode,创建了两个参数一样的对象,这两个对象的哈希值还是不一定相同的,未重写的Object类HashCode可以看做是随机生成的。
重写两个方法时,对象中用作 equals() 方法比较的 Field,计算 hashCode 值时候也要用到这些字段,这样可以尽量减少HashCode值冲突的可能。
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。
LinkHashSet
LinkedHashset在继承原有的HashSet的基础上,每个数据还维护了两个引用,记录此数据的前后元素(在添加的元素上有额外提供了一对双向链表)
优点:对于频繁的遍历操作,LinkedHashSet的效率高于HashSet

TreeSet
向TreeSet中添加的数据,要求是相同类的对象
TreeSet里的元素会进行排序,排序的依据是什么?
TreeSet里存储的元素,必须要实现Comparable接口
TreeSet里的元素在比较时,会将元素转换成为Comparable类型的对象,然后调用元素的compareTo方法,看结果是否为0,如果是0,认定为同一个元素,不存入。如果大于0,往后面排队,小于0往前面排。
存入的元素必须实现Comparable接口,可以考虑自然排序(重写compareTo)或者定制排序(实现comparable接口)
自然排序中,比较两个对象是否相同的标准是compareTo返回0,不再是equals()
TreeSet和后面的TreeMap都采用红黑树的存储结构, 特征是有序,查询速度比list快
- 作者:tacjin
- 链接:http://jin.wiki/article/0ca48005-0111-4245-aa6a-9e7cec007d44
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。