FindWithinHorizon не соответствует

Я хотел бы найти количество экземпляров шаблона "$$$$" в текстовом файле. Следующий метод работает с некоторыми файлами, но не со всеми файлами. Например, он не работает со следующим файлом (http://www.hmdb.ca/downloads/structures.zip - это заархивированный текстовый файл с расширением .sdf) Не могу понять почему? Я также пытался избежать пробелов. Неудачно. Он возвращает 11, когда имеется более 35000 шаблонов "$$$$". Обратите внимание, скорость имеет решающее значение. Поэтому я не могу использовать более медленные методы.

public static void countMoleculesInSDF(String fileName)
{
    int tot = 0;
    Scanner scan = null;
    Pattern pat = Pattern.compile("\\$\\$\\$\\$");

    try {  
        File file = new File(fileName);
        scan = new Scanner(file);
        long start = System.nanoTime();
        while (scan.findWithinHorizon(pat, 0) != null) {
            tot++;
        }
        long dur = (System.nanoTime() - start) / 1000000;
        System.out.println("Results found: " + tot + " in " + dur + " msecs");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        scan.close();
}
}

person lochi    schedule 08.10.2013    source источник


Ответы (2)


Для связанного файла и вашего кода, как вы его разместили, у меня постоянно было 218 совпадений. Это, конечно, неверно: при проверке с помощью функции подсчета notepad++ файл должен содержать 41498 совпадений. Так что, должно быть, что-то не так с Scanner (я подумал) и начал отладку внутри него, когда было выполнено последнее совпадение, то есть когда Сканер сказал, что совпадений больше не осталось. При этом я наткнулся на исключение в его частном методе readInput(), который не вызывается напрямую, а вместо этого сохраняется в переменной локали.

try {
    n = source.read(buf);
} catch (IOException ioe) {
    lastException = ioe;
    n = -1;
}

Это исключение можно получить с помощью метода Scanner#ioException():

IOException ioException = scanner.ioException();
if (ioException != null) {
    ioException.printStackTrace();
}

Печать этого исключения показала, что некоторые входные данные не могут быть декодированы

java.nio.charset.UnmappableCharacterException: Input length = 1
    at java.nio.charset.CoderResult.throwException(CoderResult.java:278)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:338)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
    at java.io.Reader.read(Reader.java:100)
    at java.util.Scanner.readInput(Scanner.java:849)

Поэтому я просто попытался передать набор символов конструктору сканера:

scan = new Scanner(file, "utf-8");

И это заставило его работать!

Results found: 41498 in 2431 msecs

Таким образом, проблема заключалась в том, что Сканер использовал кодировку платформы, которая не подходила для полного декодирования файла, который у вас есть.

Мораль истории:

  1. Всегда явно передавайте кодировку при работе с текстом.
  2. Проверяйте наличие IOException при работе с Scanner.

PS: несколько удобных способов процитировать строку для использования в качестве регулярного выражения

Pattern pat = Pattern.compile("\\Q$$$$\\E");

or

Pattern pat = Pattern.compile(Pattern.quote("$$$$"));
person A4L    schedule 08.10.2013

Вот что я в итоге сделал... (до того, как вы опубликовали свой ответ). Этот метод кажется более быстрым, чем сканер. Какую реализацию вы бы предложили? Сканер или карта памяти? Будет ли отображение памяти неудачным для больших файлов? Не уверен..

private static final Charset CHARSET = Charset.forName("ISO-8859-15");
private static final CharsetDecoder DECODER = CHARSET.newDecoder();

public static int getNoOfMoleculesInSDF(String fileName) 
    {   
    int total=0;
    try
    {    
    Pattern endOfMoleculePattern = Pattern.compile("\\$\\$\\$\\$");
    FileInputStream fis = new FileInputStream(fileName);
    FileChannel fc = fis.getChannel();
    int fileSize = (int) fc.size();
    MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
    CharBuffer cb = DECODER.decode(mbb);
    Matcher matcher = endOfMoleculePattern.matcher(cb);
    while (matcher.find()) {
      total++;
    }
    }
    catch(Exception e)
    {
        LOGGER.error("An error occured while counting molecules in the SD file");
    }
    return total;
    }
person lochi    schedule 08.10.2013
comment
Этот метод тоже выглядит хорошо, но, к сожалению, он не работает для больших файлов, таких как тот, который вы связали (~ 250 МБ). Он вылетает с OutOfMemoryError: Java heap space, потому что DECODER.decode(mbb) пытается выделить буфер символов размером с сами файлы, чего не избежать даже при увеличении пространства кучи jvm с опцией -Xmx. Раньше я пробовал использовать буферизованный ридер и применять шаблон к каждой строке, он работал нормально, но занимал в 4 раза больше времени, чем сканер. Я думаю, что метод Scanner — лучший выбор, чтобы избежать OOME во время выполнения. Буфер сканера всего 1024! - person A4L; 09.10.2013
comment
См. ответ на этот вопрос как почему OOME все еще может произойти, несмотря на настройку с -Xmx - person A4L; 09.10.2013
comment
Этот метод работал с -Xms2000m. Это было намного быстрее - 600 мс по сравнению с 1900 мс для того же файла. Однако ограниченная память может стать проблемой. пойду со сканером.. - person lochi; 09.10.2013
comment
Ого, сколько памяти! Я поднялся только до -Xmx1G без везения, после этого моя система даже не смогла выделить память для самого jvm. Действительно, вы не можете полагаться на тот факт, что у вас всегда может быть так много памяти для вашего приложения, даже если файл еще больше, вам в конечном итоге потребуется больше памяти! Вы все еще можете ускорить работу сканера, увеличив размер его буфера, к сожалению, этот член final и private, но с отражением почти все возможно ;-) - person A4L; 09.10.2013