Tại sao API Java sử dụng int
, khi nào short
hoặc thậm chí byte
là đủ?
Ví dụ: DAY_OF_WEEK
Trường trong lớp Calendar
sử dụng int
.
Nếu sự khác biệt quá nhỏ, thì tại sao những kiểu dữ liệu đó ( short
, int
) lại tồn tại?
Tại sao API Java sử dụng int
, khi nào short
hoặc thậm chí byte
là đủ?
Ví dụ: DAY_OF_WEEK
Trường trong lớp Calendar
sử dụng int
.
Nếu sự khác biệt quá nhỏ, thì tại sao những kiểu dữ liệu đó ( short
, int
) lại tồn tại?
Câu trả lời:
Một số lý do đã được chỉ ra. Ví dụ, thực tế là "... (Hầu hết) Tất cả các hoạt động trên byte, short sẽ thúc đẩy các nguyên hàm này thành int" . Tuy nhiên, câu hỏi tiếp theo rõ ràng sẽ là: TẠI SAO các loại này được quảng bá int
?
Vì vậy, để đi sâu hơn một cấp: Câu trả lời có thể chỉ đơn giản là liên quan đến Bộ hướng dẫn máy ảo Java. Như tóm tắt trong Bảng trong Java Virtual Machine Đặc điểm kỹ thuật , tất cả các phép tính số học không thể thiếu, như thêm, chia và những người khác, chỉ có sẵn cho các loại int
và các loại long
, và không cho các loại nhỏ hơn.
(Bỏ qua một bên: Các loại nhỏ hơn ( byte
và short
) về cơ bản chỉ dành cho các mảng . Một mảng như new byte[1000]
sẽ lấy 1000 byte và một mảng như thế new int[1000]
sẽ mất 4000 byte)
Bây giờ, tất nhiên, người ta có thể nói rằng "... câu hỏi tiếp theo rõ ràng sẽ là: TẠI SAO những hướng dẫn này chỉ được cung cấp cho int
(và long
)?" .
Một lý do được đề cập trong Spec JVM được đề cập ở trên:
Nếu mỗi lệnh được nhập hỗ trợ tất cả các kiểu dữ liệu thời gian chạy của Máy ảo Java, sẽ có nhiều hướng dẫn hơn mức có thể được biểu thị trong một byte
Ngoài ra, Máy ảo Java có thể được coi là một bản tóm tắt của bộ xử lý thực. Và giới thiệu Đơn vị logic số học chuyên dụng cho các loại nhỏ hơn sẽ không đáng nỗ lực: Nó sẽ cần thêm các bóng bán dẫn, nhưng nó vẫn chỉ có thể thực hiện một phép cộng trong một chu kỳ đồng hồ. Kiến trúc thống trị khi JVM được thiết kế là 32 bit, vừa phải cho 32 bit int
. (Các hoạt động liên quan đến long
giá trị 64 bit được thực hiện như một trường hợp đặc biệt).
(Lưu ý: Đoạn cuối hơi quá đơn giản, xem xét khả năng vector hóa có thể, v.v., nhưng nên đưa ra ý tưởng cơ bản mà không đi sâu vào các chủ đề thiết kế bộ xử lý)
EDIT: Một phụ lục ngắn, tập trung vào ví dụ từ câu hỏi, nhưng theo nghĩa chung hơn: Người ta cũng có thể hỏi liệu có hữu ích khi lưu trữ các trường bằng cách sử dụng các loại nhỏ hơn không. Ví dụ, người ta có thể nghĩ rằng bộ nhớ có thể được lưu bằng cách lưu trữ Calendar.DAY_OF_WEEK
dưới dạng a byte
. Nhưng ở đây, Định dạng tệp lớp Java xuất hiện: Tất cả các trường trong tệp lớp chiếm ít nhất một "khe", có kích thước bằng một int
(32 bit). (Các trường "rộng" double
và long
, chiếm hai vị trí). Vì vậy, tuyên bố rõ ràng một trường là short
hoặc byte
sẽ không lưu bất kỳ bộ nhớ.
int
. Nếu bạn có một tham chiếu đến một triển khai khác, tôi sẽ cập nhật câu trả lời và chèn liên kết tương ứng.
(Hầu hết) Tất cả các hoạt động trên byte
, short
sẽ thúc đẩy chúng int
, ví dụ, bạn không thể viết:
short x = 1;
short y = 2;
short z = x + y; //error
Arithologists dễ dàng và đơn giản hơn khi sử dụng int
, không cần phải đúc.
Về mặt không gian, nó tạo ra một sự khác biệt rất nhỏ. byte
và short
sẽ làm phức tạp mọi thứ, tôi không nghĩ rằng tối ưu hóa vi mô này đáng giá vì chúng ta đang nói về một lượng biến cố định.
byte
có liên quan và hữu ích khi bạn lập trình cho các thiết bị nhúng hoặc xử lý các tệp / mạng. Ngoài ra những nguyên thủy này còn hạn chế, nếu tính toán có thể vượt quá giới hạn của chúng trong tương lai thì sao? Hãy thử nghĩ về một phần mở rộng cho Calendar
lớp có thể phát triển số lượng lớn hơn.
Cũng lưu ý rằng trong một bộ vi xử lý 64-bit, người dân địa phương sẽ được lưu trong thanh ghi và sẽ không sử dụng bất kỳ tài nguyên, vì vậy sử dụng int
, short
và nguyên thủy khác sẽ không thực hiện bất kỳ khác biệt nào cả. Hơn nữa, nhiều triển khai Java sắp xếp các biến * (và các đối tượng).
* byte
Và short
chiếm không gian giống như int
nếu họ là địa phương biến, lớp biến hoặc thậm chí dụ biến. Tại sao? Bởi vì trong (hầu hết) các hệ thống máy tính, các địa chỉ biến được căn chỉnh , vì vậy, ví dụ nếu bạn sử dụng một byte đơn, thực tế bạn sẽ kết thúc với hai byte - một cho chính biến đó và một cho đệm.
Mặt khác, trong các mảng, byte
lấy 1 byte, short
lấy 2 byte và int
lấy bốn byte, bởi vì trong mảng chỉ có phần đầu và có thể phần cuối của nó phải được căn chỉnh. Điều này sẽ tạo ra sự khác biệt trong trường hợp bạn muốn sử dụng, ví dụ System.arraycopy()
, sau đó bạn sẽ thực sự ghi nhận sự khác biệt về hiệu suất.
Bởi vì các phép toán số học dễ dàng hơn khi sử dụng số nguyên so với quần short. Giả sử rằng các hằng số thực sự được mô hình hóa bằng short
các giá trị. Sau đó, bạn sẽ phải sử dụng API theo cách này:
short month = Calendar.JUNE;
month = month + (short) 1; // is july
Chú ý đúc rõ ràng. Các giá trị ngắn được ngầm phát huy thành int
các giá trị khi chúng được sử dụng trong các phép toán số học. (Trên ngăn toán hạng, quần short thậm chí được biểu thị bằng số nguyên.) Điều này sẽ khá cồng kềnh khi sử dụng, đó là lý do tại sao int
các giá trị thường được ưa thích cho các hằng số.
So với điều đó, mức tăng hiệu quả lưu trữ là tối thiểu vì chỉ tồn tại một số lượng cố định như vậy. Chúng ta đang nói về 40 hằng số. Thay đổi lưu trữ của họ từ int
để short
Would an toàn bạn 40 * 16 bit = 80 byte
. Xem câu trả lời này để tham khảo thêm.
Nếu bạn đã sử dụng triết lý trong đó các hằng số tích phân được lưu trữ theo loại nhỏ nhất mà chúng phù hợp, thì Java sẽ có một vấn đề nghiêm trọng: bất cứ khi nào lập trình viên viết mã bằng các hằng số tích phân, họ phải chú ý cẩn thận đến mã của họ để kiểm tra xem loại các hằng số quan trọng, và nếu vậy hãy tra cứu loại trong tài liệu và / hoặc thực hiện bất kỳ chuyển đổi loại nào là cần thiết.
Vì vậy, bây giờ chúng tôi đã vạch ra một vấn đề nghiêm trọng, bạn có thể hy vọng đạt được những lợi ích gì với triết lý đó? Tôi sẽ không ngạc nhiên nếu hiệu ứng duy nhất có thể quan sát được của sự thay đổi đó sẽ là loại bạn nhận được khi bạn nhìn lên hằng số thông qua sự phản chiếu. (và, tất nhiên, bất kỳ lỗi nào được đưa ra bởi các lập trình viên lười biếng / không mong muốn không hạch toán chính xác các loại hằng số)
Cân nhắc ưu và nhược điểm là rất dễ: đó là một triết lý tồi.
Sự phức tạp trong thiết kế của một máy ảo là một chức năng có bao nhiêu loại hoạt động mà nó có thể thực hiện. Thật dễ dàng hơn khi có bốn triển khai một lệnh như "nhân" - mỗi lần cho số nguyên 32 bit, số nguyên 64 bit, dấu phẩy động 32 bit và dấu phẩy động 64 bit - ngoài ra còn có ở trên, các phiên bản cho các loại số nhỏ hơn là tốt. Một câu hỏi thiết kế thú vị hơn là tại sao nên có bốn loại, thay vì ít hơn (thực hiện tất cả các tính toán số nguyên với số nguyên 64 bit và / hoặc thực hiện tất cả các tính toán dấu phẩy động với các giá trị dấu phẩy động 64 bit). Lý do sử dụng số nguyên 32 bit là Java dự kiến sẽ chạy trên nhiều nền tảng trong đó các loại 32 bit có thể được xử lý nhanh như các loại 16 bit hoặc 8 bit, nhưng các hoạt động trên các loại 64 bit sẽ đáng chú ý Chậm hơn.chỉ có loại 32 bit.
Đối với việc thực hiện tính toán dấu phẩy động trên các giá trị 32 bit, các ưu điểm hơi rõ ràng hơn một chút. Có một số nền tảng trong đó tính toán nhưfloat a=b+c+d;
có thể được thực hiện nhanh nhất bằng cách chuyển đổi tất cả các toán hạng thành loại có độ chính xác cao hơn, thêm chúng và sau đó chuyển đổi kết quả trở lại thành số dấu phẩy động 32 bit để lưu trữ. Có những nền tảng khác sẽ hiệu quả hơn khi thực hiện tất cả các tính toán bằng cách sử dụng các giá trị dấu phẩy động 32 bit. Những người tạo ra Java đã quyết định rằng tất cả các nền tảng nên được yêu cầu thực hiện theo cùng một cách và họ nên ưu tiên các nền tảng phần cứng để tính toán dấu phẩy động 32 bit nhanh hơn các nền tảng dài hơn, mặc dù PC này bị suy giảm nghiêm trọng cả về tốc độ và độ chính xác của toán học dấu phẩy động trên một PC điển hình, cũng như trên nhiều máy không có đơn vị dấu phẩy động. Lưu ý, btw, tùy thuộc vào các giá trị của b, c và d, sử dụng các tính toán trung gian có độ chính xác cao hơn khi tính toán các biểu thức như đã nói ở trênfloat a=b+c+d;
đôi khi sẽ mang lại kết quả chính xác hơn đáng kể so với tất cả các toán hạng trung gian được tính toán float
chính xác, nhưng đôi khi sẽ mang lại một giá trị ít chính xác hơn một chút. Trong mọi trường hợp, Sun quyết định mọi thứ nên được thực hiện theo cùng một cách và họ đã chọn sử dụng các float
giá trị độ chính xác tối thiểu .
Lưu ý rằng các ưu điểm chính của các loại dữ liệu nhỏ hơn trở nên rõ ràng khi số lượng lớn chúng được lưu trữ cùng nhau trong một mảng; ngay cả khi không có lợi thế nào khi có các biến riêng lẻ có loại nhỏ hơn 64 bit, thì đáng để có các mảng có thể lưu trữ các giá trị nhỏ gọn hơn; có một biến cục bộ là một byte
thay vì long
tiết kiệm bảy byte; có một mảng 1.000.000 số giữ mỗi số byte
thay vì mộtlong
sóng 7.000.000 byte. Vì mỗi loại mảng chỉ cần hỗ trợ một vài thao tác (đáng chú ý nhất là đọc một mục, lưu trữ một mục, sao chép một phạm vi các mục trong một mảng hoặc sao chép một phạm vi các mục từ mảng này sang mảng khác), độ phức tạp thêm của việc có nhiều hơn các kiểu mảng không nghiêm trọng bằng sự phức tạp của việc có nhiều loại giá trị số rời rạc có thể sử dụng trực tiếp hơn.
Trên thực tế, có một lợi thế nhỏ. Nếu bạn có một
class MyTimeAndDayOfWeek {
byte dayOfWeek;
byte hour;
byte minute;
byte second;
}
sau đó trên một JVM điển hình, nó cần nhiều không gian như một lớp chứa một đơn int
. Mức tiêu thụ bộ nhớ được làm tròn thành bội số 8 hoặc 16 byte tiếp theo (IIRC, có thể định cấu hình được), vì vậy các trường hợp khi có lưu thực sự là khá hiếm.
Lớp này sẽ dễ sử dụng hơn một chút nếu các Calendar
phương thức tương ứng trả về a byte
. Nhưng không có Calendar
phương thức nào như vậy , chỉ có phương thức get(int)
phải trả về int
vì các trường khác. Mỗi thao tác trên các loại nhỏ hơn đều khuyến khích int
, vì vậy bạn cần đúc rất nhiều.
Rất có thể, bạn sẽ từ bỏ và chuyển sang một int
hoặc viết setters như
void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = checkedCastToByte(dayOfWeek);
}
Sau đó, loại DAY_OF_WEEK
không quan trọng.