目录
一、类集设置的目的
对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。
在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。
类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
所有的类集操作的接口或类都在 java.util 包中。
二、链表与二叉树思路
2.1 数组存在的缺点
1,动态扩容
步骤:声明更大的数组-》将数据拷贝进去-》引用类型的变量指向新的地址;
缺点:浪费空间且耗时;
2,删除数据
删除一个数据后,需要将其后的数据全部前移一位;
2.2 链表
链接:链表
2.3 二叉树
链接:二叉树
三、常见数据结构
链接:常见数据结构
四、Collection 接口
4.1 集合概述
Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。
此接口定义如下:
public interface Collection<E> extends Iterable<E>
此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。
本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是 java.util.List 和 java.util.Set 。
List 的特点是元素有序、元素可重复。
Set 的特点是元素无序,而且不可重复。
List 接口的主要实现类有 java.util.ArrayList 和 java.util.LinkedList
Set 接口的主要实现类有 java.util.HashSet 和 java.util.TreeSet 。
4.2 Collection 常用功能
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,
这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e) : 把给定的对象添加到当前集合中 。
public void clear() : 清空集合中所有的元素。
public boolean remove(E e) : 把给定的对象在当前集合中删除。
public boolean contains(E e) : 判断当前集合中是否包含给定的对象。
public boolean isEmpty() : 判断当前集合是否为空。
public int size() : 返回集合中元素的个数。
public Object[] toArray() : 把集合中的元素,存储到数组中。
五、List 接口
5.1 List接口介绍
public interface List<E> extends Collection<E>
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。
所以,证明,List 接口拥有比 Collection 接口更多的操作方法。
了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个: · ArrayList(95%)、Vector(4%)、LinkedList(1%)
java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了List 接口的对象称为List集合。
在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。
另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
List 子接口的定义:
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就
是按照11、22、33的顺序完成的)。 - 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道
理)。 - 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
5.2 List接口中常用方法
public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。
public E get(int index) :返回集合中指定位置的元素。
public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新
前的元素。
六、ArrayList
6.1 概述
java.util.ArrayList 集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。
许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
ArrayList 是 List 接口的子类,此类的定义如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。
public static void main(String[] args) {
List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型
all.add("hello "); // 增加内容,此方法从Collection接口继承而来
System.out.println(all.get(0));//打印arraylist的内容
all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的
System.out.println(all.get(0)+"---------"+all.get(1));//打印arraylist的内容
all.add("world"); // 增加内容,此方法从Collection接口继承而来
System.out.println(all.get(0)+"---------"+all.get(1)+"---------"+all.get(2));//打印arraylist的内容
all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、");
// 此方法是List接口单独定义的
}
}
输出的内容是:
hello
LAMP ---------hello
LAMP ---------hello ---------world
集合中的内容是:LAMP 、
6.2 构造方法
6.3 源码解析
虽然API说明ArrayList无参构造器初始化长度是10,其实不然,请看以下讲解
1、创建一个ArrayList无参集合
(目前状态下,arrayList的对象其实初始容量为0)
2、进入源码
存数据的数组
this.elementData
默认的值
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
可以看出,通过无参构造方法创建的是一个长度为0 的数组,并不是长度为10的空列表;(并不是API错误)
3、添加元素
但是我们添加元素的时候,长度会变为10,我们使用add()方法
进入add()源码
参数:
e:元素
elementData:元素数组
size:原数组长度
不管添加成功都是返回true
再进入add()方法
七、Vector
与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:
public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。
从Java 2平台v1.2开始,该类被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的。 如果不需要线程安全实现,建议使用ArrayList代替Vector 。
7.1 构造方法
当增量为0(增量:每次扩容,增加的容量)时,扩容是每次翻一番;
7.2 范例
以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。
但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此 类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。
但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。
7.3 Vector类与ArrayList类的区别
八、LinkedList
8.1 说明
使用双向链表实现
此类的使用几率是非常低的,但是此类的定义如下:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:
8.2 范例
8.3 常用方法
九、Iterator与ListIterator
9.1 问题引入
对于ArrayList来说,普通的遍历方法(如下图)效率差别不大,但对于LinkedList来说,并不是最优的。
因此可以使用迭代器实现
9.2 概述
1,简述
- 在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator 。
- Iterator 接口也是Java集合中的一员,但它与 Collection 、 Map 接口有所不同, Collection 接口与 Map 接口主要用于存储元素,而 Iterator 主要用于迭代访问(即遍历)Collection 中的元素,因此 Iterator 对象也被称为迭代器 。
- Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
- 此接口定义如下:public interface Iterator
- 要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。 此接口规定了以下的三个方法:
通过 Collection 接口为其进行实例化之后,一定要记住,Iterator 中的操作指针是在第一条元素之上,当调用 next()方 法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。
2,Iterator与ListIterator
- Iterator:迭代Collection下所有集合,包括List和Set;
- ListIterator:只用于遍历List
十、forEach
10.1 概述
增强for循环,最早出现在C#中;
用于迭代数组 或 集合(Collection下的);
10.2 实际应用
在使用 foreach 输出的时候一定要注意的是,里面的操作泛型要指定具体的类型,这样在输出的时候才会更加有针对性;
十一、Set
Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法通过下标获取数据进行输出。
在此接口中有两个常用的子类:HashSet、TreeSet
十二、HashSet
12.1 概述
java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。
java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持。
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode 与 equals 方法。
有插入和删除的方法,但是没有get方法,所以为了获取元素需要通过迭代器 或 转换成数组 来进行;
TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的;
12.2 注意
1, HashSet 底层的实现其实是一个HashMap 支持
2,插入操作有返回值
12.3 存储数据结构(哈希表)
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示
JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
十三、TreeSet与Comparable
13.1 概述
与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable
此类的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。
补充:
-
快速失败:遍历的是集合本身,如果遍历过程中,通过其他操作使得集合本身发生改变,就会抛出异常;
-
安全失败:失败不会出错,在遍历的时候先将集合复制一份,即使原集合发生改变,也不会抛出异常;
13.2 举例
十四、Map
14.1 概述
Map与Collection同一级别,而不是与List和Set同一级别;
Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了;
取数据操作不同:ArrayList可以通过下标,Set可以通过迭代器,Map需要先取出键形成Set集合,再根据具体的键取出值
Map中的键(key)不可重复
HashSet使用的HashMap,TreeSet使用的TreeMap,LinkedHashSet使用的LinkedHashMap。即Set集合使用Map的键来存储数据
十五、哈希表概述
Object中的hashCode可以提高哈希表的性能;
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
哈希桶(数组table中每个方格)中的数据量大于8时,从链表转换为红黑二叉树.当哈希桶中的数据量减少到6时,从红黑二叉树转换为链表;
初始桶的数量16,散列因子0.75。当使用桶的数量所占比例超过0.75后,需要扩容为原先的两倍;
十六、HashMap源码分析
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
1、构造方法
作为一般规则,默认加载因子(.75)在时间和空间成本之间提供了良好的折衷。 较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put )。 在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以便最小化重新散列操作的数量。 如果初始容量大于最大条目数除以加载因子,则不会发生重新加载操作。
2、源码
HashMap的构造方法
加载因子默认为0.75
初始容量
put方法
十七、Map集合使用案例
17.1 HashMap
线程不安全(效率高)
17.2 HashTable
线程安全(效率低)
17.3 CurrentHashMap
采用分段机制,线程安全,效率又比较高
17.4 TreeMap
TreeSet借助TreeMap实现。TreeMap保证存储顺序
十八、存储自定义对象
十九、JDK9集合新特性
List,Set,Map三个接口存在of方法,可以存入任意数量的元素,但大小不能改变。