一文掌握常见常用Java集合框架

掌握常见常用Java集合框架

说到集合框架,下面这张图一定经常会看见

在这里插入图片描述

初看这副图,你可能会觉得眼花缭乱,问题不大,本文这就带你去了解这副图。

1.整体感知
  • 从图中可以看出,集合框架主要分为两个类型,CollectionMap , Collection 是一个存储一系列单个对象的容器,Map 是一个图,可以存储 一系列键值对。Collection 有三个子接口 ListSet, Queue

  • 所以集合框架有四种具体的类型:Map, List, Set, Queue

  • List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储键值对(key-value)的集合,可根据元素的key来访问value。

2.顶层接口

Iterator Iterable ListIterator

  • 先来看我们经常用到的 Iterator 接口 和它的子接口 ListIterator

    iterator

​ Iterator 有三个主要方法

  1. hasNext() : 检测集合是否还有下一个元素

  2. next() : 返回迭代器的下一个元素,并更新迭代器的游标(类似指针)

  3. remove() : 将迭代器返回的元素删除

    下面来通过例子来观察一下

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(89);
        list.add(39);
        list.add(29);
        list.add(19);
        list.add(29);
    
        Iterator<Integer> it = list.iterator();  //获取迭代器
        while(it.hasNext()){
            Integer next = it.next();
            System.out.print(next + " ");
        }
    
        Iterator<Integer> it2 = list.iterator();
        while(it2.hasNext()){
            if(it2.next() < 30){
                it2.remove();
            }
        }
        System.out.println();
        Iterator<Integer> it3 = list.iterator();  //获取迭代器
        while(it3.hasNext()){
            Integer next = it3.next();
            System.out.print(next + " ");
        }
    }
    

    输出结果为

    89 39 29 19 29 
    89 39
    

    下面解释一下运行的过程,新建一个ArrayList集合,往里面添加了5个元素,第一次获取迭代器,遍历了集合,第二次获取迭代器,把集合里小于30的数删除,第三次再获取迭代器,再次遍历集合,发现小于30的数都被删除掉了。

  • 下面来看一下ListIterator 接口,具体可以参考:Java 集合中关于Iterator 和ListIterator的详解

    • (1)ListIterator有**add()方法,可以向List中添加对象,而Iterator不能
      (2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有
      hasPrevious()previous()**方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
      (3)ListIterator可以定位当前的索引位置,**nextIndex()previousIndex()**可以实现。Iterator没有此功能。
      (4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。

    • 最后来看一下Iterable接口

      public interface Iterable<T> {
          Iterator<T> iterator();
          /**
           * @since 1.8
           */
          default void forEach(Consumer<? super T> action) {
              Objects.requireNonNull(action);
              for (T t : this) {
                  action.accept(t);
              }
          }
          default Spliterator<T> spliterator() {
              return Spliterators.spliteratorUnknownSize(iterator(), 0);
          }
      }
      

      可以看到其实里面也提供了Iterator接口,在JDK1.8之后,iterable提供foreach遍历,也就是我们经常用的增强for循环。但究其本质,增强for循环其实底层还是用迭代器遍历。

  • 最后来小结一波

    • Iterator 是最常用的迭代器接口,主要提供了三个方法供我们去操作集合,hasNext(), next(), remove()。

    • ListIterator则可以称的上是Iterator的增强版,但它只可以对list集合操作,在Iterator的基础上,它还提供了add()方法,hasPrevious() 和 previous() 供我们后续遍历list集合,它还可以定位一个元素的索引和修改元素。

    • Iterable则提供了foreach遍历集合的方法。

    3.Map集合

    Map存储的是键值对<key, value> 形式的值, 每个key对应唯一一个value, 所以Map不可以有重复的key值,但存储重复的key值时,会将后来的 value 值覆盖掉原来的value值。也就是这个原因,作为key值得元素都必须重写hashcode()和equals()方法。

    • HashMap 和 HashTable

      HashMap是最常用的一个实现类,当存入元素时,会将key的hashcode转化为数组的索引放入对应的数组位置,查找时以同样的方式查找。

      HashTable一般都用不到了,操作方法和HashMap差不多,但性能比HashMap差,主要是底层实现导致的。HashTable有一个子类叫properties,是一个key和value都是String类型得Map,主要用于读取配置文件。

      两者的区别:

      1. HashTable是线程安全的,HashMap是线程不安全的, HashTable底层实现的时候加了synchronized关键字。
      2. 底层实现时,HashTable是数组+链表,HashMap是数组+链表+红黑树, 具体是当链表长度大于8时会转为红黑树,因为这样查询效率会比原来快。
      3. HashMap可以用null作为key,而HashTable不可以
    • LinkedHashMap

      LinkedHashMap是HashMap的子类,它内部有一条双向链表来维护键值对的次序,维护了Map的迭代顺序,与插入顺序一致,具体可以用来实现LRU缓存策略。

    • TreeMap

      TreeMap有排序的功能,底层是数组+红黑树实现的,每一个键值对是一个树节点,默认按key值排序,因此key值必须实现Comparable接口。迭代的时候输出就是按照key值得默认顺序输出。

      下面来例子演示一下

      public static void main(String[] args) {
          Map<String, Integer> hashmap = new HashMap<>();
          hashmap.put("Messi",6);
          hashmap.put("Ronaldo",5);
          hashmap.put("Kaka",1);
          hashmap.put("Modric",1);
          hashmap.put("Lingard",100);
          System.out.println("用HashMap:   ");
          Set<Map.Entry<String, Integer>> set1 = hashmap.entrySet();
          for(Map.Entry<String, Integer> s: set1){
              System.out.println(s.getKey() +" "+s.getValue());
          }
      
          Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
          linkedHashMap.put("Messi",6);
          linkedHashMap.put("Ronaldo",5);
          linkedHashMap.put("Kaka",1);
          linkedHashMap.put("Modric",1);
          linkedHashMap.put("Lingard",100);
          System.out.println("用LinkedHashMap: ");
          Set<Map.Entry<String, Integer>> set2 = linkedHashMap.entrySet();
          for(Map.Entry<String, Integer> s: set2){
              System.out.println(s.getKey() +" "+s.getValue());
          }
      
          Map<String,Integer> treeMap = new TreeMap<>();
          treeMap.put("Messi",6);
          treeMap.put("Ronaldo",5);
          treeMap.put("Kaka",1);
          treeMap.put("Modric",1);
          treeMap.put("Lingard",100);
          System.out.println("用TreeMap: ");
          Set<Map.Entry<String, Integer>> set3 = treeMap.entrySet();
          for(Map.Entry<String, Integer> s: set3) {
              System.out.println(s.getKey() + " " + s.getValue());
          }
      }
      
      用HashMap:   
      Ronaldo 5
      Lingard 100
      Modric 1
      Messi 6
      Kaka 1
      用LinkedHashMap: 
      Messi 6
      Ronaldo 5
      Kaka 1
      Modric 1
      Lingard 100
      用TreeMap: 
      Kaka 1
      Lingard 100
      Messi 6
      Modric 1
      Ronaldo 5
      

      用HashMap会使遍历得时候变得无序,每次遍历得时候可能都会不一样,用LinkedHashMap

      则严格按照添加顺序输出, 用TreeMap时则会将key值排序在输出。

    4.Set集合

    Set集合就是存储一系列不重复得元素,和Map有点像,就是少了value。

    Set集合和基本上差不多,有几个具体得实现类,

    HashSet,是基于HashMap 实现的,

    ** LinkedHashSet ,是基于LinkedHashMap实现的, **

    TreeSet,是基于TreeMap实现的

    操作除了没了value值,和Map差不多,存储的元素都要重写hashcode()和equals()方法。

    5.List集合

    常见的实现有ArrayList, LinkedList, Vector, Stack,

    先说一下Vector和Stack, Stack是vector 的子类,它们都是线程安全的,也正是因为这一点,使得它们现在已经过时了。

    • 现在主要来说一下ArrayList和LinkedList

    ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

在这里插入图片描述

ArrayList 继承了 AbstractList ,并实现了 List 接口。

ArrayList 新建时默认容量为10,如果快达到了容量,就会有扩容机制将容量扩大到原来的1.5倍,所以如果我们创建ArrayList时知道要存储大小时最好指定一下大小,避免不断扩容而增大开销

LinkedList类似于 ArrayList,底层是用链表实现的,并实现了List, Deque, Cloneable, Serializable

接口,实现了Deque接口,说明Deque可以当作双端队列来使用,也就是说,既可以当作“栈”使用,又可以当作队列使用。

在这里插入图片描述

关于两者的区别

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。

  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

6.Queue集合

主要分为单向队列和双端队列,双端队列就是实现Deque接口,最常见的就是上面所说的LinkedList, 单向队列主要介绍优先队列PriorityQueue。

  • 下面先来看JDK为我们提供的双端队列方法

Deque

细心的朋友可能会发现,API为每种操作都提供两种方法,那么它们有什么不同呢,调用不同方法操作失败时,结果也会不同。

失败结果添加删除查找
抛出异常add()remove()get()
Falseoffer()poll()peek()

实现双端队列的除了LinkedList, 还有ArrayDeque, 两者的区别和LinkedList和ArrayList的区别有点像,都是一个底层是链表,一个是数组。用的不多

  • 最后讲一下单向队列的PriorityQueue

    PriorityQueue是基于优先堆实现的,优先队列,顾名思义它可以根据优先级来进行排序

    要求添加的元素实现Comparable接口,并不可以存NULL值

    下面来个例子演示一下

    先定义了一个Player类,实现Comparable接口,并重写了CompareTo方法

    public static void main(String[] args) {
          Queue<Player> players = new PriorityQueue<>();
          Player p1 = new Player("Ronaldo", 93);
          Player p2 = new Player("Messi",94);
          Player p3 = new Player("Neymar",92);
          Player p4 = new Player("lewandovsiki",91);
          Player p5 = new Player("Lingard",100);
    
          players.add(p1);
          players.add(p2);
          players.add(p3);
          players.add(p4);
          players.add(p5);
    
          while (!players.isEmpty()){
              Player p = players.poll();
              System.out.println(p);
          }
    }
    
Player{name='Lingard', ability=100}
Player{name='Messi', ability=94}
Player{name='Ronaldo', ability=93}
Player{name='Neymar', ability=92}
Player{name='lewandovsiki', ability=91}
会发现输出顺序和ability有关,这是因为内部已经根据CompareTo方法排好序了,也就是根据ability为优先级了。      

最后献上一个表格来总结一下

实现类增删复杂度查复杂度底层数据结构线程安全
VectorO(N)O(1)数组是(过时)
ArrayListO(N)O(1)数组
LinkedListO(1)O(N)双向链表
HashSetO(1)O(1)数组+链表+红黑树
TreeSetO(logN)O(logN)红黑树
LinkedHashSetO(1)O(1)~O(N)数组 + 链表 + 红黑树
ArrayDequeO(N)O(1)数组
PriorityQueueO(logN)O(logN)堆(数组实现)
HashTableO(1) / O(N)O(1) / O(N)数组+链表是(过时)
HashMapO(1) ~ O(N)O(1) ~ O(N)数组+链表+红黑树
TreeMapO(logN)O(logN)数组+红黑树
​HashTableO(1) / O(N)O(1) / O(N)数组+链表是(过时)
HashMapO(1) ~ O(N)O(1) ~ O(N)数组+链表+红黑树
TreeMapO(logN)O(logN)数组+红黑树

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值