美文网首页
Java笔记之容器

Java笔记之容器

作者: 码匠 | 来源:发表于2019-01-20 18:13 被阅读0次

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

    ArrayList

    内部组成

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

    可迭代接口

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

    迭代器接口

    public interface Iterator<E> {
        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<K,V>[] table,初始化为空数组
    • Set<Map.Entry<K,V>> entrySet,键值对集合
    • int size,实际键值对的个数
    • int modCount,记录修改次数
    • int threshold,阈值,当键值对个数大于该值时才进行扩展,threshold = table.length * loadFactor
    • float loadFactor,负载因子,表示table被占用的程度,默认为 0.75

    Map.Entry

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

    HashMap.Node

    内部类,HashMap的核心元素

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

    保存键值对

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

    HashSet

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

    内部组成

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

    排序二叉树

    特点

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

    查找

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

    最值

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

    遍历

    递归

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

    不递归

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

    后继节点查找方式

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

    插入

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

    删除

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

    平衡二叉树

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

    AVL树

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

    TreeMap

    有序的Map

    构造方法

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

    内部组成

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

    SortedMap 接口

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

    NavigableMap 接口

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

    TreeMap.Entry

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

    TreeSet

    构造方法

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

    内部组成

    • NavigableMap<E,Object> 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<? super E> 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<K,V> head,双向链表的头
    • LinkedHashMap.Entry<K,V> tail,双向链表的尾
    • boolean accessOrder,是否按访问顺序的标志

    LinkedHashMap.Entry

    • before:前一个节点
    • after:后一个节点
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        
        Entry(int hash, K key, V value, Node<K,V> 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<K> keyTypekey的类型
    • K[] keyUniverse,存储所有key
    • Object[] vals,存储所有的value
    • int size,记录元素个数

    实现原理

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

    EnumSet

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

    内部组成

    • Class<E> 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笔记之容器

          本文链接:https://www.haomeiwen.com/subject/bntfjqtx.html