做多了一些leetcode发现在java中LinkedList适用于很多题,因为其类中提供的很多方法让其既可以作为一个栈又可以作为一个(双端)队列,在解决一些关于队列与栈的问题的时候比较全能,所以这篇详细讲解一下LinkedList,让在做算法题的时候知道这个类有这些方法可以用!!!
LinkedList中有很多方法的作用重复了,所以找到合适自己(容易记)的方法去做题会更好,不用全记住这些方法。
构造方法:
构造一个空列表。 |
LinkedList(Collection<? extends E> c) 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 |
代码测试:
@Test
@DisplayName("LinkedList构造方法")
public void constructor() {
//无参构造方法
LinkedList<Integer> noParameter = new LinkedList<>();
//传入另一个集合去初始化生成的LinkedList对象
LinkedList<Integer> parameter = new LinkedList<>(List.of(1,2,3,4,5));
LinkedList<Integer> copy = new LinkedList<>(parameter);
}
常用方法:
代码引入:(后面会调用到)
//生成一个LinkedList
private LinkedList<Integer> getList() {
LinkedList<Integer> list = new LinkedList<>(List.of(1, 2, 3));
return list;
}
//打印LinkedList
private void printList(LinkedList<Integer> list) {
for (Integer i : list) {
System.out.print(i + " ");
}
System.out.println();
}
添加元素类型方法
add添加元素
boolean类型
add(E e)
将指定的元素追加到此列表的末尾。addAll(Collection<? extends E> c)
按照指定集合的迭代器返回的顺序将指定集合中的所有元素追加到此列表的末尾。addAll(int index, Collection<? extends E> c)
将指定集合中的所有元素插入到此列表中,从指定的位置开始。
@Test
@DisplayName("add_Boolean")
public void addBoolean(){
LinkedList<Integer> list = getList();// 1 2 3
boolean add = list.add(4);// 1 2 3 "4"
boolean addAll1 = list.addAll(getList());// 1 2 3 4 "1 2 3"
boolean addAll2 = list.addAll(0, getList());// "1 2 3" 1 2 3 4 1 2 3
printList(list);
}
void类型
- addFirst(E e): 这个方法将指定的元素插入到列表的开头。
- addLast(E e): 这个方法将指定的元素添加到列表的末尾;void版本的add(E e)
add(int index, E e)
将指定的元素添加到列表的指定位置中。
一般的在实际做题中我推荐就记住 addFirst(E e) 和 addLast(E e) 这两个方法基本就可以了。
@Test
@DisplayName("add_Void")
public void addVoid(){
LinkedList<Integer> list = getList();// 1 2 3
list.add(1,2);// 1 "2" 2 3
list.addFirst(0);// "0" 1 2 2 3
list.addLast(0);// 0 1 2 2 3 "0"
printList(list);
}
offer添加元素
boolean | 将指定的元素添加为此列表的尾部(最后一个元素)。 |
boolean |
在此列表的前面插入指定的元素。 |
boolean | 在该列表的末尾插入指定的元素。 |
offer(E e): 这个方法将指定的元素添加到此列表的末尾(最后一个元素)。这个方法等同于add(E e)和offerLast(E e)。
offerFirst(E e): 这个方法在此列表的开头插入指定的元素。
offerLast(E e): 这个方法在此列表末尾插入指定的元素。这个方法等同于add(E e)和offer(E e)。
@Test
@DisplayName("offer_Boolean")
public void offer(){
LinkedList<Integer> list = getList();// 1 2 3
boolean offer = list.offer(4);// 1 2 3 "4"
boolean offerFirst = list.offerFirst(5);// "5" 1 2 3 4
boolean offerLast = list.offerLast(6);// 5 1 2 3 4 "6"
printList(list);
}
push添加元素
void | push(E e) 将元素推送到由此列表表示的堆栈上。 |
push(E e): 这个方法将元素推入此列表所表示的堆栈。实际上,这个方法将元素插入到列表的开头。这个方法等同于addFirst(E e)。
@Test
@DisplayName("push_Void")
public void push(){
LinkedList<Integer> list = getList();
list.push(4);// "4" 1 2 3
printList(list);
}
删除元素类型方法
E | remove() 检索并删除此列表的头(第一个元素)。 |
E | remove(int index) 删除该列表中指定位置的元素。 |
boolean | remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。 |
E | removeFirst() 从此列表中删除并返回第一个元素。 |
boolean | removeFirstOccurrence(Object o) 删除此列表中指定元素的第一个出现(从头到尾遍历列表时)。 |
E | removeLast() 从此列表中删除并返回最后一个元素。 |
boolean | removeLastOccurrence(Object o) 删除此列表中指定元素的最后一次出现(从头到尾遍历列表时)。 |
@Test
@DisplayName("remove")
public void remove(){
LinkedList<Integer> list = getList();
list.addAll(getList());// 1 2 3 1 2 3
//删除列表头的第一个元素
Integer ra = list.remove();//ra = 1 "1" 2 3 1 2 3
//删除指定位置的元素,返回给位置的元素值
Integer rb = list.remove(2);//rb = 1 2 3 "1" 2 3
printList(list);
//从列表中删除指定元素的第一个出现
Boolean rc = list.remove(new Integer(3));//true 2 "3" 2 3
printList(list);
}
注意remove中的remove(int index)和remove(Object o) ,在一般的刷题中这两个方法都很少用到,但是要懂得区分,就像你要删除数字3,如果直接为 remove(3),那就默认为删除索引为3的位置的元素,注意remove(Object o)传入的参数为一个Object,要删除数字3
就要使用包装类的3才能进行删除
@Test
@DisplayName("remove_Other")
public void removeOther(){
LinkedList<Integer> list = getList();
list.addAll(getList());// 1 2 3 1 2 3
Integer ra = list.removeFirst();// "1" 2 3 1 2 3
Integer rb = list.removeLast();// 2 3 1 2 "3"
list = getList();
list.addAll(getList());// 1 2 3 1 2 3
//删除此列表中指定元素3的第一个出现
boolean f = list.removeFirstOccurrence(new Integer(3));
printList(list);// 1 2 "3" 1 2 3
boolean l = list.removeLastOccurrence(new Integer(2));
printList(list);// 1 2 1 "2" 3
}
这么多remove的方法,我记住的只有 removeFirst() 和 removeLast()
因为这两个比较常用,其他的比较少见,remove() 和 removeFirst() 的作用一样,在做题的时候觉得哪个方便用哪个就可以了
获取元素类型方法
E | element() 检索但不删除此列表的头(第一个元素)。 |
E | get(int index) 返回此列表中指定位置的元素。 |
E | getFirst() 返回此列表中的第一个元素。 |
| getLast() 返回此列表中的最后一个元素。 |
|
|
int | lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。 |
这一类的方法了解即可,要是说有真的常用的我觉得就 get(int index)
,这个方法一般在用于遍历集合的时候会常用到,其他的像 getFirst()和 getLast()
其实当你学了数据结构后,就会习惯性的使用peek()或peek()衍生方法,
@Test
@DisplayName("get")
public void get() {
LinkedList<Integer> list = getList();
list.addAll(getList());// 1 2 3 1 2 3
//assertEquals:断言式,判断参数a和参数b是否相等,相等就通过运行,不等就运行报错
//element() 获取列表头元素 "1" 2 3 1 2 3
assertEquals(list.element(), new Integer(1));
//get(2) 返回此列表中位置索引为2的元素 1 2 "3" 1 2 3
assertEquals(list.get(2), new Integer(3));
//getFirst() 返回此列表中的第一个元素。相等于element() "1" 2 3 1 2 3
assertEquals(list.getFirst(), new Integer(1));
//getLast() 返回此列表中的最后一个元素 1 2 3 1 2 "3"
assertEquals(list.getLast(), new Integer(3));
Integer targer = new Integer(2);
//indexOf(new Integer(2)) 返回此列表中元素2的第一次出现的索引,如果此列表不包含元素,则返回-1
assertEquals(list.indexOf(targer), 1);
//lastIndexOf(new Integer(2)) 返回此列表中元素2的最后一次出现的索引,如果此列表不包含元素,则返回-1
assertEquals(list.lastIndexOf(targer), 4);
}
堆栈类型获取元素方法
E | peek() 检索但不删除此列表的头(第一个元素)。 |
E | peekFirst() 检索但不删除此列表的第一个元素,如果此列表为空,则返回 null 。 |
E | peekLast() 检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null 。 |
E | poll() 检索并删除此列表的头(第一个元素)。 |
E | pollFirst() 检索并删除此列表的第一个元素,如果此列表为空,则返回 null 。 |
E | pollLast() 检索并删除此列表的最后一个元素,如果此列表为空,则返回 null 。 |
E | pop() 从此列表表示的堆栈中弹出一个元素。 |
这些都是LinkedList类中很常用的方法,即使有些功能跟上面已经介绍过的一些方法一样,但是在学数据结构的时候,关于堆栈队列的方法基本都叫上面这些名字,所以在做题的时候这些方法的优先级就会更高一点。
@Test
@DisplayName("peek")
public void peek(){
LinkedList<Integer> list = getList();// 1 2 3
Integer peek = list.peek();// 1
Integer peekFirst = list.peekFirst();// 1
Integer peekLast = list.peekLast();// 3
}
@Test
@DisplayName("poll_pop")
public void poll_pop(){
LinkedList<Integer> list = getList();
list.addAll(getList());// 1 2 3 1 2 3
Integer poll = list.poll();// 1 "1" 2 3 1 2 3
Integer pollFirst = list.pollFirst();// 2 "2" 3 1 2 3
Integer pollLast = list.pollLast();// 3 3 1 2 "3"
Integer pop = list.pop();// 3 "3" 1 2
}
其他常用的方法
boolean | isEmpty() 判断列表是否为空,是则返回 true 。 |
boolean | contains(Object o) 如果此列表包含指定的元素,则返回 true 。 |
boolean | containsAll(Collection<? extends E> c) 如果此列表包含指定的集合,则返回 true 。 |
E | set(int index, E element) 用指定的元素替换此列表中指定位置的元素。 |
int | size() 返回此列表中的元素数。 |
@Test
@DisplayName("other_common")
public void common(){
LinkedList<Integer> list = getList();// 1 2 3
boolean empty = list.isEmpty();
boolean contains = list.contains(1);
boolean containsAll = list.containsAll(List.of(1, 2, 3));
assertFalse(empty);
assertTrue(contains);
assertTrue(containsAll);
int size = list.size();
Integer set = list.set(0, 2);//将索引0中的元素1该为2,返回索引0处原来的元素值
printList(list);// 2 2 3
}
还有这两个不常用的方法,了解即可
void | clear() 从列表中删除所有元素。 |
Object | clone() 返回此 LinkedList 的浅版本。 |
LinkedList的遍历
遍历LinkedList列表的4中常用方法,需要注意的是第四种,在遍历列表中同时也在删除列表中的元素,一般是在只获取一次列表元素的时候才会用到
@Test
@DisplayName("ergodic")
public void ergodic(){
LinkedList<Integer> list = getList();
//1.使用for-each循环:
for (Integer i : list) {
System.out.print(i);
}
//2.普通for循环
for(int i = 0; i < list.size();i++){
System.out.print(list.get(i));
}
//3.使用迭代器
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next());
}
//4.使用while循环弹出式获取
while(!list.isEmpty()){
// System.out.print(list.pop());
// System.out.print(list.poll());
// System.out.print(list.pollFirst());
System.out.print(list.pollLast());
}
}
LinkedList与数组的转换
在做题中一般有用到将LinkedList或其他列表转换为数组的情况,例如题目要求的返回值是数组,但是你做题的时候用了列表或者其他集合,这时候就需要将列表转换为数组
类中的提供的方法
| toArray() 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 |
| toArray(T[] a) 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 |
@Test
@DisplayName("transform")
public void transform() {
LinkedList<Integer> numList = new LinkedList<>(List.of(1, 1, 4, 5, 1, 4));
LinkedList<String> strList = new LinkedList<>(List.of("a", "a", "b", "d", "c"));
//转换为Object[]类型的
Object[] numListArray1 = numList.toArray();
Object[] strListArray1 = strList.toArray();
//转换为各自的包装类型的
Integer[] numListArray2 = numList.toArray(new Integer[]{});
String[] strListArray2 = strList.toArray(new String[]{});
}
其他类型的方法
使用自定义遍历循环
@Test
@DisplayName("transformByCircle")
public void transformByCircle() {
LinkedList<Integer> numList = new LinkedList<>(List.of(1, 1, 4, 5, 1, 4));
int[] arr = new int[numList.size()];
for (int i = 0; i < numList.size(); i++) {
arr[i] = numList.get(i);
}
System.out.println(Arrays.toString(arr));
}
使用流转换的方式配合toArray()方法使用
其实在方法toArray(),认为其有局限性是因为只能将列表转换为对应包装类型的数组,但是在实际的做题中一般的只需要你获取返回一个基本数据类型的数组,这时候的为题就变成了包装类型转换为基本数据类型,这个问题使用流就可以解决
.stream:列表转换为流
mapToXXX:创建映射
@Test
@DisplayName("transformByFlow")
public void transformByFlow() {
LinkedList<Integer> intList = new LinkedList<>(List.of(1, 1, 4));
LinkedList<Double> doubleList = new LinkedList<>(List.of(1.14, 5.14));
int[] intArray = intList.stream().mapToInt(Integer::intValue).toArray();
double[] doubleArray = doubleList.stream().mapToDouble(Double::doubleValue).toArray();
System.out.println(Arrays.toString(intArray));
System.out.println(Arrays.toString(doubleArray));
}
LinkedList的一些底层思路
LinkedList在Java中是通过双向链表实现的。每个节点都包含对前一个和后一个节点的引用。这种实现使得在LinkedList中插入和删除元素的操作更加高效,因为它不需要像数组那样移动元素。
另外,LinkedList还实现了List接口,因此它支持按索引访问、添加和删除元素的操作。但是,由于它是基于链表的,所以随机访问的性能不如基于数组的实现(如ArrayList)。
总的来说,LinkedList适合在需要频繁插入和删除元素的场景下使用,但在需要随机访问元素的场景下性能可能不如ArrayList。
如果多个线程同时访问链接列表,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这通常通过在自然封装列表的对象上进行同步来实现。 如果没有这样的对象存在,列表应该使用Collections.synchronizedList
方法“包装”。 这最好在创建时完成,以防止意外的不同步访问列表: