HashMap을 사용할 때, put(key, value)으로 데이터를 저장할 수 있는데 HashMap의 경우 key 값이 존재할 경우 value 값을 대체하고 기존의 value 값을 반환해준다. 하지만 key 값이 존재하지 않는 경우에는 새로 데이터를 저장하고 null을 반환한다. (기존 value 값이 없기 때문)

 

put()으로 데이터를 저장하고 반환된 value 객체를 참조할려고 하니 NullPointerException이 발생한다는 경고를 보고 어떻게 구현이 되어있나 확인해보았다.

 

HashMap에서 해싱이란 해시 함수를 이용해서 데이터를 해시테이블에 저장하고 검색하는 방식을 말하는데 해시함수가 데이터가 저장되어 있는 곳을 알려주기 때문에 다량의 데이터 중에서 원하는 데이터를 빠르게 찾을 수 있다.

해싱에서 사용하는 자료구조는 배열과 링크드 리스트의 조합으로 되어 있는데 key 값을 해시함수에 넣으면 배열의 한 요소를 얻게 되고, 다시 그 곳에 연결되어 있는 링크드 리스트에 저장이 된다.

해시함수의 계산 결과인 해시코드로 해당 값이 저장되어 있는 링크드 리스트를 찾고, 링크드 리스트에서 검색한 키와 일치하는 데이터를 찾는다. (링크드 리스트는 검색에 불리하기 때문에 해시코드가 서로 중복되지 않도록 하는 전략이 좋다.)

 

HashMap의 경우 Object 클래스에 정의된 hashCode()를 해시 함수로 사용하는데 이는 객체의 주소를 이용하는 알고리즘으로 모든 객체에 대해 hashCode() 결과가 서로 유일하게 된다. 

 

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

 

그래서 실제 HashMap의 put() 메서드를 보면 putVal() 메서드를 호출하면서 해시함수 hash(key)를 실행하고 해시 코드를 넘겨준다.

 

 

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
	    ...
        
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

 

putVal() 메서드의 일부를 보면 key가 존재하는지 확인을 하고 value를 교체한 뒤 oldValue를 반환하거나 그렇지 않을 경우 null을 반환한다.

 

 

Stack 자료구조의 특정 객체를 파라미터로 받아서 해당 요소가 있으면 위치를 반환해주는 search() 메서드를 구현해보던 중 equals()로 비교를 할 때, 전달 받은 파라미터가 null인 경우를 따로 체크하는 부분이 있었다.

 

