Java之集合

Java之集合

  • Java提供了一套相当完整的容器类,其中基本的类型时List、Set、Queue和Map。
  • Java容器的作用是保存对象,分为两种:
    • ①Collection:一个独立元素的序列,这些元素都服从一条或多条规则。所有的Connection都可以for-each循环遍历。
      • List:用来存储有序的元素序列。
        • ArrayList:基于数组的实现,可以通过索引(下标)快速访问元素,但插入、删除元素时需要移动其他元素,效率较低。
        • LinkedList:基于链表的实现,在插入、删除元素时只需要调整相邻元素的指针即可,效率较高,但访问元素时需要遍历整个链表,效率较低。
      • Set:不能有重复的元素。
        • HashSet:无序,使用哈希表来存储元素,具有很快的插入、删除和查找的速度。但是对于需要有序的操作如遍历,需要将元素拷贝到一个List集合中或使用TreeSet。
        • TreeSet:有序,它按元素的自然顺序或Comparator的比较结果的升序排列。使用红黑树存储元素,查找、插入和删除的时间复杂的都是O(logn),它可以进行有序的遍历操作。
        • LinkedHashSet:有序,它按照被添加的顺序保存对象。它继承自HashSet,内部使用链表维护元素的插入顺序。在遍历时,它可以按照插入的顺序进行迭代,同时还具有HashSet的高性能。
      • Queue:按照排队规则来确定对象的顺序(与它们被插入的顺序相同)。
        • PriorityQueue:基于优先级的队列,每个元素有一个优先级,优先级高的先出队。使用堆(Heap)实现,可以快速定位队列中的最大最小值,插入和删除的时间复杂度为O(logn)。
    • ②Map:一组成对的“键值对”对象,允许使用键来查找值,就像字典一样。
      • HashMap:无序,基于哈希表实现,但提供了最快的查找和插入技术。
      • TreeMap:有序,它按照比较结果的升序保存键。基于红黑树实现。
      • LinkedHashMap:它按照插入顺序保存键,基于哈希表和双向链表实现,保留了HashMap的查询速度。
  • Java集合提供了重写了toString()方法,因此直接打印即可。
  • 集合使用总结:
    • ①List:ArrayList适合插入删除少、频繁访问元素的场景,LinkedList适合插入删除多、访问元素次数少的场景。
    • ②HashSet适合需要快速的查找和插入操作的场景,TreeSet适合需要有序的集合的场景,LinkedHashSet适合需要维护元素的插入顺序场景。
    • ④HashMap适合需要快速插入和查找元素,并且不关心元素的顺序的场景,TreeMap适合需要按照键的顺序来遍历元素的场景,LinkedHashMap适合需要保留元素插入的顺序,但同时也要快速地进行元素查找和插入的场景。

List

