HashMap 에서 7 가지 옮 겨 다 니 는 방식 의 성능 분석
JDK 1.8 Streams API 가 발표 되면 서 HashMap 은 더 많은 스 트 리밍 방식 을 가지 게 되 었 으 나 그런 스 트 리밍 방식 을 선택해 야 합 니까?오히려 문제 가 되 었 다.
본 논문 의 주요 내용 은 다음 그림 과 같다.
2.HashMap 옮 겨 다 니 기
HashMap 은 큰 방향 에서 볼 때 다음 과 같은 4 가지 로 나 눌 수 있 습 니 다.
2.1,교체 기 EntrySet
@Test
public void testIterator() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Oracle Database");
//
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
실행 결과:2.2,교체 기 키 세트
@Test
public void testKeySet() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Test KeySet");
//
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key + ":" + map.get(key));
}
}
실행 결과:2.3、ForEachEntrySet
@Test
public void testForEachEntrySet() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Test ForEach EntrySet");
//
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
실행 결과:2.4、ForEach KeySet
@Test
public void testForEachKeySet() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Test ForEach KeySet");
//
for (Integer key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
}
실행 결과:2.5、Lambda
@Test
public void testLambda() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Test Lambda");
//
map.forEach((key, value) -> {
System.out.println(key + ":" + value);
});
}
실행 결과:2.6,Streams API 단일 스 레 드
@Test
public void testStreamApi() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Test Stream API");
//
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey() + ":" + entry.getValue());
});
}
실행 결과:2.7,Streams API 다 중 스 레 드
@Test
public void testParallelStreamApi() {
// HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Test Parallel Stream API");
//
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey() + ":" + entry.getValue());
});
}
실행 결과:성능 분석
다음은 Oracle 이 공식 적 으로 제공 하 는 성능 테스트 도구 JMH(Java Microbenchmark Harness,JAVA 마이크로 기준 테스트 세트)를 이용 하여 7 가지 순환 성능 을 테스트 해 보 겠 습 니 다.
우선 JMH 프레임 워 크 를 도입 해 야 합 니 다.이번 구축 은 의존 사용 도 구 를 Gradle 로 하고 다음 과 같은 설정 을 도입 합 니 다.
implementation "org.openjdk.jmh:jmh-core:1.23"
implementation "org.openjdk.jmh:jmh-generator-annprocess:1.23"
Maven 을 사용 하면 다음 설정 을 도입 할 수 있 습 니 다.
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
<scope>provided</scope>
</dependency>
성능 테스트 코드 작성:
//@BenchmarkMode(Mode.Throughput) // :
@BenchmarkMode(Mode.AverageTime) // :
//@OutputTimeUnit(TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) // 4 , 1s
@Measurement(iterations = 10, time = 3, timeUnit = TimeUnit.SECONDS) // 10 , 3s
@Fork(1) // fork 1
@State(Scope.Thread) //
public class HashMapTest {
static Map<Integer, String> map = new HashMap() {
{
for(int var1 = 0; var1 < 2; ++var1) {
this.put(var1, "Kevin:" + var1);
}
}
};
public static void main(String[] args) throws RunnerException {
//
Options opt = new OptionsBuilder()
.include(HashMapTest.class.getSimpleName()) //
.output("E:/IDEAWorkSpaces/Test/src/main/java/com/kevin/performance/jmh-map2.log") //
.build();
new Runner(opt).run(); //
}
/**
* Iterator entrySet
*/
@Benchmark
public void entrySet() {
//
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
Integer k = entry.getKey();
String v = entry.getValue();
}
}
/**
* Foreach entrySet
*/
@Benchmark
public void forEachEntrySet() {
//
for (Map.Entry<Integer, String> entry : map.entrySet()) {
Integer k = entry.getKey();
String v = entry.getValue();
}
}
/**
* Iterator keySet
*/
@Benchmark
public void keySet() {
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer k = iterator.next();
String v = map.get(k);
}
}
/**
* Foreach keySet
*/
@Benchmark
public void forEachKeySet() {
for (Integer key : map.keySet()) {
Integer k = key;
String v = map.get(k);
}
}
/**
* Lambda
*/
@Benchmark
public void lambda() {
map.forEach((key, value) -> {
Integer k = key;
String v = value;
});
}
/**
*
*/
@Benchmark
public void streamApi() {
map.entrySet().stream().forEach((entry) -> {
Integer k = entry.getKey();
String v = entry.getValue();
});
}
/**
*
*/
public void parallelStreamApi() {
map.entrySet().parallelStream().forEach((entry) -> {
Integer k = entry.getKey();
String v = entry.getValue();
});
}
}
@Benchmark
주석 이 추 가 된 모든 방법 이 테스트 됩 니 다.(parallelStream 은 다 중 스 레 드 버 전 으로 성능 이 다른 단일 스 레 드 로 인해 이번 테스트 에 참여 하지 않 습 니 다)테스트 결 과 는 다음 과 같 습 니 다.그 중에서 Units 는 N/op 으로 실행 완료 시간(단 위 는 나 초)을 의미 하고 Score 는 평균 실행 시간 으로 분류 되 며
±
기 호 는 오 차 를 나타 낸다.이 같은 결 과 를 통 해 알 수 있 듯 이 두 개entrySet
의 성능 이 비슷 하고 집행 속도 가 가장 빠르다.이 어stream
두 개keySet
,성능 이 가장 나 쁜 것 은KeySet
이다.결론:
이상 의 결 과 를 통 해 알 수 있 듯 이
entrySet
의 성능 은keySet
의 성능 보다 배가 높 기 때문에 우 리 는 가능 한 한entrySet
을 사용 하여 맵 집합 을 옮 겨 다 녀 야 한다.바이트 코드 분석
이상 의 테스트 결 과 를 이해 하려 면 모든 스 트 리밍 코드
javac
를 바이트 코드 로 컴 파일 하여 구체 적 인 원인 을 볼 필요 가 있다.컴 파일 후,우 리 는 Idea 를 사용 하여 바이트 코드 를 엽 니 다.내용 은 다음 과 같 습 니 다.
public class HashMapTest {
static Map<Integer, String> map = new HashMap() {
{
for(int var1 = 0; var1 < 2; ++var1) {
this.put(var1, "Kevin:" + var1);
}
}
};
public HashMapTest() {
}
public static void main(String[] var0) {
entrySet();
keySet();
forEachEntrySet();
forEachKeySet();
lambda();
streamApi();
parallelStreamApi();
}
public static void entrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey() + ":" + (String)var1.getValue());
}
}
public static void keySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1 + ":" + (String)map.get(var1));
}
}
public static void forEachEntrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey() + ":" + (String)var1.getValue());
}
}
public static void forEachKeySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1 + ":" + (String)map.get(var1));
}
}
public static void lambda() {
map.forEach((var0, var1) -> {
System.out.println(var0 + ":" + var1);
});
}
public static void streamApi() {
map.entrySet().stream().forEach((var0) -> {
System.out.println(var0.getKey() + ":" + (String)var0.getValue());
});
}
public static void parallelStreamApi() {
map.entrySet().parallelStream().forEach((var0) -> {
System.out.println(var0.getKey() + ":" + (String)var0.getValue());
});
}
}
// , Lambda Streams API , for EntrySet , Entry , :
public static void entrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey() + ":" + (String)var1.getValue());
}
}
public static void forEachEntrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey() + ":" + (String)var1.getValue());
}
}
// KeySet , :
public static void keySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1 + ":" + (String)map.get(var1));
}
}
public static void forEachKeySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1 + ":" + (String)map.get(var1));
}
}
결 과 를 통 해 알 수 있 듯 이 Lambda 와 Streams API 를 제외 하고 교체 기 순환 과for
순환 을 통 해 옮 겨 다 니 는EntrySet
최종 적 으로 생 성 된 코드 는 똑 같 습 니 다.그들 은 모두 순환 에서 옮 겨 다 니 는 대상Entry
을 만 들 었 습 니 다.코드 는 다음 과 같 습 니 다.
public static void entrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey() + ":" + (String)var1.getValue());
}
}
public static void forEachEntrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey() + ":" + (String)var1.getValue());
}
}
그리고KeySet
의 코드 도 비슷 하 다.다음 과 같다.
public static void keySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1 + ":" + (String)map.get(var1));
}
}
public static void forEachKeySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1 + ":" + (String)map.get(var1));
}
}
그래서 우리 가 교체 기 를 사용 하거나for
순환EntrySet
을 사용 할 때 그들의 성능 은 모두 같다.왜냐하면 그들 이 최종 적 으로 생 성 한 바이트 코드 는 기본적으로 똑 같 기 때문이다.같은 이치KeySet
의 두 가지 옮 겨 다 니 는 방식 도 비슷 하 다.5.EntrySet 성능 분석
EntrySet
가KeySet
보다 성능 이 높 은 이 유 는KeySet
순환 할 때map.get(key)
를 사 용 했 고map.get(key)
지도 집합 을 다시 한 번 옮 겨 다 니 며 조회key
에 대응 하 는 값 을 조회 한 셈 이기 때문이다.왜'또'라 는 단 어 를 써 야 합 니까?교체 기 나 for 순환 을 사용 할 때 맵 집합 을 한 번 옮 겨 다 녔 기 때문에 다시map.get(key)
조 회 를 사용 할 때 두 번 옮 겨 다 니 는 셈 이다.그리고
EntrySet
맵 집합 을 한 번 만 옮 겨 다 녔 고 그 후에 코드'Entrykey
와value
값 을 모두Entry
대상 에 넣 었 기 때문에key
와value
값 을 얻 을 때 맵 집합 을 옮 겨 다 니 지 않 고Entry
대상 에서 값 을 얻 으 면 된다.그래서
EntrySet
의 성능 은KeySet
의 성능 보다 배가 높 았 다.KeySet
는 맵 집합 을 두 번 순환 한 셈 이 고EntrySet
는 한 번 만 순환 한 셈 이기 때문이다.6.안전성 테스트
위의 성능 테스트 결과 와 원리 분석 을 통 해 저 는 여러분 들 이 그런 옮 겨 다 니 는 방식 을 선택해 야 한다 고 생각 합 니 다.그 다음 에 우 리 는'안전'의 측면 에서 출발 하여 그런 옮 겨 다 니 는 방식 을 분석 하 는 것 이 더욱 안전 합 니 다.
우 리 는 상기 사 이 를 네 가지 로 나 누 어 테스트 를 진행 합 니 다.교체 기 방식,For 순환 방식,Lambda 방식 과 Stream 방식,테스트 코드 는 다음 과 같 습 니 다.
6.1 교체 기 방식
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
if (entry.getKey() == 1) {
//
System.out.println("del:" + entry.getKey());
iterator.remove();
} else {
System.out.println("show:" + entry.getKey());
}
}
실행 결과:show:0
del:1
show:2
테스트 결과:교체 기 에서 데 이 터 를 순환 적 으로 삭제 하 는 것 이 안전 합 니 다.
6.2 For 순환 방식
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() == 1) {
//
System.out.println("del:" + entry.getKey());
map.remove(entry.getKey());
} else {
System.out.println("show:" + entry.getKey());
}
}
실행 결과:테스트 결과:For 순환 에서 데 이 터 를 삭제 하 는 것 은 안전 하지 않 습 니 다.
6.3 람 다 방식
map.forEach((key, value) -> {
if (key == 1) {
System.out.println("del:" + key);
map.remove(key);
} else {
System.out.println("show:" + key);
}
});
실행 결과:테스트 결과:Lambda 순환 에서 데 이 터 를 삭제 하 는 것 은 안전 하지 않 습 니 다.
Lambda 에서 삭제 하 는 올 바른 방법:
// map key
map.keySet().removeIf(key -> key == 1);
map.forEach((key, value) -> {
System.out.println("show:" + key);
});
실행 결과:show:0
show:2
위의 코드 를 보면 알 수 있 듯 이
Lambda
의removeIf
를 사용 하여 불필요 한 데 이 터 를 삭제 한 다음 에 순환 을 하 는 것 은 집합 을 정확하게 조작 하 는 방식 이다.6.4、스 트림 방식
map.entrySet().stream().forEach((entry) -> {
if (entry.getKey() == 1) {
System.out.println("del:" + entry.getKey());
map.remove(entry.getKey());
} else {
System.out.println("show:" + entry.getKey());
}
});
실행 결과:테스트 결과:Stream 순환 에서 데 이 터 를 삭제 하 는 것 은 안전 하지 않 습 니 다.
Stream 순환 의 올 바른 방식:
map.entrySet().stream().filter(m -> 1 != m.getKey()).forEach((entry) -> {
if (entry.getKey() == 1) {
System.out.println("del:" + entry.getKey());
} else {
System.out.println("show:" + entry.getKey());
}
});
실행 결과:show:0
show:2
위의 코드 를 통 해 알 수 있 듯 이
Stream
중의filter
을 사용 하여 쓸모없는 데 이 터 를 걸 러 내 고 옮 겨 다 니 는 것 도 안전 한 조작 집합 방식 이다.6.5 소결
우 리 는 사 이 를 옮 겨 다 니 며 집합
map.remove()
을 사용 하여 데 이 터 를 삭제 할 수 없다.이것 은 비 안전 한 조작 방식 이지 만,우 리 는 교체 기iterator.remove()
의 방법 으로 데 이 터 를 삭제 할 수 있다.이것 은 안전하게 집합 을 삭제 하 는 방식 이다.마찬가지 로 저 희 는 Lambda 의removeIf
를 사용 하여 데 이 터 를 미리 삭제 하거나 Stream 의filter
을 사용 하여 삭제 할 데 이 터 를 걸 러 내 고 순환 할 수 있 습 니 다.이것 은 모두 안전 합 니 다.물론 저 희 는for
순환 전에 데 이 터 를 삭제 할 수 있 습 니 다.7.총화
본 고 는 HashMap 4 가지 옮 겨 다 니 는 방식 을 설명 했다.교체 기,for,lambda,stream,그리고 구체 적 인 7 가지 옮 겨 다 니 는 방법,종합 적 인 성능 과 안전성 을 보면 우 리 는 가능 한 한 교체 기(Iterator)를 사용 하여 옮 겨 다 니 는
EntrySet
방식 으로 Map 집합 을 조작 해 야 한다.그러면 안전 하면 서도 효율 적 이다.이상 은 HashMap 에서 7 가지 옮 겨 다 니 는 방식 의 성능 분석 에 대한 상세 한 내용 입 니 다.HashMap 옮 겨 다 니 는 성능 분석 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
[프로그래머스 / 완전 탐색] 메뉴 리뉴얼 (c++)문제와 제한 사항이 조금 복잡해서 직접 읽는 것이 더 편할 것이다 이번 문제 역시 직접 값들을 하나하나 찾아야 한다. 문제 해결을 2파트로 나눌 수 있다. 1. orders 배열에 있는 각 주문들이 만들 수 있는 조...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.