02月09日
一、今日完成情况
- Java学习完成循环、结构体的基本语法上手,实现项目
二、今日感悟
- 核心业务数据:
- Java项目上手基础,是Java容器,并且梳理出理解容器的基础要求,这就要求数据结构和算法我需要快速回忆,并且上手基本的数据结构算法题(力扣基本要求)
- 完成链表的学习,自行编写函数。
- 今日工作总结:
- 今天效率不高,其实是在桐树林的缘故,不过放松和学习不可兼得。
- 明日工作计划:
- 图书馆完成双向链表和快慢指针的Java实操,Java这方面的语法和C++确实非常接近。
- 今日学习成长:
- 语法和数据结构,在实践当中理解Java的类的管理。
三、备注
- 无
四、Java容器了解
C++ STL = 模板 + 值语义
Java 容器 = 泛型 + 引用语义
还是类比C++ 的STL容器,准确来说,Java的使用在工程上,不会使用基础语法当中的数组等方法的,而是使用封装完毕的Java容器,实现增删查改。
https://docs.oracle.com/javase/8/docs/api/java 这是Javase的官方文档,还是建议直接看官方文档,慢慢习惯英语即可。下面我将开始上手文档,还有简单了解常见的数据结构在Java容器当中的封装。
1、List、Set、Map
| 接口 | 特点 | 常用实现类 | 场景建议 |
|---|---|---|---|
| List | 有序、可重复、有索引 | ArrayList |
默认首选。频繁随机访问数据时最快。 |
| Set | 不允许重复、无序 | HashSet |
需要去重时使用(比如统计独立访客 IP)。 |
| Map | 键值对 (Key-Value) | HashMap |
需要快速查找(比如通过 ID 找用户信息)。 |
| 这里分别是数组,集合,图的数据结构,所以我需要快速了解这几个数据结构的定义: |
List:你需要一个“动态数组”
- ArrayList (首选):底层是数组。查询极快(
get(index)是常数时间),但在中间插入或删除元素较慢(需要移动数据)。 - LinkedList:底层是链表。适合频繁在头部/中间插入或删除,但随机访问速度慢。
Set:你需要“去重”或“检查是否存在”
- HashSet (首选):基于哈希表,速度极快,但不保证存储顺序。
- TreeSet:元素会自动排序(按自然顺序或指定比较器)。
- LinkedHashSet:既能去重,又能记住你插入的顺序。
Map:你需要“查字典”或“键值对应”
- HashMap (首选):查找、插入都是瞬时完成。
- TreeMap:按 Key 的顺序自动排序。
综上,可以看出来,这上面的数据结构,本质上使用的是下面的几个基本类型:
数据、链表、哈希表、树。关于哈希表我存储我知之甚少,所以需要打一下基础。至于数组是我们最先学习的,读取非常快,可是插入数据非常慢,因为要平移数据,这个是非常清楚的,可是关于二叉树的高级用法,还有哈希表的高级用法,我需要快速学习一下:
| 特性 | ArrayList (数组) | HashSet (哈希表) |
|---|---|---|
| 底层实现 | Object[] |
HashMap (数组 + 链表/红黑树) |
| 查询效率 | 极快($O(1)$,通过下标) | 极快($O(1)$,通过 Hash 算位置) |
| 插入效率 | 尾部快,中间慢 | 很快(不需要移动其他元素) |
| 数据顺序 | 保证顺序 | 不保证顺序 |
| 去重能力 | 无(可重复) | 有(通过 Hash 和 Equals 判定) |
2、哈希表原理
要学会哈希表就要学会红黑树,要学会红黑树就要学会二叉树的原理。
背景: 我使用C++在学习数据结构和算法的时候,使用的是STL容器的,vector方法,vector编写二叉树红黑树的原理,还是非常丝滑的,现在使用Java了,因此我需要学会Java的“vector”。
| 功能 | C++ (STL) | Java (Collections) |
|---|---|---|
| 动态数组 | vector<int> v |
List<Integer> list = new ArrayList<>(); |
| 哈希表 | unordered_map<K, V> |
HashMap<K, V> map = new HashMap<>(); |
| 有序映射 | map<K, V> (红黑树) |
TreeMap<K, V> map = new TreeMap<>(); |
| 取值 | v[i] 或 m[key] |
list.get(i) 或 map.get(key) |
| 判空/大小 | empty(), size() |
isEmpty(), size() |
| 我打算从链表开始实现一遍,顺便快速上手Java的数据结构。 |
然后使用Java编写二叉树的原理,不使用现成的库,就是用List的方法进行编写,把二叉树,红黑树,哈希表的原理都编写一遍,在了解其底层原理之后,开始快手上手其简单封装用法,所以我下面将梳理Java的最基本的数据结构–链表。
3、链表任务分解
第一阶段:定义“灵魂”—— Node 节点
第二阶段:必须亲手实现的四大基本功
第三阶段:Java 特有的“避坑”指南
第四阶段:力扣(LeetCode)实战秘籍
- 快慢指针
- 虚拟头节点 (Dummy Head)
因为Java没有指针这个说法,因此我在学习的时候,使用this的用法,感觉和python差不多。
A、MyLinkedList手写
addAtHead, addAtTail, deleteAtIndex 等方法,完成力扣一下了例题:
- 206. 反转链表 (入门必做)
- 203. 移除链表元素 (练习虚拟头节点)
- 141. 环形链表 (练习快慢指针)
package io.github.luduihang.learning.list;
class ListNode {
int val;
ListNode next;
ListNode(int val) {this.val = val;}
}
public class MyLinkedList {
public static void main(String[] args) {
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
n1.next = n2;
n2.next = n3;
n3.next = null;
printList(n1);
ListNode rev = reverseList(n1);
printList(rev);
rev = addNode(rev, 0);
printList(rev);
System.out.println(deleteNode(rev, 6));
printList(rev);
}
static void printList(ListNode head) {
ListNode curr = head;
while (curr != null) {
System.out.print(curr.val + " -> ");
curr = curr.next;
}
System.out.print("null");
System.out.println();
}
static ListNode reverseList(ListNode head) {
ListNode preNode = null;
ListNode curr = head;
while (curr != null) {
ListNode nextNode = curr.next;
curr.next = preNode;
preNode = curr;
curr = nextNode;
}
return preNode;
}
static ListNode addNode(ListNode head, int val) {
if (head == null) {
head = new ListNode(val);
}
else {
ListNode curr = head;
while (curr.next != null) {
curr = curr.next;
}
curr.next = new ListNode(val);
}
return head;
}
static boolean deleteNode(ListNode head, int val) {
ListNode virtual = new ListNode (-1);
virtual.next = head;
ListNode curr = virtual;
while (curr.next != null) {
if (curr.next.val == val) {
curr.next = curr.next.next;
return true;
}
else {
curr = curr.next;
}
}
return false;
}
}
B、静态关键词使用
在Java中,方法(函数)分为两种:
查看你的MyLinkedList类:
public class MyLinkedList {
public static void main(String[] args) {
// ...
printList(n1); // ❌ 错误:尝试直接调用实例方法
}
void printList(ListNode head) { // 实例方法(没有static)
// ...
}
ListNode reverseList(ListNode head) { // 实例方法(没有static)
// ...
}
}
问题原因:main方法是static的,但printList和reverseList是实例方法。在静态方法中,不能直接调用实例方法,因为:
- 静态方法属于类,不需要对象就能调用
- 实例方法属于对象,需要先有对象才能调用
- 在静态的
main方法中,没有MyLinkedList的实例(对象)
方案1:将方法改为静态(推荐用于工具类)
static void printList(ListNode head) { // 添加static关键字
// ...
}
static ListNode reverseList(ListNode head) { // 添加static关键字
// ...
}
优点:简单直接,不需要创建对象
缺点:如果方法需要访问类的实例变量就不行(但你的方法不需要)
方案2:创建对象实例
public static void main(String[] args) {
MyLinkedList list = new MyLinkedList(); // 创建对象
// ...
list.printList(n1); // 通过对象调用
ListNode rev = list.reverseList(n1);
list.printList(rev);
}
优点:保持了方法的实例特性,符合面向对象设计
缺点:需要多写一行创建对象的代码
对于你的MyLinkedList类,由于printList和reverseList只是操作ListNode参数,不访问类的实例变量,我推荐方案1(改为静态方法)。这样代码更简洁,符合工具类的设计。
C、静态关键词 C++/Java区别
关键差异:在 C++ 中,你习惯把工具函数写在类外面作为全局函数。但在 Java 中,没有类外函数。如果你想写一个“工具函数”(比如反转链表),你必须把它写在类里,并加上 static 来模拟全局函数的行为。
之所以我在 Java 里感到“难受”,是因为 Java 强制要求所有代码必须写在 class 里,包括程序入口 main。
使用 static 的场景:工具属性
如果这个方法只依赖于传入的参数,不依赖类里的其他成员变量。
- 例子:
reverseList(ListNode head)。它只管处理传入的head,不管MyLinkedList类里现在有多少个元素。 - 地位:它更像是一个“函数式”的工具。
D、单向链表函数梳理
public static void main(String[] args)
static void printList(ListNode head)
static ListNode reverseList(ListNode head)
static ListNode addNode(ListNode head, int val)
static boolean deleteNode(ListNode head, int val)
以上函数是常见的函数,对于链表的节点,明天我尝试双向链表,如果时间有余,尝试二叉树的快速上手,特别是后天关于红黑树需要系统性的上手,这是充满智慧的。
五、无筛选视频网站
检索南京大屠杀的视频的时候,这个链接比较好看:
https://www.dailymotion.com/video/x2toixb
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 kipleyarch@gmail.com