先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
(1)无参构造:
1 public LinkedList() {
2 }
2)用已有的集合创建链表的构造方法:
1 public LinkedList(Collection<? extends E> c) {
2 this();
3 addAll©;
4 }
注意:由于链表的容量可以一直增加,因此没有指定容量的构造器。
第一个为无参构造器。
第二个为使用指定集合的集合构造,并调用 addAll() 方法,继续跟进该方法,代码如下:
1 public boolean addAll(Collection<? extends E> c) {
2 return addAll(size, c);
3 }
4
5 public boolean addAll(int index, Collection<? extends E> c) {
6 //1:检查index范围是否在size之内
7 checkPositionIndex(index);
8
9 //2:toArray()方法把集合的数据存到对象数组中
10 Object[] a = c.toArray();
11 int numNew = a.length;
12 if (numNew == 0)
13 return false;
14
15
16 //3:获取当前链表的前驱和后继结点,得到插入位置的前驱节点和后继节点
17 Node pred, succ;
18 //如果插入位置为尾部,前驱节点为last,后继节点为null
19 if (index == size) {
20 succ = null;
21 pred = last;
22 }
23 //若非尾结点,获取指定位置的结点,调用node()方法得到后继节点,再得到前驱节点,
24 else {
25 succ = node(index); //获取当前节点
26 pred = succ.prev; //获取当前节点前驱节点
27 }
28
29 // 4:循环将数组中的元素插入到链表
30 for (Object o : a) {
31 @SuppressWarnings(“unchecked”) E e = (E) o;
32 //创建新节点
33 Node newNode = new Node<>(pred, e, null);
34 //如果插入位置在链表头部
35 if (pred == null)
36 first = newNode;
37 else
38 pred.next = newNode;
39 pred = newNode;
40 }
41
42 //如果插入位置在尾部,重置last节点
43 // 若插入到末尾,则数组中的最后一个元素就是尾结点
44 if (succ == null) {
45 last = pred;
46 }
47
48 //否则,将插入的链表与先前链表连接起来
49 else {
50 // 若插入到指定位置,将数组中最后一个元素与下一个位置关联起来
51 pred.next = succ;
52 succ.prev = pred;
53 }
54 size += numNew;
55 modCount++;
56 return true;
57 }
58
59 private void checkPositionIndex(int index) {
60 if (!isPositionIndex(index))
61 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
62 }
63
64 private boolean isPositionIndex(int index) {
65 return index >= 0 && index <= size;
66 }
上面可以看出addAll方法通常包括下面四个步骤:
-
检查index范围是否在size之内;
-
toArray()方法把集合的数据存到对象数组中;
-
得到插入位置的前驱和后继节点;
-
遍历数据,将数据插入到指定位置,如果没有在尾部,把原来数据链起来;
其中 node(index) 方法为获取指定位置的结点,代码如下:
1 Node node(int index) {
2 // assert isElementIndex(index);
3 // 判断下标在哪里,若下标在前一半,则从前往后遍历;否则从后往前遍历
4 if (index < (size >> 1)) {
5 Node x = first;
6 for (int i = 0; i < index; i++)
7 x = x.next;
8 return x;
9 } else {
10 Node x = last;
11 for (int i = size - 1; i > index; i–)
12 x = x.prev;
13 return x;
14 }
15 }
该方法通过遍历链表获取指定的元素。
值得注意的是,该方法并非直接从头到尾遍历整个链表,而是先判断下标的位置,若在前一半则从前往后遍历;否则就从后往前遍历。这样能减少遍历结点的个数。
因为链表的内存空间是非连续的,所以不支持随机访问(下标访问)。所以,查询某个结点是通过遍历整个链表来实现的。
===================================================================
1、新增结点方法【尾插法】:add(),addLast(),offer(),offerLast()
源码分析:
1 public boolean add(E e) {
2 linkLast(e);
3 return true;
4 }
5 public void addLast(E e) {
6 linkLast(e);
7 }
8 public boolean offer(E e) {
9 return add(e);
10 }
11 public boolean offerLast(E e) {
12 addLast(e);
13 return true;
14 }
可以看到他们都是调用了同一个方法 linkLast(e) 实现的,如下:
1 /**
2 * Links e as last element.
3 */
4 void linkLast(E e) {
5 final Node l = last;
6 // 创建一个节点,将prev指针指向链表的尾节点。
7 final Node newNode = new Node<>(l, e, null);
8
9 // 将last指针指向新创建的这个节点。
10 last = newNode;
11
12 if (l == null)
13 // 如果当前链表为空,那么将头指针也指向这个节点。
14 first = newNode;
15
16 else
17 // 若链表不为空,将新结点插入到链表尾部
18 // 将链表的尾节点的next指针指向新建的节点,这样就完整的实现了在链表尾部添加一个元素的功能。
19 l.next = newNode;
20 size++;
21 modCount++;
22 }
该操作就是将指定的结点添加到链表末尾。
2、新增节点【头插法】:addFirst(),offerFirst()
源码:
1 public void addFirst(E e) {
2 linkFirst(e);
3 }
4 public boolean offerFirst(E e) {
5 addFirst(e);
6 return true;
7 }
可以看到他们都是调用了同一个方法 linkFirst(e) 实现的,如下:
1 /**
2 * Links e as first element.
3 */
4 private void linkFirst(E e) {
5 final Node f = first;
6 // 创建一个新元素,将元素的next指针指向当前的头结点
7 final Node newNode = new Node<>(null, e, f);
8 // 将头指针指向这个节点。
9 first = newNode;
10 if (f == null)
11 // 如果当前节点为空,则把尾指针指向这个节点。
12 last = newNode;
13 else
14 // 将当前头结点的prev指针指向此结点。
15 f.prev = newNode;
16 size++;
17 modCount++;
18 }
这段代码就是实现将元素添加的链表头部。
3、新增节点【指定位置插入】:add(int index, E element)
源码:
1 public void add(int index, E element) {
2 checkPositionIndex(index);
3
4 if (index == size)
5 linkLast(element);
6 else
7 linkBefore(element, node(index));
8 }
在这里分了两种情况:
① 如果刚好到尾部,直接在尾部插入;
② 如果没有在尾部,在非null节点之前插入元素e。
源码:
1 void linkLast(E e) {
2 final Node l = last;
3 final Node newNode = new Node<>(l, e, null);
4 last = newNode;
5 if (l == null)
6 first = newNode;
7 else
8 l.next = newNode;
9 size++;
10 modCount++;
11 }
12
13 void linkBefore(E e, Node succ) {
14 // assert succ != null;
15 final Node pred = succ.prev;
16 final Node newNode = new Node<>(pred, e, succ);
17 succ.prev = newNode;
18 if (pred == null)
19 first = newNode;
20 else
21 pred.next = newNode;
22 size++;
23 modCount++;
24 }
4、设置值:set(int index, E element)
源码:
1 public E set(int index, E element) {
2 //索引检查
3 checkElementIndex(index);
4
5 //获取该索引的元素
6 Node x = node(index);
7 E oldVal = x.item;
8 x.item = element;
9 return oldVal;
10 }
源码:
1 public E get(int index) {
2 checkElementIndex(index);
3 return node(index).item;
4 }
5 private void checkElementIndex(int index) {
6 if (!isElementIndex(index))
7 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
8 }
9 private boolean isElementIndex(int index) {
10 return index >= 0 && index < size;
11 }
可以看到,这里还是调用了上面的 node() 方法进行查找的。
源码:
1 public E getFirst() {
2 final Node f = first;
3 if (f == null)
4 throw new NoSuchElementException();
5 return f.item;
6 }
7 public E element() {
8 return getFirst();
9 }
10 public E peek() {
11 final Node f = first;
12 return (f == null) ? null : f.item;
13 }
14 public E peekFirst() {
15 final Node f = first;
16 return (f == null) ? null : f.item;
17 }
区别:
getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,
其中getFirst() 和element() 方法将会在链表为空时,抛出异常;element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException;
而 peek() 和 peekFirst() 方法在链表为空时会返回空;
1 public E getLast() {
2 final Node l = last;
3 if (l == null)
4 throw new NoSuchElementException();
5 return l.item;
6 }
7 public E peekLast() {
8 final Node l = last;
9 return (l == null) ? null : l.item;
10 }
两者区别: getLast() 方法在链表为空时,会抛出NoSuchElementException,而peekLast() 则不会,只是会返回 null。
int indexOf(Object o): 从头遍历找
1 public int indexOf(Object o) {
2 int index = 0;
3 if (o == null) {
4 //从头遍历
5 for (Node x = first; x != null; x = x.next) {
6 if (x.item == null)
7 return index;
8 index++;
9 }
10 } else {
11 //从头遍历
12 for (Node x = first; x != null; x = x.next) {
13 if (o.equals(x.item))
14 return index;
15 index++;
16 }
17 }
18 return -1;
19 }
int lastIndexOf(Object o): 从尾遍历找
1 public int lastIndexOf(Object o) {
2 int index = size;
3 if (o == null) {
4 //从尾遍历
5 for (Node x = last; x != null; x = x.prev) {
6 index–;
7 if (x.item == null)
8 return index;
9 }
10 } else {
11 //从尾遍历
12 for (Node x = last; x != null; x = x.prev) {
13 index–;
14 if (o.equals(x.item))
15 return index;
16 }
17 }
18 return -1;
19 }
源码:
1 public boolean contains(Object o) {
2 return indexOf(o) != -1;
3 }
10、删除头节点方法:remove() ,removeFirst(),pop(),poll(),pollFirst()
源码:
1 public E remove() {
2 return removeFirst();
3 }
4
5 public E pop() {
6 return removeFirst();
7 }
8
9 public E removeFirst() {
10 final Node f = first;
11 if (f == null)
12 throw new NoSuchElementException();
13 return unlinkFirst(f);
14 }
15
16 public E poll() {
17 final Node f = first;
18 return (f == null) ? null : unlinkFirst(f);
19 }
20 public E pollFirst() {
21 final Node f = first;
22 return (f == null) ? null : unlinkFirst(f);
23 }
本质上都是调用了 unlinkFirst()方法
源码:
1 private E unlinkFirst(Node f) {
2 // assert f == first && f != null;
3 final E element = f.item;
4 final Node next = f.next;
5 f.item = null;
6 f.next = null; // help GC
7 first = next;
8 if (next == null)
9 last = null;
10 else
11 next.prev = null;
12 size–;
13 modCount++;
14 return element;
15 }
11、删除尾节点方法:removeLast(),pollLast()
源码:
1 public E removeLast() {
2 final Node l = last;
3 if (l == null)
4 throw new NoSuchElementException();
5 return unlinkLast(l);
6 }
7
8 public E pollLast() {
9 final Node l = last;
10 return (l == null) ? null : unlinkLast(l);
11 }
区别: removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。
本质上都是调用了 unlinkLast()方法。
源码:
1 private E unlinkLast(Node l) {
2 // assert l == last && l != null;
3 final E element = l.item;
4 final Node prev = l.prev;
5 l.item = null;
6 l.prev = null; // help GC
7 last = prev;
8 if (prev == null)
9 first = null;
10 else
11 prev.next = null;
12 size–;
13 modCount++;
14 return element;
15 }
12、删除指定元素:remove(Object o) & 删除指定位置的元素:remove(int index)
1 public boolean remove(Object o) {
2 if (o == null) {
3 for (Node x = first; x != null; x = x.next) {
4 if (x.item == null) {
5 unlink(x);
6 return true;
7 }
8 }
9 } else {
10 for (Node x = first; x != null; x = x.next) {
11 if (o.equals(x.item)) {
12 unlink(x);
13 return true;
14 }
15 }
16 }
17 return false;
18 }
19
20 public E remove(int index) {
21 checkElementIndex(index);
22 return unlink(node(index));
23 }
当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false。
本质上还是调用了 unlink(Node x) 方法:
1 E unlink(Node x) {
2 // assert x != null;
3 final E element = x.item;
4 final Node next = x.next;//得到后继节点
5 final Node prev = x.prev;//得到前驱节点
6
7 //删除前驱指针
8 if (prev == null) {
9 first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点
10 } else {
11 prev.next = next;//将前驱节点的后继节点指向后继节点
12 x.prev = null;
13 }
14
15 //删除后继指针
16 if (next == null) {
17 last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点
18 } else {
19 next.prev = prev;
20 x.next = null;
21 }
22
23 x.item = null;
24 size–;
25 modCount++;
26 return element;
27 }
13、序列化方法:writeObject(java.io.ObjectOutputStream s)
源码:
1 private void writeObject(java.io.ObjectOutputStream s)
2 throws java.io.IOException {
3 // Write out any hidden serialization magic
4 s.defaultWriteObject();
5
6 // Write out size
7 s.writeInt(size);
8
9 // Write out all elements in the proper order.
10 for (Node x = first; x != null; x = x.next)
11 s.writeObject(x.item);
12 }
14、反序列化方法:readObject(java.io.ObjectInputStream s)
源码:
1 private void readObject(java.io.ObjectInputStream s)
2 throws java.io.IOException, ClassNotFoundException {
3 // Read in any hidden serialization magic
4 s.defaultReadObject();
5
6 // Read in size
7 int size = s.readInt();
8
9 // Read in all elements in the proper order.
10 for (int i = 0; i < size; i++)
11 linkLast((E)s.readObject());
12 }
=======================================================================
队列的原理就是每次都从链表尾部添加元素,从链表头部获取元素,就像生活中的排队叫号,总是有个先来后到。
源码:
1 // 队列尾部添加一个元素,建议使用这个,约定俗成吧。
2 publicboolean offer(E e){
3 return add(e); 4 }
5
6 // 队列尾部添加一个元素
7 publicboolean offerLast(E e){
8 addLast(e);
9 return true;
10 }
11
12 // offer和offerLast底层调用的都是linkLast这个方法,顾名思义就是将元素添加到链表尾部。
13 void linkLast(E e){
14 finalNode l =last;
15
16 // 创建一个节点,将prev指针指向链表的尾节点。
17 finalNode newNode =newNode<>(l, e,null);
18
19 // 将last指针指向新创建的这个节点。
20 last= newNode;
21
22 if(l ==null)
23 // 如果当前链表为空,那么将头指针也指向这个节点。
24 first = newNode;
25 else
26 // 将链表的尾节点的next指针指向新建的节点,这样就完整的实现了在链表尾部添加一个元素的功能。
27 l.next= newNode;
28
29 size++;
30 modCount++;
31 }
32
33 // 在链表头部删除一个元素,建议用这个
34 public E poll(){
35 final Node f = first;
36 return(f ==null)?null: unlinkFirst(f);
37 }
38 // 在链表头部删除一个元素
39 public E pollFirst(){
40 final Node f = first;
41 return(f ==null)?null: unlinkFirst(f);
42 }
43
44 // poll和pollFirst底层调用的就是这个方法,将链表的头元素删除。
45 private E unlinkFirst(Node f){
46 // assert f == first && f != null;
47 final E element = f.item;
48 final Nodenext= f.next;
49 f.item =null;
50 f.next=null;// help GC
51 first =next;
52 if(next==null)
53 last=null;
54 else
55 next.prev =null;
56 size–;
57 modCount++;
58 return element;
59 }
60
61 // 获取头元素,但是不会删除他。
62 public E peek(){
63 final Node f = first;
64 return(f ==null)?null: f.item;
65 }
更准确来说,链表是一个双端链表的结构,可以在头尾都进行操作节点。
栈的原理是每次从头部添加元素,也从头部获取元素,那么后进入的元素反而最先出来。就像我们平时叠盘子,洗好了就一个一个往上放,然后要用了就从上往下一个一个拿。
源码:
1 // 在链表的头部添加一个元素
2 publicvoid push(E e){
3 addFirst(e);
4 }
5
6 // addFirst调用的就是linkFirst,这段代码就是实现将元素添加的链表头部。
7 private void linkFirst(E e){
8 final Node f = first;
9 // 创建一个新元素,将元素的next指针指向当前的头结点
10 final Node newNode =newNode<>(null, e, f);
11 // 将头指针指向这个节点。
12 first = newNode;
13 if(f ==null)
14 // 如果当前节点为空,则把尾指针指向这个节点。
15 last= newNode;
16 else
17 // 将当前头结点的prev指针指向此结点。
18 f.prev = newNode;
19 size++;
20 modCount++;
21 }
22
23 // 弹出顶部结点。
24 public E pop(){
25 return removeFirst();
26 }
27
28 // removeFirst调用的就是unlinkFirst,unlinkFirst实现将链表顶部元素删除
29 private E unlinkFirst(Node f){
30 // assert f == first && f != null;
31 final E element = f.item;
32 final Nodenext= f.next;
33 f.item =null;
34 f.next=null;// help GC
35 first =next;
36 if(next==null)
37 last=null;
38 else
39 next.prev =null;
40 size–;
41 modCount++;
42 return element;
43 }
44
45 // 获取顶部结点,但是不删除
46 public E peek(){
47 final Node f = first;
48 return(f ==null)?null: f.item;
49 }
====================================================================
LinkedList的迭代器实现有两个,一个是实现了Iterator接口的DescendingIterator,另一个则是实现了ListIterator接口的ListItr。
Docker步步实践
目录文档:
①Docker简介
②基本概念
③安装Docker
④使用镜像:
⑤操作容器:
⑥访问仓库:
⑦数据管理:
⑧使用网络:
⑨高级网络配置:
⑩安全:
⑪底层实现:
⑫其他项目:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
=null;
34 f.next=null;// help GC
35 first =next;
36 if(next==null)
37 last=null;
38 else
39 next.prev =null;
40 size–;
41 modCount++;
42 return element;
43 }
44
45 // 获取顶部结点,但是不删除
46 public E peek(){
47 final Node f = first;
48 return(f ==null)?null: f.item;
49 }
====================================================================
LinkedList的迭代器实现有两个,一个是实现了Iterator接口的DescendingIterator,另一个则是实现了ListIterator接口的ListItr。
Docker步步实践
目录文档:
[外链图片转存中…(img-WBnHIjVB-1713290932265)]
[外链图片转存中…(img-s3xnnoKZ-1713290932266)]
①Docker简介
②基本概念
③安装Docker
[外链图片转存中…(img-9xsHCXwX-1713290932266)]
④使用镜像:
[外链图片转存中…(img-R3Q1LdKj-1713290932266)]
⑤操作容器:
[外链图片转存中…(img-MUL0UyXk-1713290932267)]
⑥访问仓库:
[外链图片转存中…(img-xBnM42cg-1713290932267)]
⑦数据管理:
[外链图片转存中…(img-JVxmJsTJ-1713290932267)]
⑧使用网络:
[外链图片转存中…(img-NIMf8M3F-1713290932268)]
⑨高级网络配置:
[外链图片转存中…(img-sNqSVJVs-1713290932268)]
⑩安全:
[外链图片转存中…(img-3LlpfuXe-1713290932268)]
⑪底层实现:
[外链图片转存中…(img-9dJUzVlB-1713290932269)]
⑫其他项目:
[外链图片转存中…(img-kh9nprYX-1713290932269)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-worfNdL5-1713290932269)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!