개요
BitMask
를 사용하는 이유와, 적용하게 된 계기를 적어 보고자 한다. 그러나 결국 EnumSet
을 사용하기로 결정하였기 때문에, BitMask
대신 EnumSet
사용을 권장한다.
단, 이 포스팅에서 BitMask
와 EnumSet
을 디테일하게 다루지는 않을 것이다. 이 글은 ‘왜’ 사용해야 하는가와 '어떤 장점’이 있는지에 초점을 맞추어 작성하였다.
BitMask요? 갑자기요?
스무 살, C언어 강의를 듣던 언젠가, 어렴풋이 들었던 것도 같다. (심지어 그 당시 교수님도 그리 중요하게 다루지 않으셨고, 나는 A+이지렁!) 지금도 잘 모르지만 그땐 더욱 몰랐으니 논외로 치고, 처음 BitMask
를 이해해야만 했던 것은 약 1년 전이다. 우리 앱[1]이 특정 앱에게 intent
로 정보를 요청하고, 수신해야 했는데 이때 수신한 값이 BitMask
형태라는 것이다. 헤헤. 청천벽력 같았다. ‘어차피 intent
에 data
담아서 줄 거면 그 값 그대로 주면 되지 굳이?’ 라는 생각이 들었지만, 그렇게 준다는 걸 어떡해. 앞으로 쓸 일도 없을 것 같고, 대충 이런 게 있구나 정도로만 이해하고 넘어갔다.
코드 구경, 이해, 깨달음, 적용
다른 업무로 옆 파트 과장님과 의견 교환 중에 우연히 과장님의 코드를 볼 기회가 생겼다. 업무 관련해서 조언을 구했는데, 과장님께서 본인 코드[2] 를 적절히 고치면 웬만해서 해결될 거라고 하셨다. 그렇다. 예상했겠지만, 이 코드 안에 BitMask
를 사용한 비즈니스 로직이 포함되어 있었다.
후. 이렇게 다시 만날 줄 몰랐다. 그러나 더는 피할 곳이 없었다. 이전엔 이미 마스킹된 값을 풀어내는 것에 그쳤지만, 이번엔 이것을 제대로 적용해 볼 수 있는 기회였다. 과장님께서 코드까지 주셨는데 두려울 게 뭐가 있겠습니까.
그래서 BitMask가 뭔데?
말 그대로 bit
에 관련된 것이다. bit
는 이진 숫자를 뜻하는 말로, 컴퓨터에서 사용되는 데이터의 최소 단위이다. 맞다, 0과 1! 그런데 왜 bit
에 mask
가 붙는 걸까? BitMask
는 0과 1로만 이루어진 bit
의 특성을 이용한 테크닉의 일종이다. 결론만 말하자면, 이 테크닉은 ‘조건 지옥’ 에서 조금이나마 벗어날 수 있도록 해 준다. 아래 코드를 보자.
1 | public class HobbyManager { |
작명이 영 별로이긴 하지만, 위 코드는 누군가의 취미를 설정하고, 추가하고, 특정 취미 보유 여부를 판단할 수 있도록 한다. 사용 방법은 간단하다. 모든 함수가 static
함수로 선언되어 있기에 코드 어느 곳에서나, 필요할 때 위의 함수를 호출하기만 하면 된다.
1 | // 초기 취미 Setting |
사실, HobbyManager
를 작성한 본질적인 이유는 취미 보유 여부 확인 이라고 해도 과언이 아니다. 만약, BitMask
를 사용하지 않는다면 어떤 코드를 작성할 수 있을까?
1 | ArrayList<String> hobbies = new ArrayList<>(); |
적당히 못난 코드로 작성해 보았다. hasHobby
에서 판단하고 싶은 hobby
를 전달받고, 유저의 취미 리스트를 0부터 하나씩 비교하여 존재할 경우 true
, 아니면 false
를 반환하게 된다. 물론, 이렇게 개발해도 크리티컬한 문제는 없다. 이런 코드들이 쌓이고 쌓이면 문제가 될 수는 있겠지만 말이다. 그렇지만 코드가 예쁘고, 조금 더 효율적이면 기분이 조크든요. 기왕 하는 거 예쁘게 작성하면 모두가 행복합니다!
다시 돌아와서
막상 적용하니 문제가 생겼다. 위의 코드를 예시로 들면, 우리 앱에서 지원하고 싶은 취미가 9가지가 넘는단 사실이다. 이 경우에 int
형 대신 long
을 사용해서 더 많은 기준을 잡을 수 있다고는 하는데, 관련 예시가 없어 선뜻 적용하기가 어려웠다. 방법을 찾기 위해 열심히 구글링을 하다가, BitMask
대신 사용할 수 있는 엄청난 것을 발견했다. (자바로 안드로이드 개발하지만 자바를 잘 모르는, 또 나만 몰랐겠지?) 나와 같은 고민을 하는 사람이 없도록, Java는 위대했던 거시다.
결론
EnumSet
쓰세요. 두 번 쓰세요. 특징을 정리하면 다음과 같다.
EnumSet
에서 제공하는 모든 메서드는 산술 비트 연산을 사용하여 구현되므로 일반적인 연산 속도가 굉장히 빠르다.HashSet
과 같은 다른Set
구현체와 비교했을 때, 데이터가 예상 가능한 순서로 저장되어 있다.- 계산시 하나의 비트만이 필요하므로 더 빠르다.
HashSet
처럼 데이터 저장 버킷을 찾을 때 hash code를 계산할 필요가 없다.
EnumSet
은 내부적으로 big vector로 표현된다.- 비트 벡터의 특성상 더 작은 메모리를 사용한다.
bit flag, BitMask는 고전적인 방법(…)이라고 한다. 굳이 쓰지 말고, 내부적으로 bit flag를 사용하는 EnumSet
을 사용하자. 그럼, 위의 예시 코드를 바꿔 봐야지!
1 | public class HobbyManager { |
다음과 같이 사용하면 된다.
1 | // 초기 취미 Setting |
이번 개발 건에서는 '보유 여부 확인’만 하면 되어서 이정도 코드로 충분했으나, 만일 ‘제거’ 가 필요하다면 EnumSet
에서 지원하는 remove
를 HobbyManager
에 적용하여 확장하면 된다. Easy!
참고
- 10진수를 2진수 코드화
- 비트마스크(BitMask)는 무엇인가?
- Java 데이터 형식 범위
- Java enum의 사용
- Enum, 자바의 열거타입을 알아보자
- EnumSet in Java