Jun 3, 2019 - 이펙티브 자바_01

«««< HEAD 아키텍쳐 : 프로젝트에 참여하는 개발자들이 설계에 대해 공유하는 이해를 반영하는 주관적인 개념

  • 중요한 것
  • 변경하기 어려운 것
  • 가급적이면, 일찍, 올바르게 바꾸고 싶은 것

레이어 아키텍쳐 : 유사한 관심사들을 레이어로 나눠서 수직적으로 배열 (간단하게 케익 같은 구조.)

  • 유사한 얘들은 한 레이어에 만들어두었으니 그 레이어를 빼서 다른 곳으로 바꾸는 것이 가능.
  • 프리젠테이션 레이어 : 화면 조작, 사용자의 입력을 처리하기 위한 관심사를 처리함.
  • 도메인 레이어 : 도메인적인 내용을 묶어서 처리
  • 데이타소스 레이어 : 데이터를 관리하기 위한 부분들을 전부 묶어서 처리

                    Pattern urlPattern = Pattern.compile("^(http[s]?):\\/\\/([^:\\/\\s]+)(:([^\\/]*))?((\\/[^\\s/\\/]+)*)?\\/([^#\\s\\?]*)(\\?([^#\\s]*))?(#(\\w*))?$");
                    Matcher mc = urlPattern.matcher(this.domain);
                    if (mc.matches()) {
                        this.domain = mc.group(2);
                    }
    

http 를 찾는 로직입니다.
urlPattern 이라는 String 을 정규표현식에서 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서는 반복해 사용하기에 적합하지 않다고 써져있습니다.
이 메서드가 내붕네서 만드는 정규표현식용 Pattern 인스턴스는, 한번 쓰고 버려져 곧바로 가비지 컬렉션 대상이 된다고 합니다.

Pattern은 입력받은 정규표현식에 해당하는 유한 상태머신(finite state machine)을 만듥기 때문에 인스턴스 생성 비용이 높다고 하여, 성능을 개선하기 위해서 정규표현식을 표현하는 (불변인) Pattern 인스턴스를 초기화(정적 초기화)과정에서 직접 생성해 캐싱해두고, 나중에 static 메소드로 반복하여 이 인스턴스를 재사용하는 방법을 가이드 주었습니다.

private static final Pattern URL_PATTERN = Pattern.compile("^(http[s]?):\\/\\/([^:\\/\\s]+)(:([^\\/]*))?((\\/[^\\s/\\/]+)*)?\\/([^#\\s\\?]*)(\\?([^#\\s]*))?(#(\\w*))?$");

public static String isUrlPattern(String url) {
  Matcher mc = URL_PATTERN.matcher(url);
  if (mc.matches()) {
    return mc.group(2);
  }
  try {
    String[] temp = url.split("\\?");
    return temp[0];
  } catch (Exception e) {
    return url;
  }
}

이렇게 처리했을 때, 빈번히 호출되는 상황에서 성능을 상당히 끌어올릴 수 있다고 합니다. 테스트를 위해 임시 테스트 클래스를 생성했습니다.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) {
        long start = System.currentTimeMillis(); //시작하는 시점 계산
        for (int i = 0; i < 100000; i++) {
            PastternTest.isUrlPattern("www.daum.net");
        }
        long end = System.currentTimeMillis(); //프로그램이 끝나는 시점 계산
        System.out.println("실행 시간 : " + (end - start) / 1000.0 + "초"); //실행 시간 계산 및 출력


        start = System.currentTimeMillis(); //시작하는 시점 계산
        for (int i = 0; i < 100000; i++) {
            PastternTest.isUrlPattern2("www.daum.net");
        }
        end = System.currentTimeMillis(); //프로그램이 끝나는 시점 계산
        System.out.println("실행 시간 : " + (end - start) / 1000.0 + "초"); //실행 시간 계산 및 출력
    }


    private static class PastternTest {
        private static final Pattern URL_PATTERN = Pattern.compile("^(http[s]?):\\/\\/([^:\\/\\s]+)(:([^\\/]*))?((\\/[^\\s/\\/]+)*)?\\/([^#\\s\\?]*)(\\?([^#\\s]*))?(#(\\w*))?$");

        private static String isUrlPattern(String url) {
            Matcher mc = URL_PATTERN.matcher(url);
            if (mc.matches()) {
                return mc.group(2);
            }
            try {
                String[] temp = url.split("\\?");
                return temp[0];
            } catch (Exception e) {
                return url;
            }
        }

        private static String isUrlPattern2(String url) {
            Pattern URL_PATTERN2 = Pattern.compile("^(http[s]?):\\/\\/([^:\\/\\s]+)(:([^\\/]*))?((\\/[^\\s/\\/]+)*)?\\/([^#\\s\\?]*)(\\?([^#\\s]*))?(#(\\w*))?$");
            Matcher mc = URL_PATTERN2.matcher(url);
            if (mc.matches()) {
                return mc.group(2);
            }
            try {
                String[] temp = url.split("\\?");
                return temp[0];
            } catch (Exception e) {
                return url;
            }
        }
    }
}

100000 번 돌렸을 때 결과는 다음과 같았습니다.

실행 시간 : 0.029초
실행 시간 : 0.308초

대략 9~10배 정도의 성능향상이 생길 것으로 기대됩니다.

Jun 3, 2019 - Base64 인코딩, 디코딩하기

