CFSDN nhấn mạnh vào giá trị tạo ra nguồn mở và chúng tôi cam kết xây dựng nền tảng chia sẻ tài nguyên để mọi nhân viên CNTT có thể tìm thấy thế giới tuyệt vời của bạn tại đây.
Bài viết blog CFSDN tóm tắt cách sử dụng Java ArrayList này được tác giả sưu tầm và biên soạn. Nếu các bạn quan tâm đến bài viết này thì nhớ like nhé.
Khi nhắc đến ArrayList, tôi tin rằng nhiều bạn bè đã sử dụng và thường xuyên sử dụng. Nhưng cách đây vài năm, trong một cuộc phỏng vấn, người phỏng vấn đã yêu cầu tôi giải thích cơ chế mở rộng của ArrayList. Rõ ràng lúc đó tôi không để ý đến những điều này nên đã bỏ lỡ cơ hội. Nhưng may mắn thay, tôi vẫn thích gây rắc rối, nên bài viết hôm nay có thể coi là lấp hố. Sau khi đọc bài viết này bạn sẽ biết:
- Triển khai cơ bản ArrayList
- Tại sao ArrayList cho phép giá trị null?
- Tại sao ArrayList có thể lặp lại
- So sánh hiệu quả truy vấn ArrayList và hiệu quả chèn
Sơ đồ lớp
Hình dưới đây là cấu trúc sơ đồ lớp của ArrayList.

