2012-05-28 19 views
10

Potrzebowałabym zapisać ciąg kodu języka, na przykład "en", który zawsze będzie zawierał 2 znaki.W jaki sposób typy ciągów i znaków są przechowywane w pamięci w .NET?

Czy lepiej zdefiniować typ jako "Ciąg" lub "Szar"?

private string languageCode; 

vs

private char[] languageCode; 

czy też istnieje inna, lepsza opcja?

W jaki sposób są przechowywane 2 w pamięci? ile bajtów lub bitów zostanie im przydzielonych przy przypisaniu wartości?

+5

Czy udowodniłeś sobie, że to już problem? Bardzo rzadko zdawałem sobie sprawę z potrzeby martwienia się o pamięć podczas korzystania z ciągów - zwłaszcza takich małych. Jeśli nie pojawia się jako problem, nie przejmuj się nim, dopóki nie pojawi się. To łatwa łatka w późniejszym terminie, jeśli łańcuchy powodują problem z pamięcią. W przeciwnym razie użyj łańcucha i nawet nie myśl o problemach z pamięcią. –

+0

Mam bardzo intensywną logikę, która przechowuje tysiące z nich w pamięci, więc każda mała pomaga. –

+0

@ William Jeśli wydajność jest tak ważna, dlaczego nie zadeklarować 'enum LanguageCode: short' i zapisać 2 bajty? –

Odpowiedz

8

jak są one przechowywane

Zarówno string i char[] są przechowywane na stercie - więc przechowywanie jest taka sama. Wewnętrznie zakładam, że string jest po prostu osłoną dla char[] z partii dodatkowego kodu, aby był dla ciebie przydatny.

Również jeśli masz dużo powtarzających się ciągów, możesz użyć wartości Interning, aby zmniejszyć ślad pamięci tych ciągów.

lepszym rozwiązaniem

chciałbym faworyzować ciąg - to od razu bardziej widoczne, co typ danych jest i jak zamierzasz go używać. Ludzie są również bardziej przyzwyczajeni do używania łańcuchów, więc konserwacja nie ucierpi. Zyskasz także wiele korzyści z całego kodu standardowego, który został zrobiony dla ciebie. Firma Microsoft dokłada również wszelkich starań, aby upewnić się, że typ string nie jest technologią wydajności.

alokacji Rozmiar

nie mam pojęcia, ile jest alokowana, wierzę ciągi są całkiem skuteczne w tym, że tylko przeznaczyć wystarczająco do przechowywania znaków Unicode - ponieważ są one niezmienne jest to bezpieczne, aby to zrobić . Tablic nie można również zmienić rozmiaru bez przydzielania miejsca w nowej tablicy, więc ponownie zakładam, że pobierają tylko to, czego potrzebują.

Overhead of a .NET array?

Alternatywy

na podstawie informacji, że istnieją tylko 20 kody językowe i wydajność jest kluczowa, można zadeklarować swoją enum w celu zmniejszenia wielkości wymaganej do reprezentowania kodów:

enum LanguageCode : byte 
{ 
    en = 0, 
} 

ten będzie jedynie 1 bajt w przeciwieństwie do 4+ dla dwóch char (w tablicy), ale nie ogranicza zakresu dostępny w miejscach e LanguageCode wartości do zakresu byte - co jest więcej niż wystarczająco duże dla 20 elementów.

Możesz zobaczyć rozmiar typów wartości za pomocą operatora sizeof(): sizeof(LanguageCode). Wyliczenia są tylko podstawowym typem pod maską, domyślnie są to int, ale jak widać w moim przykładzie kodu, można to zmienić, "dziedzicząc" nowy typ.

+0

Nie jawnie internować ciągi w .Net; są internowani dla ciebie bezwarunkowo przez ich zwykłą deklarację. Ponadto łańcuchy znaków i tablice znaków są bardzo różne w .Net, ponieważ tablice znaków są zmiennymi strukturami na stercie, a nawet stos, w zależności od tego, jak je zadeklarowaliśmy, podczas gdy łańcuchy są niezmienne i jak artykuł, który łączyłeś z notatkami, kompilacja i budować i budować w puli intern, zamiast zwykłej pamięci .Net Framework - co oznacza, że ​​mogą być bardzo nieekonomiczne. –

+0

@ChrisMoschini Nie wszystkie napisy są internowane. Literały są internowane, ale nic więcej nie jest. Jeśli pobierzesz ciąg znaków, odczytaj go z pliku zasobów lub innego źródła, którego nie internował. Musisz internować je ręcznie. Co ciekawe, moja odpowiedź nie wskazywała ani w żaden sposób. –

+0

To zależy od tego, w jaki sposób zostanie napisany kod - na przykład, jeśli wyszukuje pęk bitów zadeklarowanych w kodzie, niemniej jednak kończy się zbiorem internowanych ciągów. Jednak ważnym problemem związanym z wydajnością jest wyrzucanie mnóstwa niepotrzebnych ciągów pośrednich na stercie, gdy wiesz, że ich nie potrzebujesz - pojedyncza tablica znaków zawsze będzie znacznie tańsza pod względem pamięci i jeśli napiszesz swój kod podobny do wewnątrz Regex, tańszy procesor. Mniej pamięci w sieci .Net oznacza także mniej GC, który ma inną zaletę procesora. –

