Самый быстрый способ удалить все непечатаемые символы из строки Java
каков самый быстрый способ удалить все непечатаемые символы из String
в Java?
до сих пор я пробовал и измерял на 138-байтовой, 131-символьной строке:
- строки
replaceAll()
- самый медленный метод- результаты 517009 / сек
- предварительная компиляция шаблона, а затем использовать совпадений по
replaceAll()
- 637836 результаты / сек
- используйте StringBuffer, получить коды, используя
codepointAt()
один-на-один и присоеденить к StringBuffer- 711946 результаты / сек
- используйте StringBuffer, получить символы с помощью
charAt()
один-на-один и присоеденить к StringBuffer- 1052964 результаты / сек
- назначить
char[]
буфер, получить символы с помощьюcharAt()
один за другим и заполнить этот буфер, а затем преобразовать обратно в строку- 2022653 результаты / сек
- выделить 2
char[]
буферы-старые и новые, получить все символы для существующей строки сразу с помощьюgetChars()
, повторите старый буфер один за другим и заполните новый буфер, затем преобразуйте новый буфер в строку -моя собственная самая быстрая версия- 2502502 результаты / сек
- то же самое с 2 буферами - только с помощью
byte[]
,getBytes()
и указание кодировки как " utf-8"- 857485 результаты / сек
- то же самое с 2
byte[]
буферы, но указание кодировки как константыCharset.forName("utf-8")
- 791076 результаты / сек
- то же самое с 2
byte[]
буферы, но указание кодировки как 1-байтовой локальной кодировки (едва ли разумная вещь)- 370164 результаты / сек
моя лучшая попытка была следующей:
char[] oldChars = new char[s.length()];
s.getChars(0, s.length(), oldChars, 0);
char[] newChars = new char[s.length()];
int newLen = 0;
for (int j = 0; j < s.length(); j++) {
char ch = oldChars[j];
if (ch >= ' ') {
newChars[newLen] = ch;
newLen++;
}
}
s = new String(newChars, 0, newLen);
любые мысли о том, как сделать еще быстрее?
бонусные баллы за ответ на очень странный вопрос: почему использование имени кодировки "utf-8" напрямую дает лучшую производительность, чем использование предварительно выделенного статического const Charset.forName("utf-8")
?
обновление
- предложение храповика урод дает впечатляющие результаты 3105590 / сек производительность, + 24% улучшение!
- предложение Эд Стауб дает еще одно улучшение-3471017 результаты / сек, а +12% по сравнению с предыдущим лучшим.
обновление 2
я сделал все возможное, чтобы собрать все предлагаемые решения и его кросс-мутации и опубликовал его как небольшие рамки бенчмаркинга в github. В настоящее время он имеет 17 алгоритмов. Один из них "особенный" -Voo1 (предоставлено пользователем SO Voo) использует сложные трюки отражения, таким образом достигая звездных скоростей, но это портит состояние строк JVM, таким образом это бенчмаркинг отдельно.
вы можете проверить его и запустить его, чтобы определить результаты на вашем поле. Вот резюме результатов, которые у меня есть. Это спецификации:
- Debian sid
- в Linux 2.6.39-2-для amd64 (x86_64 с)
- Java установлен из пакета
sun-java6-jdk-6.24-1
, JVM идентифицирует себя как- Java (TM) SE среда выполнения (build 1.6.0_24-b07)
- Java HotSpot (TM) 64-разрядный сервер VM (сборка 19.1-b02, смешанный режим)
различные алгоритмы показывают в конечном счете разные результаты, учитывая другой набор входных данных. Я запустил тест в 3 режимах:
одной строкой
этот режим работает на одной и той же строке, предоставленной StringSource
класс как константа. Вскрытие:
Ops / s │ Algorithm ──────────┼────────────────────────────── 6 535 947 │ Voo1 ──────────┼────────────────────────────── 5 350 454 │ RatchetFreak2EdStaub1GreyCat1 5 249 343 │ EdStaub1 5 002 501 │ EdStaub1GreyCat1 4 859 086 │ ArrayOfCharFromStringCharAt 4 295 532 │ RatchetFreak1 4 045 307 │ ArrayOfCharFromArrayOfChar 2 790 178 │ RatchetFreak2EdStaub1GreyCat2 2 583 311 │ RatchetFreak2 1 274 859 │ StringBuilderChar 1 138 174 │ StringBuilderCodePoint 994 727 │ ArrayOfByteUTF8String 918 611 │ ArrayOfByteUTF8Const 756 086 │ MatcherReplace 598 945 │ StringReplaceAll 460 045 │ ArrayOfByteWindows1251
в графической форме: одной строкой график http://www.greycat.ru/img/os-chart-single.png
несколько строк, 100% строк содержат управляющие символы
поставщик исходных строк предварительно сгенерировал множество случайных строк, используя (0..127) набор символов-таким образом, почти все строки содержат хотя бы один управляющий символ. Алгоритмы получили строки из этого предварительно сгенерированного массива в циклическом режиме.
Ops / s │ Algorithm ──────────┼────────────────────────────── 2 123 142 │ Voo1 ──────────┼────────────────────────────── 1 782 214 │ EdStaub1 1 776 199 │ EdStaub1GreyCat1 1 694 628 │ ArrayOfCharFromStringCharAt 1 481 481 │ ArrayOfCharFromArrayOfChar 1 460 067 │ RatchetFreak2EdStaub1GreyCat1 1 438 435 │ RatchetFreak2EdStaub1GreyCat2 1 366 494 │ RatchetFreak2 1 349 710 │ RatchetFreak1 893 176 │ ArrayOfByteUTF8String 817 127 │ ArrayOfByteUTF8Const 778 089 │ StringBuilderChar 734 754 │ StringBuilderCodePoint 377 829 │ ArrayOfByteWindows1251 224 140 │ MatcherReplace 211 104 │ StringReplaceAll
в графической форме: множественные строки, концентрация 100% http://www.greycat.ru/img/os-chart-multi100.png
несколько строк, 1% строк содержат управляющие символы
так же, как и предыдущий, но только 1% строк было сгенерировано с управляющими символами - другие 99% были сгенерированы с помощью [32..127] набор символов, поэтому они не могли содержать символы управления вообще. Эта синтетическая нагрузка ближе всего подходит к реальному применению этого алгоритма в моем месте.
Ops / s │ Algorithm ──────────┼────────────────────────────── 3 711 952 │ Voo1 ──────────┼────────────────────────────── 2 851 440 │ EdStaub1GreyCat1 2 455 796 │ EdStaub1 2 426 007 │ ArrayOfCharFromStringCharAt 2 347 969 │ RatchetFreak2EdStaub1GreyCat2 2 242 152 │ RatchetFreak1 2 171 553 │ ArrayOfCharFromArrayOfChar 1 922 707 │ RatchetFreak2EdStaub1GreyCat1 1 857 010 │ RatchetFreak2 1 023 751 │ ArrayOfByteUTF8String 939 055 │ StringBuilderChar 907 194 │ ArrayOfByteUTF8Const 841 963 │ StringBuilderCodePoint 606 465 │ MatcherReplace 501 555 │ StringReplaceAll 381 185 │ ArrayOfByteWindows1251
в намеченный форма: множественные строки, концентрация 1% http://www.greycat.ru/img/os-chart-multi1.png
мне очень сложно решить, кто дал лучший ответ, но, учитывая, что лучшее решение для приложения в реальном мире было дано/вдохновлено Эдом Стаубом, я думаю, было бы справедливо отметить его ответ. Спасибо всем, кто принимал в этом участие, Ваш вклад был очень полезным и бесценным. Не стесняйтесь запускать набор тестов на вашем ящике и предлагать еще лучшие решения (рабочий JNI решение, кто?).
ссылки
- репозитории GitHub с набором бенчмаркинга
7 ответов
если целесообразно внедрить этот метод в класс, который не является общим для потоков, вы можете повторно использовать буфер:
char [] oldChars = new char[5];
String stripControlChars(String s)
{
final int inputLen = s.length();
if ( oldChars.length < inputLen )
{
oldChars = new char[inputLen];
}
s.getChars(0, inputLen, oldChars, 0);
etc...
это большой выигрыш-20% или около того, как я понимаю, текущий лучший случай.
Если это должно использоваться на потенциально больших строках, и "утечка" памяти является проблемой, можно использовать слабую ссылку.
использование 1 массива символов может работать немного лучше
int length = s.length();
char[] oldChars = new char[length];
s.getChars(0, length, oldChars, 0);
int newLen = 0;
for (int j = 0; j < length; j++) {
char ch = oldChars[j];
if (ch >= ' ') {
oldChars[newLen] = ch;
newLen++;
}
}
s = new String(oldChars, 0, newLen);
и я избегал повторных звонков в s.length();
еще одна микро-оптимизация, которая может работать
int length = s.length();
char[] oldChars = new char[length+1];
s.getChars(0, length, oldChars, 0);
oldChars[length]='';//avoiding explicit bound check in while
int newLen=-1;
while(oldChars[++newLen]>=' ');//find first non-printable,
// if there are none it ends on the null char I appended
for (int j = newLen; j < length; j++) {
char ch = oldChars[j];
if (ch >= ' ') {
oldChars[newLen] = ch;//the while avoids repeated overwriting here when newLen==j
newLen++;
}
}
s = new String(oldChars, 0, newLen);
Ну, я избил текущий лучший метод (решение freak с предварительно выделенным массивом) примерно на 30% в соответствии с моими мерами. Как? Продав свою душу.
поскольку я уверен, что все, кто следил за обсуждением до сих пор, знают, что это нарушает практически любой основной принцип программирования, но хорошо. В любом случае следующее работает только в том случае, если используемый массив символов строки не разделяется между другими строками - если это делает тот, кто должен отлаживать это, будет иметь полное право решать чтобы убить вас (без вызовов substring () и используя это в литеральных строках, это должно работать, поскольку я не вижу, почему JVM будет интернировать уникальные строки, прочитанные из внешнего источника). Хотя не забудьте убедиться, что эталонный код этого не делает - это чрезвычайно вероятно и, очевидно, поможет решению отражения.
в любом случае здесь мы идем:
// Has to be done only once - so cache those! Prohibitively expensive otherwise
private Field value;
private Field offset;
private Field count;
private Field hash;
{
try {
value = String.class.getDeclaredField("value");
value.setAccessible(true);
offset = String.class.getDeclaredField("offset");
offset.setAccessible(true);
count = String.class.getDeclaredField("count");
count.setAccessible(true);
hash = String.class.getDeclaredField("hash");
hash.setAccessible(true);
}
catch (NoSuchFieldException e) {
throw new RuntimeException();
}
}
@Override
public String strip(final String old) {
final int length = old.length();
char[] chars = null;
int off = 0;
try {
chars = (char[]) value.get(old);
off = offset.getInt(old);
}
catch(IllegalArgumentException e) {
throw new RuntimeException(e);
}
catch(IllegalAccessException e) {
throw new RuntimeException(e);
}
int newLen = off;
for(int j = off; j < off + length; j++) {
final char ch = chars[j];
if (ch >= ' ') {
chars[newLen] = ch;
newLen++;
}
}
if (newLen - off != length) {
// We changed the internal state of the string, so at least
// be friendly enough to correct it.
try {
count.setInt(old, newLen - off);
// Have to recompute hash later on
hash.setInt(old, 0);
}
catch(IllegalArgumentException e) {
e.printStackTrace();
}
catch(IllegalAccessException e) {
e.printStackTrace();
}
}
// Well we have to return something
return old;
}
для моей teststring, которая получает 3477148.18ops/s
и 2616120.89ops/s
старый вариант. Я уверен, что это единственный способ победить. может быть, написать его на C (возможно, нет) или какой-то совершенно другой подход, о котором никто до сих пор не думал. Хотя я абсолютно не уверен, что время стабильно на разных платформах-по крайней мере, дает надежные результаты на моем поле (Java7, Win7 x64).
вы могли бы разделить задачу на несколько параллельных подзадач, в зависимости от количества процессоров.
IANA низкоуровневый Java Performance junkie, но вы пробовали разворачивание основной петли? Похоже, что это может позволить некоторым процессорам выполнять проверки параллельно.
и этой имеет некоторые интересные идеи для оптимизации.
Я был так свободен и написал небольшой тест для разных алгоритмов. Это не идеально, но я беру минимум 1000 запусков данного алгоритма 10000 раз над случайной строкой (примерно с 32/200% непечатаемых по умолчанию). Это должно позаботиться о таких вещах, как GC, инициализация и так далее - не так много накладных расходов, что любой алгоритм не должен иметь хотя бы одного запуска без особых препятствий.
не особенно хорошо документировано,но хорошо. поехали - I включены оба алгоритма ratchet freak и базовая версия. В данный момент я произвольно инициализирую длинную строку длиной 200 символов с равномерно распределенными символами в диапазоне [0, 200).
Почему использование имени кодировки "utf-8" напрямую дает лучшую производительность, чем использование предварительно выделенной статической кодировки const.forName ("utf-8")?
если вы имеете в виду String#getBytes("utf-8")
etc.: Это не должно быть быстрее-за исключением лучшего кэширования-так как Charset.forName("utf-8")
используется внутренне, если charset не сохраняется.
одна вещь может быть, что вы используете разные кодировки (или, возможно, некоторые из вашего кода прозрачно), но кодировка кэшируется в StringCoding
не изменение.