Мне нужен алгоритм быстрой подстановки ключей для java

Учитывая строку с заменяющими ключами в ней, как я могу наиболее эффективно заменить эти ключи значениями времени выполнения, используя Ява? Мне нужно делать это часто, быстро и на достаточно длинных строках (скажем, в среднем 1-2кб). Форма ключей - это мой выбор, так как здесь я тоже предоставляю шаблоны.

Вот пример (пожалуйста, не зацикливайтесь на том, что это XML; я хочу сделать это, если возможно, дешевле, чем использование операций XSL или DOM). Я бы хотел заменить здесь все шаблоны @[^@]*?@ значениями свойств из свойств bean-компонентов, истинными свойствами Property и некоторыми другими источниками. Ключ здесь - быстрый. Любые идеи?

<?xml version = "1.0" encoding = "utf-8"?>

<envelope version = "2.3">

  <delivery_instructions>

    <delivery_channel>
      <channel_type>@CHANNEL_TYPE@</channel_type>
    </delivery_channel>

    <delivery_envelope>
      <chan_delivery_envelope>
    <queue_name>@ADDRESS@</queue_name>
      </chan_delivery_envelope>
    </delivery_envelope>

  </delivery_instructions>

  <composition_instructions>
    <mime_part content_type = "application/xml">
      <content><external_uri>@URI@</external_uri></content>
    </mime_part>
  </composition_instructions>

</envelope>

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

Сколько разных шаблонов вы ожидаете? Будете ли вы использовать небольшой набор шаблонов снова и снова? Или для каждого набора значений свойств существует отдельный шаблон?

bobwienholt 21.01.2009 02:03

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

Chris R 21.01.2009 18:26
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
2 312
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Ответ принят как подходящий

Метод appendReplacement в Матчер выглядит так, как будто он может быть полезен, хотя я не могу ручаться за его скорость.

Вот пример кода из Javadoc:

Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
    m.appendReplacement(sb, "dog");
}
m.appendTail(sb);
System.out.println(sb.toString());

Обновлено: Если это настолько сложно, насколько это возможно, вы, вероятно, могли бы довольно легко реализовать свой собственный конечный автомат. Вы в значительной степени будете делать то, что уже делает appendReplacement, хотя специализированная реализация может быть быстрее.

То, о чем я не знал, это appendReplacement и appendTail; это фантастические инструменты!

Chris R 21.01.2009 08:42

У меня также есть библиотека подстановки без регулярных выражений, доступная здесь. Я не тестировал его скорость, и он напрямую не поддерживает синтаксис в вашем примере. Но было бы легко расширить для поддержки этого синтаксиса; см., например, этот класс.

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

При желании превратить текст в поток. Прочтите его char, перенаправляя каждый символ в строку / поток вывода, пока не увидите @, затем читаете следующему @, прихлебывая ключ, подставляя ключ в вывод: повторяйте до конца потока.

Я знаю, что это старый добрый зверь, но он, наверное, лучший.

Я предполагаю, что у вас есть какое-то разумное предположение о «@», а не просто о «появлении» независимо от ваших токен-ключей во входных данных. :)

mmyers 'Matcher может сообщить, каким был совпавший текст, при использовании с примером регулярного выражения Криса R String обрабатывается только один раз, каждое совпадение проверяется и заменяется различными строками в зависимости от того, что было совпадением.

Stephen Denne 21.01.2009 03:19

Есть ли в Java форма regexp replace (), в которой вызывается функция?

Испортил метод Javascript String.replace (). (В этом отношении вы могли бы запустить Rhino и использовать Javascript, но почему-то я не думаю, что это было бы так же быстро, как чистый вызов Java, даже если бы компилятор / интерпретатор Javascript был эффективным)

изменить: неважно, у @mmyers, вероятно, есть лучший ответ.

беспричинное унижение: (и потому что я хотел посмотреть, смогу ли я сделать это сам :)

Pattern p = Pattern.compile("@([^@]*?)@");
Matcher m = p.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) 
{
    m.appendReplacement(sb,substitutionTable.lookupKey(m.group(1)));
}
m.appendTail(sb);
// replace "substitutionTable.lookupKey" with your routine

Спасибо, Джейсон; то, что у вас есть, есть почти дословная версия, которую я написал. Я тоже собираюсь поэкспериментировать с прямой циклической заменой, но думаю, что меня устроит такой подход.

Chris R 21.01.2009 08:43

@ ([^ @] *) @ или даже лучше (если поддерживается) @ ([^ @] * +) @

Brad Gilbert 21.01.2009 18:47

Преждевременно переходить к написанию собственного. Я бы начал с простого решения по замене и протестировал его. Затем я бы попробовал стороннее решение для создания шаблонов. ТОГДА я бы попробовал версию кастомного потока.

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

И учтите, что сторонние решения вполне могут оказаться близкими к оптимальным.

Stephen C 23.08.2009 13:46

please don't get hung up on it being XML; I want to do this, if possible, cheaper than using XSL or DOM operations

Все, что ниже по течению от вашего процесса, зависнет, если вы также не обработаете вставленные строки для экранирования символов. Это не означает, что вы не можете сделать это самостоятельно, если у вас есть веская причина, но это означает, что вам нужно либо убедиться, что все ваши шаблоны находятся в текстовых узлах, либо вы также правильно избегаете заменяющего текста.

Какое точное преимущество @ Foo @ над стандартным & Foo; синтаксис уже встроен в библиотеки XML, которые поставляются с Java?

