深入解析Java和Go语言中String与byte数组的转换原理

1.Java String与byte[]互相转换存在的问题

java中,按照byte[] =》string =》byte[]的流程转换后,byte数据与最初的byte不一致。

多说无益,上代码,本地macos机器执行,统一使用的UTF-8编码。

import java.nio.charset.StandardCharsets;
import java.util.Arrays;


public class StringConversionExample {
    public static void main(String[] args) {
byte[] original2 = new byte[]{(byte)0xef, (byte)0x8f, (byte)0xff};
byte[] transformed2 = new String(original2).getBytes();
System.out.println(Arrays.toString(original2));
System.out.println(Arrays.toString(transformed2));
System.out.println(Arrays.equals(original2, transformed2));
}
}

执行结果

[-17, -113, -1]
[-17, -65, -67, -17, -65, -67]
false

发现问题了没有?byte转成string后,再转回byte,竟然最初的数据不一致,代码开发中,肯定是与预期不一致,容易写出bug。

2.首先熟悉相关字符、字符集、字符编码与字符概念

了解了大概的问题,我们带着疑问,先了解一下相关的概念。

计算机里的unicode编码和UTF-8的关系-CSDN博客

字符编码和字符集到底有什么区别?Unicode和UTF-8是什么关系?-CSDN博客

主要理解一下下面几个概念的区别:

  1. 字符:就是我们看到的一个字母或一个汉字、一个标点符号都叫字符。如汉字“一”就是一个字符。
  2. 字符码:在指定的字符集中,一个字符对应唯一一个数字,这个数字就叫字符码。如上边的字符“一”,在 Unicode 字符集中,对应的字符码为 \u4e00
  3. 字符集:规定了字符和字符码之间的对应关系。
  4. 字符编码:规定了一个字符码在计算机中如何存储,比如UTF-8。

3.java中byte[]与string互转出现问题的表面原因是什么,我们控制变量做了一个对比实验

首先看下如下代码:

byte[] original1 = new byte[]{(byte)0xef, (byte)0x8f, (byte)0x8f};
byte[] transformed1 = new String(original1).getBytes();
System.out.println(Arrays.toString(original1));
System.out.println(Arrays.toString(transformed1));
System.out.println(Arrays.equals(original1, transformed1));

它的执行结果是:

[-17, -113, -113]
[-17, -113, -113]
true

这两个字节数组内容是完全相等的,第一个byte array在经过到String的转换,再到bytes的转换后,内容保持不变。

再看如下代码:

byte[] original2 = new byte[]{(byte)0xef, (byte)0x8f, (byte)0xff};
byte[] transformed2 = new String(original2).getBytes();
System.out.println(Arrays.toString(original2));
System.out.println(Arrays.toString(transformed2));
System.out.println(Arrays.equals(original2, transformed2));

它的执行结果是:

[-17, -113, -1]
[-17, -65, -67, -17, -65, -67]
false

这一次,两个byte array的结果不一样了,且结果差异很大。

这两段代码的唯一区别是,original1的最后一个字节值是0x8f, 而original2的最后一个字节值为0xff。

所以,0xff是导致byte[]与string互转出现问题的表面原因

4.0xff为什么会导致出现问题?

(1)了解java byte与string的转换原理

上面的示例代码的执行环境中,系统默认的字符集是UTF-8,所以字节到字符的转换,会按 UTF-8编码来进行转换。

(2)UTF-8可变长度字符编码

UTF-8(Unicode Transformation Format-8)是一种变长编码方案(多位元组序列),用于将Unicode字符集中的字符编码为字节序列,以便在计算机系统中存储和传输。

UTF-8的定义和使用如下:

1. 字符编码范围:UTF-8可以表示Unicode字符集中的所有字符,包括各种语言的字符、符号、标点符号等。

2. 变长编码UTF-8使用变长编码方式,根据字符的不同范围使用不同长度的字节表示字符。具体规则如下:
   - 对于ASCII字符(Unicode码范围为U+0000至U+007F),使用一个字节表示,其最高位为0。
   - 对于非ASCII字符,使用多个字节表示。UTF-8的编码规则如下:
     - 对于2字节字符,使用两个字节表示,其最高位为110。
     - 对于3字节字符,使用三个字节表示,其最高位为1110。
     - 对于4字节字符,使用四个字节表示,其最高位为11110。
     - 对于更高范围的字符,UTF-8可以使用最多6个字节进行表示。

3. 兼容性:UTF-8编码方案保持了与ASCII字符集的兼容性。ASCII字符在UTF-8中仍然使用一个字节表示,因此任何以ASCII编码的文本都是有效的UTF-8文本。

4. 字节顺序:UTF-8不涉及字节顺序问题,因为它是一种字节序列的编码方式,而不是多字节字符的编码方式。因此,字节顺序对UTF-8没有影响。

在使用UTF-8时,常见的操作包括将字符串从编码为UTF-8字节序列,以及将UTF-8字节序列解码为字符串。编程语言通常提供了相应的API和函数来进行这些操作,例如Java中的`getBytes()`方法和`new String()`构造函数,以及Go语言中的`[]byte`类型和`string()`类型转换。

总结:UTF-8是一种用于将Unicode字符编码为字节序列的编码方案,它具有变长编码、兼容性和广泛支持的特点。使用UTF-8可以在计算机系统中存储和传输各种语言的文本数据,并且保持与ASCII字符集的兼容性。

(3)UTF-8可变长度字符编码的官方说法

这里引用一下更为官方的解释:https://zh.wikipedia.org/wiki/UTF-8#UTF-8%E7%9A%84%E7%B7%A8%E7%A2%BC%E6%96%B9%E5%BC%8F

设计UTF-8的理由

UTF-8的设计有以下的多字符组序列的特质:

  • 单字节字符的最高有效比特永远为0。
  • 多字节序列中的首个字符组的几个最高有效比特决定了序列的长度。最高有效位为110的是2字节序列,而1110的是三字节序列,如此类推。
  • 多字节序列中其余的字节中的首两个最高有效比特为10

UTF-8的这些特质,保证了一个字符的字节序列不会包含在另一个字符的字节序列中。这确保了以字节为基础的部分字符串比对(sub-string match)方法可以适用于在文字中搜索字或词。有些比较旧的可变长度8位编码(如Shift JIS)没有这个特质,故字符串比对的算法变得相当复杂。虽然这增加了UTF-8编码的字符串的信息冗余,但是利多于弊。另外,资料压缩并非Unicode的目的,所以不可混为一谈。即使在发送过程中有部分字节因错误或干扰而完全丢失,还是有可能在下一个字符的起点重新同步,令受损范围受到限制。

