Java语言基础(SE)-第七节 集合(Collections)

集合简介

  1. 集合(Collections)
    1. java.util 包中集合框架Collections Framework,提供了常用的数据结构:ArrayList、LinkedList、Queue、Stack、HashSet、HashMap
  2. 集合框架预览

    图1

    1. 紫色代表接口,箭头表示某个类实现了某个接口
    2. 蓝色代表抽象类,不能实例化,从下往上是父类到子类
    3. 绿色代表类,可以实例化

ArrayList

  1. 数组的局限性:无法动态扩容、操作元素的过程不够面向对象
  2. ArrayList 是 Java 中的动态数组:一个可以动态扩容的数组、封装了各种实用的数组操作
  3. ArrayList 的扩容原理
    1. ArrayList 的最小容量是 10 (可查看源码)
    2. 每次扩容时,新容量是旧容量的 1.5 倍
  4. ArrayList 的常用方法

     private int size;
     public boolean isEmpty()
     public boolean contains(Object o)
     public int indexOf(Object o)
     public int lastIndexOf(Object o) 
     public E get(int index)
     public E set(int index, E element)
     public boolean add(E e)
     public void add(int index, E element) 
     public E remove(int index)
     public boolean remove(Object o)
     public void clear()
     public boolean addAll(Collection<? extends E> c)
     public boolean addAll(int index, Collection<? extends E> c
     public boolean removeAll(Collection<?> c)
     public boolean retainAll(Collection<?> c)
     public void forEach(Consumer<? super E> action)
     public void sort(Comparator<? super E> c)
     public Object[] toArray()
     public <T> T[] toArray(T[] a)
     //根据现有数组的实际大小(现有数组的容量为1w,但是实际存了10个元素,浪费),进行缩小容量,提高性能
     public void trimToSize()
     //指定动态数组一次性扩展到多少容量,不用每次不足再动态计算,提前扩容
     public void ensureCapacity(int minCapacity)
    
  5. ArrayList 的基本使用

     ArrayList list = new ArrayList();
     list.add(11);
     list.add(false);
     list.add(null);//可以添加null值
     list.add(3.4);
     list.add(0,"jack");
     list.add('8');
     //3
     System.out.println(list.indexOf(null));
     //6
     System.out.println(list.size());
     //[jack, 11, false, null, 3.4, 8]
     System.out.println(list);
    
  6. retainAll/removeAll

     //以接口作为类型,便于扩展
     List<Integer> list1 = new ArrayList<>();
     list1.add(11);
     list1.add(22);
     list1.add(33);
     list1.add(44);
        	
     List<Integer> list2 = new ArrayList<>();
     list2.add(22);
     list2.add(44);
     //[11, 33]
     //list1.removeAll(list2);
     //从list中删掉list2除中元素以外的所有元素
     //[22, 44]
     list1.retainAll(list2);
     System.out.println(list1);
    
  7. toArray

     List<Integer> list = new ArrayList<>();
     list.add(11);
     list.add(22);
     list.add(33);
     //将ArrayList转化为普通数组,但是类型是Object的
     Object[]  array1 = list.toArray();
     //[Ljava.lang.Object;@7852e922
     System.out.println(array1);
     //[11, 22, 33]
     System.out.println(Arrays.toString(array1));
        	
     //转化为指定类型的普通数组
     Integer[] array2 = list.toArray(new Integer[0]);
     //[Ljava.lang.Integer;@4e25154f
     System.out.println(array2);
     //[11, 22, 33]
     System.out.println(Arrays.toString(array2));
    

遍历

  1. 举例

     List<Integer> list = new ArrayList<>();
     list.add(11);
     list.add(22);
     list.add(33);
     //方法1
     for (int i = 0; i < list.size(); i++) {
         System.out.println(list.get(i));
     }
     //方法2:使用迭代器
     //拿到迭代器
     Iterator it = list.iterator();
     //是否有下一个
     while (it.hasNext()) {
         Object obj = it.next();
         System.out.println(obj);
     }
     //方法3
     for (Object obj : list) {
         System.out.println(obj);
     }
     //方法4
     list.forEach((obj)->{
         System.out.println(obj);
     });
    
  2. for-each 格式

     for(元素类型 变量名 : 数组/Iterable){}
    
    1. 实现了 Iterable 接口的对象,都可以使用 for-each 格式遍历元素
      1. 比如 List、Set 等
    2. Iterable 在使用 foreach 格式遍历元素时,本质是使用了 Iterator 对象
  3. 自定义 Iterable、Iterator
    1. 如何自定义一个集合,可以使用foreach格式遍历元素

       package com.zh;
       import java.util.Iterator;
       public class ClassRoom implements Iterable<String> {
           //存放所有学生
           private String[] students;
           public ClassRoom(String... students) {
               this.students = students;
           }
           //实现接口方法Iterable,该方法要求返回一个Iterator(迭代器)接口类型的对象
           @Override
           public Iterator<String> iterator() {
               //返回一个Iterator类型的对象
               return new ClassRoomIterator();
           }
                  	
           //创建一个私有类继实现Iterator这个接口
           private class ClassRoomIterator implements Iterator<String> {
               private int index;
               //该方法用于判断当前数组是否还有下一个元素
               @Override
               public boolean hasNext() {
                   return index < students.length;
               }
               //该方法是用来返回当前数组的下一个元素
               @Override
               public String next() {
                   return students[index++];
               }
           }
       }
              
       //main函数
       ClassRoom room = new ClassRoom("jack","rose");
       //jack rose
       for (String name : room) {
           System.out.println(name);
       }
       	//方法2
       Iterator<String> it = room.iterator();
       while (it.hasNext()) {
           String obj = it.next();
           System.out.println(obj);
       }
      
  4. 遍历的注意点
    1. 需求:通过遍历的方式,挨个删除所有的元素

       List<Integer> list = new ArrayList<>();
       list.add(11);
       list.add(22);
       list.add(33);
       list.add(44);
       int size = list.size();
              
       for (int i = 0; i < size; i++) {
           //报错:java.lang.IndexOutOfBoundsException
           //list.remove(i);
       }
              	
       for (int i = 0; i < list.size(); i++) {
           list.remove(i);
       }
       //[22, 44] 注意!!!,并不能删除完全
       System.out.println(list);
              
       for (Integer e : list) {
           //报错: java.util.ConcurrentModificationException
           //list.remove(e);
       }
       list.forEach((e)->{
           //报错:java.util.ConcurrentModificationException
           list.remove(e);
       });
       Iterator<Integer> it = list.iterator();
       while (it.hasNext()) {
           //报错:java.util.ConcurrentModificationException
           list.remove(it.next());
       }
      
    2. 如果希望在遍历元素的同时删除元素
      1. 请使用 Iterator 进行遍历
      2. 然后使用 Iterator 的 remove 方法删除元素
       Iterator<Integer> it = list.iterator();
       while (it.hasNext()) {
           it.next();
           it.remove();
       }
       //[],删除成功
       System.out.println(list);
      
    3. 使用迭代器、forEach 遍历集合元素时,若使用了集合自带的方法修改集合的长度(比如 add、remove 等方法)
      1. 那么会抛出 java.util.ConcurrentModificationException 异常

ListIterator

  1. ListIterator 继承自 Iterator,在 Iterator 的基础上增加了一些功能

     boolean hasNext();
     E next();
     boolean hasPrevious();
     E previous();
     int nextIndex();
     int previousIndex();
     void remove();
     void set(E e);
     void add(E e);
    
  2. 举例使用

     ListIterator<Integer> it = list.listIterator();
     while (it.hasNext()) {
         //11 22 33
         System.out.println(it.next());
     }
     while (it.hasPrevious()) {
         //33 22 11
         System.out.println(it.previous());
     }
        
     ListIterator<Integer> it = list.listIterator();
     while (it.hasNext()) {
         it.set(it.next()+55);
     }
     //[66, 77, 88]
     System.out.println(list);
        
     while (it.hasNext()) {
         it.add(66);
         //11 22 33
         System.out.println(it.next());
         it.add(77);
     }
     //[66, 11, 77, 66, 22, 77, 66, 33, 77]
     System.out.println(list);
    

LinkedList

  1. LinkedList 是一个双向链表
    1. 实现了 List 接口
    2. API 跟 ArrayList 类似
  2. LinkedList vs ArrayList
    1. ArrayList :开辟、销毁内存空间的次数相对较少,但可能造成内存空间浪费(可以通过缩容解决)
    2. LinkedList :开辟、销毁内存空间的次数相对较多,但不会造成内存空间的浪费
    3. 如果频繁在尾部进行添加、删除操作, ArrayList、LinkedList 均可选择
    4. 如果频繁在头部进行添加、删除操作,建议选择使用 LinkedList
    5. 如果有频繁的(在任意位置)添加、删除操作,建议选择使用 LinkedList
    6. 如果有频繁的查询操作(随机访问操作),建议选择使用 ArrayList

Stack

  1. Stack,译为“栈”,只能在一端进行操作
    1. 往栈中添加元素的操作,一般叫做 push,入栈
    2. 从栈中移除元素的操作,一般叫做 pop,出栈(只能移除栈顶元素,也叫做:弹出栈顶元素)
    3. 后进先出的原则,Last In First Out,LIFO
  2. 注意: 这里说的“栈”与内存中的“栈空间”是两个不同的概念
  3. 常用方法

     public E push(E item)
     public E pop()
     public E peek() //返回栈顶元素
     public int size()
     public boolean empty()
     public int search(Object o)
    
  4. 举例使用:

     Stack<Integer> stack = new Stack();
     stack.push(11);
     stack.push(22);
     stack.push(33);
     //33
     System.out.println(stack.peek());
     //3
     System.out.println(stack.search(11));
     //33 22 11
     while (!stack.empty()) {
         System.out.println(stack.pop());
     }
    

Queue

  1. Queue,译为“队列” ,只能头尾两端进行操作
    1. 队尾(rear):只能从队尾添加元素,一般叫做 入队
    2. 队头(front):只能从队头移除元素,一般叫做 出队
    3. 先进先出的原则,First In First Out,FIFO
  2. 常用方法
    1. java.util.Queue 是一个接口,它的常用实现是 LinkedList
     boolean add(E e); //入队
     boolean offer(E e);//入队
     E remove();//出队
     E poll();//出队
     E element();//返回队头
     E peek();//返回队头
     boolean isEmpty()
     public int size()
    
  3. 举例:

     Queue<Integer> queue = new LinkedList<>();
     queue.add(11);
     queue.add(22);
     queue.add(33);
     //11
     System.out.println(queue.element());
     while (!queue.isEmpty()) {
         //11 22 33
         System.out.println(queue.remove());
     }
    

Set

HashSet

  1. 跟ArrayList一样,但是也有区别:
    1. 元素是无序的,不能通过索引来访问元素
    2. 不能有重复的元素,可以去重复
     Set<String> set = new HashSet<>();
     set.add("Jack");
     set.add("Rose");
     set.add("Jim");
     set.add("Jack");
     set.add("Kate");
     //4
     System.out.println(set.size());
     //[Kate, Rose, Jack, Jim]
     System.out.println(set);
     set.remove("Rose");
     //[Kate, Jack, Jim]
     System.out.println(set);
    

LinkedHashSet

  1. LinkedHashSet 在 HashSet 的基础上,记录了元素的添加顺序
  2. 就是添加的顺序是什么,在容器里面的元素顺序就是什么

TreeSet

  1. TreeSet 要求元素必须具备可比较性,默认按照从小到大的顺序遍历元素

     Set<String> set = new TreeSet<>();
     set.add("Jack");
     set.add("Rose");
     set.add("Jim");
     set.add("Kate");
     set.add("Rose");
     set.add("Larry");
     //[Jack, Jim, Kate, Larry, Rose]
     System.out.println(set);
     for (String str : set) {
         System.out.println(str);
     }//Jack Jim Kate Larry Rose
    
  2. 自定义比较方式

     //自定义比较方式
     Set<Integer> set = new TreeSet<>((i1,i2) -> i2 - i1);
     set.add(33);
     set.add(11);
     set.add(55);
     set.add(22);
     set.add(44);
        	
     //[55, 44, 33, 22, 11]
     System.out.println(set);
     for (Integer i : set) {
         System.out.println(i);
     }//55 44 33 22 11
    

Map

HashMap

  1. HashMap 存储的是键值对(key-value),Map 译为“映射”,有些编程语言中叫做“字典”
Map<String,Integer> map = new HashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 22);
map.put("Jack", 33);
map.put("Kate", 11);
//4
System.out.println(map.size());
//33
System.out.println(map.get("Jack"));
//{Kate=11, Rose=22, Jack=33, Jim=22}
System.out.println(map);
map.remove("Rose");
//{Kate=11, Jack=33, Jim=22}
System.out.println(map);
//遍历
Set<String> keys = map.keySet();
for (String key : keys) {
    //先遍历key,又通过key去找到value(整体效率低)
    System.out.println(key + "=" + map.get(key));
}//Kate=11 Jack=33 Jim=22
//只能遍历value
Collection<Integer>  values = map.values();
for (Integer value : values) {
    System.out.println(value);
}//11 33 22
	
//推荐
//一个Entry对象代表一个键值对
Set<Entry<String,Integer>> entries =map.entrySet();
for (Entry<String, Integer> entry : entries) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}//Kate=11 Jack=33 Jim=22
//推荐
map.forEach((key,value)->{
    System.out.println(key + "=" + value);
});//Kate=11 Jack=33 Jim=22

LinkedHashMap

  1. LinkedHashMap 在 HashMap 的基础上,记录了元素的添加顺序
Map<String,Integer> map = new LinkedHashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
map.put("Kate", 44);
//{Jack=11, Rose=22, Jim=33, Kate=44}
System.out.println(map);
map.forEach((k,v)->{
    System.out.println(k + "=" + v);
});//Jack=11 Rose=22 Jim=33 Kate=44
map.remove("Rose");
//{Jack=11, Jim=33, Kate=44}
System.out.println(map);

TreeMap

  1. TreeMap 要求 key 必须具备可比较性,默认按照从小到大的顺序遍历 key
Map<String,Integer> map = new TreeMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
map.put("Kate", 44);
//{Jack=11, Jim=33, Kate=44, Rose=22}
System.out.println(map);
map.forEach((k,v)->{
    System.out.println(k + "=" + v);
});//Jack=11 Jim=33 Kate=44 Rose=22

List vs Set vs Map

  1. List 的特点
    1. 可以存储重复的元素
    2. 可以通过索引访问元素
  2. Set 的特点
    1. 不可以存储重复的元素
    2. 不可以通过索引访问元素
  3. Map 的特点
    1. 不可以存储重复的 key,可以存储重复的 value
    2. 不可以通过索引访问 key-value
  4. Set 的底层是基于 Map 实现的
    1. HashSet 底层用了 HashMap
    2. LinkedHashSet 底层用了 LinkedHashMap
    3. TreeSet 底层用了TreeMap

java.util.Collections

  1. java.util.Collections 是一个常用的集合工具类,提供了很多实用的静态方法
  2. 类似于Arrays,Arrays是针对数组的工具类
  3. JDK中凡是复数(Arrays)形式的类型,基本都是工具类
List<Integer> list = new ArrayList<>();
//将元素添加到list中
Collections.addAll(list, 45,11,22);
Table of Contents