相同点:
区别:
数组的缺点: 不灵活,容量需要事先定义好,不能随着需求的变化而扩容。
但是我们的开发又不可能离开数组,所以最初就只能依靠一些数据结构来实现动态的数组处理,其中最为重要的两个数据结构:链表、数组,但是面对这些数据结构的实现又不得不面对如下的问题?
数据结构的代码实现困难,对于一般的开发者是无 法进行使用的。
对于链表或二叉树当进行更新处理的时候维护是非常麻烦的。
对于链表或二叉树还需要尽可能保证其操作的性能。
最初的集合实现由于Java本身的技术所限,所以对数据的控制并不严格,全部采用了Object类型进行数据的接收;在JDK1.5之后由于泛型技术的推广,集合框架也得到了良好的改进,可以直接利用泛型来保存相同类型的数据;随着数据量的不断增加,从JDK1.8开始集合框架中的实现算法也得到了良好的性能提升。
java.util.Collection 是单列集合操作的最大的父接口,在该接口中定义了单列集合的所有操作。
Collection接口实现类的特点:
方法名称 | 功能 |
---|---|
boolean add(E e) | 添加单个元素 |
boolean addAll(Collection coll) | 把coll集合中的所有元素复制一份,并添加到当前集合中 |
void clear() | 清空集合中的所有元素 |
boolean contains(Object o) | 判断当前集合中是否包含指定的数据 (需要equals方法支持) |
boolean isEmpty() | 判断当前合是否为空 |
boolean remove(Object o) | 删除(有相同元素,只能删除一个)(需要equals方法支持) |
int size() | 获取集合中元素的个数 |
Object[] toArray() | 将集合变成Object数组返回 |
Iterator | 返回此集合中元素的迭代器 |
在进行集合操作的时候有两个方法最为常用:【添加数据】add()、【输出数据】iterator(),在JDK1.5版本之前Collection只是一个独立的接口,但是从JDK1.5之后提供了Iterable父接口,并且在JDK1.8之后 Iterable 接口也得到一些扩充。
但是往往我们玩的都是Collection的两个子接口: List(有序有索引可重复)、Set(无序不可以重复)接口。
使用场景的总结:
1、如果希望元素可以重复,又有索引,索引查询要快?
2、如果希望元素可以重复,又有索引,增删首尾操作快?
3、如果希望增删改查都快,但是元素不重复、无序、无索引。
4、如果希望增删改查都快,但是元素不重复、有序、无索引。
5、如果要对对象进行排序。
我们知道Collection继承了Iterable接口,而在Iterable接口中定义了一个iterator()抽象方法,用于返回一个Iterator对象即迭代器对象,来遍历集合中所有元素。
而在Collection接口中重写了Iterable接口的iterator()方法,所以Collection的 子接口/实现类 都会有这个iterator()方法。
Iterator接口中的方法:
方法 | 功能 |
---|---|
boolean hasNext() | 如果仍有元素可以迭代,则返回 true 。 |
E next() | 返回迭代的下一个元素。 |
default void remove() | 使用迭代器删除集合中的元素,它会自动更新迭代器,并且更新集合。 |
迭代器的执行原理:
迭代器源代码分析:
注意:
在调用next()方法之前必须先调用hasNext()方法来检测下一个元素是否存在。若不调用且下一条记录无效时(也就是已经遍历完所有元素),再调用next()会抛出NoSuchElementException
异常。
当while循环结束后,iterator迭代器会指向最后一个元素,如果希望再次遍历集合,需要重新获取新的迭代器对象。
如果在迭代器中添加删除指定元素,则会报ConcurrentModificationException
并发修改异常。
如果要在迭代器中删除指定元素,需要调用iterator的remove()方法,他会自动更新迭代器,并且更新集合。
增强for循环,JDK1.5新特性
增强for循环可以代替iterator迭代器,它只能用于遍历数组或集合。
语法格式:
for(元素类型 变量名 :集合或数组名){sout(变量名)
}
如果遍历数组:foreach底层源码是普通for循环。
如果遍历集合:foreach底层源码是iterator迭代器(简化版的迭代器),只能对集合进行遍历操作。
扩展:使用指定编码编译 javac -encoding utf-8 xx.java
数据接口有:数组、队列、栈、链表、树、散列、堆、图。
特点:先进后出、后进先出、入栈(压栈)、出栈(弹栈)。
特点: 先进先出、后进后出(例如排队做核酸)
特点:查询快(有索引、元素内存连续分配)、增删慢(不断扩容)。
特点:查询慢(需要遍历),增删快(不需要创建新的链表,只需修改链表中节点保存的地址)。
单项链表:
双向链表:
链表查询慢的原因:
树
二叉树
二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。(左边小、右边大)
查找二叉树
二叉树的一个重要应用是在它们查找中的使用,假设树中的每个结点存储一项数据,使得二叉树成为二叉查找树的性质是:对于树中的每个结点X,它的左子树中所有项的值小于X,而它的右子树中所有项的值大于X,这意味着该树所有的元素可以用某种一致的方式排序。
平衡二叉树
在生成二叉树/二叉查找树的时候是非常容易失衡的,造成的最坏的情况就是一边倒(只有左子树/右子树),这样将会导致树的检索效率大大降低,所以为了维持二叉树的平衡,大牛们提出了各种实现的算法,比如:AVL树–每个结点的左子树和右子树深度最多差1。
红黑树
红黑树顾名思义就是结点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。
对于一颗有效的红黑树而言我们必须增加如下规则:
这些约束强制了红黑树的关键性质:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果就是这棵树大致上是平衡的,因为插入、删除和查找某个值得最坏情况时间都要求与树的高度成比例,这个高度理论上限允许红黑树只在最坏情况下都是高效的。
集合它可以存放任意对象,当把对象存储到集合后,他们都会被提升成Object类型。然后我们再取出每一个对象并且进行相应操作时,必须采用强制类型转换。
public class Demo01Generic {public static void main(String[] args) {// 创建一个ArrayList集合对象,指定存储数据的类型为StringArrayList list = new ArrayList<>();list.add("aa");list.add("bbb");list.add("cccc");// 将运行时异常,提前到了编译时期,降低了程序员的工作量//list.add(1000); //只能存String类型数据// 使用增强for进行遍历for (String str : list) {System.out.println(str + "的长度: " + str.length());}System.out.println("===================");// 创建集合,不指定存储数据的类型// 默认按照Object类型处理ArrayList list2 = new ArrayList();list2.add("aa");list2.add("bbb");list2.add("cccc");// 可以存// 但是取出来进行强制类型转换,报出类型转换异常list2.add(2022);// 使用增强for进行遍历for (Object obj : list2) {// 因为创建ArrayList集合对象时,并没有指定存储数据的类型(泛型),// 所以内部存储的所有内容均被当做Object类型处理// 必须做强制类型转换(向下转型),存在安全隐患:类型转换异常 ClassCastExceptionString str = (String) obj;System.out.println(obj + "的长度: " + str.length());}}
}
为什么会报错ClassCastException?因为上述的ArrayList集合只能存储同一类型对象(例如list2存储的都是字符串对象),当取出String类型数据时需要强转,即Object强转成String,又因为元素2022它强转后是Integer而不是String类型,所以把Integer类型的数据赋值给String类型后就会报ClassCastException类型转换异常。
如何解决:使用泛型来约束集合存储指定类型数据。(泛型,可以在类或方法中预支地使用未知的类型。)
(1)那使用泛型有哪些好处?
使用泛型的好处
定义和使用
泛型通配符