Производительность интенсивного разделения строк и манипуляций в Java - PullRequest
3 голосов
/ 31 мая 2010

Какой самый эффективный способ разбить строку с помощью очень простого разделителя?

Некоторый фон:

Я портирую написанную на C функцию с арифметикой указателей на java, и она невероятно медленная (после некоторой оптимизации все еще на 5 * медленнее) После его профилирования оказывается, что большая часть этих служебных данных находится в String.split

Рассматриваемая функция берет имя хоста или IP-адрес и делает его общим:

123.123.123.123 -> * 123.123.123

.

a.b.c.example.com -.> * Example.com

Это может выполнять несколько миллионов элементов на регулярной основе, поэтому производительность является проблемой.

Редактировать: правила конвертации следующие:

  • Если это ip-адрес, заменить первую часть
  • В противном случае найдите основное доменное имя и сделайте предыдущую часть общей.

foo.bar.com-> * .bar.com foo.bar.co.uk-> * .bar.co.uk

Теперь я переписал, используя lastIndexOf и подстроку для работы со спины, и производительность улучшилась на дрожжах.

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

Вот то, что я придумал сейчас (часть ip - незначительная проверка перед вызовом этой функции)

private static String hostConvert(String in) {
    final String [] subs = { "ac", "co", "com", "or", "org", "ne", "net", "ad", "gov", "ed" };

    int dotPos = in.lastIndexOf('.');
    if(dotPos == -1)
        return in;
    int prevDotPos = in.lastIndexOf('.', dotPos-1);
    if(prevDotPos == -1)
        return in;
    CharSequence cs = in.subSequence(prevDotPos+1, dotPos);
    for(String cur : subs) {
        if(cur.contentEquals(cs)) {
            int start = in.lastIndexOf('.', prevDotPos-1);
            if(start == -1 || start == 0)
                return in;
            return "*" + in.substring(start);
        }
    }

    return "*" + in.substring(prevDotPos);
}

Если есть место для дальнейшего совершенствования, было бы хорошо услышать.

Ответы [ 3 ]

6 голосов
/ 31 мая 2010

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

static String starOutFirst(String s) {
    final int K = s.indexOf('.');
    return "*" + s.substring(K);
}
static String starOutButLastTwo(String s) {
    final int K = s.lastIndexOf('.', s.lastIndexOf('.') - 1);
    return "*" + s.substring(K);
}

Тогда вы можете сделать:

    System.out.println(starOutFirst("123.123.123.123"));
    // prints "*.123.123.123"

    System.out.println(starOutButLastTwo("a.b.c.example.com"));
    // prints "*.example.com"

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

2 голосов
/ 31 мая 2010

Из вашего вопроса неясно, что именно должен делать код. Находит ли это первое '.' и заменить все до него на «*»? Или за этим стоит какая-то причудливая логика? Может быть, все до nth '.' заменяется на '*'?

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

Имейте в виду, что String в Java является неизменным. Может быть быстрее изменить последовательность на месте. Проверьте другие реализации CharSequence, чтобы увидеть, что вы можете сделать, например, StringBuffer и CharBuffer . Если параллелизм не требуется, StringBuilder может быть вариантом.

Используя изменяемый CharSequence вместо методов на String, вы избегаете оттока объектов. Если все, что вы делаете, это замените некоторый фрагмент базового символьного массива более коротким массивом (то есть {'*'}), это, вероятно, приведет к ускорению, поскольку такие копии массива довольно оптимизированы . Вы все еще будете делать копию массива в конце дня, но это может быть быстрее, чем новые String выделения.

UPDATE

Все вышесказанное - в значительной степени фигня. Конечно, может быть, вы можете реализовать свой собственный CharSequence, который дает вам лучшую нарезку и лениво изменяет размер массива (иначе фактически ничего не обрезает, пока это абсолютно не нужно), возвращая Strings на основе смещений и еще много чего. Но StringBuffer и StringBuilder, по крайней мере напрямую, не работают так же хорошо, как опубликованное решение. CharBuffer полностью неприменимо; Я не осознавал, что это был урок nio раньше: он предназначен для других вещей.

В коде поли есть несколько интересных моментов, которые меня интересуют, знал ли он или она до публикации, а именно, что изменение "*" в последних строках методов на '*' приводит к значительному замедлению.

Тем не менее, вот мой тест. Я обнаружил одну небольшую оптимизацию: объявление выражений '.' и "*" в качестве констант добавляет немного ускорения, а также использование StringBuilder в локальной области вместо оператора конкатенации двоичных инфиксных строк.

Я знаю, что gc() в лучшем случае является рекомендацией, а в худшем - неактивным, но я подумал, что добавление его с небольшим временем ожидания может позволить ВМ выполнить некоторую очистку после создания 1M String s. Кто-то может исправить меня, если это совершенно наивно.

