Internowanie łańcuchów
Internowanie łańcuchów lub internalizacja łańcuchów – metoda przechowywania i dostępu do obiektów typu String, w której identyfikatorem konkretnego obiektu znajdującego się w pamięci jest unikatowy łańcuch znaków stanowiący jednocześnie jego wartość. Bezpośrednie odwołania do tak przechowywanego obiektu polegają na podaniu jego nazwy jednoznacznie identyfikującej jego instancję, niezależnie od umiejscowienia w kodzie.
Internalizację łańcuchów można zobrazować stosując porównanie do obiektów tworzonych na podstawie klasy string z języka C++:
#include <iostream>
#include <string>
using std::string;
int main (int argc, char *argv[])
{
string a("tekst");
string b("tekst");
const char *ap = a.c_str();
const char *bp = b.c_str();
std::cout << "a = " << a << " (" << &ap << ")" << std::endl
<< "b = " << b << " (" << &bp << ")" << std::endl;
return(0);
}
Gdyby w powyższym przykładzie użyto łańcuchów internalizowanych zamiast typowych obiektów typu string to wartości stałych wskaźnikowych ap
i bp
byłyby takie same. Ponieważ w C++ nie istnieje wbudowana internalizacja łańcuchów, więc aby to osiągnąć należy użyć zewnętrznej klasy.
Obsługa
[edytuj | edytuj kod]Większość języków obsługujących internalizację łańcuchów pozwala tworzyć ich nazwy w dynamiczny sposób, na przykład z użyciem innych zmiennych i wyrażeń, tak jak w poniższym kodzie Ruby’ego:
a = "te"
b = "kst"
c = :tekst
puts 'adres symbolu o nazwie w zmiennej c = ' + c.object_id.to_s
puts 'adres symbolu o nazwie w zmiennych a i b = ' + :"#{a+b}".object_id.to_s # dynamicznie utworzona nazwa symbolu
Aby dostęp do internalizowanych obiektów był możliwy kompilator lub interpreter danego języka programowania tworzy wewnętrzną pulę internowania łańcuchów (ang. string intern pool), w której dochodzi do odwzorowania łańcuchów tekstowych będących ich identyfikatorami na wskaźniki do miejsc w pamięci pod którymi się znajdują. Nazwa internalizacja lub internowanie oznacza, że ten sposób obsługi obiektów polega na nadawaniu stworzonym w trakcie działania programu łańcuchom „wewnętrznego” znaczenia. Dzięki tej operacji stają się one semantycznie istotnym elementem programu, zbliżonym do nazw zmiennych.
Zastosowania
[edytuj | edytuj kod]Łańcuchy internalizowane używana są do obsługi często prezentowanych komunikatów tekstowych (np. stałych informacji diagnostycznych lub ich przedrostków), a także w połączeniu ze strukturami danych, które używają łańcuchów jako indeksów (np. tablice asocjacyjne). To ostatnie zastosowanie daje programiście pewność, że nie dojdzie do przypadkowej zmiany przechowywanego w zmiennej klucza indeksującego.
Dzięki internalizacji można uniknąć zbędnego duplikowania łańcuchów znaków i zaoszczędzić ilość pamięci zajmowanej przez uruchomiony program. Odwoływanie się do łańcuchów wskazywanych za pomocą ich nazw jest też wygodne w użyciu, szczególnie tam, gdzie znaczenie ma dynamiczny ich wybór dokonywany w zależności od warunków umieszczonych w programie – przyspiesza więc proces tworzenia i zwiększa czytelność kodu. Wadą internowania łańcuchów jest nieco dłuższy czas potrzebny na ich utworzenie lub dostęp do nich.
Internalizacja w językach programowania
[edytuj | edytuj kod]Internowanie łańcuchów dostępne jest w niektórych nowych, obiektowych językach programowania, włączając w to Pythona, Javę, Ruby’ego i języki działające w ramach platformy programistycznej Microsoft .NET. W niektórych językach użycie internalizacji wymaga jedynie posłużenia się odpowiednią składnią, a w innych konieczne jest wykorzystanie odwołania specjalnej klasy. Na przykład w Javie pojedyncza kopia każdego łańcucha, nazywana też jego internem, jest obsługiwana za pomocą metody klasy String o nazwie String.intern()
.
Java
[edytuj | edytuj kod]W Javie internalizacja obsługiwana jest za pomocą metody intern()
należącej do klasy String:
class Internalizacja {
public static void main(String[] args) {
String a = "tekst";
String b = new StringBuffer("te").append("kst").toString();
System.out.println("przed intern");
if (a == b) {
System.out.println("" + a.hashCode() + " i " + b.hashCode() + " - ten sam obiekt");
}
String c = a.intern();
String d = b.intern();
System.out.println("po intern");
if (c == d) {
System.out.println("" + c.hashCode() + " i " + d.hashCode() + " - ten sam obiekt");
}
}
}
Warto zauważyć, że stałe tekstowe Javy są internalizowane automatycznie:
class Internalizacja {
public static void main(String[] args) {
String a = "tekst";
String b = new StringBuffer("te").append("kst").toString().intern();
if (a == b) {
System.out.println("" + a.hashCode() + " i " + b.hashCode() + " - ten sam obiekt");
}
}
}
Ruby
[edytuj | edytuj kod]W języku Ruby internalizacja stosowana jest w odniesieniu podstawowego typu danych zwanego symbolem. Poniższy fragment kodu pokazuje różnicę między zwykłymi łańcuchami tekstowymi a łańcuchami internalizowanymi:
puts "Przypisanie tekstu"
a = "tekst"
b = "tekst"
puts 'adres a = ' + a.object_id.to_s
puts 'adres b = ' + b.object_id.to_s
puts "Przypisanie symbolu"
a = :tekst
b = :tekst
puts 'adres a = ' + a.object_id.to_s
puts 'adres b = ' + b.object_id.to_s
Po jego wykonaniu otrzymamy na przykład:
Przypisanie tekstu adres a = 82110 adres b = 82150 Przypisanie symbolu adres a = 101378 adres b = 101378
W przypadku użycia internalizowanych łańcuchów (w języku Ruby nazywanych symbolami) obiekty a
i b
zajmują ten sam obszar w pamięci.
Historia
[edytuj | edytuj kod]Pierwsze konstrukcje oznaczające internalizowane łańcuchy pojawiły się w języku programowania Lisp pod nazwą symboli atomowych lub atomów. Struktura danych używana do utrzymywania takiej puli łańcuchów nosiła nazwę oblist (w przypadku listy połączonej) lub obarray (w przypadku zaimplementowania jej w formie tablicy).