6 Stimmen

Vor- und Nachteile von unveränderlichen Zeichenketten

Einige Sprachen (C# oder Java) haben unveränderliche Zeichenfolgen, während andere (z.B. Ruby) veränderliche haben. Was sind die Gründe hinter diesen Designentscheidungen?

4 Stimmen

Hier ist etwas Ähnliches: stackoverflow.com/questions/3407403/…

0 Stimmen

@Science_Fiction Die beste Antwort auf diese Frage bezieht sich im Allgemeinen auf Unveränderlichkeit. Aber warum Strings? Ich glaube, es hat etwas mit dem Mark-and-Sweep-Garbage-Collector zu tun.

1 Stimmen

5voto

comingstorm Punkte 24293

Ein Grund, warum unveränderbare Zeichenfolgen gut sind, ist, dass dies die Unicode-Unterstützung erleichtert. Modernes Unicode passt nicht mehr effizient in eine festgelegte Datenzelle, was die Eins-zu-Eins-Korrespondenz zwischen Zeichenfolgenindex und Speicheradresse aufhebt, die veränderbare Zeichenfolgen so vorteilhaft macht.


In der Vergangenheit haben die meisten westlichen Anwendungen einzelbyte-Zeichen verwendet (verschiedene auf ASCII basierende Codierungen oder EBCDIC...), sodass Sie sie effizient behandeln konnten, indem Sie Zeichenfolgen als Bytepuffer behandeln (wie in traditionellen C-Anwendungen).

Als Unicode noch recht neu war, gab es nicht viel Anforderung an etwas außerhalb der ersten 16 Bits, daher verwendete Java doppelbyte-Zeichen für seine Strings (und StringBuffers). Dies hat doppelt so viel Speicher verwendet und ignorierte mögliche Probleme von Unicode-Erweiterungen über 16 Bits hinaus, aber es war damals bequem.

Jetzt ist Unicode nicht mehr so neu, und obwohl die am häufigsten verwendeten Zeichen immer noch in 16 Bits passen, können Sie sich nicht wirklich damit herausreden, dass das Basic Multilingual Plane alles ist, was existiert. Wenn Sie ehrlich behaupten wollen, Unicode-Unterstützung zu haben, benötigen Sie entweder Zeichen mit variabler Länge oder sogar größere (32-Bit?) Zeichenzellen.

Bei Zeichen mit variabler Länge können Sie nicht mehr in O(1) Zeit in einen Zeichenfolgen beliebiger Länge zugreifen - ohne zusätzliche Informationen müssen Sie sich vom Anfang an bis zum N-ten Zeichen durchzählen. Dies tötet auch den Hauptvorteil der veränderbaren Zeichenpuffer: die Fähigkeit, Teile von Zeichenfolgen nahtlos an Ort und Stelle zu ändern.

Zum Glück benötigt die meisten Zeichenfolgenmanipulation diese Möglichkeit zur Änderung vor Ort tatsächlich nicht. Das Lesen, Parsen und Suchen erfolgt alle auf sequentieller, iterativer Basis, von Anfang bis Ende. Die allgemeine Suchen-und-Ersetzen war nie vor Ort, da die Ersetzungszeichenfolge nicht die gleiche Länge wie das Original haben muss.


Das Konkatenieren großer Mengen von Teilzeichenfolgen benötigt tatsächlich keine Änderung vor Ort, um effizient zu sein. Sie müssen jedoch vorsichtiger damit umgehen, da ein einfacher Konkatenationsloop (wie andere bereits angemerkt haben) leicht in O(N^2) werden kann, indem für jeden der N Teile Teilzeichenfolgen eine neue Zeichenfolge allokiert wird...

Ein Weg, um eine einfache Konkatenation zu vermeiden, besteht darin, ein veränderbares StringBuffer oder ConcatBuffer-Objekt bereitzustellen, das für effiziente Konkatenation ausgelegt ist. Ein anderer Weg wäre, einen unveränderbaren Zeichenfolgenkonstruktor einzuschließen, der einen Iterator in eine Sequenz von Zeichenfolgen enthält, die (effizient) konkateniert werden sollen.

Aber generell ist es möglich, eine unveränderbare Zeichenfolgenbibliothek zu schreiben, die durch Referenz effizient konkateniert. Diese Art von Zeichenfolge wird oft als "rope" oder "Schnur" bezeichnet, um anzudeuten, dass sie etwas schwerwiegender ist als die grundlegenden Zeichenfolgen, aus denen sie besteht, aber für Konkatenationszwecke ist sie viel effizienter, da sie die Daten überhaupt nicht neu kopieren muss!

Der obige Wikipedia-Link besagt, dass "rope"-Datenstrukturen O(log N) zum Konkatenieren sind, aber das wegweisende Papier "Purely Functional Data Structures" von Okasaki zeigt, wie man Konkatenation in O(1) Zeit durchführt.

0 Stimmen

Nur ein paar Dinge, bei denen ich anderer Meinung bin. Ich denke, du hast alle falschen Punkte angesprochen. Zunächst einmal ist Unicode eine Zuordnung von Codepunkten zu Zeichen und Glyphen. Das gewählte Codierungsschema ist ein separates Thema, das sehr gut eins-zu-eins gemacht werden könnte - was den direkten Indexzugriff effizient macht. Auch wenn das Erkennen von Lexer, Syntaxanalyse und Suche nicht vor Ort geändert wird, gibt es viele andere Fälle, in denen dies der Fall ist - Ihre typischen toupper()/tolower()/titlecase()/mid()/left()/right()/reverse‌() nur um einige wenige häufige Fälle zu nennen, die alle vor Ort (mit einigen lokalen Ausnahmen) durchgeführt werden können. Sogar der Ersatz kann

0 Stimmen

In der Regel in-place durchgeführt, da veränderbare Zeichenfolgenobjekte in der Regel mehr Speicher reservieren, als sie verwenden (die meisten verwenden Potenzen von zwei, um das Gesamtwachstum amortisiert O(1) zu machen), was sie unter realen Bedingungen recht effizient macht.

2voto

templatetypedef Punkte 343693

Zumindest im Fall von Java lag ein Teil des Grundes für die Unveränderlichkeit von Strings in der Sicherheit und der Thread-Sicherheit. Java legt großen Wert auf Laufzeitsicherheit (ursprünglich wurde es entwickelt, um Set-Top-Boxen und Webbrowsern das Herunterladen und Ausführen von Remote-Inhalten ohne Beeinträchtigung des Host-Systems zu ermöglichen). Um die Sicherheit zu erhöhen, sind Strings unveränderlich und können nicht untergeordnet werden. Das bedeutet, dass die Java-Laufzeit Umgebungen und Benutzern Strings übergeben oder von ihnen empfangen kann, während sichergestellt wird, dass der Wert des Strings konstant bleibt (d. h., ein Angreifer kann den String nicht unterordnen, einen anscheinend gültigen String in eine Funktion übergeben und dann später den Wert ändern, um Zugriff auf falsche Daten zu erhalten, oder alternativ mehrere Threads verwenden, damit ein String zu einem Zeitpunkt korrekt erscheint, der dann jedoch später mutiert wird).

Zusätzlich bieten Unveränderlichkeit Effizienzvorteile in Multithread-Systemen, da keine Sperrung des Strings erforderlich ist. Außerdem ermöglicht es die einfache Implementierung von Teilzeichenfolgenoperationen, da viele Zeichenfolgen das gleiche zugrunde liegende Array von Zeichen teilen können, jedoch mit unterschiedlichen Start- und Endpunkten.

1voto

ddyer Punkte 1732

Wenn man darüber nachdenkt, sind alle grundlegenden Datentypen in einem Computer unveränderlich. Du änderst die Ganzzahl 10 nicht in 11, sondern ersetzt 10 durch 11. Indem Zeichenketten grundlegend und unveränderlich sind, ermöglicht das Pooling und andere Optimierungen, die sonst nicht möglich wären.

0 Stimmen

Und was macht einen Datentyp grundlegend?

0 Stimmen

Eine, die in die Sprache integriert ist (anstatt von einer Bibliothek oder Erweiterung hinzugefügt zu werden)

1 Stimmen

In manchen Sprachen ist das Zeichen der grundlegende Typ. Ein String ist einfach ein Array von Zeichen.

1voto

katspaugh Punkte 16624

Was die Nachteile betrifft, erfordern unveränderliche Zeichenfolgen ergänzende veränderbare Datenstrukturen (d.h. String-Puffers), um wirtschaftliches Anfügen, Neuanordnen und andere ähnliche Operationen zu ermöglichen.

Solche Operationen, die über unveränderlichen Strukturen durchgeführt werden, würden unvernünftige Mengen an Ressourcen erfordern.

Programmieren in Lua hat eine brillante Erklärung zum Thema.


Um weiter zu reflektieren, haben einige Sprachen (wie Common Lisp) sowohl nicht-destruktive als auch destruktive Funktionen, andere - sowohl unveränderliche als auch veränderliche Listen (Python).

Um ein Buch über Common Lisp zu zitieren:

Wenn Zuweisung so problembehaftet ist, warum sollte man sie dann nicht einfach aus der Sprache weglassen? Es gibt zwei Gründe: Ausdruckskraft und Effizienz. Zuweisung ist der klarste Weg, gemeinsame Daten zu ändern. Und Zuweisung ist effizienter als Bindung. Die Bindung erstellt einen neuen Speicherort, was Speicher zuweist, der zusätzlichen Speicher verbraucht (wenn die Bindung niemals den Gültigkeitsbereich verlässt) oder den Garbage Collector belastet (wenn die Bindung schließlich den Gültigkeitsbereich verlässt).


Allerdings verwenden viele JavaScript-Interpreten (die unveränderliche Zeichenfolgen besitzen) Strings auf der Implementierungsebene als veränderliche Arrays.

In ähnlicher Weise hat Clojure Transient, die wie elegante reine Funktionen über unveränderlichen Datenstrukturen aussehen, aber intern mutable Zustände für Effizienz verwenden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X