ArrayList:

  • 使用:List dogs = new ArrayList<>();。这里ArrayList向上转型为List。
  • 尖括号括起来的是类型参数,可以有多个,它制定了这个容器实例可以保存的类型。
  • 通过泛型,就可以在编译期防止将错误类型的对象放置到容器中。
  • 当指定了泛型类型后,你可以将该类型的对象放入其中,当从列表中取出的对象的类型就是你指定的类型,就不用再进行类型转换了。
  • 当指定了某个类型作为泛型参数时,并不仅限于只能将该类型的对象放置到容器中,向上转型也可以像作用于其他类型一样作用于泛型。
    •  //动物
       abstract class Animal {}
      
       //陆生动物
       abstract class TerrestrialAnimal extends Animal {}
       //水生动物
       abstract class AquaticAnimal extends Animal {}
       //两栖动物
       abstract class AmphibiousAnimal extends Animal {}
      
       //鸟类
       class Bird extends TerrestrialAnimal {}
       //犬类
       class Dog extends TerrestrialAnimal {}
       //鱼类
       class Fish extends AquaticAnimal {}
       //蛙类
       class Frog extends AmphibiousAnimal {}
      
       //哈士奇
       class Husky extends Dog {}
       //鲤鱼
       class Carp extends Fish {}
       //鲨鱼
       class Shark extends Fish {}
      
       class Test {
       	public static void main(String[] args) {
      			ArrayList<Animal> animals = new ArrayList<>();
      			animals.add(new Bird());
      			animals.add(new Dog());
      			animals.add(new Fish());
      			animals.add(new Frog());
      			animals.add(new Husky());
      			animals.add(new Carp());
      			animals.add(new Shark());
      			for (Animal animal : animals) {
          			System.out.println(animal);
      			}
       	}
       }
       
       输出:
       com.eos.javalearn.Bird@39ed3c8d
       com.eos.javalearn.Dog@71dac704
       com.eos.javalearn.Fish@123772c4
       com.eos.javalearn.Frog@2d363fb3
      
  • java.util.Arrays.asList()方法接收一个数组或是一个用逗号分隔的元素列表(原理是使用可变参数…),并将其转换成一个List对象。
    • 使用这种方法创建的List是不可以向其中添加元素或从中删除元素的,因为其底层使用的是数组。
    •  int[] i = {1, 2, 3};
      	List<int[]> a = Arrays.asList(i);
      	List<Integer> b = Arrays.asList(1, 2, 3);
      	Integer[] i1 = {1, 2, 3};
      	List<Integer> c = Arrays.asList(i1);
      	//会报错
      	//c.remove(1);
      	//c.add(1);
      	List<Integer> d = new ArrayList<>();
      	//没问题
      	d.add(1);
      	d.remove(0);
      
    • 如果Arrays.asList()中只有TerrestrialAnimal类型时,需要将List的泛型指定为TerrestrialAnimal,否则会报错,如果要解决这个问题,就需要进行显式类型参数说明。
      • 其实就是找到这些类型公共的且离它们最近的父类型作为泛型的类型。
      •  //会报错,因为它们最近的公共基类为TerrestrialAnimal,而不是Animal
         //List animals1 = Arrays.asList(new Dog(), new Bird());
         //没问题
         List<TerrestrialAnimal> animals1 = Arrays.asList(new Dog(), new Bird());
         //它们最近的公共基类为Animal
         List<Animal> animals2 = Arrays.asList(new Dog(), new Bird(), new Carp());
         //显式类型参数说明
         List<Animal> animals3 = Arrays.<Animal>asList(new Dog(), new Bird());
        
  • Collections的addAll()方法接收一个Collection对象,以及一个数组或是一个用逗号分隔的元素列表,将元素添加到Collection中。
    • 对于Arrays.asList()出现的问题,在这里就不会发生。因为它从第一个参数中已经知道了目标类型是什么。
      •  List<Animal> animals4 = new ArrayList<>();
         Collections.addAll(animals4, new Dog(), new Bird());
        
  • 除了以上两种初始化方式外,还可以使用双括号进行初始化:
    • 这种方式需要使用两个花括号,第一个花括号用于创建一个匿名内部类来扩展List接口,第二个花括号用于添加初始元素。
    • 注意:这种方式创建的列表是可变的。但是由于它创建了一个匿名内部类,因此可能会对性能产生影响。
    •  ArrayList<String> strings = new ArrayList<String>() {
           {
               add("1");
               add("2");
               add("3");
           }    
       };
      
  • Collections常用方法:
    • 除了上面说的addAll()方法以外,还有一些其他常用的方法。
      • sort():接收一个集合,对其进行排序。使用的是归并排序算法。这里并不关心指定集合中元素的顺序,只关心存不存在。
      • shuffle():接收一个集合,用于随机打乱集合中元素的顺序。使用了伪随机算法。
      • shuffle():接收一个集合和一个Random对象,用于随机打乱集合中元素的顺序。Random对象用来指定随机化算法。
  • ArrayList常用方法:
    • 增:
      • add(Object):添加一个元素。
      • add(int, Object):在指定的下标插入一个元素。
      • addAll(int, Collection):从指定下标开始插入指定集合中的元素,原来的元素向后挪。
    • 删:
      • remove(Object):删除指定的元素。如果集合中不存在该元素不会有任何响应,如果存在多个则只会删除第一个,返回是否删除成功。
      • remove(int):删除指定下标的元素,如果下标越界则会报数组越界异常,返回是否删除成功。
      • removeAll(Collection):删除指定集合中的元素,如果某个元素存在多个则只会删除第一个。
    • 改:
      • set(int, Object):将指定下标的元素修改为新元素。
    • 查:
      • get(int):通过下标获取元素。
    • 其他:
      • contains(Object):检查列表中是否包含指定的元素。
      • containsAll(Collection):检查列表中是否包含指定集合中的所有元素,
      • indexOf(Object):获取指定元素的下标。
      • subList(int, int):从原集合中根据指定的下标区间获取元素并返回一个新的集合(即,获取子集合)。区间左闭右开,如subList(1, 4)表示区间[1, 4)的元素。
      • retainAll(Collection):用于保留集合中与指定集合中相同的元素,即保留两个集合的交集。
      • isEmpty():检查集合中的元素个数是否为0。
      • clear():移除集合中所有的元素。
      • toArray():将列表转换成一个对象数组。
      • toArray(T[]):将集合中的元素转换成指定类型的数组,如果指定的数组容量小于集合的元素数量,那么会创建一个新的数组,新数组的大小就是集合元素的个数,如果指定的数组容量大于集合的元素数量,那么数组中的剩余元素将被设置为 null。如果指定类型的数组的类型与集合中元素的类型不能向上或向下转型就会报错,这和上面Arrays.asList()方法出现的错一样。
  • 注:这些方法或多或少会受到equals方法的影响,List可能会因为equals方法的行为不同而表现出不同行为。

