Bỏ qua

Mảng — Array 1D & 2D

1. Khái niệm

Mảng là cấu trúc dữ liệu lưu một dãy các phần tử cùng kiểu, kích thước cố định, được đánh index từ 0.

int[] scores = {85, 92, 78, 95, 88};
//              [0] [1] [2] [3] [4]

Array memory layout — Stack reference pointing to Heap array

Ba đặc điểm cốt lõi:

  • Fixed-size — kích thước xác định khi tạo, không thể thay đổi sau đó
  • Zero-indexed — phần tử đầu ở index 0, phần tử cuối ở index length - 1
  • Heap-allocated — biến trên Stack chỉ lưu reference (địa chỉ), dữ liệu thật nằm trên Heap

2. Tại sao quan trọng

Mảng là nền tảng của mọi cấu trúc dữ liệu trong Java:

  • ArrayList bên trong dùng một Object[] để lưu dữ liệu
  • HashMap bên trong là một mảng các bucket
  • Thuật toán sắp xếp, tìm kiếm đều bắt đầu từ mảng
  • Interview thường xuyên: two-pointer, sliding window, prefix sum — đều trên mảng

3. Khai báo và khởi tạo

Ba cách khai báo mảng 1D

// Cách 1 — cấp phát, chưa gán giá trị (dùng default values)
int[] scores = new int[5]; // [0, 0, 0, 0, 0]

// Cách 2 — khai báo + khởi tạo ngay (phổ biến nhất)
int[] scores = {85, 92, 78, 95, 88};

// Cách 3 — new + initializer (dùng khi truyền thẳng vào method)
printArray(new int[]{85, 92, 78, 95, 88});

Giá trị mặc định khi dùng new

Kiểu Giá trị mặc định
int, long, short, byte 0
double, float 0.0
boolean false
char ''
Object (String, ...) null
int[] arr = new int[3];
System.out.println(arr[0]); // 0

String[] names = new String[3];
System.out.println(names[0]); // null

Truy cập và thuộc tính

int[] scores = {85, 92, 78, 95, 88};

System.out.println(scores[0]);     // 85 — phần tử đầu
System.out.println(scores[4]);     // 88 — phần tử cuối
System.out.println(scores.length); // 5 — field, không có dấu ()
scores[2] = 80;                    // gán lại giá trị

.length là field, không phải method

arr.lengthkhông có dấu (). Khác với String.length() hay List.size(). Hay nhầm khi mới học.


4. Duyệt mảng

int[] scores = {85, 92, 78, 95, 88};

for (int i = 0; i < scores.length; i++) {
    System.out.println("scores[" + i + "] = " + scores[i]);
}

Dùng khi cần chỉ số i, duyệt ngược, hoặc sửa phần tử.

int[] scores = {85, 92, 78, 95, 88};

for (int score : scores) {
    System.out.println(score);
}

Gọn hơn khi chỉ cần đọc từng phần tử, không cần index.

int[] scores = {85, 92, 78, 95, 88};
System.out.println(Arrays.toString(scores)); // [85, 92, 78, 95, 88]

In toàn bộ mảng ra một lần — tiện khi debug.


5. Các thao tác phổ biến

Tổng và trung bình

int[] scores = {85, 92, 78, 95, 88};
int sum = 0;

for (int s : scores) sum += s;

double avg = (double) sum / scores.length;
System.out.println("Tổng: " + sum);       // 438
System.out.println("Trung bình: " + avg); // 87.6

Tìm min / max

int[] scores = {85, 92, 78, 95, 88};
int min = scores[0], max = scores[0];

for (int i = 1; i < scores.length; i++) {
    if (scores[i] < min) min = scores[i];
    if (scores[i] > max) max = scores[i];
}
System.out.println("Min: " + min + ", Max: " + max); // Min: 78, Max: 95

Sao chép mảng

int[] original = {1, 2, 3, 4, 5};

// Arrays.copyOf — gọn nhất
int[] copy1 = Arrays.copyOf(original, original.length);

// Arrays.copyOfRange — copy một đoạn [fromIndex, toIndex)
int[] copy2 = Arrays.copyOfRange(original, 1, 4); // [2, 3, 4]

// System.arraycopy — nhanh nhất, kiểm soát tuyệt đối
int[] copy3 = new int[original.length];
System.arraycopy(original, 0, copy3, 0, original.length);

Không copy mảng bằng =

int[] a = {1, 2, 3};
int[] b = a;      // ❌ b và a cùng trỏ về một mảng
b[0] = 99;
System.out.println(a[0]); // 99 — a bị thay đổi!

int[] c = Arrays.copyOf(a, a.length); // ✅ copy thật sự

6. Mảng 2D

