Java-java中的有符號,無符號操作以及DataInputStream


 

1、 無符號和有符號

計算機中用補碼表示負數,並且有一定的計算方式;另外,用二進制的最高位表示符號,0表示正數、1表示負數。這種說法本身沒錯,可是要有一定的解釋,不然它就是錯的,至少不能解釋,為什么字符類型的-1二進制表示是“1111 111116進制表示為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,事實上,-11111 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類型。為了執行該計算,Javaint類型的數值用拓寬原生類型轉換提升為long類型,然后對兩個long類型數值相加。因為int是有符號的整數類型,所以這個轉換執行的是符號擴展


這個加法的右操作數0xcafebabe32位,將被提升為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,所以就補81,最后為161;然后從charint的提升時,由於是char型提升到其他類型,所以采用零擴展而不是符號擴展,結果int數值就成了65535

 

如果將一個char數值c轉型為一個寬度更寬的類型時,只是以零來擴展,但如果清晰表達以零擴展的意圖,則可以考慮

使用一個位掩碼:

1         int i = c & 0xffff;//實質上等同於:int i = c ; 

說明:

至於0xff,這屬於java的字面常量,他已經是int了,ff表示為11111111java對這種字面常量,不把他前面的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)0x90int數值0x90Java

通過拓寬原生類型將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進行運算的原因

javabyte轉換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
的大小為8bitsint的大小為32bits
2.java
的二進制采用的是補碼形式

在這里先溫習下計算機基礎理論

byte是一個字節保存的,有8個位,即801
8
位的第一個位是符號位, 
也就是說0000 0001代表的是數字
1000 0000
代表的就是-1 
所以正數最大位0111 1111,也就是數字127 
負數最大為1111 1111,也就是數字-128

 

上面說的是二進制原碼,但是在java中采用的是補碼的形式,下面介紹下什么是補碼

Integer.toHexString的參數是int,如果不進行&0xff,那么當一個byte會轉換成int時,由於int32位,而byte只有8位這時會進行補位,例如補碼11111111的十進制數為-1轉換為int時變為11111111111111111111111111111111好多1啊,呵呵!即0xffffffff但是這個數是不對的,這種補位就會造成誤差。和0xff相與后,高24比特就會被清0了,結果就對了。

 

補碼是有符號數,所以從8位變為int需要有符號擴展,變為11111111111111111111111111111111(最終的值為-1)。

至於0xff,這屬於java的字面常量,他已經是int了,ff表示為11111111java對這種字面常量,不把他前面的1看做符號位,雖然也是有符號擴展,但是,擴展成的是00...ff.

一般在有些編譯器中,寫ff,會把第一位1認為是符號位,所以可以這么寫:0x0ff

 

8、 DataInputStream

DataInputStream in = new DataInputStream(
                                new BufferedInputStream(fileInputStream));

 

 


免責聲明!

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



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