
Bộ sưu tập
84. Hãy cho chúng tôi biết về các trình vòng lặp và cách chúng được sử dụng
Bộ sưu tập là chủ đề được yêu thích trong bất kỳ cuộc phỏng vấn nhà phát triển Java nào. Khi trả lời các câu hỏi về hệ thống phân cấp bộ sưu tập, thí sinh thường nói rằng nó bắt đầu bằng giao diện Bộ sưu tập . Nhưng nó không như vậy. Có một giao diện khác ở cấp độ trên: Iterable . Giao diện này bao gồm phương thức iterator() , cho phép bạn truy cập đối tượng Iterator cho bộ sưu tập hiện tại. Và chính xác thì đối tượng Iterator này là gì ? Đối tượng Iterator cung cấp khả năng di chuyển qua một bộ sưu tập và lặp lại các phần tử của nó và người dùng không cần biết chi tiết triển khai cụ thể của bộ sưu tập. Nói cách khác, nó là một loại con trỏ tới các phần tử của bộ sưu tập, như thể nó đang nhìn vào một trong số chúng. Trình vòng lặp có các phương thức như:-
hasNext() - trả về true nếu phép lặp có phần tử khác (phương thức này cho bạn biết khi nào bạn đã đến cuối bộ sưu tập);
-
next() - trả về mục tiếp theo trong lần lặp. Nếu không có thì ngoại lệ NoSuchElementException sẽ được ném ra. Điều đó có nghĩa là trước khi bạn sử dụng phương pháp này, tốt nhất bạn nên sử dụng phương thức hasNext() để đảm bảo phần tử tiếp theo tồn tại;
-
Remove() - xóa khỏi bộ sưu tập phần tử cuối cùng nhận được bằng phương thức next() . Nếu next() chưa bao giờ được gọi thì việc gọi Remove() sẽ khiến IllegalStateException bị ném ra;
-
forEachRemaining(<Consumer>) - thực thi hành động được truyền trên từng thành phần của bộ sưu tập (phương thức này đã xuất hiện trong Java 8).
List<String> list = new ArrayList<>();
list.add("Hello ");
list.add("World, ");
list.add("It's ");
list.add("Amigo!");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(list.size());
Bảng điều khiển sẽ hiển thị như sau:
iterator.forEachRemaining(x -> System.out.print(x));
Nhưng một khi chúng ta làm điều này, trình vòng lặp sẽ trở nên không phù hợp để sử dụng tiếp: nó đã duyệt qua toàn bộ danh sách và một trình vòng lặp thông thường không có phương pháp lặp ngược lại. Và điều đó tạo nên một sự chuyển tiếp thú vị trong cuộc thảo luận về LinkedList , cụ thể là phương thức listIterator() của nó , trả về một kiểu lặp nâng cao: ListIterator . Ngoài các phương thức của một trình vòng lặp (tiêu chuẩn) thông thường, loại này còn có các phương thức sau:
-
add(<Element>) - thêm một phần tử mới vào danh sách;
-
hasPrevious() - trả về true nếu có một phần tử nằm trước phần tử tiếp theo (nếu có phần tử trước đó);
-
nextIndex() - trả về chỉ mục của phần tử tiếp theo;
-
trước() - trả về phần tử trước đó (phần tử trước phần tử tiếp theo);
-
previousIndex trả về chỉ mục của phần tử trước đó.
-
set(<Element>) - thay thế phần tử cuối cùng được trả về bởi next() hoặc previous() .

85. Hệ thống phân cấp bộ sưu tập nào tồn tại trong Khung công tác sưu tập Java?
Có hai hệ thống phân cấp bộ sưu tập trong Java. Hệ thống phân cấp đầu tiên là hệ thống phân cấp Bộ sưu tập, có cấu trúc sau:
-
Set là một giao diện mô tả một tập hợp, một cấu trúc dữ liệu chứa các phần tử duy nhất (không lặp lại) không có thứ tự. Giao diện này có một số triển khai tiêu chuẩn: TreeSet , HashSet và LinkedHashSet .
-
Danh sách là một giao diện mô tả cấu trúc dữ liệu lưu trữ một chuỗi các đối tượng được sắp xếp. Các đối tượng trong Danh sách có thể được chèn và xóa theo chỉ mục của chúng trong danh sách (giống như một mảng, nhưng có khả năng thay đổi kích thước động). Giao diện này cũng có một số triển khai tiêu chuẩn: ArrayList , Vector ( không được dùng nữa và không thực sự được sử dụng ) và LinkedList .
-
Hàng đợi là giao diện mô tả cấu trúc dữ liệu lưu trữ các mục trong hàng đợi Vào trước ra trước (FIFO) . Giao diện này có các cách triển khai tiêu chuẩn sau: LinkedList (đúng vậy, nó cũng triển khai Queue ) và PritityQueue .


