Java基本數據類型和Integer緩存機制


一、8種基本數據類型(4整,2浮,1符,1布)

​ 整型:byte(最小的數據類型)、short(短整型)、int(整型)、long(長整型);

​ 浮點型:float(浮點型)、double(雙精度浮點型);

​ 字符型:char(字符型);

​ 布爾型:boolean(布爾型)。

二、取值范圍

  數據類型名稱 占用字節 默認值 最小值最大值 對應包裝類
整數類型 byte 1 0 -128(-2^7)~ 127(2^7-1) Byte
整數類型 short 2 0 -32768(-2^15)~ 32767(2^15 - 1) Short
整數類型 int 4 0 -2,147,483,648(-2^31)~ 2,147,483,647(2^31 - 1) Integer
整數類型 long 8 0.0L -9,223,372,036,854,775,808(-2^63)~ 9,223,372,036,854,775,807(2^63 -1) Long
浮點類型 float 4 0.0f 1.4E-45 ~ 3.4028235E38 Float
浮點類型 double 8 0.0 4.9E-324 ~ 1.7976931348623157E308 Double
字符類型 char 2 \u0000(即為0)~ \uffff(即為65,535) Character
布爾類型 boolean 1 flase true 和 false Boolean

三、Integer 的緩存機制

Integer 緩存是 Java 5 中引入的一個有助於節省內存、提高性能的特性。

Integer的緩存機制: Integer是對小數據(-128~127)是有緩存的,再jvm初始化的時候,數據-128~127之間的數字便被緩存到了本地內存中,如果初始化-128~127之間的數字,會直接從內存中取出,不需要新建一個對象.

首先看一個使用 Integer 的示例代碼,展示了 Integer 的緩存行為。

  1. /**
  2. * 測試Integer的緩存 IntegerCache.cache
  3. */
  4. private static void testIntegerCache() {
  5. System.out.println("---int---");
  6. int a = 127, b = 127;
  7. System.out.println(a == b); //true
  8. a = 128;
  9. b = 128;
  10. System.out.println(a == b); //true
  11. System.out.println("---Integer---");
  12. Integer aa = 127, bb = 127;
  13. System.out.println(aa == bb); //true
  14. aa = 128;
  15. bb = 128;
  16. System.out.println(aa == bb); //false
  17. System.out.println(aa.equals(bb)); //true
  18. }

在 Java 5 中,為 Integer 的操作引入了一個新的特性,用來節省內存和提高性能。整型對象在內部實現中通過使用相同的對象引用實現了緩存和重用。
上面的規則適用於整數區間 -128 到 +127。

Java 編譯器把原始類型自動轉換為封裝類的過程稱為自動裝箱(autoboxing),這相當於調用 valueOf 方法。

下面是 JDK 1.8.0 build 25 中的代碼。

  1. /**
  2. * Returns an {@code Integer} instance representing the specified
  3. * {@code int} value. If a new {@code Integer} instance is not
  4. * required, this method should generally be used in preference to
  5. * the constructor {@link #Integer(int)}, as this method is likely
  6. * to yield significantly better space and time performance by
  7. * caching frequently requested values.
  8. *
  9. * This method will always cache values in the range -128 to 127,
  10. * inclusive, and may cache other values outside of this range.
  11. *
  12. * @param i an {@code int} value.
  13. * @return an {@code Integer} instance representing {@code i}.
  14. * @since 1.5
  15. */
  16. public static Integer valueOf(int i) {
  17. if (i >= IntegerCache.low && i <= IntegerCache.high)
  18. return IntegerCache.cache[i + (-IntegerCache.low)];
  19. return new Integer(i);
  20. }

在創建新的 Integer 對象之前會先在 IntegerCache.cache (是個Integer類型的數組)中查找。

