【Java进阶】07-Java集合类进阶

概述

首先看一下集合类的继承关系
在这里插入图片描述

ArrayList

  • List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。
  • 非线程安全
  • 底层使用的数据结构为数组
  • 适合查改,弱于增删

方法介绍

set()

public E set(int index, E element)
将下标为 index 的元素替代为 element,并返回旧值

add()

public boolean add(E e)
在末尾添加新的成员 e,速度较快,返回是否成功
public void add(int index, E element)
在指定位置添加新的成员 e,该位置及以后的原始成员须往后移动。由于涉及大量数据的移动,速度不如上一种方式。

remove()

public E remove(int index)
删除指定位置的元素,涉及数据的移动。返回该位置旧值

ensureCapacity()

public void ensureCapacity(int minCapacity)
数组扩容扩展,主要是修改 length。注意这里是修改容量 length,而不是存储的元素个数 size。默认按照 length 的1.5倍方式扩展,如果扩展之后还是 < minCapacity,就设置 length 为 minCapacity。

LinkedList

  • List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)
  • 实现 Deque 接口,为 add()、poll() 提供先进先出队列操作以及其他堆栈和双端队列操作,所以每个 Node 都有属性关于前一个节点和后一个节点的引用
  • 非线程安全
  • 适合增删,弱于查改

Node()

Node(Node<E> prev, E element, Node<E> next)
内部节点的构造方法,prev 为前一个 Node 的引用,element 为包含数据的元素属性,next 为后一个 Node 引用。

node()

Node<E> node(int index)
获取下标为 index 的 Node 对象

add()

public void add(int index, E element)
在指定位置添加结点

addFirst()

public void addFirst(E e)
在链表开头添加结点

addLast()

public void addLast(E e)
在链表末尾添加元素

remove()

public boolean remove(Object o)
通过对象删除某个结点
public E remove(int index)
通过下标删除某个结点
public E remove()
删除第一个结点
public E removeFirst()
删除第一个结点
public E removeLast()
删除最后一个结点

比较

综合 ArrayList 和 LinkedList 的特性,比较发现:

  • ArrayList 适合查询修改数量多于增删的场合
  • LinkedList 适合增删数量多于查询修改的场合

HashMap

  • 基于哈希表的 Map 接口实现。提供所有可选的映射操作,并且允许使用 null 值和 null 键(只有一个键)。
  • 非线程安全
  • 不保证映射的顺序,特别是它不保证顺序的不变
  • 实现方式是首先用 key 经过 hash 函数计算出一个 hashcode。首先不同的 hashcode 对应数组的一个元素,对应同一个数组元素的 key-value 再存到一个链表里面。

类似于如下的结构
在这里插入图片描述

Node()

Node(int hash, K key, V value, Node<K,V> next)
结点的构造函数
hash 为 key 的 hashcode,使用 hash() 方法计算而来,next 是下一个结点的引用
注意 HashMap 里面的链表是单向链表

put()

public V put(K key, V value)
插入一个键为 key,值为 value 的结点,如果 key 已经存在,返回旧值,否则返回 null

hash()

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这里放一下 hash() 函数的一种实现方式

static final int hash(int h) {
    h ^= (h>>>20)^(h>>>12);
    return h^(h>>>7)^(h>>>4);
}

上面是一种更复杂一些的 hash() 函数实现

get()

public V get(Object key)
返回键为 key 的 value,如果 hashmap 中没有 key,就会返回 null

总结

HashMap 实现的过程中 hashcode 对应数组的元素有可能会相同,这时候就会有碰撞。对应到同一个数组元素的 key-value 用一个链表存储。如果碰撞的频率太高就会严重影响查询的效率(理论上 hash 查找的复杂度最低为 O(1))。当 hashcode 数组越满的时候,碰撞率就会增高。所以当数组快满的时候就会进行数组的扩容。但是这个扩容是非常耗时的。默认当数组中 75% 的容量存储了数据的时候就会进行一次扩容,每次容量翻倍。这时就会对每一个键重新根据 hashcode 指定数组的位置,耗时是很大的。

  • HashTable 和 HashMap 采用相同的存储机制,二者的实现本质是一致的
  • 不允许有 null 值得存在
  • HashTable 是线程安全的,内部方法基本都是同步的
  • 迭代器具有强一致性

TreeMap

  • Map 接口的树实现
  • 不允许有 null 值得存在
  • 非线程安全
  • 键有序

TreeMap 使用的收据结构是红黑树,红黑树是平衡二叉树的修改版本。AVL 树(平衡二叉树)是严格的,但是插入删除的时候会涉及较多的旋转。红黑树通过将结点标记为 red 和 black 和一些手段,使得深度比 AVL 数略深,但是旋转次数更少。所以性能上可能略微优一点,但是差别不大。

结点的 key 就是红黑树用于比较的值,所以 TreeMap 天然是键有序的。

TreeMap 的优势

  • 空间利用率高。由于 HashMap 每次扩容是容量翻倍的,使得一些空间会被浪费,最多有超过一半的空间会被浪费。而 TreeMap 中树的每一个结点就代表了一个元素,不会浪费空间。
  • 性能稳定。Hash 碰撞的时候会导致 HashMap 的查询开销较大,HashMap 扩容的时候需要重新计算,开销较大。TreeMap 的操作均能够在 O(log n) 的时间内完成。

LinkedHashMap

  • Map 接口的哈希表和链接列表的实现,提供所有可选的映射操作,允许 null 值和 null 键(一个键)
  • 非线程安全
  • 具有可以预知的迭代顺序

get()

public V get(Object key)
获得 key 对应的 value。如果 LinkedHashMap 中没有 key,返回 null。

总结

  • HashMap 适用于一般键值映射的去求
  • HashTable 适用于多线程并发的场合
  • TreeMap 适用于按照键排序的迭代场合
  • LinkedHashMap 适用于特殊顺序的迭代场合(如 LRU 算法)

HashSet

  • 实现 Set 接口,由哈希表支持,允许使用 null 元素
  • 非线程安全
  • 不保证 set 的迭代顺序,特别是不保证顺序不变

实际是使用 HashMap 实现

add()

public boolean add(E e)
添加结点,返回是否添加成功

contains()

public boolean contains(Object o)
是否包含了某个结点

  • HashSet 通过 HashMap 实现
  • TreeSet 通过 TreeMap 实现
  • LinkedHashSet 通过 LinkedHashMap实现
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值