Java8 HashMap 소스 코드 분석 단계를 안내합니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

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

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

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

Clothoff.io
AI 옷 제거제

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

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

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

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











해시맵의 확장 메커니즘은 용량을 다시 계산하고 원래 배열을 새 배열로 교체하는 것입니다. 원본 배열의 모든 데이터를 다시 계산하여 새 배열을 삽입한 후 새 배열을 가리키며, 용량 확장 전 배열이 최대값에 도달한 경우 직접 임계값을 최대 정수로 설정하고 반환합니다.

HashMap 클래스의 put() 메소드를 사용하여 HashMap에 키-값 쌍을 삽입하는 방법 HashMap은 Java 컬렉션 프레임워크에서 매우 중요한 클래스입니다. 실제 개발에서는 HashMap 클래스의 put() 메서드를 사용하여 쉽게 구현할 수 있는 키-값 쌍을 HashMap에 삽입해야 하는 경우가 많습니다. HashMap의 put() 메소드의 서명은 다음과 같습니다: Vput(Kkey,Vvalue)

1. Map은 기본적으로 HashMap을 사용할 수 있는데, HashMap에 문제가 있다고 설명합니다. 즉, HashMap을 반복하는 순서가 HashMap이 배치된 순서와 다르거나 순서가 맞지 않는다는 것입니다. HashMap의 이러한 단점은 종종 문제를 야기합니다. 일부 시나리오에서는 LinkedHashMap인 정렬된 맵을 기대하기 때문입니다. 2. 차이점 인스턴스 publicstaticvoidmain(String[]args){Mapmap=newLinkedHashMap();map.put("apple","Apple");map.put("

javaHashMap에 중복된 Key 값 삽입 HashMap에 중복된 값을 삽입하려면 먼저 HashMap에 요소가 어떻게 저장되어 있는지 파악해야 합니다. Put 메소드 Map에 저장된 각 요소는 키-값 쌍이며 모두 Put 메소드를 통해 추가되며 동일한 키는 Map에서 하나의 연관된 값만 갖습니다. Put 메소드는 Map에서 다음과 같이 정의됩니다. Vput(Kkey,Vvalue); put() 메소드 구현: 먼저 hash(key)는 키의 hashcode()를 가져오고, hashmap은 얻은 해시코드를 기반으로 삽입할 위치가 있는 체인을 찾습니다.

Java 문서 해석: HashMap 클래스의 containKey() 메소드 사용법에 대한 자세한 설명이 필요합니다. 소개: HashMap은 Java에서 일반적으로 사용되는 데이터 구조입니다. ContainsKey() 메서드는 HashMap에 지정된 키가 포함되어 있는지 확인하는 데 사용됩니다. 이 문서에서는 HashMap 클래스의 containKey() 메서드를 사용하는 방법을 자세히 설명하고 구체적인 코드 예제를 제공합니다. 1. 계속

1. 싱글턴 패턴이란 무엇인가요? 싱글톤 패턴은 객체의 특정 인스턴스를 생성하는 데 사용되는 객체 생성 패턴으로, 시스템에서 클래스의 인스턴스가 하나만 생성되도록 할 수 있습니다. Java로 구현된 싱글톤은 가상 머신의 범위 내에 있으므로 클래스를 로드하는 기능은 가상 머신에 속하므로 가상 머신은 자체 ClassLoad를 통해 싱글톤 클래스를 로드할 때 클래스의 인스턴스를 생성합니다. Java 언어에서 이러한 동작은 두 가지 주요 이점을 가져올 수 있습니다. 1. 자주 사용되는 객체의 경우 객체 생성에 소요되는 시간을 생략할 수 있습니다. 이는 중량 객체에 대한 시스템 오버헤드가 매우 큽니다. 2. 새로운 작업 수가 많기 때문입니다. 감소하면 시스템 메모리 사용 빈도도 줄어들어 GC 압력이 감소합니다.

JavaMap은 Java 표준 라이브러리에서 일반적으로 사용되는 데이터 구조로, 키-값 쌍의 형태로 데이터를 저장합니다. Map의 성능은 애플리케이션의 실행 효율성에 매우 중요합니다. Map의 성능이 좋지 않으면 애플리케이션이 느리게 실행되거나 충돌할 수도 있습니다. 1. 적절한 맵 구현 선택 Java는 HashMap, TreeMap 및 LinkedHashMap을 포함한 다양한 맵 구현을 제공합니다. 각 맵 구현에는 고유한 장점과 단점이 있습니다. 맵 구현을 선택할 때는 애플리케이션의 특정 요구 사항에 따라 적절한 구현을 선택해야 합니다. HashMap: HashMap은 가장 일반적으로 사용되는 맵 구현입니다. 해시 테이블을 사용하여 데이터를 저장하고 삽입, 삭제 및 검색 속도가 더 빠릅니다.

Java는 HashMap 클래스의 putAll() 함수를 사용하여 Map을 다른 Map에 추가합니다. Map은 Java에서 일반적으로 사용되는 데이터 구조이며 키-값 쌍의 컬렉션을 나타내는 데 사용됩니다. Java의 컬렉션 프레임워크에서 HashMap은 일반적으로 사용되는 구현 클래스입니다. 이는 데이터 병합 및 복사를 용이하게 하기 위해 하나의 맵을 다른 맵에 추가하는 데 사용되는 putAll() 함수를 제공합니다. 이 기사에서는 putAll() 함수를 사용하는 방법을 소개하고 해당 코드 예제를 제공합니다. 첫 번째,
