ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JAVA-LinkedList代码实现与使用总结

JAVA-LinkedList代码实现与使用总结 LinkedList 详解核心特性、常用接口与总结附图解本文聚焦 Java 集合框架中的LinkedList从「什么是链表」讲起用图解方式直观展示 LinkedList 的底层结构系统梳理所有常用接口的作用与返回值最后给出模拟代码实现并且对比 ArrayList 给出整体总结。适合作为学习笔记或面试速查。目录一、从 ArrayList 的缺陷说起二、链表LinkedList 的底层原理图解三、LinkedList 简介四、LinkedList 的构造方式五、LinkedList 常用接口作用 返回值六、LinkedList 的三种遍历方式七、ArrayList 和 LinkedList 的区别八、模拟代码实现九、总结一、从 ArrayList 的缺陷说起ArrayList 底层使用数组存储元素。由于其底层是一段连续空间在任意位置插入或删除元素时需要将后序元素整体往前或往后搬移时间复杂度为O(n)效率较低。因此ArrayList 不适合做任意位置插入和删除较多的场景。为此Java 集合中又引入了LinkedList即链表结构。二、链表LinkedList 的底层原理图解2.1 什么是链表链表是一种物理存储结构上非连续的存储结构数据元素的逻辑顺序是通过链表中的引用链接次序实现的——元素存储在一个个独立的节点Node中节点之间靠引用串起来。与顺序表对比着看更直观一句话总结顺序表是「物理连续、逻辑也连续」链表是「物理不连续、逻辑连续」。2.2 链表的 8 种结构实际中链表的结构非常多样以下三种情况组合起来共有2 × 2 × 2 8 种链表结构虽然结构很多但重点掌握两种无头单向非循环链表结构简单一般不会单独用来存数据实际中更多是作为其他数据结构的子结构如哈希桶、图的邻接表并且是笔试面试的高频考点无头双向链表Java 集合框架中 LinkedList 的底层实现就是无头双向循环链表。2.3 LinkedList 的底层结构无头双向循环链表每个节点Node包含三个部分prev前驱引用| item数据| next后继引用同时额外维护first和last两个引用。由于链表没有将元素存储在连续的空间中因此在任意位置插入或删除元素时不需要搬移元素只需修改引用的指向效率较高。三、LinkedList 简介在集合框架中LinkedList 也实现了List 接口。它的核心特性如下特性说明实现了 List 接口拥有 List 的全部通用方法底层是双向链表元素存储在独立节点中通过引用连接不支持随机访问没有实现 RandomAccess 接口按下标访问只能从头遍历效率 O(n)插入删除效率高任意位置插入和删除元素时效率比较高改引用即可时间复杂度为 O(1)¹适合频繁插删的场景与 ArrayList 互补¹ 精确地说「改引用」这一步是 O(1)但定位到目标位置仍需 O(n) 遍历不过相比 ArrayList 插入时还要整体搬移元素LinkedList 依然优势明显。此外LinkedList 还实现了Deque双端队列接口所以它既能当列表用也能当栈、队列用四、LinkedList 的构造方式publicstaticvoidmain(String[]args){// 1. 构造一个空的 LinkedListListIntegerlist1newLinkedList();// 2. 使用其他集合构造 LinkedList拷贝构造ListStringlist2newjava.util.ArrayList();list2.add(JavaSE);list2.add(JavaWeb);list2.add(JavaEE);// list3 构造好之后与 list2 中的元素一致ListStringlist3newLinkedList(list2);}构造方法作用LinkedList()构造一个空的LinkedListLinkedList(Collection? extends E c)使用其他集合中的元素构造 LinkedList拷贝构造注意与 ArrayList 不同LinkedList没有「指定初始容量」的构造方法——链表按需创建节点不存在预分配容量的概念。五、LinkedList 常用接口作用 返回值5.1 List 通用接口方法作用返回值boolean add(E e)尾插在链表末尾添加元素添加成功返回truevoid add(int index, E e)在index位置插入元素无返回值E remove(int index)删除index位置的元素返回被删除的元素boolean remove(Object o)删除第一次出现的指定元素删除成功返回true不存在返回falseE get(int index)获取index位置的元素返回该位置的元素E set(int index, E e)将index位置的元素设置为e返回被替换的旧元素boolean contains(Object o)检测元素o是否存在存在返回true否则返回falseint indexOf(Object o)从前往后找o第一次出现的位置找到返回下标未找到返回 -1int lastIndexOf(Object o)从后往前找o第一次出现的位置找到返回下标未找到返回 -1int size()获取链表中有效元素个数返回元素个数void clear()清空链表无返回值ListE subList(int from, int to)用 list 中[from, to)之间的元素构造一个新的 List 返回返回子列表5.2 LinkedList 特有的首尾操作来自 Deque 双端队列接口这是 LinkedList 区别于 ArrayList 的最大亮点——头尾操作全部 O(1)方法作用返回值void addFirst(E e)头插将元素插到链表头部无返回值void addLast(E e)尾插将元素插到链表尾部无返回值E remove()删除第一个元素内部调用的就是removeFirst()返回被删除的元素链表为空时抛异常E removeFirst()删除第一个元素返回被删除的元素链表为空时抛异常E removeLast()删除最后一个元素返回被删除的元素链表为空时抛异常E getFirst()获取第一个元素不删除返回首个元素链表为空时抛异常E getLast()获取最后一个元素不删除返回末尾元素链表为空时抛异常boolean offer(E e)尾插等价add(e)队列风格成功返回trueboolean offerFirst(E e)头插队列风格成功返回trueboolean offerLast(E e)尾插队列风格成功返回trueE poll()删除第一个元素队列风格返回被删除元素链表为空返回null而不抛异常E pollFirst()/E pollLast()删除首 / 尾元素同上空链表返回nullE peek()获取第一个元素不删除空链表返回null不抛异常E peekFirst()/E peekLast()获取首 / 尾元素不删除空链表返回nullvoid push(E e)头插栈风格等价addFirst(e)无返回值E pop()删除第一个元素栈风格等价removeFirst()返回被删除元素空链表抛异常⚠️两组方法的区别务必记牢remove(int index)返回被删除的元素remove(Object o)返回boolean——重载语义不同「抛异常组」removeFirst/getFirst/pop…与「返回特殊值组」pollFirst/peekFirst/offer…功能相同区别仅在于链表为空时前者抛异常、后者返回null或false。5.3 接口使用示例publicstaticvoidmain(String[]args){LinkedListIntegerlistnewLinkedList();list.add(1);// add(elem): 表示尾插list.add(2);list.add(3);list.add(4);list.add(5);list.add(6);list.add(7);System.out.println(list.size());System.out.println(list);// 在 index 位置插入元素 elem在起始位置插入 0list.add(0,0);System.out.println(list);list.remove();// remove(): 删除第一个元素内部调用的是 removeFirst()list.removeFirst();// removeFirst(): 删除第一个元素list.removeLast();// removeLast(): 删除最后一个元素list.remove(1);// remove(index): 删除 index 位置的元素System.out.println(list);// contains(elem): 检测 elem 元素是否存在如果存在返回 true否则返回 falseif(!list.contains(1)){list.add(0,1);}list.add(1);System.out.println(list);System.out.println(list.indexOf(1));// indexOf(elem): 从前往后找到第一个 1 的位置System.out.println(list.lastIndexOf(1));// lastIndexOf(elem): 从后往前找第一个 1 的位置intelemlist.get(0);// get(index): 获取指定位置元素list.set(0,100);// set(index, elem): 将 index 位置的元素设置为 elemSystem.out.println(list);// subList(from, to): 用 list 中 [from, to) 之间的元素构造一个新的 List 返回ListIntegercopylist.subList(0,3);System.out.println(list);System.out.println(copy);list.clear();// 将 list 中元素清空System.out.println(list.size());}六、LinkedList 的三种遍历方式publicstaticvoidmain(String[]args){LinkedListIntegerlistnewLinkedList();list.add(1);// add(elem): 表示尾插list.add(2);list.add(3);list.add(4);list.add(5);list.add(6);list.add(7);System.out.println(list.size());// 1. foreach 遍历for(inte:list){System.out.print(e );}System.out.println();// 2. 使用迭代器遍历 --- 正向遍历ListIteratorIntegeritlist.listIterator();while(it.hasNext()){System.out.print(it.next() );}System.out.println();// 3. 使用反向迭代器 --- 反向遍历ListIteratorIntegerritlist.listIterator(list.size());while(rit.hasPrevious()){System.out.print(rit.previous() );}System.out.println();} 由于 LinkedList没有实现 RandomAccess 接口用「for 循环 下标get(i)」遍历的代价是每次get都从头找整体为O(n²)不推荐。推荐foreach或迭代器遍历。七、ArrayList 和 LinkedList 的区别对比维度ArrayListLinkedList底层结构动态顺序表连续数组无头双向循环链表随机访问支持实现了 RandomAccessget(i)为O(1)不支持get(i)需从头遍历为O(n)任意位置插入 / 删除需整体搬移元素O(n)只需修改引用O(1)¹首尾操作尾插均摊 O(1)头插 O(n)头插、尾插都是O(1)内存占用连续空间有一定冗余容量每个节点额外维护 prev / next 两个引用占用更大扩容需要申请新空间 拷贝数据无需扩容按需创建节点适用场景查询多、增删少任意位置增删多、查询少¹ 同第三节的说明定位节点本身是 O(n)此处指节点间的插入 / 删除操作。一句话选型以「查」为主选 ArrayList以「增删」为主选 LinkedList。八、模拟代码实现packagelinkedlist;classNode{publicStringvalue;publicNodenext;publicNode(Stringv){valuev;}}publicclassMyLinkedList{privateNodeheadnull;publicvoidaddFirst(Stringv){NodenewNodenewNode(v);//需要完成的两个步骤newNode.nexthead;headnewNode;}publicvoidaddLast(Stringv){if(headnull){addFirst(v);return;}NodenewNodenewNode(v);Nodecurhead;while(cur.next!null){curcur.next;}cur.nextnewNode;}publicStringtoString(){StringBuilderstrnewStringBuilder();str.append([);Nodecurhead;//打印的话要遍历所有 而不是走到最后就结束了while(cur!null){str.append(cur.value);if(cur.nextnull)break;str.append(,);curcur.next;}str.append(]);returnstr.toString();}publicintsize(){intsize0;Nodecurhead;while(cur!null){curcur.next;size;}returnsize;}publicvoidadd(intindex,Stringv){if(index0||indexsize()){thrownewIndexOutOfBoundsException();}NodenewNodenewNode(v);Nodeprevnull;Nodecurhead;while(index--0){prevcur;curcur.next;}if(prevnull){addFirst(v);}else{prev.nextnewNode;newNode.nextcur;}}publicbooleancontains(Stringv){if(size()0)returnfalse;Nodecurhead;while(cur!null){if(cur.value.equals(v))returntrue;curcur.next;}returnfalse;}publicvoidremove(intindex){if(index0||indexsize()){thrownewIndexOutOfBoundsException();}Nodeprevnull;Nodecurhead;for(inti0;iindex;i){prevcur;curcur.next;}if(prevnull){headhead.next;return;}prev.nextcur.next;return;}publicvoidremove(Stringv){if(size()0)return;Nodecurhead;Nodeprevnull;while(cur!null){if(cur.value.equals(v)){if(prevnull){headhead.next;return;}prev.nextcur.next;return;}prevcur;curcur.next;}}publicintindexOf(Stringv){Nodecurhead;intindex0;while(cur!null){if(cur.value.equals(v)){returnindex;}curcur.next;index;}return-1;}publicvoidremoveAllKey(Stringkey){Nodeprevnull;Nodecurhead;while(cur!null){if(cur.value.equals(key)){if(prevnull){headhead.next;curhead;continue;}prev.nextcur.next;curprev.next;continue;}prevcur;curcur.next;}}publicvoidclear(){//垃圾回收机制 只要没引用指向 就会自动释放内存headnull;}publicstaticvoidmain(String[]args){MyLinkedListlistnewMyLinkedList();list.addFirst(Hello);list.addFirst(World);list.addLast(Hello);list.addLast(Hello);list.addLast(World);list.add(2,gogogo);list.add(0,hel);System.out.println(list);list.removeAllKey(Hello);System.out.println(list);}}九、总结LinkedList 本质底层是无头双向循环链表的 List 实现同时实现了List、Deque等接口核心特性物理存储不连续、不支持随机访问未实现 RandomAccess、头尾插入删除效率极高O(1)、无需扩容关键接口add/remove/get/set/contains/indexOf/lastIndexOf/size/clear/subList以及特有的一整套首尾操作addFirst/addLast/removeFirst/removeLast/getFirst/getLast/poll/peek/push/pop需牢记各自作用与返回值返回值易错点remove(int index)返回被删除元素remove(Object o)返回 booleanremoveFirst()/getFirst()/pop()空链表抛异常pollFirst()/peekFirst()空链表返回 null遍历建议用foreach或迭代器不要用「for get(i)」O(n²)与 ArrayList 的选型查询多选 ArrayList任意位置增删多选 LinkedList两者是互补关系而非替代关系。如果这篇文章对你有帮助欢迎点赞 收藏 ⭐ 关注后续会继续更新 Java 集合框架系列文章
返回列表