最近正准备回顾一下Java,所以在此做一些记录。
1.LinkedList使用的是链表结构,先看一下节点的定义
1 /** 2 * 3 * 连接的节点 4 */ 5 private static class Node<E> { 6 //保存的数据 7 E item; 8 //后置节点 9 Node<E> next; 10 //前置节点 11 Node<E> prev; 12 //构造方法 13 Node(Node<E> prev, E element, Node<E> next) { 14 this.item = element; 15 this.next = next; 16 this.prev = prev; 17 } 18 }
2.add(E e) 添加一个元素
1 /** 2 * 添加一个元素 3 * 4 * @param e 添加的元素 5 */ 6 public boolean add(E e) { 7 //调用添加到末尾 8 linkLast(e); 9 return true; 10 } 11 12 13 /** 14 * 添加一个元素到链表的尾部 15 */ 16 void linkLast(E e) { 17 //获取链表的尾部节点 18 final Node<E> l = last; 19 //创建新的节点,前置节点为last,后置节点为空 20 final Node<E> newNode = new Node<>(l, e, null); 21 //重新设置尾部节点 22 last = newNode; 23 //如果之前的尾部节点为空,说明之前没有元素 24 if (l == null) 25 //设置头节点也为空 26 first = newNode; 27 else 28 //如果之前的尾部节点不为空,则为之前的尾部节点追加后置节点 29 l.next = newNode; 30 //数量加一 31 size++; 32 modCount++; 33 } 34
3.add(int index, E element) 添加一个元素到指定位置
1 /** 2 * 添加一个元素到指定位置 3 * 4 * @param index 指定的位置 5 * @param element 添加的元素 6 */ 7 public void add(int index, E element) { 8 //检查指定的位置是否越界 9 checkPositionIndex(index); 10 //如果插入的是尾部,直接调用插入尾部的方法 11 if (index == size) 12 linkLast(element); 13 else 14 //需要先找到指定位置的节点 15 // 插入到该节点之前 16 linkBefore(element, node(index)); 17 } 18 19 //检查是否越界 20 private void checkPositionIndex(int index) { 21 if (!isPositionIndex(index)) 22 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 23 } 24 25 private boolean isPositionIndex(int index) { 26 return index >= 0 && index <= size; 27 } 28 29 /** 30 * 查询指定位置的节点 31 */ 32 Node<E> node(int index) { 33 // 类似于二分查询 34 // 如果位置小于集合大小的一半,从头节点开始遍历 35 // 否则从尾部节点开始遍历 36 37 if (index < (size >> 1)) { 38 Node<E> x = first; 39 for (int i = 0; i < index; i++) 40 x = x.next; 41 return x; 42 } else { 43 Node<E> x = last; 44 for (int i = size - 1; i > index; i--) 45 x = x.prev; 46 return x; 47 } 48 } 49 50 /** 51 * 在某个节点之前进行插入 52 */ 53 void linkBefore(E e, Node<E> succ) { 54 // 获取当前节点的前置节点 55 final Node<E> pred = succ.prev; 56 //保存创建的新节点 57 final Node<E> newNode = new Node<>(pred, e, succ); 58 //将新节点设置为当前位置节点的前置节点 59 succ.prev = newNode; 60 //如果当前位置节点的前置节点为空,则说明当前位置为头节点 61 if (pred == null) 62 //重新设置头节点 63 first = newNode; 64 else 65 //将当前位置节点的前置节点的后置节点设置为新节点 66 pred.next = newNode; 67 //数量增加 68 size++; 69 modCount++; 70 }
4.get(int index) 获取指定位置的元素
1 /** 2 * 获取指定位置的元素 3 * 4 * @param index 指定的位置 5 * @return 查询到的元素 6 */ 7 public E get(int index) { 8 //判断是否越界 9 checkElementIndex(index); 10 //查询到节点的值 11 return node(index).item; 12 } 13 14 //检查是否越界 15 private void checkElementIndex(int index) { 16 if (!isElementIndex(index)) 17 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 18 } 19 20 private boolean isElementIndex(int index) { 21 return index >= 0 && index < size; 22 } 23 24 /** 25 * 查询指定位置的节点 26 */ 27 Node<E> node(int index) { 28 // 类似于二分查询 29 // 如果位置小于集合大小的一半,从头节点开始遍历 30 // 否则从尾部节点开始遍历 31 32 if (index < (size >> 1)) { 33 Node<E> x = first; 34 for (int i = 0; i < index; i++) 35 x = x.next; 36 return x; 37 } else { 38 Node<E> x = last; 39 for (int i = size - 1; i > index; i--) 40 x = x.prev; 41 return x; 42 } 43 }
5.remove(Object o) 删除指定的元素
1 /** 2 * 删除指定的元素 3 * 4 * @param o 需要删除的元素 5 */ 6 public boolean remove(Object o) { 7 //判断需要删除的元素是否为空 8 if (o == null) { 9 //如果为空,从头节点开始遍历 10 for (Node<E> x = first; x != null; x = x.next) { 11 if (x.item == null) { 12 //删除节点 13 unlink(x); 14 return true; 15 } 16 } 17 } else { 18 //删除的元素不为为空,也是头节点开始遍历 19 for (Node<E> x = first; x != null; x = x.next) { 20 if (o.equals(x.item)) { 21 //删除节点 22 unlink(x); 23 return true; 24 } 25 } 26 } 27 return false; 28 } 29 30 /** 31 * 删除一个节点 32 */ 33 E unlink(Node<E> x) { 34 // 获取删除节点的值 35 final E element = x.item; 36 //获取删除节点的后置节点 37 final Node<E> next = x.next; 38 //获取删除节点的前置节点 39 final Node<E> prev = x.prev; 40 41 //判断前置节点是否为空 42 if (prev == null) { 43 //为空则其后置节点晋升为头节点 44 first = next; 45 } else { 46 //修改前置节点的后置节点 47 prev.next = next; 48 //将删除节点的前置节点置空 49 x.prev = null; 50 } 51 52 //判断后置节点是否为空 53 if (next == null) { 54 //后置节点为空,则其前置节点修改为尾部节点 55 last = prev; 56 } else { 57 //后置节点的前置节点修改 58 next.prev = prev; 59 //删除节点的后置节点置空 60 x.next = null; 61 } 62 //删除节点的值设置为空 63 x.item = null; 64 //数量减1 65 size--; 66 modCount++; 67 return element; 68 }
6.set(int index, E element) 在指定位置节点设置值
1 /** 2 * 在指定位置节点设置值 3 * 4 * @param index 指定的位置 5 * @param element 元素值 6 */ 7 public E set(int index, E element) { 8 //检查位置是否越界 9 checkElementIndex(index); 10 //查询到当前位置的节点 11 Node<E> x = node(index); 12 //获取旧值 13 E oldVal = x.item; 14 //设置新值 15 x.item = element; 16 //返回旧值 17 return oldVal; 18 } 19 20 /** 21 * 查询指定位置的节点 22 */ 23 Node<E> node(int index) { 24 // 类似于二分查询 25 // 如果位置小于集合大小的一半,从头节点开始遍历 26 // 否则从尾部节点开始遍历 27 28 if (index < (size >> 1)) { 29 Node<E> x = first; 30 for (int i = 0; i < index; i++) 31 x = x.next; 32 return x; 33 } else { 34 Node<E> x = last; 35 for (int i = size - 1; i > index; i--) 36 x = x.prev; 37 return x; 38 } 39 }
7.indexOf(Object o) 查询指定元素值所在位置,lastIndexOf也一样,只是从尾部节点开始查询
1 /** 2 * 查询指定元素值所在位置 3 * 4 * @param o 元素值 5 */ 6 public int indexOf(Object o) { 7 int index = 0; 8 //判断查询的值是否为null 9 if (o == null) { 10 //从头节点开始遍历查询 11 for (Node<E> x = first; x != null; x = x.next) { 12 if (x.item == null) 13 return index; 14 index++; 15 } 16 } else { 17 //从头节点开始遍历查询 18 for (Node<E> x = first; x != null; x = x.next) { 19 if (o.equals(x.item)) 20 return index; 21 index++; 22 } 23 } 24 return -1; 25 }
8.peek() 获取头节点的元素值,但不删除头节点
poll() 获取头节点的元素值,并删除头节点
pop() 获取头节点的元素值,并删除头节点,头节点为空则抛出异常
1 /** 2 * 获取头节点的元素值,但不删除头节点 3 * 4 */ 5 public E peek() { 6 final Node<E> f = first; 7 return (f == null) ? null : f.item; 8 } 9 10 /** 11 * 获取头节点的元素值,并删除头节点 12 */ 13 public E poll() { 14 final Node<E> f = first; 15 return (f == null) ? null : unlinkFirst(f); 16 } 17 18 /** 19 * 删除头节点 20 */ 21 private E unlinkFirst(Node<E> f) { 22 //获取节点的值 23 final E element = f.item; 24 //获取到后置节点 25 final Node<E> next = f.next; 26 //清空节点的值和后置节点 27 f.item = null; 28 f.next = null; 29 //重新设置头节点 30 first = next; 31 //判断后置节点是否为null 32 if (next == null) 33 //说明只有这么一个节点,尾部节点设置null 34 last = null; 35 else 36 //已经设置为头节点了,所以清空前置节点 37 next.prev = null; 38 //数量减1 39 size--; 40 modCount++; 41 return element; 42 } 43 44 /** 45 * 获取头节点的元素值,并删除头节点 46 */ 47 public E pop() { 48 return removeFirst(); 49 } 50 51 /** 52 * 删除头节点 53 */ 54 public E removeFirst() { 55 final Node<E> f = first; 56 //头节点为空则抛出异常 57 if (f == null) 58 throw new NoSuchElementException(); 59 //删除头节点 60 return unlinkFirst(f); 61 }
9.offer(E e) 添加新元素到末尾
push(E e) 添加新元素到头节点
1 /** 2 * 添加新元素到尾部 3 * 4 * @param e 新元素 5 */ 6 public boolean offer(E e) { 7 return add(e); 8 } 9 10 11 /** 12 * 添加一个元素 13 * 14 * @param e 添加的元素 15 */ 16 public boolean add(E e) { 17 //调用添加到末尾 18 linkLast(e); 19 return true; 20 } 21 22 /** 23 * 添加一个元素到链表的尾部 24 */ 25 void linkLast(E e) { 26 //获取链表的尾部节点 27 final Node<E> l = last; 28 //创建新的节点,前置节点为last,后置节点为空 29 final Node<E> newNode = new Node<>(l, e, null); 30 //重新设置尾部节点 31 last = newNode; 32 //如果之前的尾部节点为空,说明之前没有元素 33 if (l == null) 34 //设置头节点也为空 35 first = newNode; 36 else 37 //如果之前的尾部节点不为空,则为之前的尾部节点追加后置节点 38 l.next = newNode; 39 //数量加一 40 size++; 41 modCount++; 42 } 43 44 /** 45 * 添加新元素到头节点 46 * 47 * @param e the element to push 48 * @since 1.6 49 */ 50 public void push(E e) { 51 addFirst(e); 52 } 53 54 55 /** 56 * 添加元素到头节点 57 * 58 * @param e 新增的元素 59 */ 60 public void addFirst(E e) { 61 //添加元素到头节点 62 linkFirst(e); 63 } 64 65 /** 66 * 添加元素到头节点 67 */ 68 private void linkFirst(E e) { 69 //获取当前的头节点 70 final Node<E> f = first; 71 //创建新节点 72 final Node<E> newNode = new Node<>(null, e, f); 73 //设置新节点为头节点 74 first = newNode; 75 //判断头节点是否为空 76 if (f == null) 77 //头节点为空,说明之前没有节点,新节点同时为尾部节点 78 last = newNode; 79 else 80 //头节点不为空,则头节点的前置节点指向新节点 81 f.prev = newNode; 82 //数量加一 83 size++; 84 modCount++; 85 }
总结一下
1.LinkedList作为双向链表,维护了头尾节点,头尾节点的插入比较方便,中间数据的插入需要遍历查询再做插入
2.查询指定位置时,使用了一次二分,最多需要遍历一半的节点