另一方面,由于其字节序列设计,如果一个疑似为字符串的序列被验证为UTF-8编码,那么我们可以有把握地说它是UTF-8字符串。一段两字节随机序列碰巧为合法的UTF-8而非ASCII的几率为32分1。对于三字节序列的几率为256分1,对更长的序列的几率就更低了。

UTF-8的编码方式

UTF-8是UNICODE的一种变长度的编码表达方式《一般UNICODE为双字节(指UCS2)》,它由肯·汤普逊(Ken Thompson)于1992年建立,现在已经标准化为RFC 3629。UTF-8就是以8位为单元对UCS进行编码,而UTF-8不使用大尾序和小尾序的形式,每个使用UTF-8存储的字符,除了第一个字节外,其余字节的头两个比特都是以"10"开始,使文字处理器能够较快地找出每个字符的开始位置。

但为了与以前的ASCII码兼容(ASCII为一个字节),因此UTF-8选择了使用可变长度字节来存储Unicode:

(注意:不论是Unicode (Table 3.7) [12],还是ISO 10646 (10.2 UTF-8) [13],目前都只规定了最高码位是0x10FFFF的字符的编码。下表中表示大于0x10FFFF的UTF-8编码是不符合标准的。)

Unicode 和 UTF-8 之间的转换关系表 ( x 字符表示码点占据的位 )
码点的位数码点起值码点终值字节序列Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6
  7U+0000U+007F10xxxxxxx
11U+0080U+07FF2110xxxxx10xxxxxx
16U+0800U+FFFF31110xxxx10xxxxxx10xxxxxx
21U+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx
26U+200000U+3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
31U+4000000U+7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
  • 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,这样的好处是当UNICODE文件中只有ASCII码时,存储的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件兼容。
  • 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxx前三位的二进制表示告诉我们这是个2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。

从这里可以得出,需要看byte是否符合UTF-8标准编码:需要先把16进制byte转为二进制,然后参考是否与UTF-8设计规范一致,参阅Unicode 和 UTF-8 之间的转换关系表 ( x 字符表示码点占据的位 )。

(4)示例中的(byte)0xef, (byte)0x8f, (byte)0xff为什么不是UTF-8标准编码?

(4.1)先补充一个知识点,十六进制转二进制
计算方式一

将十六进制数 `0xef` 转换为其对应的二进制表示形式可以按照以下步骤进行:

1. 将十六进制数 `0xef` 拆分为两个十六进制位:`0xe` 和 `0xf`。
2. 将每个十六进制位转换为对应的四位二进制数。
   - `0xe` 转换为二进制为 `1110`。
   - `0xf` 转换为二进制为 `1111`。
3. 将两个四位二进制数连接起来,得到八位二进制数。
   - `11101111`

因此,十六进制数 `0xef` 转换为二进制为 `11101111`。

计算方式二:

如果你觉得方式一太复杂,可以先拆分为e、f,然后使用网站快速转换:在线进制转换

计算方式三:

查表,先转为10进制,然后手动计算二进制

(4.1.1)如下图,0xEF的十进制为239

将十进制数 239 转换为二进制数,可以使用逐次除以 2 的方法,将每次的余数记录下来,直到商为 0。这个过程如下所示:

(4.1.2)转换步骤
  1. 将十进制数 239 除以 2

    • 239 ÷ 2 = 119,余数 1
    • 记录余数:1
  2. 将商 119 除以 2

    • 119 ÷ 2 = 59,余数 1
    • 记录余数:1
  3. 将商 59 除以 2

    • 59 ÷ 2 = 29,余数 1
    • 记录余数:1
  4. 将商 29 除以 2

    • 29 ÷ 2 = 14,余数 1
    • 记录余数:1
  5. 将商 14 除以 2

    • 14 ÷ 2 = 7,余数 0
    • 记录余数:0
  6. 将商 7 除以 2

    • 7 ÷ 2 = 3,余数 1
    • 记录余数:1
  7. 将商 3 除以 2

    • 3 ÷ 2 = 1,余数 1
    • 记录余数:1
  8. 将商 1 除以 2

    • 1 ÷ 2 = 0,余数 1
    • 记录余数:1
(4.1.3)汇总余数

将上述步骤中的余数从下到上(即从最后一步到第一步)排列起来,就得到了二进制数:

  1. 从下到上,余数依次是:11101111
(4.1.4)结论

十进制数 239 转换为二进制数是 11101111

(4.1.5)验证

(4.2)根据上面的计算方式,计算出本次的16进制对应的二进制

下面是 `(byte)0xef, (byte)0x8f, (byte)0xff` 这三个字节的 8 位二进制表示形式:

1. `(byte)0xef` 对应的 8 位二进制数为:`11101111`
2. `(byte)0x8f` 对应的 8 位二进制数为:`10001111`
3. `(byte)0xff` 对应的 8 位二进制数为:`11111111`

(4.3)查看二进制是否符合UTF-8的编码规范

这些二进制数表示了各自字节的位模式,然后查看Unicode 和 UTF-8 之间的转换关系表。

0xef对应:`11101111`,所以属于转换关系表的字节序列3,也就是说的UTF-8的三字节字符。剩下的两位字符需要对应Byte 2和Byte 3,应该以10xxxxxx格式开头。

0x8f为`10001111`,符合。

0xff为`11111111`,不符合。

发现这些字节序列并不符合 UTF-8 编码规范,所以它们并不是有效的 UTF-8 编码

可以回答第4章最初的问题,因为(byte)0xef, (byte)0x8f, (byte)0xff不符合三字节字符对应的转换关系表规范,所以不是UTF-8标准编码。

Unicode 和 UTF-8 之间的转换关系表 ( x 字符表示码点占据的位 )
码点的位数码点起值码点终值字节序列Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6
  7U+0000U+007F10xxxxxxx
11U+0080U+07FF2110xxxxx10xxxxxx
16U+0800U+FFFF31110xxxx10xxxxxx10xxxxxx
21U+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx
26U+200000U+3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
31U+4000000U+7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

(5)不是UTF-8标准编码,跟java转换有什么关系?

经过上面一串的解释,你应该会得出一个结论:因为(byte)0xef, (byte)0x8f, (byte)0xff中的(byte)0xff不是标准的UTF-8字符编码,所以转换过程有问题。它活该呀,谁让它不标准呢。

但是还有一个疑问没解决,那就是为什么它不标准,java就转换不正确

