day06_Collection、List、Set
1. 集合进阶
- List系列集合:添加的元素是有序、可重复、有索引
- Set系列集合:添加的元素是无序、不重复、无索引
2. Collection
2.1 方法汇总
- Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
2.2 遍历方式
-
迭代器
- 迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator
- 通过
Iterator<E> iterator()
方法获取集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素 - 通过
boolean hasNext()
方法询问当前位置是否有元素存在,存在返回true,不存在返回false - 通过
E next()
方法获取当前位置的元素,并同时将迭代器对象指向下一个元素处 - 代码演示
Iterator<String> it = lists.iterator(); while (it.hasNext()) { String ele = it.next(); System.out.println(ele); }
-
增强for循环
- 代码演示
for (元素的数据类型 变量名:数组或集合) { ... } // 举例 Collection<String> c = new ArrayList<>(); ... for(String s : c) { System.out.println(s); }
- 增强for可以用来遍历集合或者数组,本质就是迭代器遍历集合的简化写法
- 修改增强for中的变量值不会影响到集合中的元素
-
Lambda表达式
- 使用
default void forEach(Consumer<? super T> action)
结合lambda完成遍历 - 代码演示
Collection<String> lists = new ArrayList<>(); ... /* lists.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }); */ lists.forEach(s -> { System.out.println(s); });
- 使用
3. List集合
-
List系列集合特点: 有序,可重复,有索引
-
ArrayList:有序,可重复,有索引
-
LinkedList:有序,可重复,有索引
3.1 常用方法
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
3.2 遍历方式
-
for循环
List<String> col = new ArrayList<String>(); col.add("张三丰"); col.add("张无忌"); col.add("张翠山"); col.add("张学友"); for (int i = 0; i < col.size(); i++) { System.out.println(col.get(i)); }
-
迭代器
Iterator<String> it = col.iterator(); while (it.hasNext()) { System.out.println(it.next()); }
-
增强for循环
for (String s : col) { System.out.println(s); }
-
Lambda表达式
System.out.println("----------lambda----------"); col.forEach((s) -> System.out.println(s)); System.out.println("----------方法引用----------"); col.forEach(System.out::println);
3.3 ArrayList集合的底层原理
实现:
- 基于数组实现
特点:
- 查询快,增删慢
- 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
- 删除效率低:可能需要把后面很多的数据进行前移
- 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容
底层原理:
- 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
适用场景:
- 适合根据索引查询数据的场景
- 不适合数据量大的同时,又要频繁的进行增删操作的场景
3.4 LinkedList集合的底层原理
实现:
- 基于双链表实现
底层原理:
链表:
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址
- 查询慢,无论查询哪个数据都要从头开始找
- 链表增删相对快
双链表:
- 查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的
特有方法:
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
适用场景:
- 队列
- 栈
4. set集合
- Set系列集合特点: 无序;添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引
- HashSet : 无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:可排序、不重复、无索引
案例演示:
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>();
hashSet.add("q");
hashSet.add("w");
hashSet.add("e");
hashSet.add("r");
hashSet.add("t");
hashSet.add("y");
hashSet.add("u");
hashSet.add("i");
hashSet.add("o");
hashSet.add("p");
System.out.println(hashSet); // [p, q, r, t, e, u, w, y, i, o]
Iterator<String> it = hashSet.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("a");
linkedHashSet.add("s");
linkedHashSet.add("d");
linkedHashSet.add("f");
linkedHashSet.add("g");
linkedHashSet.add("h");
linkedHashSet.add("j");
linkedHashSet.add("k");
linkedHashSet.add("l");
System.out.println(linkedHashSet); // [a, s, d, f, g, h, j, k, l]
for (String s : linkedHashSet) {
System.out.println(s);
}
Set<String> treeSet = new TreeSet<>();
treeSet.add("z");
treeSet.add("x");
treeSet.add("c");
treeSet.add("v");
treeSet.add("b");
treeSet.add("n");
treeSet.add("m");
System.out.println(treeSet); // [b, c, m, n, v, x, z]
treeSet.forEach(System.out::println);
}
4.1 HashSet集合的底层原理
哈希值:
- 一个int类型的数值,Java中每个对象都有一个哈希值
- Java中的所有对象,都可以调用
public int hashCode();
方法,返回该对象自己的哈希值 - 同一个对象多次获取的哈希值是相同的
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
树结构
-
二叉树
如图所示就是一棵典型的二叉树,即任意节点的度不大于2
* 度:每一个节点的子节点数量
* 树高:树的总层数
* 根节点:最顶层的节点
* 左子节点、右子节点、左子树、右子树
- 二叉查找树
规则:
* 小的存左边
* 大的存右边
* 一样的不存
-
平衡二叉树
在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能
-
红黑树
红黑树就是可以自平衡的二叉树,是一种增删改查数据性能相对都较好的结构
实现:
- 基于哈希表实现
- 哈希表是一种增删改查数据,性能都较好的数据结构
- JDK8之前,哈希表 = 数组+链表
- JDK8开始,哈希表 = 数组+链表+红黑树
JDK8前HashSet的底层原理:
- 根据hashCode值计算存放的位置
- 如果这个位置没有元素,直接存储
- 如果这个位置有元素,调用equals比较
- equals()为false,存储
- equals()为true,不存储(认为是相同的元素)
JDK8开始HashSet的底层原理:
- 开始
- 当链表元素过多
- 转换成红黑树
- 创建一个默认长度16的数组,默认加载因子为0.75
- 当数组快占满了(16 * 0.75 = 12),则会扩容
- 扩容后的数组是原数据长度的2倍 (16 * 2 = 32)
- 当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
自定义去重:
- 重写对象的hashCode()和equals()方法
4.2 LinkedHashSet集合的底层原理
实现:
- 基于哈希表(数组、链表、红黑树)实现的
- 每个元素都额外的多了一个双链表的机制记录它前后元素的位置
底层实现:
4.3 TreeSet集合
实现:
- 基于红黑树实现的排序
注意:
- 对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序
- 对于字符串类型:默认按照首字符的编号升序排序
- 对于自定义类型如Student对象,TreeSet默认是无法直接排序的
去重原理:
- 排序时当遇到相等情况,则认为两个相同,后添加的不会被记录到TreeSet集合中
自定义排序规则:
-
让自定义的类实现Comparable接口,重写里面的compareTo方法来指定比较规则
-
通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)
public TreeSet(Comparator<? super E> comparator)
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序
-
案例演示
// 方法一:类实现Comparable接口并重写compareTo方法 @Override public int compareTo(Student o) { return Double.compare(this.age, o.age); } // 方法二:使用构造器,传入比较规则 Set<Student> treeSet = new TreeSet<>((o1, o2) -> Double.compare(o1.age, o2.age));