[java] ArrayList和LinkedList实现原理及比较

Java中的LinkedList和ArrayList 在本文中,我们将介绍LinkedList和ArrayList的相同点和不同点,以及它们各自的优点和缺点,使用场景和功能特性,并给出代码示例,同时介绍它们的底层实现原理和源码。LinkedList是基于链表实现的,每个节点都包含了一个指向前一个节点和后一个节点的指针,而ArrayList是基于数组实现的,它的每个元素都可以通过下标来访问。LinkedList和ArrayList都是Java中的集合类,它们都可以用来存储一组数据,并且都实现了List接口,支持随机访问和遍历操作。 阅读详情

Table of Contents

ArrayList

Add 方法

Get方法

remove方法

Vector

ArrayList在循环过程中删除,会不会出问题,为什么?

LinkedList

LinkedList的get方法实现原理

LinkedList的size方法实现原理

LinkedList的add方法实现原理

LinkedList的remove方法实现原理

问题:ArrayList和LinkedList有什么区别,它们是怎么扩容的?

问题:Arrays.sort的排序方法是什么?

问题:Array和ArrayList有何区别?什么时候更适合用Array?


本篇分析基于jdk1.8。

数组与链表的基本知识在这里不作展开,属于数据结构与算法的重点内容。

ArrayList

ArrayList是一个容器,容器是用来存储对象的,ArrayList当中存储对象的变量是elementData,elementData的定义如下:

 /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

非常简单,就是一个对象数组而已。注意,它使用了transient关键字来修饰,这意味着ArrayList不会被序列化。

直接上几个ArrayList里的方法。

Add 方法

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

忽略返回值做了两件事情。

  • 确保当前数组的容量
  • 将需要添加的元素,也就是这里的e加到数组的末尾。size当前数组的大小,size++自然就是当前数组的后一位。

其他值得注意的是,此方法并没有做任何同步处理。并且,事实上整个Arraylist里的所有方法都没有做任何同步处理。因此这些涉及数据操作的方法都不是线程安全的。

Get方法

 /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

做了两件事情

  • 检测传入的参数,即数组的下标,是否越界
  • 若通过检测,则返回对应位置的元素即可,因为数组是有序排列的。

remove方法

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

这个实现很有意思的是它判断了一下要删除的对象是否是null,为什么要做这个判断呢,因为他们判断对象是否相等的方法不一样。null直接用两个等号判断,而非null对象,则是用equals方法判断相等。

遍历数组,找到相等对象,确定下标,然后删除当前数组中该下标的元素。删除方法为fastRemove,实现如下

/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

由于是删除,所以不需要做越界检查。结构变化数modCount加一,计算需要移动的数据元素,为当前数组大小,减去删除元素位置,再减去1,为什么要减去1因为数组下标从0开始,很好理解,做个草图如下:

当前数组元素一共9个,然后假设我要删除的是第6个元素。即size=9,index=5(从0开始)。要删除的元素是6,需要移动的元素即为7,4,8一共三个。

size-index-1 = 9 - 5 -1 = 3.

计算正确,一个测试用例诞生~。

然后将五个参数传入了System类下的arraycopy方法。这个方法的作用就是根据需要移动的元素,重新构造一个没有要删除元素的新数组,也即实现了删除的功能。这里额外插一句,从clean code的角度来讲,这个方法的参数已经太多了,clean code的建议是三个最多,只能说凡事无绝对吧。或者也可以说jdk的代码也并非从各个角度来看都完美无缺。

方法arraycopy在System类看不到实现,它是一个native方法。java众多的底层方法,实现几乎都是native方法,也就是用别的语言实现的。作为一个现代的程序员,或者说作为一个流水线上的程序员,大多数人真的一辈子也许也不需要知道这个方法到底如何实现的。

Vector

那么,如果要使用线程安全的容器,应该使用哪一个呢。答案就是Vector。

Vector的add方法如下:

