1.foreach
除了使用迭代器遍历集合之外,JDK1.5及以后版本JDK,提供了增强for循环实现集合遍历,这种方式x相对迭代器遍历更简单。
遍历格式:
for(集合元素类型 变量名 : 集合) {
//操作元素变量
}
每次循环,引用变量会指向集合中的一个元素对象,然后在循环体中对该元素对象进行操作
案例展示:
package com.briup.chap08.test;
import java.util.ArrayList; import java.util.Collection;
public class Test043_Foreach {
public static void main(String[] args) {
//1.实例化集合对象,专门存放String元素
Collection<String> c1 = new ArrayList<>();
String s1 = "hello"; String s2 = "world"; String s3 = "nihao";
//2.往集合中添加元素c1.add(s1); c1.add(s2); c1.add(s3);
//3.增强for循环遍历
// 快捷键:输入fore 后按下 Alt+/
for (String str : c1) { System.out.println(str);
}
}
}
可以看出,使用foreach循环对集合进行遍历,会更加简单一些
同时,使用foreach循环也可以遍历数组:
public static void main(String[] args) { int[] arr = {1,3,5,7,9};
//每次循环,使用变量i接收数组中的一个数据
for(int i : arr){ System.out.println(i);
}
}
注意,Collection及其子类型的集合,还有数组,都可以使用foreach循环进行遍历!
5.List接口
java.util.List 接口继承了 Collection 接口,是常用的一种集合类型。List 集合具有 Collection 集合的特点之外,还具有自己的一些特点:
List是一种有序集合
例如,向集合中存储的元素顺序是8、2、5。那么集合中就是按照这个顺序进行存储的
List一种带索引的集合
可以通过元素的下标索引,精确查找对应的元素数据List集合可以存放重复元素
可以把相同的数据,在List集合中多次保存
5.1 继承体系
List接口继承了Collection接口,Collection接口继承了Iterable接口。
List接口源码:
package java.util;
public interface List<E> extends Collection<E> {
//省略...
}
List接口的实现类:
注意,这些实现类中,都已经实现了List接口、Collection接口、Iterable接口中的方法,我们只要了解并能使用这些接口中的方法,就已经能够操作这些集合对象了(面向接口)。
额外的,我们还需要了解这些常用的接口实现类,分别都是什么特点,使用的什么数据结构,以及适合在什么样的场景下使用。
5.2 常用方法
//返回集合中指定位置的元素。
E get(int index);
//用指定元素替换集合中指定位置的元素,并返回被替代的旧元素。
E set(int index, E element);
//将指定的元素,添加到该集合中的指定位置上。
void add(int index, E element);
//从指定位置开始,把另一个集合的所有元素添加进来
boolean addAll(int index, Collection<? extends E> c);
//移除列表中指定位置的元素, 并返回被移除的元素。
E remove(int index);
//查收指定元素在集合中的所有,从前往后查到的第一个元素(List集合可以重复存放数据)
int indexOf(Object o);
//查收指定元素在集合中的所有,从后往前查到的第一个元素(List集合可以重复存放数据)
int lastIndexOf(Object o);
//根据指定开始和结束位置,截取出集合中的一部分数据
List<E> subList(int fromIndex, int toIndex);
注意,除了这些方法之外,还有从父接口Collection中继承过来的方法
List集合的方法使用:
package com.briup.chap08.test;
import java.util.ArrayList; import java.util.Iterator; import java.util.List;
public class Test052_List {
public static void main(String[] args) {
//1.创建List集合对象
List<String> list = new ArrayList<>();
//2.添加元素,默认尾部添加list.add("hello1"); list.add("hello2"); list.add("hello3"); list.add("hello1");
System.out.println(list);
//3.指定位置添加元素
// add(int index,String s) list.add(1, "world"); System.out.println(list);
//3.删除索引位置为2的元素
//boolean f = list.remove(2);
//System.out.println("remove(2): " + f);
//System.out.println("after remove: " + list);
//4.修改指定位置元素list.set(0, "briup");
System.out.println(list);
/5.借助get方法遍历集合
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));
}
System.out.println("-----------------");
//6.使用foreach遍历
for(Object obj : list){ System.out.println(obj);
}
System.out.println("-----------------");
//7.使用迭代器遍历
Iterator<String> it = list.iterator(); while(it.hasNext()){
String str = it.next(); System.out.println(str);
}
}
}
由上述案例可知,List集合的特点:有序可重复,并且可以使用下标索引进行访问
5.3 ArrayList
java.util.ArrayList 是最常用的一种List类型集合, ArrayList 类底层使用数组来实现数据的存储,所以它的特点是:增删慢,查找快。
在日常的开发中,查询数据也是用的最多的功能,所以ArrayList是最常用的集合。
但是,如果项目中对性能要求较高,并且在集合中大量的数据做增删操作,那么 ArrayList 就不太适合了。
5.4 LinkedList
java.util.LinkedList 底层采用的数据结构是双向链表,其特点是:增删快,查找慢
它的特点刚好和 ArrayList 相反,所以在代码中,需要对集合中的元素做大量的增删操作的时候,可以选择使用 LinkedList 。
注意:这里描述的快和慢,需要在大量的数据操作下,才可以体现,如果数据量不大的话,集合每一种集合的操作几乎没有任何区别。
1)特点验证
实例化ArrayList、LinkedList集合对象,放入100000个元素,测试两种集合插入、查询效率!
package com.briup.chap08.test;
import java.util.ArrayList; import java.util.LinkedList; import java.util.List;
public class Test054_LinkedList {
public static void main(String[] args) {
//操作集合的次数
final int NUM = 100000;
//1.实例化集合对象
List<String> list = new ArrayList<>();
//List<String> list = new LinkedList<>();
//2.开启计时,往集合种放入 100000 个元素
long start1 = System.currentTimeMillis(); for (int i = 0; i < NUM; i++) {
list.add(0,"hello"+i);
}
long end1 = System.currentTimeMillis();
//3.输出时长System.out.println(list.getClass().getSimpleName()+"插
入"+NUM+"条数据耗时"+(end1-start1)+"毫秒");
//4.开启计时,从集合种取 100000 个元素
long start2 = System.currentTimeMillis(); for(int i = 0; i < list.size(); i++){
list.get(i); }
long end2 = System.currentTimeMillis();
//5.输出时长System.out.println(list.getClass().getSimpleName()+"检
索"+NUM+"条数据耗时"+(end2-start2)+"毫秒");
}
}
//运行效果:
//根据电脑的当前情况,每次运行的结果可能会有差异
//以下是我的电脑运行俩次的实验结果,第一次使用ArrayList,第二次使用
LinkedList
ArrayList插入100000条数据耗时508毫秒ArrayList检索100000条数据耗时2毫秒
LinkedList插入100000条数据耗时22毫秒LinkedList检索100000条数据耗时17709毫秒
注意事项:
list.getClass().getSimpleName();
获取list引用指向对象所属类型名(不含包名),了解即可,后续反射会讲解。
System.currentTimeMillis();
获取当前时刻的时间戳,要求掌握,后续还会用到。
2)底层实现,此部分为补充内容(了解即可)
LinkedList底层借助双向链表实现,双向链表又由节点构成,每个节点由三部分构成:俩个引用(分别指向前、后节点),一个数据域(存储集合元素).
3)头尾节点操作方法
在 LinkedList 中,定义了一些操作头节点和尾节点的方法:
//将指定元素插入此列表的开头
void addFirst(E e)
//将指定元素添加到此列表的结尾void addLast(E e)
//返回此列表的第一个元素
E getFirst()
//返回此列表的最后一个元素E getLast()
//移除并返回此列表的第一个元素
E removeFirst()
//移除并返回此列表的最后一个元素E removeLast()
//从此列表所表示的堆栈处弹出一个元素
E pop()
//将元素推入此列表所表示的堆栈void push(E e)
案例展示:创建一个LinkedList对象,通过节点方法往里面添加、更新、删除元素。
package com.briup.chap08.test; import java.util.LinkedList; public class Test054_Node {
public static void main(String[] args) {
//1.实例化集合对象
//注意,要测试LinkedList中的方法,必须用LinkedList引用指向LinkedList对象
LinkedList<String> list = new LinkedList<>();
String s1 = "hello";
String s2 = "world";
String s3 = "nihao";
//2.往集合中添加元素并输出
list.add(s1); list.add(s2); list.add(s3);
System.out.println("list: " + list);
System.out.println("------------");
//3.往头、尾节点添加元素list.addFirst("first"); list.addLast("last"); System.out.println("list: " + list);
System.out.println("------------");
//4.获取头尾节点元素
System.out.println("getFirst: " + list.getFirst()); System.out.println("getLast: " + list.getLast());
System.out.println("------------");
//5.删除头尾节点元素list.removeFirst(); list.removeLast(); System.out.println("list: " + list);
}
}
//程序运行效果:
list: [hello, world, nihao]
------------
list: [first, hello, world, nihao, last]
------------ getFirst: first getLast: last
------------
list: [hello, world, nihao]
注意:要测试LinkedList中的方法,必须用LinkedList引用指向LinkedList对象!
5.5 Vector
Vector是在JDK1.0引入的,它实现了List接口,属于Java集合框架的一部分,其基于动态数组(Dynamic Array)实现,线程安全,Vector在功能和使用方式上和ArrayList非常相似。
ArrayList是在JDK 1.2引入的,非线程安全,但单线程环境下性能更高效,是Vector的一个非线程安全的替代品。
关于线程安全,不需要过多关注,后面线程章节会具体学习。
Vector继承体系如下:
Vector部分源码:
package java.util;
/**
* The {@code Vector} class implements a growable array of
* objects. Like an array, it contains components that can be * accessed using an integer index.
* @see LinkedList * @since JDK1.0
*/
public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//省略
/**
* 底层借助数组存储数据
* The array buffer into which the components of the vector are
* stored. */
protected Object[] elementData; }
Vector 内部也是采用了数组来存储数据,但是 Vector 中的方法大多数都是线程安全的方法,所以在多线并发访问的环境中,可以使用 Vector 来保证集合中元据操作的安全。
案例展示:
创建Vector集合,往里面添加元素,遍历输出,对比其和ArrayList的用法。
package com.briup.chap08.test;
import java.util.Enumeration; import java.util.Vector;
//Vector线程安全但效率较低
//其早期提供的方法使用较为繁琐
public class Test055_Vector {
public static void main(String[] args) {
//1.实例化Vector对象
Vector<String> v = new Vector<>();
//2.往集合中添加元素v.add("hello");
//早期添加元素,相对麻烦v.addElement("123"); v.addElement("abc");
//3.遍历Vector
// 早期遍历方式,相对麻烦
Enumeration<String> elements = v.elements(); while(elements.hasMoreElements()) {
System.out.println(elements.nextElement()); }
}
}
结论:我们今后主要使用ArrayList。多线程环境需要保证线程安全的话,后期学习工具类,可以使 ArrayList变成线程安全。
List小结
集合类 | 特点 | 示例 |
ArrayList | 内部使用数组实现,动态调整大小。读取快,插入删除慢 | List list = new ArrayList<>(); |
LinkedList | 内部使用双向链表实现,插入删除快,读取慢 | List list = new LinkedList<>(); |
Vector | 线程安全的动态数组实现,操作方法同步,多线程环境安全 | List list = new Vector<>(); |
6 数据结构
概述
数据结构是计算机科学中研究数据组织、存储和操作的一门学科。它涉及了如何组织和存储数据以及如何设计和实现不同的数据操作算法和技术。常见的据结构有线性数据结构(含数组、链表、栈和队列等),非线性数据结构(树、图等)。
注意:不同的数据结构适用于不同的场景和问题,选择合适的数据结构可以提高算法的效率和性能。
Java集合框架中不同的实现类底层借助不同数据结构来存储输出,常见的数据结构有:
- 数组(Array):有序集合,可以包含重复的元素,常见实现类有ArrayList、Vector
- 链表(LinkedList):链表是一种动态数据结构,通过节点之间的链接来组织数据。常见的链表实现类是LinkedList
- 集合(Set):集合是不允许包含重复元素的无序集合。常见的集合实现类有HashSet、LinkedHashSet和TreeSet
- 映射(Map):映射是一种键值对的集合,每个键只能对应一个值。常见的映射实现类有HashMap、LinkedHashMap和TreeMap
- 队列(Queue):队列是一种先进先出(FIFO)的数据结构。常见的队列实现类有LinkedList和PriorityQueue
- 栈(Stack):栈是一种后进先出(LIFO)的数据结构。常见的栈实现类是Stack
- 树(Tree):树是一种具有分层结构的数据结构,常见的树实现类有BinaryTree和BinarySearchTree
数组
数组(array),内存中一块连续的空间,元素数据在其中按照下标索引依次存储,比较常用的数据结构。
其特点是:通过下标索引,可以快速访问指定位置的元素,但是在数组中间位置添加数据或者删除数据会比较慢,因为数组中间位置的添加和删除元素,为了元素数据能紧凑的排列在一起,那么就会引起其后面的元素移动位置。
所以,数组查询元素较快,中间位置的插入、删除元素较慢。
可以看出,数组中间添加数据后,之后的数据都要依次移动位置。同理,中间位置删除的时候也是这样
链表
链表(linked list),是由一个一个node节点组成,每个node节点中包含两项数据:指针域、数据域。数据域存储了一个数据,指针域存储指向下一个node节点对象的引用(单向链表)。
如果是双向链表的话,指针域会存储2个引用,一个指向前一个node节点对象,另一个指向了下一个node节点对象。
单链表插入、删除节点:
链表特点:
查找元素慢,因为需要通过连接的节点,依次向后查找指定元素(没有直接的下标索引)
新增和删除元素较快,例如删除,只需要让当前node节点中的引用指向另一个节点对象即可,原来的指向的node节点就相当于删除了
可以看出,只需要将数据2节点中的引用,指向数据4的节点对象即可head表示链表的头部,tail表示链表的尾部
思考,是否能根据单向链表的特点,想象出双向链表的特点?
栈
栈(stack),又称堆栈,仅允许在栈的一端进行插入和删除操作,并且不允许在其他任何位置进行操作。
其特点是:先进后出,最先存进去的元素,最后才能取出来 。
例如,薯片存在薯片桶中,我们当前只能取出最上面的一个薯片,而最早存放到薯片桶的薯片,反而是我们最后吃到的一片。
注意1,入栈也称为压栈,把数据存入到栈的顶端位置注意2,出栈也称为弹栈,把栈顶位置的数据取出
思考,JVM中的栈区中,为什么把main方法标注在最低端位置?
队列
队列(queue),仅允许在队列的一端进行插入,而在队列的另一端进行删除。其特点是:先进先出,最先存进去的元素,可以最先取出来 。
例如,火车穿过山洞的时候,第一节车厢先进去山洞的一端,并且这节车厢优先从山洞的另一端出来,后面的车厢依次从一端进入并另一端出来。
队列的入口、出口分别在队列的俩端:
红黑树
二叉树(Binary tree)是树形结构的一个重要类型。二叉树特点是每个结点最多只能有两棵子树,且有左右之分。
二叉树顶上的叫根结点,两边被称作“左子树”和“右子树”。
二叉树中有一种叫做红黑树(Red/Black Tree),它最早被称为平衡二叉B树(symmetric binary B-trees),后来被称为红黑树。
红黑树是一种特殊化的平衡二叉树,它可以在进行插入和删除的时候,如果左右子数的高度相差较大,那么就通过特定操作(左旋、右旋)保持二叉查找树的平衡(动态平衡),从而获得较高的查找性能。
红黑树的每一个节点的左子树的所有数据都比自己小,而右子树的所有数据都比自己大,并且左右子树的高度近似。
红黑树的约束:
- 根节点必须是黑色
- 其他节点可以是红色的或者黑色
- 叶子节点(特指null节点)是黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
注意,红黑树的指定颜色的目的,是利用颜色值作为二叉树的平衡对称性的检查
例如,从空树开始演示一个案例:
数字插入顺序为 9、8、12、7、6,对于一个节点来说,新数据如果小于本节点,会被放在左节点的位置,反之则放在右节点的位置
当插入数字6的时候,对于红黑树来说整个结构失去平衡,需要通过自旋来调整,最后结果如下:红黑树在线演示
可以通过在线工具,进行节点的添加,查看红黑树的动态调整的动画效果,建议使用chrome浏览器打开:
哈希表
java中的哈希表(hash),在JDK1.8之前是采用数组+链表进行实现,根据数据的哈希值,把数据存在数组中,但是当前哈希值冲突的时候,再使用链表进行存储,那么在数组中,同一hash值的数据都存在一个链表里。
注意,之前学习过Object中hashCode方法的作用,hash值的特点以及和对象之间的关系
例如,
例如,如果数据的哈希值相同,在数组使用使用链表存储哈希值相同的几个数据
可以看出,当链表中元素过多,即hash值相等的元素较多时,查找的效率会变低
JDK1.8中,哈希表存储采用数组+链表+红黑树进行实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样可以大大提高查找的性能。