IntegerCache 是 Integer 類中一個私有的靜態類,負責 Integer 的緩存。

  1. /**
  2. * Cache to support the object identity semantics of autoboxing for values between
  3. * -128 and 127 (inclusive) as required by JLS.
  4. *
  5. * The cache is initialized on first usage. The size of the cache
  6. * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
  7. * During VM initialization, java.lang.Integer.IntegerCache.high property
  8. * may be set and saved in the private system properties in the
  9. * sun.misc.VM class.
  10. */
  11. private static class IntegerCache {
  12. static final int low = -128;
  13. static final int high;
  14. static final Integer cache[];
  15. static {
  16. // high value may be configured by property
  17. int h = 127;
  18. String integerCacheHighPropValue =
  19. sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
  20. if (integerCacheHighPropValue != null) {
  21. try {
  22. int i = parseInt(integerCacheHighPropValue);
  23. i = Math.max(i, 127);
  24. // Maximum array size is Integer.MAX_VALUE
  25. h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
  26. } catch( NumberFormatException nfe) {
  27. // If the property cannot be parsed into an int, ignore it.
  28. }
  29. }
  30. high = h;
  31. cache = new Integer[(high - low) + 1];
  32. int j = low;
  33. for(int k = 0; k < cache.length; k++)
  34. cache[k] = new Integer(j++);
  35. // range [-128, 127] must be interned (JLS7 5.1.7)
  36. assert IntegerCache.high >= 127;
  37. }
  38. private IntegerCache() {}
  39. }

Javadoc 詳細的說明這個類是用來實現緩存支持,並支持 -128 到 127 之間的自動裝箱過程。最大值 127 可以通過 JVM 的啟動參數 -XX:AutoBoxCacheMax=size 修改。 緩存通過一個 for 循環實現。從小到大的創建盡可能多的整數並存儲在一個名為 cache 的整數數組中。這個緩存會在 Integer 類第一次被使用的時候被初始化出來。以后,就可以使用緩存中包含的實例對象,而不是創建一個新的實例(在自動裝箱的情況下)。

實際上在 Java 5 中引入這個特性的時候,范圍是固定的 -128 至 +127。后來在 Java 6 中,最大值映射到 java.lang.Integer.IntegerCache.high,可以使用 JVM 的啟動參數設置最大值。這使我們可以根據應用程序的實際情況靈活地調整來提高性能。是什么原因選擇這個 -128 到 127 這個范圍呢?因為這個范圍的整數值是使用最廣泛的。 在程序中第一次使用 Integer 的時候也需要一定的額外時間來初始化這個緩存。

Java 語言規范中的緩存行為
在 Boxing Conversion 部分的Java語言規范(JLS)規定如下:
如果一個變量 p 的值屬於:-128至127之間的整數(§3.10.1),true 和 false的布爾值 (§3.10.3),’u0000′ 至 ‘u007f’ 之間的字符(§3.10.4)中時,將 p 包裝成 a 和 b 兩個對象時,可以直接使用 a == b 判斷 a 和 b 的值是否相等。
其他緩存的對象
這種緩存行為不僅適用於Integer對象。我們針對所有整數類型的類都有類似的緩存機制。
有 ByteCache 用於緩存 Byte 對象
有 ShortCache 用於緩存 Short 對象
有 LongCache 用於緩存 Long 對象
有 CharacterCache 用於緩存 Character 對象
Byte,Short,Long 有固定范圍: -128 到 127。對於 Character, 范圍是 0 到 127。除了 Integer 可以通過參數改變范圍外,其它的都不行。

四、浮點型數據

浮點類型是指用於表示小數的數據類型。

單精度和雙精度的區別:

​ 單精度浮點型float,用32位存儲,1位為符號位, 指數8位, 尾數23位,即:float的精度是23位,能精確表達23位的數,超過就被截取。

​ 雙精度浮點型double,用64位存儲,1位符號位,11位指數,52位尾數,即:double的精度是52位,能精確表達52位的數,超過就被截取。

​ 雙精度類型double比單精度類型float具有更高的精度,和更大的表示范圍,常常用於科學計算等高精度場合。

浮點數與小數的區別:

​ 1)在賦值或者存儲中浮點類型的精度有限,float是23位,double是52位。

​ 2)在計算機實際處理和運算過程中,浮點數本質上是以二進制形式存在的。

​ 3)二進制所能表示的兩個相鄰的浮點值之間存在一定的間隙,浮點值越大,這個間隙也會越大。如果此時對較大的浮點數進行操作時,浮點數的精度問題就會產生,甚至出現一些“不正常”的現象。

為什么不能用浮點數來表示金額

先給出結論:金額用BigDecimal

1)精度丟失問題

