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);
      }
      

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,合法索引范围是 04

    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 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) 的获取数组最大值结构(辅助栈思路)

    这个题可以作为数组+栈+算法的综合考察,适合中高级工程师。

    思路

    我们将使用两个数据结构:

    1. 主栈:用于存储实际的数组元素。
    2. 辅助栈:用于存储当前的最大值。当我们向主栈中添加元素时,我们会检查这个元素是否大于或等于辅助栈的栈顶元素,如果是,就将其压入辅助栈。这样,辅助栈的栈顶始终保持当前的最大值。

    操作

    • 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()); // 输出 3
            maxStack.pop();
            System.out.println(maxStack.getMax()); // 输出 3
            maxStack.pop();
            System.out.println(maxStack.getMax()); // 输出 1
        }
    
    
        private Stack mainStack;
        private Stack maxStack;
    
        public MaxStack() {
            mainStack = new Stack<>();
            maxStack = new Stack<>();
        }
    
        public void push(int x) {
            mainStack.push(x);
            // 如果 maxStack 为空,或者 x 大于等于 maxStack 的栈顶元素,则将 x 压入 maxStack
            if (maxStack.isEmpty() || x >= maxStack.peek()) {
                maxStack.push(x);
            }
        }
    
        public void pop() {
            if (mainStack.isEmpty()) {
                return;
            }
            int poppedValue = mainStack.pop();
            // 如果弹出的元素等于 maxStack 的栈顶元素,则同时弹出 maxStack
            if (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 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 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) 操作任意数组元素。

    你可能感兴趣的:(java,开发语言,数组)