集合

189 阅读6分钟

[toc]

List集合

graph LR
List-->ArrayList
List-->LinkList
List-->Vector
  • ArrayList集合
    • 底层采用数组数据结构
    • 线程不安全
  • LinkList集合底层采用双向链表数据结构。
  • Vector集合
    • 底层采用数组数据结构
    • 线程安全

Vector 的效率低,原因:底层的方法都采用synchronized关键字保证线程安全,现在保证线程安全有其他方案,使用较少。

Set集合

graph LR
Set-->HashSet
Set-->AbstractSet
AbstractSet-->TreeSet
  • HashSet集合
    • 底层是new了一个HashMap对象,向HashSet中存储数据,实际上是存储在HashMap中
    • 底层采用哈希表数据结构
  • TreeSet集合
    • TreeSet 继承 AbstractSet,而AbstractSet实现了Set接口,本身没有实现Set接口
    • 底层new了一个TreeMap对象,向TreeSet中存储数据,实际上是存储在TreeSet中
    • 底层采用二叉树数据结构

Map集合

graph LR
Map-->HashMap
Map-->Hashtable
Map-->AbstractMap
AbstractMap-->TreeMap
Hashtable-->Properties
  • HashMap集合
    • 底层是哈希表数据结构,非线程安全
  • Hashtable集合
    • 底层是哈希表数据结构,线程安全
    • 效率低
  • TreeMap集合
    • 底层是二叉树数据结构
  • Properties集合
    • 因为继承Hashtable,所以线程是安全的。
    • Properties的Key和Value只支持String类型,所以Properties被称为属性类

Map集合和Collection集合没有关系,Map集合以Key和Value的这种键值对的方式存储元素,Key和Value都是存储java对象的内存地址,所有Map集合的Key特点:无序不可重复,Map集合的Key和Set集合存储的元素特点相同

Hashtable具体情况和Vector相同,所有方法都有synchronized关键字保证线程安全

HashSet

  • 存储时顺序和取出的顺序不同
  • 不可重复
  • 放到HashSet集合中的元素实际上是放到HashSet集合的key部分

Vector

  • 底层为数组
  • 初始化容量为10
  • 扩容之后是原容量的2倍
  • 所有方法都是带有线程同步的,带有cynchronized,是线程安全的,效率低。

LinkList集合

  • 有下标
  • 底层是双向链表
  • 没有初始化容量
  • 最初链表中没有元素,first和last都是null

链表的优点: 由于链表上元素在空间存储上内存地址不连续,所以增删不会有大量元素位移,因此效率高。

链表的缺点:不能通过数学表达式计算被查找元素的内存地址,每一次查找都要从头开始遍历,直到找到为止,因此查找效率低

ArrayList集合

  • 初始化容量是==10==,底层先创建一个长度为0的数组,当添加第一个元素的时候,初始化为==10==
  • 底层是一个Object类型的数组
  • 构造方法
    - new ArrayList();
    - new ArrayList(int initialCapacity);
    
  • 集合的扩容,原集合的1.5倍
  • 优化
    • 尽可能少扩容。因为数组扩容效率比较低,建议在初始的时候定容量,减少数组的扩容次数
  • 优点
    • 检索效率高
  • 缺点
    • 随机增删效率低
    • 数组无法存储大数据量
  • 向数组末尾添加效率高,不受影响

HashMap

  • 初始化容量为16
  • 当底层的容量达到75%,数组开始扩容
  • HashMap集合的初始化容量必须是2的倍数,也是官方推荐的,这是为了达到散列均匀,提高集合的存储效率
  • 向集合中存储,会先调用key的hashCode()方法,然后调用equals()方法
  • 非线程安全
  • 单向链表中的元素超过8个,会变成红黑树。但红黑树的节点数量小于6时,会重新变成单向链表。
  • 允许key为null

Hashtable

  • 底层时哈希表
  • key和value不能为空
  • 线程安全,效率低
  • 初始化11,当达到75%时扩容
  • 扩容:原容量 * 2 + 1

Properties

  • 是一个Map集合,继承Hashtable
  • key和value都是String类型
  • 被称为属性类对象
  • 线程安全

TreeSet

  • 底层是TreeMap的Key

  • 无序不可重复

  • 存储的元素会自动按照大小顺序排序

  • 底层是一个二叉树

  • TreeSet无法对自定义类型排序

    • 原因没有实现Comparable<T>接口
    • 没有在构造集合的时候给他传一个比较器对象Comparator

    当比较规则不会发生改变的时候用Comparable<T>接口

    当比较规则多的时候使用Comparator<T>接口

    Comparator<T>接口符合OCP原则

自平衡二叉树

  • 遵循左小右大原则存放
  • 遍历的方式
    • 前序遍历:根左右
    • 中序遍历:左根右
    • 后序遍历:左右根

    TreeSet集合/TreeMap集合采用中序遍历方式

哈希表

  • 哈希表是一种数组和单向链表的结合体。
    • 数组:在查询的方面效率高,随机增删效率低。
    • 单向链表:在随机增删方面效率高,查询效率低。
  • HashMap源码
//Hash底层实际上就是一个数组
transient Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果hash值通过哈希函数/算法,可以转换存储成数组的下标)
        final K key;
        V value;
        Node<K,V> next;//下一个节点的内存地址
}
  • 哈希表/散列表:一堆数组,这个数组中的每一个元素是一个单向链表。(数组和链表的结合体)
    • 哈希表数据结构
  • map.put(k,y)实现原理
    1. 先将k,y封装到Node对象
    2. 底层调用hashCode()方法得出hash值,然后通过哈希算法/哈希函数,然后将哈希值转换成数组的下标。如果下标有对应的位置有链表,会将k值与链表上的k值进行equals()比较,如果都是false,那么将会添加到链表的末尾。如果是true,那么这个节点的value将会被覆盖。
  • map.get(k)实现原理
    1. 调用k的hashCode()方法得出哈希值,通过哈希算法/哈希函数转换成数组下标,通过数组下标来定位,如果没有返回null。如果有单向链表,则会与所有节点的k进行equals()方法,返回false则输出null,返回true则输出该节点的value值。
  • 散列分布
    • 散列发布不均匀
      • hashCode()方法返回固定某个值或者都不一样,导致哈希表变成纯单向链表
    • 散列发布均匀
      • 例如假设有100个元素,10个单向链表,每个链表10个节点

同一个单向链表上的hash值相同。 重点:放在HashMap集合的key元素和放在HashSet集合中的元素,需要同时重写hashCode()和equals()方法