Mảng 2D là mảng của các mảng — dùng để biểu diễn ma trận, bảng, lưới ô vuông.

// Khai báo + cấp phát
int[][] matrix = new int[3][4]; // 3 hàng, 4 cột, mặc định 0

// Khai báo + khởi tạo
int[][] grid = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Truy cập: [hàng][cột]
System.out.println(grid[1][2]); // 6

System.out.println(grid.length);    // 3 — số hàng
System.out.println(grid[0].length); // 3 — số cột của hàng 0

2D Array Memory Layout

Duyệt mảng 2D

int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

for (int i = 0; i < grid.length; i++) {
    for (int j = 0; j < grid[i].length; j++) {
        System.out.printf("%3d", grid[i][j]);
    }
    System.out.println();
}
//   1  2  3
//   4  5  6
//   7  8  9
int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

for (int[] row : grid) {
    for (int val : row) {
        System.out.printf("%3d", val);
    }
    System.out.println();
}
int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
System.out.println(Arrays.deepToString(grid));
// [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Jagged array — hàng dài ngắn khác nhau

Java cho phép từng hàng trong mảng 2D có độ dài khác nhau:

int[][] triangle = new int[4][];
for (int i = 0; i < triangle.length; i++) {
    triangle[i] = new int[i + 1];
}
// hàng 0: [0]
// hàng 1: [0, 0]
// hàng 2: [0, 0, 0]
// hàng 3: [0, 0, 0, 0]

Điều này khác với C/C++ — trong Java, mảng 2D không phải vùng nhớ liên tục mà là mảng các reference.


7. java.util.Arrays

Arrays là utility class chứa các phương thức xử lý mảng hay dùng nhất — cần import java.util.Arrays.

int[] arr = {5, 2, 8, 1, 9, 3};

// Sắp xếp tại chỗ
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 5, 8, 9]

// Tìm nhị phân — mảng phải đã sort
int idx = Arrays.binarySearch(arr, 5);    // 3

// Điền giá trị
int[] filled = new int[5];
Arrays.fill(filled, 7);                   // [7, 7, 7, 7, 7]

// Sao chép
int[] copy  = Arrays.copyOf(arr, 4);            // [1, 2, 3, 5]
int[] range = Arrays.copyOfRange(arr, 2, 5);    // [3, 5, 8]

// So sánh nội dung
int[] a = {1, 2, 3}, b = {1, 2, 3};
System.out.println(Arrays.equals(a, b));        // true
System.out.println(a == b);                     // false — địa chỉ khác

// Mảng 2D
int[][] x = {{1, 2}, {3, 4}};
int[][] y = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(x, y));    // true
System.out.println(Arrays.deepToString(x));     // [[1, 2], [3, 4]]

8. Array vs ArrayList

int[] / String[] ArrayList<Integer>
Kích thước Cố định Động, tự mở rộng
Kiểu phần tử Primitive + Object Object only (autoboxing)
Hiệu năng Cao hơn Có overhead của boxed type
API Ít (Arrays.*) Phong phú (add, remove, contains...)
Khi dùng Kích thước biết trước, cần hiệu năng Kích thước không biết trước, cần thêm/xóa

Nguyên tắc chọn

Biết trước số phần tử và không cần thêm/xóa → dùng array. Còn lại → ArrayList. Phase 02 sẽ đi sâu vào toàn bộ Java Collections.


9. Code ví dụ

Verified

Bản đầy đủ có thể compile: ArraysDemo.java

import java.util.Arrays;

public class ArraysDemo {

    static int sum(int[] arr) {
        int total = 0;
        for (int x : arr) total += x;
        return total;
    }

    static double average(int[] arr) {
        return (double) sum(arr) / arr.length; // (1)
    }

    static int max(int[] arr) {
        int result = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > result) result = arr[i];
        }
        return result;
    }

    static int[] twoSum(int[] nums, int target) { // (2)
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] == target) return new int[]{i, j};
            }
        }
        return new int[]{-1, -1};
    }

    static void printMatrix(int[][] m) {
        for (int[] row : m) System.out.println(Arrays.toString(row));
    }

    public static void main(String[] args) {
        int[] scores = {85, 92, 78, 95, 88};

        System.out.println("Tổng: "      + sum(scores));     // 438
        System.out.println("Trung bình: " + average(scores)); // 87.6
        System.out.println("Lớn nhất: "  + max(scores));     // 95

        Arrays.sort(scores);
        System.out.println("Đã sort: " + Arrays.toString(scores)); // [78, 85, 88, 92, 95]

        int[] nums = {2, 7, 11, 15};
        System.out.println("TwoSum(9): " + Arrays.toString(twoSum(nums, 9))); // [0, 1]

        int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
        printMatrix(grid);
    }
}
  1. Cast sum(arr) sang double trước khi chia — không cast thì phép chia giữa hai int sẽ cắt phần thập phân, trả về 87 thay vì 87.6.
  2. twoSum là bài LeetCode #1 — bài đầu tiên hầu hết mọi người làm. Hiểu array và vòng lặp lồng nhau là đủ để giải O(n²). Hash map cho O(n) — Phase 02 sẽ bàn.