让我们回到最初的java代码

    public static void main(String[] args) {
byte[] original2 = new byte[]{(byte)0xef, (byte)0x8f, (byte)0xff};
byte[] transformed2 = new String(original2).getBytes();
System.out.println(Arrays.toString(original2));
System.out.println(Arrays.toString(transformed2));
System.out.println(Arrays.equals(original2, transformed2));
}

其中转换逻辑使用到的关键java代码:

new String(original2).getBytes();

分析String源码

简单的从 String 的构造方法可以看出,没有设置编码的情况下,会采用系统默认编码,macOS上的为 UTF-8,解码过程中使用到一个关键的CharsetDecoder,来看一下CharsetDecoder 的构造方法:

代码位置:JDK 1.8,java.lang.String#String(byte[], int, int, java.nio.charset.Charset),636行、611行

JDK 1.8,java.nio.charset.CharsetDecoder#CharsetDecoder(java.nio.charset.Charset, float, float),234行

// java.lang.String#String(byte[], int, int, java.nio.charset.Charset),653行
  // decode using CharsetDecoder
            int en = scale(length, cd.maxCharsPerByte());
            cd.onMalformedInput(CodingErrorAction.REPLACE)
                    .onUnmappableCharacter(CodingErrorAction.REPLACE);

// CodingErrorAction.REPLACE)参数
    /**
     * Action indicating that a coding error is to be handled by dropping the
     * erroneous input, appending the coder's replacement value to the output
     * buffer, and resuming the coding operation.
     */
    public static final CodingErrorAction REPLACE
        = new CodingErrorAction("REPLACE");

// java.nio.charset.CharsetDecoder#CharsetDecoder(java.nio.charset.Charset, float, float),234行
    /**
     * Initializes a new decoder.  The new decoder will have the given
     * chars-per-byte values and its replacement will be the
     * string <code>"&#92;uFFFD"</code>.
     *
     * @param  cs
     *         The charset that created this decoder
     *
     * @param  averageCharsPerByte
     *         A positive float value indicating the expected number of
     *         characters that will be produced for each input byte
     *
     * @param  maxCharsPerByte
     *         A positive float value indicating the maximum number of
     *         characters that will be produced for each input byte
     *
     * @throws  IllegalArgumentException
     *          If the preconditions on the parameters do not hold
     */
    protected CharsetDecoder(Charset cs,
                             float averageCharsPerByte,
                             float maxCharsPerByte)
    {
        this(cs,
             averageCharsPerByte, maxCharsPerByte,
             "\uFFFD");
    }

其中比较关键的是 CodingErrorAction.REPLACE,javadoc中的解释:

    /**
     * Action indicating that a coding error is to be handled by dropping the
     * erroneous input, appending the coder's replacement value to the output
     * buffer, and resuming the coding operation.
     */
    public static final CodingErrorAction REPLACE
        = new CodingErrorAction("REPLACE");

进行string转byte时,针对错误部分的byte:(byte)0xff,会被java使用"\uFFFD"进行替换。所以再将byte转回string时,自然无法准备恢复最初的数据了。

(6)使用java代码验证,是否使用"\uFFFD"替换了错误部分的byte?

package com.shopee.mmdb.chunnel.store.kafka;

import java.nio.charset.Charset;
import java.util.Arrays;


public class StringConversionExample {
    public static void main(String[] args) {
        byte[] original2 = new byte[] {(byte) 0xef, (byte) 0x8f, (byte) 0xff};
        byte[] transformed2 = new String(original2).getBytes();
        System.out.println(Arrays.toString(original2));
        System.out.println(Arrays.toString(transformed2));
        System.out.println(Arrays.equals(original2, transformed2));

        String unicodeString = "\uFFFD";
        Charset charset = Charset.forName("UTF-8");
        byte[] byteArr = unicodeString.getBytes(charset);
        System.out.println(Arrays.toString(byteArr));
    }
}

运行结果,显而易见,"\uFFFD"就是 [-17, -65, -67]。

[-17, -113, -1]
[-17, -65, -67, -17, -65, -67]
false
[-17, -65, -67]

(7)解决问题

既然是UTF-8编码不标准的问题,那么可以通过单字节编码模式,获取到正确的结果,操作如下:

byte[] newBytes = new String(bytes, StandardCharsets.ISO_8859_1).getBytes(StandardCharsets.ISO_8859_1)

// 完整代码
package com.shopee.mmdb.chunnel.store.kafka;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;


public class StringConversionExample {
    public static void main(String[] args) {
        byte[] original2 = new byte[] {(byte) 0xef, (byte) 0x8f, (byte) 0xff};
        byte[] transformed2 =
            new String(original2, StandardCharsets.ISO_8859_1).getBytes(StandardCharsets.ISO_8859_1);
//        byte[] transformed2 = new String(original2).getBytes();
        System.out.println(Arrays.toString(original2));
        System.out.println(Arrays.toString(transformed2));
        System.out.println(Arrays.equals(original2, transformed2));

        String unicodeString = "\uFFFD";
        Charset charset = Charset.forName("UTF-8");
        byte[] byteArr = unicodeString.getBytes(charset);
        System.out.println(Arrays.toString(byteArr));
    }
}

执行结果

[-17, -113, -1]
[-17, -113, -1]
true
[-17, -65, -67]

5.java byte转string问题的总结

java中通过 byte[] 转换为 String 时,可能因为一些编码规则,比如UTF-8,造成部分被替换,反向转换为 byte[] 后和之前不同;在转换时,可以通过指定 StandardCharsets.ISO_8859_1 等单字节编码来解决问题;

这里引入另一个思考的问题,一个 String 转换为 byte[] 后,再转换为 String 会有问题么?答案是不会,因为转换为 byte[] 的字节编码是符合 UTF-8 的;

6.那么golang中是否也存在该问题呢?

(1)看goalng的例子

同样的使用不标准的UTF-8编码 0xef, 0x8f, 0xff,golang执行 byte 转 string,然后string再转回byte。

package main

import (
	"fmt"
	"reflect"
	"unicode/utf8"
)

func main() {
	original2 := []byte{0xef, 0x8f, 0xff}
	transformed2 := []byte(string(original2))

	fmt.Println("Original2:", original2)
	fmt.Println("Transformed2:", transformed2)
	fmt.Println("Are equal:", reflect.DeepEqual(original2, transformed2))
	fmt.Println("Original2 string:", string(original2))

	if utf8.Valid(original2) {
		fmt.Println("Original is valid UTF-8")
	} else {
		fmt.Println("Original is not valid UTF-8")
	}
}

运行结果,byte经过一通转换后,又回到了最初的byte,数据完全一致。

