从 Go 开发公司跑路的第 3 天,我开始重新补 Java 基础和链表题了
从 Go 开发公司跑路的第 3 天,我开始重新补 Java 基础和链表题了
前言
大家好,我是瓦瓦gogo,这是我准备开始认真写 CSDN 学习复盘的第一篇博客。
我目前是一名大二 Java 后端方向学生。之前在一家 Go 开发公司实习过一段时间,接触过定位系统、Redis、goroutine、HTTP 推送、定时任务等内容。那段经历让我知道真实业务代码和学校里写 demo 的感觉确实不一样,但也让我发现一个问题:有些东西当时能跟着写、跟着改,可真要让我在面试里把原理和项目讲清楚,还是会有点虚。
所以最近我开始重新按 Java 后端实习面试的节奏补基础。今天的学习内容主要分成两块:一块是复习小林 coding 里的 Java 基础面试题,重点看了“数据类型”这一部分;另一块是刷了三道链表相关的双指针题。
越准备越能感觉到基础的重要性,很多时候代码能写出来,但面试不一定能讲清楚。所以今天没有一味往后学新东西,而是回头把一些基础知识重新过了一遍。Java 数据类型这些内容平时写代码经常会遇到,但真正面试时,如果只是知道“有 int、long、double 这些类型”,其实是不够的。面试官更可能继续追问包装类型、自动装箱、Integer 缓存、浮点数精度这些细节。
算法方面,链表题也是面试里很经典的一类题。今天做的三道题分别是删除链表倒数第 N 个节点、链表相交和环形链表。它们代码都不算长,但对指针移动关系的理解要求比较高。
Java 数据类型复习
Java 的数据类型可以分为两类:基本数据类型和引用数据类型。
基本数据类型一共有 8 种:
byteshortintlongfloatdoublecharboolean
其中,byte、short、int、long 都属于整数类型,float 和 double 属于浮点类型,char 用来表示字符,boolean 用来表示布尔值。
平时写代码时,最常用的应该是 int、long、double 和 boolean。比如循环下标一般用 int,数据库里的主键 id 很多时候会用 Long,判断状态经常用 boolean。
这部分看起来简单,但面试中经常不会只问“Java 有哪些基本数据类型”。如果回答完 8 种类型,面试官很可能继续问它们的包装类型是什么,以及基本类型和包装类型有什么区别。
基本类型存的是具体的值,而包装类型是对象。比如 int 对应的包装类型是 Integer,long 对应 Long,double 对应 Double,boolean 对应 Boolean。
包装类型的一个重要作用是配合集合和泛型使用。比如 ArrayList<int> 是不允许的,只能写成 ArrayList<Integer>。因为 Java 泛型要求使用引用类型,不能直接使用基本类型。
另外,基本类型有默认值,比如 int 默认是 0,boolean 默认是 false。包装类型作为对象,默认值是 null。这也是实际开发和面试中都需要注意的地方。
面试高频追问
1. 自动装箱和自动拆箱
自动装箱就是基本类型自动转换成包装类型,自动拆箱就是包装类型自动转换成基本类型。
Integer a = 10; // 自动装箱
int b = a; // 自动拆箱
这段代码表面上看起来很自然,但底层其实是编译器帮我们做了转换。Integer a = 10 可以理解为调用了 Integer.valueOf(10),而 int b = a 可以理解为调用了 a.intValue()。
这里最容易被追问的是空指针问题:
Integer num = null;
int value = num;
这段代码编译时不会报错,但运行时会出现空指针异常。原因是 num 在拆箱时需要调用 intValue() 方法,而此时 num 是 null。
所以在实际写代码时,如果一个包装类型变量可能为 null,就不要直接拆箱,应该先做判空处理。
2. Integer 缓存机制
Integer 缓存机制也是 Java 基础面试中的高频点。
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
默认情况下,Integer 会缓存 -128 到 127 之间的整数对象。所以上面 a 和 b 使用的是同一个缓存对象,== 比较结果是 true。
而 128 超出了默认缓存范围,c 和 d 通常是两个不同对象,所以 == 比较结果是 false。
这里要记住一点:包装类型比较数值时,尽量使用 equals(),不要依赖 ==。
3. float 和 double 的精度问题
浮点数精度问题也很常见,比如:
System.out.println(0.1 + 0.2);
这段代码输出的结果并不是精确的 0.3。原因是计算机底层使用二进制表示小数,而有些十进制小数无法被二进制精确表示,所以会产生精度误差。
如果是普通的科学计算或者对精度要求不高的场景,使用 float、double 问题不大。但如果涉及金额计算,就不应该直接使用它们,而应该使用 BigDecimal。
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));
这里使用字符串创建 BigDecimal,可以避免先把小数转成 double 后产生精度误差。
三道算法题思路
今天做的三道链表题都可以用双指针解决。链表和数组不一样,不能通过下标随机访问,只能从当前节点走到下一个节点。所以这类题的关键不是写多少代码,而是想清楚两个指针分别怎么走。
下面统一使用这个链表节点定义:
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
1. 删除链表倒数第 N 个节点
这道题的核心思路是快慢指针。先让快指针走 n 步,然后快慢指针一起走。当快指针走到最后一个节点时,慢指针刚好停在要删除节点的前一个位置。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
这里用了一个虚拟头节点 dummy,这样可以统一处理删除头节点的情况。如果没有 dummy,当要删除的是第一个节点时,还需要额外判断。
这题的关键是让 fast 和 slow 之间保持 n 个节点的距离。最后 slow.next 就是要删除的节点,直接让 slow.next = slow.next.next 即可。
2. 链表相交
链表相交不是判断两个节点的值是否相等,而是判断两个链表是否在某个节点引用上相同。
这道题可以让两个指针分别从两个链表头节点出发。如果一个指针走到末尾,就切换到另一条链表的头节点继续走。这样两个指针最终走过的总长度是一样的。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA;
ListNode pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
假设链表 A 独有部分长度是 a,链表 B 独有部分长度是 b,公共部分长度是 c。那么指针 A 走的长度是 a + c + b,指针 B 走的长度是 b + c + a。如果有交点,它们会在交点相遇;如果没有交点,它们最后都会走到 null。
这题比较巧妙的地方在于不用提前计算链表长度,也不用额外开空间。
3. 环形链表
环形链表可以用快慢指针判断。慢指针每次走一步,快指针每次走两步。如果链表有环,快指针最终一定会在环中追上慢指针;如果没有环,快指针会先走到 null。
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
这里循环条件必须写成 fast != null && fast.next != null,因为快指针每次要走两步。如果不判断 fast.next,就可能出现空指针异常。
有环时,快指针进入环后会不断缩短和慢指针之间的距离。因为每一轮快指针都比慢指针多走一步,所以最后一定会相遇。
今日总结
今天复习下来,我感觉 Java 数据类型这部分虽然属于基础内容,但并不是背一遍 8 种基本类型就结束了。真正面试时,面试官更关心的是能不能把包装类型、自动装箱拆箱、Integer 缓存、浮点数精度这些细节讲清楚。
算法题方面,三道链表题都用到了双指针,但每道题的指针设计不一样。删除倒数第 N 个节点是利用快慢指针之间的固定距离;链表相交是让两个指针走相同总长度;环形链表是利用快慢指针的速度差判断是否有环。
今天最大的收获是:准备面试不能只停留在“我看过”或者“我会写代码”,还要能把为什么这么写讲出来。晚上还要复习一下期末考试相关内容,如果时间够的话,再看看之前公司的业务代码,尝试把那段经历包装成能讲清楚的项目经验。后面继续复习时,我也会尽量按照“概念、易错点、代码、解释”的方式整理,这样对面试表达会更有帮助。
更多推荐

所有评论(0)