List概述

List是一个有序,可重复的集合,可以在List的中间插入和移除元素,根据整数索引访问元素

下图是List集合的框架图

下面是对上图的简单介绍

  1. AbstractCollection: 提供 Collection 接口的骨干实现
  2.  
  3. Iterator: 迭代器
  4. ListIterator:列表迭代器
  5.  
  6. Queue:队列
  7. Deque:一个线性 collection,支持在两端插入和移除元素
  8.  
  9. AbstractSequentialList:提供了 List 接口的骨干实现
  10. LinkedList:链表的实现
  11.  
  12. ArrayList:大小可变数组的实现
  13.  
  14. Vector:实现可增长的对象数组
  15. Stack:后进先出(LIFO)的对象堆栈

ArrayList

底层存储

属性 elementData 存储集合中的内容

  1. /** 缓存区:存储元素 */
  2. private transient Object[] elementData;

将元素添加到指定位置

将元素添加到指定位置后, 把原数组中 该位置和之后的元素 向后移动一位

  1. /** 在指定位置添加元素 */
  2. public void add(int index, E element) {
  3. rangeCheckForAdd(index); //判断索引位置是否正确
  4.  
  5. ensureCapacityInternal(size + 1); // 扩容
  6.  
  7. //对原数组进行复制处理(位移),从index + 1到size-index
  8. //即向右移动当前位于该位置的元素以及所有后续元素。
  9. System.arraycopy(elementData, index, elementData, index + 1,
  10. size - index);
  11. elementData[index] = element; //插入
  12. size++;
  13. }

介绍下 System.arraycopy(......)

  1. /**
  2. 即从指定源数组中复制一个数组,
  3. 复制从指定的位置开始,到目标数组的指定位置结束。
  4. 将原数组src从srcPos位置开始复制到dest数组中,到dest的destPos位置开始,复制的长度为length
  5. */
  6. public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

删除指定位置的元素

删除元素后, 把原数组中 该位置之后的元素 向前移动一位

  1. public E remove(int index) {
  2. rangeCheck(index); //判断移动位置
  3.  
  4. modCount++; //记录改变次数
  5. E oldValue = elementData(index); //要删除的元素
  6.  
  7. int numMoved = size - index - 1; //移动的长度
  8.  
  9. //向左移动numMoved个长度
  10. if (numMoved > 0)
  11. System.arraycopy(elementData, index+1, elementData, index,
  12. numMoved);
  13. elementData[--size] = null; //把最后一个元素设为null
  14.  
  15. return oldValue;
  16. }

扩容

首先扩容为原来的1.5倍, 在检查新容量,  最大容量为Integer.MAX_VALUE

  1. private void grow(int minCapacity) {
  2. ...
  3. int newCapacity = oldCapacity + (oldCapacity >> 1); //将新的容量变为原来容量的1.5倍。
  4. ...
  5. hugeCapacity(int minCapacity);
  6. }
  7.  
  8. //对扩容后的容量进行检查
  9. private static int hugeCapacity(int minCapacity) {
  10. if (minCapacity < 0) //得到的容量<0,为什么会有这种情况,内存溢出了。
  11. throw new OutOfMemoryError();
  12. return (minCapacity > MAX_ARRAY_SIZE) ? //如果需要的容量比规定的最大容量大,那么最大容量只能是 Integer.MAX_VALUE。
  13. Integer.MAX_VALUE :
  14. MAX_ARRAY_SIZE;
  15. }

动态扩容,其实是一个新数组; 在清楚业务,插入的数据量大于扩容后的1.5倍, 应该自己设置一个合适容量

Vector 和 Stack

概述

Vector可以看做ArrayList的同步实现, 具体可参考ArrayList的分析

Stack类提供了栈的结构, 表示先进后出的操作方法

Stack 继承 Vector , 它定义了5个方法并且调用的都是父类中的方法, 下面是方法定义

  1. E push(E item) //把对象压入栈顶部, 内部调用是父类的 addElement(item)
  2. E pop() //移除栈顶对象,内部调用是父类的 removeElementAt(int index)
  3. E peek() //查看栈顶的对象,内部调用是父类的 elementAt(int index)
  4. int search(Object o) // 返回对象在堆栈中的位置,内部调用父类lastIndexOf(Object o)
  5. empty() // 测试堆栈是否为空

