서문 : Java 8 이후에 추가 된 새로운 것들이 많이 있습니다. 온라인으로 관련 정보를 찾았습니다. 해시 맵이 남용 된 후, 나는 나 자신을 위해 관련 지식을 정리하기로 결정했습니다. 사진 및 일부 내용에서 참조 할 수있는이 기사 : http://www.vevb.com/article/80446.htm
해시 맵의 저장 구조는 그림에 나와 있습니다. 버킷에 8 개 이상의 노드가있는 경우 저장 구조는 빨간색과 검은 색 트리이며 8 미만은 일방 통행 링크 목록입니다.
1 : 해시 맵의 일부 특성
Public Class Hashmap <k, v> 확장 AbstractMap <k, v> emp <k, v>, 클로닝 가능, 직렬화 가능 {private static final long serialversionuid = 362498820763181265L; // 기본 초기 용량은 16static final int default_initial_capacity = 1 << 4 ;/ FINSTATIC 최종 최종 int default_initial_capacity; 30; // 기본 충전 계수 (이전 버전을로드 계수라고도 함) 정적 최종 플로트 기본 _load_factor = 0.75f; // 임계 값입니다. 버킷의 링크 된 목록 수가이 값보다 클 때는 빨간색과 검은 색 트리로 변환됩니다. PUT 방법의 코드는 정적 최종 int treeify_threshold = 8; // 임계 값이기도합니다. 버킷의 링크 된 목록의 수가이 값보다 작을 때, 트리는 링크 된 목록으로 변환됩니다. 정적 최종 최종 int untreeify_threshold = 6; // 소스 코드 주석에서 : 트리의 최소 용량은 4 x 이상입니다. 4 x treeify_threshold = 32. 요소의 배열은 항상 2 개의 과도 노드의 배수로 저장됩니다. <k, v> [] 표; 과도 세트 <map.entry <k, v >> enthrentset; // 저장된 요소 수는 배열 길이와 같지 않습니다. 과도 int 크기; // 맵 구조의 각 확장 및 변경에 대한 카운터는 일시적 int 모드 카운트입니다. // 실제 크기 (용량 * 채우기 팩터)가 임계 값을 초과 할 때 임계 값이 확장됩니다. 용량은 int 임계 값을 확장합니다.2 : 해시 맵 구조 방법
// 초기 용량 및 채우기 요소를 지정하기위한 생성자 공개 해시 맵 (int initialcapacity, float loadfactor) {// 지정된 초기 용량은 (초기 커피 <0) 새로운 불법 초기 용량을 던지는 경우 (불법 초기 용량 : +초기 용량); // 최대 용량보다 크면 최대 용량을 설정하는 경우 (초기-초기 용량). maximum_capacity) initialcapacity = maximum_capacity; // 채우기 비율은 양수입니다. this.loadfactor = loadfactor; // 용량을 지정한 후 테이블 크기에 대한 메소드는 임계 값을 계산합니다. 데이터를 넣을 때 값이 초과되면 확장됩니다. 값은 확실히 2의 배수입니다. 지정된 초기 용량은 저장되지 않았으며, 임계 값 만 생성하는 데만 사용됩니다. threshold = tablesizefor (InitialCapacity);} //이 방법은 항상 캡보다 큰 값을 반환하고 2의 배수를 반환합니다. 예를 들어, 999에서 통과하는 경우 {int n apr (int n) {// int in in int a. | = n >>> 1; n | = n >>> 2; n | = n >>> 4; n | = n >>> 8; n | = n >>> 16; // 삼각 연산자의 중첩 된 반환 (n <0)? 1 : (n> = maximum_capacity)? maximum_capacity : n + 1;} // 생성자 2public hashmap (int initialcapacity) {this (initialcapacity, default_load_factor);} // 3public hashmap () {this.loadfactor = default_load_factor; // 다른 모든 필드 기본값}3 : 얻을 때 배열에서 요소의 위치를 결정하십시오.
정적 최종 INT HASH (Object Key) {int h; return (key == null)? 0 : (h = key.hashcode ()) ^ (h >>> 16);}위치를 결정합니다
첫 번째 단계 : 첫 번째는 int 유형 번호 인 키의 해시 코드를 계산하는 것입니다. 다음 h >>> 16 소스 코드 의견 : 해시 충돌을 피하기 위해 높은 위치는 낮은 위치로 퍼져 있으며, 이는 속도 및 성능과 같은 다양한 요소를 고려한 후에 이루어집니다.
2 단계 : h는 해시 코드이고 길이는 위의 노드 [] 배열의 길이이며 동일한 작업 h & (길이 -1)를 수행합니다. 길이는 2 -1의 배수이므로 이진 코드는 1이고 다른 숫자의 1은 0 또는 1 일 수 있으므로 작업 후 균일 성을 보장합니다. 즉, 해시 방법은 결과의 균일 성을 보장하며, 이는 매우 중요하며 해시 맵의 퍼팅 및 성능에 큰 영향을 미칩니다. 비교를 위해 다음 그림을 참조하십시오.
그림 3.1은 비대칭 해시 결과입니다
그림 3.2는 균형 잡힌 해시 결과입니다
이 두 그래프에는 데이터가 많지 않습니다. 링크 된 목록이 8보다 길면 빨간색과 검은 나무로 변환됩니다. 당시에는 더 분명 할 것입니다. JDK8은 항상 링크 된 목록이었습니다. 링크 된 목록 쿼리의 복잡성은 O (n)이며 자체 특성으로 인한 빨간색과 검은 나무의 복잡성은 O (log (n))입니다. 해시 결과가 고르지 않으면 작동의 복잡성에 큰 영향을 미칩니다. a <a href = "http://blog.chinaunix.net/UID-26575352-3061918.html"> Red 및 Black Tree Basic Knowledge Blog </a> 온라인에서 다른 예제가 있습니다. 사용자 정의 객체는 키를 만들고 Hashcode () 방법을 조정하는 데 사용됩니다.
public class mutablekeytest {public static void main (string args []) {class mykey {integer i; public void seti (integer i) {this.i = i;} public mykey (integer i) {this.i = i;}@atriadepublic int hashcode () {// retuy 1rethon i; must be implemented @Overridepublic boolean equals(Object obj) {if (obj instanceof MyKey) {return i.equals((((MyKey)obj).i);} else {return false;}}}// My machine configuration is not high. If 25000 is normal for 27ms, you can try 25 million. If hashCode() method returns 1, 2.5 million will be stuck Map<MyKey,String> map = new Hashmap <> (25000,1); 날짜 시작 = new 날짜 (); for (int i = 0; i <20000; i ++) {map.put (new Mykey (i), "test" + i);} date end = new date (); system.out.println ( "time (ms)" + (end.gettime.gettime ());4 : 방법을 얻으십시오
public v get (객체 키) {node <k, v> e; return (e = getNode (hash (key)) == null? null : e.value;} 최종 노드 <k, v> getnode (int Hash, Object Key) {node <k, v> [] 탭; 노드 <k, v> 먼저, e; int n; k k; // hash & (길이 -1) 빨간색과 검은 색 트리의 루트 위치 또는 링크 된 목록의 루트를 가져옵니다. if ((tab = table)! = null && (n = tab.length)> 0 && (first = tab [(n -1) & hash])! = null) {if (first.hash == hash && // (k = first.key) (k = first.key). key.equals (k)))) 먼저 반환; if ((e = first.next)! = null) {// 트리 인 경우 빨간색과 검은 색 트리를 가로 지르는 복잡성은 O (log (n))가 O (log (n))를 얻기 위해 (treenode의 첫 번째 인스턴스)를 얻습니다 ((treenode <k, v>). hash && ((k = e.key) == key || (key! = null && key.equals (k))) return e;} while ((e = e.next)! = null);}} return null;}5 : Put Method를 넣으면 H & (길이 1)에 따라 버킷을 찾은 다음 빨간색과 검은 색 나무 또는 링크 된 목록 및 Putval인지 확인하십시오.
public v put (k key, v value) {return putval (hash (key), key, value, false, true);} final v putval (int hash, k key, v value, boolean onityifabsent, boolean evict) {node <k, v> [] tab; 노드 <k, v> p; int n, i; // 탭이 비어 있거나 길이가 0 인 경우, 메모리 할당 resize () if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize ()). 길이; // (n -1) & hash는 풋 위치를 찾습니다. 비어 있으면 직접 묻다 ((p = 탭 [i = (n -1) & hash]) == null) 탭 [i] = newnode (해시, 키, 값, null); else {node <k, v> e; k k; // 첫 번째 노드의 해시 값은 동일하며 키 값은 삽입 키와 동일합니다. Putval 후에는 나무 전체를 가로 질러야합니다. 필요한 경우 값을 수정하여 빨간색과 검은 색 트리의 특성을 보장하기 위해 값을 수정하십시오 e = ((treenode <k, v>) p) .puttreeval (this, 탭, 탭, key, value); else {// 링크 된 목록 (int bincount = 0;; ++ bincount) {if (e = p.next) == null) {// e는 비어 있음을 나타내니다. 테이블의 끝에 새 노드 P.Next = NewNode (해시, 키, 값, NULL); // 새 노드를 추가 한 후 노드의 수가 임계 값에 도달하면 링크 된 목록을 빨간색과 검은 색 트리로 변환하면 (bincount> = treeify_threshold -1) // -1에 1streyifybin (tab, hash); break; hash && ((k = e.key) == key || (key! = null && key.equals (k))) break; p = e;}} // 동일한 해시 값과 키 값으로 노드 값을 업데이트합니다. if (e! = null) {// keyv oldvalue = e.value에 대한 기존 매핑 (! value; ofternodeaccess (e); return OldValue;}} ++ modcount; if (++ size> threshold) resize (); offerodeInsertion (evict); return null;}6 : 방법을 조정하십시오
최종 노드 <k, v> [] resize () {node <k, v> [] oldtab = table; int OldCap = (OldTab == null)? 0 : oldtab.length; int oldthr = threshold; int newcap, newcrap, newthr = 0; if (oldcap> 0) {if (oldcap> = maximum_capacity) {threshold = integer.max_value; return oldtab;} //이 문장이 더 중요하다는 것을 알 수 있습니다. && oldcap> = default_initial_capacity) newthr = oldthr << 1; // Double Threshold} else if (Oldthr> 0) // 초기 용량은 임계 값에 배치되었습니다. thresholdnewcap = Oldthr; else {// Zero 초기 임계 값은 defaultsnewcap = default_initial_capacity; newthr = (int) (default_load_actor * default_initial_capacity)를 사용하여 의미합니다. loadFactor; newthr = (newCap <maximum_capacity && ft <(float) maximum_capacity? (int) ft : integer.max_value);} threshold = newthr; @suppresswarnings ({ "rawtypes", "unchecked"}) 노드 <k, v> [] newtab = (node <k, v>). 노드 [newCap]; table = newTab; if (oldtab! = null) {for (int j = 0; treenode) ((treenode <k, v>) e) .split (this, newtab, j, oldcap); else {// preserve ordernode <k, v> lohead = null, lotail = null; node <k, v> hihead = null, hitail = null; node <k, v v> next {e.next; if & if (e.hash & if). (lotail == null) lohead = e; elselotail.next = e; lotail = e;} else {if (hitail == null) hihead = e; elsehitail.next = e; hitail = e;}} while (e = next)! if (lotail! = null) {hitail = null; null; leghead; null) {hitail.next = null; newtab [j + OldCap] = hihead;}}}} retrack newtab;}위는 편집자가 귀하에게 소개 한 Java8 Hashmap의 구현 원리 분석에 대한 관련 지식입니다. 나는 그것이 당신에게 도움이되기를 바랍니다!