Обрезать строку в Java при сохранении полного слова

Мне нужно обрезать строку в java, чтобы:

Быстрая коричневая лиса перепрыгивает через лаз-собаку.

становится

Быстрый коричневый...

В приведенном выше примере я обрезаю до 12 символов. Если бы я просто использовал подстроку, я бы получил:

Быстрый бр...

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

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

Спасибо,


person AMZFR    schedule 12.10.2011    source источник


Ответы (7)


Ниже приведен метод, который я использую для обрезки длинных строк в своих веб-приложениях. «Мягкий» boolean, как вы выразились, если установить true, сохранит последнее слово. Это самый краткий способ сделать это, который я мог придумать, используя StringBuffer, который намного эффективнее, чем воссоздание неизменяемой строки.

public static String trimString(String string, int length, boolean soft) {
    if(string == null || string.trim().isEmpty()){
        return string;
    }

    StringBuffer sb = new StringBuffer(string);
    int actualLength = length - 3;
    if(sb.length() > actualLength){
        // -3 because we add 3 dots at the end. Returned string length has to be length including the dots.
        if(!soft)
            return escapeHtml(sb.insert(actualLength, "...").substring(0, actualLength+3));
        else {
            int endIndex = sb.indexOf(" ",actualLength);
            return escapeHtml(sb.insert(endIndex,"...").substring(0, endIndex+3));
        }
    }
    return string;
}

Обновить

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

Примечание. escapeHtml — это статический импорт из apache commons:

import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;

Вы можете удалить его, и код должен работать так же.

person Ali    schedule 12.10.2011
comment
Как StringBuffer помогает производительности здесь? Нет причин, по которым substring, indexOf и length будут работать быстрее на StringBuffer, чем на String. - person Piotr Praszmo; 12.10.2011
comment
Позвольте мне уточнить, спрашивающий сказал, что это токенизация, а затем сборка строки. Каждый раз, когда он добавляет новый токен обратно в строку, вся строка уничтожается и создается заново. Для длинных строк эта операция намного дороже, чем использование StringBuffer. Хотя я согласен, разница в производительности, вероятно, незначительна, учитывая, что StringBuffer создается, и когда мы возвращаемся, мы фактически создаем строку как минимум 3 раза (подстрока, добавление точек, экранирование [, обрезка]). - person Ali; 12.10.2011
comment
Проблема в том, что в вашем коде вы ничего не добавляете к StringBuffer. - person Piotr Praszmo; 12.10.2011
comment
Спасибо, вы абсолютно правы, точки должны быть добавлены как минимум в StringBuffer. Я обновлю свой ответ после тестирования, чтобы убедиться, что ошибок нет. - person Ali; 12.10.2011
comment
@TranDinhThoai escapeHtml — это статический импорт. import static org.apache.commons.lang.StringEscapeUtils.escapeHtml; Вы можете удалить его, он будет работать так же. Он у меня есть, потому что я использую его в веб-приложении и мне нужно избегать html-объектов. - person Ali; 12.10.2011
comment
@Ali Здесь все еще не стоит использовать StringBuffer. Объединение только двух строк вряд ли будет быстрее, чем +. В обоих случаях вы делаете почти одно и то же. - person Piotr Praszmo; 12.10.2011

Вот простое однострочное решение на основе регулярных выражений:

str.replaceAll("(?<=.{12})\\b.*", "..."); // How easy was that!? :)

Объяснение:

  • (?<=.{12}) – это отрицательный просмотр, утверждающий, что слева от совпадения есть как минимум 12 символов, но это совпадение без захвата (т. е. с нулевой шириной).
  • \b.* соответствует границе первого слова (после не менее 12 символов - выше) до конца

Это заменено на...

Вот тест:

public static void main(String[] args) {
    String input = "The quick brown fox jumps over the lazy dog.";
    String trimmed = input.replaceAll("(?<=.{12})\\b.*", "...");
    System.out.println(trimmed);
}

Выход:

The quick brown...

Если производительность является проблемой, предварительно скомпилируйте регулярное выражение для примерно 5-кратного ускорения (YMMV), скомпилировав его один раз:

