从零理解java之字符串与二进制数据

笑容可以瞒过别人,可心痛却骗不了自己

Posted by yishuifengxiao on 2023-09-28

一 字符串转为二进制数据

如果明确使用UTF-8编码,字符串”中”中的字符数仍然是1,但是占用的字节数会根据编码方式不同而有所变化。

在UTF-8编码中,一个中文字符占用3个字节。因此,字符串”中”在UTF-8编码下占用的字节数是3。

下面是具体的处理代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package org.example;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

public class Main {
public static void main(String[] args) throws UnsupportedEncodingException {
String text = "中";
int charCount = text.length();
//字符数:1
System.out.println("字符数:" + charCount);
System.out.println("\r\n");

//UTF-8编码下字节数:3
//UTF-8编码下字节数的二进制表示:11100100 10111000 10101101
printArray(text, StandardCharsets.UTF_8.name());
System.out.println("\r\n");

//US-ASCII编码下字节数:1
//US-ASCII编码下字节数的二进制表示:111111
printArray(text, StandardCharsets.US_ASCII.name());
System.out.println("\r\n");

//unicode编码下字节数:4
//unicode编码下字节数的二进制表示:11111110 11111111 1001110 101101
printArray(text, "unicode");
System.out.println("\r\n");


//GB2312 兼容 ASICII 编码, GBK 兼容 GB2312 编码,GB18030 兼容 GB2312 编码 和 GBK 编码

//GB18030编码下字节数:2
//GB18030编码下字节数的二进制表示:11010110 11010000
printArray(text, "GB18030");
System.out.println("\r\n");

//GB2312编码下字节数:2
//GB2312编码下字节数的二进制表示:11010110 11010000
printArray(text, "GB2312");
System.out.println("\r\n");

//GBK编码下字节数:2
//GBK编码下字节数的二进制表示:11010110 11010000
printArray(text, "GBK");
System.out.println("\r\n");
}

private static void printArray(String text, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = text.getBytes(charsetName);
System.out.println(charsetName + "编码下字节数:" + bytes.length);
System.out.print(charsetName + "编码下字节数的二进制表示:");
for (byte b : bytes) {
// 注意这里执行了 & 0xFF 操作
System.out.print(Integer.toBinaryString(b & 0xFF) + " ");
}
}
}

java中字符的长度与编码方式无关,但是使用的字节数与编码方式有关。

注意上面数据输出时执行了& 0xFF操作,原因参见后面章节。

二 二进制数据转为字符串

2.1 byte形式的二进制数据转为字符串

1
2
3
4
5
6
7
8
9
10
public class Main {


public static void main(String[] args) {
byte[] bytes = {(byte) 0b11100100, (byte) 0b10111000, (byte) 0b10101101};
String str = new String(bytes, StandardCharsets.UTF_8);
// 转换后的字符串:中
System.out.println("转换后的字符串:" + str);
}
}

运行以上代码,输出如下:

1
转换后的字符串:中

以上代码使用String类的构造方法,将字节数组以UTF-8编码格式转换为字符串。StandardCharsets.UTF_8表示使用UTF-8编码。最后输出转换后的字符串”中”。

2.2 字符串形式的二进制数据转为字符串

将上面 “中”经过utf-8编码后的二进制数据 “11100100 10111000 10101101”转换回来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {


public static void main(String[] args) {
String text = "11100100 10111000 10101101";

final String[] tokens = text.split(" ");
List<Byte> byteList = new ArrayList<>();

for (String token : tokens) {
byte data = (byte) Integer.parseInt(token, 2);
byteList.add(data);
}

byte[] bytes = new byte[byteList.size()];

for (int i = 0; i < byteList.size(); i++) {
bytes[i] = byteList.get(i);
}
String str = new String(bytes, StandardCharsets.UTF_8);
System.out.println("转换后的字符串:" + str);
}
}

代码运行结果为

1
转换后的字符串:中

三 java中数字转为16进制时为什么要先执行 & 0xFF 操作

在Java中,数字转换为16进制时,执行 & 0xFF 操作是为了确保结果是一个有效的8位无符号整数。