10. Lỗi thường gặp

Lỗi 1 — ArrayIndexOutOfBoundsException

int[] arr = {1, 2, 3}; // length = 3, index hợp lệ: 0, 1, 2

System.out.println(arr[3]); // ❌ ArrayIndexOutOfBoundsException: index 3 out of bounds for length 3

System.out.println(arr[arr.length - 1]); // ✅ phần tử cuối luôn ở length - 1

Lỗi 2 — NullPointerException với mảng Object

String[] names = new String[3]; // [null, null, null]

System.out.println(names[0].length()); // ❌ NullPointerException

if (names[0] != null) {                // ✅ kiểm tra trước khi dùng
    System.out.println(names[0].length());
}

Lỗi 3 — So sánh mảng bằng ==

int[] a = {1, 2, 3};
int[] b = {1, 2, 3};

System.out.println(a == b);              // false — so sánh địa chỉ bộ nhớ
System.out.println(Arrays.equals(a, b)); // true ✅

int[][] x = {{1, 2}, {3, 4}};
int[][] y = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(x, y)); // true ✅ — bắt buộc dùng deepEquals cho 2D

Lỗi 4 — Gán = tưởng là copy

int[] original = {1, 2, 3};
int[] alias = original;                          // ❌ cùng reference
int[] copy   = Arrays.copyOf(original, 3);       // ✅ copy thật sự

alias[0] = 99;
System.out.println(original[0]); // 99 — bị ảnh hưởng!
copy[0]  = 77;
System.out.println(original[0]); // vẫn 99 — an toàn ✅

Lỗi 5 — In mảng trực tiếp

int[] arr = {1, 2, 3};
System.out.println(arr);                   // ❌ [I@6d06d69c — địa chỉ bộ nhớ
System.out.println(Arrays.toString(arr));  // ✅ [1, 2, 3]

int[][] m = {{1, 2}, {3, 4}};
System.out.println(Arrays.toString(m));     // ❌ [[I@..., [I@...]
System.out.println(Arrays.deepToString(m)); // ✅ [[1, 2], [3, 4]]

11. Câu hỏi phỏng vấn

Q1: Sự khác nhau giữa ArrayArrayList?

Array kích thước cố định, chứa được primitive, hiệu năng cao hơn. ArrayList kích thước động, chỉ chứa Object (autoboxing với primitive), API phong phú hơn. Chọn array khi kích thước biết trước và không cần thêm/xóa; chọn ArrayList khi cần linh hoạt.

Q2: Làm sao copy mảng đúng cách? Shallow copy là gì?

Dùng Arrays.copyOf(), Arrays.copyOfRange(), hoặc System.arraycopy(). Gán b = a không phải copy — chỉ tạo thêm reference trỏ vào cùng mảng. Lưu ý: đây đều là shallow copy — nếu mảng chứa Object, chỉ reference được copy, object bên trong không được duplicate.

Q3: Giá trị mặc định khi tạo mảng bằng new là gì?

Numeric → 0/0.0. booleanfalse. char''. Object → null. Java luôn khởi tạo phần tử mảng khi cấp phát, không bao giờ có "garbage value" như C/C++.

Q4: Tại sao không dùng Arrays.equals() cho mảng 2D?

Arrays.equals() chỉ so sánh một lớp — nó so sánh các phần tử của mảng ngoài, mà mỗi phần tử là một int[] (reference). Hai reference khác nhau dù trỏ đến mảng cùng nội dung vẫn trả về false. Phải dùng Arrays.deepEquals() để so sánh đệ quy.

Q5: Arrays.binarySearch() yêu cầu điều kiện gì?

Mảng phải được sắp xếp tăng dần trước khi gọi. Nếu chưa sort, kết quả trả về là undefined. Thông thường kết hợp Arrays.sort() trước rồi mới binarySearch().


12. Tài liệu tham khảo

Tài liệu Nội dung
JLS §10 — Arrays Đặc tả chính thức về mảng trong Java
java.util.Arrays Javadoc API reference đầy đủ
Oracle Tutorial — Arrays Hướng dẫn chính thức
Effective Java — Joshua Bloch Item 28: Prefer lists to arrays (lý do tại sao generics và array không hợp nhau)

Bình luận