Ein besserer Weg, um viele Zeichenfolgen zu ersetzen - Verschleierung in C #

8

Ich versuche, eine große Menge an Daten zu verschleiern. Ich habe eine Liste von Wörtern (Token) erstellt, die ich ersetzen möchte, und ich ersetze die Wörter nacheinander mit der StringBuilder-Klasse wie folgt:

%Vor%

Es ist ziemlich langsam! Gibt es irgendwelche einfachen Dinge, die ich tun kann, um es zu beschleunigen?

Tokens ist eine Liste von etwa eintausend Strings mit einer Länge von jeweils 5 bis 15 Zeichen.

    
Mr. Flibble 02.04.2009, 21:49
quelle

4 Antworten

13

Statt Ersetzungen in einer großen Zeichenfolge auszuführen (was bedeutet, dass Sie sich um viele Daten bewegen), arbeiten Sie die Zeichenfolge ab und ersetzen Sie jeweils ein Token.

Erstellen Sie eine Liste mit dem nächsten Index für jedes Token, suchen Sie das erste Token, und kopieren Sie den Text bis zum Token in das Ergebnis, gefolgt vom Ersetzen des Tokens. Überprüfen Sie dann, wo sich das nächste Vorkommen dieses Tokens in der Zeichenfolge befindet, um die Liste auf dem neuesten Stand zu halten. Wiederholen Sie den Vorgang, bis keine weiteren Token gefunden wurden, und kopieren Sie den restlichen Text in das Ergebnis.

Ich habe einen einfachen Test gemacht, und diese Methode hat 125000 Ersetzungen auf einer 1000000 Zeichenkette in 208 Millisekunden durchgeführt.

Token- und TokenList-Klassen:

%Vor%

Anwendungsbeispiel:

%Vor%

Ausgabe:

%Vor%

Hinweis: Dieser Code behandelt keine überlappenden Token. Wenn Sie zum Beispiel die Token "Ananas" und "Apfel" haben, funktioniert der Code nicht richtig.

Bearbeiten:
Damit der Code mit überlappenden Tokens funktioniert, ersetzen Sie diese Zeile:

%Vor%

mit diesem Code:

%Vor%     
Guffa 02.04.2009, 23:41
quelle
5

OK, Sie sehen, warum es lange dauert, oder?

Sie haben 1 MB Strings und für jedes Token durchläuft replace die 1 MB und erstellt eine neue 1 MB Kopie. Nun, keine exakte Kopie, da ein gefundenes Token durch den neuen Token-Wert ersetzt wird. Aber für jedes Token lesen Sie 1 MB, erstellen 1 MB Speicherplatz neu und schreiben 1 MB.

Können wir uns jetzt einen besseren Weg vorstellen? Wie wäre es, anstatt die 1-MB-Zeichenfolge für jedes Token zu wiederholen, gehen wir stattdessen einmal vor.

Bevor wir es gehen, erstellen wir eine leere Ausgabezeichenfolge.

Wenn wir die Quellzeichenfolge durchlaufen, werden wir, wenn wir ein Token finden, token.length() -Zeichen nach vorne springen und das verschleierte Token ausschreiben. Andernfalls gehen wir zum nächsten Zeichen über.

Im Wesentlichen drehen wir den Prozess um, machen die for-Schleife auf der langen Kette und suchen bei jedem Punkt nach einem Token. Um das schnell zu machen, wollen wir die Token schnell loopen, also legen wir sie in eine Art assoziatives Array (eine Menge).

  

Ich sehe, warum es lange in Ordnung ist,   aber nicht sicher auf die Lösung. Für jede 1 MB   String, auf dem ich spiele   Ersatz, ich habe 1 bis 2 Tausend   Tokans möchte ich ersetzen. Also gehen   Charakter für Charakter sucht nach etwas   von tausend Token scheint nicht   schneller

Was dauert generell am längsten bei der Programmierung? Speicher neu erstellen.

