Java以反碼存儲數字

之前只知道計算機使用反碼來進行計算,可是沒有想到,也沒有想過計算機存儲數字的時候是用什麼格式存儲的,固然它是二進制的,個人意思是它是原碼,反碼,補碼中的哪種。今天由於學習ServerSocketChannel,涉及到了這個問題,才把這個知識點摸透,是以反碼形式存儲的。認識到這一點有什麼做用呢,且聽我說。java

用byte數組表示IP地址

編寫服務器程序的時候,須要啓動server socket監聽某個端口,以及綁定一張網卡(也就是服務器上的網卡,當你的服務器有多張網卡的時候,你能夠選擇將socket綁定到其中一張,此時客戶端到來的請求,只有網卡和端口都相同,纔會被這個socket識別)。我有一張網卡的地址是:192.168.1.78。程序員

try {
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    // 綁定一個地址
    serverSocketChannel.bind(socketAddress);
} catch (IOException e) {
    e.printStackTrace();
}

上面代碼中的socketAddress是一個SocketAddress接口的實例,java提供了對該接口的實現類InetSocketAddress,該實現類有構造函數:windows

public InetSocketAddress(InetAddress addr, int port)

InetAddress是一個public類,但它的構造函數是默認權限,也就是包內或子類可訪問,它在java.net包下,但它提供了靜態方法來獲取其實例:數組

public static InetAddress getByAddress(byte[] addr) throws UnknownHostException

固然它還有其餘一些靜態方法來獲取實例,並非必定要用byte數組才行。而我決定使用它,並所以發現了本身知識圖譜中的一個缺陷。服務器

首先咱們知道:app

  • 計算機必定是使用二進制存儲數字的
  • java的數字是有符號位的
  • ip地址是32位的
  • 一個byte是8位,因此這裏要咱們傳入的應該是4個byte的數組
  • 一個byte的範圍是[-128-127]

而後我當時自覺得,java是以原碼存儲數字的。socket

在這樣的知識背景下,我來算一算,我應該傳哪4個byte。我要綁定的網卡的IP地址是192.168.1.78,因此最低位是78,在byte的表達範圍內,不須要特殊處理;第二位是1,也不須要處理;第三位是168,168在java裏用byte已經表達不了了,由於java的byte的最高位用來表達符號,因此它實際上只有7位能表達數值。怎麼回事,難道這個方法建立的InetAddress實例只能表示[0.0.0.0-127.127.127.127]內的IP地址?函數

不對勁,不對勁,我去看一看這個方法的內部是怎麼實現的。學習

public static InetAddress getByAddress(byte[] addr)
    throws UnknownHostException {
    return getByAddress(null, addr);
}

看來它調用了另外一個方法,咱們再去看看它是怎麼回事。.net

public static InetAddress getByAddress(String host, byte[] addr)
    throws UnknownHostException {
    if (host != null && host.length() > 0 && host.charAt(0) == '[') {
        if (host.charAt(host.length()-1) == ']') {
            host = host.substring(1, host.length() -1);
        }
    }
    if (addr != null) {
        if (addr.length == Inet4Address.INADDRSZ) {
            return new Inet4Address(host, addr); 		// into here
        } else if (addr.length == Inet6Address.INADDRSZ) {
            byte[] newAddr
                = IPAddressUtil.convertFromIPv4MappedAddress(addr);
            if (newAddr != null) {
                return new Inet4Address(host, newAddr);
            } else {
                return new Inet6Address(host, addr);
            }
        }
    }
    throw new UnknownHostException("addr is of illegal length");
}

從第一段代碼咱們能看出來調用getByAddress方法時,傳入的host是null,因此第一個if直接跳過不看,咱們傳入的addr參數,是一個長度爲4的byte數組,因此代碼執行到了into here註釋所在的那一行。繼續:

Inet4Address(String hostName, byte addr[]) {
    holder().hostName = hostName;
    holder().family = IPv4;
    if (addr != null) {
        if (addr.length == INADDRSZ) {
            int address  = addr[3] & 0xFF;
            address |= ((addr[2] << 8) & 0xFF00);
            address |= ((addr[1] << 16) & 0xFF0000);
            address |= ((addr[0] << 24) & 0xFF000000);
            holder().address = address;
        }
    }
    holder().originalHostName = hostName;
}

原來!在這裏,經過移位運算,byte數組被轉換成了一個int值。左移位是不會影響符號位的,因此在將byte轉換成int的過程當中,符號位實際上被當成了一個數值位(符號位成爲了int中低位上的數值位,但最後一個byte的符號位成爲了int的符號位)。int是4字節=32位=ip地址長,這樣表明IP地址,能夠的。

因此InetAddress是能夠表示[0.0.0.0-255.255.255.255]的,只不過你要將byte中的最高位也考慮上。讓咱們來算一算,無符號的168用有符號的誰來表示。windows自帶程序員計算器,搬出來計算一下,168的二進制表示是10101000。java的byte的最高位是符號位,因此它表示一個負的00101000。而00101000是40,因此我應該用有符號的-40表示無符號的168。同理計算出192用-64表示。

綜上,個人代碼應該是

// 192.168.1.78
InetAddress inetAddress = InetAddress.getByAddress(new byte[]{-64, -40, 1, 78});
System.out.println(inetAddress)

然而,println輸出的倒是:/192.216.1.78

怎麼回事?216是什麼鬼?檢查了一通,發現-40沒有問題,爲何-64就正確的表達了192,-40就叉屁了呢?

debug一下看看吧

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

11000000_11011000_00000001_01001110,-40的10101000,到這裏怎麼就變成11011000了呢?其餘都是正確的。到這裏我其實沒看出來致使這個問題的根本緣由,可是計算機裏的二進制確實有原碼,反碼,補碼一說,我來套一套看看。

若是數據的存儲不是原碼,而是反碼呢?

10101000 原 11010111 反(符號位不變,其他取反) 11011000 補(反碼+1)

我去,柳暗花明又一村,得來全不費工夫。新的問題是,-64怎麼就對上192了呢

11000000 原 10111111 反 11000000 補

竟然還有這麼巧的事情。我差一點就由於-64和192的對應是正確的而沒往這個方向想。差點由於一個巧合走向了錯誤的思路。也給本身提個醒,有些看似不該該有問題的地方,也有多是存在問題的。

至此我也是才發現一個很重要的知識點,java在存儲數值的時候,使用的是反碼,並非之前我覺得的,用原碼存儲,用反碼計算。這一點平時開發可能基本遇不到,但當你使用 >> << >>> 這種移位運算符,或者用 $ | 運算符的時候就會用到這個知識點。

最後,要想表達168,直接使用它的二進制補碼11011000就能夠了,將他轉換爲有符號的byte是-88。

InetAddress inetAddress = InetAddress.getByAddress(new byte[]{-64, -88, 1, 78});
System.out.println(inetAddress) // /192.168.1.78
相關文章
相關標籤/搜索