86. Cấu trúc bên trong của ArrayList là gì?
ArrayList giống như một mảng, nhưng nó có thể mở rộng một cách linh hoạt . Điều đó nghĩa là gì? Về cơ bản, ArrayList sử dụng một mảng thông thường, tức là nó lưu trữ các phần tử của nó trong một mảng bên trong có kích thước mặc định là 10 ô. Khi mảng bên trong đã đầy, một mảng mới sẽ được tạo. Kích thước của mảng mới được xác định theo công thức sau:<size of the current array> * 3 / 2 + 1
Vì vậy, nếu kích thước của mảng của chúng ta là 10, thì kích thước của mảng mới sẽ là: 10 * 3/2 + 1 = 16. Sau đó, tất cả các giá trị từ mảng ban đầu (cũ) sẽ được sao chép vào mảng đó bằng cách sử dụng tính năng tích hợp sẵn Phương thức System.arraycopy() và mảng ban đầu sẽ bị xóa. Tóm lại, đó là cách ArrayList thực hiện thay đổi kích thước động. Hãy xem xét các phương thức ArrayList phổ biến nhất : 1. add(<Element>) — thêm một phần tử vào cuối mảng (trong ô trống cuối cùng), sau lần đầu tiên kiểm tra xem có ô nào có sẵn trong mảng hay không. Nếu không, một mảng mới sẽ được tạo và các phần tử sẽ được sao chép vào mảng đó. Độ phức tạp về thời gian của thao tác này là O(1). Có một phương thức add(<Index>, <Element>) tương tự . Nó thêm một phần tử không phải vào cuối danh sách (mảng), mà vào ô cụ thể được biểu thị bằng chỉ mục được đưa vào dưới dạng đối số. Trong trường hợp này, độ phức tạp về thời gian sẽ khác nhau tùy thuộc vào vị trí bạn thêm:
- nếu phần bổ sung ở gần đầu danh sách thì độ phức tạp về thời gian sẽ gần bằng O(N), bởi vì tất cả các phần tử nằm ở bên phải của phần tử mới sẽ phải được di chuyển sang bên phải một ô;
- nếu phần tử được chèn vào giữa thì nó sẽ là O(N/2), vì chúng ta chỉ cần dịch chuyển một nửa số mục trong danh sách sang phải một ô.
87. Cấu trúc bên trong của LinkedList là gì?
Một ArrayList chứa các phần tử trong mảng bên trong, nhưng LinkedList lưu trữ chúng trong danh sách liên kết đôi. Điều này có nghĩa là mỗi phần tử chứa một liên kết đến phần tử trước và phần tử tiếp theo . Phần tử đầu tiên không liên kết với phần tử trước đó (xét cho cùng, nó là phần tử đầu tiên). Nó cũng được coi là phần đầu của danh sách và đối tượng LinkedList có tham chiếu trực tiếp đến nó. Tương tự, phần tử cuối cùng không có phần tử tiếp theo vì nó là phần tử cuối của danh sách. Đối tượng LinkedList cũng tham chiếu trực tiếp đến nó. Điều này có nghĩa là độ phức tạp về thời gian của việc truy cập phần đầu hoặc phần cuối của danh sách là O(1).

- nếu nó ở gần đầu hoặc đuôi, thao tác sẽ tiến tới O(1), vì thực tế không cần thiết phải lặp lại các phần tử;
- nếu nó ở gần giữa thì chúng ta sẽ có O(N/2), vì phương thức sẽ tìm kiếm từ đầu và đuôi cùng lúc cho đến khi tìm thấy phần tử mong muốn.

88. Cấu trúc bên trong của HashMap là gì?
Đây có thể là một trong những câu hỏi phỏng vấn phổ biến nhất dành cho các ứng viên phát triển Java. HashMap hoạt động với các cặp khóa-giá trị . Chúng được lưu trữ bên trong HashMap như thế nào? HashMap có một mảng nút bên trong:Node<K,V>[] table
Theo mặc định, kích thước của mảng là 16 và nó tăng gấp đôi mỗi khi nó chứa đầy các phần tử (nghĩa là khi đạt đến LOAD_FACTOR ; nó chỉ định ngưỡng cho mức độ đầy đủ của mảng - theo mặc định là 0,75 ) . Mỗi nút lưu trữ một hàm băm của khóa, khóa, giá trị và tham chiếu đến phần tử tiếp theo: 
- Ô trống - trong trường hợp này, giá trị Nút mới được lưu trữ trong đó.
- Ô không trống — trong trường hợp này, giá trị của các khóa được so sánh. Nếu chúng bằng nhau thì giá trị Nút mới sẽ ghi đè giá trị cũ; nếu không bằng thì giá trị tiếp theo sẽ được truy cập và khóa của nó được so sánh... Và cứ như vậy, cho đến khi giá trị mới ghi đè một số giá trị cũ hoặc chúng ta đến cuối danh sách liên kết đơn và sau đó lưu giá trị mới ở đó dưới dạng phần tử cuối cùng.

GO TO FULL VERSION