LinkedList

  • LinkedList在插入、删除元素时更高效一点,但是在随机访问上要差一些。
  • LinkedList添加了将其用作栈(Stack)、队列(Queue)或双端队列(Deque)的方法。

Stack

  • 栈是一种后进先出(LIFO)的容器。
  • LinkedList具有能够直接实现栈的所有功能的方法。
  • 它的操作方法包括(前4个是基本操作):
    • push:向栈顶添加一个元素,即入栈。
    • pop:从栈顶移除一个元素,并返回该元素的值,即出栈。
    • top/peek:返回栈顶元素的值,但不移除该元素。
    • isEmpty:判断栈是否为空。
    • isFull:判断栈是否已满(对于固定大小的栈,有该方法)。
    • size:返回栈中元素的数量。
    • clear:清空栈中的所有元素。
    • search:查找某个元素在栈中的位置,并返回距离栈顶的距离。如果元素不存在于栈中,返回-1。
  • 由于java.util.Stack太老而且它设计的不太好,所以一般我们不用它,而是用LinkedList。同时,我们可以自己使用LinkedList封装一个栈:
    •  class Stack<T> {
       	private LinkedList<T> linkedList = new LinkedList<>();
      
       	public void push(T v) {
       		linkedList.push(v);
       	}
      
       	public T pop() {
       		return linkedList.pop();
       	}
      
       	public T peek() {
       		return linkedList.peek();
       	}
      
       	public boolean isEmpty() {
       		return linkedList.isEmpty();
       	}
      
       	@Override
       	public String toString() {
       		return linkedList.toString();
       	}
       }
       
       使用:
       Stack<Integer> stack = new Stack<>();
       for (int i = 0; i < 5; i++) {
           stack.push(i);
       }
       System.out.println(stack);
       
       输出:
       [4, 3, 2, 1, 0]
      
    • 这里我们定义了一个可以持有T类型对象的Stack,在类被使用时,T将会被实际的类型替换。

Queue

  • 队列是一种先进先出(FIFO)的容器。
  • LinkedList具有能够直接实现队列的所有功能的方法。
  • 常用方法:
    • offer(E e):将元素插入到队列尾部,如果队列满了,返回false。
    • add(E e):将元素插入到队列尾部,如果队列满了,抛出异常。
    • poll():获取并删除队列头部元素,如果队列为空,则返回null。
    • remove():获取并删除队列头部元素,如果队列为空,则抛出异常。
    • peek():获取队列头部元素,但是不删除,如果队列为空,则返回null。
    • element():获取队列头部元素,但是不删除,如果队列为空,则抛出异常。
    • size():获取队列中元素的个数。
    • isEmpty():判断队列是否为空。
    •  Queue<Integer> queue = new LinkedList<>();
       for (int i = 0; i < 5; i++) {
           if (i < 3) {
               queue.add(i);
           } else {
               queue.offer(i);
           }
       }
       System.out.println(queue);
       System.out.println("peek方法获取队头元素:" + queue.peek());
       System.out.println("element方法获取队头元素:" + queue.element());
       System.out.println("poll方法移除队头元素" + queue.poll());
       System.out.println(queue);
       System.out.println("remove方法移除队头元素" + queue.remove());
       System.out.println(queue);
       
       输出:
       [0, 1, 2, 3, 4]
       peek方法获取队头元素:0
       element方法获取队头元素:0
       poll方法移除队头元素0
       [1, 2, 3, 4]
       remove方法移除队头元素1
       [2, 3, 4]
      