/**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

用了synchronized直接修饰这个方法。除此之外实现逻辑和ArrayList几乎一样。modCount从注释来看这个变量代表的是这个容器的结构变化次数,所谓结构变化很好理解,就是数组中元素增加了一个,减少了一个,就是结构的变化。这里把modCount放在这里做计算,是一个和ArrayList不一样的地方,ArrayList是在check的时候来做。但这仅仅是代码风格的不统一。

Vector的get方法如下:

/**
     * Returns the element at the specified position in this Vector.
     *
     * @param index index of the element to return
     * @return object at the specified index
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *            ({@code index < 0 || index >= size()})
     * @since 1.2
     */
    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

也是用sychronized修饰了这个方法,然后先检查是否越界,不越界则返回,但是它没有像ArrayList一样将这个check提出一个方法,不太明白为何jdk的两个关联性非常强的类还会存在实现方法不一样,代码风格不一致的问题,虽然从功能上讲没有任何区别。与ArrayList基本一样。

Vector的remove方法如下:

/**
     * Removes the first (lowest-indexed) occurrence of the argument
     * from this vector. If the object is found in this vector, each
     * component in the vector with an index greater or equal to the
     * object's index is shifted downward to have an index one smaller
     * than the value it had previously.
     *
     * <p>This method is identical in functionality to the
     * {@link #remove(Object)} method (which is part of the
     * {@link List} interface).
     *
     * @param   obj   the component to be removed
     * @return  {@code true} if the argument was a component of this
     *          vector; {@code false} otherwise.
     */
    public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }

indexOf方法实现如下:

/**
     * Returns the index of the first occurrence of the specified element in
     * this vector, searching forwards from {@code index}, or returns -1 if
     * the element is not found.
     * More formally, returns the lowest index {@code i} such that
     * <tt>(i&nbsp;&gt;=&nbsp;index&nbsp;&amp;&amp;&nbsp;(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i))))</tt>,
     * or -1 if there is no such index.
     *
     * @param o element to search for
     * @param index index to start searching from
     * @return the index of the first occurrence of the element in
     *         this vector at position {@code index} or later in the vector;
     *         {@code -1} if the element is not found.
     * @throws IndexOutOfBoundsException if the specified index is negative
     * @see     Object#equals(Object)
     */
    public synchronized int indexOf(Object o, int index) {
        if (o == null) {
            for (int i = index ; i < elementCount ; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = index ; i < elementCount ; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

removeElementAt方法实现如下:

/**
     * Deletes the component at the specified index. Each component in
     * this vector with an index greater or equal to the specified
     * {@code index} is shifted downward to have an index one
     * smaller than the value it had previously. The size of this vector
     * is decreased by {@code 1}.
     *
     * <p>The index must be a value greater than or equal to {@code 0}
     * and less than the current size of the vector.
     *
     * <p>This method is identical in functionality to the {@link #remove(int)}
     * method (which is part of the {@link List} interface).  Note that the
     * {@code remove} method returns the old value that was stored at the
     * specified position.
     *
     * @param      index   the index of the object to remove
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *         ({@code index < 0 || index >= size()})
     */
    public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }

实现原理很简单,根据要删除的对象找到它的下标i,然后根据这个下标删除第i个元素即可。除了加上了锁,以及一些代码风格的,与ArrayList没有区别。

其他的方法不作仔细分析,到这里可以明确的是,对于Arraylist与Vector最常用的方法,以及其实现原理,以及他们之间的差异,都已经非常清楚了。

ArrayList在循环过程中删除,会不会出问题,为什么?

根据ArrayList循环的方式和删除元素的方式,可能会出现各种各样的问题:

 public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("a1", "ab2", "a3", "ab4", "a5", "ab6", "a7", "ab8", "a9"));
        // 迭代删除方式一:报错 java.util.ConcurrentModificationException
        for (String str : list) {
            System.out.println(str);
            if (str.contains("b")) {
                list.remove(str);
            }
        }

        // 迭代删除方式二
        /*
            报错:下标越界 java.lang.IndexOutOfBoundsException

        list移除了元素但size大小未响应变化,所以导致数组下标不对;
        list.remove(i)必须size--

        而且取出的数据的索引也不准确,同时需要做i--操作
         */
        int size = list.size();
        for (int i = 0; i < size; i++) {
            String str = list.get(i);
            System.out.println(str);
            if (str.contains("b")) {
                list.remove(i);
                //                size--;
                //                 i--;
            }
        }

        // 迭代删除方式三:正常删除,不推荐;每次循环都需要计算list的大小,效率低
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            System.out.println(str);
            if (str.contains("b")) {
                list.remove(i);
            }
        }

        // 迭代删除方式四:正常删除,推荐使用
        for (Iterator<String> ite = list.iterator(); ite.hasNext(); ) {
            String str = ite.next();
            System.out.println(str);
            if (str.contains("b")) {
                ite.remove();
            }
        }

        // 迭代删除方式五:报错- java.util.ConcurrentModificationException
        for (Iterator<String> ite = list.iterator(); ite.hasNext(); ) {
            String str = ite.next();
            if (str.contains("b")) {
                list.remove(str);
            }
        }
    }

