JavaSE 数组从入门到面试全解析
JavaSE 数组从入门到面试全解析
在 Java 编程的世界里,数组是一种基础且重要的数据结构。无论是初学者入门,还是开发者面试,数组都是绕不开的知识点。本文将从数组的基础知识讲起,逐步深入,并解答面试中关于数组的常见问题。
数组基础知识
数组的定义
数组是相同类型数据的有序集合。它可以存储多个相同类型的元素,这些元素在内存中占据连续的空间,通过索引来访问。
数组的特点
-
数组中的元素必须是相同类型。
-
数组的长度是固定的,一旦创建,长度就不能改变。
-
数组中的元素可以是基本数据类型,也可以是引用数据类型。
-
数组本身是引用数据类型,它存储在堆内存中。
数组的声明
在 Java 中,数组的声明有两种方式:
// 方式一
数据类型[] 数组名;
// 方式二
数据类型 数组名[];
例如:
int[] arr1;
int arr2[];
通常推荐使用方式一,这种方式更能体现数组是一种引用类型。
数组的初始化
数组的初始化有静态初始化和动态初始化两种方式。
静态初始化
静态初始化是在声明数组的同时为数组元素赋值,数组的长度由赋值的元素个数决定。
// 完整语法
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, ..., 元素n};
// 简化语法
数据类型[] 数组名 = {元素1, 元素2, ..., 元素n};
示例:
int[] arr1 = new int[]{1, 2, 3, 4, 5};
int[] arr2 = {6, 7, 8, 9, 10};
动态初始化
动态初始化是先指定数组的长度,然后再为数组元素赋值,数组元素会有默认值(基本数据类型的默认值如 int 为 0,boolean 为 false 等;引用数据类型的默认值为 null)。
数据类型[] 数组名 = new 数据类型[长度];
示例:
int[] arr = new int[5];
arr[0] = 1;
arr[1] = 2;
// 依次为其他元素赋值
数组的访问
数组通过索引来访问元素,索引从 0 开始,最大索引为数组长度减 1。
数组名[索引]
示例:
int[] arr = {1, 2, 3};
System.out.println(arr[0]); // 输出1
arr[1] = 4;
System.out.println(arr[1]); // 输出4
数组的遍历
遍历数组就是依次访问数组中的每个元素,常用的方式有 for 循环和增强 for 循环(foreach)。
for 循环遍历
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);
}
增强 for 循环遍历
增强 for 循环更简洁,但不能修改数组元素的值(如果修改,不会影响原数组)。
int[] arr = {1, 2, 3, 4, 5};
for (int num : arr) {System.out.println(num);
}
数组的常见操作
数组的复制
可以使用System.arraycopy()方法或Arrays.copyOf()方法来复制数组。
- System.arraycopy():public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length),其中 src 是源数组,srcPos 是源数组复制的起始位置,dest 是目标数组,destPos 是目标数组接收复制元素的起始位置,length 是复制的长度。
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
System.arraycopy(src, 0, dest, 0, 3);
// dest数组此时为{1, 2, 3, 0, 0}
- Arrays.copyOf():public static T[] copyOf(T[] original, int newLength),original 是源数组,newLength 是新数组的长度。如果新长度大于源数组长度,多余部分用默认值填充;如果小于,只复制前 newLength 个元素。
int[] src = {1, 2, 3, 4, 5};
int[] dest1 = Arrays.copyOf(src, 3); // dest1为{1, 2, 3}
int[] dest2 = Arrays.copyOf(src, 7); // dest2为{1, 2, 3, 4, 5, 0, 0}
数组的排序
可以使用Arrays.sort()方法对数组进行排序,该方法对基本数据类型数组采用双轴快速排序算法,对引用数据类型数组采用 TimSort 算法。
int[] arr = {3, 1, 4, 1, 5, 9};
Arrays.sort(arr);
// 排序后arr为{1, 1, 3, 4, 5, 9}
数组的查找
- 线性查找:逐个遍历数组元素,查找目标值。
public static int linearSearch(int[] arr, int target) {for (int i = 0; i < arr.length; i++) {if (arr[i] == target) {return i;}}return -1;
}
- 二分查找:要求数组必须是有序的,通过不断缩小查找范围来查找目标值。Arrays.binarySearch()方法实现了二分查找。
int[] arr = {1, 3, 5, 7, 9};
int index = Arrays.binarySearch(arr, 5); // index为2
int index2 = Arrays.binarySearch(arr, 4); // 查找不到,返回-3(插入点为2,-(2 + 1) = -3)
面试常见问题及答案
问题 1:数组和集合有什么区别?
答案:数组和集合的区别主要体现在以下几个方面:
-
长度方面:数组的长度是固定的,一旦创建就不能改变;而集合的长度是可变的,可以动态地添加或删除元素。
-
存储类型方面:数组可以存储基本数据类型,也可以存储引用数据类型;而集合只能存储引用数据类型(如果要存储基本数据类型,需要使用对应的包装类)。
-
功能方面:集合提供了丰富的方法来操作元素,如添加、删除、查找等,而数组的操作相对简单,很多功能需要自己实现。
-
类型安全方面:泛型出现后,集合可以提供编译时的类型安全检查;而数组在编译时也会进行类型检查,但在某些情况下可能会出现类型转换异常。
问题 2:数组的初始化方式有哪几种?
答案:数组的初始化方式有静态初始化和动态初始化两种。
-
静态初始化:在声明数组的同时为数组元素赋值,数组的长度由赋值的元素个数决定。例如int[] arr = {1, 2, 3};或int[] arr = new int[]{1, 2, 3};。
-
动态初始化:先指定数组的长度,然后再为数组元素赋值,数组元素会有默认值。例如int[] arr = new int[3];,然后再为 arr [0]、arr [1]、arr [2] 赋值。
问题 3:如何获取数组的长度?
答案:在 Java 中,可以通过数组的length属性来获取数组的长度,例如arr.length。需要注意的是,length是属性,不是方法,所以不需要加括号。
问题 4:数组的索引为什么从 0 开始?
答案:主要有以下几个原因:
-
历史原因:许多早期的编程语言(如 C 语言)的数组索引从 0 开始,Java 在设计时借鉴了 C 语言的很多特性,包括数组索引从 0 开始。
-
内存地址计算:数组元素在内存中是连续存储的,对于数组arr,第i个元素的内存地址可以通过arr[0]的地址加上i * 元素大小来计算,从 0 开始可以使计算更简单。
问题 5:什么是数组的深拷贝和浅拷贝?
答案:
-
浅拷贝:当数组中的元素是引用数据类型时,浅拷贝只是复制了数组的引用,新数组和原数组中的元素指向同一个对象。修改新数组中元素的属性,会影响原数组中对应元素的属性。
-
深拷贝:不仅复制了数组的引用,还为数组中的每个引用数据类型元素创建了一个新的对象,并将原对象的属性值复制到新对象中。新数组和原数组中的元素指向不同的对象,修改新数组中元素的属性,不会影响原数组中对应元素的属性。
对于基本数据类型数组,浅拷贝和深拷贝的效果是一样的,因为基本数据类型存储的是值本身。
问题 6:Arrays 工具类有哪些常用方法?
答案:Arrays 工具类提供了很多操作数组的静态方法,常用的有:
-
sort():对数组进行排序。
-
binarySearch():在有序数组中进行二分查找,返回元素的索引。
-
copyOf():复制数组,创建一个新的数组。
-
arraycopy():复制数组的一部分到另一个数组。
-
equals():比较两个数组是否相等(元素值和顺序都相同)。
-
fill():将数组中的元素填充为指定的值。
-
toString():将数组转换为字符串。
问题 7:数组在内存中是如何存储的?
答案:数组是引用数据类型,它在内存中的存储分为两部分:
-
数组引用:存储在栈内存中,它指向数组对象在堆内存中的地址。
-
数组对象:存储在堆内存中,数组中的元素就存储在数组对象中。对于基本数据类型数组,数组对象直接存储元素的值;对于引用数据类型数组,数组对象存储的是元素对象的引用(即元素对象在堆内存中的地址)。
例如,int[] arr = new int[3];,arr是数组引用,存储在栈内存中,它指向堆内存中创建的int数组对象,该数组对象有 3 个元素,初始值为 0,存储在堆内存中。
问题 8:如何判断一个数组是否包含某个元素?
答案:可以通过以下几种方式判断:
-
对于无序数组,使用线性查找,遍历数组中的每个元素,看是否有与目标元素相等的元素。
-
对于有序数组,可以使用二分查找(如Arrays.binarySearch()方法),如果返回的索引大于等于 0,则说明数组包含该元素。
-
也可以将数组转换为集合,然后使用集合的contains()方法来判断,例如Arrays.asList(arr).contains(target),但需要注意,对于基本数据类型数组,这种方式需要先将其转换为对应的包装类数组。
数组作为 JavaSE 中的基础数据结构,是我们学习后续复杂数据结构和算法的基石。掌握好数组的相关知识,不仅能帮助我们更好地理解 Java 的内存模型和数据处理逻辑,也能在面试中从容应对各类问题。希望本文所整理的内容能为你的 Java 学习之路助力,祝大家在编程的世界里不断探索,收获成长!