自定义Stack

  1. /**
  2. * 自定义Stack
  3. */
  4. public class MyStack {
  5. private static final int CAPACITY = 3;
  6. private static Object[] array = new Object[CAPACITY];
  7. private static int top = -1;
  8.  
  9. /** 把对象压入栈顶 */
  10. void push(Object o) throws Exception {
  11. if(getSize() == CAPACITY) {
  12. throw new ExceptionStack("stack is full");
  13. }
  14.  
  15. array[++top] = o;
  16. }
  17.  
  18. /** 移除栈顶元素 */
  19. Object pop() throws Exception {
  20. if(isEmpty()) {
  21. throw new ExceptionStack("stack is empty");
  22. }
  23. return array[top--];
  24.  
  25. }
  26.  
  27. /** 查看栈顶元素 */
  28. Object peek() throws Exception {
  29. if(isEmpty()){
  30. throw new ExceptionStack("Stack is empty");
  31. }
  32. return array[top];
  33. }
  34.  
  35. /** 判断尺寸已满 */
  36. int getSize() {
  37. if(isEmpty()){
  38. return 0;
  39. }else{
  40. return top + 1;
  41. }
  42. }
  43.  
  44. /** 判断为空 */
  45. boolean isEmpty() {
  46. return (top < 0);
  47. }
  48.  
  49. public static void main(String[] args) throws Exception {
  50. MyStack s = new MyStack();
  51. System.out.println(s.isEmpty());
  52.  
  53. s.push("1");
  54. s.push("2");
  55. s.push("3");
  56.  
  57. System.out.println(s.isEmpty());
  58. System.out.println(s.getSize());
  59. System.out.println(s.pop());
  60. System.out.println(s.peek());
  61. }
  62. }

自定义异常类:

  1. public class ExceptionStack extends Exception{
  2.  
  3. //Define myself exception construct with parameters
  4. public ExceptionStack(String string){
  5. super(string);
  6. }
  7. }

LinkedList

LinkedList 的内部实现是双向链表,它允许null的存在,它的方法是不同步的

与ArrayList相比,在插入和删除时优于ArrayLis, 而随机访问则比ArrayList逊色些

主要属性

  1. transient int size = 0; //集合长度
  2. transient Node<E> first; //头节点
  3. transient Node<E> last; //尾节点

介绍下静态内部类Node

  1. private static class Node<E> {
  2. E item; //原节点
  3. Node<E> next; //指向后一个节点
  4. Node<E> prev; //指向前一个节点
  5.  
  6. //构造方法
  7. Node(Node<E> prev, E element, Node<E> next) {
  8. this.item = element;
  9. this.next = next;
  10. this.prev = prev;
  11. }
  12. }

构造方法

分析下:  LinkedList(Collection<? extends E> c)  构造一个包含指定 collection 中的元素的列表

最终调用的是  addAll(..) ,如下:

  1. public boolean addAll(int index, Collection<? extends E> c) {
  2. checkPositionIndex(index); //判断下表越界
  3.  
  4. Object[] a = c.toArray(); //把c转为数组
  5. int numNew = a.length; //numNew为数组长度
  6. if (numNew == 0)
  7. return false;
  8.  
  9. Node<E> pred, succ;
  10.  
  11. if (index == size) { //在构造的调用过程中,肯定是相等的
  12. succ = null;
  13. pred = last; //pred指向尾节点
  14. }
  15.  
  16. /* 注释的是构造时不执行的
  17. else {
  18. succ = node(index);
  19. pred = succ.prev;
  20. }
  21. */
  22.  
  23. for (Object o : a) {
  24. E e = (E) o; //从数组中取出元素
  25. Node<E> newNode = new Node<>(pred, e, null); //创建新节点
  26.  
  27. /*
  28. if (pred == null)
  29. first = newNode;
  30. */
  31. else
  32. pred.next = newNode; //原尾节点的后一个节点指向新节点
  33. pred = newNode; //pred成了新节点
  34. }
  35.  
  36. if (succ == null) {
  37. last = pred; //last是添加c后的尾节点
  38. }
  39.  
  40. /*
  41. else {
  42. pred.next = succ;
  43. succ.prev = pred;
  44. }
  45. */
  46.  
  47. size += numNew; //修改容量
  48. modCount++; //修改次数+1
  49. return true;
  50. }

node(int index) 返回指定位置的节点

node(int index) 很重要, 添加,删除等操作都会用到, 下面就介绍它

  1.   /**
  2. 返回指定位置的节点
  3. */
  4. Node<E> node(int index) {
  5.  
  6. //判断遍历的方向
  7. if (index < (size >> 1)) { // size >> 1 = siz2 /2
  8. Node<E> x = first; //从指定位置的节点开始往前遍历
  9. for (int i = 0; i < index; i++)
  10. x = x.next;
  11. return x;
  12. } else {
  13. Node<E> x = last; //从尾节点开始向指定位置遍历
  14. for (int i = size - 1; i > index; i--)
  15. x = x.prev;
  16. return x;
  17. }
  18. }

添加

add(E e ): 把节点加到链表的最后 ,实际上调用的是linkLast(E e)方法

  1. void linkLast(E e) {
  2. final Node<E> l = last; //把末尾节点保存
  3. final Node<E> newNode = new Node<>(l, e, null); //创建一个新节点,并指定前一个节点是原链表的末尾节点
  4. last = newNode; //最后一个节点的引用指向新节点
  5. if (l == null)
  6. first = newNode;
  7. else
  8. l.next = newNode; //原链表的末尾节点的下一个节点指向新节点
  9. size++; //链表总数+1
  10. modCount++;
  11. }

