Я строю процесс, который извлекает данные из 6 файлов в стиле csv и двух плохо выложенных .txt отчетов и создает выходные CSV-файлы, и я полностью осознаю, что будет непростой поиск во всех этих пробелах тысяч раз, но я никогда не ожидал, что преобразование около 50 000 записей займет 12 часов.
Выдержка из моего кода соответствия вручную (я знаю, это ужасно, что я использую списки таких токенов, но это было лучшее, что я мог придумать):
public static String lookup(Pattern tokenBefore,
List<String> tokensAfter)
{
String result = null;
while(_match(tokenBefore)) { // block until all input is read
if(id.hasNext())
{
result = id.next(); // capture the next token that matches
if(_matchImmediate(tokensAfter)) // try to match tokensAfter to this result
return result;
} else
return null; // end of file; no match
}
return null; // no matches
}
private static boolean _match(List<String> tokens)
{
return _match(tokens, true);
}
private static boolean _match(Pattern token)
{
if(token != null)
{
return (id.findWithinHorizon(token, 0) != null);
} else {
return false;
}
}
private static boolean _match(List<String> tokens, boolean block)
{
if(tokens != null && !tokens.isEmpty()) {
if(id.findWithinHorizon(tokens.get(0), 0) == null)
return false;
for(int i = 1; i <= tokens.size(); i++)
{
if (i == tokens.size()) { // matches all tokens
return true;
} else if(id.hasNext() && !id.next().matches(tokens.get(i))) {
break; // break to blocking behaviour
}
}
} else {
return true; // empty list always matches
}
if(block)
return _match(tokens); // loop until we find something or nothing
else
return false; // return after just one attempted match
}
private static boolean _matchImmediate(List<String> tokens)
{
if(tokens != null) {
for(int i = 0; i <= tokens.size(); i++)
{
if (i == tokens.size()) { // matches all tokens
return true;
} else if(!id.hasNext() || !id.next().matches(tokens.get(i))) {
return false; // doesn't match, or end of file
}
}
return false; // we have some serious problems if this ever gets called
} else {
return true; // empty list always matches
}
}
Интересно, как бы я работал в эффективном поиске строк (Бойер-Мур или аналогичный). Мой сканер id
сканирует java.util.String
, полагая, что буферизация его в памяти уменьшит число операций ввода-вывода, поскольку поиск здесь выполняется тысячи раз для относительно небольшого файла. Увеличение производительности по сравнению со сканированием BufferedReader (FileReader (File)), вероятно, было менее 1%, процесс по-прежнему занимает ДЛИННОЕ время.
Я также отслеживал выполнение, и медленность моего общего процесса преобразования определенно находится между первым и последним, как в методе поиска. На самом деле, настолько, что я запустил процесс ярлыка для подсчета количества вхождений различных идентификаторов в файлах в стиле .csv (я использую 2 метода поиска, это только один из них), и процесс завершил индексацию примерно 4 разных идентификаторы для 50 000 записей менее чем за минуту. По сравнению с 12 часами, это мгновенно.
Некоторые заметки (обновлено 06.06.2010):
- Мне все еще нужно поведение сопоставления с образцом для tokensBefore.
- Все идентификационные номера, которые мне нужны, не обязательно начинаются с фиксированной позиции в строке, но гарантируется, что после токена ID будет имя соответствующего объекта.
- В идеале я хотел бы вернуть строку, а не начальную позицию результата как int или что-то еще.
Поможет все, что поможет мне, даже если оно сэкономит 1 мс на поиск, поэтому все входные данные приветствуются. Thankyou!
Сценарий использования 1: у меня есть список объектов в файле A, у которых в системе старого стиля есть идентификационный номер, которого нет в файле A. Однако, это ВОЗМОЖНО в другом файле в стиле csv (файл B ) или, возможно, все еще в отчете .txt (файл C), каждый из которых также содержит кучу другой информации, которая здесь бесполезна, и поэтому в файле B необходимо выполнить поиск полного имени объекта (1 токен, поскольку он будет находиться в пределах второй столбец любой данной строки), а затем первый столбец должен быть идентификатором. Если это не сработает, тогда мы должны разделить токен поиска по пробелам на отдельные токены, прежде чем выполнять поиск файла C по этим токенам.
Обобщенный код:
String field;
for (/* each record in file A */)
{
/* construct the rest of this object from file A info */
// now to find the ID, if we can
List<String> objectName = new ArrayList<String>(1);
objectName.add(Pattern.quote(thisObject.fullName));
field = lookup(objectSearchToken, objectName); // search file B
if(field == null) // not found in file B
{
lookupReset(false); // initialise scanner to check file C
objectName.clear(); // not using the full name
String[] tokens = thisObject.fullName.split(id.delimiter().pattern());
for(String s : tokens)
objectName.add(Pattern.quote(s));
field = lookup(objectSearchToken, objectName); // search file C
lookupReset(true); // back to file B
} else {
/* found it, file B specific processing here */
}
if(field != null) // found it in B or C
thisObject.ID = field;
}
Все токены objectName - это заглавные слова с возможными дефисами или апострофами, разделенными пробелами (имя человека).
Согласно ответу aioobe, я предварительно скомпилировал регулярное выражение для моих постоянных токенов поиска, которое в данном случае составляет всего \r\n
. Заметное ускорение было примерно в 20 раз в другом из процессов, где я скомпилировал [0-9]{1,3}\\.[0-9]%|\r\n|0|[A-Z'-]+
, хотя это не было замечено в приведенном выше коде с \r\n
. Работая в этом направлении, я задаюсь вопросом:
Было бы лучше для меня сопоставить \r\n[^ ]
, если единственные пригодные совпадения будут в строках, начинающихся в любом случае с непробельного символа? Это может уменьшить количество выполнений _match.
Другая возможная оптимизация заключается в следующем: объединить все токены после и поставить (.*)
заранее. Это уменьшит количество регулярных выражений (которые в любом случае являются буквальными), которые будут скомпилированы примерно на 2/3, а также, я надеюсь, позволит мне извлечь текст из этой группы вместо сохранения «потенциального токена» из каждой строки с удостоверение личности на это. Это тоже стоит делать?
Приведенную выше ситуацию можно было бы разрешить, если бы я смог заставить java.util.Scanner вернуть токен, предшествующий текущему, после вызова findWithinHorizon.