Wenn wir nun einen StringBuffer erzeugen, wird wahrscheinlich eine Menge Speicherplatz zugewiesen (z. B. 64 Bytes) und jedes Mal, wenn wir mehr als die aktuelle Kapazität anhängen, wird der Speicherplatz verdoppelt der alte Zeichenpuffer zu dem neuen Zeichen. (Es ist möglich, dass wir C erneut zuweisen können und nicht kopieren müssen.)

Wenn wir also mit 64 Bytes beginnen, um 1 MB zu bekommen, ordnen und kopieren wir: 64, dann 128, dann 256, dann 512, dann 1024, dann 2048 ... das machen wir zwanzigmal, um bis zu 1 MB zu bekommen. Und um hierher zu kommen, haben wir 1 MB zugewiesen, nur um es wegzuwerfen.

Vorzuordnen, indem wir etwas verwenden, das der Funktion reserve() von C ++ analog ist, lässt uns das alles auf einmal machen. Aber es ist immer noch alles für jedes Token. Sie erzeugen mindestens eine 1 MB temporäre Zeichenfolge für jedes Token. Wenn Sie 2000 Token haben, ordnen Sie etwa 2 Milliarden Byte Speicher zu, alle mit 1 MB. Jedes 1-MB-Wegwerfelement enthält die Transformation der vorherigen resultierenden Zeichenfolge, wobei das aktuelle Token angewendet wird.

Und deshalb dauert es so lange.

Nun ja, die Entscheidung darüber, welches Token (falls vorhanden) bei jedem Zeichen angewendet wird, braucht auch Zeit. Vielleicht möchten Sie einen regulären Ausdruck verwenden, der intern eine Zustandsmaschine aufbaut, um alle Möglichkeiten zu durchlaufen, anstatt eine festgelegte Suche, wie ich es anfangs vorgeschlagen habe. Aber was dich wirklich umbringt, ist die Zeit, all diesen Speicher für 2000 Kopien einer 1-MB-Zeichenfolge zu reservieren.

Dan Gibson schlägt vor:

  

Sortieren Sie Ihre Tokens, damit Sie nicht müssen   Suche nach jeweils 1000 Token   Charakter. Die Sorte würde etwas brauchen   Zeit, aber es würde wahrscheinlich enden   schneller sein, da du es nicht musst   suche tausende Token   Zeichen.

Das war meine Überlegung, sie in ein assoziatives Array zu setzen (z. B. Java HashSet). Aber das andere Problem ist die Übereinstimmung, z. B. wenn ein Token "a" und ein anderes "an" ist - wenn es irgendwelche gemeinsamen Präfixe gibt, das heißt, wie passen wir zusammen?

Hier kommt die Antwort von Keltex zum Tragen: Er delegiert das Matching an einen Regex, was eine großartige Idee ist, wie Regex bereits definiert (greedy match) und implementiert, wie das geht. Sobald die Übereinstimmung hergestellt ist, können wir untersuchen, was erfasst wurde, und dann eine Java-Map (auch ein assoziatives Array) verwenden, um das verschleierte Token für den übereinstimmenden, unverschmutzten Token zu finden.

Ich wollte meine Antwort darauf konzentrieren, nicht nur, wie ich das beheben kann, sondern auch, warum es überhaupt ein Problem gab.

    
tpdi 02.04.2009 22:07
quelle
2

Wenn Sie Ihre Tokens über einen regulären Ausdruck finden können, können Sie Folgendes tun:

%Vor%

Definieren Sie dann Replacer als:

%Vor%     
Keltex 02.04.2009 22:06
quelle
1

Wäre es schneller, einen String nach dem anderen zu bauen, nur wenn nötig? Dazu könnte GetObfuscatedString() wie folgt implementiert werden:

%Vor%

Nun können Sie dem Builder wie folgt jedes Token hinzufügen:

%Vor%

Sie müssen nur einen Durchlauf über die Zeichenfolge machen, und es könnte schneller sein.

    
OwenP 02.04.2009 22:01
quelle