캐싱 기법
캐싱이란
캐시는 컴퓨터 과학에서 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 캐시는 데이터를 접근해 시간이 오래 걸리는 경우나 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다. 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터에 접근할 수 있다.
Java Wrapper Class
Java에서는 원시 타입을 참조 타입으로 사용하기 위해 만든 Wrapper 클래스가 있다.
인스턴스는 각각의 주소를 가지고, Wrapper Class 로 생성한 값들은 서로 다른 주소값을 가지기 때문에 실제 값을 비교하기 위해 오버라이딩이 된 equals()
를 사용한다. ==
으로 비교를 한다면 값이 같아도 false가 나올 것이다.
Integer num1 = 1;
Integer num2 = 1;
System.out.println(num1 == num2); -> 하지만 true 가 나온다. 왜?
Integer의 캐싱
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++); // 인스턴스 미리 생성!!
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)]; // 저장된 값 반환!!
return new Integer(i); // 새로운 Integer 생성 후 반환!!
}
위의 코드를 보면 low 값인 -128부터 high 값인 127 까지 Integer 인스턴스를 미리 생성하여 cache 배열에 저장하는 것을 볼 수 있다.
valueOf()
는 low와 hight 사이의 값일 경우 IntegerCache의 cache에 저장된 값을 반환하고 그 외의 경우 새로 Integer 인스턴스를 생성하여 반환한다.
이 IntegerCache에서 사용된 캐싱 기법을 Lotto 미션에서도 구현을 해보자.
package domain.Lotto;
import utils.ExceptionMessage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LottoNumber {
private static final int MINIMUM_LOTTO_NUMBER = 1;
private static final int MAXIMUM_LOTTO_NUMBER = 45;
private static final Map<Integer, LottoNumber> CACHE = new HashMap<>();
static {
for (int number = MINIMUM_LOTTO_NUMBER; number <= MAXIMUM_LOTTO_NUMBER; number++) {
CACHE.put(number, new LottoNumber(number));
}
}
private final int number;
public LottoNumber(int number) {
validateLottoNumberBound(number);
this.number = number;
}
private void validateLottoNumberBound(int number) {
if (number < MINIMUM_LOTTO_NUMBER || number > MAXIMUM_LOTTO_NUMBER) {
throw new IllegalArgumentException(ExceptionMessage.LOTTO_NUMBER_OUT_OF_BOUND);
}
}
public static LottoNumber valueOf(int number) {
LottoNumber lottoNumber = CACHE.get(number);
if (lottoNumber == null) {
CACHE.put(number, new LottoNumber(number));
lottoNumber = CACHE.get(number);
}
return lottoNumber;
}
public static List<LottoNumber> values() {
return new ArrayList<>(CACHE.values());
}
public int getNumber() {
return number;
}
}
1에서 45의 LottoNumber 객체를 미리 만들어놔서 valueOf()
와 values()
를 사용해 숫자가 같은 LottoNumber 인스턴스는 모두 동일한 인스턴스가 된다.
캐싱을 함으로써 발생하는 메모리 낭비 문제를 해결할 수 있다.
또한 숫자가 같은 LottoNumber는 같은 객체이므로 , equals와 hashcode를 오버라이딩하지 않아도 된다.
참고
https://tecoble.techcourse.co.kr/post/2020-06-24-caching-instance/