PriorityQueue

  • PriorityQueue是一个优先队列。
  • 操作方法:
    • add(E e):添加一个元素到队列中。
    • offer(E e):添加一个元素到队列中,如果队列已满,则返回false。
    • remove():移除并返回队列头部的元素,如果队列为空,则抛出异常。
    • poll():移除并返回队列头部的元素,如果队列为空,则返回null。
    • element():返回队列头部的元素但不移除,如果队列为空,则抛出异常。
    • peek():返回队列头部的元素但不移除,如果队列为空,则返回null。
    • size():返回队列中元素的个数。
    • isEmpty():判断队列是否为空。
    •  PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
       for (int i = 0; i < 10; i++) {
           Random random = new Random();
           priorityQueue.add(random.nextInt(100) + 1);
       }
       System.out.println(priorityQueue);
       
       输出:
       [1, 4, 32, 50, 63, 71, 51, 86, 91, 72]
      

Deque

  • 双端队列是一种具有队列和栈的特性的数据结构,可以在队列两端进行插入和删除。
  • 常用方法:
    • addFirst(E e):在队列头部插入元素。
    • addLast(E e):在队列尾部插入元素。
    • offerFirst(E e):在队列头部插入元素,如果队列满了,返回false。
    • offerLast(E e):在队列尾部插入元素,如果队列满了,返回false。
    • removeFirst():删除并获取队列头部元素,如果队列为空,则抛出异常。
    • removeLast():删除并获取队列尾部元素,如果队列为空,则抛出异常。
    • pollFirst():删除并获取队列头部元素,如果队列为空,则返回null。
    • pollLast():删除并获取队列尾部元素,如果队列为空,则返回null。
    • getFirst():获取队列头部元素,但是不删除,如果队列为空,则抛出异常。
    • getLast():获取队列尾部元素,但是不删除,如果队列为空,则抛出异常。
    • peekFirst():获取队列头部元素,但是不删除,如果队列为空,则返回null。
    • peekLast():获取队列尾部元素,但是不删除,如果队列为空,则返回null。
    • size():获取队列中元素的个数。
    • isEmpty():判断队列是否为空。

Set

  • Set不保存重复的元素。
  • 常用方法:
    • add(Object):向Set中添加元素。
    • addAll(Set):向Set中添加指定集合中的元素。
    • contains(Object):检查Set中是否包含指定元素。
    • containsAll(Set):检查Set中是否包含指定Set中的元素。
    • remove(Object):从Set中移除指定元素。
    • removeAll(Set):从Set中移除指定Set中的元素。
    •  Set<Integer> set = new HashSet();
       for (int i = 0; i < 5; i++) {
           set.add(i);
       }
       System.out.println(set);
       set.addAll(Arrays.asList(100, 200, 300));
       System.out.println(set);
       System.out.println("集合中是否包含100:" + set.contains(400));
       boolean is = set.containsAll(Arrays.asList(100, 200, 300));
       System.out.println("集合中是否包含[100, 200, 300]:" + is);
       set.remove(2);
       System.out.println(set);
       set.removeAll(Arrays.asList(100, 200, 300));
       System.out.println(set);
       
       输出:
       [0, 1, 2, 3, 4]
       [0, 1, 2, 3, 4, 100, 200, 300]
       集合中是否包含100false
       集合中是否包含[100, 200, 300]true
       [0, 1, 3, 4, 100, 200, 300]
       [0, 1, 3, 4]
      

Map

  • 由于需要根据键来查找值,所以键值必须唯一,但是值可以重复。
  • 常用方法:
    • put(Object, Object):向Map中添加一个元素键值对。
    • get(Object):通过键查找值。
    • containsKey(Object):查询Map中是否包含指定的键。
    • containsValue(Object):查询Map中是否包含指定的值。
    •  Map<Integer, Integer> map = new HashMap<>();
       for (int i = 0; i < 5; i++) {
           map.put(i, i);
       }
       System.out.println(map);
       System.out.println("键1对应的值为:" + map.get(1));
       System.out.println("是否包含键9:" + map.containsKey(9));
       System.out.println("是否包含值2:" + map.containsValue(2));
      	
       输出:
       {0=0, 1=1, 2=2, 3=3, 4=4}1对应的值为:1
       是否包含键9false
       是否包含值2true
      
    • Map的键和值还可以设置为其他Collection,因此可扩展的用法很多。
      •  Map<Dog, List<String>> dogListMap = new HashMap<>();
         Dog dog1 = new Dog("lala");
         List<String> names1 = new ArrayList<>(Arrays.asList("la", "xiao la"));
         Dog dog2 = new Dog("wangwang");
         List<String> names2 = new ArrayList<>(Arrays.asList("wang", "xiao wang"));
         dogListMap.put(dog1, names1);
         dogListMap.put(dog2, names2);
         System.out.println(dogListMap);
         Set<Dog> dogs = dogListMap.keySet();
         System.out.println(dogs);
         Collection<List<String>> values = dogListMap.values();
         System.out.println(values);
         
         输出:
         {lala=[la, xiao la], wangwang=[wang, xiao wang]}
         [lala, wangwang]
         [[la, xiao la], [wang, xiao wang]]
        

