Internowanie łańcuchów

Z Wikipedii, wolnej encyklopedii

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).

Linki zewnętrzne[edytuj | edytuj kod]