一、集合框架
1.1 集合概述
集合:广义上的集合简单理解就是容器。需要注意的是,集合只能存放对象类型的数据。
所以如果你要存放基本数据类型的数据是不能直接存储的,需要转化成包装类,包装类详见常见类库(下)。
1.2 集合框架
集合框架是Java中非常重要的技术体系,在日常开发中无处不在。
集合框架指的是java.util包中定义的各种容器类、相关的工具类、接口的统称。不同的容器存储不同结构的数据。使用时要进行导包操作。
具体集合的框架的体系图如下:
其中,Iterator是根部的父接口,总结来说,接口一共分为两大类(三小类),分别是Collection和Map(List,Set和Map)。
具体特性如上图所示(以下无序是指在逻辑上的有无序),
List是有序可重复的集合,实现类有ArrayList,LinkedList;
Set是无序不可重复的集合,实现类有HashSet,TreeSet,LinkedHashSet;
Map里面存放的是无序的具有映射关系的键值对,实现类有HashMap,LinkedHashMap,TreeMap。
二、Collection接口
2.1 Collection接口概述
具体Collection接口的类结构见1.2中的图所示,实际上,其中TreeSet上面是有一个父类SortedSet的,后面的某一个点会有要提到的地方。
在常见类库(上)中我们我们有提到,了解一个类可以从它的定位,也就是作用,和它的构造器,以及它的成员来学习,同样,接触到了一个新的接口,我们先看一下它的结构。
Collection接口继承了Iterator,在Iterator接口中,规定了实现类必须要提供迭代器对象,在Java中使用迭代器去遍历集合中的元素,这是一种新的遍历方法,在学习此之前都是循环遍历。
Collection作为Iterable接口的子接口,意味着Collection接口的实现类一定会实现Iterable接口中规定的方法;List、Set接口的实现类也一定会实现Iterable接口中规定的方法。
2.2 Collection接口的方法
因为它是一个接口,所以其中定义的大多都是抽象方法,是没有方法体的。
详细的方法如下:
//获取集合中元素的个数。 int size() //向集合中添加元素,添加后如果容器内容发生了变化,返回true,否则返回false。 //有些容器不允许重复元素。 boolean add(E e) //将指定集合中的元素添加到当前集合中,添加后如果容器内容发生了变化,返回true,否则返回false。 booleanaddAll(Collection <? extends E > c) //从集合中删除指定的元素,删除后如果容器内容发生了变化,返回true,否则返回false。 boolean remove(Obiect 0) //删除指定集合中包含的全部元素,删除后如果容器内容发生了变化,返回true,否则返回false。 boolean removeAl(Collection<?>c) //删除集合中全部元素。 void clear() //判断集合中是否包含指定的元素。 boolean contains(Object0) //判断集合中是否包含指定集合中全部元素。 boolean containsAll(Collection<?> c) //获取集合对应的迭代器。(迭代器可以遍历集合) Iterator<E>iterator() //判断是不是空集合 boolean isEmpty() //将集合转换成对象数组。 Object[] toArray()因为List接口和Set接口继承了该接口,所以这些方法在这些接口及其实现类中也是可以调用的,如果没有涉及到改动,在下面的讲述中就只总结新遇到的方法。
三、List接口
3.1 List接口概述
List 是一个“有顺序、可重复”的集合,像排队一样,每个元素都有固定位置(索引),所以这个有序指的是逻辑是否有序。
因为存在下标,所以List可以利用下表去做一些不同的事,也就是它定义了自己的一些关于下标的方法。
3.2 List接口的方法
//在集合指定的位置添加元素。 void add(int index,E e) //在集合中指定的位置添加一组元素 void addAll(int index, Collection<?extends E> c) //获取集合中指定位置的元素。 E get(int index) //获取对象第一次出现在集合中的下标。如果没有这个对象返回-1。 int indexOf(Object o) //获取对象最后一次出现在集合中的下标。如果没有返回-1。 int lastindexOf(Object o) //删除指定下标的元素,返回值是被删除的元素。 E remove(int index) //将指定位置的元素修改成e,返回值是此位置原来的元素。 E set(int index, E e) //获取子列表。不含toIndex对应的元素 List< E> subList(int fromIndex, int tolndex)3.3 List接口的实现类
因为讲的是集合,集合是要存放数据的,关于数据就要涉及到增删改查,这方面是重点,所以下面我们重点介绍一下相关的增删改查的一些方法。
3.3.1 ArraysList实现类
ArraysList实现类的底层逻辑是数组,有关数组的逻辑在这里就不再多讲了吧,如果对数组的内存有疑问也可以去查看我之前的文章。
同时,这个数组存放又和我们之前的数组有些不同,ArrayList是长度可变的数组,长度不足时会自动扩充容量。
由于底层是靠数组完成的数据存取,所以查询效率高(根据index查询),增删效率低。
具体逻辑:因为数组有下标,根据索引可以很快地找到对应的数据,但是又因为数组结构在存放上是物理有序的,所以当我们需要增删时,我们需要随之对后续的数据都要进行前移或后挪,所以查询效率高(根据index查询),增删效率低。
3.3.1.1 构造器
//创建一个初始容量为10的空列表。 ArrayList() //创建一个列表,包含参数中全部的元素,顺序与参数中元素的顺序一致。 ArrayList(Collection<? extends E> c) //创建一个指定容量的空列表。 ArrayList(int capacity)3.3.1.2 增删改查
1)增
//向ArrayList中添加一个元素,元素会添加到ArrayList的末尾。如果添加成功,返回true boolean add(E e) //在指定位置插入元素。 void add(int index, E e) //将指定集合中的数据按顺序添加到ArrayList的末尾。 boolean addAll(Collection<? extends E> c) //将指定集合中的数据插入到ArrayList的指定的位置。 boolean addAll(int index, Collection<? extends E> c)2)删
//从列表中删除指定的元素,如果有多个,只会删除第一个。如果成功删除返回true,如果没有删除返回false。 boolean remove(Object o) //删除指定位置的元素。返回值是被删除的元素。 E remove(int index) //从列表中删除指定集合中包含的元素。如果列表删除了数据返回true,否则返回false。 boolean removeAll(Collection<? extends E> c) //清空列表中的元素 void clear()3)改
E set(int index, E e) //将指定下标的元素修改为e4)查
- 是否包含此元素
//判断列表中是否包含指定的元素,如果包含返回true,否则返回false。 boolean contains(Object o)- 获取指定下标的元素
E get(int index) //获取指定下标处的元素。- 根据元素获取下表
//返回列表中指定元素第一次出现的下标。如果没有指定元素返回-1 int indexOf(Object o) //返回列表中指定元素最后一次出现的下标。如果没有指定元素返回-1 int lastIndexOf(Object o)- 获取列表中元素的个数
int size() //获取列表中元素的个数3.3.1.3 ArraysList的遍历
作为数组的存储方式,ArraysListu也可以使用普通for循环进行遍历。
- 通过普通for循环遍历
for(int i = 0; i < arrayList.size(); i++){ arrayList.get(i); }- 加强for循环
for(数据类型 对象 : arrayList){ 对象;//对象就是遍历出来的数据 }- 迭代器循环
//调用iterator方法返回的值赋给新建的Iterator对象,使用该对象调用对应的方法进行迭代器循环 Iterator<数据类型> it = arrayList.iterator(); while(it.hasNext()){ 数据类型 对象 = it.next(); }3.3.2 LinkedList实现类
LinkedList底层是靠双向链表来存放元素的。链表中的元素在逻辑上连续,但物理上不连续。因此,LinkedList增、删效率高,增删元素无需做任何移动,直接改变链表的指向即可。但根据下标查找元素效率低。
3.3.2.1 构造器
//创建一个空列表。 LinkedList() //创建一个列表,包含参数中全部的元素,顺序与参数中元素的顺序一致。 LinkedList(Collection<? extends E> c)3.3.2.2 增删改查
因为都是有下标的集合,所以在增删改查方面和ArraysList基本相同,由于内容较多,在此就不再多赘述,详细查看3.3.1.2 。
3.3.2.3 LinkedList的遍历
- 通过普通for循环遍历
for(int i = 0; i < LinkedList.size(); i++){ LinkedList.get(i); }- 加强for循环
for(数据类型 对象 : LinkedList){ 对象;//对象就是遍历出来的数据 }- 迭代器循环
//调用iterator方法返回的值赋给新建的Iterator对象,使用该对象调用对应的方法进行迭代器循环 Iterator<数据类型> it = LinkedList.iterator(); while(it.hasNext()){ 数据类型 对象 = it.next(); }四、Set接口
4.1 Set接口概述
Set接口规定了无序(无下标),元素不可重复的容器应该具有什么功能。既然是不含重复元素的集合,那它就具有排除重复元素的能力。
所以我们在进行Set接口的总结时也要加上它相关的去重原理。
4.2 Set接口的方法
Set接口中并没有声明多少独特的方法,而是声明了和父接口相同的方法。
所以具体的方法和Collection接口,也就是父接口中的相同,再次也不过多赘述,详情请见2.2内容
4.3 Set接口的实现类
4.3.1 HashSet实现类
HashSet实现类中存储的数据是不重复的,元素的顺序也是无序的。底层数据结构是哈希表。
在学习这一块时我问过AI,它给了我一个比较形象的例子,说它就像是你要往一面墙上贴照片,墙上的每个位置都有一个自己的编码(哈希码),当来了一个照片(数据),就会根据它的内容找到(生成)一个位置(哈希码),然后贴上去。如果墙上的那个位置已经存在照片,就说明已经贴过了,这就是一种查重。
4.3.1.1 HashSet的构造器
//创建一个空集合,集合的初始容量是16,加载因子是0.75 HashSet() //创建一个包含指定元素的集合,会去除重。加载因子是0.75 HashSet(Collection<? extends E> c) //创建一个指定初始容量的空集合。加载因子是0.75 HashSet(int capacity) //创建一个空集合,指定初始容量和加载因子。 HashSet(int capacity, float loadFactor)4.3.1.2 HashSet的方法
1)增
//向集合中添加一个元素,如果添加成功,返回true boolean add(E e) //将指定集合中的数据添加到HashSet中,会去重。 boolean addAll(Collection<? extends E> c)2)删
//从集合中删除指定的元素,如果成功删除返回true,如果没有删除返回false(例如:没有指定的元素)。 boolean remove(Object o) //从HashSet中删除指定集合中包含的元素。如果删除了数据返回true,否则返回false。 boolean removeAll(Collection<? extends E> c) //清空集合中的元素 void clear()3)改
因为HashSet没有下标,且是无序的,所以无法进行修改数据,所谓的修改,是通过for循环或者迭代器进行的一种删除数据再添加的操作。
4)查
//判断集合中是否包含某元素 boolean contains(Object o) //获取集合中元素的个数。 int size()4.3.1.3 HashSet的去重机制
我们可以先将List集合转化成Set集合,再转换回来,就可以达到去重的目的。
1)先判断对象的hashCode是否一样,如果hashCode不同,认为是不同的元素。如果hashCode相同,进入下面的判断。
2.)判断equals方法返回值是true还是false,如果是false认为是不同的元素,如果是true认为是元素重复。不再添加进集合。
3)hashCode可以通过重写根类Object的hashCode()方法来返回一个hashCode值。
但是当我们new两个对象,但是他们的内容完全相同,对于代码而言,他们就是不同的,因为内存地址不同,但是从逻辑上来看,他们就是完全重复的内容。这时候,我们可以通过重写对应类里的hashCode和equals方法,来达到根据我们的条件去重的目的。
4.3.1.4 HashSet的遍历
因为没有下表,所以无法使用普通for循环来进行遍历
- 加强for循环
for(数据类型 对象 : HashSet){ 对象;//对象就是遍历出来的数据 }- 迭代器循环
//调用iterator方法返回的值赋给新建的Iterator对象,使用该对象调用对应的方法进行迭代器循环 Iterator<数据类型> it = HashSet.iterator(); while(it.hasNext()){ 数据类型 对象 = it.next(); }4.3.2 LinkedHashSet实现类
LinkedHashSet是HashSet的子类,它具有和HashSet同样的功能,元素也是不能重复,唯一不同的是,LinkedHashSet会维持元素加入的顺序。LinkedHashSet和LinkedList一样,底层数据结构是双向链表+哈希表。
4.3.2.1 LinkedHashSet的构造器
//创建一个空集合,集合的初始容量是16,加载因子是0.75 LinkedHashSet() //创建一个包含指定元素的集合,会去除重。加载因子是0.75 LinkedHashSet(Collection<? extends E> c) //创建一个指定初始容量的空集合。加载因子是0.75 LinkedHashSet(int capacity) //创建一个空集合,指定初始容量和加载因子。 LinkedHashSet(int capacity, int loadFactor)4.3.2.2 LinkedHashSet的方法
因为LinkedHashSet是HashSet的子类,所以详情见4.3.1.2,
4.3.2.3 LinkedHashSet的遍历
见HashSet的遍历4.3.1.4
4.3.3 TreeSet实现类
TreeSet使用的是二叉树存储元素, 通过中序遍历的方式来遍历(检索)元素
二叉树的遍历有3种方式,先序遍历,中序遍历和后序遍历。无论是否哪种遍历方式.都是从树根开始查看的 。
先序:先父节点,再左,最后右ABDECFG
中序:先左,再父,最后右DBEAFCG
后序:先左,再右,最后父DEBFGCA
TreeSet实现类会有一个默认的排序,第一个进入的值在根位置,之后进来的元素都会和第一个元素进行比较,比根小就在左边,比根大就在右边。
4.3.3.1 TreeSet的构造器
//创建一个空集合,根据内部元素的自然顺序排序。 TreeSet() //创建一个包含指定元素的集合,根据内部元素的自然顺序排序。 TreeSet(Collection<? extends E> c) //创建一个空集合。根据指定的比较器进行排序。 TreeSet(Comparator<? super E> comparator) //创建一个与参数一样的集合,按参数的顺序组织元素顺序。 TreeSet(SortedSet<E> s)4.3.3.2 TreeSet的方法
基本和HashSet的方法相同,在这里我举几个独有的方法吧
//获取集合倒序的迭代器对象 Iterator< E > descendingIterator() //获取集合第一个元素 E first() //获取结合最后一个元素 E last() //获取从from到to的元素。 SortedSet< E > subSet(E from, E to)4.3.3.3 TreeSet的去重方法
去重和内容有序的原理: 第一个加入set的元素, 会成为树的根, 从第二个元素开始, 要和set中已有的元素比较大小, 如果要添加的元素 大于了已有元素,放在元素右侧, 如果小于了已有元素, 放左侧, 如果要添加的元素和已有元素相等, 会认为重复, 不进行添加 。
4.3.3.4 TreeSet的遍历
因为也是没有下标,所以在遍历方面也基本和HashSet相同,唯独多了一个倒序迭代器,如下。
Iterator<String> it = set.descendingIterator(); while(it.hasNext()) { String str = it.next(); System.out.println(str); }