Java以反码存储数字

以前只知道计算机使用反码来进行计算,但是没有想到,也没有想过计算机存储数字的时候是用什么格式存储的,当然它是二进制的,我的意思是它是原码,反码,补码中的哪一种。今天因为学习ServerSocketChannel,涉及到了这个问题,才把这个知识点摸透,是以反码形式存储的。认识到这一点有什么作用呢,且听我说。

用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,该实现类有构造函数:

 public InetSocketAddress(InetAddress addr, int port) 

InetAddress是一个public类,但它的构造函数是默认权限,也就是包内或子类可访问,它在java.net包下,但它提供了静态方法来获取其实例:

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

当然它还有其他一些静态方法来获取实例,并不是一定要用byte数组才行。而我决定使用它,并因此发现了自己知识图谱中的一个缺陷。

首先我们知道:

  • 计算机一定是使用二进制存储数字的
  • java的数字是有符号位的
  • ip地址是32位的
  • 一个byte是8位,所以这里要我们传入的应该是4个byte的数组
  • 一个byte的范围是[-128-127]

然后我当时自以为,java是以原码存储数字的。

在这样的知识背景下,我来算一算,我应该传哪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);
}

看来它调用了另一个方法,我们再去看看它是怎么回事。

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值