// 当o!=null时,o使用equals去比较集合元素,删除第一个结果为true的元素所在节点
linkedList.remove(“A”);
// clear()方法用于清空集合元素
// 内部实现:
// 1.遍历循环链表节点,将每个节点的prev,item,next设置为null
// 2.将first,last设置为null
// 3.将size=0
// 4.modCount++
//linkedList.clear();
// set(int index,E e)
// 该方法用于将集合元素索引位置1的节点的item改为e
// 内部实现,先判断index是否合法,即0<=index<size,再获取index处节点node(index),并修改node(index).item=e
linkedList.set(1,“F”);
// indexOf(Object o)用于获取o元素再集合中首次出现的位置索引
// lastIndexOf(Object o)用于获取o元素在集合中末次出现的位置索引
// 这两个方法本质也是遍历链表节点,找到和o相同的元素,只是indexOf是从首到尾遍历,lastIndexOf是从尾到首遍历
// 比较时,o如果是null,采用==,o如果是非null,采用equals
// 上述两个方法当找不到对应元素时返回-1
linkedList.indexOf(“B”);
linkedList.lastIndexOf(“B”);
// contains(E e)用于判断集合中是否包含e
// 内部实现:return indexOf(o) != -1
linkedList.contains(“B”);
// size()用于返回集合元素个数
// 即 return size;
linkedList.size();
// toArray()用于将集合转为Object[]数组
// 内部实现:
// 1.先创建一个长度为size的Object[]数组
// 2.遍历LinkedList底层链表的节点,并取出节点的item存入数组中
linkedList.toArray();
// toAarry(T[] a)用于将集合转成T[]数组
// 当形参a数组的长度小于size时,创建一个T[]类型的长度为size的新数组
// 当形参a数组的长度大于size时,则将大于size部分的索引元素设为null
// 遍历链表,将集合元素存入数组0~size-1索引中。
linkedList.toArray(new Object[0]);
}
public static void testMethodFromObject(){
LinkedList linkedList = new LinkedList();
Person p1 = new Person(“qfc”,18);
Person p2 = new Person(“zyx”,19);
Person p3 = new Person(“swk”,20);
Person p4 = new Person(“zbj”,21);
Collections.addAll(linkedList,p1,p2,p3,p4);
System.out.println(linkedList.get(0));
/**
-
由于LinkedList实现了Cloneable接口,且重写clone方法,在clone方法中调用了super.clone(),获得对象拷贝
-
后面将拷贝对象的first,last设为null,将size,modCount设为null
-
然后遍历被拷贝对象的节点,将节点中的item取出,通过cloneObject.add(item);给克隆对象插入对应元素
-
为什么不直接使用super.clone()结果呢?因为这样会导致 拷贝对象的first,last 和 被拷贝对象的first,last 指向同一个内存上节点,这实际上已经不算对象克隆了。
*/
LinkedList clone = (LinkedList)linkedList.clone();
Person person = (Person) clone.get(0);
person.setName(“sg”);
person.setAge(12);
System.out.println(linkedList.get(0));
/**
- 说明:LinkedList的对象克隆是浅克隆
*/
}
public static void testMethodFromDeque(){
LinkedList linkedList = new LinkedList©;
System.out.println(linkedList);
//下面几个方法都是见名知意的
linkedList.addFirst(“A-”);//[A-,A, B, C, D]
linkedList.addLast(“Z”);//[A-,A, B, C, D, Z]
linkedList.removeFirst();//[A, B, C, D, Z]
linkedList.removeLast();//[A, B, C, D]
linkedList.removeFirstOccurrence(“A”);//[B, C, D]
linkedList.removeLastOccurrence(“B”);//[C, D]
//下面几个方法是模拟顺序栈的操作
linkedList.push(“A-”);//[A-,C, D]
linkedList.peek();//[A-,C, D]
linkedList.pop();//[C, D]
System.out.println(linkedList);//[C, D]
//下面几个方法是模拟双向栈的操作
//注意offer()是栈底压栈
linkedList.offer(“A–”);//[C, D, A–]
linkedList.offerFirst(“A-”);//[A-, C, D, A–]
linkedList.offerLast(“Z”);//[A-, C, D, A–, Z]
linkedList.peekFirst();//[A-, C, D, A–, Z]
linkedList.peekLast();//[A-, C, D, A–, Z]
linkedList.poll();//[C, D, A–, Z]
linkedList.pollFirst();//[D, A–, Z]
linkedList.pollLast();//[D, A–]
System.out.println(linkedList);
//取出栈顶元素
Object element = linkedList.element();//D
System.out.println(element);
}
public static void testIterator(){
LinkedList linkedList = new LinkedList©;
// iterator()本质是调用的LinkedList的listIterator(0);
// 所以该方法本质是返回一个ListIterator对象
Iterator iterator = linkedList.iterator();
ListIterator listIterator = (ListIterator) iterator;
boolean hasPrevious = listIterator.hasPrevious();
System.out.println(hasPrevious);//由于该迭代器本质是listIterator(0),所以next为node(0),所以nextIndex=0,而hasPrevious内部是要求 nextIndex>0
boolean hasNext = listIterator.hasNext();
System.out.println(hasNext);//hasNext()本质就是要求 nextIndex<size
listIterator.next();
listIterator.next();
listIterator.next();
listIterator.next();
System.out.println(listIterator.hasNext());
System.out.println(listIterator.hasPrevious());
listIterator.previous();
listIterator.previous();
listIterator.nextIndex();
listIterator.previousIndex();
/**
-
到目前为止,以证明了LinkedList的iterator()方法,该方法虽然返回的是Iterator实现类对象,但实质是ListIterator实现类对象
-
因为Iterator实现类对象没有previous(),hasPrevious(),nextIndex(),previousIndex()方法
-
其他set,add方法就先不在此处演示
*/
}
public static void testListIterator(){
/**
-
LinkedList有两种方式直接获取ListIterator实现类对象
-
1.ListIterator listIterator();
-
2.ListIterator listIterator(int index);
-
其中1的内部实现就是调用了2的listIterator(0);这里不演示listIterator()
*/
LinkedList linkedList = new LinkedList©;
ListIterator listIterator = linkedList.listIterator(4);
while (listIterator.hasPrevious()){
System.out.println(listIterator.previous());
}
System.out.println(“====================================”);
System.out.println(listIterator.nextIndex());
System.out.println(listIterator.previousIndex());
System.out.println(“====================================”);
while(listIterator.hasNext()){
System.out.println(listIterator.next());
}
System.out.println(“====================================”);
System.out.println(listIterator.nextIndex());
System.out.println(listIterator.previousIndex());
System.out.println(“====================================”);
listIterator.add(“E”);
System.out.println(linkedList);
System.out.println(“====================================”);
// 注意add操作后,lastReturned被重置为null,而remove操作时删除lastReturned索引上节点,所以该处remove操作会抛出非法状态异常
// listIterator.remove();//java.lang.IllegalStateException
listIterator.previous();
listIterator.remove();
System.out.println(linkedList);
System.out.println(“====================================”);
// 注意remove操作后,lastReturned被重置为null,而set操作是修改lastReturned索引上的节点的item,所以该处set操作会抛出非法状态异常
// listIterator.set(“DD”);
listIterator.previous();
listIterator.set(“DD”);
System.out.println(linkedList);
System.out.println(“====================================”);
listIterator.previous();
listIterator.previous();
// 注意foreachRemaining不是从头遍历集合,而是从nextIndex索引为止开始遍历剩下的集合元素
listIterator.forEachRemaining(data-> System.out.println(data));
}
public static void testDescendingIterator(){
LinkedList linkedList = new LinkedList©;
Iterator descendingIterator = linkedList.descendingIterator();
while (descendingIterator.hasNext()){
Object next = descendingIterator.next();
System.out.println(next);
if(next.equals(“C”)){
descendingIterator.remove();
}
}
System.out.println(linkedList);
System.out.println(“====================================”);
LinkedList linkedList1 = new LinkedList©;
Iterator descendingIterator1 = linkedList1.descendingIterator();
descendingIterator1.forEachRemaining(data-> System.out.println(data));
/**
-
测试DescendingIterator迭代器,发现它的hasNext,next,foreachRemaing都是逆序执行
-
因为hasNext的内部实现调用的是ListItr的hasPrevious
-
next的内部实现调用的是ListItr的previous
-
foreachRemaining内部调用是自己的hasNext,和next
*/
}
}
package linkedlist;
public class Person implements Cloneable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return “Person{” +
“name='” + name + ‘’’ +
“, age=” + age +
‘}’;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (getAge() != person.getAge()) return false;
return getName() != null ? getName().equals(person.getName()) : person.getName() == null;
}
@Override
public int hashCode() {
int result = getName() != null ? getName().hashCode() : 0;
result = 31 * result + getAge();
return result;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
思考题
===
为什么LinkedList不直接继承AbstractList,而要继承AbstractSequentialList?
我们知道ArrayList,Vector直接继承了AbstractList。而LinkedList直接继承了AbstractSequentialList。
而AbstractList,AbstractSequentialList作为List实现类的抽象父类,它们存在的意义是提取子类中重复代码形成模板。
而ArrayList,Vector的底层是数组,LinkedList的底层是双向链表。它们实现基本操作如增删改查的的实现是不一样的。
所以说 AbstractList是ArrayList,Vector的抽象,AbstractSequentialList是LinkedList的抽象。
另外,AbstractSequentialList的基本操作都依赖于listIterator(int index)方法,而LinkedList中重写了listIterator(int index),所以AbstractSequentialList中的操作都依赖于LinkedList中的listIterator(int index)
为什么说ArrayList的容量是伪动态的?而说LinkedList的容量是真动态的?
因为ArrayList的底层是数组。而数组的长度是固定的,一旦初始化,就无法再变更数组长度。
ArrayList的动态容量是 通过创建新数组来实现的。
而LinkedList的底层是双向链表。而链表的长度不是固定,后期可以实时增加或减少。
所以说LinkedList的容量 是 真动态的。
为什么说LinkedList的集合元素索引是伪索引?而ArrayList是真索引?
因为ArrayList的底层是数组。而数组本身是有索引的。所以说ArrayList集合元素的索引是真索引。
而LinkedList的底层是双向链表,而链表节点是没有索引能力的。体现在LinkedList元素上的索引,其实是底层链表的节点的顺序序号,即first节点的元素是索引0,last节点的元素是索引size-1。
LinkedList的元素插入删除肯定比ArrayList快吗?ArrayList的查询操作一定比LinkedList的快吗?
如果ArrayList容量足够的话,插入元素应该比LinkedList快。因为ArrayList可以随机访问,可以借助索引直接插入元素,不需要像LinkedList需要顺序访问,从first节点还是一个一个实时计算索引,然后创建新节点,删除旧节点,解开旧链接,创建新链接。
还有ArrayList删除最后一个索引的元素 理论上是比 LinkedList删除元素快的,因为ArrayList删除最后一个索引的元素,只需要将底层数组该索引元素设置null即可。不需要移位操作。而LinkedList删除元素,实际上是删除节点,而删除节点前需要找到节点,这些动作比较耗时。
ArrayList的查询操作也是随机访问性的,即可以根据索引直接访问对应元素。
LinkedList查询首尾元素很快,因为first和last节点在二分法查询下都是第一个被遍历的节点。所以感觉也很快,不一定比ArrayList的慢。
我们常说的LinkedList插入删除元素比ArrayList快,有如下场景
1.ArrayList容量不足时,需要扩容,然后将老数组中元素全部按序复制到新数组中。
2.ArrayList越靠前的索引处插入或删除元素时,后面元素都要做移位操作。
而LinkedList由于是真动态容量,所以不需要手动扩容,另外LinkedList插入删除元素,底层链表不需要后面的节点移位,只需要在操作的地方解开链,再创建新链就可以了。
我们常说的ArrayList查询元素比LinkedList快是,有如下场景
1.LinkedList每次查询对应索引的元素,都要通过遍历链表节点,来实时计算每个节点元素的索引,如果是单次还好,如果是循环查索引,它的复杂度将是ArrayList的n倍。
2.LinkedList采用二分法查询节点,并访问节点元素。如果是首尾节点还好,查询会很快。但是如果是靠近中间的节点,就会查询很慢。
而ArrayList是可以根据索引直接访问对应元素的。
所以只能本题的问题的前面加上一个前提词:大体上。
大体上,ArrayList查询快,增删慢。大体上,LinkedList增删快,查询慢。
比较下Stack模拟栈和LinkedList模拟栈的区别
二者的区别主要是底层数据结构上的。
Stack的底层是数组。LinkedList的底层是双向链表。
数组和双向链表都可以模拟栈。栈数据结构的特点是:元素后进先出。主要操作有push,peek,pop
而数组可以将每次push的元素存在数组的length-1索引位置,peek操作取出length-1索引位置的元素,pop是取出length-1索引位置的元素,并设置该索引元素为null(达到删除元素的目的)
但是数组有个缺点就是容量固定,当push元素超出数组长度时,需要扩容,即创建新数组来转移存储。整体来说,性能不是很好。
而双向链表模拟栈比数组更加合适,双向链表可以将first节点处当成栈顶,也可以将last节点当成栈顶,且双向链表push,pop操作起来就是创建新节点,新链接,删除老节点,老链接的事,没有扩容,转移存储这些麻烦的操作。peek就是获取栈顶的节点的元素。双向链表本身操作首尾节点就很快,所以说双向链表模拟栈太合适了。
另外Stack只能模拟顺序栈,不能模拟双向栈。而LinkedList模拟双向栈简直就是蜜蜂掉到糖罐里,找到家了。相应的LinkedList的栈操作方法比Stack多,多的部分主要是双向栈的操作。
如 offer(E e),offerLast(E e) 压栈(底)offerFirst(E e)压栈(顶)
poll(),pollFirst() 弹栈(顶) pollLast() 弹栈(底)
peek(),peekFirst() 获取栈顶元素 peekLast()获取栈底元素
push(E e) 压栈(顶),pop() 弹栈(顶)
为什么说LinkedList是双向不循环链表呢?
JDK8中,LinkedList的first,last节点有特殊注释说明
一般循环链表的first,last是链接的,即first.prev = last ; last.next = first;
但是在JDK8 LinkedList源码中 关于 first,last节点的描述只有两种情况
空链表:first = null, last = null
非空链表 first.prev = null && first != null
last.next = null && last != null
所以LinkedList从设计上来说,就不是循环链表。
LinkedList支持对象克隆吗?是深克隆还是浅克隆?
LinkedList实现了Cloneable接口,且重写了clone()方法,重写的clone()中调用super.clone()。
所以LinkedList要支持对象克隆。
且还是浅克隆。因为clone()方法中没有对链表节点中item做对象克隆,而是直接使用了原对象节点中item的引用。
为什么说LinkedList是顺序访问集合,而ArrayList是随机访问集合?
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ble接口,且重写了clone()方法,重写的clone()中调用super.clone()。
所以LinkedList要支持对象克隆。
且还是浅克隆。因为clone()方法中没有对链表节点中item做对象克隆,而是直接使用了原对象节点中item的引用。
为什么说LinkedList是顺序访问集合,而ArrayList是随机访问集合?
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-rtxD05Qe-1714937813190)]
[外链图片转存中…(img-ywOVCfW0-1714937813190)]
[外链图片转存中…(img-ySenuKO4-1714937813190)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!