所以在golang里并没有复现出,java里的byte转换后数据最终被篡改的问题。

Original2: [239 143 255]
Transformed2: [239 143 255]
Are equal: true
Original2 string: ��
Original is not valid UTF-8

(2)golang里为什么byte转string不会出现数据被篡改的情况?

基本信息和源码

GO的字符串内存中string用UTF-8编码

go sdk 1.21.5的string源码,如下:

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

同时根据golang官方博客https://blog.golang.org/strings的原文:

Go source code is always UTF-8.
A string holds arbitrary bytes.
A string literal, absent byte-level escapes, always holds valid UTF-8 sequences.

大致意思如下:

  • go中的代码总是用UTF-8编码,并且字符串能够存储任何字节
  • 没有经过字节级别的转义,那么字符串是一个标准的utf8序列

JAVA的字符串:内存中string使用的UTF-16编码

JDK 1.8源码:java.lang.String#String(byte[])

value = StringUTF16.toBytes(ca, 0, caLen);

可能到这里,你就会急了“你上面JAVA长篇大论,一直给我讲UTF-8不标准字符,导致了java的转换问题,我刚接受这个概念。怎么到这里,又变成了java使用的UTF-16,golang反倒使用的UTF-8,并且golang还没有出问题?”。

很好,我接着解释。

JAVA转换路径

1、输入非标准UTF-8字符 =》

2、根据用户显式指定字符编码集或者Macos机器默认内置的字符编码UTF-8,通知程序本次输入的byte数组是UTF-8编码规则 =》

3、JAVA调用decodeUTF8_UTF16方法,把byte[]转换为UTF-16 =》

4、方法decodeWithDecoder使用"\uFFFD"替换掉不标准的字符,并从byte[]转为char[]  =》

5、最终使用方法StringUTF16.toBytes(ca, 0, caLen),把char[]再次转为UTF16,并设置到string的private final byte[] value;里

// jdk 1.8 string类 573行,方法路径java.lang.String#decodeUTF8_UTF16
dp = decodeUTF8_UTF16(bytes, offset, sl, dst, dp, true);
GOLANG转换路径

1、根据传入的内存大小,判断是否需要分配重新分配内存;
2、构建stringStruct,分类长度和内存空间;
3、赋值[]byte里面的数据到新构建stringStruct的内存空间中。

查看源码 src/runtime/string.go

type stringStruct struct {
	str unsafe.Pointer
	len int
}

func slicebytetostring(buf *tmpBuf, b []byte) (str string) {
	l := len(b)
	if l == 0 {
		return ""
	}
	if l == 1 {
		stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])
		stringStructOf(&str).len = 1
		return
	}
	var p unsafe.Pointer
	// 判断传入的缓冲区大小,决定是否重新分配内存
	if buf != nil && len(b) <= len(buf) {
		p = unsafe.Pointer(buf)
	} else {
		// 重新分配内存
		p = mallocgc(uintptr(len(b)), nil, false)
	}
	// 将输出的str转化成stringStruct结构
	// 并且赋值
	stringStructOf(&str).str = p
	stringStructOf(&str).len = len(b)
	// 将[]byte中的内容,复制到内存空间p中
	memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))
	return
}

// 转换成
func stringStructOf(sp *string) *stringStruct {
	return (*stringStruct)(unsafe.Pointer(sp))
}
JAVA与GOLANG转换区别的对比
  • 基本存储逻辑的区别:
    • Java: String 是用 UTF-16 编码存储的字符序列,设计上是用于表示文本数据的类。byte[] 是原始的字节数据,通常与 String 之间的转换涉及字符编码。
    • Go: string 总是 UTF-8 (不代表一定)编码的字节序列视图,表示不可变的字节数据。它更接近于一个只读的 []byte,在内部没有进行复杂的编码处理。
  • 对无效编码的处理:
    • Java: 在将 byte[] 转换为 String 时,如果字节序列不符合字符集(例如 UTF-8)的要求,Java 会用替代字符 '\uFFFD' 来表示解码失败的字节。这是为了保证转换结果在文本上是合理的。
    • Go: 将 []byte 转换为 string 时,不会检查字节序列的有效性。无论字节是否符合 UTF-8 规范,转换后的 string 都会直接包含这些原始字节,不会进行替换。
  • 设计上的考虑:
    • Java: 强调国际化和多语言支持,String 是表示人类可读文本的类,必须处理和转换字符集。Java 期望转换过程中得到有效的字符,因此必须处理无效字节序列(替换或者抛出异常)。
    • Go: 设计上强调简单和直接,string 只是字节序列的不可变视图,不做额外的编码检查和处理。这使得处理二进制数据和文本时更灵活,但要求开发者自己确保字节序列的有效性。
  • 总结
    • Java: 强调对字符集和编码的处理,String 是用来表示文本的类,在转换时需要处理不合法的字节。
    • Gostring 是不可变的字节序列视图,直接允许 []byte 转换为 string,无论字节是否构成有效的 UTF-8 字符。

 想看更加详细对比,请参考另外一篇文章Java 和 Go 中的 byte 和 String 转换-CSDN博客

最终结论

java底层存储string,使用byte字节序列,严格限制合法的UTF-16编码,所以为了强调String的有效性,做了严格限制,比如不合法的byte会被替换掉。

golang底层存储string,使用byte字节序列,默认总表示UTF-8编码的字符,也可以不是,不做额外的字符合法性校验。

所以,Java里使用new String(new byte[]{new byte[] {(byte) 0xef, (byte) 0x8f, (byte) 0xff}})会导致数据被篡改。golang里使用string([]byte{0xef, 0x8f, 0xff})不会导致数据被篡改,所以golang不存在该问题

7.JAVA和Golang String分析过程中的细节补充

(1)UTF-8和UTF-16什么区别

  • UTF-8: 以可变长度编码字符,具有较好的 ASCII 兼容性和网络传输效率,非常适合以英语为主的文本处理和互联网应用。
  • UTF-16: 适合需要处理大量非 ASCII 字符的场景,虽然占用存储空间较大,但在处理字符时较为高效,适合在内存中存储文本和内部字符处理。UTF16详细设计信息,参考:https://zh.wikipedia.org/wiki/UTF-16

更多对比参考文章:编码规则UTF-8 和 UTF-16的区别-CSDN博客

(2)为什么Java默认使用UTF-16,Golang默认使用UTF-8呢?

Java 选择 UTF-16:

