1、 無符號和有符號
計算機中用補碼表示負數,並且有一定的計算方式;另外,用二進制的最高位表示符號,0表示正數、1表示負數。這種說法本身沒錯,可是要有一定的解釋,不然它就是錯的,至少不能解釋,為什么字符類型的-1二進制表示是“1111 1111”16進制表示為FF,而不是1000 0001。
在計算機中,可以區分正負的類型,稱為有符號類型,無正負的類型,稱為無符號類型。
使用二進制中的最高位表示正負
一個字節為8位,按0開始記,那它的最高位就是第7位,2個字節,最高位就是15位,4個字節,最高位是31位,不同長度的類型,最高位雖不同,但總是最左邊那位。

無符號和有符號數的范圍的區別
無符號數中,所有的位都用於直接表示該值的大小;有符號數中最高位用於表示正負,所以,正值時,該數的最大值就會變小:
無符號數:1111 1111 值:255=1*2^7+1*2^6+.....=2^n-1
有符號數:0111 1111 值:127
同樣一個字節,無符號的最大值是255,有符號的最大值是127,下圖是無符號數和有符號數的范圍:

一個由符號的數據類型的最大值的計算方法完全和無符號一樣,只不過它少了一個最高位,但是在負數范圍內,數值的計算方法不能直接使用前面的公式,在計算機種,負數除了最高位為1以外,還采用補碼的形式,所以在計算前,需要對補碼進行還原。
以10進制的計算經驗,1表示正1,-1表示和1相對的負值。那么很容易想到在二進制中,0000 0001表示正1,則高位為1后:1000 0001應該表示-1,不過實際上,計算機中的規定有些相反:

可以發現,1000 0000沒有拿來表示-0,而1000 0001也不能拿來直觀地表示-1,事實上,-1用1111 1111來表示。從一個角度來理解,-1大還是-128大,當然是-1大,-1是最大的負整數,所以,無論是字符類型或整數類型,也無論這個整數是幾個字節,從計算結果上來看也是對的:1111 1111-1=1111 1110,表示-2,這樣一直減下去,當減到只身最高位用於表示符號的1以外,其他低位全為0,就是最小的負值,也就是-128:

2、 Java基本數據類型


