Skip to content

Commit

Permalink
Text.replaceUmlaute() optimiert (4x schneller)
Browse files Browse the repository at this point in the history
  • Loading branch information
oboehm committed Mar 1, 2019
1 parent 2451578 commit e0f1186
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 10 deletions.
1 change: 1 addition & 0 deletions doc/release-notes.adoc
Expand Up @@ -5,6 +5,7 @@
== 2.2.2 (SNAPSHOT9

* _fixed_: ArrayIndexOutOfBoundException im Adressvergleich behoben.
* Text.replaceUmlaute() ist jetzt um Faktor 4 schneller


== 2.2.1 (28-Feb-2019)
Expand Down
90 changes: 80 additions & 10 deletions src/main/java/de/jfachwert/Text.java
Expand Up @@ -19,9 +19,9 @@

import de.jfachwert.pruefung.NullValidator;
import de.jfachwert.pruefung.exception.LocalizedIllegalArgumentException;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ValidationException;
import java.nio.CharBuffer;
import java.util.WeakHashMap;

/**
Expand Down Expand Up @@ -151,21 +151,91 @@ public final Text replaceUmlaute() {
/**
* Ersetzt Umlaute und scharfes 'S'. Diese Methode wurde als statische
* Methode herausgezogen, da sie an anderen Stellen benoetigt werden.
* <p>
* Mit v2.2.2 wurde die Methode optimiert, da der alte Ansatz mit
* {@link String#replace(CharSequence, CharSequence)} sich als Flaschenhals
* beim Vergleich grosser Datenmenge herausstellte. Durch die Umstellung
* auf zeichenweises Mapping ist diese Methode jetzt ca. 4 x schneller.
* </p>
*
* @param text Text (mit Umlaute)
* @return Text ohne Umlaut und scharfem 's'
* @since 2.1
*/
public static String replaceUmlaute(String text) {
String s = text.replace("\u00fc", "ue").replace("\u00f6", "oe").replace("\u00e4", "ae")
.replace("\u00df", "ss").replaceAll("\u00dc(?=[a-z\u00e4\u00f6\u00fc\u00df ])", "Ue")
.replaceAll("\u00d6(?=[a-z\u00e4\u00f6\u00fc\u00df ])", "Oe")
.replaceAll("\u00c4(?=[a-z\u00e4\u00f6\u00fc\u00df ])", "Ae").replace("\u00dc", "UE")
.replace("\u00d6", "OE").replace("\u00c4", "AE");
return StringUtils
.replaceChars(s, "\u00e1\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00f3\u00f2\u00f4\u00fa\u00f9\u00fb" +
"\u00c1\u00c0\u00c2\u00c9\u00c8\u00ca\u00d3\u00d2\u00d4\u00da\u00d9\u00db",
"aaaeeeeooouuuAAAEEEOOOUUU");
char[] zeichen = text.toCharArray();
CharBuffer buffer = CharBuffer.allocate(zeichen.length * 2);
for (char c : zeichen) {
switch (c) {
case '\u00e4':
buffer.put("ae");
break;
case '\u00f6':
buffer.put("oe");
break;
case '\u00fc':
buffer.put("ue");
break;
case '\u00df':
buffer.put("ss");
break;
case '\u00c4':
buffer.put("Ae");
break;
case '\u00d6':
buffer.put("Oe");
break;
case '\u00dc':
buffer.put("Ue");
break;
case '\u00e1':
case '\u00e0':
case '\u00e2':
buffer.put('a');
break;
case '\u00e9':
case '\u00e8':
case '\u00ea':
case '\u00eb':
buffer.put('e');
break;
case '\u00f3':
case '\u00f2':
case '\u00f4':
buffer.put('o');
break;
case '\u00fa':
case '\u00f9':
case '\u00fb':
buffer.put('u');
break;
case '\u00c1':
case '\u00c0':
case '\u00c2':
buffer.put('A');
break;
case '\u00c9':
case '\u00c8':
case '\u00ca':
buffer.put('E');
break;
case '\u00d3':
case '\u00d2':
case '\u00d4':
buffer.put('O');
break;
case '\u00da':
case '\u00d9':
case '\u00db':
buffer.put('U');
break;
default:
buffer.put(c);
break;
}
}
buffer.rewind();
return buffer.toString().trim();
}

/**
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/de/jfachwert/TextTest.java
Expand Up @@ -19,6 +19,13 @@

import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.logging.Logger;

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.*;

Expand All @@ -29,6 +36,8 @@
*/
public final class TextTest extends FachwertTest {

private static final Logger LOG = Logger.getLogger(TextTest.class.getName());

@Override
protected Text createFachwert() {
return new Text("Hallo Welt!");
Expand Down Expand Up @@ -97,6 +106,34 @@ public void testReplaceUebung() {
assertEquals(Text.of("Uebung"), Text.of("\u00dcbung").replaceUmlaute());
}

/**
* Beim Adressvergleich von 300.000 wurde festgestellt, dass viel Zeit in
* {@link Text#replaceUmlaute)()} verbraucht wurde. Dies ist zwar kein
* echter Performance-Test, er gibt aber zumindestens Anhaltspunkte, ob
* die Performance sich verbessert hat.
*
* So dauert dieser Test auf einem Entwickler-Notebook von 2015 zwischen
* 0,8 und 1,3 ms fuer die urspruengliche Implementierung. Nach der
* Optimierung der Methode braucht sie jetzt zwischen 0,2 und 0,3 ms
* (auf dem gleichen Rechner).
*
* @throws IOException the io exception
*/
@Test
public void testReplaceUmlautePerformance() throws IOException {
Properties props = new Properties();
try (InputStream istream = getClass().getResourceAsStream("/de/jfachwert/messages_de.properties")) {
props.load(istream);
}
String s = props.toString();
LOG.info("replaceUmlaute started");
long t0 = System.nanoTime();
String r = Text.replaceUmlaute(s);
long t1 = System.nanoTime();
LOG.info("replaceUmlaute started ended after " + (t1 - t0) / 1000000.0 + " ms");
assertThat (r, not(containsString("W\u00e4hrung")));
}

@Test
public void testEqualsIgnoreCase() {
assertTrue(Text.of("hello").equalsIgnoreCase(Text.of("Hello")));
Expand Down

0 comments on commit e0f1186

Please sign in to comment.