컬렉션
84. 반복자와 그것이 어떻게 사용되는지 알려주세요
컬렉션은 Java 개발자 인터뷰에서 가장 좋아하는 주제입니다. 컬렉션 계층 구조에 대한 질문에 답할 때 지원자는 컬렉션 인터페이스로 시작한다고 말하는 경우가 많습니다. 그러나 이것은 그렇지 않습니다. 한 수준 위에 또 다른 인터페이스가 있습니다: Iterable . 이 인터페이스는 현재 컬렉션의 Iterator 객체 에 액세스할 수 있는 iterator() 메서드로 구성됩니다. 그리고 이 Iterator 객체는 정확히 무엇입니까 ? Iterator 객체는 컬렉션을 통해 이동하고 해당 요소 를 반복하는 기능을 제공하며 사용자는 컬렉션의 특정 구현 세부 사항을 알 필요가 없습니다. 즉, 컬렉션의 요소 중 하나를 들여다보는 것처럼 컬렉션의 요소에 대한 일종의 포인터입니다. 반복자에는 다음과 같은 메서드가 있습니다.-
hasNext() — 반복에 다른 요소가 있으면 true를 반환합니다. 이 메서드를 사용하면 컬렉션의 끝에 도달했음을 알 수 있습니다.
-
next() — 반복의 다음 항목을 반환합니다. 없는 경우 NoSuchElementException 이 발생합니다. 즉, 이 메서드를 사용하기 전에 hasNext() 메서드를 사용하여 다음 요소가 있는지 확인하는 것이 가장 좋습니다 .
-
제거() — next() 메서드를 사용하여 받은 마지막 요소를 컬렉션에서 제거합니다 . next()가 호출된 적이 없는 경우 , 제거()를 호출하면 IllegalStateException 이 발생합니다 .
-
forEachRemaining(<Consumer>) — 컬렉션의 각 요소에 대해 전달된 작업을 실행합니다(이 메서드는 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());
콘솔에 다음이 표시됩니다.
iterator.forEachRemaining(x -> System.out.print(x));
그러나 일단 이렇게 하면 반복자는 더 이상 사용하기에 적합하지 않게 됩니다. 전체 목록을 순회했으며 일반 반복자에는 뒤로 반복할 수 있는 방법이 없습니다. 그리고 이는 LinkedList , 특히 향상된 유형의 반복자인 ListIterator 를 반환하는 listIterator() 메서드 에 대한 논의로 이어지는 멋진 부분입니다 . 일반(표준) 반복자의 메서드 외에도 이 종류에는 다음이 있습니다.
-
add(<Element>) — 목록에 새 요소를 추가합니다.
-
hasPrevious() — 다음 요소 앞에 요소가 있는 경우(이전 요소가 있는 경우) true를 반환합니다.
-
nextIndex() — 다음 요소의 인덱스를 반환합니다.
-
이전() — 이전 요소(다음 요소 이전의 요소)를 반환합니다.
-
PreviousIndex는 이전 요소의 인덱스를 반환합니다.
-
set(<Element>) — next() 또는 이전() 이 반환한 마지막 요소를 대체합니다 .
85. Java Collection Framework에는 어떤 컬렉션 계층이 존재합니까?
Java에는 두 가지 컬렉션 계층이 있습니다. 첫 번째 계층은 다음과 같은 구조를 갖는 컬렉션 계층입니다. 이는 다음과 같은 하위 컬렉션으로 나뉩니다.-
Set은 순서가 지정되지 않은 고유한(반복되지 않는) 요소를 포함하는 데이터 구조인 집합을 설명하는 인터페이스입니다. 이 인터페이스에는 TreeSet , HashSet 및 LinkedHashSet 과 같은 몇 가지 표준 구현이 있습니다 .
-
List 는 순서가 지정된 개체 시퀀스를 저장하는 데이터 구조를 설명하는 인터페이스입니다. 목록 의 개체는 목록의 인덱스에 따라 삽입 및 제거될 수 있습니다(배열과 유사하지만 동적 크기 조정이 가능함). 이 인터페이스에는 ArrayList , Vector ( 더 이상 사용되지 않으며 실제로 사용되지 않음 ) 및 LinkedList 와 같은 일부 표준 구현도 있습니다 .
-
큐는 FIFO(선입선출) 큐 에 항목을 저장하는 데이터 구조를 설명하는 인터페이스입니다 . 이 인터페이스에는 LinkedList (맞습니다. Queue 도 구현함 ) 및 PriotityQueue 라는 표준 구현이 있습니다 .
86. ArrayList의 내부 구조는 무엇입니까?
ArrayList 는 배열과 비슷하지만 동적으로 확장할 수 있습니다. 그게 무슨 뜻이에요? 내부적으로 ArrayList는 일반 배열을 사용합니다. 즉, 기본 크기가 10개 셀인 내부 배열에 해당 요소를 저장합니다. 내부 배열이 가득 차면 새 배열이 생성됩니다. 새 배열의 크기는 다음 공식으로 결정됩니다.<size of the current array> * 3 / 2 + 1
따라서 배열의 크기가 10이면 새 배열의 크기는 10 * 3 / 2 + 1 = 16이 됩니다. 그러면 원래(이전) 배열의 모든 값이 내장된 배열을 사용하여 복사됩니다. System.arraycopy() 메서드를 사용하고 원래 배열은 삭제됩니다. 간단히 말해서 이것이 ArrayList가 동적 크기 조정을 구현하는 방식입니다. 가장 널리 사용되는 ArrayList 메서드를 고려해 보겠습니다 . 1. add(<Element>) — 먼저 배열에 사용 가능한 셀이 있는지 확인한 후 배열 끝(마지막 빈 셀)에 요소를 추가합니다. 그렇지 않은 경우 새 배열이 생성되고 해당 배열에 요소가 복사됩니다. 이 연산의 시간 복잡도는 O(1)입니다. 비슷한 add(<Index>, <Elelement>) 메서드가 있습니다 . 목록(배열)의 끝부분이 아닌 인자로 들어온 인덱스가 가리키는 특정 셀에 요소를 추가합니다. 이 경우 시간 복잡도는 추가하는 위치에 따라 달라집니다.
- 추가가 목록의 시작 부분에 가까우면 시간 복잡도는 O(N)에 가까울 것입니다. 왜냐하면 새 요소의 오른쪽에 있는 모든 요소가 한 셀 오른쪽으로 이동해야 하기 때문입니다.
- 요소가 중간에 삽입되면 목록 항목의 절반만 오른쪽으로 한 셀 이동하면 되므로 O(N/2)가 됩니다.
87. LinkedList의 내부 구조는 무엇입니까?
ArrayList 에는 내부 배열의 요소가 포함되어 있지만 LinkedList는 해당 요소 를 이중 연결 목록에 저장합니다. 즉, 각 요소에는 이전 요소와 다음 요소에 대한 링크가 포함되어 있습니다. 첫 번째 요소는 이전 요소에 연결되지 않습니다(결국 첫 번째 요소임). 이는 또한 목록의 헤드로 간주되며 LinkedList 개체는 이를 직접 참조합니다. 마찬가지로 마지막 요소는 목록의 꼬리이므로 다음 요소를 갖지 않습니다. LinkedList 개체 도 이를 직접 참조합니다. 이는 목록의 헤드 또는 테일에 액세스하는 시간 복잡도가 O(1)임을 의미합니다. ArrayList 에서 목록이 커지면 내부 배열도 커집니다. 여기에서는 모든 것이 더 쉽습니다. 참조를 추가하는 것은 링크 몇 개를 변경하는 것만큼 간단합니다. 가장 많이 사용되는 LinkedList 메소드 중 일부를 살펴보겠습니다 . 1. add(<Element>) — 목록 끝에 요소를 추가합니다. 즉, 마지막 요소(5) 뒤에 새 요소에 대한 링크가 다음으로 추가됩니다. . 새 요소의 이전 참조는 이제 목록에서 앞에 있는 요소(5)를 가리킵니다. 이 작업의 시간 복잡도는 O(1)입니다. 왜냐하면 마지막 요소에 대한 링크만 필요하고, 기억하시겠지만 LinkedList 개체는 꼬리에 대한 직접 참조를 가지며 이에 액세스하는 데 최소한의 일정한 시간 복잡도가 있습니다. 2. add(<Index>, <Element>) — 인덱스별로 요소를 추가합니다. 예를 들어 목록 중간에 요소를 추가할 때 이 메서드는 먼저 원하는 위치를 찾을 때까지 머리와 꼬리(양방향)에서 요소를 반복합니다. 위 그림에서 세 번째 요소와 네 번째 요소 사이에 요소를 추가하면 세 번째 요소의 다음 링크가 새 요소를 가리킵니다. 그리고 새로 추가된 요소 중 이전 요소는 세 번째 요소를 가리킵니다. 결과적으로 이전 네 번째 요소의 이전 링크는 이제 새 요소를 가리키고 새 요소의 다음 링크는 새로운 네 번째 요소를 가리킵니다. 이 메서드의 시간 복잡도는 새 요소의 인덱스에 따라 달라집니다.- 머리나 꼬리에 가까우면 실제로 요소를 반복할 필요가 없기 때문에 작업은 O(1)에 접근합니다.
- 중간에 가까우면 O(N/2)가 됩니다. 왜냐하면 이 메서드는 원하는 요소를 찾을 때까지 머리와 꼬리에서 동시에 검색하기 때문입니다.
88. HashMap의 내부 구조는 무엇입니까?
이는 Java 개발자 지원자에게 묻는 가장 인기 있는 인터뷰 질문 중 하나일 수 있습니다. HashMap 은 키-값 쌍 으로 작동합니다 . HashMap 자체 에는 어떻게 저장되나요 ? HashMap 에는 내부 노드 배열이 있습니다.Node<K,V>[] table
기본적으로 배열의 크기는 16이고 요소로 채워질 때마다 두 배로 늘어납니다(즉, LOAD_FACTOR에 도달 하면 배열이 얼마나 가득 찰 수 있는지에 대한 임계값을 지정합니다. 기본적으로 0.75 입니다 ). . 각 노드는 키의 해시, 키, 값 및 다음 요소에 대한 참조를 저장합니다. 이 경우 "다음 요소에 대한 참조"는 단일 연결 목록을 처리한다는 의미입니다. 각 요소에는 다음 요소에 대한 링크가 포함되어 있습니다. 즉, HashMap은 단일 연결 목록 배열에 데이터를 저장합니다. 그러나 테이블 배열의 셀에 둘 이상의 요소로 구성된 단일 연결 목록이 있는 경우에는 좋지 않다는 점을 바로 말씀드리겠습니다 . 이 현상을 충돌 이라고 합니다 . 하지만 가장 먼저 해야 할 일이 있습니다. put 메소드를 사용하여 새 쌍이 어떻게 저장되는지 살펴보겠습니다 . 먼저 이 메서드는 키의 hashCode() 를 가져옵니다 . 이는 HashMap 이 올바르게 작동하려면 사용하는 키가 이 메서드를 재정의하는 클래스여야 함을 의미합니다. 그런 다음 이 해시 코드는 내부 hash() 메서드에서 사용되어 테이블 배열 내 일부 셀의 인덱스를 결정합니다. 결과 인덱스를 사용하면 테이블 배열의 특정 셀에 액세스할 수 있습니다. 여기에는 두 가지 경우가 있습니다.
- 셀은 비어 있습니다. 이 경우 새 Node 값이 셀에 저장됩니다.
- 셀이 비어 있지 않습니다. 이 경우 키 값이 비교됩니다. 동일하면 새 Node 값이 이전 값을 덮어씁니다. 같지 않으면 다음 항목에 액세스 하고 해당 키를 비교합니다. 새 값이 이전 값을 덮어쓰거나 단일 연결 목록의 끝에 도달한 다음 새 값을 거기에 저장합니다. 마지막 요소.
GO TO FULL VERSION