public synchronized int lastIndexOf(Object o, int index) {
    if (index >= elementCount)
        throw new IndexOutOfBoundsException(index + " >= "+ elementCount);

    // Obect o 객체가 null 인 경우
    if (o == null) {
        for (int i = index; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = index; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

 

equals()를 사용할 때는 주의해야 될 부분이 있는데 값을 비교할 때 앞에 null인 객체가 들어올 경우 NullPointerException 예외가 발생하는 것이다. 

public int lastIndexOf(Object value) {
    int index = array.length - 1;
    for (int i = index; i >= 0; i--) {
        // value가 Null인 경우
        if (value.equals(array[i])) {
            return i;
        }
    }
    return -1;
}

 

객체 참조를 하기 때문에 당연한 부분이지만 전에도 이런 실수를 많이 했던 것 같다. 그래서 객체와 문자열의 값을 비교하는 경우에는 문자열을 왼쪽에 입력하고, 객체끼리 비교하는 경우에는 null 값을 고려해서 코드를 짜도록 해야겠다.

 

 

 

벤치마킹(benchmarking)이란 측정의 기준이 되는 대상을 설정하고 그 대상과 비교 분석을 하는 행위로

JMH(Java Microbenchmark Harness)을 사용하면 자바에서 간단하게 벤치마킹을 하여 성능 비교를 할 수 있다.

 

[JMH 사용법]

 

Microbenchmarking with Java | Baeldung

Learn about JMH, the Java Microbenchmark Harness.

www.baeldung.com

 

Stack 자료구조를 공부하던 중 System.arraycopy()를 발견하고 Arrays.copy()랑 무슨 차이가 있나 알아보았다.

사실 Arrays.copyOf() 메서드가 어떻게 되어있나 한번 봤으면 내부적으로 System.arraycopy() 를 호출한다는 것을 바로 알았을텐데 멍청하게도 둘이 다른 것인줄 알았다.

 

 

System.arraycopy()의 경우 src의 지정된 위치 srcPos에서부터 dest의 지정된 위치 destPos로 length만큼 복사를 하는데 JVM은 복사하기 전 소스 배열과 대상 배열의 타입 비교를 한다.

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

 

Arrays.copyOf()는 내부에서 System.arraycopy()를 호출하는만큼 별도로 추가적인 기능을 제공하는데 새로운 배열을 만들고 내용을 자르거나 채울 수 있다. Object 타입 체크를 하는 arraycopy()와 달리 두 Object가 다른 타입일 경우에도 Arrays.newInstance()로 생성을 한다. (primitive 타입일 경우는 바로 배열을 생성하고 복사)

 

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class) 
      ? (T[]) new Object[newLength]
      : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}
public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

 

동적 배열 할당의 경우에는 배열을 동적으로 늘려야되기 때문에 Arrays.copyOf()를 사용하면 되지만 새로운 인스턴스를 생성하지 않고 원하는 부분만 부분적으로 복사하고 싶은 경우 System.arraycopy()를 직접 사용하는 것도 생각해보면 좋을 것 같다.

 

 

다음은 그래도 JMH를 간단하게라도 한번 사용해보고 싶어서 새로운 인스턴스를 생성하지 않는 경우 System.arraycopy()로 값을 복사할 때랑 Arrays.copyOf()를 사용해서 새로운 인스턴스가 계속 생성되는 경우 속도 차이를 비교해보았다.

 

@BenchmarkMode로 벤치마킹 모드를 설정하고 @Warmup, @Meansurement로 벤치마킹 전 워밍업 테스트를 몇 번 실행할지, 실제로 몇 번 측정할지 설정할 수 있다. @Fork로 벤치마킹 실행 횟수를 설정할 수 있고 그 외에도 다양한 설정들이 있다.

 

public class BenchmarkRunner {
    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }
}
package benchmarking;

import org.openjdk.jmh.annotations.*;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5)
@Fork(value = 1)
@Measurement(iterations = 5)
public class PrimitivesCopyBenchmark {

    @Param("10")
    public int SIZE;

    int[] src;

    @Setup
    public void setup() {
        Random r = new Random();
        src = new int[SIZE];

        for (int i = 0; i < SIZE; i++) {
            src[i] = r.nextInt();
        }
    }

    @Benchmark
    public int[] systemArrayCopyBenchmark() {
        System.arraycopy(src, 0, src, 0, SIZE);
        return src;
    }

    @Benchmark
    public int[] arraysCopyOfBenchmark() {
        return Arrays.copyOf(src, SIZE);
    }
}

 

 

벤치마킹 결과

더보기

# Run progress: 0.00% complete, ETA 00:03:20
# Fork: 1 of 1
# Warmup Iteration   1: 9.806 ns/op
# Warmup Iteration   2: 8.583 ns/op
# Warmup Iteration   3: 7.721 ns/op
# Warmup Iteration   4: 7.707 ns/op
# Warmup Iteration   5: 7.693 ns/op
Iteration   1: 7.651 ns/op
Iteration   2: 7.810 ns/op
Iteration   3: 7.729 ns/op
Iteration   4: 7.824 ns/op
Iteration   5: 7.650 ns/op

 

 

Result "benchmarking.PrimitivesCopyBenchmark.arraysCopyOfBenchmark":
  7.733 ±(99.9%) 0.322 ns/op [Average]
  (min, avg, max) = (7.650, 7.733, 7.824), stdev = 0.084
  CI (99.9%): [7.411, 8.054] (assumes normal distribution)

 

 

# Run progress: 50.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration   1: 6.442 ns/op
# Warmup Iteration   2: 5.592 ns/op
# Warmup Iteration   3: 5.658 ns/op
# Warmup Iteration   4: 5.667 ns/op
# Warmup Iteration   5: 6.035 ns/op
Iteration   1: 6.255 ns/op
Iteration   2: 6.291 ns/op
Iteration   3: 6.247 ns/op
Iteration   4: 6.034 ns/op
Iteration   5: 6.463 ns/op


