整理一下前面幾篇文章,按順序閱讀效果更好。java
走進 JDK 之 Floatbash
今天來講說 Byte
。微信
public final class Byte extends Number implements Comparable<Byte> 複製代碼
和以前的如出一轍,不可變類,繼承了抽象類 Number
,實現了 Comparable
接口。數據結構
private final byte value; // 包裝的 byte 值
public static final byte MIN_VALUE = -128; // 最小值是 -128
public static final byte MAX_VALUE = 127; // 最大值是 127
public static final Class<Byte> TYPE = (Class<Byte>) Class.getPrimitiveClass("byte");
public static final int SIZE = 8; // byte 佔 8 bits
public static final int BYTES = SIZE / Byte.SIZE; // byte 佔一個字節
private static final long serialVersionUID = -7183698231559129828L;
複製代碼
都是很熟悉的屬性,再也不過多分析了。這裏提第一個問題,爲何最大值是 127
,最小值是 -128
,最小值的絕對值能夠比最大值的絕對值大 1
呢 ?這裏先不說,看完代碼再來解答。函數
public Byte(byte value) {
this.value = value;
}
public Byte(String s) throws NumberFormatException {
this.value = parseByte(s, 10);
}
複製代碼
兩個構造函數。第一個傳入 byte
直接給 value
賦值,第二個傳入字符串,調用 parseByte()
方法轉換爲 byte
。post
public static byte parseByte(String s, int radix) throws NumberFormatException {
int i = Integer.parseInt(s, radix);
if (i < MIN_VALUE || i > MAX_VALUE)
throw new NumberFormatException(
"Value out of range. Value:\"" + s + "\" Radix:" + radix);
return (byte)i;
}
複製代碼
調用 Integer.parseInt()
方法轉換爲 int
,再強轉 byte
。Integer.parseInt()
詳細解析見 走進 JDK 之 Integer 。不光是 parseInt()
方法,Byte.java
中還有好幾個地方都是當作 int
來處理,後面的分析中將會看到。this
這裏再提一個問題,做爲方法內部局部變量的 byte
在內存中佔幾個字節 ?spa
public static Byte valueOf(String s, int radix) throws NumberFormatException {
return valueOf(parseByte(s, radix));
}
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
複製代碼
再來看一下 ByteCache
:
private static class ByteCache {
private ByteCache(){}
static final Byte cache[] = new Byte[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}
複製代碼
一樣也是緩存了 -128
到 127
,也就是說緩存了 byte
的全部可取值。
public String toString() {
return Integer.toString((int)value);
}
複製代碼
toString()
方法直接調用了 Integer.toString()
。
其餘的確沒啥好說的了,Byte
類源碼比較簡單,認真度過 Integer
源碼的同窗,大概瀏覽一下就有數了。後面來回答一下前面提問的兩個問題。
爲何 Byte 最小值的絕對值比最大值的絕對值大
1
呢 ?
其實不光是 Byte
,Java 的因此基本整數類型都是這樣(固然不包括 char
) :
基本類型 | 最大值 | 最小值 |
---|---|---|
byte | 127 | -128 |
short | 215-1 | - 215 |
int | 231-1 | 231 |
long | 263-1 | -263 |
能夠看到取值範圍都是不對稱的,負數的範圍比正數的範圍都大 1
。解釋這個問題以前,先來看幾個基本概念:
1
例如 -8
,其原碼是 1000 1000
,反碼是 1111 0111
,補碼是 1111 1000
。那麼計算機中到底存儲的是哪一種形式呢?這就要涉及到減法運算了。相比減法運算,計算機是更樂意作加法運算的,若是遇到 1 - 8
這道題目,它就會想我計算 1 + (-8)
不是一個道理嗎,最好我還能不把符號位當符號位,一塊兒做加法,還能提升一點運算效率。那麼,負數的加法運算怎麼作的,咱們來嘗試一下。
首先,咱們按原碼計算:
1 + (-8) = (0000 0001)(原) + (1000 1000)(原) = (1000 1001)(原) = -9
複製代碼
顯然不正確。再看反碼:
1 + (-8) = (0000 0001)(反) + (1111 0111)(反) = (1111 1000)(反) = (1000 0111)(原) = -7
複製代碼
好像沒什麼毛病,計算結果很正確。再換個例子看看:
1 + (-1) = (0000 0001)(反) + (1111 1110)(反) = (1111 1111)(反) = (1000 0000)(原) = -0
複製代碼
上篇文章解析 Float 時說過,浮點數是區分 +0.0
和 -0.0
的。可是整數的 0
是沒有正負之分的,用反碼無法解決 -0
的問題。最後來看一下補碼運算是否會存在 -0
:
1 + (-1) = (0000 0001)(補) + (1111 1111)(補) = (0000 0000)(補) = (0000 0000)(原) = 0
複製代碼
經過進位把符號位的 1
給溢出了,從而避免產生了 -0
。
綜上所述,補碼是比較適合在計算機中來表示整數的,實際上大多數計算機也正是這麼作的。再回到這個 -0
,二進制表示爲 1000 0000
,總不能把它丟掉吧,多點表示範圍老是好的,就把它定爲了 -128
,而且它沒有反碼,也沒有補碼,它就是 -128
。
如今咱們知道了 -128
其實就是替代了 -0
的存在。再來講一個知識點,你會更加直觀的瞭解 -128
。如何快速的將補碼轉換爲十進制數?其實不論正數仍是負數,補碼二進制轉換爲咱們熟悉的十進制都遵循相同的規律。看看下面幾個轉換:
15 = (0000 1111)(補)
= - 0*2^8 + (1*2^3 + 1*2^2 + 1*2^1 +1*2^0)
= 0 + 8 + 4 + 2 + 1
-15 = (1111 0001)(補)
= - 1*2^8 + (1*2^6 + 1*2^5 + 1*2^4 +1*2^0)
= -128 + 64 + 32 + 16 + 1
複製代碼
不須要轉換爲源碼,直接按補碼計算。正數符號位表示爲 0
,負數符號位表示爲 -128
。顯而易見,最小的負數確定是 10000 0000 = -128 + 0 + 0 + ...
。如今你應該對個問題很清楚了吧。下面看第二個問題:
做爲方法內部局部變量的
byte
在內存中佔幾個字節 ?
乍看之下我在問一個廢話,byte
那不願定是 1
個字節嗎 !沒錯,byte
是一個字節,可是我這個問題有特定的條件,做爲方法內部局部變量的 byte
。咱們一般所說的 byte
佔一個字節,指的是若是在 java 堆上分配一個 byte
,那麼就是一個字節。同理,int
就是四個字節。那麼,方法內的局部變量
是存儲在堆上的嗎?顯然不是的,它是存儲在棧中的。若是不理解的話,咱們先來回顧一下 Java 的運行時數據區域。
Java 的運行時數據區包含一下幾塊:
咱們一般所說的棧就是指 Java 虛擬機棧。每個線程都有本身的 Java 虛擬機棧,用於存儲棧幀。棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構。每一個方法在執行的同時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應一個棧幀在虛擬機棧中入棧到出棧的過程。因此,方法內的局部變量 byte
不出意外應該就是存儲在局部變量中了。那麼,局部變量表的結構又是怎麼樣的呢?
局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的變量。在 Java 程序編譯 Class 文件時,就在方法的 Code
屬性的 max_locals
數據項中肯定了該方法所需分配的局部變量表的最大容量。在我以前一篇文章 Class 文件格式詳解 中,詳細解析了 Class 文件結構,咱們再來回顧一下它的 main()
方法的 Code
屬性:
max_stack
表明了操做數棧深度的最大值。在方法執行的任意時刻,操做數棧都不會超過這個深度。虛擬機運行的時候須要根據這個值來分配棧幀中的操做棧深度。
max_locals
表明了局部變量表所需的存儲空間,以 slot 爲單位。Slot 是虛擬機爲局部變量分配內存所使用的最小單位。簡而言之,棧幀就是一個 Slot[]
,利用下標來訪問數組元素。那麼,對於不一樣的數據類型是如何處理的呢?這裏就是典型的以空間換時間。除了 long
和 double
佔用兩個 Slot
之外,其餘基本類型 boolean
、byte
、char
、short
、int
、float
等都佔用一個 Slot
。這樣就而已快速的利用下標索引來進行定位了。因此,在局部變量表中,byte
和 int
佔用的內存是同樣的。
Byte
源碼沒有說的不少,不少方法都是直接調用 Integer
類的方法。後面主要說了兩個知識點:
-128
,也就是 1000 0000
long
和 double
佔兩個 Slot
,其餘都佔用一個 Slot
文章同步更新於微信公衆號:
秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!