W Javie istnieją prymitywne typy na byte
, short
, int
a long
i to samo dla float
i double
. Dlaczego osoba musi ustawić, ile bajtów należy użyć dla wartości pierwotnej? Czy nie można dynamicznie określić rozmiaru w zależności od tego, jak duża była przekazywana liczba?
Są dwa powody, dla których mogę myśleć:
- Dynamiczne ustawienie rozmiaru danych oznaczałoby, że musiałaby również istnieć możliwość dynamicznej zmiany. Może to potencjalnie powodować problemy z wydajnością?
- Być może programista nie chciałby, aby ktoś mógł użyć liczby większej niż określony rozmiar, co pozwala mu to ograniczyć.
Nadal uważam, że można wiele zyskać, używając pojedynczego int
i pojedynczego float
typu. Czy był jakiś konkretny powód, dla którego Java zdecydowała się nie iść tą drogą?
java
language-design
data-types
numbers
yitzih
źródło
źródło
Odpowiedzi:
Podobnie jak wiele innych aspektów projektowania języka, dochodzi do kompromisu elegancji z wydajnością (nie wspominając o historycznych wpływach wcześniejszych języków).
Alternatywy
Z pewnością jest możliwe (i dość proste) stworzenie języka programowania, który ma tylko jeden typ liczb naturalnych
nat
. Prawie wszystkie języki programowania używane w badaniach akademickich (np. PCF, System F) mają ten jeden typ liczbowy, który, jak się domyślacie, jest bardziej eleganckim rozwiązaniem. Ale projektowanie języka w praktyce nie polega wyłącznie na elegancji; musimy również wziąć pod uwagę wydajność (zakres, w jakim brana jest pod uwagę wydajność, zależy od zamierzonego zastosowania języka). Wydajność obejmuje zarówno czas, jak i przestrzeń.Ograniczenia przestrzeni
Pozwalając programiście wybrać z góry liczbę bajtów, można zaoszczędzić miejsce w programach o ograniczonej pamięci. Jeśli wszystkie liczby będą mniejsze niż 256, możesz użyć 8 razy więcej
byte
s niżlong
s lub użyć zapisanej pamięci dla bardziej złożonych obiektów. Standardowy programista aplikacji Java nie musi się martwić tymi ograniczeniami, ale pojawiają się.Wydajność
Nawet jeśli zignorujemy miejsce, nadal jesteśmy ograniczeni przez procesor, który ma tylko instrukcje działające na określonej liczbie bajtów (8 bajtów w architekturze 64-bitowej). Oznacza to, że nawet podanie pojedynczego 8-bajtowego
long
typu znacznie uprościłoby implementację języka niż posiadanie nieograniczonego typu liczby naturalnej, dzięki możliwości odwzorowania operacji arytmetycznych bezpośrednio na pojedyncze podstawowe instrukcje procesora. Jeśli zezwolisz programiście na użycie dowolnie dużych liczb, wówczas pojedyncza operacja arytmetyczna musi zostać odwzorowana na sekwencję złożonych instrukcji maszyny, co spowolniłoby program. To jest punkt (1), który przywołałeś.Typy zmiennoprzecinkowe
Dotychczasowa dyskusja dotyczyła tylko liczb całkowitych. Typy zmiennoprzecinkowe to złożona bestia o niezwykle subtelnej semantyce i przypadkowych przypadkach. Tak więc, choć możemy łatwo zastąpić
int
,long
,short
, ibyte
za pomocą jednegonat
typu, nie jest jasne, jaki rodzaj liczb zmiennoprzecinkowych nawet jest . Nie są to oczywiście liczby rzeczywiste, ponieważ liczby rzeczywiste nie mogą istnieć w języku programowania. Nie są to również liczby racjonalne (choć w razie potrzeby można łatwo utworzyć racjonalny typ). Zasadniczo IEEE zdecydowało się na sposób przybliżenia liczb rzeczywistych i od tego czasu wszystkie języki (i programiści) utknęli z nimi.Wreszcie:
To nie jest prawidłowy powód. Po pierwsze, nie mogę wymyślić żadnych sytuacji, w których typy mogłyby naturalnie kodować granice liczbowe, nie wspominając już o astronomicznie niskich szansach, że granice, które programista chce egzekwować, odpowiadają dokładnie rozmiarom dowolnego z pierwotnych typów.
źródło
type my_type = int (7, 2343)
?Powód jest bardzo prosty: wydajność . Na wiele sposobów.
Rodzime typy danych: Im bardziej typy danych języka odpowiadają bazowym typom danych sprzętu, tym bardziej efektywny jest język. (Nie w tym sensie, że twoje programy będą koniecznie wydajne, ale w tym sensie, że jeśli naprawdę wiesz, co robisz, możesz napisać kod, który będzie działał tak wydajnie, jak sprzęt może go uruchomić.) Oferowane typy danych przez Java odpowiada bajtom, słowom, podwójnym słowom i poczwórnym słowom najpopularniejszego sprzętu. To najbardziej efektywny sposób.
Nieuzasadnione koszty ogólne w systemach 32-bitowych: gdyby podjęto decyzję o odwzorowaniu wszystkiego na stały rozmiar 64-bitowy, nałożyłoby to ogromną karę na architektury 32-bitowe, które potrzebują znacznie więcej cykli zegara, aby wykonać 64- operacja bitowa niż operacja 32-bitowa.
Marnotrawstwo pamięci: istnieje wiele sprzętu, który nie jest zbyt wybredny w zakresie wyrównywania pamięci (przykłady to architektury Intel x86 i x64), więc tablica 100 bajtów na tym sprzęcie może zajmować tylko 100 bajtów pamięci. Jeśli jednak nie masz już bajtu i zamiast tego musisz użyć długiej, ta sama tablica zajmie pamięć o rząd wielkości. A tablice bajtów są bardzo powszechne.
Obliczanie wielkości liczb: Twoje pojęcie dynamicznego określania wielkości liczby całkowitej w zależności od tego, jak duża była przekazywana liczba, jest zbyt uproszczone; nie ma jednego punktu „przekazania” liczby; obliczenie, jak duża musi być liczba, musi zostać wykonane w czasie wykonywania, dla każdej pojedynczej operacji, która może wymagać wyniku większego rozmiaru: za każdym razem, gdy liczba jest zwiększana, za każdym razem, gdy dodajesz dwie liczby, za każdym razem mnożąc dwie liczby itp.
Operacje na liczbach o różnych rozmiarach: Następnie posiadanie w pamięci liczb o potencjalnie różnych rozmiarach skomplikowałoby wszystkie operacje: Nawet w celu zwykłego porównania dwóch liczb środowisko wykonawcze musiałoby najpierw sprawdzić, czy obie liczby do porównania są takie same rozmiar, a jeśli nie, zmień rozmiar mniejszego, aby dopasować go do większego.
Operacje wymagające określonych rozmiarów operandów: Niektóre operacje bitowe zależą od liczby całkowitej mającej określony rozmiar. Ponieważ nie określono wcześniej określonego rozmiaru, operacje te musiałyby zostać emulowane.
Narzut polimorfizmu: zmiana rozmiaru liczby w czasie wykonywania oznacza w zasadzie, że musi być polimorficzna. To z kolei oznacza, że nie może być prymitywem o stałym rozmiarze przydzielonym na stosie, musi to być obiekt przydzielony na stercie. To jest okropnie nieefektywne. (Ponownie przeczytaj nr 1 powyżej.)
źródło
Aby uniknąć powtarzania punktów, które zostały omówione w innych odpowiedziach, spróbuję nakreślić wiele perspektyw.
Z perspektywy projektowania języka
Przyczyny historyczne
Jest to już omówione w artykule Wikipedii na temat historii Javy, a także krótko omówione w odpowiedzi Marco13 .
Chciałbym zauważyć, że:
Przyczyny wydajności
Kiedy wydajność ma znaczenie?
Wydajność przechowywania (w pamięci lub na dysku)
Wydajność wykonania (w procesorze lub między procesorem a pamięcią)
Potrzeba języków programowania zapewniających abstrakcję dla małych liczb całkowitych, nawet jeśli jest ograniczona do określonych kontekstów
Interoperacyjność
char
tablicę o rozmiarze 256. (Przykład.)BitConverter
) Ułatwiające pakowanie i rozpakowywanie wąskich liczb całkowitych do strumieni bitów i strumieni bajtów.Obsługa ciągów
Obsługa formatu pliku
Celowość, jakość oprogramowania i odpowiedzialność programisty
Rozważ następujący scenariusz.
Często oprogramowanie, które można bezpiecznie skalować o wiele rzędów wielkości, musi być zaprojektowane w tym celu ze zwiększającą się złożonością. Nie przychodzi automatycznie, nawet jeśli problem przepełnienia liczb całkowitych zostanie wyeliminowany. Dzieje się to w pełnym kręgu odpowiadającym perspektywie projektowania języka: często oprogramowanie, które odmawia wykonania pracy, gdy nastąpi niezamierzone przepełnienie liczb całkowitych (przez zgłoszenie błędu lub wyjątku), jest lepsze niż oprogramowanie, które automatycznie wykonuje astronomicznie duże operacje.
Oznacza to perspektywę PO,
nie jest poprawne. Programiści powinni mieć możliwość, a czasem wymagane, określenie maksymalnej wielkości , jaką może przyjąć wartość całkowita w krytycznych częściach oprogramowania. Jak wskazuje odpowiedź ogrodnika , naturalne ograniczenia narzucone przez prymitywne typy nie są przydatne w tym celu; język musi zapewniać programistom sposoby deklarowania wielkości i egzekwowania takich ograniczeń.
źródło
Wszystko pochodzi od sprzętu.
Bajt to najmniejsza adresowalna jednostka pamięci na większości urządzeń.
Każdy wspomniany typ jest zbudowany z pewnej wielokrotności bajtów.
Bajt ma 8 bitów. Dzięki temu możesz wyrazić 8 booleanów, ale nie możesz wyszukać tylko jednego na raz. Adresujesz 1, adresujesz wszystkie 8.
Kiedyś było to takie proste, ale potem przeszliśmy z 8-bitowej magistrali na 16, 32, a teraz 64-bitową.
Co oznacza, że chociaż wciąż możemy adresować na poziomie bajtów, nie możemy odzyskać ani jednego bajtu z pamięci bez uzyskania sąsiednich bajtów.
W obliczu tego sprzętu projektanci języków postanowili pozwolić nam wybrać typy, które pozwoliły nam wybrać typy pasujące do sprzętu.
Możesz twierdzić, że taki szczegół może i powinien zostać wyodrębniony, zwłaszcza w języku, który ma działać na dowolnym sprzęcie. Miałoby to ukryte problemy z wydajnością, ale możesz mieć rację. Po prostu tak się nie stało.
Java faktycznie próbuje to zrobić. Bajty są automatycznie promowane do Ints. Fakt, który doprowadzi Cię do szału przy pierwszej próbie wykonania jakiejkolwiek poważnej zmiany.
Dlaczego więc nie zadziałało dobrze?
Wielką zaletą Javy jest to, że można było usiąść ze znanym dobrym algorytmem C, wpisać go w Javie i przy drobnych poprawkach zadziałałoby. A C jest bardzo blisko sprzętu.
Utrzymywanie tego i wyodrębnianie rozmiaru z integralnych typów po prostu nie działało razem.
Więc mogliby. Po prostu nie.
To jest prawidłowe myślenie. Istnieją na to sposoby. Funkcja zacisku dla jednego. Język może posunąć się tak daleko, że ustanowi dowolne granice dla swoich typów. A kiedy te granice są znane w czasie kompilacji, pozwoli to na optymalizację sposobu przechowywania tych liczb.
Java po prostu nie jest tym językiem.
źródło
Prawdopodobnie jednym z ważnych powodów, dla których te typy istnieją w Javie, jest prosty i niepokojąco nietechniczny:
C i C ++ również miały te typy!
Chociaż trudno jest udowodnić, że jest to powód, istnieją co najmniej pewne mocne dowody: Specyfikacja języka dębowego (wersja 0.2) zawiera następujący fragment:
Pytanie może sprowadzać się do:
Dlaczego krótkie, int i długie zostały wynalezione w C?
Nie jestem pewien, czy odpowiedź na pytanie listowe jest zadowalająca w kontekście zadanego tutaj pytania. Ale w połączeniu z innymi odpowiedziami tutaj może stać się jasne, że posiadanie tych typów może być korzystne (niezależnie od tego, czy ich istnienie w Javie jest jedynie dziedzictwem C / C ++).
Najważniejsze powody, o których mogę myśleć
Bajt to najmniejsza adresowalna jednostka pamięci (jak już wspomniano CandiedOrange). A
byte
to podstawowy element danych, który można odczytać z pliku lub przez sieć. Niektóre powinny istnieć wyraźne przedstawienie tego procesu (i to nie istnieje w większości języków, nawet jeśli czasem przychodzi w przebraniu).Prawdą jest, że w praktyce sensowne byłoby reprezentowanie wszystkich pól i zmiennych lokalnych za pomocą jednego typu i wywoływanie tego typu
int
. Istnieje pokrewne pytanie na ten temat podczas przepływu stosu: Dlaczego interfejs API Java używa int zamiast krótkiego lub bajtu? . Jak wspomniałem w mojej odpowiedzi, jednym z uzasadnień posiadania mniejszych typów (byte
ishort
) jest to, że możesz tworzyć tablice tych typów: Java ma reprezentację tablic, która wciąż jest raczej „bliska sprzętowi”. W przeciwieństwie do innych języków (i w przeciwieństwie do tablic obiektów, takich jakInteger[n]
tablica),int[n]
tablica nie jest zbiorem referencji, w których wartości są rozproszone po całym stosie. Zamiast tego będziew praktyce być kolejnym blokiemn*4
bajtów - jednym kawałkiem pamięci o znanym rozmiarze i układzie danych. Gdy masz do wyboru przechowywanie 1000 bajtów w zbiorze obiektów o wartościach całkowitych o dowolnym rozmiarze lub wbyte[1000]
(który zajmuje 1000 bajtów), ten ostatni może rzeczywiście zaoszczędzić trochę pamięci. (Niektóre inne zalety tego mogą być bardziej subtelne i stają się oczywiste tylko w przypadku łączenia Javy z bibliotekami natywnymi)Odnośnie punktów, o które konkretnie pytałeś:
Prawdopodobnie byłoby możliwe dynamiczne ustawienie wielkości zmiennych, gdyby wziąć pod uwagę zaprojektowanie zupełnie nowego języka programowania od zera. Nie jestem ekspertem w budowie kompilatorów, ale myślę, że trudno byłoby rozsądnie manipulować kolekcjami dynamicznie zmieniających się typów - szczególnie, gdy masz mocno napisany język. Prawdopodobnie sprowadzałoby się to do tego, że wszystkie liczby są przechowywane w „ogólnym, arbitralnym typie danych liczbowych o precyzji precyzyjnej”, co z pewnością miałoby wpływ na wydajność. Oczywiście, nie są języki programowania, które są silnie wpisane i / lub zaoferować dowolnie wielkości typów numerycznych, ale nie sądzę, że jest to prawdziwy język programowania ogólnego przeznaczenia, który udał się w ten sposób.
Notatki dodatkowe:
Być może zastanawiałeś się nad
unsigned
modyfikatorem wymienionym w specyfikacji Oak. W rzeczywistości zawiera także uwagę: „unsigned
nie jest jeszcze zaimplementowany; może nigdy nie będzie”. . I mieli rację.Oprócz zastanawiania się, dlaczego C / C ++ w ogóle ma te różne typy liczb całkowitych, możesz zastanawiać się, dlaczego tak je pomieszali, że nigdy nie wiesz, ile bitów
int
ma. Uzasadnienia tego są zwykle związane z wydajnością i można je sprawdzić gdzie indziej.źródło
To z pewnością pokazuje, że jeszcze nie uczysz o wydajności i architekturze.
Ignorując znaczenie wielkości danych, zawsze wpływa to na wydajność, musisz zużywać tyle zasobów, ile to konieczne, ale nigdy więcej, zawsze!
Taka jest różnica między programem lub systemem, który robi naprawdę proste rzeczy i jest niesamowicie nieefektywny, wymagający dużej ilości zasobów i powodujący, że korzystanie z tego systemu jest naprawdę kosztowne; lub system, który dużo robi, ale działa szybciej niż inne i jest naprawdę tani w obsłudze.
źródło
Istnieje kilka dobrych powodów
(1) podczas gdy przechowywanie jednego bajtu zmiennych wierszowych jeden długi jest nieznaczne, przechowywanie milionów w tablicy jest bardzo znaczące.
(2) „natywna” arytmetyka oparta na konkretnych liczbach całkowitych może być znacznie wydajniejsza, a dla niektórych algorytmów na niektórych platformach może być ważna.
źródło