Java笔记之容器

本笔记来自 计算机程序的思维逻辑 系列文章

ArrayList

内部组成

  • Object[] elementData,动态数组,存放数据
  • int size,记录实际元素个数
  • int modCount,记录修改次数
  • int DEFAULT_CAPACITY,数组初始化默认分配空间为 10

可迭代接口

  • Iterable 表示可迭代;
  • iterator()返回一个实现了Iterator接口的对象
public interface Iterable {
    Iterator iterator();
}

迭代器接口

public interface Iterator {
    boolean hasNext();
    E next();
    void remove();
}

ListIterator

扩展了Iterator接口,增加向前遍历,返回索引,添加元素,修改元素等

迭代中删除元素

使用迭代器遍历,调用迭代器的remove方法进行删除

必须先调用next(),使cursor指向该元素再进行删除

实现接口

ArrayList实现了Collection RandomAccess List

  • Collection表示数据集合,数据间没有位置或顺序的概念
  • RandomAccess表示可以随机访问
  • List表示有顺序或位置的数据集合

LinkedList

内部组成

由长度、头节点和尾节点组成双向链表

节点

private static class Node {
    E item;
    Node prev;
    Node next;
    
    Node(Node prev, E element, Node next) {
        this.item = element;
        this.prev = prev;
        this.next = next;
    }
}

特点

  • 按需分配内存
  • 不可以随机访问,按照索引访问效率比较低,从头或尾顺着链表找,效率为O(N/2)
  • 按照内容查找,效率为O(N)
  • 在两端添加或删除元素,效率为O(1)
  • 在中间插入,删除元素,需定位,效率为O(N),修改本身效率为O(1)

实现接口

实现了List Queue Deque接口

队列 Queue

特点

先进先出

方法

add offer element peek remove poll

注意
  • 队列为空时,elementremove会抛异常,而peekpoll会返回null
  • 队列为满时,add会抛异常,而offer会返回false

栈 Stack

特点

先进后出

方法

push peek pop

注意
  • 栈为空时,pop会抛异常,peek返回null
  • 栈为满时,push会抛异常

双端队列 Deque

特点

扩展了Queue,包含栈的操作方法,额外添加其他方法

方法

addFirst addLast getFirst getLast removeFirst removeLast offerFirst offerLast peekFirst peekLast pollFirst pollLast

注意
  • 队列为空时,getremove会抛异常,peekpoll会返回null
  • 队列为满时,add会抛异常,offer会返回false

迭代器descendingIterator,从后往前遍历

HashMap

键值对结构,一个键映射一个值;键不能重复,即对同一个键重复操作会覆盖原来的值

内部组成

  • Node[] table,初始化为空数组
  • Set> entrySet,键值对集合
  • int size,实际键值对的个数
  • int modCount,记录修改次数
  • int threshold,阈值,当键值对个数大于该值时才进行扩展,threshold = table.length * loadFactor
  • float loadFactor,负载因子,表示table被占用的程度,默认为 0.75

Map.Entry

interface Entry {
    K getKey();
    V getValue();
    V setValue(V value);
}

HashMap.Node

内部类,HashMap的核心元素

static class Node implements Map.Entry {
    final int hash;
    final K key;
    V value;
    Node next;
    