历史背景: Java 诞生时 Unicode 设计目标主要是 16 位编码,UTF-16 是当时处理多语言的最佳选择。
效率考虑: 固定长度的 2 字节编码简化了字符处理逻辑,特别是在需要高效定位和操作字符时。
兼容性需求: 与 Windows 系统的兼容性需求,使得 UTF-16 在跨平台应用中具有优势。

Go 选择 UTF-8:

创始人背景:Go 语言的3位创始人,其中有2位同时也是UTF-8 字元编码的创始人。

互联网标准: Go 语言诞生在互联网时代,UTF-8 已成为网络传输和文件存储的标准。
简单高效: Go 的设计强调简单和高效,UTF-8 作为默认编码简化了字符串处理,特别是在处理 ASCII 兼容文本时。
全球化支持: UTF-8 能够支持全球所有字符,适应了现代互联网应用的需求。

总结

Java 和 Go 在处理字符编码方面的选择,反映了它们各自的历史背景和设计哲学。Java 的 UTF-16 选择了稳定和兼容,而 Go 的 UTF-8 则倾向于现代互联网应用的灵活性和效率。

更多原因,参考另外一篇文章:为什么Java默认使用UTF-16,Golang默认使用UTF-8呢?-CSDN博客

8.Golang中String与Byte、Rune的关系

(1)有必要重温一下,最基本的字符编码概念(上面已经讲过了)

字符串:字符串是由一个或多个字符组成的序列。它可以包含字母、数字、符号和其他特殊字符。

Unicode:Unicode 是一个字符集,它为世界上几乎所有的字符都分配了一个唯一的标识符(码点)。每个字符都有一个对应的 Unicode 码点,用 U+ 加上一个十六进制数字表示,例如 U+0041 表示字符 'A'。

UTF-8:UTF-8 是一种变长编码方案,用于在计算机中表示 Unicode 字符。它使用 8 位字节来编码字符,根据字符的码点范围,使用不同长度的字节序列来表示字符。UTF-8 可以准确地表示各种语言字符、符号和表情等,它是最常用的字符编码方案之一。

Unicode 和 UTF-8 的关系是,Unicode 定义了字符集和字符的标识符(码点),而 UTF-8 是一种编码方案,用于在计算机中表示 Unicode 字符。UTF-8 编码将 Unicode 码点转换为字节序列,并且可以通过解码将字节序列转换回字符。

字符Unicode 码点UTF-8 编码十六进制UTF-8 编码二进制
'爱'U+7231E788B1[11100111 10001000 10110001]
'A'U+004141[01000001]
'9'U+003939[00111001]

讲解可变长字符,三字节的''字的转换过程:

  • 查询Unicode表,找到Unicode 码点: U+7231 十六进制数
  • 十六进制数转换为二进制: 0111 0010 0011 0001
  • 根据 UTF-8 编码规则,U+7231 需要 3 字节。
  • UTF-8 编码格式: 1110xxxx 10xxxxxx 10xxxxxx
  • 将二进制数字按位插入格式,按从右向左依次填充这个格式中的 x,多出的 x 用 0 补上:
    • 1110xxxx => 11100111
    • 10xxxxxx => 10001000
    • 10xxxxxx => 10110001
  • 二进制每4位一组,根据下面的对照表,转十六进制,最终得到:E7 88 B1

需要进一步详细重温原理,可以看本文的第2个章节:【2.首先熟悉相关字符、字符集、字符编码与字符概念】。

(2)String、Byte和Rune代码中的基本概念

详细讲解版本

String:Go 语言中的字符串是一个只读的字节切片。默认为UTF-8的字节序列,所以string底层转字节时,是默认按照UTF-8编码转换。字节转string时,也默认按照UTF-8解码,如果这组字节不是UTF-8个格式,显示string的时候则会乱码。

[]rune可以把字符串转换为一个rune数组(即unicode数组)

  • 一个rune 4个字节,32 bit
  • 一个rune就表示一个Unicode字符
  • 每个Unicode字符,在内存中是以utf-8的形式存储
  • Unicode字符,输出[]rune,会把每个UTF-8转换为Unicode后再输出

[]byte可以把字符串转换为一个byte数组

  • 一个byte 1个字节,8 bit(为了对比rune,所以这句话看起来有点废话)
  • 输出[]byte, 会按字符串在内存中实际存储形式(UTF-8)输出
  • Unicode字符,按[]byte输出,就会把UTF-8的每个字节单个输出

ASCII(American Standard Code for Information Interchange) 是一种字符编码标准,用于表示英文字符及一些控制字符。

  • 7 位编码:标准 ASCII 使用 7 位来编码字符,可以表示 128 个字符(从 0 到 127)。
  • 8 位扩展:扩展 ASCII 使用 8 位,可以表示 256 个字符(从 0 到 255),包括标准 ASCII 和额外的字符。
  • 标准 ASCII 字符的Unicode范围是U+0000U+007F使用单个字节表示
  • 256个字符,正好可以使用1个字节表示(8bit,也就是2的8次方256个字符)全部可以存下。

非ASCII:非 ASCII 字符指的是那些超出了标准 ASCII 范围的字符。非 ASCII 字符的编码值通常在 128 以上,要更多的比特来编码,以表示更广泛的字符集。

  • 截至 Unicode 15.0(最新版本),已定义的字符总数为 149,186 个 。
  • 其中,128 个是标准 ASCII 字符(不包括拓展)。因此,非 ASCII 字符的数量是 149,186 - 128 = 149,058 个。
  • 非 ASCII 字符的Unicode范围是 U+0080U+10FFFF,最大的Unicode为U+10FFFF,二进制需要4个字节才能表示。需要使用可变的多字节表示(从 2 到 4 个字节)
  • 所以

查看Go源码,byte和rune的定义分别是:type byte = uint8type rune = int32

// string is the set of all strings of 8-bit bytes, conventionally but not
// necessarily representing UTF-8-encoded text. A string may be empty, but
// not nil. Values of string type are immutable.
type string string

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

byte uint8范围是0-255(2的8次方,1字节),只能够表示ASCII Unicode。

Rune int32,4字节的rune完全兼容所有的unicode,包括最大的Unicode U+10FFFF

区别总结