​ 從上面我們可以知道,float的精度是23位,double精度是63位。在存儲或運算過程中,當超出精度時,超出部分會被截掉,由此就會造成誤差。

​ 對於金額而言,舍去不能表示的部分,損失也就產生了。

32位的浮點數由3部分組成:1比特的符號位,8比特的階碼(exponent,指數),23比特的尾數(Mantissa,尾數)。這個結構會表示成一個小數點左邊為1,以底數為2的科學計數法表示的二進制小數。浮點數的能表示的數據大小范圍由階碼決定,但是能夠表示的精度完全取決於尾數的長度。long的最大值是2的64次方減1,需要63個二進制位表示,即便是double,52位的尾數也無法完整的表示long的最大值。不能表示的部分也就只能被舍去了。對於金額,舍去不能表示的部分,損失也就產生了。
  了解了浮點數表示機制后,丟失精度的現象也就不難理解了。但是,這只是浮點數不能表示金額的原因之一。還有一個深刻的原因與進制轉換有關。十進制的0.1在二進制下將是一個無線循環小數。

  1. public class MyTest {
  2. public static void main(String[] args) {
  3. float increment = 0.1f;
  4. float expected = 1;
  5. float sum = 0;
  6. for (int i = 0; i < 10; i++) {
  7. sum += increment;
  8. System.out.println(sum);
  9. }
  10. if (expected == sum) {
  11. System.out.println("equal");
  12. } else {
  13. System.out.println("not equal ");
  14. }
  15. }
  16. }

輸出結果:

  1. 0.1
  2. 0.2
  3. 0.3
  4. 0.4
  5. 0.5
  6. 0.6
  7. 0.70000005
  8. 0.8000001
  9. 0.9000001
  10. 1.0000001
  11. not equal

2)進制轉換誤差

​ 從上面我們可以知道,在計算機實際處理和運算過程中,浮點數本質上是以二進制形式存在的。

​ 而十進制的0.1在二進制下將是一個無限循環小數,這就會導致誤差的出現。

​ 如果一個小數不是2的負整數次冪,用浮點數表示必然產生浮點誤差。

​ 換言之:A進制下的有限小數,轉換到B進制下極有可能是無限小數,誤差也由此產生。

​ 浮點數不精確的根本原因在於:尾數部分的位數是固定的,一旦需要表示的數字的精度高於浮點數的精度,那么必然產生誤差!

​ 解決這個問題的方法是BigDecimal的類,這個類可以表示任意精度的數字,其原理是:用字符串存儲數字,轉換為數組來模擬大數,實現兩個數組的數學運算並將結果返回。

BigDecimal的使用要點:

​ 1、BigDecimal變量初始化——必須用傳入String的構造方法

  1. `BigDecimal num1 = ``new` `BigDecimal(0.005);``//用數值轉換成大數,有誤差``BigDecimal num12 = ``new` `BigDecimal(``"0.005"``);``//用字符串轉換成大數,無誤差`

因為:不是所有的浮點數都能夠被精確的表示成一個double 類型值,有些浮點數值不能夠被精確的表示成 double 類型值時,它會被表示成與它最接近的 double 類型的值,此時用它來初始化一個大數,會“先造成了誤差,再用產生了誤差的值生成大數”,也就是“將錯就錯”。

