要想知道ArrayList和LinkedList的区别,首先我们要知道什么是ArrayList和LinkedList。
ArrayList
1、ArrayList的概念
ArrayList是一个可以处理变长数组的类型,这里不局限于“数”组,ArrayList是一个泛型类,可以存放任意类型的对象。顾名思义,ArrayList是一个数组列表,因此其内部是使用一个数组来存放对象的,实现了List接口的容器类。因为是数组,所以一旦创建,就不能轻易改变其长度。因此他的查询快和修改快,增、删慢。
2、常用方法
ArrayList类常用的方法有add()、clear()、get()、indexOf()、remove()、sort()、toArray()、toString()等等,同时ArrayList内部有一个私有类实现Iterator接口,因此可以使用iterator()方法得到ArrayList的迭代器,同时,还有一个私有类实现了ListIterator接口,因此ArrayList也可以调用listIterator()方法得到ListIterator迭代器。
另外,由于ArrayList的所有方法都是默认在单一线程下进行的,因此ArrayList不具有线程安全性。若想在多线程下使用,应该使用Colletions类中的静态方法synchronizedList()对ArrayList进行调用即可。
下面是模仿底层的代码
设计类
package cn.itsource.arraylist2;
import java.util.Arrays;
/**
* 基于数组,适用于查改。
* 增删插效率低下
* 1. 请设计一个类ArrayListDemo,可以装多个不同类型的数据,初始长度是10个
* 底层是基于数组,因为是可以装不同数据类型的数据,所以是Object数组
* int/byte/short /boolean....
* Object[] value = new Object[10];
* value[0] = 1;//int 的1装好为对应的包装类Integer
*
* 2. 可以记录当前数据的个数 int size()
*
* 3. 可以任意添加任意类型的数据 void add(Object obj)
* version2 自动扩容
*
* 4. 可以打印当前数据结构的对象,格式如下:[值1,值2.。。。。]
*
* 5. 查询指定下标的元素 Object searchByIndex(int index)
* 6. 查询指定的元素第一次出现的位置 int searchByElement(Object ele);
*
* 7. 修改指定下标处的元素 void updateByIndex(int index,Object obj)
* 8. 删除指定下标处的元素,返回删除的元素 Object deleteByIndex(int index)
* 9. 删除的首次出现的指定元素 void deleteByElement(Object ele)
* 10.插入指定下标处一个指定元素 void insertByIndex(int index,Object obj)
*
* 11.将对象中所有元素清空 void clear();
* 12.可以创建指定长度的ArrayList对象
* 13.创建一个默认长度是10的ArrayList对象
* 14.创建一个默认长度是10,且有指定元素的ArrayList对象
*/
public class ArrayListDemo {
/**1. 请设计一个类ArrayListDemo,可以装多个不同类型的数据,初始长度是10个*/
private Object[] value;
/**元素个数 2. 可以记录当前数据的个数 int size()*/
private int size;
/**
* 3. 可以任意添加任意类型的数据 void add(Object obj)
*/
public void add(Object obj){
//先判断当前数组是否需要扩容
growArrayCapcity();
//给数组添加元素
value[size] = obj;
//元素个数+1
size++;
}
/**
* 数组扩容方法
* 扩容机制 *2
*/
private void growArrayCapcity() {
//先判断当前数组是否可以装下新添加的元素
if (size == value.length) {//判断当前元素个数是否已经等于当前数组长度了,若等于,说明数组已经装不下了,要扩容
//数组扩容: 扩容机制,原数组长度*2
value = Arrays.copyOf(value, value.length * 2);//必须将新扩容后的数组地址赋值给value
}
}
/**
* 2. 可以记录当前数据的个数 int size()
* @return
*/
public int size(){
return size;
}
/**
* 4. 可以打印当前数据结构的对象,格式如下:[值1,值2.。。。。]
*/
@Override
public String toString() {
//数组缩容,只要将有元素的数组转换为字符串即可,所以要缩容
Object[] newArr = Arrays.copyOf(value,size);
String str = Arrays.toString(newArr);//将数组转换为字符串
return str;
}
/**
* 5. 查询指定下标的元素 Object searchByIndex(int index)
* @param index
* @return
*/
public Object searchByIndex(int index){
//先校验下标合法性
checkIndex(index);//alt + shift + m 抽取成一个方法
return value[index];
}
/**
* 校验下标方法
* @param index
*/
private void checkIndex(int index) {
if (index < 0 || index >= size) {
//抛出非法参数异常,提示一下
throw new IllegalArgumentException("请注意下标范围,范围是:[0," + (size-1) + "]");
}
}
/**
* 6. 查询指定的元素第一次出现的位置 int searchByElement(Object ele);
* @param ele
* @return
*/
public int searchByElement(Object ele){
//如果ele是null元素,为了避免空指针一次,用 == 比较
if (ele == null) {
//遍历数组,如何用==比较,如果有相等就返回下标
for (int i = 0; i < size; i++) {//注意,这里必须是装的元素个数,而不是数组长度
if (ele == value[i]) {//如果相等就返回
return i;//返回下标
}
}
} else {
//遍历数组,如何用equals比较,如果有相等就返回下标
for (int i = 0; i < size; i++) {//注意,这里必须是装的元素个数,而不是数组长度
if (ele.equals(value[i])) {//如果相等就返回
return i;//返回下标
}
}
}
//如果找不到就返回-1
return -1;//表示没有找到
}
/**
* 7. 修改指定下标处的元素 void updateByIndex(int index,Object obj)
* @param index
* @param obj
*/
public void updateByIndex(int index,Object obj){
//先校验下标合法性
checkIndex(index);//alt + shift + m 抽取成一个方法
//通过下标修改数组元素
value[index] = obj;
}
/**
* 8. 删除指定下标处的元素,返回删除的元素 Object deleteByIndex(int index)
* //注意,该方法必须结合画图来实现
* @param index
* @return
*/
public Object deleteByIndex(int index){
//先校验下标合法性
checkIndex(index);//alt + shift + m 抽取成一个方法
//声明一个变量保存要删除的元素
Object obj = value[index];
//注意,该方法必须结合画图来实现
System.arraycopy(value, index + 1,value,index,size - index -1);//必须结合画图确定复制的元素个数和下标
//删除后元素个数-1
size--;
return obj;
}
/**
* 9. 删除的首次出现的指定元素 void deleteByElement(Object ele)
* @param ele
*/
public void deleteByElement(Object ele){
//直接调用上面的方法,获取ele的下标,如果下标合法证明有当前元素ele,直接调用上面根据下标删除方法删除即可
int index = searchByElement(ele);
//如果index < 0或者大于最大下标,说明当前数组中没有该元素,直接结束方法
if (index <0 || index > size-1) {
return;
}
//调用根据下标删除方法
deleteByElement(index);
}
/**
* 10.插入指定下标处一个指定元素 void insertByIndex(int index,Object obj)
* 也要考虑3种情况,首、尾和中间
* 也要结合画图,参考删除方法
* 图见课堂画图
* @param index
* @param obj
*/
public void insertByIndex(int index,Object obj) {
//因为数组下标有可能越界,所以要校验合法性,注意,这里特殊处理,因为允许在size处插入元素,所以判断条件要改一下
if (index < 0 || index > size){
throw new IllegalArgumentException("亲,请注意,只允许插入下标范围是:[0," + size + "]之内的元素");
}
//先将特殊情况单独处理,即下标 是size的时候,相当于直接在添加元素,所以直接调用add方法
if (index == size) {
add(obj);
return;//添加完毕后,必须结束方法
}
//先判断是否需要扩容,这里因为在add方法中也有扩容需求,所以将扩容功能抽取为一个方法growArrayCapcity
growArrayCapcity();
//画图 搞清楚需要复制几个元素和下标的几个特殊情况:第一个、最后一个。注意:必须先复制再给指定下标元素赋值。
System.arraycopy(value, index, value, index+1, size-index);
//给指定下标元素赋值
value[index] = obj;
//元素个数+1
size++;
}
/**
* 11.将对象中所有元素清空 void clear();
*/
public void clear(){
//循环遍历,重新赋值null/Arrays.fill()
//将vlaue重新赋值即可
value = new Object[10];//没有充分调用GC
//元素个数重置为0
size = 0;
//扩展:遍历数组将每一个元素赋值为null,可以提高GC回收能力,节约资源
}
/**
* 12.可以创建指定长度的ArrayList对象
* @param length
*/
public ArrayListDemo(int length) {
super();
value = new Object[length];
}
/**
* 13.创建一个默认长度是10的ArrayList对象
*/
public ArrayListDemo() {
this(10);
}
/**
* 14.创建一个默认长度是10,且有指定元素的ArrayList对象
*/
public ArrayListDemo(Object obj) {
this(10);
//直接都用添加元素方法
add(obj);
}
}
测试类
package cn.itsource.arraylist2;
/**
* 测试ArrayListDemo类
*/
public class ArrayListDemoTest {
public static void main(String[] args) {
// 创建对象
ArrayListDemo list = new ArrayListDemo();
// 3. 可以任意添加任意类型的数据 void add(Object obj)
list.add(1);//实际添加的是Integer类型的1
list.add(true);
list.add(3.14);
list.add("黄瓜");
list.add("狼牙棒");
list.add(null);
// 2. 获取当前数据的个数 int size()
System.out.println(list.size());
// 4. 可以打印当前数据结构的对象,格式如下:[值1,值2.。。。。]
System.out.println(list);
// 5. 查询指定下标的元素 Object searchByIndex(int index)极限测试
Object searchByIndex = list.searchByIndex(5);
System.out.println(searchByIndex);
// 6. 查询指定的元素第一次出现的位置 int searchByElement(Object ele);
int searchByElement = list.searchByElement("娃娃");
System.out.println(searchByElement);
// 7. 修改指定下标处的元素 void updateByIndex(int index,Object obj)
list.updateByIndex(3, "蜡烛");
System.out.println(list);
// 8. 删除指定下标处的元素,返回删除的元素 Object deleteByIndex(int index) 必须结合画图
Object deleteByIndex = list.deleteByIndex(3);
System.out.println("要删除的元素:" + deleteByIndex);
System.out.println(list);
// 剩余方法自己测试
}
}
LinkedList
1、概念
LinkedList是一个链式结构,他可以分为单向链表、双向链表、循环链表。当然它实现了Deque接口和List接口。同样,LinkedList也是线程不安全的,如果在并发环境下使用它,同样用Colletions类中的静态方法synchronizedList()对LinkedList进行调用即可。在LinkedList的内部实现中,并不是用普通的数组来存放数据的,而是使用结点来存放数据的,有一个指向链表头的结点first和一个指向链表尾的结点last。不同于ArrayList只能在数组末尾添加数据,LinkList可以很方便在链表头或者链表尾插入数据,或者在指定结点前后插入数据,还提供了取走链表头或链表尾的结点,或取走中间某个结点,还可以查询某个结点是否存在。add()方法默认在链表尾部插入数据。总之,LinkedList提供了大量方便的操作方法,并且它的插入或增加等方法的效率明显高于ArrayList类型,但是查询的效率要低一点,因为它是一个双向链表。
2、方法
增加:
add(E e):在链表后添加一个元素; 通用方法
addFirst(E e):在链表头部插入一个元素; 特有方法
addLast(E e):在链表尾部添加一个元素; 特有方法
push(E e):与addFirst方法一致
offer(E e):在链表尾部插入一个元素
add(int index, E element):在指定位置插入一个元素。
offerFirst(E e):JDK1.6版本之后,在头部添加; 特有方法
offerLast(E e):JDK1.6版本之后,在尾部添加; 特有方法
删除:
remove() :移除链表中第一个元素; 通用方法
remove(E e):移除指定元素; 通用方法
removeFirst(E e):删除头,获取元素并删除; 特有方法
removeLast(E e):删除尾; 特有方法
pollFirst():删除头; 特有方法
pollLast():删除尾; 特有方法
pop():和removeFirst方法一致,删除头。
poll():查询并移除第一个元素 特有方法
查:
get(int index):按照下标获取元素; 通用方法
getFirst():获取第一个元素; 特有方法
getLast():获取最后一个元素; 特有方法
peek():获取第一个元素,但是不移除; 特有方法
peekFirst():获取第一个元素,但是不移除;
peekLast():获取最后一个元素,但是不移除;
pollFirst():查询并删除头; 特有方法
pollLast():删除尾; 特有方法
poll():查询并移除第一个元素 特有方法
下面是模拟底层实现的方法
设计类
package cn.itsource.linkedlist2;
/**
* 基于变量(对象):可以保存多个不同类型的数据
*
* 1. 可以任意添加任意类型的数据 void add(Object obj)
* 2. 可以记录当前数据的个数 int size()
* 3. 可以打印当前数据结构的对象 toString(),格式如下:[值1,值2.。。。。]
*
* 根据下标查询当前的节点对象 Node searchNodeByIndex(int index)(重要方法)
*
* 4. 查询指定下标的元素 Object searchByIndex(int index)
* 5. 查询指定的元素第一次出现的位置 int searchByElement(Object ele);
*
* 6. 修改指定下标处的元素 void updateByIndex(int index,Object obj)
* 7. 删除指定下标处的元素,返回删除的元素 Object deleteByIndex(int index)
* 8. 删除的首次出现的指定元素 void deleteByElement(Object ele)
* 9. 插入指定下标处一个指定元素 void insertByIndex(int index,Object obj)
*
* 10.将对象中所有元素清空 void clear();
* 11.创建对象,可以直接传入一个参数初始化
*/
public class LinkedListDemo{
/**保存第一个添加的元素地址*/
private Node frist;
/**添加的元素个数*/
private int size;
/**
* 1. 可以任意添加任意类型的数据 void add(Object obj)
*/
public void add(Object obj){
//创建新的对象
Node node = new Node(obj);
//判断是否是第一元素,如果是就把第一个元素赋值
if (frist == null) {
frist = node;
}else {
//从第一个元素开始遍历,创建临时变量
Node temp = frist;
while (temp.next != null) {
temp = temp.next;
}
temp.next = node;
}
size++;
}
/**
* 2. 可以记录当前数据的个数 int size()
* @return
*/
public int size() {
return size;
}
/**
* 3. 可以打印当前数据结构的对象 toString(),格式如下:[值1,值2.。。。。]
*/
@Override
public String toString() {
//创建StringBulider对象
StringBuilder builder = new StringBuilder("[");
//判断是否是有元素,乳沟没有元素这返回[]
if (frist == null) {
return builder.append("]").toString();
}else {
//循环遍历拼接字符串
//创建一个对象来储存第一个元素
Node node = frist;
builder.append(node.value);
for (int i = 0; i < size-1; i++) {
builder.append(",");
node = node.next;
builder.append(node.value);
}
}
return builder.append("]").toString();
}
/**
* 根据下标查询当前的节点对象 Node searchNodeByIndex(int index)
* @param index
* @return
*/
public Node searchNodeByIndex(int index) {
checkIndex(index);
Node node = frist;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
/**
* 校验链表下标方法
* @param index
*/
private void checkIndex(int index) throws IllegalArgumentException {
if (index > size-1 || index < 0) {
throw new IllegalArgumentException("请注意下标是否越界,范围是[0,"+(size-1)+"]");
}
}
/**
* 4. 查询指定下标的元素 Object searchByIndex(int index)
* @param index
* @return
*/
public Object searchByIndex(int index) {
Node node = searchNodeByIndex(index);
return node.value;
}
/**
* 5. 查询指定的元素第一次出现的位置 int searchByElement(Object ele);
* @param ele
* @return
*/
public int searchByElement(Object ele) {
Node node = frist;
if (ele == null) {
for (int i = 0; i < size; i++) {
if (ele == node.value) {
return i;
}
}
}else {
for (int i = 0; i < size; i++) {
if (ele.equals(node.value)) {
return i;
}
node = node.next;
}
}
return -1;
}
/**
* 6. 修改指定下标处的元素 void updateByIndex(int index,Object obj)
* @param index
* @param obj
* @throws IllegalArgumentException
*/
public void updateByIndex(int index,Object obj) {
checkIndex(index);
Node byIndex = searchNodeByIndex(index);
byIndex.value = obj;
}
/**
* 7. 删除指定下标处的元素,返回删除的元素 Object deleteByIndex(int index)
* @param index
* @return
* @throws IllegalArgumentException
*/
public Object deleteByIndex(int index) {
checkIndex(index);
Node obj = null;
if (index == 0) {
obj = frist;
frist = frist.next;
}else {
Node lastindex = searchNodeByIndex(index-1);
obj = searchNodeByIndex(index);
lastindex.next = obj.next;
}
size--;
return obj.value;
}
/**
* 8. 删除的首次出现的指定元素 void deleteByElement(Object ele)
* @param ele
* @throws IllegalArgumentException
*/
public void deleteByElement(Object ele) {
int index = searchByElement(ele);
if (index < 0) {
System.out.println("没有这个元素");
}else {
deleteByIndex(index);
System.out.println("删除的首次出现的指定元素 :"+this.toString());
}
}
/**
* 9. 插入指定下标处一个指定元素 void insertByIndex(int index,Object obj)
* 跟删除方法类似,通过画图得知,只需要将index上一个元素的next赋值为当前插入的元素地址,将插入的元素next赋值为index处元素的地址即可
* @param index
* @param obj
* @throws IllegalArgumentException
*/
public void insertByIndex(int index,Object obj) throws IllegalArgumentException{
checkIndex(index);
if (index < 0 || index > size) {
throw new IllegalArgumentException("下标越界,请注意下标范围[0,"+size+"]");
}
Node node = new Node(obj);
if (index == 0) {
node.next = frist;
frist = node;
}else if (index == size) {
add(obj);
return;
}else {
Node nextIndex = searchNodeByIndex(index-1);
Node now = searchNodeByIndex(index);
node.next = now;
nextIndex.next = node;
}
size++;
}
/**
* 10.将对象中所有元素清空 void clear();
*/
public void clear() {
Node node = frist;
for (int i = 0; i < size; i++) {
frist = null;
node.next = frist;
}
size = 0;
}
/**
* 11.创建对象,可以直接传入一个参数初始化
* @param obj
*/
public LinkedListDemo(Object obj){
add(obj);
}
public LinkedListDemo() {
super();
}
/**
* 内部类
* 不希望在SinglyLinkedList外部被使用到当前Node类,所以用private修饰
*/
private class Node {
/**用来储存任意类型的值的*/
Object value;
/**用来保存下一个元素地址的*/
Node next;
/**
* 声明有参构造
*/
public Node(Object value) {
this.value = value;
}
}
}
测试类
package cn.itsource.linkedlist3;
/**
* LinkedListDemo测试
* @author zjj
*
*/
public class LinkedListTest {
public static void main(String[] args) {
//创建LinkedListDemo对象
LinkedListDemo listDemo = new LinkedListDemo();
//1. 可以记录当前数据的个数 int size()
// System.out.println(listDemo.size());
//2、添加元素并调用toString方法
listDemo.add("菠萝");
listDemo.add(123);
listDemo.add(4);
listDemo.add('A');
System.out.println(listDemo.toString());
//4. 查询指定下标的元素 Object searchByIndex(int index)
Object value = listDemo.searchByIndex(2);
System.out.println("查询指定下标的元素为:"+value);
// 5. 查询指定的元素第一次出现的位置 int searchByElement(Object ele);
int searchByElement = listDemo.searchByElement('A');
System.out.println("查询指定的元素第一次出现的位置:"+searchByElement);
//6. 修改指定下标处的元素
listDemo.updateByIndex(2, 321);
System.out.println("修改指定下标处的元素:"+listDemo);
//7. 删除指定下标处的元素,返回删除的元素
listDemo.deleteByIndex(0);
System.out.println("删除指定下标出的元素:"+listDemo);
//8. 删除的首次出现的指定元素
listDemo.deleteByElement(321);
System.out.println("删除的首次出现的指定元素:"+listDemo);
//9. 插入指定下标处一个指定元素
listDemo.insertByIndex(0, 55);
System.out.println("插入指定下标处一个指定元素:"+listDemo);
//10.将对象中所有元素清空
listDemo.clear();
System.out.println("清空后的元素:"+listDemo);
}
}
下面是结构图(稍微参考一下就行)
总结一下
ArrayList底层就是一个可变的数组,其可变是指里面可以储存多个不同类型的元素,因为是数组,所以数组一旦被创建,其长度就不容易被改变,因此对于元素的删除、添加效率是十分低下的,但是查询和修改是十分快速的。而LinkedList是一个链式结构,是靠结点来操作的,需要找到当前元素是需要上一个元素来帮忙的。因此他的查询和更改效率低下,删除和添加与ArrayList相反。