Java8 Base64 Encoder/Decoder
Java8에서 새로 추가된 기능중에 Base64 인코더와 디코더가 있습니다.(이제와서!?!?) 사실 여타의 프로그래밍 언어에는 대체적으로 언어 내부에 구현체가 존재하거나 사실상 표준의 라이브러리들이 존재해왔으나 Java만은 이상하게도 지금까지 제공하지 않았습니다.(Oracle/sun의 JDK에 포함된 구현체와 같이 특정 벤더에 종속적인 API는 여기서 언급하지 않겠습니다.) 따라서 Apache재단의 commons-codec라이브러리를 사용하는 것이 가장 일반적인 선택이 아니었나 싶습니다. 하지만 Java8부터 표준API가 제공되기 시작함으로서 외부 라이브러리에 의존하는 일도 이제 다 지난일이 될 듯 합니다. 자 그럼 Java8의 Base64 API에 대해서 알아보도록 합시다.
Java8 Base64 문자열 변환 소스코드
public static String fileToBase64String(final File target) throws IOException { Encoder encoder = Base64.getEncoder(); BufferedInputStream bis = null; ByteArrayOutputStream baos = null; try { bis = new BufferedInputStream(new FileInputStream(target)); baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int readSize = 0; while ((readSize = bis.read(buffer)) > -1) { baos.write(buffer, 0, readSize); } return encoder.encodeToString(baos.toByteArray()); } finally { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } }
Java8 Base64 문자열 to File 변환 소스코드
public static void base64StringToFile(final String encoded, final File output) throws IOException { Decoder decoder = Base64.getDecoder(); BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(output)); bos.write(decoder.decode(encoded)); } finally { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } }
위에서 소개한 소스코드는 인자로 받은 파일을 Base64인코딩 하고 인코딩된 문자열을 다시 디코딩하는코드입니다. 각각 소스코드 상단에 Base64클래스의 팩토리 메소드를 호출해서 인코더와 디코더를 취득한 것을 볼 수 있는데 이러한 팩토리 메소드에는 아래와 같이 세종류가 있습니다.
- RFC4648 Base64인코딩
Encoder encoder = Base64.getEncoder();
Decoder decoder = Base64.getDecoder();
- RFC4648 Base64URL인코딩
Encoder encoder = Base64.getUrlEncoder();
Decoder decoder = Base64.getUrlDecoder();
- RFC2045(Mime)
Encoder encoder = Base64.getMimeEncoder();
Decoder decoder = Base64.getMimeDecoder();
위처럼 세가지 타입의 팩토리 메소드가 있으며 기본적으로 패딩처리(3바이트 단위로 끊어지지 않는 경우 나머지 부분을 채움 '='문자로 채움)가 활성화 되있으며 마임타입 인코딩의 경우 한 라인의 문자수가 디폴트로 76문자에 개행문자로 CRLF로 지정되어있다. RFC표준에 충실하게 구현되어있는 느낌입니다. 물론 아래처럼 간단하게 다른 수치를 지정할 수 있다.
Encoder encoder = Base64.getEncoder().withoutPadding(); //패딩 실행 안함
Encoder encoder = Base64.getMimeEncoder(80, new byte[]{'\n'}); //라인당 문자수 80, 개행문자 LF
Java8 Base64 API와 commons-codec과의 차이점
사실 직전 포스팅에서 Apache commons-codec 라이브러리의 API에 대해서 알아보았었으므로 각각의 차이점에 대해서 언급하지 않을 수 없습니다. Java8의 경우는 RFC4648규약과, RFC2045규약을 각각 지원하고 있었고 commons-codec의 경우는 RFC2045만을 지원하고 있었습니다. 그러나 사실 RFC4648규약이 하위 규약을 표준화하는 성격의 규약인 관계로 인코더/디코더를 교차적으로 사용해도 특별히 문제되는 것 같지 않았습니다.
※위에서 소개한 파일을 Base64 인코딩하고 디코딩하는 소스로 Java8과 commons-codec의 API이용하여 교차적으로 인코딩 디코딩 테스트를 해본 결과 특별히 문제없는듯 했습니다. 물론 간단한 결과로 문제없다고 단정짓기는 힘드므로 실제 적용하실 경우엔 주의가 필요합니다.
위에서 소개한대로 Java8의 경우는 각각의 규약별로 별도의 인코더, 디코더 인스턴스를 제공하는 팩토리 메소드를 제공하고 있어서 동일한 메소드 호출로 처리할 수 있으나 commons-codec의 경우는 별로도 제공하고 있지 않습니다. 따라서 필요에따라 직접 관련 인자를 메소드에 제공해서 호출하거나 별도의 메소드를 호출해야 합니다.
Java8의 MimeEncoder에 대응하는 commons-codec의 API
byte[] org.apache.commons.codec.binary.Base64.encodeBase64Chunked(byte[] binaryData)
위의 메소드는 Java8의 MimeEncoder에 대응하는 메소드로서 위를 실행하여 문자열로 변환후에 출력하면 Java8과 동일하게 행별로 76문자 + CRLF로이루어진 문자열이 출력됩니다. 다만 미묘하게 틀린점 하나는 가장 마지막 라인에 개행문자가 한번 더 출력된다는 점입니다.
Java8의 UrlEncoder에 대응하는 commons-codec의 API
String org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString(byte[] binaryData)
위의 메소드가 마찬가지로 Java8의 UrlEncoder에 대응하는 메소드입니다. 역시나 약간의 차이가 있는데 commons-codec의 구현체는 padding이 없으므로 동일한 결과를 원한 경우는 위에서 소개한 withoutpadding메소드를 통해 인코드를 취득해야 합니다.
마지막으로 성능면에서 간단한 테스트 결과 Java8의 구현체가 commons-codec의 구현체에 비해 약 3-4배정도 차이가 났습니다만 어찌보면 당연한 결과일 듯 합니다. 표준API로 포함되어있으므로 컴파일러 최적화나 이런 부분에 있어서 어느정도 유리한 위치에 서있기 때문입니다.
지금까지 Java8에 추가된 Base64관련 표준 API에 대해서 살펴보았습니다. 표준API인 만큼 하위 호환성을 고려해야하는 상황이 아닌 경우에는 선택에 주저할 필요가 없을 듯 합니다. 물론 국내 환경상 Java8이 주류가 되기까지는 어느정도 시간이 필요하겠지만 말입니다.