    Node(int hash, K key, V value, Node next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

保存键值对

  • 如果为空数组,则初始化,默认长度为 16
  • 计算keyhash
  • 再计算其存储位置
  • 再判断该位置是否有值
    • 有则判断key是否一样,是则修改其value并返回oldValue
    • 没有则直接插入该链表头部,返回null

HashSet

没有重复元素,无序的集合

内部组成

  • HashMap map,使用mapkey来存储元素
  • Object PRESENTmap的固定value

排序二叉树

特点

  • 没有重复元素,有序
  • 如果左子树不为空,则左子树所有节点都小于该节点
  • 如果右子树不为空,则右子树所有节点都大于该节点

查找

  • 跟根节点比较,相同则找到
  • 小于根节点,则到左子树递归查找
  • 大于根节点,则到右子树递归查找

最值

最左边是最小值,最右边是最大值

遍历

递归

访问左子树,访问当前节点,访问右子树

不递归

先找到最左边节点,然后依次找后继节点

后继节点查找方式

  • 如果该节点有右孩子,则后继节点为右子树中最小的节点
  • 如果该节点没有右孩子,则后继节点为父节点或某个祖先节点
    • 从当前节点往上找,如果它是父节点的右孩子,则继续找父节点
    • 直到它不是右孩子或父节点为空,第一个非右孩子节点的父节点就是后继节点
    • 如果找不到,则后继节点为空,遍历结束

插入

  • 首先查找插入位置,即插入节点的父节点,从根节点开始找
    • 与当前节点相同,说明已存在,不能再插入
    • 小于当前节点,则到左子树查找,如果左子树为空,则当前节点为插入节点的父节点
    • 大于当前节点,则到右子树查找,如果右子树为空,则当前节点为插入节点的父节点
  • 找到父节点后,如果插入元素小于父节点,则作为左孩子插入,反之作为右孩子插入

删除

  • 节点为叶子节点
    • 即没有孩子,直接删除,修改父节点的对应孩子为空
  • 节点只有一个孩子
    • 替换待删节点为孩子节点
  • 节点有两个孩子
    • 先找到该节点的后继节点(该节点为右子树的最小节点,没有左孩子),找到后,替换待删节点为后继节点,再删除后继节点(节点只有一个孩子的情况)

平衡二叉树

节点分布比较均匀,即为平衡

AVL树

  • 任何节点的左右子树的高度差最多为一
  • 实现方法:在每次的插入和删除操作时,通过一次或多次的旋转操作来重新平衡树

TreeMap

有序的Map

构造方法

  • 空构造,要求Map中的键实现Comparable接口
  • Comparator参数的构造,传入一个comparator对象,TreeMap内部会用它比较元素

内部组成

  • Comparator comparator,没传则为null
  • Entry root,指向根节点
  • int size,记录元素个数
  • int modCount,记录修改次数

SortedMap 接口

public interface SortedMap extends Map {
    Comparator comparator();
    SortedMap subMap(K fromKey, K toKey);
    SortedMap headMap(K toKey);
    SortedMap tailMap(K fromKey);
    K firstKey();
    K lastKey();
    Set keySet();
    Collection values();
    Set> entrySet();
}

NavigableMap 接口

public interface NavigableMap extends SortedMap {
    Map.Entry lowerEntry(K key);
    K lowerKey(K key);
    Map.Entry floorEntry(K key);
    K floorKey(K key);
    Map.Entry ceilingEntry(K key);
    K ceilingKey(K key);
    Map.Entry higherEntry(K key);
    K higherKey(K key);
    Map.Entry firstEntry();
    Map.Entry lastEntry();
    Map.Entry pollFirstEntry();
    Map.Entry pollLastEntry();
    NavigableMap descendingMap();
    NavigableSet navigableKeySet();
    NavigableSet descendingKeySet();
    NavigableMap subMap(K fromKey, boolean fromInclusive,
                             K toKey,   boolean toInclusive);
    NavigableMap headMap(K toKey, boolean inclusive);
    NavigableMap tailMap(K fromKey, boolean inclusive);
}

TreeMap.Entry

static final class Entry implements Map.Entry {
    K key;
    V value;
    Entry left;
    Entry right;
    Entry parent;
    boolean color = BLACK;
    
    Entry(K key, V value, Entry parent) {
        this.key = key;
        this.value = value;
        this.parent = parent;
    }
}

TreeSet

构造方法

  • 空构造,要求元素实现Comparable接口
  • Comparator参数的构造,内部使用它比较元素

内部组成

  • NavigableMap m,同HashSet,使用mapkey存储元素
  • Object PRESENT,同HashSet,固定的value

内部操作

都是调用内部变量m的对应方法

概念上是完全二叉树,使用数组进行物理存储

满二叉树

除最后一层外,每个节点都有两个孩子,最后一层都是叶子节点,没有孩子

完全二叉树

不要求最后一层是满的,但如果不满,要求所有节点必须集中在最左边,从左到右是连续的,中间不能有空的

特点:在完全二叉树中,可以给每个节点一个编号,编号从1开始递增,从上到下,从左到右;当给定任意一个节点的编号,可以快速计算出其父节点和孩子节点编号

这个特点使得逻辑概念上的二叉树可以方便的存储到数组中

最大堆 最小堆

与排序二叉树不同,在堆中,可以有重复元素,元素间不是完全有序的,父子节点之间有一定的顺序要求

算法(最小堆)

添加元素

log2(N) siftup

  • 添加元素到最后位置
  • 与父节点比较,如果大于等于父节点,则满足堆的性质,结束
  • 否则与父节点交换,再与父节点比较,直到父节点为空或大于等于父节点
从头部删除元素

log2(N) siftdown

  • 用最后一个元素替换头部元素,并删除最后一个元素
  • 将新的头部元素与俩孩子节点中较小的的比较,如果不大于该孩子节点,则满足堆的性质,结束
  • 否则与该孩子节点交换,再与较小的孩子节点比较,直到没有孩子或者不大于两个孩子
从中间删除元素
  • 还是用最后一个元素替换头部元素,并删除最后一个元素
  • 然后判断,如果该元素大于某个孩子节点,则需向下调整,如果小于父节点,则需向上调整
构建初始堆

O(N) heapify

  • 从最后一个非叶子节点开始,一直往前直到根,对每个节点,执行向下调整
查找和遍历

O(N)

  • 就是数组的查找和遍历

PriorityQueue

有优先级的队列

特点

  • 长度没有限制,默认为 11
  • 元素有优先级,队头的元素永远是优先级最高的
  • 内部元素不是完全有序的,逐个出队会得到有序的输出
  • TreeMap TreeSet类似,要么元素实现Comparable接口,要么传入一个Comparator比较器

内部组成

  • Object[] queue,存储元素的数组
  • int size,记录元素个数
  • Comparator comparator,比较器
  • int modCount,记录修改次数

ArrayDeque

使用数组存储的双端队列

内部组成

  • Object[] elements,存储元素的数组
  • int head,头部元素索引
  • int tail,尾部元素索引

循环数组

  • 如果headtail相同,则数组为空,长度为 0
  • 如果tail大于head,则第一个元素为elements[head],最后一个元素为elements[tail - 1],长度为tail - head,元素索引从headtail - 1
  • 如果tail小于head,且为 0 ,则第一个元素为elements[head],最后一个元素为elements[elements.length - 1],元素索引从headelements.length - 1
  • 如果tail小于head,且大于 0 ,则会形成循环,第一个元素为elements[head],最后一个元素为elements[tail - 1],元素索引从headelements.length - 1,然后再从0tail - 1

内部实现

  • 数组的长度要严格大于实际存储元素的个数,为了让headtail指向下一个空位
  • 数组长度为 2 的幂次方,方便计算
  • 计算方式
    • 下一个位置:(tail + 1) & (elements.length - 1)
    • 上一个位置:(head - 1) & (elements.length - 1)

LinkedHashMap

特点

支持两种顺序:插入顺序和访问顺序

内部组成

  • LinkedHashMap.Entry head,双向链表的头
  • LinkedHashMap.Entry tail,双向链表的尾
  • boolean accessOrder,是否按访问顺序的标志

LinkedHashMap.Entry

  • before:前一个节点
  • after:后一个节点
static class Entry extends HashMap.Node {
    Entry before, after;
    
    Entry(int hash, K key, V value, Node next) {
        super(hash, key, value, next);
    }
}

访问顺序

访问指get put操作,对一个键执行get put操作后,其对应的键值对会移到链表末尾

构造方法

只有一个构造方法可以指定按访问顺序

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

用途

用于实现缓存,比如 LRU缓存

如果缓存满了,当需要存储新数据时,就需要一定的策略将一些老的数据清理,这个策略称为 替换算法

EnumMap

内部组成

  • Class keyTypekey的类型
  • K[] keyUniverse,存储所有key
  • Object[] vals,存储所有的value
  • int size,记录元素个数

实现原理

内部维护两个数组,长度相等,一个表示所有的键,一个表示对应的值,值为null表示没有该键值对,键都有一个对应的索引ordinal,根据索引可以很方便访问键和值

EnumSet

抽象类,如果枚举个数小于 64 ,则静态工厂方法创建的就是 RegularEnumSet,大于则是JumboEnumSet

内部组成

  • Class elementType,元素类型
  • Enum[] universe,存储所有枚举值

RegularEnumSet

元素个数在 64 以内的集合

  • long elements,位向量,使用一个long类型的变量存储所有元素
  • int size(),返回元素个数,方法实现:Long.bitCount(elements);

用一个位表示一个元素的状态,用一组位表示一个集合的状态,每个位对应一个元素,而状态只可能有两种

JumboEnumSet

元素个数在 64 以上的集合

  • long elements[],使用long数组存储所有元素
  • int size,记录元素个数

内部操作

  • 都是利用位运算来实现的
  • 根据枚举值找到索引,对对应的位进行 位或位与取反 操作

容器类

接口

  • Collection
  • List
  • Map
  • Set
  • Queue
  • Deque

抽象类

  • AbstractCollection
  • AbstractList
  • AbstractMap
  • AbstractSet
  • AbstractQueue
  • AbstractSequentialList

AbstractList 与 AbstractSequentialList 对比

  • AbstractList需要具体子类重写根据索引操作的方法get set add remove,它提供了迭代器,但迭代器是基于这些方法实现的;它假定子类可以高效的根据索引位置进行操作,适用于内部是随机访问类型的存储结构(如数组),比如ArrayList就继承自AbstractList
  • AbstractSequentialList需要具体子类重写迭代器,它提供了根据索引操作的方法get set add remove,但这些方法是基于迭代器实现的;它适用于内部是顺序访问类型的存储结构(如链表),比如LinkedList就继承自AbstractSequentialList

操作

查找
  • 利用迭代器遍历进行查找
二分查找
  • 要求List的每个元素实现Comparable接口或提供一个Comparator
  • 如果List可以随机访问,即实现RandomAccess接口,或者元素个数比较少,则直接使用索引访问中间元素进行查找;否则使用迭代器的方式访问中间元素进行查找
查找元素出现个数
  • frequency方法
查看两个集合是否有交集
  • disjoint方法
排序
  • 先将List元素拷贝到一个数组中,然后使用Arrays.sort方法,排序后,再拷贝回List
交换元素位置
  • list.set(i, list.set(j, list.get(i)))
翻转列表顺序
  • 如果List可以随机访问,将第一个和最后一个交换,第二个和倒数第二个交换,依此类推,直到中间两个元素交换完毕;否则,使用一前一后两个listIterator定位待交换的元素
随机化重排
  • 如果List可以随机访问,从后往前遍历列表,逐个给每个位置重新赋值,值从前面未重新赋值的元素中随机选取;否则,先将List拷贝到一个数组中,重排后再拷贝回List
循环移位
  • 使用rotate(list, distance)方法
  • distance:正数表示右移,负数表示左移
  • 可以看作列表的两个子列表进行顺序交换
  • 根据列表长度和移位个数,计算出两个子列表的分割点,然后通过三次翻转得到最终结果

你可能感兴趣的:(Java笔记之容器)