ArrayList的内部实现了一个迭代器:

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

这个迭代器在里面定义了一个 int expectedModCount = modCount; 

即记录当前arraylist对象修改的次数。而从前面remove方法的分析我们看到,在每次remove方法的调用过程中,modCount这个成员变量是会自增长的,但是remove方法里面并不会更新。而我们可以看待迭代器里的next方法的第一步就是调用方法checkForComodification, 去判断这两个count是否相等,不相等就抛出异常。而在remove元素之后此处的modCount已经比expectedModCount大1了,因此会抛出一个ConcurrentModificationException。

因此在使用For-Each快速遍历时,ArrayList内部创建了一个内部迭代器iterator,使用的是hasNext和next()方法来判断和取下一个元素。就可能会出现问题。如何避免这个问题呢?最好的办法就是拿到这个迭代器,即上面代码当中的方法四。

LinkedList

LinskedList作为一个容器,用的是一个链表来存储对象,链表的结点用对象Node来进行抽象,同时定义了头节点和尾节点:

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

通过定义头节点和尾节点可以方便再某些时候的一些计算.

Node类的定义如下:


    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;
        }
    }

Item属性用于存储节点的对象,同时每个节点类有两个成员变量,next和prev分别指向当前节点的后一个结点和前一个结点。

从上面的信息已经可以判断出jdk的linkedlist采用的是有头尾节点的单链表的设计方式。

LinkedList的get方法实现原理

定义如下:

 /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

注释已经说的很清楚了,返回list里面指定位置的element。

node(index)方法实现如下:

 /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(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;
        }
    }

size>>1 即计算size的一半是多少。思想是判断index是否比size的一半小,若是则从前往后找,若不是则从后往前找。

找的方法并不可能逃出链表本身的特性。只能通过前后节点一个一个找。

LinkedList的size方法实现原理

 transient int size = 0;

 public int size() {
        return size;
    }

size定义成了一个成员变量,每次链表元素增加时它就加一。

LinkedList的add方法实现原理

transient Node<E> last;

public boolean add(E e) {
        linkLast(e);
        return true;
    }

/**
* Links e as last element.
*/
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

代码很简单,添加元素默认是添加在最好,由于linkedlist用一个成员变量来表示当前最后一个元素,因此效率上也非常高。

LinkedList的remove方法实现原理

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

注释已经说明了,删除第一个匹配的元素,如果没有匹配的元素,list不会发生改变。

很有意思的是,null对象是可以存储也可以删除的,并且判断null元素用的是==,而是一般的对象元素则是用的equals方法。

删除的方法unlink实现如下

/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

实现思想如下图:

删除后

问题:ArrayList和LinkedList有什么区别,它们是怎么扩容的?

事实上就是数组和链表作为两种最核心的数据结构的区别。

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据