特性stringbyte[]rune
数据类型不可变的字符串可变的字节切片可变的 rune(字符)切片
单位UTF-8 字符串单个字节Unicode 码点(字符)
主要用途存储和操作文本数据操作原始二进制数据或单字节字符处理和操作 Unicode 字符
可变性不可变可变可变
访问方式按字节或字符(rune)访问按字节访问按字符(rune)访问
示例"Hello, 世界"[]byte{'H', 'e', 'l', 'l', 'o'}[]rune{'H', '世'}
简单总结版本
  1. string

    • 定义string 类型表示一个不可变的字节序列(文本字符串)。
    • 特点
      • 每个字符可能是一个或多个字节。
      • 在 Go 中,string 是 UTF-8 编码的字节序列,这意味着它可以包含多字节字符。
    • 操作string 可以使用内置的字符串操作函数,如 len+ 等。
  2. byte

    • 定义byte 是一个 8 位无符号整数类型,本质上等同于 uint8
    • 特点
      • byte 类型通常用于表示原始的字节数据。
      • 处理单字节字符(如 ASCII 字符)或原始的二进制数据时非常有用。
    • 操作byte 数组([]byte)可以进行字节级别的操作,如读取、写入和修改。
  3. rune

    • 定义rune 是一个 32 位有符号整数类型,本质上等同于 int32
    • 特点
      • rune 表示一个 Unicode 码点,可以表示世界上所有的字符。
      • 使用 rune 处理多字节字符和非 ASCII 字符时更方便。
    • 操作rune 切片([]rune)可以方便地处理字符串中的 Unicode 字符。
存储同一个字符串时,byte和rune的代码表现

包含非ascii码的字符串

package main

import (
	"fmt"
	"unicode/utf8"
	"unsafe"
)

func main() {
	c := "go语言"
	s_rune_c := []rune(c)
	s_byte_c := []byte(c)
	fmt.Println(s_rune_c) // [103 111 35821 35328]  
	fmt.Println(s_byte_c) // [103 111 232 175 173 232 168 128]
	fmt.Println(utf8.RuneCountInString(c)) 	//4
  fmt.Println(len(c))   					//8
	fmt.Println(len(s_rune_c)) 				//4
}
  • 汉字占3个字节(可变长字节存储unicode),所以转换的[]byte长度为8
  • 由于已经转换为[]rune,所以长度为4
  • utf8.RuneCountInString()获取UTF-8编码字符串的长度,所以跟[]rune一致

想了解更多它们的区别,可以参考另外一篇文章:golang string、byte[]以及rune的基本概念,用法以及区别-CSDN博客

(3)String、Byte和Rune代码的用法示例和转换注意事项

示例与转换
1. string[]byte 的转换
  • string[]byte:将字符串转换为字节数组,可以用于对原始数据进行处理或传输。
  • []bytestring:将字节数组转换回字符串,用于将处理后的数据重新组装为字符串。
package main

import (
    "fmt"
)

func main() {
    // string 转换为 []byte
    str := "Hello, 世界"
    byteArray := []byte(str)
    fmt.Printf("string to []byte: %v\n", byteArray)
    fmt.Printf("string to []byte: % X\n", byteArray) // 用 16 进制显示字节
    
    // []byte 转换为 string
    newStr := string(byteArray)
    fmt.Printf("[]byte to string: %s\n", newStr)
}

输出

string to []byte: [72 101 108 108 111 44 32 228 184 150 231 149 140]
string to []byte: 48 65 6C 6C 6F 2C 20 E4 B8 96 E7 95 8C
[]byte to string: Hello, 世界

注意事项

  • string[]byte 时,字符可能会被分割成多个字节。
  • []bytestring 时,确保字节数组是合法的 UTF-8 编码,否则会出现乱码或非法字符。
2. string[]rune 的转换
  • string[]rune:将字符串转换为 rune 数组,方便逐字符处理,特别是对于非 ASCII 字符。
  • []runestring:将 rune 数组转换回字符串,用于重新组合字符。
package main

import (
    "fmt"
)

func main() {
    // string 转换为 []rune
    str := "Hello, 世界"
    runeArray := []rune(str)
    fmt.Printf("string to []rune: %v\n", runeArray)
    fmt.Printf("string to []rune: %c\n", runeArray) // 用字符显示每个 rune
    
    // []rune 转换为 string
    newStr := string(runeArray)
    fmt.Printf("[]rune to string: %s\n", newStr)
}

输出

string to []rune: [72 101 108 108 111 44 32 19990 30028]
string to []rune: [H e l l o ,   世 界]
[]rune to string: Hello, 世界

注意事项

  • rune 表示的是 Unicode 码点,一个 rune 可以表示一个完整的字符。
  • string[]rune 时,每个字符都转换为对应的 Unicode 码点,即使是多字节字符。
  • []runestring 时,字符会被正确地重新组装为字符串。
3. []byte[]rune 的转换
  • []byte[]rune:首先需要将 []byte 转换为 string,然后再将 string 转换为 []rune
  • []rune[]byte:将 []rune 转换为 string,然后再将 string 转换为 []byte
package main

import (
    "fmt"
)

func main() {
    // []byte 转换为 []rune
    byteArray := []byte("Hello, 世界")
    str := string(byteArray)    // 中间转换为 string
    runeArray := []rune(str)    // 再转换为 []rune
    fmt.Printf("[]byte to []rune: %v\n", runeArray)
    fmt.Printf("[]byte to []rune: %c\n", runeArray)
    
    // []rune 转换为 []byte
    newStr := string(runeArray) // 中间转换为 string
    newByteArray := []byte(newStr) // 再转换为 []byte
    fmt.Printf("[]rune to []byte: %v\n", newByteArray)
    fmt.Printf("[]rune to []byte: % X\n", newByteArray) // 用 16 进制显示字节
}

输出

[]byte to []rune: [72 101 108 108 111 44 32 19990 30028]
[]byte to []rune: [H e l l o ,   世 界]
[]rune to []byte: [72 101 108 108 111 44 32 228 184 150 231 149 140]
[]rune to []byte: 48 65 6C 6C 6F 2C 20 E4 B8 96 E7 95 8C

注意事项

  • 由于 byte 只表示单个字节,因此在转换到 rune 之前,需要经过 string 以处理多字节字符。
  • 直接将 []rune 转换为 []byte 是不可行的,因为 rune 是 32 位,而 byte 是 8 位,需要中间通过 string 处理字符的编码。
总结与注意事项
  • string 是 UTF-8 编码的字节序列,适合用于表示和处理常规的文本数据。
  • byte 类型用于处理原始字节数据,适合低级别的数据操作和处理单字节字符。
  • rune 类型用于处理 Unicode 码点,适合表示和操作多字节的字符,特别是非 ASCII 字符。
  • 转换时需要注意字符编码,确保在从一种表示形式转换到另一种时不会丢失数据或出现乱码,尤其是在处理非 ASCII 字符时。

9.不符合UTF-8的Byte转换示例

Java

代码
package com.xxx;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;


