01-Java集合之单向队列,如Collection接口,List接口,Set接口,Queue接口及其实现类的底层结构和特点

单列集合

特点

单列集合分为三大类

  • 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 CollectionTest01 {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); // trueboolean 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 CollectionTest04 {public static void main(String[] args) {// 创建集合对象Collection c = new ArrayList();// 向集合中存储元素String s1 = new String("abc"); // s1 = 0x99c.add(s1); String s2 = new String("def"); // s2 = 0x77c.add(s2);// 新建的对象StringString x = new String("abc"); // x = 0x94// 虽然c集合不包含x对象的内存地址,但是调用equals方法时会判断它和s1相等,等价于集合中包含xSystem.out.println(c.contains(x)); //true}
}

remove方法原理: 当需要删除集合中对应的元素时,底层是拿着该元素并调用它的equals方法与集合中的元素一个一个进行比对判断是否相等,相等则删除

public class CollectionTest05 {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是等价的所以会删除u1c.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/foreach遍历集合

通用方式: 先获取集合对象的迭代器对象Iterator,通过获取的迭代器对象开始迭代/遍历集合,foreach底层对集合或数组的遍历也是利用的迭代器原理

集合获取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 CollectionTest06 {public static void main(String[] args) {Collection c = new ArrayList();// 创建集合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}
}

List接口的实现类

List类型的集合存储元素特点就是有序可重复

  • 有序: 元素有下标并且从0开始以1递增, 因为有下标所以List集合可以通过下标遍历集合
  • 可重复:可以重复存储同一个元素
  • 元素特点: 存放在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 LinkedList();//List myList = new Vector();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 Cfor(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 Dfor(int i = 0; i < myList.size(); i++){Object obj = myList.get(i);System.out.println(obj);}}
}

ArrayList集合(数组)

ArrayList集合没有指定泛型前底层是一个Object[]数组,执行List接口通过指定下标位置元素进行添加或删除的方法时效率较低,因为涉及到数组中元素的位移

  • 集合扩容: 增长到原容量的1.5倍, 数组扩容效率比较低建议在创建ArrayList集合的时候给定一个初始化容量
  • 检索效率最高: 数组的每个元素占用空间大小相同且内存地址是连续的, 可以通过首元素内存地址和指定位置的下标快速定位到指定元素的内存地址
  • 随机增删元素效率比较低: 向数组末尾添加元素效率很高,集合中可以添加多个null
  • 数据存储: 无法存储大数据量的数据,因为很难找到一块非常巨大的连续的内存空间
  • 安全: 非线程安全
构造方法功能
ArrayList()默认数组的初始化容量为10,底层是先创建了一个长度为0的数组,只有当添加了第一个元素时才会初始化数组容量为10
ArrayList(int)指定数组的初始化容量
ArrayList(Collection)将Collection类型的集合转换成ArrayList类型的集合,这样就可以使用List集合特有的遍历方式
public class ArrayListTest02 {public static void main(String[] args) {// 默认数组的初始化容量为10List myList1 = new ArrayList();// 指定数组的初始化容量为100List 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集合效率较低所以使用较少,因为我们可以使用集合工具类Collections将一个线程不安全的ArrayList集合转换成线程安全的

  • 集合扩容: Vector集合的底层数组每次扩容之后是原容量的2倍

  • 安全: 线程安全,所有方法都带有synchronized关键字即是线程同步的,但是由于执行效率太低已被弃用

public class VectorTest {public static void main(String[] args) {// 创建一个Vector集合List vector = new Vector();//Vector vector = new Vector();// 添加元素 , 默认初始化容量10个。满了之后扩容(扩容之后的容量是20)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集合就是线程安全的了。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;elsel.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) {// HashSet集合特点: 无序不可重复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<>();// 添加Stringts.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 , Ideque.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());// LanQiaoSystem.out.println(deque.pollLast()); // Ideque.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()); // 2System.out.println("=======real remove=========");// poll才是真正的removeSystem.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>() {@Overridepublic int compare(Integer o1, Integer o2) {return o2 - o1;}
});