​ 2、使用除法函數在divide的時候要設置各種參數,要精確的小數位數和舍入模式,其中有8種舍入模式:

  1. 1ROUND_UP
  2. 遠離零的舍入模式。
  3. 在丟棄非零部分之前始終增加數字(始終對非零舍棄部分前面的數字加1)。
  4. 注意,此舍入模式始終不會減少計算值的大小。
  5. 2ROUND_DOWN
  6. 接近零的舍入模式。
  7. 在丟棄某部分之前始終不增加數字(從不對舍棄部分前面的數字加1,即截短)。
  8. 注意,此舍入模式始終不會增加計算值的大小。
  9. 3ROUND_CEILING
  10. 接近正無窮大的舍入模式。
  11. 如果 BigDecimal 為正,則舍入行為與 ROUND_UP 相同;
  12. 如果為負,則舍入行為與 ROUND_DOWN 相同。
  13. 注意,此舍入模式始終不會減少計算值。
  14. 4ROUND_FLOOR
  15. 接近負無窮大的舍入模式。
  16. 如果 BigDecimal 為正,則舍入行為與 ROUND_DOWN 相同;
  17. 如果為負,則舍入行為與 ROUND_UP 相同。
  18. 注意,此舍入模式始終不會增加計算值。
  19. 5ROUND_HALF_UP
  20. 向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則為向上舍入的舍入模式。
  21. 如果舍棄部分 >= 0.5,則舍入行為與 ROUND_UP 相同;否則舍入行為與 ROUND_DOWN 相同。
  22. 注意,這是我們大多數人在小學時就學過的舍入模式(四舍五入)。
  23. 6ROUND_HALF_DOWN
  24. 向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則為上舍入的舍入模式。
  25. 如果舍棄部分 > 0.5,則舍入行為與 ROUND_UP 相同;否則舍入行為與 ROUND_DOWN 相同(五舍六入)。
  26. 7ROUND_HALF_EVEN
  27. 向“最接近的”數字舍入,如果與兩個相鄰數字的距離相等,則向相鄰的偶數舍入。
  28. 如果舍棄部分左邊的數字為奇數,則舍入行為與 ROUND_HALF_UP 相同;
  29. 如果為偶數,則舍入行為與 ROUND_HALF_DOWN 相同。
  30. 注意,在重復進行一系列計算時,此舍入模式可以將累加錯誤減到最小。
  31. 此舍入模式也稱為“銀行家舍入法”,主要在美國使用。
  32. 如果前一位為奇數,則入位,否則舍去。
  33. 以下例子為保留小數點1位,那么這種舍入方式下的結果。
  34. 1.15>1.2 1.25>1.2
  35. 8ROUND_UNNECESSARY
  36. 斷言請求的操作具有精確的結果,因此不需要舍入。
  37. 如果對獲得精確結果的操作指定此舍入模式,則拋出ArithmeticException

經典面試題

1、short s1 = 1; s1 = s1 + 1;有什么錯? short s1 = 1; s1 +=1;有什么錯?

答:對於short s1=1;s1=s1+1來說,在s1+1運算時會自動提升表達式的類型為int,那么將int賦予給short類型的變量s1會出現類型轉換錯誤。

對於short s1=1;s1+=1來說 +=是java語言規定的運算符,java編譯器會對它進行特殊處理,因此可以正確編譯。

2、char類型變量能不能儲存一個中文的漢子,為什么?

char類型變量是用來儲存Unicode編碼的字符的,unicode字符集包含了漢字,所以char類型當然可以存儲漢字的,還有一種特殊情況就是某個生僻字沒有包含在unicode編碼字符集中,那么就char類型就不能存儲該生僻字。

3、Integer和int的區別

int是java的8種內置的原始數據類型。Java為每個原始類型都提供了一個封裝類,Integer就是int的封裝類。

int變量的默認值為0,Integer變量的默認值為null,這一點說明Integer可以區分出未賦值和值為0的區別,比如說一名學生沒來參加考試,另一名學生參加考試全答錯了,那么第一名考生的成績應該是null,第二名考生的成績應該是0分。關於這一點Integer應用很大的。Integer類內提供了一些關於整數操作的一些方法,例如上文用到的表示整數的最大值和最小值。

4、switch語句能否作用在byte上,能否作用在long上,能否作用在string上?

byte的存儲范圍小於int,可以向int類型進行隱式轉換,所以switch可以作用在byte上

long的存儲范圍大於int,不能向int進行隱式轉換,只能強制轉換,所以switch不可以作用在long上

string在1.7版本之前不可以,1.7版本之后switch就可以作用在string上了

5.是否存在 x>x+1?為什么?

這就是臨界值,當x=最大值 時; 再加1(根據二進制運算+1)就超過了它的臨界值,剛好會是它最小值。

舉個例子吧,byte 8位, -128 ~ 127

127 二進制: 0111 1111

1 二進制 : 0000 0001

相加結果: 1000 0000

byte 8位 有符號, 1000 0000 剛好 為 -128

Reference

java 基礎—8 種基本數據類型:整型、浮點型、布爾型、字符型 整型中 byte、short、int、long 的取值范圍 什么是浮點型?什么是單精度和雙精度?為什么不能用浮點型表示金額?

理解Java Integer的緩存策略


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM