HashMap 깊이 해석: HashMap 에 대해 철저히 알 게 해 드 립 니 다.
HashMap 은 Map 족 에서 가장 많이 사용 되 는 것 이자 자바 Collection Framework 의 중요 한 구성원 입 니 다.본 고 는 먼저 HashMap 의 실질 을 제시 하고 Map, HashSet 과 의 관 계 를 개술 한 다음 에 HashMap 이 JDK 에서 의 정 의 를 내 렸 고 소스 코드 와 결합 하여 네 가지 구조 방식 을 분석 했다.마지막 으로 HashMap 의 데이터 구조, 실현 원리, 소스 코드 실현 세 가지 측면 에 대한 분석 을 통 해 그의 바 텀 Hash 저장 체제 에 깊이 들 어가 바 텀 배열 의 길이 가 항상 2 의 n 차방 의 원인 을 설명 하고 빠 른 액세스, 확대 와 확대 후의 중 하 쉬 의 원리 와 실현 을 밝 혔 다.
본 논문 의 모든 HashMap 에 관 한 소스 코드 는 모두 기초 이다. JDK 1.6 네, 서로 다른 JDK 버 전 간 에 약간의 차이 가 있 을 수 있 지만 저희 가 HashMap 의 데이터 구조, 원리 등 전체적인 파악 과 이해 에 영향 을 주지 않 습 니 다.
HashMap 개요
Map 은 Key - Value 가 맵 에 대한 추상 적 인 인터페이스 로 이 맵 은 중복 되 는 키, 즉 하나의 키 가 하나의 값 에 대응 하 는 것 을 포함 하지 않 습 니 다.HashMap 은 자바 Collection Framework 의 중요 한 구성원 이자 Map 족 (아래 그림 참조) 에서 우리 가 가장 자주 사용 하 는 것 이다.쉽게 말 하면 HashMap 은 해시 표 의 Map 인터페이스의 실현 을 바탕 으로 Key - Value 형식 으로 존재 한다. 즉, 저장 대상 은 Entry (Key 와 Value 를 동시에 포함) 이다.HashMap 에 서 는 hash 알고리즘 에 따라 key - value 의 저장 위 치 를 계산 하고 빠 른 접근 을 합 니 다.특히, HashMap 은 최대 한 개의 Entry 키 만 Null (여러 개 는 덮어 쓰기) 로 허용 하지만, 여러 개의 Entry 값 은 Null 로 허용 합 니 다.그 밖 에 HashMap 은 Map 의 비동기 적 인 실현 이다.
HashMap 과 HashSet 이 실현 하 는 인터페이스 규범 은 다 르 지만 그 밑 에 있 는 Hash 저장 메커니즘 은 완전히 같다.실제로 HashSet 자체 가 HashMap 을 바탕 으로 이 루어 진 것 이다.
HashMap 의 JDK 정의
HashMap 은 Map 인 터 페 이 스 를 실현 하고 AbstractMap 추상 류 를 계승 하 며 그 중에서 Map 인 터 페 이 스 는 키 맵 규칙 을 정의 합 니 다.AbstractCollection 추상 류 가 Collection 족 에서 의 역할 과 유사 하 다. AbstractMap 추상 류 는 Map 인터페이스의 핵심 실현 을 제공 하여 Map 인 터 페 이 스 를 실현 하 는 데 필요 한 작업 을 최대한 줄인다.HashMap 은 JDK 에서 다음 과 같이 정의 합 니 다.
public class HashMap extends AbstractMapimplements Map, Cloneable, Serializable {
...}
HashMap 의 구조 함수
HashMap 은 모두 네 개의 구조 함 수 를 제 공 했 는데 그 중에서 기본 적 인 구조 함수 와 매개 변 수 는 Map 의 구조 함수 가 자바 Collection Framework 규범 의 추천 실현 이 고 나머지 두 개의 구조 함 수 는 HashMap 이 전문 적 으로 제공 한 것 이다.
1、HashMap()
이 구조 함 수 는 > 기본 초기 용량 (16) 과 기본 부하 인자 (0.75) 를 가 진 빈 HashMap 을 구성 하 는 것 을 의미 합 니 다. 자바 Collection Framework 규범 이 추천 하 는 것 입 니 다. 그 소스 코드 는 다음 과 같 습 니 다.
/** * Constructs an empty HashMap with the default initial capacity * (16) and the default load factor (0.75). */
public HashMap() {
// : this.loadFactor = DEFAULT_LOAD_FACTOR;
//HashMap , HashMap threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
// HashMap , table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
2、HashMap(int initialCapacity, float loadFactor)
이 구조 함 수 는 초기 용량 과 부하 인 자 를 지정 하 는 빈 HashMap 을 구성 하 는 데 목적 을 둡 니 다. 그 소스 코드 는 다음 과 같 습 니 다.
/** * Constructs an empty HashMap with the specified initial capacity and load factor. */
public HashMap(int initialCapacity, float loadFactor) {
// 0 if (initialCapacity MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// HashMap 2 , initialCapacity 2^n
int capacity = 1;
while (capacity
3、HashMap(int initialCapacity)
(0.75) HashMap, :
// Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75) public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); // }
4、HashMap(Map extends K, ? extends V> m)
이 구조 함 수 는 지 정 된 맵 과 같은 맵 을 가 진 HashMap 을 구성 하 는 것 을 의미 합 니 다. 초기 용량 은 16 (지 정 된 맵 의 크기 에 구체 적 으로 의존) 보다 작 지 않 고 부하 인 자 는 0.75 이 며 자바 Collection Framework 규범 이 추천 하 는 것 입 니 다. 그 소스 코드 는 다음 과 같 습 니 다. // Constructs a new HashMap with the same mappings as the specified Map.
// The HashMap is created with default load factor (0.75) and an initial capacity // sufficient to hold the mappings in the specified Map. public HashMap(Map extends K, ? extends V> m) {
// 16
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
여기 서 우 리 는 두 가지 매우 중요 한 매개 변 수 를 언급 했다. 초기 용량 과 부하 인자, 이 두 가지 매개 변 수 는 HashMap 의 성능 에 영향 을 주 는 중요 한 매개 변수 이다.그 중에서 용량 은 해시 표 의 통 수량 (table 배열 의 크기) 을 나타 내 고 초기 용량 은 해시 표를 만 들 때 통 의 수량 입 니 다.부하 인 자 는 해시 표 가 용량 이 자동 으로 증가 하기 전에 얼마나 가득 채 울 수 있 는 척도 로 산 목록 의 공간의 사용 정 도 를 평가 하고 부하 인자 가 클 수록 산 목록 의 충전 정도 가 높 고 반대로 작 음 을 나타 낸다.
HashMap 의 데이터 구조
해시 의 관련 개념
Hash 는 임의의 길이 의 입력 (예 영사, pre - image 라 고도 함) 을 해시 알고리즘 을 통 해 고정된 길이 의 출력 (보통 정형) 으로 변환 하 는데 이 출력 은 해시 값 이다.이러한 전환 은 압축 맵 이다. 즉, 해시 값 의 공간 은 보통 입력 한 공간 보다 훨씬 작다.서로 다른 입력 은 같은 출력 으로 흩 어 질 수 있 으 며, 흩 어 진 값 에서 유일한 입력 값 을 정할 수 없습니다.쉽게 말 하면 임의의 길이 의 메 시 지 를 특정한 길이 의 이자 요약 함수 로 압축 하 는 것 이다.
해시 의 응용: 데이터 구조
우 리 는 배열 의 특징 은 주소 찾기 가 쉽 고 삽입 과 삭제 가 어렵 다 는 것 을 알 고 있다.링크 의 특징 은 주소 찾기 가 어렵 고 삽입 과 삭제 가 쉽다 는 것 이다.그렇다면 우 리 는 이들 의 특성 을 종합 하여 주소 찾기 가 쉽 고 삽입 과 삭제 도 쉬 운 데이터 구 조 를 만 들 수 있 을 까?답 은 긍정 적 이다. 이것 이 바로 우리 가 제기 하고 자 하 는 해시 표 이다.사실은 하 쉬 표 는 여러 가지 서로 다른 실현 방법 이 있 습 니 다. 우 리 는 다음 에 가장 전형 적 인 방법 인 지퍼 법 을 설명 합 니 다. 우 리 는 이 를 링크 의 배열 로 이해 할 수 있 습 니 다. 아래 그림 과 같 습 니 다.
우 리 는 위의 그림 에서 볼 수 있 듯 이 왼쪽 은 분명히 배열 이 고 배열 의 모든 구성원 은 하나의 링크 이다.이 데이터 구조 에 포 함 된 모든 요 소 는 하나의 지침 을 포함 하여 요소 간 의 링크 에 사 용 됩 니 다.우 리 는 요소 자체 의 특징 에 따라 요 소 를 서로 다른 링크 에 배분 한다. 반대로 우 리 는 바로 이런 특징 을 통 해 정확 한 링크 를 찾 은 다음 에 링크 에서 정확 한 요 소 를 찾 는 것 이다.그 중에서 요소 특징 에 따라 요소 배열 의 아래 표 시 를 계산 하 는 방법 은 바로 해시 알고리즘 이다.
전반적 으로 해시 표 는 빠 른 검색, 삭제 의 기본 데이터 구조 로 적합 하 며, 일반적으로 총 데 이 터 량 을 메모리 에 넣 을 수 있어 야 한다.해시 표를 사용 할 때 다음 과 같은 몇 가지 관건 이 있 습 니 다. hash 함수 (해시 알고리즘) 의 선택: 서로 다른 대상 (문자열, 정수 등) 에 대한 구체 적 인 해시 방법; 충돌 처리: 자주 사용 하 는 두 가지 방식 이 있 는데 하 나 는 open hashing, 즉 > 지퍼 법 이다. 다른 하 나 는 closed hashing, 즉 주소 법 (opened addressing) 이다.
HashMap 의 데이터 구조
우 리 는 자바 에서 가장 자주 사용 하 는 두 가지 구 조 는 배열 과 링크 로 거의 모든 데이터 구 조 는 이 두 가지 로 조합 하여 실현 할 수 있다 는 것 을 알 고 있다. HashMap 은 바로 이런 응용의 전형 이다.실제로 HashMap 은 하나의 링크 배열 로 다음 과 같은 데이터 구조 입 니 다.
위의 그림 에서 우 리 는 HashMap 의 밑바닥 이 실현 되 는 지 수조 가 되 는 지 형상 적 으로 볼 수 있다. 다만 수조 의 모든 항목 이 하나의 사슬 일 뿐이다.그 중에서 매개 변수 initialCapacity 는 이 배열 의 길이, 즉 통 의 개 수 를 대표 합 니 다.3 절 에서 우 리 는 HashMap 의 기본 구조 함수 의 소스 코드 를 알 게 되 었 다. /** * Constructs an empty HashMap with the default initial capacity * (16) and the default load factor (0.75). */
public HashMap() {
// : this.loadFactor = DEFAULT_LOAD_FACTOR;
//HashMap , HashMap threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
// HashMap , table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
상기 소스 코드 에서 알 수 있 듯 이 HashMap 을 새로 만 들 때마다 Entry 형식의 table 배열 을 초기 화 합 니 다. 그 중에서 Entry 유형의 정 의 는 다음 과 같 습 니 다.static class Entry implements Map.Entry {
final K key; // V value; // Entry next; // final int hash; // hash(key.hashCode()) /** * Creates new entry. */
Entry(int h, K k, V v, Entry n) { // Entry value = v;
next = n;
key = k;
hash = h;
}
......}
그 중에서 Entry 는 HashMap 의 내부 클래스 로 Map. Entry 인 터 페 이 스 를 실 현 했 는데 키 키 키, 값 value, 다음 노드 next, 그리고 hash 값 네 가지 속성 을 포함한다.사실 Entry 는 해시 표를 구성 하 는 초석 으로 해시 표 에 저 장 된 요소 의 구체 적 인 형식 이다.
HashMap 의 빠 른 액세스
다음은 JDK 소스 코드 와 결합 하여 HashMap 의 액세스 실현 을 살 펴 보 겠 습 니 다.
HashMap 의 저장 실현
HashMap 에서 키 값 이 맞 는 저장 소 는 put (key, vlue) 방법 으로 이 루어 집 니 다. 그 소스 코드 는 다음 과 같 습 니 다. /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with key, or null if there was no mapping for key. * Note that a null return can also indicate that the map previously associated null with key. */
public V put(K key, V value) {
// key null , putForNullKey , table
if (key == null)
return putForNullKey(value);
// key hashCode hash int hash = hash(key.hashCode()); // ------- (1) // ( ) int i = indexFor(hash, table.length); // ------- (2) // table i , key for (Entry e = table[i]; e != null; e = e.next) { // ------- (3) Object k;
// hash key , , value, value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; // }
}
modCount++; // 1, // HashMap , addEntry(hash, key, value, i);
return null;
}
NULL 키 에 대한 특별 처리: putForNullKey ()
우 리 는 직접 그 소스 코드 를 본다. /** * Offloaded version of put for null keys */
private V putForNullKey(V value) {
// key==null, table , table[0] for (Entry e = table[0]; e != null; e = e.next) {
if (e.key == null) { // key null , , V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++; // addEntry(0, null, value, 0); // , table[0] return null;
}
HashMap 의 해시 정책 (알고리즘)/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits.
*
* Note: Null keys always map to hash 0, thus index 0.
*/
static int hash( int h )
{
/*
* This function ensures that hashCodes that differ only by
* constant multiples at each bit position have a bounded
* number of collisions (approximately 8 at default load factor).
*/
h ^= (h >>> 20) ^ (h >>> 12);
return(h ^ (h >>> 7) ^ (h >>> 4) );
}
JDK 가 공식 적 으로 이 방법 에 대한 설명 처럼 hash () 방법 으로 한 대상 의 hashCode 를 재 계산 하 는 것 은 품질 이 낮은 hashCode () 함수 의 실현 을 방지 하기 위 한 것 이다.hashMap 의 지지 배열 길 이 는 항상 2 의 미터 이기 때문에 오른쪽 이동 을 통 해 낮은 비트 의 데 이 터 를 최대한 다 르 게 하여 hash 값 의 분 포 를 최대한 고 르 게 할 수 있 습 니 다.
상기 hash () 방법 을 통 해 Key 의 hash 값 을 계산 한 후에 어떻게 해 야 요소 가 table 의 모든 통 에 고 르 게 분포 되 는 것 을 확보 할 수 있 습 니까?우 리 는 모델 링 을 생각 할 것 입 니 다. 그러나 모델 링 의 효율 이 비교적 낮 기 때문에 HashMap 은 위의 index For () 방법 으로 처 리 했 습 니 다. 간단 할 뿐만 아니 라 효율 도 높 습 니 다. 소스 코드 는 다음 과 같 습 니 다./** * * Returns index for hash code h. * */static int indexFor( int h, int length ){
return(h & (length - 1) ); /* , */}
HashMap 의 키 쌍 추가: addEntry ()
우 리 는 직접 그 소스 코드 를 본다. /** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. *
* */
void addEntry(int hash, K key, V value, int bucketIndex) {
// bucketIndex Entry e = table[bucketIndex];
// Entry bucketIndex
table[bucketIndex] = new Entry(hash, key, value, e);
// HashMap threshold, if (size++ >= threshold)
resize(2 * table.length);
}
HashMap 의 확장: resize ()
HashMap 에서 요소 의 수량 이 점점 많아 지면 서 충돌 이 발생 할 확률 이 점점 커지 고 발생 하 는 서브 체인 의 길이 가 점점 길 어 집 니 다. 그러면 HashMap 의 액세스 속도 에 영향 을 줄 것 입 니 다.HashMap 의 효율 을 확보 하기 위해 서 시스템 은 특정한 임계 점 에서 확장 처 리 를 해 야 합 니 다. 이 임계 점 은 HashMap 에서 요소 의 수량 이 수치 적 으로 threshold (table 배열 길이 * 로드 인자) 와 같 습 니 다.그러나 확장 은 새로운 table 배열 에 있 는 위 치 를 다시 계산 하고 복사 처리 해 야 하기 때문에 시간 이 많이 걸 리 는 과정 이 라 고 할 수 밖 에 없다.따라서 만약 에 우리 가 HashMap 에서 원소 의 개 수 를 미리 예측 할 수 있다 면 HashMap 을 구성 할 때 원소 의 개 수 를 미리 설정 하면 HashMap 의 성능 을 효과적으로 향상 시 킬 수 있다.우 리 는 직접 그 소스 코드 를 본다./** * * Rehashes the contents of this map into a new array with a * * larger capacity. This method is called automatically when the * * number of keys in this map reaches its threshold. * * * * If current capacity is MAXIMUM_CAPACITY, this method does not * * resize the map, but sets threshold to Integer.MAX_VALUE. * * This has the effect of preventing future calls. * * * * @param newCapacity the new capacity, MUST be a power of two; * * must be greater than current capacity unless current * * capacity is MAXIMUM_CAPACITY (in which case value * * is irrelevant). * */void resize( int newCapacity ){
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
/* oldCapacity , threshold Integer.MAX_VALUE */
if ( oldCapacity == MAXIMUM_CAPACITY )
{
threshold = Integer.MAX_VALUE;
return; /* */
}
/* , */
Entry[] newTable = new Entry[newCapacity];
/* Entry */
transfer( newTable );
table = newTable;
threshold = (int) (newCapacity * loadFactor); /* threshold */}
HashMap 의 중 해시: transfer ()
하 쉬 를 다시 계산 하 는 것 은 원래 하 쉬 맵 의 요소 가 새 table 배열 에 있 는 위 치 를 다시 계산 하고 복사 처리 하 는 과정 입 니 다. 우 리 는 그 소스 코드 를 직접 봅 니 다./** * * Transfers all entries from current table to newTable. * */void transfer( Entry[] newTable ){/* table src */
Entry[] src = table;
int newCapacity = newTable.length;/* src newTable */
for ( int j = 0; j e = src[j];
if ( e != null )
{
src[j] = null; /* src *//* newTable */
do
{
Entry next = e.next;/* e.hash hash(key.hashCode()) ; *//* newTable , */
int i = indexFor( e.hash, newCapacity );
e.next = newTable[i];
newTable[i] = e;
e = next;
}
while ( e != null );
}
}}
특히 주의해 야 할 것 은 중 하 쉬 과정 에서 원래 한 통 에 속 했 던 Entry 대상 이 다른 통 으로 나 눌 수 있다 는 점 이다. HashMap 의 용량 이 변 했 기 때문에 h & (length - 1) 의 값 도 상응 한 변화 가 생 길 수 있다.극단 적 으로 무 거 운 해시 이후 에 도 한 통 에 속 했 던 엔 트 리 대상 이 같은 통 에 속한다 면 무 거 운 해시 도 의 미 를 잃 게 된다.
HashMap 읽 기 실현
HashMap 의 저장 소 에 비해 읽 기 가 간단 합 니 다.HashMap 은 key 의 hash 값 을 통 해 table 배열 의 특정한 통 을 찾 은 다음 에 이 key 에 대응 하 는 value 를 찾 아 되 돌려 주면 되 기 때 문 입 니 다. 소스 코드 는 다음 과 같 습 니 다./** * * Returns the value to which the specified key is mapped, * * or {@code null} if this map contains no mapping for the key. * * More formally, if this map contains a mapping from a key * * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * * key.equals(k))}, then this method returns {@code v}; otherwise * * it returns {@code null}. (There can be at most one such mapping.) *
A return value of {@code null} does not necessarily * * indicate that the map contains no mapping for the key; it's also * * possible that the map explicitly maps the key to {@code null}. * * The {@link #containsKey containsKey} operation may be used to * * distinguish these two cases. * @see #put(Object, Object) * */public V get( Object key ){/* null, getForNullKey value */
if ( key == null )/* table key null ; , null */
return(getForNullKey() );/* key hashCode hash */
int hash = hash( key.hashCode() );/* table */
for ( Entry e = table[indexFor( hash, table.length )];
e != null;
e = e.next )
{
Object k;/* key key , value */
if ( e.hash == hash && ( (k = e.key) == key || key.equals( k ) ) )
return(e.value);
}
return(null);}
키 가 NULL 인 키 쌍 에 대해 HashMap 은 전문 적 인 처 리 를 제공 합 니 다: getForNullKey (), 그 소스 코드 는 다음 과 같 습 니 다./** * * Offloaded version of get() to look up null keys. Null keys map * * to index 0. This null case is split out into separate methods * * for the sake of performance in the two most commonly used * * operations (get and put), but incorporated with conditionals in * * others. * */private V getForNullKey(){
/* NULL , */
for ( Entry e = table[0]; e != null; e = e.next )
{
if ( e.key == null )
return(e.value);
}
/* NULL , null */
return(null);}
따라서 HashMap 의 get (Object key) 방법 을 호출 한 후 반환 값 이 NULL 이면 다음 과 같은 두 가지 가능성 이 있 습 니 다. 이 키 에 대응 하 는 값 은 null 입 니 다. HashMap 에는 이 key 가 존재 하지 않 습 니 다.
다음으로 전송:https://blog.51cto.com/14207296/2380981
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.