JAVA 정규표현식 관련 질문

2013-08-24 00:03
HTMLUtil 이란 클래스의 메소드입니다.
	public static String setAutoLinkHashTag(String url, String content){
		List<String> list = new ArrayList<String>();
		String pattern = "(?:^|\\s|[\\p{Punct}&&[^/]])(#[\\p{L}0-9-_]+)";


		String strRet = content;
		
		try{
			Pattern compiledPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
			Matcher matcher = compiledPattern.matcher(content);
			while(matcher.find()) {
				   list.add(matcher.group().trim());
			}
			Collections.sort(list);
			if(list.size() > 0){
				for(int k = list.size() -1; k >= 0; k--){
					strRet = strRet.replaceAll(list.get(k), "<a href='" + url + "?q%5B%5D=" + URLEncoder.encode(list.get(k), "UTF-8") + "'>" + list.get(k) + "</a>"); 
				}
			}
		}catch(Exception ex){
			ex.printStackTrace();
		}


		return strRet;		
	}	


위와 같은 메소드를 다음과 같이 이용합니다.



String content = "안녕 #구글 #써니베일 #ㅋㅋ ㅎㅎ #hello"; System.out.println(HtmlUtil.setAutoLinkHashTag("[http://localhost/search/plaza",](http://localhost/search/plaza",) content));

위와 같이 실행하면 자동으로 hashTag에 링크가 걸리도록 한 정규표현식입니다. 그런데 전 사실... 위의 코드가 맘에 안듭니다.

전 이렇게 메소드를 만들고 싶었습니다.

       public static String setAutoLinkHashTag(String url, String content){
		List<String> list = new ArrayList<String>();
		String pattern = "(?:^|\\s|[\\p{Punct}&&[^/]])(#[\\p{L}0-9-_]+)";


		String strRet = content;
		
		try{
			Pattern compiledPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
			Matcher matcher = compiledPattern.matcher(content);
                        matcher.replaceAll("<a href='" + url + "?q%5B%5D=$1'>$1</a>"); 
		}catch(Exception ex){
			ex.printStackTrace();
		}


		return strRet;		
	}	

위와 같이 하고 싶었죠. 그런데... 정규표현식으로 발견한 # 문자를.... URLEncode.encode로 변환시켜야 하는데... $1에 해당하는 부분을 변환시킬 방법을 모르겠습니다. OTL

결국.. 일일이 Hash코드에 대한 목록을 구하고 Hash코드의 문자열이 긴것부터 짧은 순서로.. URLEncode.encode로 적용한 후 링크를 거는방법을 사용하고야 말았네요.

혹시 좋은 방법 있으신분 있으신가요?

3개의 의견 from SLiPP

2013-08-24 07:41

다음과 같이 변경해 봤습니다. 저도 이런 api가 있는지 처음 알았네요. 덕분에 저 또한 배울 수 있었습니다.

public static String replaceHashTagToUrl(String url, String content) {
    String pattern = "(?:^|\\s|[\\p{Punct}&&[^/]])(#[\\p{L}0-9-_]+)";


    StringBuffer sb = new StringBuffer();
    try {
        Pattern compiledPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
        Matcher matcher = compiledPattern.matcher(content);
        
        while (matcher.find()) {
            String hashTag = matcher.group(1).trim();
            matcher.appendReplacement(sb, createTargetUrl(url, hashTag));
        }
    } catch (Exception e) {


    }
    return sb.toString();
}


private static String createTargetUrl(String url, String hashTag)
        throws UnsupportedEncodingException {
    return " <a href='" + url + "?q%5B%5D="
            + URLEncoder.encode(hashTag, "UTF-8") + "'>" + hashTag + "</a>";
}

위와 같이 구현했더니 코드도 많이 깔끔해 졌네요. 성능은 얼마나 개선되었는지 테스트 해봤습니다.

@Test
public void test() {
    String content = "안녕 #구글 #써니베일 #ㅋㅋ ㅎㅎ #hello";
    long start = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        ContentsConverter.setAutoLinkHashTag(
                "[http://localhost/search/plaza",](http://localhost/search/plaza",) content);
    }
    long end = System.currentTimeMillis();
    System.out.println("execution time1 : " + (end - start));


    start = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
        ContentsConverter.replaceHashTagToUrl(
                "[http://localhost/search/plaza",](http://localhost/search/plaza",) content);
    }
    end = System.currentTimeMillis();
    System.out.println("execution time2 : " + (end - start));
}

성능 테스트 코드를 위와 같이 구현한 후 결과를 확인해 보니 다음과 같네요. 10번 정도 실행해 봤어요.

execution time1 : 451 execution time2 : 115

대부분 위와 같은 결과가 나옵니다. 거의 4배 정도 성능이 빨라졌네요. 아마도 본문의 길이가 짧아서 큰 차이가 아닐 수 있다고 생각할 수 있는데 본문의 길이가 길어지면 길어질 수록, 해시 태그의 수가 많을 수록 성능의 차이는 많이 나니라 생각합니다. 그리고 코드를 보면 생성되는 객체와 GC되는 객체의 수도 많이 줄어드리라 생각합니다. 또한 코드 줄 수가 줄어 듦으로 인해 소스 코드의 가독성 또한 높아졌다고 생각합니다. 답변이 도움이 되었기를 바랍니다.

2013-08-24 07:56

위 코드를 다른 곳에서도 사용할 수 있도록 유연성을 부여해 봤습니다. 아무래도 한 곳에서만 사용하고 버리기에는 아까운지라.

public interface Replacable {
    String regexp();
    
    String replace(String findToken);
}

위와 같이 인터페이스를 하나 만든 후에 앞에서 구현한 코드를 다음과 같이 변경합니다.

public static String replaceHashTagToUrl(String contents, Replacable replacable) {
    StringBuffer sb = new StringBuffer();
    try {
        Pattern compiledPattern = Pattern.compile( replacable.regexp(), Pattern.CASE_INSENSITIVE);
        Matcher matcher = compiledPattern.matcher(contents);
        
        while (matcher.find()) {
            String replacedToken = replacable.replace(matcher.group(1).trim());
            matcher.appendReplacement(sb, replacedToken);
        }
    } catch (Exception e) {


    }
    return sb.toString();
}

이 api를 사용하는 코드는 다음과 같습니다.

@Test
public void replaceHashTag2() throws Exception {
    String content = "안녕 #구글 #써니베일 #ㅋㅋ ㅎㅎ #hello";
    Replacable replacable = new Replacable() {
        public String replace(String findToken) {
            try {
                return " <a href='[http://localhost/search/plaza?q%5B%5D="](http://localhost/search/plaza?q%5B%5D=")
                        + URLEncoder.encode(findToken, "UTF-8") + "'>"
                        + findToken + "</a>";
            } catch (UnsupportedEncodingException e) {
                throw new IllegalArgumentException();
            }
        }


        public String regexp() {
            return "(?:^|\\s|[\\p{Punct}&&[^/]])(#[\\p{L}0-9-_]+)";
        }
    };


    System.out.println(ContentsConverter.replaceHashTagToUrl(content,
            replacable));
}

성능 테스트도 해봤는데 차이는 없네요. 위와 같이 api를 구현할 경우 이 api를 사용하는 곳에서 정규 표현식과 변경할 내용을 결정할 수 있기 때문에 좀 더 유연한 방식으로 프로그래밍을 할 수 있을 것으로 생각됩니다.

자바에도 람다식이나 클로저를 지원하면 쉽게 해결할 수 있는 부분인데 현재로서는 이 정도까지 밖에 생각나지 않네요.

2013-08-24 08:30

오! 감사합니다.

 matcher.appendReplacement(sb, createTargetUrl(url, hashTag));

위의 메소드를 처음 보았네요. 정규표현식을 자주 사용하곤 하는데.....

이걸 지금에서야 알다니!!!

분명히.. 이와 같은 것이 있을꺼야... 하고 어제밤에 찾을때는 그리안보이더니!!!

이런게 있을만 한데.... 하고 찾아봤는데 엉뚱한 부분에서 찾은듯 합니다.

String str = compiledPattern.matcher(content);
                        matcher.replaceAll("<a href='" + url + "?q%5B%5D=$1'>$1</a>", 변환CallBackMethod1 , 변환CallBackMethod2 ... ); 

전 위와 같은 형식으로 있을줄 알고..... 내 스스로가 만든 함정에서 벗어나지 못한듯 합니다.

좋은거 배웠네요.

다시한번 감사합니다.

꼬랑지말 : 언제 홍대한번 오세요~~ 이런건 밥으로..감사를 표시해야할듯. ^^

의견 추가하기