Result "benchmarking.PrimitivesCopyBenchmark.systemArrayCopyBenchmark":
  6.258 ±(99.9%) 0.589 ns/op [Average]
  (min, avg, max) = (6.034, 6.258, 6.463), stdev = 0.153
  CI (99.9%): [5.669, 6.847] (assumes normal distribution)

 

당연히 System.arraycopy()의 결과가 빠른 것을 볼 수 있다. 

 

 

 

System.arraycopy() vs Arrays.copyOf()

 

Performance of System.arraycopy() vs. Arrays.copyOf() | Baeldung

Learn about the implementation and performance of System.arraycopy() and Arrays.copyOf()

www.baeldung.com

IntelliJ에서 바로 실행하는 법

 

Scala: Unable to find the resource: /META-INF/BenchmarkList · Issue #13 · artyushov/idea-jmh-plugin

idea ultimate 14.1.2 + scala plugin 1.4.15 + jmh plugin 1.0.1 I have an sbt project opened in IDEA. When I run one benchmark method or all methods in a class I get this error: Exception in thread &...

github.com

JMH gradle 설정

 

JMH를 사용한 gradle 환경에서의 Java 코드 벤치마킹

몇 달전에 “코드 수행 시간을 어떻게 측정하나요?”를 포스팅 했었다. 다양한 Singleton 구현법에 대해 퍼포먼스를 측정하고 싶었지만, 저런 측정 방법이 영 미심쩍었다. 그래서 좀 더 정확하고

hyesun03.github.io

 

 

 

public enum TableStatus {
    Y("1", true),
    N("0", false);
    
    private String table1Value;
    private boolean table2Value;
    
    TableStatus(String table1Value, boolean table2Value) {
        this.table1Value = table1Value;
        this.table2Value = table2Value;
    }
}

 

Enum의 값 "Y", "N"에 따라 table1Value, table2Value의 값을 반환해주는 별도의 메소드를 선언하는 대신 Enum 클래스에서 위와 같이 묶어서 관리할 수 있다.

 

자바8 Function 참고

 

Java 8 - Function 예제

Java 8의 Function은 1개의 인자(Type T)를 받고 1개의 객체(Type R)를 리턴하는 함수형 인터페이스입니다. 다음 예제는 Function 구현 및 apply()가 어떻게 동작하는지 알려줍니다. 다음 예제는 andThen()이 어

codechacha.com

 

Enum 클래스로 상태 코드를 조회하고 각각의 코드에 따른 로직을 별도의 메소드를 통해 수행을 하도록 만들면 상태 코드의 조회와 코드에 따른 계산이 분리되어서 서로 관계가 있음을 표현할 수가 없다. 그래서 다음과 같이 Enum 클래스를 활용하여 계산 기능을 추가할 수 있다.

 

public enum CalculatorType {
    CALC_A(value -> value),
    CALC_B(value -> value * 10),
    CALC_C(value -> value * 3),
    CALC_ETC(value -> 0L);
    
    private Function<Long, Long>. expression;
    
    CalculatorType(Function<Long, Long> expression) {
        this.expression = expression;
    }
    
    public long calculate(long value) {
        return expresiion.apply(value);
    }
}

 

Function은 value1을 받아서 value2로 반환해주는 역할을 하며 apply()를 통해 인자값을 받으면 계산식을 거쳐서 결과를 반환 받을 수 있다.

 

핵심은 객체가 상태(값)와 행위(로직)을 갖고 있는 것으로 직접 활용을 안 해봐서 100% 이해는 못 했지만 이번 프로젝트에서 적절하게 적용해보고 직접 적용해본 내용을 정리해보면 좋을 것 같다.

 

 

 

Java Enum 활용기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E

techblog.woowahan.com

 

Legacy DB의 JPA Entity Mapping (Enum Converter 편) | 우아한형제들 기술블로그

{{item.name}} 안녕하세요. 저는 우아한형제들 비즈상품개발팀의 이은경입니다. Legacy DB의 JPA Entity Mapping (복합키 매핑 편)에 이어 저는 DB의 코드값과 Java Enum을 연결해주는 과정에서 유용하게 사용

techblog.woowahan.com

 

 

Enum 활용사례 3가지

안녕하세요? 이번 시간엔 enum 활용사례를 3가지정도 소개하려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 세미

jojoldu.tistory.com

 

+ Recent posts