单列集合
特点
集合实际上就是一个容器 , 可以动态的容纳不同类型的对象;数组也是一个集合但它长度一旦确定就不能更改
- 集合是一个载体 , 可将数据库中查询到的10条记录封装成10个java对象放到某一个集合当中并传到前端,然后前端遍历这个集合做数据展示
- 集合不能直接存储基本数据类型和 java 对象 ,集合中存储的都是堆中某个java对象的内存地址, 对于
list.add(100)
底层使用了自动装箱
单列集合分为三大类
- List类型的集合: 有序可重复即集合中的元素存进去和取出来的顺序是一致的 , 这种类型的集合的元素都有下标
- Set类型的集合: 无序不可重复即集合中的元素存进去和取出来的顺序不同 , 这种类型的集合的元素都没有下标
- Queue类型的集合: 先进先出(FIFO) , 只能一端进并且在另一端出的队列
Collection中能存放的元素: 没有使用泛型之前Collection中可以存储Object的所有子类型
,使用了泛型
之后Collection中只能存储某个指定的类型
- 存放在
List集合中的元素一定要重写equals方法
, 因为调用集合的contains和remove
方法时会调用对象的equals方法
判断两个对象是否相等 - 存放在
Set集合中的元素需要同时重写hashCode()+equals()方法
,因为是先调用hashCode()方法确定元素所在分区,后调用equals()方法与该分区元素比较
单列集合继承结构图
Collection父接口
向集合中添加/删除元素
的方法
方法名 | 功能 |
---|---|
boolean add(Object e) | 向集合中添加元素(默认都是向集合末尾添加元素 ) , 没有使用泛型 之前Collection中可以存储Object的所有子类型 |
boolean addAll(Collection c) | 向集合中添加多个元素即添加一个集合 |
boolean remove(Object o) | 删除集合中的某个元素,也可以通过下标指定删除 |
boolean removeAll(Collection c) | 删除集合中的多个元素即删除一个集合 |
Object[] toArray() | 把集合转换成数组 , 同理Arrays工具类中也有方法把数组转化成集合 |
对集合中元素的判断
方法名 | 功能 |
---|---|
int size() | 获取集合中元素的个数 |
void clear() | 清空集合的元素 |
boolean contains(Object o) | 判断当前集合中是否包含某个元素,底层调用该元素的equals方法与集合中的元素一个一个进行比对 |
boolean containsAll(Collection c) | 判断集合中是否包含多个元素即一个集合 |
boolean isEmpty() | 判断该集合中元素的个数是否为0 |
public class CollectionTest {
public static void main(String[] args) {
// 利用多态创建一个集合对象,接口是抽象的无法实例化
Collection c = new ArrayList();
// 测试add方法向集合中添加元素
// 自动装箱(java5的新特性),实际上是放进去了一个对象的内存地址
c.add(1200);
c.add(3.14);
c.add(new Object());
c.add(new Student());
c.add(true);
// 测试size方法,获取集合中元素的个数
System.out.println("集合中元素个数是:" + c.size()); // 5
// 测试clear方法清空集合中的元素
c.clear();
System.out.println("集合中元素个数是:" + c.size()); // 0
// 再次向集合中添加元素
c.add("绿巨人");
// 测试contains方法判断集合中是否包含"绿巨人"
boolean flag = c.contains("绿巨人");
System.out.println(flag); // true
boolean flag2 = c.contains("蓝巨人");
System.out.println(flag2); // false
// 测试remove方法删除集合中某个元素
c.remove("绿巨人");
System.out.println("集合中元素个数是:" + c.size()); // 0
// 判断集合是否为空
System.out.println(c.isEmpty()); // true
// 再向集合中添加元素
// "helloworld!"对象的内存地址放到了集合当中
c.add("helloworld!");
c.add(new Student());
// 测试toArray()方法将集合转换成数组
Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
// 遍历数组输出集合中的元素
Object o = objs[i];
System.out.println(o);
}
}
}
class Student{
}
深入contains和remove
contains方法原理
: 当需要判断集合中是否包含某个元素时,底层是拿着该元素并调用它的equals方法与集合中的元素一个一个进行比对判断是否相等
,相等则包含
public class CollectionTest {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();
// 向集合中存储元素
String s1 = new String("abc"); // s1 = 0x99
c.add(s1);
String s2 = new String("def"); // s2 = 0x77
c.add(s2);
// 新建的对象String
String x = new String("abc"); // x = 0x94
// 虽然c集合不包含x对象的内存地址,但是调用equals方法时会判断它和s1相等,等价于集合中包含x
System.out.println(c.contains(x)); //true
}
}
remove方法原理
: 当需要删除集合中对应的元素时,底层是拿着该元素并调用它的equals方法与集合中的元素一个一个进行比对判断是否相等
,相等则删除
public class CollectionTest {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();
// 创建用户对象
User u1 = new User("jack");
// 将u1对象加入集合
c.add(u1);
// 创建u2对象
User u2 = new User("jack");
// 删除集合中的u2对象
// User类没有重写equals之前不会删除集合中的u1, 重写equals之后此时u1和u2是等价的所以会删除u1
c.remove(u2);
// 元素个数0个
System.out.println(c.size());
}
}
class User{
private String name;
public User(){}
public User(String name){
this.name = name;
}
// 重写equals方法,比较原理是只要姓名一样就表示同一个用户
public boolean equals(Object o) {
if(o == null || !(o instanceof User)) return false;
if(o == this) return true;
User u = (User)o;
return u.name.equals(this.name);
}
}
Iterable遍历集合
通用方式
: 先获取集合对象的迭代器对象Iterator
,通过获取的迭代器对象的方法开始迭代/遍历集合
集合获取Iterable
迭代器对象的方法
方法名 | 功能 |
---|---|
Iterator iterator() | 获取集合对应的 Iterator 类型的迭代器对象,对集合中的元素进行迭代 |
Iterable
迭代器对象的方法
方法名 | 功能 |
---|---|
boolean hasNext() | 判断集合中是否还有元素可以迭代,最初迭代器没有指向任何元素 |
Object next() | 返回迭代器指向的元素 在没有指定泛型之前 , 无论存进去什么类型的元素, 取出来元素的编译类型统一都是 Object 类型,元素运行类型没有改变 获取元素前一定要先判断集合中是否还有元素, 如果没有元素继续迭代则会出现 java.util.NoSuchElementException 异常 |
void remove(o) | 删除迭代器指向的元素,此时会自动更新迭代器和更新集合中的元素直接通过集合去删除元素不会更新迭代器 , 就会导致迭代器参照的快照和集合中的元素状态不同 |
当集合的结构发生改变时必须重新获取迭代器对象
, 如果还是用以前老的迭代器对象则报java.util.ConcurrentModificationException
异常
- 当我们获取集合的迭代器对象的时候,迭代器会对当前集合的状态拍一个快照,迭代器对象会参照这个快照对集合进行迭代
- 迭代器对象每迭代一次后都会拿快照与集合中的元素状态进行比较,如果不一致则报
java.util.ConcurrentModificationException
异常
使用集合的remove方法或迭代器的remove方法
删除集合元素的区别
-
集合的remove方法
: 直接删除集合中的元素并不会通知迭代器更新快照即迭代器不知道集合元素状态变化了 -
迭代器的remove方法
: 删除集合中的元素并且会通知迭代器更新快照
public class CollectionTest {
public static void main(String[] args) {
// 创建集合
Collection c = new ArrayList();
// 此时获取的迭代器是集合中没有元素状态下的迭代器
Iterator it = c.iterator();
// 向集合添加中元素,集合的状态发生了改变
c.add("abc");
c.add("def");
c.add("xyz");
// 重新获取迭代器,此时获取的是集合中有3个元素状态下的迭代器
Iterator it2 = c2.iterator();
while(it2.hasNext()){
Object o = it2.next();
// 迭代器的remove方法删除迭代器指向的当前元素
it2.remove();
System.out.println(o);
}
System.out.println(c2.size()); //0
}
}
foreach遍历集合
foreach
内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码
// 创建集合
Set<String> strs = new HashSet<>();
// 增强for遍历
for(String s : strs){
System.out.println(s);
}
// 使用jad工具对编译后的class进行反编译, 底层其实是依赖了while循环和Iterator实现的
Iterator iterator = userNames.iterator();
do
{
if(!iterator.hasNext())
break;
String s = (String)iterator.next();
} while(true);
如果在foreach 循环里对某些集合元素进行元素的 remove/add
操作的时候,就会触发fail-fast
机制进而抛出ConcurrentModificationException
List<String> userNames = new ArrayList<String>() {{
add("Hollis");
add("hollis");
add("HollisChuang");
add("H");
}};
for (String userName : userNames) {
if (userName.equals("Hollis")) {
userNames.remove(userName);
}
}
System.out.println(userNames);
List接口的实现类
List类型的集合
存储元素特点就是有序可重复
,可以通过下标遍历集合
有序
: 元素下标从0开始以1递增
, 存进去的顺序和取出的顺序一致可重复
:可以重复存储同一个元素,集合中可以添加多个null
元素特点
: 存放在List集合中的元素一定要重写equals方法
, 因为调用集合的contains和remove
方法时会调用对象的equals方法
判断两个对象是否相等
List接口的常用方法
List接口
是Collection接口的子接口有自己特有的方法
方法名 | 功能 |
---|---|
void add(int index, Object element) | 在集合中的指定下标位置插入指定一个元素 , 不指定下标默认是向集合末尾添加元素 |
void addAll(int index, Collection c) | 在集合中的指定下标位置插入一个集合的元素 , 不指定下标默认是向集合末尾添加元素 |
Object remove(int index) | 删除集合中指定下标位置的元素,对于ArrayList集合来说效率比较低 |
Object set(int index, Object element) | 将集合中指定下标位置的元素修改/替换 为另一个对象 |
Object get(int index) | 根据下标获取集合中的元素 |
int indexOf(Object o) | 获取指定对象在集合中第一次出现处的索引 |
int lastIndexOf(Object o) | 获取指定对象在集合中最后一次出现处的索引 |
List subList(int fromIndex , int toIndex) | 返回从fromIndex到toIndex位置的一个新集合(前闭后开 ) |
public class ListTest01 {
public static void main(String[] args) {
// 创建任意一种List类型的集合
List myList = new ArrayList();
// 默认都是向集合末尾添加元素
myList.add("A");
myList.add("B");
myList.add("C");
// 在集合中的指定下标位置插入指定元素,对于ArrayList集合来说效率比较低
myList.add(1, "C");
// 根据下标获取集合中的元素
Object firstObj = myList.get(0);
System.out.println(firstObj);// A
// 通过下标遍历集合
// A C B C
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
// 获取指定对象在集合中第一次出现处的索引
System.out.println(myList.indexOf("C")); // 1
// 获取指定对象在集合中最后一次出现处的索引
System.out.println(myList.lastIndexOf("C")); // 3
// 删除集合中指定下标位置的元素,对于ArrayList集合来说效率比较低
myList.remove(0);
System.out.println(myList.size()); // 3
// 修改/替换集合中指定下标位置的元素
myList.set(2, "D");
// 通过下标遍历集合
// C B D
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
}
}
ArrayList集合(数组)
ArrayList集合
没有指定泛型前底层是一个Object[]数组
,执行List接口通过指定下标位置元素进行添加或删除
的方法时效率较低,因为涉及到数组中元素的位移
集合扩容
: 增长到原容量的1.5倍, 数组扩容效率比较低建议在创建ArrayList集合的时候给定一个初始化容量检索效率最高
: 数组的每个元素占用空间大小相同且内存地址是连续的, 可以通过首元素内存地址和指定位置的下标
快速定位到指定元素的内存地址随机增删元素效率比较低
: 向数组末尾添加元素效率很高数据存储
: 无法存储大数据量的数据,因为很难找到一块非常巨大的连续的内存空间安全
: 非线程安全
构造方法 | 功能 |
---|---|
ArrayList() | 默认数组的初始化容量为10,底层是先创建了一个长度为0的数组,只有当添加了第一个元素时才会初始化数组容量为10 |
ArrayList(int num) | 指定数组的初始化容量 |
ArrayList(Collection c) | 将Collection类型的集合转换成ArrayList类型的集合,这样就可以使用List集合特有的遍历方式 |
public class ArrayListTest02 {
public static void main(String[] args) {
// 默认数组的初始化容量为10
List myList1 = new ArrayList();
// 指定数组的初始化容量为100
List myList2 = new ArrayList(100);
// 创建一个HashSet集合
Collection c = new HashSet();
// 添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
c.add(50);
// 将HashSet集合转换成List集合
List myList3 = new ArrayList(c);
for(int i = 0; i < myList3.size(); i++){
System.out.println(myList3.get(i));
}
}
}
Vector集合(数组)
Vector集合
相对于ArrayList集合效率较低所以使用较少
-
集合扩容
: 默认初始化容量10个, Vector集合的底层数组每次扩容之后是原容量的2倍 -
安全
: 线程安全,所有方法都带有synchronized关键字
即是线程同步的,但是由于执行效率太低已被弃用,因为我们可以使用集合工具类Collections
将一个线程不安全的ArrayList集合转换成线程安全的
public class VectorTest {
public static void main(String[] args) {
// 创建一个Vector集合,默认初始化容量10个
List vector = new Vector();
// 添加元素
vector.add(1);
// 获取迭代器对象遍历集合
Iterator it = vector.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
// 将一个线程不安全的ArrayList集合转换成线程安全的
List myList = new ArrayList();
Collections.synchronizedList(myList);
myList.add("111");
}
}
Stack集合
Stack
继承并扩展了Vector类,允许将一个向量作为堆栈处理用来表示对象的后进先出(LIFO)
推荐使用Deque替代Stack
: Stack类中同时暴露了set/get
方法, 即可以随机访问Stack类中存储的任意一个元素, 这与Stack先进后出的设计理念相冲突
方法 | 作用 |
---|---|
push(E) | 把元素压栈 |
pop(E) | 把栈顶的元素弹出 |
peek(E) | 获取栈顶元素但不弹出 |
boolean empty() | 判断栈是否为空 |
int search(Object o) | 查找某个元素在栈中的位置(距离栈顶有多远) |
Stack s = new Stack();
s.push(1);
s.pop();
System.out.println(s);
LinkedList(双向链表)
LinkedList集合
底层是双向链表并且集合中的元素也有下标
集合扩容
: 链表没有初始化容量, 最初链表中没有任何元素即头节点
的first和last引用的值都是null随机增删效率较高
: 链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移检索/查找效率较低
: 查找某个元素时只能从头节点开始一个一个遍历直到找到为止应用场景
: 栈、队列或双端队列
public class LinkedListTest01 {
public static void main(String[] args) {
// LinkedList集合底层也是有下标的
List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
for(int i = 0; i <list.size(); i++){
Object obj = list.get(i);
System.out.println(obj);
}
}
}
LinkedList底层添加元素
源码分析
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
Set接口的实现类
Set类型的集合
存储元素的特点无序不可重复,往Set集合中存放的数据时等价于放到了Map集合的key部分(本质就是个特殊的Map集合)
无序
: Set集合中的元素没有下标, 存进去和取出的顺序不一定相同不可重复
:同一个元素添加第二次时虽然没有报错 ,但不会再次存储到集合中元素特点
: 存放在Set/Map集合中的元素需同时重写hashCode()+equals()方法
,先调用hashCode()方法确定元素所在分区,后调用equals()方法与该分区元素比较
HashSet集合(HashMap)
测试Set集合中常用方法
public class HashSetTest01 {
public static void main(String[] args) {
// 创建集合
Set<String> strs = new HashSet<>();
// 添加元素
strs.add("hello3");
strs.add("hello1");
// 存不进去
strs.add("hello3");
// 迭代器遍历
Iterator strs = strs.iterator();
while(strs.hasNext()){
System.out.println(strs.next());
}
// 增强for遍历
for(String s : strs){
System.out.println(s);
}
}
}
TreeSet可排序集合(TreeMap)
TreeSet集合
实现了SortedSet接口,集合中的元素默认按照字典顺序规则
升序排序,也可以手动指定排序规则
第一种方式
:存储到集合中的元素需实现Comparable接口
,然后重写compareTo方法(返回值决定了元素的排序规则)
,重写了该方法后就无需重写equals方法第二种方式
: 构造TreeSet集合时传入一个实现了Comparator接口
的比较器对象并实现compare方法
,该比较器对象可以对集合中的元素进行挨个比较
public class TreeSetTest02 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
// 添加String
ts.add("zhangsan");
ts.add("lisi");
// 遍历
for(String s : ts){
// 默认按照字典顺序升序
System.out.println(s);
}
// 创建一个TreeSet集合
TreeSet<Integer> ts2 = new TreeSet<>();
// 添加Integer,底层会自动类型转换
ts2.add(100);
ts2.add(200);
ts2.add(10);
for(Integer elt : ts2){
// 默认升序
System.out.println(elt);
}
}
}
Queue接口的实现类
Queue是Collection接口的子接口
,它的特点是先进先出(FIFO), 底层是一个只能一端进并且在另一端出的单向队列
除了基本的集合操作之外,Queue
接口额外提供了插入,提取和检查
的方法,方法以两种形式存在即可以抛出异常或者返回特殊值
抛出异常的方法 | 返回特殊值的方法 | 功能 |
---|---|---|
add(e) | offer(e)插入元素失败返回false | 插入 |
remove(e) | poll()取出失败返回null | 取出 |
element(e) | peek()该元素不存在返回null | 检查 |
Deuqe(double ended queue)
Deuqe继承自Queue接口是Java中的双端队列集合类型
,在队列的两端都能插入和删除元素,既具备普通队列FIFO(先进先出)的功能也具备Stack的LIFO(后进先出)功能
模拟队列
:元素添加和删除的位置是双端队列的不同位置模拟堆栈
: 元素添加和删除的位置是双端队列的相同位置
// 普通队列只能一端进另一端出
Queue queue = new LinkedList()或Deque deque = new LinkedList();
// 双端队列两端都可以进出
Deque deque = new LinkedList();
// 堆栈
Deque deque = new LinkedList();
Deuqe接口不仅继承了父接口Queue的方法,自己也扩展了方法
抛出异常的方法 | 返回特殊值的方法 | 功能 |
---|---|---|
addFirst(e) | offerFirst(e)插入元素失败返回false | 将指定的元素插入此双端队列的前面 |
removeFirst(e) | pollFirst()取出失败返回null | 取出第一个元素 |
getFirst(e) | peekFirst()该元素不存在返回null | 检查第一个元素 |
抛出异常的方法 | 返回特殊值的方法 | 功能 |
---|---|---|
addLast(e) | offerLast(e)插入元素失败返回false | 将指定的元素插入此双端队列的后面 |
removeLast(e) | pollLast()取出失败返回null | 取出最后一个元素 |
getLast(e) | peekLast()该元素不存在返回null | 检查最后一个元素 |
Deque接口模拟栈的扩展方法
: 不要直接调用addFirst()/removeFirst()/peekFirst()
方法, 那样会破坏栈的本质
方法 | 功能 |
---|---|
push(e) | 把元素压栈,从头不添加 |
pop(e) | 把栈顶的元素弹出,从头部删除 |
peek(e) | 取栈顶元素但不弹出, 从头部取出 |
public class Main {
public static void main(String[] args) {
// 默认构造器
Deque<String> deque = new LinkedList<>();
// 从头部开始添加元素,先添加的先到尾部
// 此时deque中元素的位置(从头部开始):Java, Like , I
deque.addFirst("I");
deque.addFirst("Like");
deque.addFirst("Java");
System.out.println("---------get-------");
// 获取头
System.out.println(deque.getFirst()); // Java
// 获取尾
System.out.println(deque.getLast()); // I
System.out.println("--------poll-------");
// 弹出栈顶元素
System.out.println(deque.poll());
System.out.println("--------push-------");
// 从栈顶开始添加元素
deque.push("LanQiao");
System.out.println(deque.pollFirst());// LanQiao
System.out.println(deque.pollLast()); // I
deque.addFirst("LZP");
System.out.println("--------peek--------");
// 等价于peekFirst()取出队头元素,但是队列中还是存在该元素
System.out.println(deque.peek());
System.out.println(deque.peekFirst());
System.out.println(deque.peekLast());
System.out.println(deque.size()); // 2
System.out.println("=======real remove=========");
// poll才是真正的remove
System.out.println(deque.poll());
System.out.println(deque.size());// 1
}
}
ArrayDeque(数组结构)
ArrayDeque的底层是数组,不支持插入为null的元素
容量
: 没有容量限制,可根据需求自动进行扩容但会影响效率应用场景
: 作为栈使用时效率要高于Stack,作为队列来使用效率相较于LinkedList要更好一些
PriorityQueue(数组结构)
PriorityQueue
实现了Queue接口,优先队列中的元素默认按元素的比较器方法
自然排序,或者通过队列构造时提供的Comparator比较器
在队列实例化的时排序
元素特点
: 按照每层从左到右的顺序存放,不允许存入null值且不支持存储不可比较的对象优先队列的头元素
: 当我们获取队列时返回的是队列的头对象, 头元素是基于自然排序或者Comparator排序的最小元素容量
: 容量大小不受限制,在创建时可以也指定初始大小,当我们向优先队列增加元素的时候,队列大小会自动增加安全
: 非线程安全的,Java提供了实现BlockingQueue
接口的PriorityBlockingQueue
用于Java多线程环境
构造函数 | 描述 |
---|---|
PriorityQueue() | 使用默认的容量(11)创建一个优先队列 , 元素的顺序规则采用的是自然顺序 (看元素是否实现比较接口) |
PriorityQueue(int 容量) | 使用指定的容量创建一个优先队列 , 元素的顺序规则采用的是自然顺序 |
PriorityQueue(比较器) | 使用默认的容量创建一个优先队列 , 元素的顺序规则采用的是比较器 |
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});