Java Scanner vs String.split() vs StringTokenizer; который я должен использовать?

В настоящее время я использую split() для сканирования файла, где каждая строка имеет количество строк, разделенных символом '~'. Я где-то читал, что Scanner мог бы лучше работать с длинным файлом, по производительности, поэтому я подумал о его проверке.

Мой вопрос: должен ли я создать два экземпляра Scanner? То есть читать одну строку и другую, основанную на строке, чтобы получить токены для разделителя? Если я должен это сделать, я сомневаюсь, что я получу какое-либо преимущество от его использования. Может быть, я что-то упустил?

Ответ 1

Были ли некоторые метрики вокруг них в одной поточной модели, и вот результаты, которые я получил.

~~~~~~~~~~~~~~~~~~Time Metrics~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Tokenizer  |   String.Split()   |    while+SubString  |    Scanner    |    ScannerWithCompiledPattern    ~
~   4.0 ms   |      5.1 ms        |        1.2 ms       |     0.5 ms    |                0.1 ms            ~
~   4.4 ms   |      4.8 ms        |        1.1 ms       |     0.1 ms    |                0.1 ms            ~
~   3.5 ms   |      4.7 ms        |        1.2 ms       |     0.1 ms    |                0.1 ms            ~
~   3.5 ms   |      4.7 ms        |        1.1 ms       |     0.1 ms    |                0.1 ms            ~
~   3.5 ms   |      4.7 ms        |        1.1 ms       |     0.1 ms    |                0.1 ms            ~
____________________________________________________________________________________________________________

Выходит, что сканер дает лучшую производительность, теперь то же самое нужно оценивать в многопоточном режиме! Один из моих старших говорит, что токенизатор дает процессорный всплеск, а String.split этого не делает.

Ответ 2

Для линии обработки вы можете использовать сканер и для получения токенов из каждой строки, вы можете использовать split.

Scanner scanner = new Scanner(new File(loc));
try {
    while ( scanner.hasNextLine() ){
        String[] tokens = scanner.nextLine().split("~");
        // do the processing for tokens here
    }
}
finally {
    scanner.close();
}

Ответ 3

Вы можете использовать метод useDelimiter("~"), чтобы вы могли выполнять итерацию по токенам в каждой строке с помощью hasNext()/next(), продолжая использовать hasNextLine()/nextLine() для итерации по самим линиям.

EDIT: если вы собираетесь выполнить сравнение производительности, вы должны предварительно скомпилировать регулярное выражение, когда вы выполните тест split():

Pattern splitRegex = Pattern.compile("~");
while ((line = bufferedReader.readLine()) != null)
{
  String[] tokens = splitRegex.split(line);
  // etc.
}

Если вы используете String#split(String regex), регулярное выражение будет перекомпилировано каждый раз. (Сканер автоматически кэширует все регулярные выражения при первом их компиляции.) Если вы это сделаете, я бы не ожидал увидеть большую разницу в производительности.

Ответ 4

Я бы сказал, что split() является самым быстрым и, вероятно, достаточно хорошим для того, что вы делаете. Он менее гибкий, чем scanner. StringTokenizer устарел и доступен только для обратной совместимости, поэтому не используйте его.

EDIT: вы всегда можете проверить обе версии, чтобы узнать, какая из них быстрее. Мне любопытно, если scanner может быть быстрее, чем split(). Split может быть быстрее для заданного размера VS scanner, но я не могу быть уверен в этом.

Ответ 5

На самом деле вам не нужно регулярное выражение, потому что вы разделяете фиксированную строку. Apache StringUtils split разделяет на простые строки.

Для разделов большого объема, где разделение является узким местом, а не файлом IO, я обнаружил, что это будет в 10 раз быстрее, чем String.split(). Тем не менее, я не тестировал его против скомпилированного регулярного выражения.

В Guava также есть сплиттер, реализованный более OO-способом, но я обнаружил, что он значительно медленнее, чем StringUtils для больших разделов.