本文开坑伯克利 CS 61B(算法与数据结构)2024年春季课程学习笔记,Lecture 1 & Lecture 2 的内容为课程介绍与 Java 基础,因此直接跳过。本文内容为介绍基本数据类型与引用数据类型的区别,以及手动实现整数列表。
1. 基本数据类型 & 引用数据类型
在 Java 中,所有数据类型可以分为两大类:Primitive Types(基本数据类型) 和 Reference Types(引用数据类型)。基本数据类型是 Java 语言内置的、最基础的数据类型,它们直接存储值,而不是对象的引用。
Java 提供了8种基本数据类型,分别是:byte
、short
、int
、long
、float
、double
、char
、boolean
。基本数据类型直接存储值,存储在栈内存中,访问速度快,因为栈内存的读写效率高于堆内存。
引用数据类型是指存储在堆内存中的对象的引用。它们通过对象的引用(内存地址)来访问对象的实际数据。引用数据类型包括:类(Class)、接口(Interface)、数组(Array)、枚举(Enum)、包装类(Wrapper Classes)。
看下面这个例子:
package CS61B.Lecture3;public class PollQuestions {public static void main(String[] args) {Walrus a = new Walrus(1000, 8.3);Walrus b;b = a;b.weight = 5;System.out.println(a); // Weight: 5, TuskSize: 8.30System.out.println(b); // Weight: 5, TuskSize: 8.30int x = 5;int y;y = x;y = 2;System.out.println(x); // 5System.out.println(y); // 2}public static class Walrus {public int weight;public double tuskSize;public Walrus(int weight, double tuskSize) {this.weight = weight;this.tuskSize = tuskSize;}public String toString() {return String.format("Weight: %d, TuskSize: %.2f", weight, tuskSize);}}
}
对于基本数据类型,执行 y = x
实际上是将 x
在内存中所存放的值复制给 y
,两个变量互相独立。
而 Walrus
类的对象为引用数据类型,当声明任何引用类型的变量时,无论对象类型是什么,Java 都精确地分配一个64位大小的空间,这些位可以设置为 null
(全为零)或该类的特定实例的64位地址(由 new
返回)。
我们创建了一个 Walrus
对象,并将其引用赋值给变量 a
。此时,a
指向堆内存中的一个 Walrus
对象,而之后又声明了一个变量 b
,并将 a
的引用复制给 b
。此时,a
和 b
都指向同一个 Walrus
对象(内存地址相同)。换句话说,a
和 b
是同一个对象的两个引用。
通过 IDEA 的 Java Visualizer
插件进行调试可以直观地看到不同数据在内存中的情况:
对于函数的参数传递同样会完成复制值的操作,例如以下代码的 doStuff
方法接收的参数 w
执行了 w = walrus
操作,而 walrus
变量为一个 Walrus
类的对象地址,因此 w
接收到的是从 walrus
那复制过来的地址,而不像 x
接收到的是从 a
复制过来的值:
package CS61B.Lecture3;public class ParameterPassing {public static void main(String[] args) {Walrus walrus = new Walrus(3500, 10.5);int a = 9;doStuff(walrus, a);System.out.println(walrus); // Weight: 2500, TuskSize: 10.50System.out.println(a); // 9}public static void doStuff(Walrus w, int x) {w.weight -= 1000;x -= 5;}public static class Walrus {public int weight;public double tuskSize;public Walrus(int weight, double tuskSize) {this.weight = weight;this.tuskSize = tuskSize;}public String toString() {return String.format("Weight: %d, TuskSize: %.2f", weight, tuskSize);}}
}
2. 数组与列表
数组不是基本数据类型,因此也需要用 new
进行初始化:
int[] a = new int[]{0, 1, 2, 3}
数组的大小在创建时必须指定,并且一旦创建,其大小不能改变,数组的值存储在堆内存中,但数组变量(引用)存储在栈内存中,也就是上述代码中的变量 a
。
列表的大小可以动态变化,可以根据需要添加或删除元素,列表本身是一个对象,存储在堆内存中,其内部通过动态数组或其他数据结构(如链表)来存储元素。
我们先手动实现一个整数列表:
package CS61B.Lecture3;public class IntList {public int val;public IntList next;public IntList(int val) {this.val = val;this.next = null;}public static void main(String[] args) {IntList L = new IntList(5);L.next = new IntList(3);L.next.next = new IntList(10);while (L != null) {System.out.print(L.val + " ");L = L.next;} // 5 3 10}
}
其可视化效果如下:
同样我们还能构建一个反向列表,即元素在首部插入:
package CS61B.Lecture3;public class IntList {public int val;public IntList next;public IntList(int val, IntList next) {this.val = val;this.next = next;}public static void main(String[] args) {IntList L = new IntList(5, null);L = new IntList(3, L);L = new IntList(10, L);while (L != null) {System.out.print(L.val + " ");L = L.next;} // 10 3 5}
}
最后我们可以实现求解列表长度以及获取某个元素的方法:
package CS61B.Lecture3;public class IntList {public int val;public IntList next;public IntList(int val, IntList next) {this.val = val;this.next = next;}// 使用递归求解列表长度public int sizeRecursive() {if (this.next == null) {return 1;} else return 1 + this.next.sizeRecursive();}// 使用遍历求解列表长度public int sizeIterative() {int res = 0;IntList p = this; // 创建一个指向当前地址的变量while (p != null) {res++;p = p.next;}return res;}// 使用递归求解列表第i个元素public int getVal(int i) {if (i >= this.sizeRecursive()) {System.out.println(String.format("Index %d is out of range!", i));return 0;}if (i == 0) return this.val;else return this.next.getVal(i - 1);}public static void main(String[] args) {IntList L = new IntList(5, null);L.next = new IntList(3, null);L.next.next = new IntList(10, null);System.out.println(L.sizeRecursive()); // 3System.out.println(L.sizeIterative()); // 3System.out.println(L.getVal(2)); // 10while (L != null) {System.out.print(L.val + " ");L = L.next;} // 5 3 10}
}