Java 基础知识点——数组相关
目录
1️⃣基础知识点:
1. 数组的定义与基本特性
2. 数组的创建
3. 数组的访问与操作
4. 数组的特性与限制
5. 数组的常用方法
6. 多维数组
7. 数组的内存模型
8. 数组与集合的对比
9. 数组的克隆
10. 注意事项
✅总结:
2️⃣相关的面试问题:
1. Java 中数组是对象吗?数组的类型是什么?
2. 数组和 ArrayList 有什么区别?
3. 如何声明并初始化一个长度为 10 的 int 数组,所有值为 42?
4. 下面代码会不会抛出异常?
5. 如何实现数组的反转?时间复杂度是多少?
6. 对一个数组排序,你会选择哪种方式?为什么?
7. Java 数组在内存中是如何存储的?
8. 数组的 clone() 是浅拷贝还是深拷贝?
9. Arrays.asList() 有什么坑?
10. Java 中如何实现不定长数组?
11. 手写一个方法:找出数组中只出现一次的元素(其他元素出现两次)
12. 如何用数组实现一个栈/队列?
13. 实现一个 O(1) 的获取数组最大值结构(辅助栈思路)
📌 Part 1: 基于算法的数组面试题
1. 找出数组中只出现一次的数字(其余出现两次)
2. 数组中两个数的和等于目标值
3. 移动零
4. 最大子数组和
5. 数组旋转
6. 数组去重(原地去重)
7. 合并两个有序数组
8. 寻找数组的第 K 大元素
9. 二维数组查找(行列有序)
10. 二维数组旋转(原地)
📌 Part 2: 基于 JVM 原理的数组面试题(参考回答)
1. JVM 中数组如何分配内存?
2. 数组与对象在内存布局上的异同?
3. 为何数组越界是运行时异常?
4. 数组长度为何不可变?
5. JVM 优化数组访问的方式?
6. 为何数组可以存放基本类型而泛型不行?
7. 多维数组在内存中的表示?
8. Arrays.asList() 与原数组关系?
9. clone() 是浅拷贝还是深拷贝?
10. 使用 Unsafe 或反射操作数组?
具体内容如下:
1️⃣基础知识点:
1. 数组的定义与基本特性
- 定义:数组是一个固定大小的、相同类型元素的集合。可以存储基本数据类型(如int、char等)或对象类型。
- 大小固定:数组一旦创建,其大小不能改变。
- 索引:数组的索引从0开始,最后一个元素的索引为
length - 1
。
2. 数组的创建
- 一维数组:
int[] array1 = new int[5]; // 创建一个长度为5的整型数组 int[] array2 = {1, 2, 3, 4, 5}; // 直接初始化
- 二维数组:
int[][] matrix = new int[3][4]; // 创建一个3行4列的二维数组 int[][] matrix2 = { {1, 2}, {3, 4}, {5, 6} }; // 直接初始化
3. 数组的访问与操作
- 访问元素:通过索引访问元素,例如
array1[0]
。 - 遍历数组:
- 使用for循环:
for (int i = 0; i < array1.length; i++) {System.out.println(array1[i]); }
- 使用增强for循环:
for (int value : array1) {System.out.println(value); }
- 使用for循环:
4. 数组的特性与限制
- 类型一致性:数组中的所有元素必须是相同类型。
- 内存分配:数组在内存中是连续分配的,效率较高,但大小固定的特性可能导致内存浪费。
5. 数组的常用方法
- 数组的复制:
- 使用
System.arraycopy()
方法:System.arraycopy(sourceArray, 0, destArray, 0, length);
- 使用
Arrays.copyOf()
方法:int[] newArray = Arrays.copyOf(originalArray, newLength);
- 使用
- 排序与查找:
- 使用
Arrays.sort()
进行排序:Arrays.sort(array);
- 使用
Arrays.binarySearch()
进行二分查找:int index = Arrays.binarySearch(array, key);
- 使用
6. 多维数组
- 定义与使用:多维数组是数组的数组,可以是二维、三维等。例如:
int[][] twoDimArray = {{1, 2, 3},{4, 5, 6},{7, 8, 9}
};
多维数组的高级使用
- 不规则数组:可以创建不规则的多维数组(如“锯齿状”数组),例如:
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2]; // 第一行有2个元素
jaggedArray[1] = new int[3]; // 第二行有3个元素
jaggedArray[2] = new int[1]; // 第三行有1个元素
7. 数组的内存模型
- 内存布局:数组在内存中是连续的,每个元素占用固定的字节数(根据数据类型),这使得数组在访问时具有很高的效率。
- 栈与堆:数组变量的引用存储在栈中,而数组本身存储在堆中。
8. 数组与集合的对比
- 大小可变性:数组大小固定,而集合(如
ArrayList
)大小可变。 - 性能:数组在访问速度上更快,但集合提供了更丰富的功能(如动态扩展、插入、删除等)。
9. 数组的克隆
- 浅拷贝与深拷贝:使用
clone()
方法进行浅拷贝,注意对对象数组的深拷贝需要手动实现。int[] clonedArray = originalArray.clone();
10. 注意事项
- 空数组:可以创建一个长度为0的数组,但不能访问其元素。
- 数组越界:访问数组时如果索引超出范围,会抛出
ArrayIndexOutOfBoundsException
异常。 - 性能:在处理大量数据时,考虑数组的内存使用和访问效率。
✅总结:
数组是Java中最基本的数据结构之一,掌握数组的创建、访问、操作以及其特性,对编写高效的Java程序至关重要。理解数组的局限性和适用场景,可以帮助开发者在实际开发中做出更好的选择。
2️⃣相关的面试问题:
1. Java 中数组是对象吗?数组的类型是什么?
参考回答:
是的,Java 中的数组是对象,继承自Object
类。数组的类型由元素类型和维度共同决定。例如,int[]
是一种对象类型,String[][]
是另一种。
2. 数组和 ArrayList 有什么区别?
参考回答:
特性 | 数组 (int[] ) | ArrayList |
---|---|---|
长度 | 固定 | 动态扩容 |
类型 | 基本类型/对象 | 只能存对象 |
性能 | 更快(没有装箱) | 相对慢 |
API 支持 | 无 | 丰富的 API |
3. 如何声明并初始化一个长度为 10 的 int 数组,所有值为 42?
参考回答:
int[] arr = new int[10]; Arrays.fill(arr, 42);
4. 下面代码会不会抛出异常?
int[] arr = new int[5]; System.out.println(arr[5]);
参考回答:
会抛出ArrayIndexOutOfBoundsException
,合法索引范围是0
到4
。
5. 如何实现数组的反转?时间复杂度是多少?
参考回答:
for (int i = 0, j = arr.length - 1; i < j; i++, j--) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
时间复杂度是 O(n)。
6. 对一个数组排序,你会选择哪种方式?为什么?
参考回答:
小数组:使用
Arrays.sort(arr)
,底层是 Dual-Pivot QuickSort(对 primitives)或 TimSort(对对象数组);大数组或需要稳定性:用
TimSort
更好(Collections.sort()
背后);并行:可以考虑
Arrays.parallelSort()
。
7. Java 数组在内存中是如何存储的?
参考回答:
Java 数组在堆中分配;
数组对象包含:数组长度(metadata)+ 元素区域;
通过索引偏移寻址访问,访问速度快(连续内存);
多维数组其实是数组的数组(非真正的矩阵);
8. 数组的 clone()
是浅拷贝还是深拷贝?
参考回答:
是浅拷贝。如果是基本类型数组,复制的是值;如果是对象数组,复制的是引用。
9. Arrays.asList()
有什么坑?
参考回答:
List<Integer> list = Arrays.asList(1, 2, 3); list.add(4); // 抛异常
它返回的是固定大小的 List(内部是
Arrays$ArrayList
),不能增删元素;修改原数组会影响 List,反之亦然。
10. Java 中如何实现不定长数组?
参考回答:
使用集合类,如ArrayList
,因为数组本身长度不可变。如果必须用数组,可以自己封装扩容逻辑(比如每次扩容为 1.5 倍)来模拟。
11. 手写一个方法:找出数组中只出现一次的元素(其他元素出现两次)
参考回答:
public int findSingle(int[] nums) { int result = 0; for (int num : nums) { result ^= num; // 异或操作 } return result;
}
12. 如何用数组实现一个栈/队列?
参考回答:
封装一个类,维护一个指针(top/head/tail),配合数组读写实现。面试时可用ArrayDeque
替代,但手写可以展示底层能力。
13. 实现一个 O(1) 的获取数组最大值结构(辅助栈思路)
这个题可以作为数组+栈+算法的综合考察,适合中高级工程师。
思路
我们将使用两个数据结构:
- 主栈:用于存储实际的数组元素。
- 辅助栈:用于存储当前的最大值。当我们向主栈中添加元素时,我们会检查这个元素是否大于或等于辅助栈的栈顶元素,如果是,就将其压入辅助栈。这样,辅助栈的栈顶始终保持当前的最大值。
操作
- push(x):将元素
x
压入主栈,并根据需要更新辅助栈。- pop():从主栈中弹出元素,如果弹出的元素等于辅助栈的栈顶元素,则同时从辅助栈中弹出。
- getMax():返回辅助栈的栈顶元素,即当前的最大值。
import java.util.Stack;class MaxStack {public static void main(String[] args) {MaxStack maxStack = new MaxStack();maxStack.push(1);maxStack.push(3);maxStack.push(2);System.out.println(maxStack.getMax()); // 输出 3maxStack.pop();System.out.println(maxStack.getMax()); // 输出 3maxStack.pop();System.out.println(maxStack.getMax()); // 输出 1}private Stack<Integer> mainStack;private Stack<Integer> maxStack;public MaxStack() {mainStack = new Stack<>();maxStack = new Stack<>();}public void push(int x) {mainStack.push(x);// 如果 maxStack 为空,或者 x 大于等于 maxStack 的栈顶元素,则将 x 压入 maxStackif (maxStack.isEmpty() || x >= maxStack.peek()) {maxStack.push(x);}}public void pop() {if (mainStack.isEmpty()) {return;}int poppedValue = mainStack.pop();// 如果弹出的元素等于 maxStack 的栈顶元素,则同时弹出 maxStackif (poppedValue == maxStack.peek()) {maxStack.pop();}}public int top() {if (mainStack.isEmpty()) {throw new IllegalStateException("Stack is empty");}return mainStack.peek();}public int getMax() {if (maxStack.isEmpty()) {throw new IllegalStateException("Stack is empty");}return maxStack.peek();} }
📌 Part 1: 基于算法的数组面试题
1. 找出数组中只出现一次的数字(其余出现两次)
-
描述:给定一个整数数组,除一个元素外每个元素都出现两次,找出只出现一次的元素。
-
参考回答:使用异或操作。
public int findSingle(int[] nums) {int result = 0;for (int num : nums) {result ^= num;}return result;
}
2. 数组中两个数的和等于目标值
-
参考回答:使用哈希表存储已访问元素。
public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {int complement = target - nums[i];if (map.containsKey(complement)) {return new int[] { map.get(complement), i };}map.put(nums[i], i);}throw new IllegalArgumentException("No solution");
}
3. 移动零
-
参考回答:双指针方法。
public void moveZeroes(int[] nums) {int index = 0;for (int num : nums) {if (num != 0) nums[index++] = num;}while (index < nums.length) {nums[index++] = 0;}
}
4. 最大子数组和
-
参考回答:Kadane 算法。
public int maxSubArray(int[] nums) {int maxSoFar = nums[0], currentMax = nums[0];for (int i = 1; i < nums.length; i++) {currentMax = Math.max(nums[i], currentMax + nums[i]);maxSoFar = Math.max(maxSoFar, currentMax);}return maxSoFar;
}
5. 数组旋转
-
参考回答:三次翻转。
public void rotate(int[] nums, int k) {k %= nums.length;reverse(nums, 0, nums.length - 1);reverse(nums, 0, k - 1);reverse(nums, k, nums.length - 1);
}
private void reverse(int[] nums, int start, int end) {while (start < end) {int temp = nums[start];nums[start++] = nums[end];nums[end--] = temp;}
}
6. 数组去重(原地去重)
-
参考回答:双指针。
public int removeDuplicates(int[] nums) {if (nums.length == 0) return 0;int i = 0;for (int j = 1; j < nums.length; j++) {if (nums[j] != nums[i]) {nums[++i] = nums[j];}}return i + 1;
}
7. 合并两个有序数组
-
参考回答:逆向双指针。
public void merge(int[] nums1, int m, int[] nums2, int n) {int i = m - 1, j = n - 1, k = m + n - 1;while (j >= 0) {nums1[k--] = (i >= 0 && nums1[i] > nums2[j]) ? nums1[i--] : nums2[j--];}
}
8. 寻找数组的第 K 大元素
-
参考回答:快速选择算法(或堆排序)。
public int findKthLargest(int[] nums, int k) {PriorityQueue<Integer> heap = new PriorityQueue<>();for (int num : nums) {heap.offer(num);if (heap.size() > k) heap.poll();}return heap.peek();
}
9. 二维数组查找(行列有序)
-
参考回答:从右上角开始搜索。
public boolean searchMatrix(int[][] matrix, int target) {int row = 0, col = matrix[0].length - 1;while (row < matrix.length && col >= 0) {if (matrix[row][col] == target) return true;else if (matrix[row][col] > target) col--;else row++;}return false;
}
10. 二维数组旋转(原地)
-
参考回答:先转置,再水平翻转。
public void rotate(int[][] matrix) {int n = matrix.length;for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {int temp = matrix[i][j];matrix[i][j] = matrix[j][i];matrix[j][i] = temp;}}for (int i = 0; i < n; i++) {for (int j = 0; j < n / 2; j++) {int temp = matrix[i][j];matrix[i][j] = matrix[i][n - 1 - j];matrix[i][n - 1 - j] = temp;}}
}
📌 Part 2: 基于 JVM 原理的数组面试题(参考回答)
1. JVM 中数组如何分配内存?
-
数组在堆中分配,包含对象头、类型信息和长度字段,然后是连续的元素内存。
2. 数组与对象在内存布局上的异同?
-
都继承自
Object
;数组有长度字段,数组元素类型可为基本类型或引用类型。
3. 为何数组越界是运行时异常?
-
JVM 自动插入边界检查指令,防止非法访问。
4. 数组长度为何不可变?
-
数组创建时在堆中分配固定空间,长度信息存储在数组对象中。
5. JVM 优化数组访问的方式?
-
使用偏移地址访问(base + index * scale);JIT 会做 bounds check elimination 优化。
6. 为何数组可以存放基本类型而泛型不行?
-
泛型存在类型擦除问题,而数组是 reifiable 的,必须在运行时保留类型信息。
7. 多维数组在内存中的表示?
-
实际是数组的数组,元素不保证物理连续。
8. Arrays.asList()
与原数组关系?
-
返回的是固定大小的 List,内部使用原数组,修改会相互影响。
9. clone()
是浅拷贝还是深拷贝?
-
是浅拷贝,仅复制引用,不复制对象内容。
10. 使用 Unsafe
或反射操作数组?
-
可使用
Unsafe.getInt(array, offset)
或Array.get(array, index)
操作任意数组元素。