在Java中,整数类型是有符号的,即它们可以表示正数和负数。当将一个有符号整数转换为16进制时,如果不执行 & 0xFF 操作,那么结果可能会包含负号,或者超过16进制的有效范围。

执行 & 0xFF 操作可以将一个有符号整数的高24位清零,只保留低8位。由于无符号整数的范围是0到255(即16进制的00到FF),所以执行 & 0xFF 操作可以确保结果在有效的16进制范围内。

以下是一个示例,说明为什么要执行 & 0xFF 操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package org.example;

public class Main {


public static void main(String[] args) {
int number = -1;
String binaryString = Integer.toBinaryString(number);
// 输出 11111111 11111111 11111111 11111111
System.out.println(binaryString);

binaryString = Integer.toBinaryString(number & 0xFF);
//输出11111111
System.out.println(binaryString);

String hexString = Integer.toHexString(number);
// 输出 ffffffff
System.out.println(hexString);

hexString = Integer.toHexString(number & 0xFF);
//输出 ff
System.out.println(hexString);


// int binaryInt = Integer.parseInt("11111111111111111111111111111111", 2); 报错
// int hexInt1 = Integer.parseInt("ffffffff", 16); 报错

int hexInt2 = Integer.parseInt("ff", 16);

// System.out.println(Integer.parseInt("11111111111111111111111111111111", 2));//报错
// 输出255
System.out.println(Integer.parseInt("11111111", 2));
// System.out.println(Integer.parseInt("ffffffff", 16)); 报错
//输出255
System.out.println(Integer.parseInt("ff", 16));

// System.out.println(Integer.parseInt(Integer.toBinaryString(-1), 2)); //报错

// 输出-1
System.out.println(Integer.parseUnsignedInt(Integer.toBinaryString(-1), 2));

//输出4294967295
System.out.println(Long.parseLong(Integer.toBinaryString(-1), 2));

//输出4294967295
System.out.println(Long.parseUnsignedLong(Integer.toBinaryString(-1), 2));
}
}

在第一个示例中,由于没有执行 & 0xFF 操作,转换结果包含了负号和32个字符,这是因为在转换为16进制时,Java将有符号整数视为32位整数。

在第二个示例中,执行了 & 0xFF 操作,结果只保留了低8位,所以转换结果是一个有效的16进制数字。

四 字符串形式的二进制数据转为十进制

将字符串 1111 1111 1111 1111 1111 1111 1111 1111 转换为十进制数据-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package org.example;/**
* @author yishui
* @version 1.0.0
* @since 1.0.0
*/

import java.math.BigInteger;

/**
* @author yishui
* @version 1.0.0
* @className Demo1.java
* @description ss
* @createTime 2023年10月07日 12:35
*/
public class Demo1 {
public static void main(String[] args) {

String str = "1111 1111 1111 1111 1111 1111 1111 1111";
str = str.replaceAll("\\s", ""); // 去除空格

// 将二进制字符串转换为BigInteger对象
BigInteger decimal = new BigInteger(str, 2);
if (decimal.testBit(str.length() - 1)) {
// 如果最高位为1,说明为负数

// 生成掩码,用于将负数反转为其补码表示
// mask值为 4294967295
BigInteger mask = BigInteger.ONE.shiftLeft(str.length()).subtract(BigInteger.ONE);
// 将补码转换为负数
decimal = decimal.xor(mask).add(BigInteger.ONE).negate();
}

System.out.println(decimal); // 输出结果为-1
}
}

五 F&Q

5.1 Integer.parseInt(Integer.toBinaryString(-1), 2) 为什么会报错

Integer.parseInt(Integer.toBinaryString(-1), 2) 报错的原因是由于-1的二进制表示是32位的全1,而Integer.parseInt方法默认只能处理31位以内的二进制数。因此,超出了其处理范围,导致报错。


5.2 为什么Long.parseLong(Integer.toBinaryString(-1), 2)输出值为4294967295

Long.parseLong(Integer.toBinaryString(-1), 2) 输出值为4294967295 的原因是由于-1的二进制表示是32位的全1,而32位的全1在补码表示中代表的是无符号整数的最大值,即2^32 - 1。而Long.parseLong方法可以处理64位的二进制数,所以能够正确地解析出4294967295这个值。