Преимущество прежде всего в том, что не нужно платить за синтаксический анализ XML; более того, рассматриваемые сущности будут разрешены во время синтаксического анализа, а не во время рендеринга. «@ [^ @] +? @» выгодно, потому что в нашей модели данных он не соответствует ни одной допустимой строке.

Chris R 21.01.2009 18:24

Вам нужно проанализировать XML, но не как XML, а с использованием регулярного выражения. Я ожидал, что вы передадите XML через синтаксический анализатор и записывающее устройство в буфер, поэтому время рендеринга не учитывается. Возникает вопрос, будет ли больше работать с тем, что должно быть оптимизированным парсером, быстрее или нет.

Pete Kirkham 21.01.2009 18:41

Если у вас есть & foo; в вашей модели данных вам все равно нужно избегать его, если вы используете регулярное выражение, поэтому вам все равно придется выполнять эту часть обработки.

Pete Kirkham 21.01.2009 18:42

Обработка текста всегда будет ограничена, если вы не измените свою парадигму. Я не знаю, насколько гибок ваш домен, поэтому не уверен, применимо ли это, но вот что:

попробуйте создать индекс там, где находится ваша текстовая подстановка - это особенно хорошо, если шаблон не меняется часто, потому что он становится частью "компиляции" шаблона в двоичный объект, который может принимать значение, необходимое для замен, и вывести всю строку как массив байтов. Этот объект можно кэшировать / сохранить и в следующий раз заменить новым значением, чтобы использовать его снова. То есть вы каждый раз экономите на парсинге документа. (реализация оставлена ​​читателю в качестве упражнения = D)

Но, пожалуйста, используйте профилировщик, чтобы проверить, действительно ли это узкое место, о котором вы говорите, прежде чем приступать к написанию собственного механизма шаблонов. На самом деле проблема может быть в другом.

Взгляните на библиотеку, которая специализируется на этом, например, Скорость Apache. По крайней мере, вы можете поспорить, что их реализация, поскольку эта часть логики выполняется быстро.

Я бы не был так уверен, что принятый ответ быстрее, чем String.replaceAll (String, String). Здесь для сравнения представлена ​​реализация String.replaceAll и Matcher.replaceAll, которая используется под обложками. выглядит очень похоже на то, что ищет OP, и я предполагаю, что он, вероятно, более оптимизирован, чем это упрощенное решение.

public String replaceAll(String s, String s1)
    {
        return Pattern.compile(s).matcher(this).replaceAll(s1);
    }

public String replaceAll(String s)
    {
        reset();
        boolean flag = find();
        if (flag)
        {
            StringBuffer stringbuffer = new StringBuffer();
            boolean flag1;
            do
            {
                appendReplacement(stringbuffer, s);
                flag1 = find();
            } while(flag1);
            appendTail(stringbuffer);
            return stringbuffer.toString();
        } else
        {
            return text.toString();
        }
    }

... Чии права. Если это шаблон, который нужно запускать столько раз, что скорость имеет значение, найдите индекс ваших токенов подстановки, чтобы иметь возможность напрямую обращаться к ним без необходимости каждый раз начинать с самого начала. Преобразуйте "компиляцию" в объект с хорошими свойствами, они должны нуждаться в обновлении только после изменения шаблона.

Как уже говорили другие, appendReplacement () и appendTail () - это инструменты, которые вам нужны, но есть кое-что, на что вы должны обратить внимание. Если строка замены содержит какие-либо знаки доллара, метод попытается интерпретировать их как ссылки группы захвата. Если есть какие-либо обратные косые черты (которые используются, чтобы избежать пения долларов), он либо съест их, либо выдаст исключение.

Если ваша замещающая строка генерируется динамически, вы можете не знать заранее, будет ли она содержать знаки доллара или обратную косую черту. Чтобы предотвратить проблемы, вы можете добавить замену прямо в StringBuffer, например:

Pattern p = Pattern.compile("@([^@]*?)@");
Matcher m = p.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) 
{
    m.appendReplacement("");
    sb.append(substitutionTable.lookupKey(m.group(1)));
}
m.appendTail(sb);

Вам все равно придется каждый раз вызывать appendReplacement (), потому что это то, что позволяет вам синхронизироваться с позицией совпадения. Но этот трюк позволяет избежать бессмысленной обработки, которая может дать вам заметный прирост производительности в качестве бонуса.

это то, что я использую, из проекта apache commons http://commons.apache.org/lang/api/org/apache/commons/lang/text/StrSubstitutor.html

Rythm, механизм шаблонов Java, теперь выпущен с новой функцией под названием Режим интерполяции строк, которая позволяет вам делать что-то вроде:

String result = Rythm.render("Hello @who!", "world");

В приведенном выше случае показано, что вы можете передавать аргумент в шаблон по позиции. Rythm также позволяет передавать аргументы по имени:

Map<String, Object> args = new HashMap<String, Object>();
args.put("title", "Mr.");
args.put("name", "John");
String result = Rythm.render("Hello @title @name", args);

Поскольку содержимое вашего шаблона относительно длинное, вы можете поместить его в файл, а затем вызвать Rythm.render, используя тот же API:

Map<String, Object> args = new HashMap<String, Object>();
// ... prepare the args
String result = Rythm.render("path/to/my/template.xml", args);

Обратите внимание, что Rythm компилирует ваш шаблон в байт-код Java, и это довольно быстро, примерно в 2 раза быстрее, чем String.format.

Ссылки:

Другие вопросы по теме