ArrayList与LinkedList源码解析
来源:互联网 发布:表达知错悔恨的古诗 编辑:程序博客网 时间:2024/06/05 08:05
ArrayList与LinkedList源码解析
相信使用java的同仁对这个都不陌生, 在java中我们最长使用的数据结构就是List与Map了, 而今天我们就来看看List中最常用的两种实现。
1. 首先我们来看ArrayList集合, 他的类层级图如下:
1). 首先我们需要了解ArrayList的数据结构模型为:
struct ArrayList {
static final int DEFAULT_CAPACITY = 10;static finalObject[]EMPTY_ELEMENTDATA={};
static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};Object[]elementData;
int size;
}
从这个结构中我们可以知道ArrayList的结构是以数组的形式进行操作的。
2). 然后我们一般就需要关心数据结构的增删查改操作了, 数据结构主要的操作就是增删查改。
(1). 首先看一下ArrayList的add操作:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }这段代码的增加操作和普通的数据操作一样, 关键在于ensureCapacityinternal()这个方法, 该方法会在ArrayList长度不够时进行对应的扩容,具体源代码如下:
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); }
在我们进入ensureCapacityInternal()方法去时,我们最后发现grow()才是真正进行操作的方法从这上面来看, 我们到一般情况下扩容的时候计算方法为增加原有数组的1/2的长度。 而且我们从第二个If中我们可以知道ArrayList的限制为Integer的最大值2147483647,当然我们的对象不可能这么多, 不然服务器不爆都难。然后使用Arrays.copyOf来进行扩容。
(2).其他的删改查都很简单, 这里就不进行介绍了。
2. LinkedList的类层次结构图:
从这个图中我们知道,ArrayList和LinkedList都继承了最左边这个分支类层级, 他们中有些通用的方法都来自这个实现结构。
1). 首先来了解LinkedList的数据结构:
struct Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
struct LinkedList {
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
}
从这个结构中我们可以知道, LinkedList使用的是双向链表来进行数据存储的。
2). 现在我们来看看它的相关操作:
(1). add操作
public boolean add(E e) { linkLast(e); return true; }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的长度是没有限制的。对比ArrayList与LinkedList的add方法发现: 当使用ArrayList新增数据时, 我们要进行对应的数据移动工作, 它会先创建一个扩容后的数组, 将原数组数据拷贝到新数据中; 而我们在使用LinkedList的时候,则是直接在链表尾部追加新元素。 很明显, LinkedList的add操作比ArrayList的操作要便利很多, 性能更加。所以当我们是List时, 如果该List有频繁的增删改时, 推荐使用LinkedList操作。
(2). get操作
public E get(int index) { checkElementIndex(index); return node(index).item; }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; } }这就是它的get操作, 从该操作来看, 它先看当前索引是靠近头部还是尾部, 然后进行链表的遍历进行查找。 从LinkedList和ArrayList的get中我们可以知道,由于ArrayList采用数据方式存储数据, 该种存储是连续的,所以查找数据时不需要遍历数组就可以根据下标获得对应数据, 而LinkedList则是使用链表, 该种方式存储数据不是连续的, 当我们需要查找数据时, 需要对整个链表进行遍历; 可以知道, 当数据靠近链表中部时, 我们最快查找也需要size/2次才能查到。 所以当我们在使用List进行数据存储,更多的操作是查找的时候, 推荐使用ArrayList。
(3). remove操作:
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; }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; }上面的代码时根据对象值进行删除的, 因为根据索引删除的基本就是unlink在操作, 所以没有贴出代码, 从上面可以看到当使用对象值进行删除时, 所有的值相同的对象都会被删除; 而我们使用索引删除的时候, 只是删除对应索引位置的对象。在这点上, ArrayList和LinkedList的做法也是一样的。
(3). set操作: 我们想替换一个对象的时候使用的是set方法进行操作。
public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
set操作通过node方式找到对应的节点, 然后替换调值即可。
(4). contains操作:
public boolean contains(Object o) { return indexOf(o) != -1; }public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
为什么在这里要讲contains方法, 当我们在使用List并且想去重的时候, 我们就可以使用这个方法, 但是我们观察一下当前这个方法, 它会找到第一个值相等的元素并且返回对应的索引下标, 而在它进行对比是,它调用了当前对象的equals方法, 这个方法是关键。 所以在我们需要使用List的contains的时候, 我们需要重写当前对象对应类的equals方法, 让对应的比较按我们的需求进行。
总结: 在介绍对应源码时, 主要介绍了LinkedList, 这里并不是说它更重要, 而是因为ArrayList的是采用数组进行保存的, 对于数据的操作相比开发人员都很清楚, 所以就集中精力分析了下LinkedList。 从这些分析中知道:
1. ArrayList采用的是数组方式进行数据保存, 而LinkedList采用的是双向链表进行保存。
2. 从它们的操作实现中, 我们可以知道在查询比较多的情况下使用ArrayList, 增删改频繁的时候使用LinkedList是比较不错的选择。
3. 在我们使用pojo时, 最好别忘记了重写equals方法, 防止以后比较会使用到。
- ArrayList与LinkedList源码解析
- ArrayList LinkedList源码解析
- JAVA拾遗 - ArrayList\LinkedList\HashMap源码解析与阅读
- ArrayList与LinkedList源码分析
- arraylist arraylist使用实例 linkedlist linkedlist源码解析 linkedlist和arraylist的区别
- 【JDK】:ArrayList和LinkedList源码解析
- java ArrayList 和 LinkedList 源码解析比较
- JDK源码解析之ArrayList和LinkedList
- JDK源码学习之Arraylist与LinkedList
- LinkedList源码解析与学习
- ArrayList LinkedList 源码分析
- ArrayList,LinkedList源码分析
- ArrayList源码解析与实现
- ArrayList源码解析与学习
- Java ArrayList与LinkedList源码分析与比较
- .NET Framework源码研究系列之---ArrayList与LinkedList
- Java集合框架之List---ArrayList与LinkedList源码分析
- Java中arraylist和linkedlist源码分析与性能比较
- Java源码——文件/文件夹的路径分析(相对/绝对)(File and Directory Info Inquiry)
- Android神技之 使用SVG以及自定义IconFont字体库
- QT程序,如何在ARM板上运行
- Matlab的mapminmax函数说明
- 关于revit二次开发中,用winform剖切房间进行房间定位的问题
- ArrayList与LinkedList源码解析
- SpringBoot非官方教程 | 第九篇: springboot整合Redis
- 两招让你成为牛X的T型人才
- 从客户端中检测到有潜在危险的 Request.Form 值的详细解决方法
- JavaScript函数,作用域以及闭包
- jQuery 来了--动画,停止动画,回调函数Callback,链
- Java运算符和原反补码
- 欢迎使用CSDN-markdown编辑器
- PHP随笔