Java에서 구분 된 문자열을 분할하는 가장 빠른 방법


10

구분 된 문자열에서 다중 열 정렬 기능을 제공하는 비교기를 작성 중입니다. 현재 원시 문자열을 토큰으로 분할하기 위해 선호하는 선택으로 String 클래스의 split 메소드를 사용하고 있습니다.

이것이 원시 문자열을 문자열 배열로 변환하는 가장 좋은 방법입니까? 나는 수백만 행을 정렬하므로 접근 방식이 중요하다고 생각합니다.

잘 실행되는 것처럼 보이며 매우 쉽지만 Java에 더 빠른 방법이 있는지 확실하지 않습니다.

내 비교기에서 정렬이 작동하는 방법은 다음과 같습니다.

public int compare(String a, String b) {

    String[] aValues = a.split(_delimiter, _columnComparators.length);
    String[] bValues = b.split(_delimiter, _columnComparators.length);
    int result = 0;

    for( int index : _sortColumnIndices ) {
        result = _columnComparators[index].compare(aValues[index], bValues[index]);
        if(result != 0){
            break;
        }
    }
    return result;
}

다양한 접근 방식을 벤치마킹 한 후 믿거 나 말거나 최신 버전의 Java를 사용하여 split 방법이 가장 빠릅니다. 완성 된 비교기를 여기에서 다운로드 할 수 있습니다 : https://sourceforge.net/projects/multicolumnrowcomparator/


5
이 질문에 대한 답변의 본질은 jvm의 구현에 달려 있음을 지적합니다. 문자열의 동작 (OracleJDK가 아닌 OpenJDK에서는 공통 백업 배열을 공유 함)이 다릅니다. 이 차이는 가비지 수집 및 메모리 누수와 함께 문자열 분리 및 하위 문자열 생성에 큰 영향을 줄 수 있습니다. 이 배열은 얼마나 큽니까? 어떻게 지내세요? 실제 Java 문자열이 아닌 새로운 Stringish 유형에 대한 답변을 고려 하시겠습니까?

1
특히 패키지 전용 String 생성자를 호출하는 StringTokenizer nextToken 을 살펴보십시오 . 이것을 Java 1.7.0_06

배열 크기는 열 수에 따라 달라 지므로 가변적입니다. 이 다중 열 비교기는 다음과 같은 매개 변수로 전달됩니다. ExternalSort.mergeSortedFiles (fileList, new File ( "BigFile.csv"), _comparator, Charset.defaultCharset (), false); 외부 정렬 루틴은 실제로 분할을 수행하고 정렬 열을 기준으로 정렬 비교기이며, 전체 행 문자열을 정렬합니다
콘스탄틴

나는 lucene의 토크 나이저를 살펴볼 것입니다. 루씬은 강력한 텍스트 분석 라이브러리로 사용할 수도 간단하고 복잡한 모두 작업 수행
더그 T.

Apache Commons Lang을 고려하십시오 StringUtils.split[PreserveAllTokens](text, delimiter).
Monica Reinstate Monica

답변:


19

이에 대한 빠르고 더러운 벤치 마크 테스트를 작성했습니다. 7 가지 방법을 비교하는데,이 중 일부는 분리되는 데이터에 대한 특정 지식이 필요합니다.

기본 범용 분할의 경우 Guava Splitter는 String # split ()보다 3.5 배 빠릅니다.이를 사용하는 것이 좋습니다. Stringtokenizer는 그보다 약간 빠르며 indexOf로 자신을 분할하는 것이 다시 두 배 빠릅니다.

코드 및 자세한 내용은 http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/를 참조 하십시오.


JDK가 무엇을 사용하고 있는지 궁금합니다 ... 1.6이라면 1.6에서 결과를 요약하는 데 가장 관심이 있습니다.

1
나는 1.6이라고 생각했다. 1.7에서 실행하려면 코드가 JUnit 테스트로 존재합니다. 참고 String.split은 정규 표현식 일치를 수행하며, 정의 된 단일 문자에서 분할하는 것보다 항상 느립니다.
tom

1
그러나 1.6의 경우 StringTokenizer (및 유사한) 코드는 동일한 보조 배열을 사용하여 새 문자열을 O (1) 생성하는 String.substring ()을 호출합니다. 1.7에서 O (n) 대신 백업 배열의 필요한 부분을 복사하도록 변경되었습니다. 이로 인해 결과에 현저한 영향을 줄 수 있으므로 split과 StringTokenizer의 차이를 줄일 수 있습니다 (이전에 하위 문자열을 사용한 모든 것을 느리게 함).

1
확실합니다. 문제는 StringTokenizer가 작동하는 방식이 "새 문자열을 할당하려면 3 개의 정수를 할당하는 것"에서 "새 문자열을 생성하려면, 데이터의 배열 복사본을 수행하는 것"으로 바뀌어 그 부분의 속도가 변경됩니다. 다양한 접근 방식의 차이는 지금은 적을 수 있으며 Java 1.7로 후속 조치를 취하는 것이 흥미로울 것입니다.

1
그 기사에 감사드립니다! 매우 유용하며 다양한 접근법을 벤치마킹하는 데 사용됩니다.
Constantin

5

@Tom이 쓰는 것처럼 indexOf 형식의 접근 방식은보다 빠릅니다 String.split(). 후자는 정규식을 처리하고 추가 오버 헤드가 많기 때문입니다.