3、 Java的符號類型
Java的原始類型里沒有無符號類型,如果需要某個寬度的無符號類型,可以用>>>,這個是java的無符號右移操作符,或者使用下一個寬度的帶符號類型來模擬,例如,需無符號的short,就用int來模擬:
1 int toUnsigned(short s) { 2 return s & 0x0FFFF; 3 }
十進制的字面常理只有一個特性,就是所有的十進制字面常量都是正數,如果想寫一個負的十進制,則需要在正的十進制字面常量前面加上“-”就好了。
十六進制或者八進制的字面常量就不一定是正數或者負數,如果最高位是1,那么就是負數:
1 System.out.println(0x80);//128 2 //0x81看作是int型,最高位(第32位)為0,所以是正數 3 System.out.println(0x81);//129 4 System.out.println(0x8001);//32769 5 System.out.println(0x70000001);//1879048193 6 //字面量0x80000001為int型,最高位(第32位)為1,所以是負數 7 System.out.println(0x80000001);//-2147483647 8 //字面量0x80000001L強制轉為long型,最高位(第64位)為0,所以是正數 9 System.out.println(0x80000001L);//2147483649 10 //最小int型 11 System.out.println(0x80000000);//-2147483648 12 //只要超過32位,就需要在字面常量后加L強轉long,否則編譯時出錯 13 System.out.println(0x8000000000000000L);//-922337203685477
4、 有符號擴展和無符號擴展
System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));// cafebabe
結果為什么不是0x1cafebabe?該程序執行的加法是一個混合類型的計算:左操作數是long型,而右操作數是int類型。為了執行該計算,Java將int類型的數值用拓寬原生類型轉換提升為long類型,然后對兩個long類型數值相加。因為int是有符號的整數類型,所以這個轉換執行的是符號擴展。
這個加法的右操作數0xcafebabe為32位,將被提升為long類型的數值0xffffffffcafebabeL,之后這個數值加上了左操作0x100000000L。當視為int類型時,經過符號擴展之后的右操作數的高32位是-1,而左操作數的第32位是1,兩個數
值相加得到了0:
0x ffffffffcafebabeL
+0x 0000000100000000L
-----------------------------
0x 00000000cafebabeL
如果要得到正確的結果0x1cafebabe,則需在第二個操作數組后加上“L”明確看作是正的long型即可,此時相加時拓
展符號位就為0:
1 System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));// 1cafe
5、 窄數據類型提升至寬數據類型時是使用符號位擴展還是零擴展
System.out.println((int)(char)(byte)-1);// 65535
結果為什么是65535而不是-1?
窄的整型轉換成較寬的整型時符號擴展規則:如果最初的數值類型是有符號的,那么就執行符號擴展(即如果符號位為1,則擴展為1,如果為零,則擴展為0);如果它是char,那么不管它將要被提升成什么類型,都執行零擴展。
了解上面的規則后,我們再來看看迷題:因為byte是有符號的類型,所以在將byte數值-1(二進制為:11111111)提升到char時,會發生符號位擴展,又符號位為1,所以就補8個1,最后為16個1;然后從char到int的提升時,由於是char型提升到其他類型,所以采用零擴展而不是符號擴展,結果int數值就成了65535。
如果將一個char數值c轉型為一個寬度更寬的類型時,只是以零來擴展,但如果清晰表達以零擴展的意圖,則可以考慮
使用一個位掩碼:
1 int i = c & 0xffff;//實質上等同於:int i = c ;
說明:
至於0xff,這屬於java的字面常量,他已經是int了,ff表示為11111111,java對這種字面常量,不把他前面的1看做符號位,雖然也是有符號擴展,但是,擴展成的是00...ff.
“數字字面常量”的類型都是int型,而不管他們是幾進制,所以“2147483648”、“0x180000000(十六進制,共33位,所以超過了整數的取值范圍)”字面常量是錯誤的,編譯時會報超過int的取值范圍了,所以要確定以long來表示“2147483648L”、“0x180000000L”。
System.out.println(0x80000001);//-2147483647 ,已經是32位,最高位是符號位 System.out.println(0xcafebabe);//-889275714 System.out.println(0xffff); //65535 int是32位的,最高位已經是0,相當於0X0000ffff System.out.println(0xff); //255
如果將一個char數值c轉型為一個寬度更寬的整型,並且希望有符號擴展,那么就先將char轉型為一個short,它與char上個具有同樣的寬度,但是它是有符號的:
2 int i = (short)c;
如果將一個byte數值b轉型為一個char,並且不希望有符號擴展,那么必須使用一個位掩碼來限制它:
3 char c = (char)(b & 0xff);// char c = (char) b;為有符號擴展
6、 ((byte)0x90==0x90)?
答案是不等的,盡管外表看起來是成立的,但是它卻等於false。為了比較byte數值(byte)0x90和int數值0x90,Java
通過拓寬原生類型將byte提升為int,然后比較這兩個int數值。因為byte是一個有符號類型,所以這個轉換執行的是符號擴展,將負的byte數值提升為了在數字上相等的int值(11111111 11111111 11111111 10010000)。在本例中,該轉換將(byte)0x90提升為int數值-112,它不等於int數值的0x90,即+144。
解決辦法:使用一個屏蔽碼來消除符號擴展的影響,從而將byte轉型為int。
1 ((byte)0x90 & 0xff)== 0x90
7、 java中byte轉換int時與0xff進行運算的原因
java中byte轉換int時為何與0xff進行與運算
在剖析該問題前請看如下代碼
public static String bytes2HexString(byte[] b) { String ret = ""; for (int i = 0; i < b.length; i++) { String hex = Integer.toHexString(b[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } ret += hex.toUpperCase(); } return ret; }
上面是將byte[]轉化十六進制的字符串,注意這里b[i] & 0xFF將一個byte和 0xFF進行了與運算,然后使用Integer.toHexString取得了十六進制字符串,可以看出
b[i] & 0xFF運算后得出的仍然是個int,那么為何要和 0xFF進行與運算呢?直接 Integer.toHexString(b[i]);,將byte強轉為int不行嗎?答案是不行的.
其原因在於:
1.byte的大小為8bits而int的大小為32bits
2.java的二進制采用的是補碼形式
在這里先溫習下計算機基礎理論
byte是一個字節保存的,有8個位,即8個0、1。
8位的第一個位是符號位,
也就是說0000 0001代表的是數字1
1000 0000代表的就是-1
所以正數最大位0111 1111,也就是數字127
負數最大為1111 1111,也就是數字-128
上面說的是二進制原碼,但是在java中采用的是補碼的形式,下面介紹下什么是補碼
Integer.toHexString的參數是int,如果不進行&0xff,那么當一個byte會轉換成int時,由於int是32位,而byte只有8位這時會進行補位,例如補碼11111111的十進制數為-1轉換為int時變為11111111111111111111111111111111好多1啊,呵呵!即0xffffffff但是這個數是不對的,這種補位就會造成誤差。和0xff相與后,高24比特就會被清0了,結果就對了。
補碼是有符號數,所以從8位變為int需要有符號擴展,變為11111111111111111111111111111111(最終的值為-1)。
至於0xff,這屬於java的字面常量,他已經是int了,ff表示為11111111,java對這種字面常量,不把他前面的1看做符號位,雖然也是有符號擴展,但是,擴展成的是00...ff.
一般在有些編譯器中,寫ff,會把第一位1認為是符號位,所以可以這么寫:0x0ff
8、 DataInputStream
DataInputStream in = new DataInputStream( new BufferedInputStream(fileInputStream));