Iterator迭代器

  • 迭代器是一个对象,用来遍历并选择集合中的元素。
  • 迭代器通常被称为轻量级对象,因为创建它的代价很小。
  • Java的迭代器只能单向移动。
  • 常用方法:
    • 使用容器的iterator()方法返回一个Iterator对象。
    • 使用Iterator对象的next()方法获得序列中的下一个元素。
    • 使用Iterator对象的hasNext()方法检查序列中是否还有元素。
    • 使用Iterator对象的remove()方法将迭代器当前返回的元素删除。调用remove()前必须先调用next()。
    •  List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
       Iterator<Integer> iterator = list1.iterator();
       while (iterator.hasNext()) {
           Integer integer = iterator.next();
           System.out.println("integer = " + integer);
       }
       iterator = list1.iterator();
       for (int i = 0;i < 3 && iterator.hasNext();i++) {
           iterator.next();
           iterator.remove();
       }
       System.out.println("删除后:" + list1);
       
       输出:
       integer = 1
       integer = 2
       integer = 3
       integer = 4
       integer = 5
       删除后:[4, 5]
      
  • ListIterator:
    • 它只能用于各种List的访问。
    • Iterator只能单向,但ListIterator可以双向移动。
    • 常用方法:
      • 使用容器的listIterator()方法返回一个ListIterator对象,或使用listIterator(int)方法创建一个一开始就指向列表索引为x的元素的ListIterator对象。
      • hasNext():是否有下一个元素;
      • next():获得下一个元素;
      • hasPrevious():是否有前一个元素;
      • previous():获得前一个元素;
      • previousIndex():前一个元素的下标;
      • nextIndex():当前元素的下一个元素的下标,与next()获得的元素不是同一个元素;
      • remove():将迭代器当前返回的元素删除。调用remove()前必须先调用next()或previous();
      • set():修改迭代器当前返回的元素,调用set()前必须先调用next()或previous()。
      • add():在当前指向出插入一个元素。不必关心next()或previous()。
        •  List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
           ListIterator<Integer> integerListIterator = list1.listIterator();
           while (integerListIterator.hasNext()) {
           	int i = integerListIterator.previousIndex();
           	boolean has = integerListIterator.hasPrevious();
           	Integer val1 = null;
           	if (has) {
           		val1 = integerListIterator.previous();
           		integerListIterator.next();
           	}
           	Integer val2 = integerListIterator.next();
           	int j = 0;
           	if (integerListIterator.hasNext()) {
           		j = integerListIterator.nextIndex();
           	}
           	System.out.println("是否有前一个元素" + has  + ", 前一个元素的索引:" + i + ", 元素为:" + val1 + ", 当前元素为:" + val2 + ", 后一个元素的索引:" + j);
           }
           integerListIterator = list1.listIterator(3);
           while (integerListIterator.hasNext()) {
           	integerListIterator.next();
           	integerListIterator.set(1);
           }
           System.out.println(list1);
           
           输出:
           是否有前一个元素false, 前一个元素的索引:-1, 元素为:null, 当前元素为:1, 后一个元素的索引:1
           是否有前一个元素true, 前一个元素的索引:0, 元素为:1, 当前元素为:2, 后一个元素的索引:2
           是否有前一个元素true, 前一个元素的索引:1, 元素为:2, 当前元素为:3, 后一个元素的索引:3
           是否有前一个元素true, 前一个元素的索引:2, 元素为:3, 当前元素为:4, 后一个元素的索引:4
           是否有前一个元素true, 前一个元素的索引:3, 元素为:4, 当前元素为:5, 后一个元素的索引:0
           [1, 2, 3, 1, 1]
          

你可能感兴趣的:(java,数据结构,算法)