그러나 하나의 알고리즘 변경으로 인해 최고 속도가 향상 될 수 있습니다. 이 Comparator를 사용하여 ~ 100,000 개의 문자열을 정렬한다고 가정하고을 쓰지 마십시오 Comparator<String>. 정렬 과정에서 동일한 문자열이 여러 번 비교 될 수 있으므로 여러 번 나누는 등입니다.

모든 문자열 을 String [] s로 한 번 분할 Comparator<String[]>하고 String []을 정렬하십시오. 그런 다음 마지막에 모두 결합 할 수 있습니다.

또는 Map을 사용하여 String-> String []을 캐시하거나 그 반대로 할 수도 있습니다. 예를 들어, (sketchy) 또한, 당신은 속도를 위해 메모리를 거래하고 있습니다. 많은 RAM이 있기를 바랍니다.

HashMap<String, String[]> cache = new HashMap();

int compare(String s1, String s2) {
   String[] cached1 = cache.get(s1);
   if (cached1  == null) {
      cached1 = mySuperSplitter(s1):
      cache.put(s1, cached1);
   }
   String[] cached2 = cache.get(s2);
   if (cached2  == null) {
      cached2 = mySuperSplitter(s2):
      cache.put(s2, cached2);
   }

   return compareAsArrays(cached1, cached2);  // real comparison done here
}

이것은 좋은 지적입니다.
tom

여기에서 찾을 수있는 외부 정렬 코드를 수정해야합니다. code.google.com/p/externalsortinginjava
Constantin

1
아마도지도를 사용하는 것이 가장 쉬운 방법 일 것입니다. 편집을 참조하십시오.
user949300

이것이 외부 정렬 엔진 (사용 가능한 메모리에 들어갈 수있는 것보다 훨씬 많은 데이터를 처리하기 위해)의 일부라는 것을 감안할 때, 실제로 효율적인 "스플리터"(예 : 동일한 문자열을 반복적으로 분할하는 것은 낭비입니다) 원래 가능한
Constantin

ExternalSort 코드를 간단히 살펴보면 모든 sortAndSave()호출 이 끝날 때 (또는 시작할 때) 캐시를 지우면 거대한 캐시로 인해 메모리가 부족하지 않아야합니다. IMO 코드에는 이벤트 발생 또는 사용자가 무시할 수있는 보호되지 않은 메소드 호출과 같은 몇 가지 추가 후크가 있어야합니다. (또한 정적 메소드가 아니 어서이 작업을 수행 할 수 있는 것은 아닙니다. ) 작성자에게 문의하여 요청을 제출할 수 있습니다.
user949300

2

이 벤치 마크 에 따르면 StringTokenizer는 문자열을 분할하는 것이 빠르지 만 배열을 반환하지 않아 편리하지 않습니다.

수백만 행을 정렬 해야하는 경우 RDBMS를 사용하는 것이 좋습니다.


3
그것은 JDK 1.6 아래에있었습니다-문자열의 것들이 1.7에서 근본적으로 다릅니다 -java-performance.info/changes-to-string-java-1-7-0_06 참조 (특히 하위 문자열을 만드는 것은 더 이상 O (1)이 아니지만 오히려 O (n)). 이 링크는 1.6 Pattern.split에서 String.substring ()과는 다른 String 생성을 사용했다는 점에 주목합니다. StringTokenizer.nextToken () 및 액세스 할 수있는 패키지 개인 생성자를 따르려면 위의 주석에 링크 된 코드를 참조하십시오.

1

이것은 큰 (1GB +) 탭으로 구분 된 파일을 구문 분석하는 데 사용하는 방법입니다. 보다 오버 헤드가 적지 String.split()char분리 문자로 제한됩니다 . 누구든지 더 빠른 방법을 가지고 있다면 그것을보고 싶습니다. 이것은 또한 이상 할 수 CharSequenceCharSequence.subSequence,하지만 구현이 필요합니다 CharSequence.indexOf(char)(패키지 방법을 참조 String.indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)관심이있는 경우).

public static String[] split(final String line, final char delimiter)
{
    CharSequence[] temp = new CharSequence[(line.length() / 2) + 1];
    int wordCount = 0;
    int i = 0;
    int j = line.indexOf(delimiter, 0); // first substring

    while (j >= 0)
    {
        temp[wordCount++] = line.substring(i, j);
        i = j + 1;
        j = line.indexOf(delimiter, i); // rest of substrings
    }

    temp[wordCount++] = line.substring(i); // last substring

    String[] result = new String[wordCount];
    System.arraycopy(temp, 0, result, 0, wordCount);

    return result;
}

이 vs String.split ()을 벤치마킹 했습니까? 그렇다면 어떻게 비교합니까?
Jay Elston

@JayElston 900MB 파일에서 분할 시간이 7.7 초에서 6.2 초로 단축되어 약 20 % 빨라졌습니다. 여전히 부동 소수점 행렬 구문 분석에서 가장 느린 부분입니다. 나머지 시간의 대부분이 배열 할당이라고 생각합니다. 메소드에서 오프셋과 함께 토크 나이저 기반 접근법을 사용하여 행렬 할당을 줄일 수 있습니다.
vallismortis 1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.