public class StringConversionExample {
    public static void main(String[] args) {
        byte[] original2 = new byte[] {(byte) 0xef, (byte) 0x8f, (byte) 0xff};
        // 解决办法,使用单字节
//        byte[] transformed2 =
            new String(original2, StandardCharsets.ISO_8859_1).getBytes(StandardCharsets.ISO_8859_1);
        byte[] transformed2 = new String(original2).getBytes();
        System.out.println(Arrays.toString(original2));
        System.out.println(Arrays.toString(transformed2));
        System.out.println(Arrays.equals(original2, transformed2));

        String unicodeString = "\uFFFD";
        Charset charset = Charset.forName("UTF-8");
        byte[] byteArr = unicodeString.getBytes(charset);
        System.out.println(Arrays.toString(byteArr));
    }
}

输出
[-17, -113, -1]
[-17, -65, -67, -17, -65, -67]
false
[-17, -65, -67]

Golang

代码
package main

import (
	"fmt"
	"reflect"
	"unicode/utf8"
)

func main() {
	original2 := []byte{0xef, 0x8f, 0xff}
	transformed2 := []byte(string(original2))

	fmt.Println()
	fmt.Println("byte byte demo")
	fmt.Println("Original byte:", original2)
	fmt.Println("Transformed byte:", transformed2)
	fmt.Println("Byte are equal:", reflect.DeepEqual(original2, transformed2))
	fmt.Println("Original byte string:", string(original2))
	fmt.Println("Transformed byte string:", string(transformed2))

	if utf8.Valid(original2) {
		fmt.Println("Original is valid UTF-8")
	} else {
		fmt.Println("Original is not valid UTF-8")
	}

	fmt.Println()
	fmt.Println("rune rune demo")
	originalRune := []rune{0xef, 0x8f, 0xff}
	transformedRune := []rune(string(originalRune))

	fmt.Println("Original rune:", originalRune)
	fmt.Println("Transformed rune:", transformedRune)
	fmt.Println("Rune are equal:", reflect.DeepEqual(originalRune, transformedRune))
	fmt.Println("Original rune string:", string(originalRune))
	fmt.Println("Transformed rune string:", string(transformedRune))

	fmt.Println()
	fmt.Println("next rune to byte demo")
	originalRune2 := []rune{0xef, 0x8f, 0xff}
	transformedRune2 := []byte(string(originalRune2))

	fmt.Println("Original rune:", originalRune2)
	fmt.Println("Transformed byte:", transformedRune2)
	fmt.Println("Rune byte are equal:", reflect.DeepEqual(originalRune2, transformedRune2))
	fmt.Println("Original rune string:", string(originalRune2))
	fmt.Println("Transformed byte string:", string(transformedRune2))

	fmt.Println()
	fmt.Println("next byte to rune  demo")
	originalRune3 := []byte{0xef, 0x8f, 0xff}
	transformedRune3 := []rune(string(originalRune3))

	fmt.Println("Original byte:", originalRune3)
	fmt.Println("Transformed rune:", transformedRune3)
	fmt.Println("Byte Rune are equal:", reflect.DeepEqual(originalRune3, transformedRune3))
	fmt.Println("Original byte string:", string(originalRune3))
	fmt.Println("Transformed rune string:", string(transformedRune3))

	fmt.Println()
	fmt.Println("打印替换字符的byte和rune二进制格式")
	fmt.Println([]byte(string("\uFFFD")))
	fmt.Println([]rune(string("\uFFFD")))
}
输出
byte byte demo
Original byte: [239 143 255]
Transformed byte: [239 143 255]
Byte are equal: true
Original byte string: ��
Transformed byte string: ��
Original is not valid UTF-8

rune rune demo
Original rune: [239 143 255]
Transformed rune: [239 143 255]
Rune are equal: true
Original rune string: ïÿ
Transformed rune string: ïÿ

next rune to byte demo
Original rune: [239 143 255]
Transformed byte: [195 175 194 143 195 191]
Rune byte are equal: false
Original rune string: ïÿ
Transformed byte string: ïÿ

next byte to rune  demo
Original byte: [239 143 255]
Transformed rune: [65533 65533 65533]
Byte Rune are equal: false
Original byte string: ��
Transformed rune string: ���

打印替换字符的byte和rune二进制格式
[239 191 189]
[65533]

这里的代码中,输入都是byte[]:0xef, 0x8f, 0xff,非标准的UTF-8变长字节,转换过程会出现字符串显示乱码。

相关链接

(1)本文原理参考文档

Java里byte[]和String转换不一致的坑

Java byte[]和String转换问题

关于Java中bytes到String的转换

深入剖析go中字符串的编码问题——特殊字符的string怎么转byte?

【Golang】深究字符串——从byte rune string到Unicode与UTF-8

(2)容易忽视的类似问题,与本文的问题相似但有些区别,也很有趣

Redis数据迁移过程,使用jedis客户端,需要注意区分string和byte命令转换字符编码不一致的问题,使用不当会导致丢数据_redis数据转编议-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/750195.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【最佳实践】前端如何搭建自己的cli命令行工具,让自己编码的时候如虎添翼

作为前端开发人员&#xff0c;搭建自己的前端CLI工具是一个有趣且有意义的事情。以下是一篇详细的教程&#xff0c;包括使用场景和案例。 使用场景 假设你是一个前端团队的一员&#xff0c;需要频繁地在不同的项目中执行一些标准化的任务&#xff0c;比如&#xff1a; 根据模…

一次tcpdump抓包过程

#查询网卡 tcpdump -D # 监听 21100 端口 网卡ens192 &#xff08;不知道网卡&#xff0c;可以直接不输入 -i 网卡&#xff09;TCP数据&#xff0c;等待一段时间&#xff0c;执行CtrlC&#xff0c;终止程序 tcpdump -x -s 0 -w /tmp/123.dump -i ens192 -p tcp port 21100 #…

充电桩小程序:引领未来,携手共创绿色充电新纪元

着新能源汽车市场的迅猛增长&#xff0c;充电桩行业正迎来前所未有的发展机遇。然而&#xff0c;在这个充满竞争和机遇并存的时代&#xff0c;如何快速、高效地满足用户需求&#xff0c;成为充电桩行业老板们关注的焦点。为此&#xff0c;我们推出了全新的充电桩小程序&#xf…

【智能算法应用】麻雀搜索算法在物流配送中心选址的应用(无待选配送中心)

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】麻雀搜索算法&#xff08;SSA&#xff09;原理及实现 2.数学模型 模型假设 待定物流配送中心的库存总能满足需求点的需求不考虑从工厂到待定物流配送中心的运输成本不考虑选定区域内待确定…