因此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;
        }
    }

对于ArrayList来说,存在扩容的问题。在添加元素时我们从第一节的代码可以知道它会先确保容量是否足够,如果不够则会扩容,实现方法如下:

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

这个方法大概的意思就是根据现有的元素,和计算出的新的capacity,然后生成一个新的数组,生成新数组的方法最后是System类的arraycopy方法,这是一个native方法。

问题:Arrays.sort的排序方法是什么?

    数组的操作可以使用java提供的工具类Arrays,其中Arrays.sort()方法用于数组的排序。

  基本数据类型数组的操作,使用经过优化的快速排序算法 
  当数组的规模较小时,直接插入排序的比较次数并不会比快排或者归并多多少,其效率反而不如简单排序算法,所以在数组规模小于7时,使用直接插入排序, 
  当数组规模较大时,合理的选择快排的枢轴元素,如在规模小于40时,在数组的首,中,尾三个位置上的数,取中间大小的数做枢轴;在数组规模大于40时,从数组中取位置均匀分布的9个数,然后每三个数一组取中间数,最后三个中间数再取中间数。确定枢轴后,与数组的第一个元素交换,之后的快排与普通快排一样。 
  当数组中有大量重复元素时,选择重复元素作为枢轴,然后两个端各设置两个工作指针low、high,left、right用于始终指向要交换的元素位置,如5,2,5,6,4,3,5,1,5,7 
  从high开始判断,low <= high,若high位置的元素 >= 基准元素high–,同时若high位置的元素 == 基准元素,high位置的元素与right位置的元素交换,同时right–,继续直到high位置的元素 < 基准元素。 
  从low开始判断,low <= high,若low位置的元素 <= 基准元素low++,同时若low位置的元素 = 基准元素,low位置的元素与left位置的元素交换,同时left++,继续直到low位置的元素 > 基准元素。 
  low、high位置的元素交换,同时low++、high–,然后再从high开始继续上面的过程,最后将重复的元素至于序列的两端,中间的序列分成了两部分,左面的为小于基准元素的,右面的为大于基准元素的,如5,5,2,1,4,3,7,6,5,5,此时low在7位置,high在3位置。 
  将两端重复的元素都交换到中间后,对两端不等的元素使用快排,左侧外循环从下标0开始判断,若等于枢轴进入内循环,内循环从下标low - 1开始向前找不等于枢轴的,找到交换,直到外循环遇到不等于枢轴的退出;右侧外循环从下标n - 1开始判断,若等于枢轴进入内循环,内循环从下标high + 1开始向后找不等于枢轴的,找到交换,直到外循环遇到不等于枢轴的退出。 
   
  引用数据类型数组的排序,使用经过优化的归并排序算法。 
  当数组规模j较小时,使用直接插入排序。 
  当属组规模较大时,使用归并排序,且当合并的两个有序序列中,低子序列的最高元素小于高子序列的最低元素时,无序执行合并算法,这个可以在merge算法里判断。

