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

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

Ł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

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

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");
    }
  }
}

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

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