1. 原码、反码、补码
- 正数的原码、反码、补码都一样
- 负数的原码,就是最高位使用符号 1,代表负数,如 8bit 表示:
10000010 代表 -2 的原码,反码就是符号为不变,其他位按位取反(0-> 1,1-> 0),得到 11111101,
补码=反码 + 1,得到补码:11111110
8 位来算:2 的补码 + -2 的补码 = 0000 0010 + 1111 1110 = 1 0000 0000,溢出,相当于 0
- 计算机中的计算都是使用补码来算的,底层存储的 bit 都是一样的,只是解读方式不一样
A - B = A + (-B), A-B 相当于 加上 -B 的补码,-B = ~B + 1
2. int 类型 转 二进制(字符串表示)
private static String int2Binary(int n) { StringBuilder sb = new StringBuilder(); for (int i = 31; i >= 0; i--) { int bit = (n >>> i) & 1; sb.append(bit); if (i % 8 == 0 && i != 0) { sb.append(" "); } } return sb.toString(); }
public static String intToBinary(int n) { StringBuilder sb = new StringBuilder(); for (int i = 31; i >= 0; i--) { int bit = (1 << i) & n; if (bit != 0) { sb.append("1"); } else { sb.append("0"); } if (i % 8 == 0 && i != 0) { sb.append(" "); } } return sb.toString(); }
|
注:其他类型,比如 long,byte,short转 binary 也一样,只需要各数据类型实际的位数即可
3. long 转字节数组 相互转换
为什么要进行转换?
答:long 在 Java 是 64 位,8 个字节。网络传输,要把一个 64 位 long 数字,拆成 8 个 byte 字节数组,才能放到网络报文里发送。
为什么网络只能一个字节一个字节往外发,不能直接发 64 位 long?
1、硬件底层:网卡、TCP/IP 协议,设计的传输单位就是字节(8bit)
网卡在物理链路上,是以比特 bit 一位一位传的,但是协议层把 8 个 bit 打包成 1 个字节 byte,作为最小处理单元。
2、TCP 是字节流协议
TCP 是面向字节流:它传输的是一串无差别的字节序列。 TCP 只关心:“我要发送 N 个字节的数据”,它完全不懂上层的 Java 数据类型:long、int、double。
TCP 的 socket 发送接口:
outputStream.write(byte[]);
|
3、还有一个致命问题:大小端(字节序)
就算假设网卡可以直接丢 64 位整数:
- 有的 CPU 是小端:内存里低字节放前面;
- 有的 CPU 是大端:内存里高字节放前面。
同一 long 数字,在不同 CPU 内存里,字节摆放顺序是反过来的。 如果直接把内存里的 long 二进制原样丢上网络,小端机器发的数据,大端机器收到解析出来就是完全错误的数字。
所以网络协议强制约定:统一使用大端序(网络字节序)。 我们的longToBytes,就是手动按大端顺序,把 long 拆成字节数组。
4、内存存储 vs 网络传输,两件不一样的事
- CPU 内存里的 long:64 位,8 字节,CPU 可以直接读写,受本机 CPU 字节序影响。
- 网络上传输:跨机器通信,必须定义一个与 CPU 无关的标准二进制格式,也就是大端字节数组。
直接把内存的 long 扔网络:不同机器解析结果不一样,协议就乱套。 所以必须我们手动做转换:long → 大端 byte []。
实战代码:
public static byte[] longToBytes(long num) { byte[] bytes = new byte[8]; for (int i = 0; i < 8; i++) { bytes[i] = (byte) ((num >> (8 * (7 - i)) & 0xFF)); } return bytes; }
public static long BytesToLong(byte[] bytes) { long res = 0L; for (byte b : bytes) { res = (res << 8) | (b & 0xFF); } return res; }
|
练习:int 和 byte[] 之间的相互转换
public static byte[] intToBytes(int num) { byte[] bytes = new byte[4]; for (int i = 0; i < 4; i++) { bytes[i] = (byte) ((num >> 8 * (3 - i)) & 0xFF); } return bytes; }
public static int BytesToInt(byte[] bytes) { int res = 0; for (byte b : bytes) { res = (res << 8) | (b & 0xFF); } return res; }
|
4. socket 实现文件上传,其中使用了 long 转 byte[], int 转byte[]
客户端代码:
package com.apitest;
import java.io.*; import java.net.Socket;
public class FileUpload { public static void main(String[] args) { File file = new File("D:\\Pictures\\照片\\IMG_0639.JPG"); String fileName = file.getName(); long fileLen = file.length(); try (FileInputStream fi = new FileInputStream(file); BufferedInputStream bi = new BufferedInputStream(fi); Socket socket = new Socket("localhost", 9999); OutputStream os = socket.getOutputStream()) { byte[] fileNameBytes = fileName.getBytes(); byte[] nameLenBuf = intToBytes(fileNameBytes.length); os.write(nameLenBuf); os.write(fileNameBytes); byte[] bytes = longToBytes(fileLen); os.write(bytes); int len; byte[] buf = new byte[1024 * 4]; while ((len = bi.read(buf)) != -1) { os.write(buf, 0, len); } os.flush(); System.out.println("发送完成!"); } catch (IOException e) { throw new RuntimeException(e); } }
private static byte[] longToBytes(long num) { byte[] bytes = new byte[8]; for (int i = 0; i < 8; i++) { bytes[i] = (byte) ((num >> (8 * (7 - i)) & 0xFF)); } return bytes; }
private static byte[] intToBytes(int num) { byte[] bytes = new byte[4]; for (int i = 0; i < 4; i++) { bytes[i] = (byte) ((num >> (8 * (3 - i))) & 0xFF); } return bytes; }
}
|
服务端代码:
package com.apitest;
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.UUID;
public class FileServer { public static void main(String[] args) { File saveDir = new File("E:\\test\\img\\"); if (!saveDir.exists()) { saveDir.mkdirs(); } try (ServerSocket serverSocket = new ServerSocket(9999);) { System.out.println("文件服务器在运行..."); while (true) { try (Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream()) { byte[] fileNameBytes = is.readNBytes(4); if (fileNameBytes.length != 4) { throw new IOException("读取文件长度失败,数据流提前断开,只读到:" + fileNameBytes.length + "字节"); } int fileNameLen = bytesToInt(fileNameBytes); byte[] fileNameBuf = is.readNBytes(fileNameLen); String fileName = new String(fileNameBuf, 0, fileNameLen); String[] strings = fileName.split("\\."); String suffix = strings[1]; byte[] lenBuf = is.readNBytes(8); if (lenBuf.length != 8) { throw new IOException("读取文件长度失败,数据流提前断开,只读到:" + lenBuf.length + "字节"); } long fileLen = BytesToLong(lenBuf); fileName = UUID.randomUUID() + "." + suffix; fileName = fileName.replace("-", ""); File file = new File(saveDir, fileName); try (FileOutputStream fo = new FileOutputStream(file); BufferedOutputStream bo = new BufferedOutputStream(fo);) { byte[] buf = new byte[1024 * 4]; int len; long totalLen = 0; while (totalLen < fileLen && (len = is.read(buf)) != -1) { bo.write(buf, 0, len); totalLen += len; } bo.flush(); System.out.println("接收成功!"); System.out.println("文件名:" + fileName); System.out.println("存储路径:" + file.getAbsolutePath()); } } } } catch (IOException e) { throw new RuntimeException(e); } }
private static long BytesToLong(byte[] bytes) { long res = 0L; for (byte b : bytes) { res = (res << 8) | (b & 0xFF); } return res; }
private static int bytesToInt(byte[] bytes) { int num = 0; for (byte b : bytes) { num = (num << 8) | (b & 0xFF); } return num; }
}
|