Base64는 64문자의 영숫자를 이용하여 멀티 바이트 문자열이나 이진 데이터를 다루기 위한 인코딩 방식.
Simple 하게 java.util.Base64 에서 제공하는 기능을 사용.
인코딩에는 java.util.Base64.Encoded#encode() 메소드, 디코딩에는 java.util.Base64.Decode#decode() 메소드를 사용.
Base64 인코딩, 디코딩을 자주하는데, 자체 라이브러리가 아닐 경우 byte 를 다시 String 으로 변환해서 테스트하는 게 귀찮아서 history로 남깁니다.

Base64
Base64.Encoder
Base64.Decoder

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.nio.charset.StandardCharsets;


public class Base64EncodeDecodeSample {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String target = "암호화할내용을적어주세요";      
        byte[] targetBytes = target.getBytes(StandardCharsets.UTF_8);

        // Base64 인코딩 ///////////////////////////////////////////////////
        Encoder encoder = Base64.getEncoder();

        // Encoder#encode(byte[] src) :: 바이트배열로 반환
        byte[] encodedBytes = encoder.encode(targetBytes);
        System.out.println(new String(encodedBytes));

        // Encoder#encodeToString(byte[] src) :: 문자열로 반환
        String encodedString = encoder.encodeToString(targetBytes);
        System.out.println(encodedString);

        // Base64 디코딩 ///////////////////////////////////////////////////
        Decoder decoder = Base64.getDecoder();

        // Decoder#decode(bytes[] src)
        byte[] decodedBytes1 = decoder.decode(encodedBytes);
        // Decoder#decode(String src)
        byte[] decodedBytes2 = decoder.decode(encodedString);

        // 디코딩한 문자열을 표시
        String decodedString = new String(decodedBytes1, StandardCharsets.UTF_8);
        System.out.println(decodedString);

        System.out.println(new String(decodedBytes2, StandardCharsets.UTF_8));
    }

}

참조


May 16, 2019 - IP 대역폭 필터 기능

2012년, IP를 수치로 변환, 수치를 IP로 변환하는 법에 대해 블로그를 한 경험이 있습니다.
특정 아이피가 왔을 때 해당 대역이 어떤 지역에 속한지 분석하여 필터링 하는 로직이었습니다. ip 대역폭을 계산해야하기 때문에 long 형으로 변환하여,

public Long ipToInt(String ipAddr) {
		String[] ipAddrArray = ipAddr.split("\\.");

		long num = 0;
		for (int i=0;i<ipAddrArray.length;i++) {
			int power = 3-i;
			/*
			 * i의 값으 범위는 0~255 사이이며, 그 값을 256으로 나눈 나머지 값에
			 * 256을 power의 값인
			 * 1~9 사이는 2,
			 * 10~99 사이는 1,
			 * 100 이상은 0 의 값에 누승하여 num 값에 더함
			 */
			num += ((Integer.parseInt(ipAddrArray[i])%256 * Math.pow(256,power)));
		}
		return num;
	}

해당 대역폭에 속한 IP인지를 판단하는 로직이었습니다.
추후, 해당 로직이 조금더 간편하게 변경되었던 걸로 기억하는데,

/**
 * Returns the long version of an IP address given an InetAddress object.
 *
 * @param address the InetAddress.
 * @return the long form of the IP address.
 */
private static long bytesToLong(byte [] address) {
    long ipnum = 0;
    for (int i = 0; i < 4; ++i) {
        long y = address[i];
        if (y < 0) {
            y+= 256;
        }
        ipnum += y << ((3-i)*8);
    }
    return ipnum;
}

회사에서는 InetAddress 클래스를 별도로 구성하여, 이런 형태로 변경되었던 것으로 기억합니다.

참고주소로 보면, 조금더 디테일한 소스를 볼 수 있습니다.

public static final long[] IP_RANGE = { 16777216L, 65536L, 256L };
/**
* long형태의 아이피 정보를 "."(127.0.0.1과 같은)으로 구분된 아이피 정보로 변환하여
* 반환한다.
* @param address
* @return
*/
public static String longToIp(long address) {
String ip = "";
for (int i = 0; i < 4; i++) {
if (i < 3) {
ip += (address / IP_RANGE[i]) % 256 + ".";
} else {
ip += address % 256;
}
}
return ip;
}
/**
* 특정 IP를 "."을 기준으로 파싱한 String[]으로 받아 long IP로 반환한다.
* @param address
* @return
*/
public static long ipToLong(String[] address) {
if (address == null || address.length < 4) {
return 0L;
}
long ip = 0L;
for (int i = 0; i < 4; i++) {
if (HoverStringUtils.isDigit(address[i])) {
if (i < 3) {
ip += IP_RANGE[i] * HoverStringUtils.toDigit(address[i]);
} else {
ip += HoverStringUtils.toDigit(address[i]);
}
} else {
return 0L;
}
}
return ip;
}
/**
* 특정 아이피(IP)를 long으로 변환하여 반환한다.
* @param ip
* @return
*/
public static long ipToLong(String ip) {
return ipToLong(getBytesByInetAddress(ip));
}
/**
* 특정 아이피의 값을 byte[]받아 long으로 반환한다.
* @param address
* @return
*/
public static long ipToLong(byte[] address) {
if (address == null || address.length < 4) {
return 0L;
}
long ip = 0L;
for (int i = 0; i < 4; ++i) {
long y = address[i];
if (y < 0) {
y += 256;
}
ip += y << ((3 - i) * 8);
}
return ip;
}
/**
* 특정 아이피를 byte[]로 변환하여 반환한다.
* @param ip
* @return
*/
public static byte[] getBytesByInetAddress(String ip) {
InetAddress addr = null;
try {
addr = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
return new byte[0];
}
return addr.getAddress();
}

급한 기능을 추가할 때, 옛날에 사용했던 내용들을 정리해두면, 역시 편하다는 것을 다시한번 느꼈습니다.