python爬虫--scrapy框架

Scrapy 一 介绍 Scrapy简介 1.Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架&#xff0c;用途非常广泛2.框架的力量&#xff0c;用户只需要定制开发几个模块就可以轻松的实现一个爬虫&#xff0c;用来抓取网页内容以及各种图片&#xff0c;非…

学生成绩管理系统带8000字文档学生选课管理系统java项目javaweb项目ssm项目jsp项目java课程设计java毕业设计

文章目录 学生选课成绩管理系统一、项目演示二、项目介绍三、8500字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带8500字文档&#xff08;9.9&#xffe5;带走&#xff09; 学生选课成绩管理系统 一、项目演示 选课成绩管理系统 二、项目介绍 语言: Java …

CentOS安装ntp时间同步服务

CentOS安装ntp时间同步服务 安装ntp 检查服务器是否安装ntp&#xff1a; rpm -q ntp安装ntp&#xff1a; yum install -y ntp服务端配置 配置文件路径&#xff1a;/etc/ntp.conf 设置ntp为开机启动 systemctl enable ntpd查看ntp开机启动状态 enabled:开启, disabled:关闭 …

第4章,在 PyCharm 中创建、打开、关闭项目的操作

在 PyCharm 中创建、打开、关闭项目的操作 在PyCharm中创建、打开和关闭项目的操作步骤。以下是每个操作的步骤说明&#xff0c;以及在PyCharm界面中可能对应的区域&#xff1a; 1、创建新项目 1&#xff09;启动PyCharm&#xff1a; 打开PyCharm IDE。 2&#xff09;创建新…

数据结构 —— 哈夫曼树

数据结构 —— 哈夫曼树 哈夫曼树定义构造算法特性应用 哈夫曼编码核心概念工作原理特点 我们今天来看哈夫曼树&#xff1a; 哈夫曼树 哈夫曼树&#xff08;Huffman Tree&#xff09;&#xff0c;是一种特殊的二叉树&#xff0c;由D.A. Huffman在1952年提出&#xff0c;主要用…

php聚合快递寄快递小程序

一、引言&#xff1a;告别传统寄件&#xff0c;拥抱便捷新选择 在数字化时代&#xff0c;我们越来越追求便捷和高效。传统的寄件方式已经无法满足现代人快速、便捷的需求。因此&#xff0c;一款聚合快递优惠寄件小程序应运而生&#xff0c;它集合了多家快递公司&#xff0c;为…

推荐两款电脑文件处理工具,强大到你不舍得卸载

EasyFileCount EasyFileCount是一款基于Java开发的多功能文件管理工具&#xff0c;旨在帮助用户更轻松地管理和优化他们的文件存储。以下是EasyFileCount的主要功能和特点&#xff1a; 查看文件夹大小&#xff1a;用户可以快速统计和查看文件夹的总大小&#xff0c;实时显示各…

计算机组成原理——系统总线

题目:计算机使用总线结构便于增减外设,同时__C____。 A.减少了信息传送量 B.提高了信息传输速度 C.减少了信息传输线的条数 1. 总线的分类 1.1. 片内总线 芯片内部的总线 在CPU芯片内部,寄存器与寄存器之间、寄存器与逻辑单元ALU之间 1.1.1. 数据总线 双向传输总线 数…

ISO37001认证:防范贿赂风险的强大工具

随着全球反贿赂法规的日益严格&#xff0c;各类组织面临的贿赂风险和合规压力不断增加。ISO37001反贿赂管理体系认证应运而生&#xff0c;为组织提供了一个系统化的管理框架&#xff0c;帮助其有效发现、预防和管控贿赂风险。本文将详细探讨ISO37001认证的作用和意义&#xff0…

刷代码随想录有感(119):动态规划——打家劫舍III(树形dp)

题干&#xff1a; 代码&#xff1a; class Solution { public:vector<int>dp(TreeNode* cur){if(cur NULL)return vector<int>{0, 0};vector<int> left dp(cur -> left);vector<int> right dp(cur -> right);//偷int val1 cur -> val l…

U-boot相关基础知识

U-boot和Bootloader之间的关系 U-Boot是Bootloader的一种实现&#xff0c;它专门用于嵌入式系统&#xff0c;特别是那些基于ARM、MIPS等处理器的系统。U-Boot提供了丰富的硬件支持和功能&#xff0c;使得开发者能够轻松地初始化硬件、加载操作系统内核&#xff0c;并进行一些基…

Adobe Indesign 操作

页面设置 版面&#xff1a;图文和空白部分的总和。 版心&#xff1a;规划在版面中排印文本和图片的部分。 开本&#xff1a;单个页面的宽度和高度。 如图所示&#xff0c;新建文件&#xff0c;自定义是210297毫米。这个数据是开本大小。 点击“边距和分栏”&#xff0c;出现…

技术干货|SimLab 电子产品热流体仿真

电子产品热仿真特点有哪些&#xff1f; 结构复杂&#xff0c;电子设备包含几十~上千个元器件 体积小&#xff0c;功率密度高、关注热敏感元器件 多种冷却方式&#xff0c;自然冷却、风扇冷却、液冷、热管等 多维度&#xff0c;芯片级&#xff0c;板级&#xff0c;系统级 单…

.idea文件夹里面iml文件有啥用

.idea文件夹和其中的.iml文件对于IntelliJ IDEA项目非常重要&#xff0c;它们共同构成了IDEA对项目理解的基础。让我们详细了解一下这两者的作用&#xff1a; .idea文件夹 .idea文件夹包含了IntelliJ IDEA项目的所有配置信息&#xff0c;包括但不限于&#xff1a; 编译器设置…

掌握SEO:如何优化用ChatGPT生成的文章以提升搜索排名

在数字化时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;已经成为网站流量的重要来源。随着人工智能技术的进步&#xff0c;越来越多的人开始使用ChatGPT等AI工具来生成文章。然而&#xff0c;虽然这些工具可以快速生成内容&#xff0c;但要确保这些内容在搜索引擎中…

武汉星起航:深度洞察消费趋势,亚马逊美国站选品独具匠心

亚马逊美国站作为全球电商巨头的重要分支&#xff0c;其选品特点不仅反映了美国市场的消费趋势&#xff0c;更引领着全球消费者的购物潮流。从运动户外、宠物用品到美容个人护理&#xff0c;亚马逊美国站的选品策略始终紧跟市场脉搏&#xff0c;为消费者提供丰富多样、品质优良…