第十二章 java集合常见问题总结(上)

Java集合

概述

Java集合,也叫做容器,主要是由两大接口派生而来:

  1. Collection接口,主要用于存放单一元素
  2. Map接口,主要存放键值对

List,Set,Queue,Map区别和联系

  1. List:存储元素有序,可重复 顺序
  2. Set:存储元素无序,不可重复 独一无二
  3. Queue:按特定的排队规则来确定先后顺序,存储元素有序,可重复 实现排队功能的叫号机
  4. Map:使用键值对存储,key是无序,不可重复,value是无序,可重复。每个键最多映射到一个值 用key进行搜索

Collection接口

主要有三个子接口:List,Set,Queue

List

ArrayList:Object[]数组
Vector:Object[]数组
LinkedList:双向链表

Set

HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet(有序,唯一): LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。类似 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有区别的
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

Queue

PriorityQueue: Object[] 数组来实现二叉堆
ArrayQueue: Object[] 数组 + 双指针

Map接口

HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)

选用集合的方法

主要根据集合的特点来选用:

  1. 需要根据键值获取到元素值时就选用 Map 接口下的集合:
    a.需要排序时选择 TreeMap
    b.不需要排序时就选择 HashMap
    c.需要保证线程安全就选用 ConcurrentHashMap
  2. 当只需要存放元素值时,就选择实现Collection 接口的集合:
    a.需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSet 或 HashSet
    b.不需要元素唯一就选择实现 List 接口的比如 ArrayList 或 LinkedList

使用集合的原因

如果需要保存一组类型相同的数据时,应该用一个容器来保存,但是使用数组存储对象有弊端。在实际开发中,存储的数据类型很多,不可能是单一类型,于是出现了集合,集合是用来存储多个数据De。
数据的缺点:一旦声明后,长度不可变。声明数组的数据类型决定了该数组存储的数据的类型。数组存储的数据时有序,可重复,特点单一。
集合优点:提高了数据存储的灵活性,java集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。

Collection子接口–List

ArrayList和Vector区别

ArrayList是List的主要实现类,底层使用Object[]存储,适用于频繁的查找工作,线程不安全
Vector是List的古老实现类,底层使用的也是Object[]存储,线程安全

ArrayList和LinkedList区别

  1. 是否保证线程安全
    ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
  2. 底层数据结构
    ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
  3. 插入和删除是否受元素位置的影响
    ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响,时间复杂度为 O(1),如果是要在指定位置 i 插入和删除元素的话, 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入
  4. 是否支持快速随机访问
    LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象
  5. 内存空间占用
    ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)
    需要用到 LinkedList 的场景几乎都可以使用 ArrayList 来代替,并且,性能通常会更好
  6. ArrayList实现了RandomAccess接口,而LinkedList没有实现
    与底层数据结构有关,ArrayList底层是数组,而LinkedList底层是链表。数组支持随机访问,时间复杂度是O(1),称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度是O(n),不支持快速随机访问。ArrayList实现了RandomAccess接口,表明具有快速随机访问的功能。这个接口是标识,不是说ArrayList实现该接口后才具有的快速随机访问功能
    注:
    LinkedList 仅仅在头尾插入或者删除元素的时候时间复杂度近似 O(1),其他情况增删元素的时间复杂度都是 O(n)

补充RandomAccess接口

接口源码如下:

public interface RandomAccess {
}

该接口中什么都没定义。该接口标识 实现这个接口的类具有随机访问功能
在binarySearch()方法中,源码如下:

    public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }

分析:判断传入的list是否是RandomAccess的实例,如果是,则调用indexBinarySearch()方法,如果不是,则调用iteratorBinarySearch()方法

Collection子接口–Set

comparable和Comparator区别

  1. comparable接口出自java.lang包,有一个compareTo(Object obj)方法用来排序
  2. comparator接口出自java.util包,有一个compara(Object obj1, Object obj2)方法用来排序
    一般需要对一个集合使用自定义排序时,则重写上述两个方法中的一个。如果需要对某一个集合实现两种排序方式,需要用定制排序:
// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
     ArrayList<Integer> arrayList = new ArrayList<Integer>();
     arrayList.add(-1);
     arrayList.add(3);
     // void sort(List list),按自然排序的升序排序
     Collections.sort(arrayList);
     // 定制排序的用法
     Collections.sort(arrayList, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });

注:
如果没有实现Comparable接口的类,需要实现该接口,并进行重写,得到的TreeMap就是排好序的结果

HashSet,LinkedHashSet,TreeSet

相同点:
都是Set接口的实现类,都能保证元素唯一,并且都不是线程安全的。
不同点:

  1. 主要是底层数据结构不同。
    1.HashSet底层是哈希表,基于HashMap实现的
    2.LinkedHashSet底层是链表和哈希表,元素的插入和取出满足FIFO
    3.TreeSet底层是红黑树,元素是有序的,排序方式有自然排序和定制排序
  2. 三者的应用场景不同。
    1.HashSet用于不需要保证元素插入和取出顺序的场景
    2.LinkedHashSet用于保证元素的插入和取出顺序满足FIFO
    3.TreeSet用于支持对元素自定义排序规则的场景

Collection子接口–Queue

Queue和Deque区别

  1. Queue是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循FIFO规则
  2. Queue扩展了Collection接口,根据因为容量问题而导致操作失败后处理方式的不用分为两类方法:
    a.操作失败后抛出异常
    b.返回特殊值
Queue 接口抛出异常返回特殊值
插入队尾add(E e)offer(E e)
删除队首remove()poll()
查询队首元素element()peek()
  1. Deque是双端队列,在队列的两端均可以插入或删除元素
  2. Deque扩展了Queue接口,增加了在队首和队尾进行插入和删除的方法,同样根据失败后处理方式不同分为两类:
Deque 接口抛出异常返回特殊值
插入队首addFirst(E e)offerFirst(E e)
插入队尾addLast(E e)offerLast(E e)
删除队首removeFirst()pollFirst()
删除队尾removeLast()pollLast()
查询队首元素getFirst()peekFirst()
查询队尾元素getLast()peekLast()

ArrayDeque和LinkedList区别

ArrayDeque和LinkedList都实现了Deque接口,两者都具有队列的功能,但是两者也有区别:

  1. 基于可变长的数组和双指针来实现;通过链表来实现
  2. 不支持存储NULL数据;这个支持
  3. JDK1.6引入的;JDK1.2就存在
  4. 插入时可能存在扩容过程,均摊后插入操作O(1);这个不需要扩容,但是每次插入需要申请新的堆空间,均摊性能更慢
    综上,选用ArrayDeque实现队列要比LinkedList更好。ArrayDeque也可用于实现栈

PriorityQueue

是在JDK1.5被引入的,与Queue区别是元素出队顺序与优先级相关,即总是优先级最高的元素先出队。
特点:

  1. PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
  2. PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素
  3. PriorityQueue 是非线程安全的,且不支持存储 NULL 和 non-comparable 的对象
  4. PriorityQueue 默认是小顶堆,但可以接收一个 Comparator 作为构造参数,从而来自定义元素优先级的先后
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值