zoukankan      html  css  js  c++  java
  • 容器之路 List解析

    容器之路 List解析

    1.类图

    这里我们主要介绍两个类,分别是ArrayListLinkedList,这两个类都是List的实现类,下面是简要的类图。

    2.ArrayList

    ArrayList是我们非常常用的list实现,这个类的底层实际上是使用可以变化长度数组来保存数据,一些操作都是非常简单的,我们主要需要注意的就是可以变化长度这一点的实现方式。

    transient Object[] elementData;

    transient关键字表示elementData不会被序列化,但是这不表示ArrayList无法序列化,ArrayList使用了自定义的方法来进行序列化,这种做法可能是为了防止大量空的数据被序列化

    2.1 初始化

    当我们初始化一个ArrayList的时候,实际上就是初始化了一个数组。

    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    2.2 add方法

    在数组中增加元素是十分简单的,这个方法主要需要注意的就是边界检查,判断是否需要动态的增加数组的长度。

    类中有变量size用来保存数组中元素的个数,当我们增加一个元素的时候,size的大小需要增加。我们需要判定,增加后的大小是否超出。

    这里有两种情况,第一种,数组初始化后第一次插入数据,那么比较默认大小10和传入参数的大小,选择较大的一个。第二种,不是第一插入数据,选择传入参数。

    有了确切的应该可以容纳的元素个数后,我们判断目前数组的长度是否大于这个值。如果不大于,那么我们就需要调用方法grow来增加数组的长度。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, intminCapacity) {
        //第一次插入数据
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
        grow(minCapacity);
        
    }
    

    如果数组长度需要增加,那么,我们会让数组的长度变为原来的1.5倍,然后将原来的数据复制到新数组中。

    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    2.3 remove方法

    移除方法有两个,一个是移除某个index上的值,一个是移除object

    移除对象的操作我们一般先将移除对象之后的所有元素向前移动,然后将最后一个元素置为null,并将size减1。

    另外需要说明的一点是,我们移除一个对象,只会移除list中第一个对象。

    2.4 序列化问题

    我们在上面提到ArrayList没有使用默认的序列化方法,而是自己实现了序列化方法,下面我们给出方法。

    如果一个类需要序列化,那么我们需要让这个类实现接口Serializable
    如果我们不想使用默认的序列化方法,我们可以自己实现下面两个方法。然后,将字段使用transient修饰。

        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out size as capacity for behavioural compatibility with clone()
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
    
        /**
         * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
         * deserialize it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            elementData = EMPTY_ELEMENTDATA;
    
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in capacity
            s.readInt(); // ignored
    
            if (size > 0) {
                // be like clone(), allocate array based upon size not capacity
                int capacity = calculateCapacity(elementData, size);
                SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
                ensureCapacityInternal(size);
    
                Object[] a = elementData;
                // Read in all elements in the proper order.
                for (int i=0; i<size; i++) {
                    a[i] = s.readObject();
                }
            }
        }
    

    3.LinkedList

    LinkedList的底层是实现是使用双链表,链表中的节点数据结构如下

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

    LinkedList即实现了List接口,也实现了Deque接口,实际上Deque接口就是double queue,双端队列。

    使用链表的形式,我们可以很方便的移除或者新增一个元素,但是如果我们需要访问某个元素,只能依次遍历。同时,我们也可以使用LinkedList来实现栈或者队列的功能。

    LinkedList中有两个指针,分别指向第一个元素和最后一个元素。

    transient Node<E> first;
    
    transient Node<E> last;
    

    3.1.增加与删除元素

    类中提供了多种方式可以在链表的头部和尾部增加或删除结点,同时也可以在某个index处增加或删除结点。

    增加或删除元素时,我们需要注意指向前驱节点以及后继结点的指针的处理。

    我们仍需注意的一点时头结点和尾结点的处理。

    下图给出的是通用的链表中增加或删除元素的图示,相信大家对这些内容都是十分熟悉的,再次不再赘述。

    LinkedList的方便之处在于我们灵活的在首尾增删节点。但是如果我们需要的单纯是一个队列或者栈,我们应该选用ArrayDeque,它效率更高。LinkedList相较ArrayList增删结点等操作需要更为复杂的处理。

    4.关于遍历

    ArrayListLinkedList中有两种迭代器,分别是IteratorListIterator

    Iterator在遍历过程中不能动态增加list中的元素,但是ListIterator可以增加或修改元素。另外ListIterator不仅可以向后遍历,同时也可以向前遍历,能够灵活的移动,更加方便。

    Iterator是一个更为通用的迭代器类,所有集合对象都可以使用。

    另外需要注意,在使用迭代器遍历的过程中,不能够直接调用集合的增加修改删除方法,这样会抛出异常,当需要修改时,可以考虑使用迭代器自带的方法进行操作。

    public class IteratorTest {
        public static void main(String[] args) {
            String[] strings = {"aaa", "bbbb", "ccc"};
            ArrayList<String> list = new ArrayList<>(Arrays.asList(strings));
    
            Iterator iterator = list.iterator();
            while (iterator.hasNext()) {
                String tmp = (String) iterator.next();
                System.out.println(tmp);
                //满足条件remove当前结点
                if ("aaa".equals(tmp)) {
                    iterator.remove();
                }
            }
    
            Stream.of(list).forEach(System.out::println);
    
            ListIterator listIterator = list.listIterator();
            while (listIterator.hasNext()) {
                String tmp = (String) listIterator.next();
                System.out.println(tmp);
    
                if ("bbbb".equals(tmp)) {
                    //满足条件,修改结点的值
                    (listIterator).set("change");
                    //并且增加结点,在当前结点后面增加
                    (listIterator).add("dddd");
                }
            }
    
            Stream.of(list).forEach(System.out::println);
        }
    }
    
    //输出结果
    aaa
    bbbb
    ccc
    [bbbb, ccc]
    bbbb
    ccc
    [change, dddd, ccc]
    
  • 相关阅读:
    我的WCF之旅(1):创建一个简单的WCF程序
    与众不同 windows phone (15) Media(媒体)之后台播放音频
    与众不同 windows phone (14) Media(媒体)之音频播放器, 视频播放器, 与 Windows Phone 的音乐和视频中心集成
    与众不同 windows phone (10) Push Notification(推送通知)之推送 Tile 通知, 推送自定义信息
    与众不同 windows phone (17) Graphic and Animation(画图和动画)
    与众不同 windows phone (5) Chooser(选择器)
    与众不同 windows phone (26) Contacts and Calendar(联系人和日历)
    与众不同 windows phone (7) Local Database(本地数据库)
    与众不同 windows phone (19) Device(设备)之陀螺仪传感器, Motion API
    与众不同 windows phone (16) Media(媒体)之编辑图片, 保存图片到相册, 与图片的上下文菜单“应用程序...”和“共享...”关联, 与 Windows Phone 的图片中心集成
  • 原文地址:https://www.cnblogs.com/qmlingxin/p/9779644.html
Copyright © 2011-2022 走看看