- LinkedList和ArrayList相比较,LinkedList适合做增删,而ArrayList适合做查询。因为ArrayList的底层是数组实现的,添加和删除操作会利用拷贝技术实现,LinkedList的底层是用链表为底层实现的。
- ArrayList会考虑数组的扩容问题,但是LinkedList不需要考虑,因为底层是链表实现的。
- ArrayList是数组实现,所以是连续的内存;但是LinkedList是链表实现,所以内存不连续。
- Map集合在JDK1,7是使用数组+链表
- Map集合在JDK1.8是使用数组+红黑树实现
数组和链表的区别
-
数组
-
数组的特性:连续性,有顺序,数组可以使用下标
-
数组的删除原理就是将数组后面的数据整体复制到前面,然后将最后一位赋值为null(System.arraycopy)。相当于就是把后面的数据向前移动一位,然后把最后一个元素变为空。
-
数组的指定下标添加的原理就是还是利用复制(System.arraycopy)实现。从指定的位置开始把后面的整体进行复制,将复制的整体覆盖到到指定位置的下一位,然后将指定位置的元素变为添加的元素。
-
链表
-
链表分为双向链表和单向链表
-
链表有头指针和尾指针。
-
单向链表的特性:当前指针只能找到下一个元素,无法找到上一个元素。
-
双向链表的特性:当前指针可以找到上一个元素以及下一个元素,可以回到上一个元素。
LinkedList源码分析
- int size:表示实际存储大小
- Node first:表示的是第一个存储的元素,头指针
- Node last:表示最后一个元素,尾指针
- 如果在链表中只有一个节点,则其头指针和尾指针指向同一个
- 我们在链表中,first和last指针的作用就是,first是为了表明查询链表的时候从哪个节点开始,last的作用就是便于操作尾部的元素,无需遍历到最后一个元素,直接用last则可以进行尾添加。
- LinkedList的add方法是尾插,就是在链表的最后进行插入(用last指针实现)。
- 源码中列表的查询实际上使用的是二分查找。
手写LinkedList
- 下标为几,就需要next几次。比如下标是1的节点,就需要头结点.next,因为下标从0开
package com.xiyou.myLinkedList;
/**
* 自定义实现LinkedList
* LinkedList不需要扩容
* @author zyf
*/
public class ExtLinkedList<E> {
/**
* 存储链表实际的长度
*/
private Integer size = 0;
/**
* 存储头结点
*/
private Node first;
/**
* 存储尾节点
*/
private Node last;
/**
* 定义内部类Node,存储节点信息
*/
private class Node{
/**
* 存放当前节点的信息
*/
private Object object;
/**
* 存放上一个节点
*/
private Node prev;
/**
* 存放下一个节点
*/
private Node next;
}
/**
* 异常判断。判断传入的索引是否越界
* @param index
*/
private void checkElementIndex(int index) {
if (!isElementIndex(index)) {
throw new IndexOutOfBoundsException("查询越界啦!");
}
}
/**
* 如果传入的下标大于等于0 并且 小于当前存入的元素,因为下标是从0开始的,size相当于列表的长度
* @param index
* @return
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* 添加一个元素在LinkedList
* @param e
*/
public void add(E e){
// 创建一个节点
Node node = new Node();
// 给当前的节点赋值
node.object = e;
// 判断添加的是否是第一个元素
if (first == null) {
// 如果头结点是null,表示添加的是第一个元素
first = node;
} else {
// 表示添加的不是第一个元素
// 当前尾节点的下一个元素指向当前节点
last.next = node;
// 当前节点的上一个元素指向当前的尾节点
node.prev = last;
}
// 不管是不是第一个节点,尾节点都要指上新增的节点,因为是尾插
last = node;
// 当前存入的数量加一
size ++ ;
}
/**
* 向指定的位置上添加一个元素
* @param index
* @param e
*/
public void add(int index, E e) {
// 下标的验证
checkElementIndex(index);
// 先得到插入前的指定下标对应的节点
Node oldNode = getNode(index);
if (oldNode != null) {
// 先新建一个node出来
Node newNode = new Node();
// 保存当前节点
newNode.object = e;
// 保存之前节点的前一个节点
Node oldPrevNode = oldNode.prev;
// 新建节点的下一个就是之前的节点
newNode.next = oldNode;
// 判断前一个节点是否为null,用来判断是不是第一个节点, 如果是第一个节点则表示oldPrevNode是null
if (oldPrevNode == null) {
// 表示当前下标对应的是头结点
// 在头结点之前插入一个值,就是将新插入的值作为头结点
first = newNode;
} else {
// 传入的index不是第一个
newNode.prev = oldPrevNode;
oldPrevNode.next = newNode;
}
// 还需要将之前的节点前置指向现在新增的
oldNode.prev = newNode;
// 新增了一个
size ++ ;
}
}
/**
* 根据下标值获取指定的Node节点
* @param index
* @return
*/
public Node getNode(int index){
// 下标的验证
checkElementIndex(index);
Node node = null;
if (first != null) {
// 列表中存在元素
node = first;
// 遍历取下标是index的节点
for (int i = 0; i < index; i++) {
// 下一个节点赋值给当前节点
// 就是比如,查找下标是3的,就找到下标是2的元素,然后将其的下一个节点的对象进行赋值
node = node.next;
}
}
return node;
}
/**
* 根据下标得到具体的节点
* @return
*/
public Object get(int index){
// 下标的验证
checkElementIndex(index);
return getNode(index).object;
}
/**
* 返回列表存储了多少个数据
* @return
*/
public int getSize(){
return size;
}
/**
* 指定下标移除元素
*/
public void remove(int index) {
checkElementIndex(index);
// 获取当前下标对应的节点
Node oldNode = getNode(index);
// 判断是否为空
if (oldNode != null) {
Node oldNextNode = oldNode.next;
Node oldPrevNode = oldNode.prev;
if (oldPrevNode == null) {
// 如果旧节点的上一个为空表示头结点,即表示删除的是头结点
first = oldNextNode;
} else {
// 当前下标对应的前一个节点的下一个节点是旧节点的下一个
oldPrevNode.next = oldNextNode;
// 当前下标节点的前一个节点赋值为空
oldNode.prev = null;
}
if (oldNextNode == null) {
// 表示的是尾节点被删除
last = oldPrevNode;
} else {
oldNextNode.prev = oldPrevNode;
oldNode.next = null;
}
oldNode.object = null;
size -- ;
}
}
public static void main(String[] args) {
ExtLinkedList<String> extLinkedList = new ExtLinkedList<String>();
extLinkedList.add("a");
extLinkedList.add("b");
extLinkedList.add("c");
extLinkedList.add("e");
extLinkedList.remove(2);
// System.out.println("删除之前:" + extLinkedList.get(2)); // C
//extLinkedList.add(3, "f");// fabce
// System.out.println("删除之后:" + extLinkedList.get(2));// E
// 其实从头查到尾### 效率不高,查询算法#####折半查找
for (int i = 0; i < extLinkedList.size; i++) {
System.out.println(extLinkedList.get(i));
}
// System.out.println(extLinkedList.get(2));
}
}
注意:
- LinkedList线程不安全