ArrayList kế thừa từ Tóm tắt và triển khai các giao diện List, RandomAccess, Cloneable, java.io.Serializable. Hãy phân tích ý nghĩa của các giao diện ở đây từng cái một:
- RandomAccess là giao diện cờ, biểu thị rằng bộ sưu tập Danh sách triển khai giao diện này hỗ trợ truy cập ngẫu nhiên nhanh. Nếu quan tâm, bạn có thể xem phương thức nào trong lớp Bộ sưu tập sử dụng giao diện mang tính biểu tượng này.
- Triển khai giao diện Cloneable và ghi đè phương thức clone() để có thể nhân bản nó.
- Giao diện java.io.Serializable được triển khai, điều đó có nghĩa là ArrayList hỗ trợ tuần tự hóa và có thể được truyền qua tuần tự hóa (xin lưu ý rằng việc tuần tự hóa ArrayList hơi đặc biệt, điều này sẽ được giải thích sau).
Phân tích mã nguồn
Trước khi chính thức bước vào phân tích mã nguồn của các biến thành viên, trước tiên chúng ta cần xem xét các biến thành viên của nó. Dưới đây là các biến thành viên quan trọng hơn:
?
1
2
3
|
riêng tư
số nguyên
kích cỡ;
tạm thời
Đối tượng[] elementData;
riêng tư
tĩnh
cuối cùng
số nguyên
DUNG LƯỢNG MẶC ĐỊNH =
10
;
|
Chúng tôi có ba phương pháp khởi tạo cho phương pháp xây dựng: khởi tạo trực tiếp không có tham số, khởi tạo kích thước được chỉ định và khởi tạo dữ liệu ban đầu được chỉ định như sau.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
công cộng
Mảng List() {
cái này
.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
công cộng
ArrayList(Bộ sưu tập
mở rộng
E>c){
elementData=c.toArray();
nếu như
((kích thước=elementData.length)!=
0
){
nếu như
(elementData.getClass()!=Đối tượng[].
lớp học
){
elementData=Arrays.copyOf(elementData,kích thước,Đối tượng].
lớp học
);
}
}
khác
{
cái này
.elementData=DỮ LIỆU_PHẦN_TRỐNG
}
}
công cộng
MảngDanh sách(
số nguyên
Dung lượng ban đầu) {
nếu như
(khả năng ban đầu >
0
) {
cái này
.elementData =
mới
Đối tượng[initialCapacity];
}
khác
nếu như
(khả năng ban đầu ==
0
) {
cái này
.elementData = DỮ LIỆU_PHẦN_TỐT_TRỐNG;
}
}
|
Ngoài các nhận xét trong mã nguồn, có một số điểm bổ sung:
- Khi hàm tạo không tham số ArrayList được khởi tạo, kích thước mặc định là một mảng trống chứ không phải 10 như mọi người thường nói. 10 là giá trị mảng được mở rộng khi lần thêm đầu tiên được thực hiện.
- Khi sử dụng cách 2 để tạo đối tượng, nếu đối tượng được lưu trong vùng chứa tham số đầu vào không phải là Object thì sẽ được chuyển đổi thành Object.
DEFAULTCAPACITY_EMPTY_ELEMENTDATA và EMPTY_ELEMENTDATA là cái quái gì vậy? Nó thực sự là hai mảng trống được xác định trong các biến thành viên, .
?
1
2
|
riêng tư
tĩnh
cuối cùng
Đối tượng[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
riêng tư
tĩnh
cuối cùng
Đối tượng[] EMPTY_ELEMENTDATA = {};
|
Rõ ràng câu hỏi được đặt ra là vì cả hai đều là mảng trống, tại sao lại khai báo hai mảng? Không phải một? Độc giả hãy suy nghĩ trước và đọc phần dưới đây với câu hỏi.
Thực hiện các bổ sung và mở rộng mới
Thông qua phương pháp xây dựng, chúng ta có thể thấy rõ rằng ArrayList thực sự dựa trên mảng, nhưng động bắt đầu từ đâu? Khi thêm, bạn thêm các phần tử vào mảng, chủ yếu được chia thành hai bước:
- Xác định xem có cần mở rộng hay không và nếu có, hãy thực hiện các thao tác mở rộng;
- Phân công trực tiếp.
Mã nguồn tương ứng như sau:
?
1
2
3
4
5
6
|
công cộng
Boolean
thêm(E e) {
đảm bảoCapacityInternal(kích thước +
1
);
elementData[kích thước++] = e;
trở lại
ĐÚNG VẬY
;
}
|
Trước tiên chúng ta hãy xem mã nguồn của phần mở rộng:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
riêng tư
vô hiệu
đảm bảoCapacityInternal(
số nguyên
Dung lượng tối thiểu) {
đảm bảoExplicitCapacity(tính toánCapacity(elementData, minCapacity));
}
riêng tư
tĩnh
số nguyên
tính toánCapacity(Object[] elementData,
số nguyên
Dung lượng tối thiểu) {
nếu như
(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA trống) {
trở lại
Math.max(DUNG LƯỢNG MẶC ĐỊNH, dung lượng tối thiểu);
}
trở lại
dung lượng tối thiểu;
}
riêng tư
vô hiệu
đảm bảoExplicitCapacity(
số nguyên
Dung lượng tối thiểu) {
modĐếm++;
nếu như
(minCapacity - elementData.length >
0
)
tăng trưởng(minCapacity);
}
riêng tư
vô hiệu
phát triển(
số nguyên
Dung lượng tối thiểu){
số nguyên
oldCapacity = elementData.length;
số nguyên
newCapacity=oldCapacity+(oldCapacity>>
1
);
nếu như
(newCapacity-minCapacity<
0
)
newCapacity = minCapacity;
nếu như
(newCapacity-MAX_ARRAY_SIZE>
0
)
elementData=Arrays.copyOf(elementData,newCapacity);
}
|
Các nhận xét tương đối chi tiết nhưng bạn cần chú ý một số điểm sau:
- Một câu hỏi ở trên là tại sao cần phải khai báo hai mảng trống. Khi chúng ta thấy mã nguồn ở trên, có một phương thức gọi là CalculateCapacity. Logic bên trong của phương thức này sẽ chỉ thay đổi minCapacity được trả về khi ArrayList được khởi tạo thông qua cấu trúc không có tham số. Giá trị trả về sẽ xác định xem mảng sau có cần được mở rộng hay không. Nếu chúng ta khởi tạo ArrayList bằng cách chỉ định kích thước và chỉ định kích thước là 0, điều này có nghĩa là thứ chúng ta cần là một ArrayList trống và không cần phải mở rộng nó.
- Khi thêm, giá trị không được xác minh nên giá trị mới có thể là null và không có phán đoán giá trị trùng lặp nên phần tử có thể được lặp lại;
- Giá trị tối đa của mảng trong ArrayList là Integer.MAX_VALUE. Nếu vượt quá giá trị này, JVM sẽ không phân bổ không gian bộ nhớ cho mảng;
- Phần mở rộng là dung lượng ban đầu + một nửa dung lượng. Nói một cách đơn giản, kích thước mở rộng gấp 1,5 lần dung lượng ban đầu.
Sau khi quá trình mở rộng hoàn tất, đây là một nhiệm vụ đơn giản. Không có khóa trong quá trình gán, vì vậy nó không an toàn.
Bản chất của việc mở rộng
Khi kết thúc phương thức phát triển, việc mở rộng đạt được thông qua dòng mã Arrays.copyOf(elementData, newCapacity);. Phương thức thực sự được gọi bởi phương thức này là System.arraycopy mà chúng ta thường sử dụng:
?
1
2
3
4
5
6
7
8
9
|
/**
*@param src mảng cần được sao chép
*@param srcPos bắt đầu từ mảng
*@param mảng mục tiêu đích
*@param DestPos bắt đầu sao chép từ vị trí chỉ mục của mảng mục tiêu
*@param chiều dài của bản sao
*Phương thức này không có giá trị trả về và giá trị được truyền qua tham chiếu của dest.
*/
công cộng
tĩnh
tự nhiên
vô hiệu
arraycopy(Đối tượng src,
số nguyên
srcPos, Đối tượng đích,
số nguyên
định mệnh,
số nguyên
chiều dài);
|
Phương thức này là một phương thức gốc. Mặc dù bạn không thể thấy cách triển khai cụ thể bên trong phương thức, nhưng bạn có thể xem qua nó thông qua các tham số. Phương pháp này di chuyển phần tử. Do đó, nếu bạn muốn mở rộng mảng, bạn cần phân bổ lại một không gian lớn hơn rồi sao chép tất cả dữ liệu với độ phức tạp về thời gian là O(N); và nếu bạn muốn chèn và xóa ở giữa mảng thì phải. di chuyển tất cả những dữ liệu tiếp theo mỗi lần. Dữ liệu được giữ liên tục và độ phức tạp về thời gian là O(N). Vì mảng là một không gian bộ nhớ liên tục nên các phần tử có thể được truy cập nhanh chóng dựa trên chỉ mục. Phần trên cũng giải thích vấn đề ngay từ đầu: tại sao ArrayList chèn chậm và truy vấn nhanh.
xóa bỏ
ArrayList có nhiều phương pháp xóa, được giải thích ở đây bằng cách xóa dựa trên giá trị (các nguyên tắc khác tương tự):
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
công cộng
Boolean
xóa(Đối tượng o) {
nếu như
(o==
vô giá trị
){
vì
(
số nguyên
chỉ số=
0
;index
nếu như
(phần tửDữ liệu[chỉ mục]==
vô giá trị
){
fastRemove(chỉ mục)
trở lại
ĐÚNG VẬY
;
}
}
khác
{
vì
(
số nguyên
chỉ số=
0
;index
nếu như
(o.equals(phần tửDữliệu[chỉmục]){
fastRemove(chỉ mục)
trở lại
ĐÚNG VẬY
;
}
}
trở lại
SAI
}
|
Cốt lõi thực sự là phương thức fastRemove:
?
1
2
3
4
5
6
7
8
9
10
11
12
|
riêng tư
vô hiệu
fastRemove(
số nguyên
chỉ số){
đếm++;
số nguyên
numMoved=kích thước-chỉ số-
1
;
nếu như
(số lượng đã di chuyển>
0
)
System.arraycopy(elementData, index+
1
, elementData, index, numMoved);
elementData[--kích thước] =
vô giá trị
;
}
|
Từ mã nguồn, chúng ta có thể thấy rằng sau khi xóa một phần tử, để duy trì cấu trúc mảng, chúng ta sẽ di chuyển các phần tử ở phía sau mảng về phía trước và giải phóng tham chiếu cuối cùng để thuận tiện cho việc tái chế.
Tóm tắt
Bài viết này chủ yếu bắt đầu từ mã nguồn của ArrayList và bắt đầu tìm hiểu từ bốn khía cạnh khởi tạo, bổ sung, mở rộng và xóa. Chúng tôi nhận thấy rằng ArrayList thực sự được bao quanh bởi một mảng. Khi dung lượng mảng không đủ, mảng đó sẽ được mở rộng lên dung lượng lớn hơn nên đương nhiên nó được gọi là mảng động. Tìm kiếm "Con đường đến với Chúa của Java" trên WeChat hoặc quét mã QR bên dưới để khám phá thêm kiến thức Java thú vị và cùng nhau phát triển! .
Trên đây là tóm tắt chi tiết cách sử dụng Java ArrayList. Để biết thêm thông tin về cách sử dụng Java ArrayList, mời các bạn theo dõi các bài viết liên quan khác của tôi nhé! .
Liên kết gốc: https://www.cnblogs.com/printfn/p/14590940.html.
Cuối cùng, bài viết tóm tắt cách sử dụng Java ArrayList này kết thúc tại đây. Nếu bạn muốn biết thêm về cách tóm tắt cách sử dụng Java ArrayList, vui lòng tìm kiếm các bài viết về CFSDN hoặc tiếp tục duyệt qua các bài viết liên quan. blog trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!