0

Jeśli chcesz zapisać dokładnie 2 znaki, i robią to najbardziej efektywnie używać struct:

struct Char2 
{ 
public char C1, C2; 
} 

Korzystanie z tej struktury nie będzie zazwyczaj powodować nowe przydziały sterty. Po prostu rozbuduje istniejący obiekt (o minimalną możliwą ilość) lub zużyje przestrzeń stosu, która jest bardzo tania.

+0

Przydziały sterty zależą wyłącznie od * gdzie * deklarujesz strukturę. Będzie on umieszczony na stosie tylko wtedy, gdy zostanie zadeklarowany wewnątrz metod/właściwości. Wewnątrz klasy będzie na kupce, wraz z resztą członków klasy. –

+0

Nie spowoduje to przydzielenia * nowego *. Po prostu rozbuduje istniejący obiekt (o minimalną możliwą kwotę). – usr

+0

Tak, prawda, ale alokacje sterty są zazwyczaj bardzo szybkie i początkowo nie powinno się martwić. Mimo to, struktura 'struct LanguageCode' jest dobrym rozwiązaniem. –

4

Krótka odpowiedź: Użyj ciąg

odpowiedź Long:

private string languageCode; 

AFAIK ciągi są przechowywane jako długość prefiksem tablicy znaków. Obiekt String jest tworzony na stercie, aby zachować tę surową tablicę. Ale obiekt String jest czymś więcej niż zwykłą tablicę to umożliwia podstawowe operacje ciąg jak porównania, konkatenacji, podciąg ekstrakcji, wyszukiwania itp

Podczas

private char[] languageCode; 

będą przechowywane jako tablica znaków tj obiekt Array zostanie utworzony na stercie, a następnie zostanie użyty do zarządzania twoimi postaciami. Ale nadal ma atrybut długości, który jest przechowywany wewnętrznie, więc nie ma widocznych oszczędności w pamięci w porównaniu do łańcucha. Chociaż prawdopodobnie tablica jest prostsza niż ciąg i może zawierać mniej zmiennych wewnętrznych, co zapewnia mniejszy wydruk stopy pamięci (wymaga to weryfikacji).

Ale OTOH tracisz możliwość wykonywania operacji na łańcuchach na tej tablicy znaków. Nawet operacje takie jak porównywanie ciągów stają się teraz kłopotliwe. Tak długa historia używać ciąg!

1

W jaki sposób są przechowywane 2 w pamięci? ile bajtów lub bitów zostanie im przydzielonych przy przypisaniu wartości?

Każdy instancja w .NET jest przechowywany w następujący sposób: jeden IntPtr -sized pole dla identyfikatora typu; jeszcze jeden do blokowania instancji; pozostałe dane pola instancji są zaokrąglane do rozmiaru o rozmiarze IntPtr. W związku z tym na 32-bitowej platformie każda instancja zajmuje 8 bajtów + dane pola.

Dotyczy to zarówno string, jak i char[]. Oba z nich przechowują również długość danych jako całkowitą liczbę IntPtr, a następnie rzeczywiste dane. Tak więc dwuliterowy string i dwuliterowy char[] na 32-bitowej platformie zajmą 8 + 4 + 4 = 16 bajtów.

Jedynym sposobem na zmniejszenie tego przy przechowywaniu dokładnie dwóch znaków jest przechowywanie rzeczywistych znaków lub struktury zawierającej znaki w polu lub tablicy. Wszystkie będą zużywać tylko 4 bajty w postaci:

// Option 1 
class MyClass 
{ 
    char Char1, Char2; 
} 

// Option 2 
class MyClass 
{ 
    CharStruct chars; 
} 
... 
struct CharStruct { public char Char1; public char Char2; } 

MyClass kończy się za pomocą 8 bajtów (w urządzeniu 32-bitowy) na przykład plus 4 bajty dla znaków.

// Option 3 
class MyClass 
{ 
    CharStruct[] chars; 
} 

zostanie użyte 8 bajtów narzutu MojaKlasa plus 4 bajty dla charsodniesieniu plus 12 bajtów narzutu Array plus 4 bajtów CharStruct w tablicy.

+0

Interesujące. Skąd masz te informacje? – kristianp

+1

@kristianp Wiele z tych informacji pochodzi z tego artykułu MSDN: https://msdn.microsoft.com/en-us/magazine/cc163791.aspx (przewiń w dół do rysunku 6) –

0

Łańcuchy rzeczywiście mają rozmiar narzutowy o długości jednego wskaźnika, tj. 4 bajty dla procesu 32-bitowego, 8 bajtów dla procesu 64-bitowego. Ale znowu, struny oferują o wiele więcej w zamian niż tablice char.

Jeśli twoja aplikacja używa wielu krótkich łańcuchów i nie musisz używać swoich właściwości i metod łańcuchów, które często, prawdopodobnie możesz zabezpieczyć kilka bajtów pamięci. Ale jeśli chcesz użyć któregokolwiek z nich jako ciągu, najpierw musisz utworzyć nową instancję łańcucha. Nie rozumiem, jak to pomoże ci zachować wystarczająco dużo pamięci, by być warte kłopotów.

Powiązane problemy