字符和字符串在Java中的旅程
以下是个人对java中字符和字符串的见解,如有疏漏之处,还请不吝赐教。
下面通过一个简单的程序来说明字符和字符串在Java中的旅程。
以字符 ' 中 '为例, 它的GBK编码是2个字节:0xd6d0, UTF-16 编码是2个字节:0x4e26,UTF-8编码是3个字节: 0xe4b8ab
public class CharacterInJava { public static void main(String[] args) { char c = '中';
String s = "我是一个中国人";
System.out.println(c + "" + c1 + s);
}
}
当编辑完成CharacterInJava.java,您会选择保存文件。这时,CharacterInJava.java会保存在磁盘上。在保存的过程中,文本编辑器会帮我们将这个文件中的字符编码,我们可以指定编码,比如选择GBK编码时,字符 '中'会编码为 0xd6d0,如果是UTF-8的话,则会编码为 0xe4b8ab。下面是两种不同的编码保存文件的16进制表示:
GBK: UTF-8:
然后,我们接着要做的是编绎CharacterInJava.java, 在命令行输入javac -encoding GBK -d . CharacterInJava.java,即可编绎文件。这时可能会发生如下的错误:
错误的原因是,javac 通过-encoding 参数知道 CharacterInJava.java 是GBK编码的,因此使用GBK对源码进行解读。但其实,源码是用UTF-8编码的,因此发生了不可映射字符错误。通过更改为-encoding为UTF-8,即可编绎成功。也就是说,源文件是什么编码,程序员应该要清楚。
编绎完成后,生成了一个类文件:CharacterInJava.class,对文件进行反编绎:
Classfile /E:/JavaTutorial/IO/CharacterInJava.class Last modified 2019年2月19日; size 914 bytes MD5 checksum 5c174b19c95e5b26e64051fb06137319 Compiled from "CharacterInJava.java" public class CharacterInJava minor version: 0 major version: 53 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #6 // CharacterInJava super_class: #7 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 3 Constant pool: #1 = Methodref #7.#16 // java/lang/Object."<init>":()V #2 = String #17 // 我是一个中国人 #3 = Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream; #4 = InvokeDynamic #0:#23 // #0:makeConcatWithConstants:(CLjava/lang/String;)Ljava/lang/String; #5 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #26 // CharacterInJava #7 = Class #27 // java/lang/Object #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = Utf8 SourceFile #15 = Utf8 CharacterInJava.java #16 = NameAndType #8:#9 // "<init>":()V #17 = Utf8 我是一个中国人 #18 = Class #28 // java/lang/System #19 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #20 = Utf8 BootstrapMethods #21 = MethodHandle 6:#31 // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; #22 = String #32 // \u0001\u0001 #23 = NameAndType #33:#34 // makeConcatWithConstants:(CLjava/lang/String;)Ljava/lang/String; #24 = Class #35 // java/io/PrintStream #25 = NameAndType #36:#37 // println:(Ljava/lang/String;)V #26 = Utf8 CharacterInJava #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Methodref #38.#39 // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; #32 = Utf8 \u0001\u0001 #33 = Utf8 makeConcatWithConstants #34 = Utf8 (CLjava/lang/String;)Ljava/lang/String; #35 = Utf8 java/io/PrintStream #36 = Utf8 println #37 = Utf8 (Ljava/lang/String;)V #38 = Class #40 // java/lang/invoke/StringConcatFactory #39 = NameAndType #33:#44 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; #40 = Utf8 java/lang/invoke/StringConcatFactory #41 = Class #46 // java/lang/invoke/MethodHandles$Lookup #42 = Utf8 Lookup #43 = Utf8 InnerClasses #44 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; #45 = Class #47 // java/lang/invoke/MethodHandles #46 = Utf8 java/lang/invoke/MethodHandles$Lookup #47 = Utf8 java/lang/invoke/MethodHandles { public CharacterInJava(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: sipush 20013 3: istore_1 4: ldc #2 // String 我是一个中国人 6: astore_2 7: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_1 11: aload_2 12: invokedynamic #4, 0 // InvokeDynamic #0:makeConcatWithConstants:(CLjava/lang/String;)Ljava/lang/String; 17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 20: return LineNumberTable: line 3: 0 line 4: 4 line 6: 7 line 8: 20 }
从常量池中看到,字符串是用UTF-8编码的。从main函数中第一行sipush 20013指令看到,字符 ' 中 '是用UTF-16编码的。
也就是说,class文件的字符按照UTF-16编码,大多数的字符都是用一个代码单元就可以表示的。字符串按照UTF-8编码。下面直接在
class文件中找出这些对应的编码。0xe6 88 91 对应于 我,0x4e2d 对应于 中
接下来是运行class文件:
查阅printfln的API知道,字符和字符串的输出,先根据平台默认的字符集将输出的字符或编码成一个或多个字节,然后将这些字节输出到终端。
整个过程大致如上,知道字符和字符串的编码,应该不易出现乱码。