zoukankan      html  css  js  c++  java
  • List接口、Set接口常用实现类学习

    List接口常用实现类有ArrayList、LinkedList。

    ArrayList

    内部有一个Object数组用于存储元素。假如是无参构造器生成的ArrayList实例,则一开始数组是个空数组。在第一次add时,会扩容至10。假如创建实例时指定了初始容量,则扩容时会在初始容量的基础上扩容。扩容规则是:int newCapacity = oldCapacity + (oldCapacity >> 1),即扩容至原来的1.5倍,不是整数就向下取整。这个问题在面试中信银行信用卡中心时有被问过。

    ArrayList有个int型的size变量,父类AbstractList有个int型的modCount变量。add方法会使得size加1,modCount加1。remove方法会使得size减1,modCount加1。

    remove方法需要详细讲下:每次remove都有数组拷贝,如果删除的不是最后一个元素的话。System.arrayCopy方法。

    E remove(int index):把数组指定位置之后的数据都往前拷贝一个位移,原来最后面的位置赋值为null。

    boolean remove(Object o):找到第一个o,然后把该位置之后的数据都往前拷贝一个位移,原来最后面的位置赋值为null。寻找o索引实现:

        public boolean remove(Object o) {
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }

    如果o是null,则遍历数组,找到第一个null。如果o不是null,则遍历数组,找到第一个o.equals(elementData[index])返回为true的。

    int indexOf():和remove方法根据元素找索引的代码是一样的。需要遍历数组。

        public int indexOf(Object o) {
            if (o == null) {
                for (int i = 0; i < size; i++)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }

    boolean contains(Object o):内部是调用indexOf(o)方法,也需要遍历数组。

        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }

    iterator():返回的Iterator的next()方法内部也是判断了当前修改次数是否等于预期修改次数,如果不相等,会抛ConcurrentModificationException异常。就跟在遍历map的迭代器时修改map会抛并发修改异常是一样样的。

    LinkedList

    内部数据结构是链表,链表元素是Node实例。Node是LinkedList的内部类,有三个成员变量:E item、Node<E> next、Node<E> prev,只有一个全参构造器Node(Node<E> prev, E element, Node<E> next)。

    LinkedList有一个Node类型的first变量,标识第一个元素,有一个Node类型的last变量,标识最后一个元素。同ArrayList一样,也有一个int类型的size变量,标识当前元素个数。

    add方法会在链表的最后添加一个Node实例,把last变量的的next属性赋值为当前add的Node实例,当前add的Node实例的prev属性赋值为last。

    remove()、removeFirst()都是去掉第一个元素。

    E remove(int index):根据索引index如何找到对应Node元素呢?调用的是node(index)方法,实现是:

        Node<E> node(int index) {
            if (index < (size >> 1)) {
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }

    即先判断index和size/2谁大,如果index小于size/2,则从前往后找,这样比较快,否则从后往前找。从前往后找的话,根据first.next.next.next。。。next操作index-1次,就找到了。如果从后往前找,则根据last.prev.prev。。。prev操作size-1-index次,就找到了。找到后,调用unlink方法。对比ArrayList的remove(int index)方法,多了遍历操作,少了数组copy操作。

    boolean remove(Object o):根据o如何找到对应Node元素呢?从前往后遍历,如果o为null,则找第一个item为null的Node实例,如果o不为null,则找第一个o.equals item返回为true的Node实例。找到后,调用unlink方法。

    get(int index):内部调用node(index)方法。

    contains(Object o):内部还是调用indexOf(o)方法,从前往后遍历链表,就像remove(o)一样。

    size():返回size变量值

    clear():从first开始,利用next往下遍历,设置每个Node实例的item为null,prev为null,next为null,并把size置为0,modCount加1。

    Set接口常用实现类有HashSet、LinkedHashSet、TreeSet。

    HashSet

    内部有一个HashMap类型的变量map。在执行HashSet的构造方法时,map初始化为一个新的HashMap实例。可以选择调用HashSet的有参构造器,这样会把初始容量、负载因子透传给map。如果不传这些的话,则map的初始容量为16,负载因子为0.75。

    add():内部调用map的put()方法,put的key是add方法的入参,put的value是一个固定的Object实例。此实例是一个Object类型的静态常量,在类加载的时候赋值。

    add方法是如何去重的呢?

    从内部实现上可以看出,add判断两个元素是否一样,是依赖map的put方法的(所以如果Set中要放自定义类型元素的话,自定义类型必须重写hashCode方法和equals方法),如果add的元素一样,则put方法中只会用新value值替换旧value值,不会增加一个Node节点。

    boolean contains(Object o):内部是调用map的containsKey(key)方法。会根据key的hash值和key去找对应的Node实例,时间复杂度是O(1),不像ArrayList那样要遍历数组,也不像LinkedList那样要遍历链表。

    size():内部调用map的size()方法。

    clear():内部调用map的clear()方法。

    iterator():内部调用map.keySet().iterator()。

    LinkedHashSet

    是如何维护元素顺序的呢?LinkedHashSet内部有一个LinkedHashMap实例。在构造LinkedHashSet实例时,先构造父类HashSet实例,这时候HashMap类型的变量map会赋值为一个LinkedHashMap实例。LinkedHashMap可维护Node顺序,取各Node实例的key属性值,即LinkedHashSet的各元素,故也是有序的。

    TreeSet

    内部有一个NavigableMap类型的变量,在构造TreeSet时会被赋值为一个TreeMap实例。

    add方法同样是调用TreeMap的put方法,put的key是add的入参,put的value仍是一个Object类型的静态常量。

    itetator()方法同样是调用TreeMap实例的keySet的iterator()方法。

  • 相关阅读:
    intellij idea 热部署失效,需要手动编译类
    mac brew 安装包下载失败解决
    DataTemplate 之 ContentTemplate 的使用
    dataTemplate 之 ContentTemplate 的使用
    dataTemplate 的使用之listView
    dataTemplate 使用
    wpf中UserControl的几种绑定方式
    WPF教程(四)RelativeSource属性
    堆的概念
    哈夫曼(huffman)树和哈夫曼编码
  • 原文地址:https://www.cnblogs.com/koushr/p/5873424.html
Copyright © 2011-2022 走看看