一、数据结构
1.概述
常见的和集合相关的数据结构:栈、队列、数组、链表和红黑树。
2.栈(Stack)
栈:stack,又称为堆栈,它是运算受限的线性表,其限制是仅允许在【表的一端】进行插入和删除操作,不允许再其他任何位置进行添加、查找、删除等操作。
【先进后出】
入口和出口在集合的【同一侧】
存储元素到集合:入/压栈(从栈顶到栈底)
取出集合中元素:出/弹栈(从栈底到栈顶)
例如:
入栈:123
出栈:321
3.队列
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在【表的一端】进行插入,而在【表的另一端】进行删除。
【先进先出】
入口和出口在集合的【两侧】
例如:
存储:123
取出:123
4.数组
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。
【查询快】:数组的【地址是连续】的,我们通过数组的首地址可以找到数组,通过数组的索引可以快速查找某一个元素
【增删慢】:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新的数组,把原数组的数据复制过来
例如:
int[] arr = new int[]{1, 2, 3, 4};
new出来的对象,会在堆内存中分配一个内存地址,然后将该地址赋值给arr,我们就可以通过arr找到这个数组的首地址,然后通过索引【快速找到】对应的数值
5.链表
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
链表中的每一个元素也称为一个结点,一个结点包含了一个数据源(存储数组),两个指针域(存储地址)。
所以一个结点为:自己的地址 + 数据 + 下一个结点的地址
简单的说,采用该结构的集合,对元素的存取有如下特点:
【查询慢】:链表中【地址不是连续】的(不同于数组中地址是连续的),每次查询元素,都必须【从头开始】查询
【增删快】:链表结构,增加/删除一个元素,对链表的整体结构没有影响,所以增删快
单向链表和双向链:
【单向链表】:链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序可能不一致)
0x11 + 100 + 0x72 --> 0x72 + 200 + 0x99 --> 0x99 + 数据 + 0x11
上个结点的地址可以记住下一个结点的地址,但是下一个结点的地址记不住上一个结点的地址,所以是【无序】的。
【双向链表】:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合
0x11 + 100 + 0x72 <--> 0x72 + 200 + 0x99 <--> 0x99 + 数据 + 0x11
上个结点的地址可以记住下一个结点的地址,下一个结点的地址也可以记住上一个结点的地址,所以是【有序】的。
【如果想要删除元素200,则只需要把第一个结点中的“下一个结点地址”由0x72改为0x99即可。所以增删快】
例如:
数组就像是一排出租屋,每个房间都挨着,所以地址是连续的。
链表也是很多屋子,但是不是挨着的,所以地址不是连续的。
6.红黑树
1.二叉树:binary tree,是每个【结点不超过2】的有序树(tree)。
简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子节点。
二叉树是每个结点最多有两个子树的树结构,顶上的叫【根节点】,两边被称作“左子树”和“右子树”。
2.排序树/查找树
在二叉树的基础上,元素是有大小顺序的。
左子树小,右子树大。
特点:
查询速度非常的快
例如:
猜数字小游戏,1-100之间的数字,从50开始猜,一次减一半。能比较快速的猜出数字
对于排序树来说,首先找到根节点中的值,如果值偏大,就找左子树,否则就在右子树中寻找,所以【查询比较快】。
3.平衡树:左子树和右子树相等
4.不平衡树:左子树和右子树数量不一样。
5.红黑树:
特点:
趋近于【平衡树】,【查询的速度非常的快】,【查询叶子节点最大次数和最小次数不能超过2倍】。
约束:(了解)
1.节点可以是红色的或者是黑色的
2.根节点是黑色的
3.叶子节点(空节点)是黑色的
4.每个红色的节点的子节点都是黑色的
5.任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
二、List集合
1.概述
Collection是所有集合的最顶层接口,包含两个主要的子接口:java.util.List和java.util.Set
List接口的实现类有:ArrayList、LinkedList、Vector
Set接口的实现类有:HashSet、LinkedHashSet、TreeSet
2.java.util.List子接口
public interface List<E> extends Collection<E>
【有序】(存储和取出元素的顺序是一致的)的collection(也称为序列),与set不同,列表通常允许重复的元素。
3.List接口的特点
1.有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出也是123)
2.有索引,包含了一些带索引的方法
3.允许存储重复的元素
4.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):用指定的元素替换集合中指定位置的元素,返回值的更新前的元素
5.注意事项
操作索引的时候,一定要防止索引越界异常
IndexOutOfBoundsException:索引越界异常,该异常一般集合会报。
ArrayIndexOutOfBoundsException:数组索引越界异常
StringIndexOutOfBoundsException:字符串索引越界异常
6.List集合遍历方式
1.使用普通的for循环
2.使用增强for循环
3.使用迭代器
7.ArrayList集合
1.ArrayList集合是List子接口的实现类;List接口的大小可变数组的实现。
2.数据存储的结构是【数组结构】,元素【增删慢】,【查询快】,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
即ArrayList集合的底层是数组结构,数组存储的元素【地址是连续】的,所以查询快,增删慢。
3.注意:
1.此实现不是同步的。不是同步的,说明是多线程的,多线程就意味着效率高。
2.当查询需求比较多时,用ArrayList集合,因为查询快;但是当增删需求比较多时,用ArrayList集合就比较慢了,所以应该根据不同需求使用不同的集合。
8.LinkedList集合
java.util.LinkedList集合是List子接口的另一个实现类。
数据存储的结构是【链表】结构。【查询慢】,【增删快】。
LinkedList是一个双向链表。找到头(head)和尾(tail)非常方便,;里边有大量的【操作首尾元素】的方法。
注意,此实现不是同步的,所以这个集合也是一个多线程,多线程就意味着速度快。
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法【了解即可】
总结,LinkedList集合的特点:
1.底层是一个【链表结构】:查询慢、增删快
2.里边包含了大量操作首尾元素的方法
注意:使用LinkedList集合特有的方法,【不能使用多态】,因为使用多态以后,就不能使用子类特有的方法了,【除非向下转型】
public void addFirst(E e):将指定元素插入此列表的开头
public void addLast(E e):将指定元素添加到此列表的结尾 【等效于add()】
public void push(E e):将元素推入此列表所表示的堆栈 【等效于addFirst()】
public E getFirst():返回此列表的第一个元素
public E getLast():返回此列表的最后一个元素
public E removeFirst():移除并返回此列表的第一个元素
public E removeLast():移除并返回此列表的最后一个元素
pubic E pop():从此列表所表示的堆栈处弹出一个元素 【等效于removeFirst】
public boolean isEmpty():如果列表不包含元素,则返回true
9.Vector集合(了解)
Vector集合是List子接口的另一个实现类。
Vector集合是一个单列集合。
Vector类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector的大小可以根据需要增大或缩小,以适应创建Vector后进行添加或移除项的操作。
与新collection实现不同,Vector是【同步】的。同步就意味是单线程,速度就慢。
三、Set集合
1.Set子接口
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口。
与List接口不同的是,Set接口中【元素无序】,并且都会以某种规则保证存入的元素不出现重复。
Set接口的主要实现类:
java.util.HashSet
java.util.LinkedHashSet
2.遍历Set集合的方式
Set集合取出元素的方式可以采用:迭代器、增强for
Set集合里的元素没有索引值,所以不能使用普通for
3.Set接口的特点
1.不允许存储重复的元素
2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
4.HashSet
public class HashSet<E> extends AbstractSet<E> implements Set<E>
HashSet集合是Set子接口的一个实现类。
由【哈希表】(实际上是一个HashMap实例)支持。它不保证set的迭代顺序,特别是他不保证该顺序恒久不变。
【注意,此实现不是同步的,也就是单线程的】。
HashSet集合的特点:
1.不允许存储重复的元素
2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
3.是一个【无序】的集合,存储元素和取出元素的顺序有可能不一致
4.底层是一个【哈希表结构】(【查询的速度非常快】)
5.哈希表
哈希表 = 数组 + 链表/红黑树
特点:查询速度快
数组结构:把元素进行了分组(相同哈希值的元素是一组)
链表结构/红黑树结构:把相同哈希值的元素连接到一起
存储数据到集合中:
先计算元素的哈希值,哈希值就是元素在数组中的存储位置,
两个元素不同,但是哈希值相同,这称为【哈希冲突】。
在数组中存储哈希值,然后不同的元素挂在不同的哈希值下边,
如果链表的长度超过了8位,那么就会把链表转换为红黑树(为了提高查询的速度)
6.为什么Set集合不允许存储重复的元素?
不能存储重复元素有一个前提:
存储的元素【必须重写】hashCode方法和equals方法。
哈希表 = 数组(初始容量为16) + 链表/红黑树
数组中存储的是元素的哈希值,然后通过链表/红黑树把元素挂在对应哈希值下边。
注意事项:
Set集合在调用add方法的时候,add方法会调用元素的hashCode方法和equals方法,判断元素是否重复。
hashCode()方法用于判断是不是会有哈希冲突
equals()方法用于判断,当发生哈希冲突时,是不是数据也是一样的
什么时候才会重写hashCode方法和equals方法?
用到Set集合的时候,需要重写,因为要保证元素不重复
实例分析:
// 创建HashSet集合对象
HashSet<String> set = new HashSet<>();
String s1 = new String("abc");
String s2 = new String("abc");
set.add(s1);
/*
add方法会调用s1的hashCode方法,计算字符串"abc"的哈希值,哈希值是【96354】
在集合中找有没有【96354】这个哈希值的元素,发现【没有】
所以就会【把s1存储到集合中】
*/
set.add(s2);
/*
add方法会调用s2的hashCode方法,计算字符串"abc"的哈希值,哈希值是【96354】
在集合中找有没有【96354】这个哈希值的元素,发现【有】(【哈希冲突】)
s2会调用equals方法和哈希值相同的元素进行比较,s2.equals(s1),返回【true】
两个元素的哈希值相同,equals方法返回true,认定【两个元素相同】
【就不会把s2存储到集合中】
*/
7.HashSet存储自定义类型元素
set集合保证元素唯一:
存储的元素(String, Integer, ..., Student, Person),必须重写hashCode方法和equals方法。
因为不同的元素和相同的元素都可能有相同的哈希值,所以需要根据equals方法进一步判断:
元素相同,equals返回true
元素不同,equals返回false
8.哈希值
哈希值:是一个十进制的整数,由【系统随机给出】(就是对象的地址值,是一个【逻辑地址】,是模拟出来得到地址,不是数据实际存储的【物理地址】)
在Object类中,有一个方法,可以获取对象的哈希值
int hashCode() 返回对象的哈希码值。
hahCode方法的源码:
public native int hashCode();
native:代表该方法调用的是【本地】操作系统的方法
之前说的对象的地址值,其实就是对象的哈希值,
toString()方法,打印的默认就是对象哈希值的十六进制表示,如果想要打印对象的属性,就要重写toString方法
toString()方法的源码:
return getClose().getName() + "@" + Integer.toHexString(hashCode());
其实打印出的地址值,就是对应哈希值的十六进制表示
9.LinkedHashSet集合
java.util.LinkedHashSet,它是【链表和哈希表】组合的一个数据存储结构。
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>
LinkedHashSet集合特点:
底层是一个哈希表(数组 + 链表/红黑树) + 链表 :多了一条链表(记录元素的存储顺序),保证元素有序
四、可变参数
使用前提:
当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数。
使用格式:
定义方法时使用
修饰符 返回值类型 方法名称(数据类型... 变量名){}
可变参数的原理:
可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数
传递的参数个数,可以是0个(不传递),1,2,...
可变参数的注意事项:
1.一个方法的参数列表,只能有一个可变参数
2.如果方法的参数有多个,那么可变参数必须写在参数列表的末尾
可变参数的特殊写法:
public static void method(Object... obj){} // Object类型可以接收任意类型的参数
五、Collections
1.java.utils.Collections 是集合工具类,用来对集合进行操作。
2.Collections中常用方法
public static <T> boolean addAll(Collection<T> c, T... elements):往集合中添加一些元素
public static void shuffle(List<?> list)打乱顺序:打乱集合顺序
public static <T> void sort(List<T> list):将集合中元素按照默认规则排序,即升序排序
public static <T> void sort(List<T> list, Comparator<? super T>):将集合中元素按照指定规则排序
3.addAll()方法和shuffle()方法
ArrayList<String> list = new ArrayList<>();
//add()方法,一次只能添加一个元素,比较繁琐
//list.add("a");
//list.add("b");
//list.add("c");
// 使用集合工具类Collections中的addAll()方法
// 一次就可以添加多个元素
Collections.addAll(list, "a", "b", "c", "d", "e");
//打乱顺序:打乱集合顺序
Collections.shuffle(list);
4.sort()方法
public static <T> void sort(List<T> list):将集合中元素按照默认规则排序,即升序排序
public static <T> void sort(List<T> list, Comparator<? super T>):将集合中元素按照指定规则排序
注意事项:
1. 不管是Integer还是String,都【实现了一个接口】:Comparable<E>
public final class Integer extends Number implements Comparable<Integer>
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
在Comparable<E>接口中,【有一个用来排序的方法】:
public int compareTo(T o); 只有【重写】了这个方法,【才能进行排序】。
【Integer类和String类都重写了这个方法】
2.sort(List<T> List)【使用前提】:
被排序的集合里边存储的元素,必须实现Comparable,重写接口中的方法compareTo定义排序的规则。
【对谁进行排序,泛型就写谁】
注意:只能传递list,不能传递set
3.Comparable接口的【排序规则】:
自己(this) - 参数:升序
参数 - 自己(this):降序
Comparator和Comparable的区别:
Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
Comparator:相当于找一个第三方的裁判,比较两个。Comparator排序规则:
o1 - o2:升序。反之就是降序