Java를 사용해본 친구들은 HashMap에 대해 잘 모를 수도 있습니다. 기사의 내용은 매우 간단하므로 꼭 읽어보시기 바랍니다.
Foreword
Java7의 HashMap은 Java8의 HashMap과 다릅니다. 배열 + 연결리스트, Java8의 HashMap은 배열 + 연결리스트 + 레드-블랙 트리로 구성됩니다. 연결리스트의 길이가 8을 초과하면 검색 시간 복잡도를 줄이기 위해 레드-블랙 트리로 변환됩니다. 정도이므로 효율성이 향상됩니다. 여기서 주요 분석은 Java8의 HashMap입니다.
사용 소개
일상적인 개발에서 HashMap을 사용할 때 다음과 같은 두 가지 초기화 방법이 있습니다.
1. 초기 값 크기를 지정하지 않고 new HashMap()을 통해
2. new HashMap(int initialCapacity)는 초기 값 크기를 지정합니다.
Initialization
// 数组的默认初始容量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30; // 默认负载因子(用来控制阈值和容量,当数组已经存放的容量超过了阈值时,容量会扩大一倍 // 阈值 = 最大容量 * 负载因子) static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认链表的最大长度(当链表的长度超过8时会被转换为红黑树) static final int TREEIFY_THRESHOLD = 8; // 使用第一种初始化方式时,默认初始化容量是2的4次 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap(int initialCapacity, float loadFactor) { // 不能小于0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: initialCapacity); // 超过2的30次方,则最大容量为2的30次方 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: loadFactor); this.loadFactor = loadFactor; // 计算阈值(在第一次put值的时候,会在resize()方法中重新计算) this.threshold = tableSizeFor(initialCapacity); }
HashMap#put()
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
hash() 메서드는 주로 저장된 키 값을 판단하는 데 사용되며, null이면 0을 반환하고, null이 아니면 해시 값을 반환합니다. 16비트의 부호 없는 오른쪽 시프트 후의 값에 대해 비트별 XOR을 수행한 결과입니다(비트가 복잡함). HashMap의 키 값이 null일 수 있음을 알 수 있습니다.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 第一次put值时,会初始化当前数组长度,如果没有指定,则默认为16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 找到在数组中对应的下标,如果该位置没有值,那么直接初始化一个Node放在此位置 // 由&运算可以确保(n - 1) & hash一定是小于数组容量 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; // 如果key值已经存在,则取出这个节点 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果当前key值的节点是红黑树,则调用红黑树的插值算法 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 如果是链表,则遍历链表,采用尾插的方式 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 如果链表的长度大于等于8,则将链表转换为红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash); break; } // 如果在链表的节点中存在相同的key,则结束循环 if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 如果存在相同的key值,则重新赋值,并且返回旧值 if (e != null) { // existing mapping for key V oldValue = e.value; // 由源码可知onlyIfAbsent默认false if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 如果数组已经容纳的长度超过了设定的阈值,则会对该数组进行扩容,每次扩容是之前长度的两倍 if (++size > threshold) resize(); afterNodeInsertion(evict); // 每一个不同的key值第一次put的时候返回null。 return null; }
HashMap#resize()
resize() 메서드는 주로 배열을 초기화하거나 배열에 대한 확장 계산을 수행하는 데 사용됩니다.
final Node<K,V>[] resize() { // 备份原始数组 Node<K,V>[] oldTab = table; // 第一次put值的时候为0 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 如果没有指定初始值大小,第一次put值的时候阈值为0 int oldThr = threshold; int newCap, newThr = 0; // 如果数组不为null且长度不为0,则会 if (oldCap > 0) { // 如果长度大于等2的30次方,则默认阈值为int的最大值(即2的31次方减1) if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 如果将数组长度扩大一倍后的值小于2的30次方并且数组之前的长度大于等于2的4次方,则将阈值扩大 // 一倍,否则阈值会在下面的if (newThr == 0)中进行赋值 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY) // 将阈值扩大一倍 newThr = oldThr << 1; // double threshold } // 如果使用new HashMap(int initialCapacity)初始化,则第一次put值时会进入这里 else if (oldThr > 0) initial capacity was placed in threshold newCap = oldThr; // 如果使用new HashMap()初始化,则第一次put值时会进入这里 else { zero initial threshold signifies using defaults // 默认数组大小是2的4次方 newCap = DEFAULT_INITIAL_CAPACITY; // 默认负载因子是0.75,即默认阈值为12 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 只有以下两种情况会进入到if判断中: // 1、在使用new HashMap(int initialCapacity)初始化,并且第一次put值的时候 // 2、对数组进行扩容且数组的原始长度小于2的4次方的时候 if (newThr == 0) { // 根据指定的数组大小和负载因子乘积得到阈值 float ft = (float)newCap * loadFactor; // 如果数组大小和阈值都小于2的20次方,则确定阈值 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) // 用新的数组大小初始化新的数组 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 如果是第一次初始化,则直接返回newTab。如果不是则会进行数据的迁移操作 if (oldTab != null) { // 遍历数组 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { // 将已经被取出的位置置空 oldTab[j] = null; // 如果数组该位置只是单个节点,那么直接赋值 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 如果数组该位置是红黑树 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 如果数组该位置是链表,保证原始的循序进行迁移 else { preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
resize() 메서드에서 볼 수 있듯이 로드 비율에 따라 배열의 용량과 사용량이 결정됩니다.
부하율이 클수록 어레이의 충전 밀도가 높아져 더 많은 요소를 수용할 수 있습니다. 그러나 배열에 요소를 추가하거나 삭제하는 데 걸리는 시간 복잡도는 O(n)이므로 인덱스의 효율성은 떨어지게 됩니다.
그러나 부하 계수가 작을수록 배열의 채우기 밀도가 희박해 공간이 낭비되지만 인덱싱 효율성은 높습니다(공간이 시간으로 교환됨).
HashMap#get()
put 메소드에 비해 get 메소드는 특히 간단합니다. 더 이상 확장 문제를 걱정할 필요가 없고 데이터 수집만 처리하면 되기 때문입니다.
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; // 首先判断数组不为null以及长度大于0并且对应位置节点不为null if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 判断第一个节点是否满足条件 if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 如果节点的下一个节点不为null if ((e = first.next) != null) { // 判断该节点是否为红黑树 if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); // 遍历链表,判断是否有满足条件的节点存在 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
관련 추천 :
Java 수집 프레임워크 HashSet 및 HashMap 소스 코드 분석에 대한 자세한 설명(사진)
위 내용은 Java8 HashMap 소스 코드 분석 단계를 안내합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

이 기사에서는 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 및 Gradle을 사용하여 접근 방식과 최적화 전략을 비교합니다.

이 기사에서는 Maven 및 Gradle과 같은 도구를 사용하여 적절한 버전 및 종속성 관리로 사용자 정의 Java 라이브러리 (JAR Files)를 작성하고 사용하는 것에 대해 설명합니다.

이 기사는 카페인 및 구아바 캐시를 사용하여 자바에서 다단계 캐싱을 구현하여 응용 프로그램 성능을 향상시키는 것에 대해 설명합니다. 구성 및 퇴거 정책 관리 Best Pra와 함께 설정, 통합 및 성능 이점을 다룹니다.

이 기사는 캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA를 사용하는 것에 대해 설명합니다. 잠재적 인 함정을 강조하면서 성능을 최적화하기위한 설정, 엔티티 매핑 및 모범 사례를 다룹니다. [159 문자]

Java의 클래스 로딩에는 부트 스트랩, 확장 및 응용 프로그램 클래스 로더가있는 계층 적 시스템을 사용하여 클래스로드, 링크 및 초기화 클래스가 포함됩니다. 학부모 위임 모델은 핵심 클래스가 먼저로드되어 사용자 정의 클래스 LOA에 영향을 미치도록합니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

WebStorm Mac 버전
유용한 JavaScript 개발 도구

안전한 시험 브라우저
안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.

SublimeText3 영어 버전
권장 사항: Win 버전, 코드 프롬프트 지원!

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경