static Pattern pattern = Pattern.compile("(?<=.{12})\\b.*");

и повторное использование:

String trimmed = pattern.matcher(input).replaceAll("...");
person Bohemian♦    schedule 12.10.2011
comment
Можете ли вы объяснить регулярное выражение? Мне нравится решение, хотя мне нужно посмотреть, как оно складывается по скорости с ответом Али ниже. - person AMZFR; 12.10.2011
comment
@AMZFR не используйте регулярное выражение, если вы беспокоитесь о скорости. Это будет намного медленнее, чем indexOf + substring (в 10-100 раз медленнее). - person Piotr Praszmo; 12.10.2011
comment
Спасибо @Banthar, я склонялся просто потому, что мне нравится знать, что происходит в коде, но решение с регулярным выражением довольно элегантно. - person AMZFR; 12.10.2011
comment
Хороший ответ. Более простое решение, когда производительность не является главным фактором - person Saif Asif; 23.02.2021
comment
@SaifAsif Я добавил более быструю версию, если требуется производительность, хотя исходная версия будет выполняться всего за несколько микросекунд, поэтому, если вам не нужно, чтобы это работало очень быстро, я бы предпочел однострочный вариант статической компиляции регулярного выражения. - person Bohemian♦; 23.02.2021
comment
Да, я видел, я провел бенчмаркинг и обнаружил, что пропускная способность составляет 33 мс на токен, чего, честно говоря, вполне достаточно для моего варианта использования! - person Saif Asif; 24.02.2021

Пожалуйста, попробуйте следующий код:

private String trim(String src, int size) {
    if (src.length() <= size) return src;
    int pos = src.lastIndexOf(" ", size - 3);
    if (pos < 0) return src.substring(0, size);
    return src.substring(0, pos) + "...";
}
person Tran Dinh Thoai    schedule 12.10.2011
comment
Это красиво и просто. Спасибо! - person R Claven; 14.01.2016

Попробуйте найти последнее вхождение пробела, который находится в позиции меньше или больше 11, и обрежьте там строку, добавив "...".

person ikromm    schedule 12.10.2011

Ваши требования не ясны. Если у вас возникли проблемы с формулировкой их на естественном языке, неудивительно, что их будет трудно перевести на компьютерный язык, такой как Java.

«сохранить последнее слово» подразумевает, что алгоритм будет знать, что такое «слово», поэтому вам придется сначала сказать ему это. Разделение — это способ сделать это. Сканер/парсер с грамматикой — это другое.

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

person duffymo    schedule 12.10.2011
comment
Справедливо. Что я имел в виду под сохранением последнего слова, так это то, что я не хочу обрезать строку для любого символа, кроме пробела? Имеет ли это смысл? - person AMZFR; 12.10.2011

Как насчет:

mystring = mystring.replaceAll("^(.{12}.*?)\b.*$", "$1...");
person Highly Irregular    schedule 12.10.2011
comment
Можете ли вы объяснить регулярное выражение? Сохранит ли это последнее слово или нет? Ваше регулярное выражение отличается от богемского. - person AMZFR; 12.10.2011
comment
Возьмите первые 12 символов и минимум после этого, чтобы завершить слово, и добавьте... - person Highly Irregular; 13.10.2011
comment
Я действительно забыл добавить что-то в конец шаблона, чтобы удалить остальную часть строки. Редактирую сейчас, чтобы исправить. - person Highly Irregular; 13.10.2011

Я использую этот хак: предположим, что обрезанная строка должна иметь длину 120:

String textToDisplay = textToTrim.substring(0,(textToTrim.length() > 120) ? 120 : textToTrim.length());

        if (textToDisplay.lastIndexOf(' ') != textToDisplay.length() &&textToDisplay.length()!=textToTrim().length()) {

            textToDisplay = textToDisplay + textToTrim.substring(textToDisplay.length(),textToTrim.indexOf(" ", textToDisplay.length()-1))+ " ...";
        }
person bashizip    schedule 12.01.2015