Сравните две строки, которые лексикографически эквивалентны, но не идентичны на уровне байтов

Я ищу способ сравнить две строки Java, которые лексикографически эквивалентны, но не идентичны на уровне байтов.

Более точно возьмите следующее имя файла "baaaé.png", на уровне байта его можно представить двумя разными способами:

[98, 97, 97, 97, -61, -87, 46, 112, 110, 103] → "é" закодировано с помощью 2 байтов

[98, 97, 97, 97, 101, -52, -127, 46, 112, 110, 103] → "é" закодировано с 3 байтами

    byte[] ch = {98, 97, 97, 97, -61, -87, 46, 112, 110, 103};
    byte[] ff = {98, 97, 97, 97, 101, -52, -127, 46, 112, 110, 103};

    String st = new String(ch,"UTF-8");
    String st2 = new String(ff,"UTF-8");
    System.out.println(st);
    System.out.println(st2);
    System.out.println(st.equals(st2));

Сгенерирует следующий вывод:

baaaé.png
baaaé.png
false

Есть ли способ сделать сравнение, чтобы метод equals возвращал true?

Ответ 1

Вы можете использовать класс Collator с применимой силой, чтобы нормализовать такие вещи, как разные знаки акцента. это позволит вам успешно сравнивать строки.

В этом случае достаточно установить локализацию в США и силу TERTIARY, чтобы заставить строки быть равными

Collator usCollator = Collator.getInstance();
usCollator.setStrength(Collator.TERTIARY);
System.out.println(usCollator.equals(st, st2));

выходы

true

Вы также можете использовать класс Java Normalizer для преобразования между различными формами Unicode. Это преобразует ваши строки, но в конечном итоге они будут такими же, что позволит вам использовать стандартные инструменты строк для сравнения

Наконец, возьмите, возможно, захотите взглянуть на проект ICU (International Components for Unicode), который предоставляет множество инструментов для работая со строками Unicode множеством разных способов.

Ответ 2

Существует два вида форматов нормализации Unicode, которые необходимо изучить:

В первую очередь NFC и NFD. Пример, который вы приводите в своем вопросе, является отличным примером разных NFC и NFD. Ваша первая строка находится в NFC, а вторая - в NFD.

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

Обычно мы не используем сочетание NFC и NFD. Большинство сред определяют, какая из них является предпочтительной. Очень кратко: имена файлов MacOS X используют NFD, и в значительной степени все остальное использует NFC. Но если вам предоставлен ввод, который может быть в "другой" форме нормализации, вы можете легко его преобразовать: процесс прост (используя информацию, предоставленную в базе данных символов Юникода) и без потерь (т.е. Вы можете идти туда и обратно между NFC и NFD, если вы хотите, не теряя информацию).

java предоставляет встроенный класс Normalizer, который может преобразовать строку в определенную форму Unicode.

Существуют две другие формы нормировки: NFKC и NFKD. Эти формы не предназначены для общего использования, но только для лексикографических сравнений. Они объясняют тот факт, что, например, ¼ следует рассматривать как 1/4 в поиске или сравнении. Но они не подразумевают, что ¼ и 1/4 одинаковы или что в общем случае они должны быть преобразованы в другие.

Преобразование из NFC в NFKC и из NFD в NFKD снова является простым (вам нужна база данных символов), но на этот раз это потеря. Вам нужно сохранить исходный текст NFC/NFD и использовать NFKC/NFKD только в качестве ключа поиска/сортировки.