问题:Array和ArrayList有何区别?什么时候更适合用Array?

Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
Array是指定大小的,而ArrayList大小是固定的。
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。
(1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。
(2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
(3)如果你要使用多维数组,使用[][]比List

网易CodeWave智能生成应用实战:5分钟搞定库存管理系统(附详细配置截图) 本文详细介绍了如何利用网易CodeWave智能生成应用在5分钟内快速构建定制化库存管理系统。通过自然语言描述需求,无需编程即可生成包含产品管理、库存预警等核心功能的应用,并支持字段微调、业务流程注入等深度优化。文章附有详细配置截图,帮助用户高效实现数字化转型。 阅读详情

相关推荐

NBIS单细胞教程:差异基因(五)

此文章是通过学习瑞典国家生物信息学基础设施(NBIS) 所开放的单细胞分析教程加上网上所查找的资料,自身的理解所形成的,可能会有不足之处。该部分是对不同集群之间的差异基因进行分析。参考来源:https://github.com/NBISweden/workshop-scRNAseq/blob/master/labs/seurat/seurat_05_dge.Rmd感兴趣的话可以阅读原文。

songyi10的博客 1万+

Java 中的 ArrayList 和 LinkedList

列表是一种数据结构,为了方便理解,我们可以认为它是动态的数组。众所周知,数组的大小在定义的时候就固定了,不可改变,那么它就无法存储超过它容量的数据,而列表的容量是无限大的,因为它可以随着其存储内容的大小进行动态的变化(包括容量扩增和缩小),这和 java.util.Vector 很像,但又不完全相同。Java 中对列表的实现有两种,ArrayList 和 LinkedList。

小康2022的博客 1931

OpenCASCADE(OCC)读取 STEP 模型文件并在MFC中显示

OpenCASCADE(OCC)读取 STEP 模型文件并在MFC中显示

weixin_61900468的博客 1万+

java面经01-基础篇-排序算法、ArrayList、Iterator、LinkedList、HashMap、单例模式

(capacity-1) 其实也是映射到 0~(capacity-1) 【因为capacity-1高位全部是0,&之后不会比capacity-1大,最最大也就是全1的二进制和capacity-1的二进制相与,最大也就是capacity-1】但是肯定有缺点,还记得否学数据结构Hash表时,数组容量(或者说mod的那个值)应该选取一个质数的,这样分布是最均匀的。使用二分法在序列 1,4,6,7,15,33,39,50,64,78,75,81,89,96 中查找元素 81 时,需要经过( )次比较。

树叶子的博客 570

制作Ubuntu22.04启动盘

现在越来越多程序代码都需要运行在linux环境下,运行这些代码的前提是安装一个稳定的linux操作系统。本文选择Ubuntu 22.04去制作系统启动盘。本文主要对ubuntu22.04的启动盘制作过程进行了详细的说明,希望能帮助到有需要的朋友。

lyx_ok的博客 1万+

从零打造音乐会级特雷门琴:原理、设计与调试全解析

电子乐器的工作原理通常基于信号生成与调制。特雷门琴作为世界上最早的电子乐器之一,其核心原理是利用射频振荡器和电容传感,将演奏者的手势变化转化为音高与音量信号。这种技术通过外差式混频实现音高控制,并利用压控放大器实现细腻的音量动态,其工程价值在于将抽象的空间位置信息精确映射为连续的音频参数。在音乐制作、声音艺术和交互装置等领域,这类技术为创造独特的演奏体验提供了基础。本文聚焦于如何从电路层面实现一台高稳定性的特雷门琴,详细阐述了从LC振荡器设计、天线场形优化到音色混合检波等关键环节,并分享了基于LTspice

weixin_34046298的博客 171

在Java中,ArrayList和LinkedList

在Java中,ArrayList和LinkedList

sanmansan的博客 495

XSS漏洞之PDF生成:从XSS到服务端任意文件读取

XSS是最为常见的Web漏洞之一,多年来连续入选OWASP TOP10,相信大家都耳熟能详。 它是一种代码注入类的攻击,是一种客户端侧的攻击,攻击者通过在Web应用中注入恶意JavaScript代码,通过点击URL,最终在受害者浏览器端执行的一种漏洞。主要有反射型XSS、存储型XSS、基于DOM的XSS三类。XSS一些比较常规的危害大概有:重定向浏览器、窃取Cookie、浏览器劫持等。本文章侧重于发现XSS后的进一步利用。 主要介绍在某次实际测试过程中,发现XSS漏洞之后,如何进一步的利用,最终通过XSS达

计算机毕业论文源码,学生个人网页制作html源码。贴近用户做网络推广和互联网优化。 3780

Java基础了解-10-ArrayList/LinkedList

ArrayList/LinkedList

Xiaowu2048的博客 407

用 SemanticObject 把字段变成可点击链接:Fiori Elements Navigation Using a Link 的工程化实践

SAP Fiori elements 通过 Common.SemanticObject 注解可将字段转为可点击链接,实现应用间跳转。当源字段名与目标属性名不一致时,需使用 Common.SemanticObjectMapping 进行映射。该功能依赖 Fiori Launchpad 的目标映射配置,确保正确解析 semantic object + action 并传递参数。常见误区包括未配置目标映射或字段映射错误,需逐一检查语义标注、intent 解析和参数传递链路。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 272

华为OD机试 - 物流仓储多维度成本利润综合查询系统 - 二维数组(Python/JS/C/C++ 新系统 100分)

本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。某物流企业在全国多个仓库开展仓储业务,需要对不同仓库、不同时段的入库成本、出库收入和库存损耗进行综合统计分析,用于生成 日报、周报和月报。字段说明及取值范围如下:warehouses: int[] 扁平化数组 [w0d0in, w0d0out,

学Java,找哪吒 309

(一)ArrayList和LinkedList的原理、Java代码实现、性能比较

一、ArrayList1.1、数组和集合的区别    动态大小,即数组的大小不可变,集合的大小可变。    ArrayList从名字上来讲是数组列表,表面上是动态大小,其底层实现原理其实还是一个数组。1.2、简单模拟ArrayList    模拟过程中要注意Array和ArrayList的区别,数组在乎的是能装多少,而ArrayList在乎的是已经装了多少,因为ArrayList要让使用者觉得长度...

zl_StepByStep的博客 2766

ESP32-C3入门教程 环境篇②——Windows开发环境准备

文章目录一、注意事项二、下载ESP-IDF工具安装器三、安装内容四、详细安装步骤报错处理 一、注意事项 请注意 ESP-IDF 和 ESP-IDF 工具的安装路径不能超过 90 个字符,安装路径过长可能会导致构建失败。 Python 或 ESP-IDF 的安装路径中一定不能包含空格或括号。 除非操作系统配置为支持 Unicode UTF-8,否则 Python 或 ESP-IDF 的安装路径中也不能包括特殊字符(非 ASCII 码字符、中文字符) 二、下载ESP-IDF工具安装器 ESP-IDF Win

5244

Java学习——ArrayList和LinkedList

ArrayList和LinkedList如果你需要频繁随机访问列表中的元素,或在列表末尾添加元素的操作较多,ArrayList是更好的选择。如果你的应用需要频繁在列表的开始或中间插入或删除元素,LinkedList可能更适合。

bairui6666的博客 1864

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

ArrayList 和LinkedList的区别

1、ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。2、对于随机访问,ArrayList优于LinkedList3、对于插入和删除操作,LinkedList优于ArrayList4、LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。ArrayList类实现了可变数组的大小,存储在内的数据成为元素。它提供了快速基于索引访问元素的方式,对尾部成员的增加和删除支持较好。

m0_73857228的博客 85

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

ArrayList和LinkedList中的get方法底层实现

1.ArrayList的get方法  因为底层是数组 直接通过下标获得 2.LinkedList的get方法  因为底层是链表,链表没有下标,需要迭代遍历: if (index > 1)) { Node x = first; for (int i = 0; i < index; i++) x =

yang505581644的博客 4303

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

ArrayList底层原理浅析

一. ArrayList和LinkedList异同 1. 相同点:      ArrayList 和LinkedList 都是List 接口下的实现类,相同于List接口,这两个实现类也同样是有序且不唯一(可重复)的集合类。 2. 不同点:       ArrayList 的底层仍然是使用数组(Object[] elementData)实现的,通过对数组操作的封装,简化了程序员编程中对集合的使用过程。ArrayList是List

weixin_44794112的博客 621

Java LinkedList 和 ArrayList

Java 手册 java.util 类 ArrayList<E> java.lang.Object java.util.AbstractCollection<E> java.util.AbstractList<E> java.util.ArrayList<E> 所有已实现的接口:Se...

weixin_30849591的博客 104

JAVA中的集合-ArrayList-LinkedList-HashSet-TreeSet-HashMap

此外,需要注意的是,由于 ArrayList 内部是一个数组,当元素数量超过数组容量时,需要进行扩容,扩容的时间复杂度为 O(n),因此需要在初始化 ArrayList 时设置一个较合理的容量,以避免不必要的扩容操作。ArrayList是非线程安全的。TreeSet底层是通过红黑树实现的,红黑树是一种自平衡二叉查找树,它的每个节点上都有一个存储的值和一些其他信息,节点通过指针链接在一起,根据节点的值来比较大小,满足二叉查找树的性质:左子树所有节点的值小于父节点的值,右子树所有节点的值大于父节点的值。

weixin_49031148的博客 1107

学习笔记(三):Java中的List集合——ArrayList、LinkedList、Vector、Stack、CopyOnWriteArrayList

List集合详解以及知识点汇总

weixin_52279910的博客 640

ArrayList 和LinkedList区别

ArrayList 基于动态数组实现,LinkedList 基于双向链表实现; ArrayList 支持随机访问,LinkedList 不支持; LinkedList 在任意位置添加删除元素更快。

mywaya2333的博客 487

JAVA 集合之 LinkedList

LinkedList 前言 本文作为我学习 Java 集合 LinkedList 的一个记录与总结,如有疏漏或不足之处欢迎指出共同进步! 一、LinkedList 简介 1.1 LinkedList 概述 LinkedList 是可以动态增长或缩减的索引序列,其底层是基于双向链表实现的 LinkedList 类内部维护了一个双向链表,因此可以完成高效的的插入和删除,但由于顺序存取的原因其查询效率不如 ArrayList ,后者可以通过 index 直接取得相应的值。 LinkedList 与 A

an_vana的博客 1655

【集合】底层原理实现及各集合之间的区别

本文章由浅入深介绍了java集合的底层原理实现以及各集合之间的区别、对比。如HashMap、HashTable、ConcurrentHashMap的区别、ArrayList和LinkedList的区别等。

QiuYanping_的博客 819

Java集合框架之ArrayList与LinkedList详解

TypeScript是由微软开发的开源编程语言,作为JavaScript的超集,它添加了静态类型系统和面向对象特性,适用于大型项目开发。核心优势包括编译时类型检查、增强代码可维护性和IDE智能提示。与JavaScript相比,TypeScript需要编译为JS才能运行,并支持接口、泛型等高级特性。环境搭建可通过npm安装TypeScript和ts-node工具,使用tsconfig.json配置文件管理编译选项。TypeScript特别适合团队协作的大型前端/Node.js项目,能显著提升开发效率和代码质量

小罗的博客 861

264. Java 集合 - 插入元素性能对比:LinkedList vs ArrayList

本文对比了Java中LinkedList和ArrayList在不同位置插入元素的性能表现。LinkedList在头部和尾部插入效率稳定(O(1)),但中间插入需要遍历节点,性能较差(O(n))。ArrayList尾部插入高效,但头部和中间插入需要移动元素,扩容时成本更高。总结:高频尾部插入推荐ArrayList;频繁头尾操作选LinkedList;中间插入应避免;可预知大小时ArrayList更优。

yaoxin521123的博客 908

面试题 ArrayList和LinkedList区别

相同点:ArrayList与LinkedList都是不同步的(Vector同步),都是线程不安全的。 不同点: 1、(底层数据结构不同)ArrayList底层数据结构是Object数组,LinkedList底层数据结构是双向链表。 2、(主要控件开销不同)ArrayList主要为List列表的结尾预留一定的容量空间;LinkedList主要内存花费在存储指针和节点上。 3、(插入和删除是否受元素位置影响)ArrayList采用数组存储,插入和删除元素时间复杂度受元素位置影响;LinkedList采用

JAMES BLOG 1260
上一篇: [java] 深度解析容器
下一篇: [Java] String类深度解析
topEngineerr
博客等级 码龄11年 627粉丝 157原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值