Простой бенчмарк

import java.util.ArrayList;
import java.util.Arrays;

public class StringSplitters {
    private static final String PREFIX = "*";
    private static final char DOT = '.';

    public static String starOutFirst(String s) {
        final int k = s.indexOf(DOT);
        return PREFIX + s.substring(k);
    }

    public static String starOutFirstSb(String s) {
        StringBuilder sb = new StringBuilder();
        final int k = s.indexOf(DOT);
        return sb.append(PREFIX).append(s.substring(k)).toString();
    }

    public static void main(String[] args) throws InterruptedException {
        double[] firstRates = new double[10];
        double[] firstSbRates = new double[10];
        double firstAvg = 0;
        double firstSbAvg = 0;
        double firstMin = Double.POSITIVE_INFINITY;
        double firstMax = Double.NEGATIVE_INFINITY;

        double firstSbMin = Double.POSITIVE_INFINITY;
        double firstSbMax = Double.NEGATIVE_INFINITY;

        for (int i = 0; i < 10; i++) {
            firstRates[i] = testFirst();

            firstAvg += firstRates[i];

            if (firstRates[i] < firstMin)
                firstMin = firstRates[i];
            if (firstRates[i] > firstMax)
                firstMax = firstRates[i];

            Thread.sleep(100);
            System.gc();
            Thread.sleep(100);
        }

        firstAvg /= 10.0d;

        for (int i = 0; i < 10; i++) {
            firstSbRates[i] = testFirstSb();

            firstSbAvg += firstSbRates[i];

            if (firstSbRates[i] < firstSbMin)
                firstSbMin = firstSbRates[i];
            if (firstSbRates[i] > firstSbMax)
                firstSbMax = firstSbRates[i];

            Thread.sleep(100);
            System.gc();
            Thread.sleep(100);
        }

        firstSbAvg /= 10.0d;

        System.out.printf("First:\n\tMin:\t%07.3f\tMax:\t%07.3f\tAvg:\t%07.3f\n\tRates:\t%s\n\n", firstMin, firstMax,
                firstAvg, Arrays.toString(firstRates));
        System.out.printf("FirstSb:\n\tMin:\t%07.3f\tMax:\t%07.3f\tAvg:\t%07.3f\n\tRates:\t%s\n\n", firstSbMin,
                firstSbMax, firstSbAvg, Arrays.toString(firstSbRates));

    }

    private static double testFirst() {
        ArrayList<String> strings = new ArrayList<String>(1000000);

        for (int i = 0; i < 1000000; i++) {
            int first = (int) (Math.random() * 128);
            int second = (int) (Math.random() * 128);
            int third = (int) (Math.random() * 128);
            int fourth = (int) (Math.random() * 128);

            strings.add(String.format("%d.%d.%d.%d", first, second, third, fourth));
        }

        long before = System.currentTimeMillis();
        for (String s : strings) {
            starOutFirst(s);
        }
        long after = System.currentTimeMillis();

        return 1000000000.0d / (after - before);
    }

    private static double testFirstSb() {
        ArrayList<String> strings = new ArrayList<String>(1000000);

        for (int i = 0; i < 1000000; i++) {
            int first = (int) (Math.random() * 128);
            int second = (int) (Math.random() * 128);
            int third = (int) (Math.random() * 128);
            int fourth = (int) (Math.random() * 128);

            strings.add(String.format("%d.%d.%d.%d", first, second, third, fourth));
        }

        long before = System.currentTimeMillis();
        for (String s : strings) {
            starOutFirstSb(s);
        }
        long after = System.currentTimeMillis();

        return 1000000000.0d / (after - before);
    }
}

выход

First:
    Min:    3802281.369 Max:    5434782.609 Avg:    5185796.131
    Rates:  [3802281.3688212926, 5181347.150259067, 5291005.291005291, 5376344.086021505, 5291005.291005291, 5235602.094240838, 5434782.608695652, 5405405.405405405, 5434782.608695652, 5405405.405405405]

FirstSb:
    Min:    4587155.963 Max:    5747126.437 Avg:    5462087.511
    Rates:  [4587155.963302752, 5747126.436781609, 5617977.528089887, 5208333.333333333, 5681818.181818182, 5586592.17877095, 5586592.17877095, 5524861.878453039, 5524861.878453039, 5555555.555555556]
2 голосов
/ 31 мая 2010

Я бы попробовал использовать .indexOf (".") и .substring (index)

Вы не уточнили, какой именно шаблон вы хотите сопоставить, но если вы можете избежать split (), он должен сократить количество новых строк, которые он выделяет (1 вместо нескольких).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...