add(int index, E element): 把数据插入指定的位置,实际上调用的是 linkBefore(element, node(index))

  1. /**
  2. 参数1: 代表新元素
  3. 参数2: succ 代表指定位置的节点
  4. */
  5. void linkBefore(E e, Node<E> succ) {
  6.  
  7. final Node<E> pred = succ.prev; //把指定位置的前一个节点保存在pred
  8. final Node<E> newNode = new Node<>(pred, e, succ); //创建新节点
  9. succ.prev = newNode; //指定新节点的后一个节点
  10. if (pred == null)
  11. first = newNode;
  12. else
  13. pred.next = newNode; //指定新节点的前一个节点
  14. size++;
  15. modCount++;
  16. }

删除

remove(Object o):  从此列表中移除首次出现的指定元素, 实际上调用的是 unlink(Node<E> x)

  1. /**
  2. 参数: 要移除的节点
  3. */
  4. E unlink(Node<E> x) {
  5.  
  6. final E element = x.item;
  7. final Node<E> next = x.next; // 保存 移除节点 的后一个节点 在变量next中
  8. final Node<E> prev = x.prev; //保存 移除节点 的前一个节点 在变量prev中
  9.  
  10. if (prev == null) { //true代表 移除节点 是头节点
  11. first = next;
  12. } else {
  13. prev.next = next; // 将 prev后一个节点 指向next
  14. x.prev = null; // 将 移除节点的prev设为空
  15. }
  16.  
  17. if (next == null) { //true代表移除节点是尾节点
  18. last = prev;
  19. } else {
  20. next.prev = prev; //将 next前一个节点 指向prev
  21. x.next = null; //将 移除节点的next 设为空
  22. }
  23.  
  24. x.item = null; //将移除节点的元素 设为空
  25. size--;
  26. modCount++;
  27. return element; //返回移除的元素
  28. }

总结

对LinkedList的操作实际上是对指向前节点和后节点的引用操作,所以其插入和删除效率较高,但是随机访问效率较差,因为要遍历

Java集合源码 -- List列表的更多相关文章

  1. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  2. Java集合源码学习(一)集合框架概览

    >>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...

  3. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  4. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  5. Java 集合源码分析(一)HashMap

    目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...

  6. java集合源码分析几篇文章

    java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915

  7. java集合源码分析(三):ArrayList

    概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...

  8. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  9. Java集合源码学习(三)LinkedList分析

    前面学习了ArrayList的源码,数组是顺序存储结构,存储区间是连续的,占用内存严重,故空间复杂度很大.但数组的二分查找时间复杂度小,为O(1),数组的特点是寻址容易,插入和删除困难.今天学习另外的 ...

随机推荐

  1. [转]C# 6.0 的新特性

    本文的内容包括引入C#6.0中的新的语言特性有哪些. 还有已经被引入的代码名称为 “Roslyn”新编译器. 编译器是开放源码的,并且可以从 codeplex 网站的这个地址下载到源代码: https ...

  2. JavaScript迭代

    定义: 指的是按照某种顺序逐个访问列表中的每一项. 迭代在数学中的定义: 在循环的基础上, 每一次循环, 都比上一次更为接近结果. 循环定义:指的是在满足条件的情况下,重复执行同一段代码. 迭代方法: ...

  3. Java学习--Java 中的包装类

    Java 中的包装类 相信各位小伙伴们对基本数据类型都非常熟悉,例如 int.float.double.boolean.char 等.基本数据类型是不具备对象的特性的,比如基本类型不能调用方法.功能简 ...

  4. C#实现局部峰值查找,功能对应Matlab中的findpeaks.m

    相关算法的原理参考Ronny,地址:图像分析:投影曲线的波峰查找,这里感谢下原作者. 参照C++的代码实现,我用C#翻译了下,其实原理也很简单的,下面放相关实现代码: private double[] ...

  5. jvm工具及命令大全

      虚拟机栈 栈桢大小缺省为1M,可用参数 –Xss调整大小,例如-Xss256k 堆 -Xms:堆的最小值: -Xmx:堆的最大值: -Xmn:新生代的大小: -XX:NewSize:新生代最小值: ...

  6. shell文本操作

    一.find查找命令的使用 1.find . -name "*.txt" 在当前目录下,查找以txt结尾的文件 2.find . -name "[a-z]" 在 ...

  7. Java反射学习总结

    我开始学习反射的初衷是为了理解Spring 里的控制反转,其次可以利用反射来达到类中的解耦. 自己写的一些心得,希望能帮到大家 1.反射指的是对象的反向处理操作,是根据对象来取得对象的来源信息. 反射 ...

  8. Lua脚本语言基础知识

      注释 在Lua中,你可以使用单行注释和多行注释. 单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止.相当于C++语言中的"//". 多行注 ...

  9. Jenkins2.138配置slave节点时,启动方法只有两个选项

    Jenkins2.138配置slave节点时,启动方法只有两个选项,并没有通过javaweb代理启动这个选项 解决办法 全局安全配置->代理->选择随机选取

  10. 解决:IDEA 中 new Java Class 怎么没了?

    前言:写代码时遇到的问题,所以记录下来.我的包名为“interface”,只有这个包及包下的文件不能建java文件. 问题 解决方式(对于普通包名) 点击应用,ok就可以了. 解决方式(对于包名为“i ...