编程基础+二进制
声明:少数引用部分,知识学习分享,不予商业用途。
1、编程基础
1.1 数据类型和变量
(1)整数类型:byte short int long
byte:8位带符号的二进制数,最高位表示正负,0为正,1为负;占一个字节。
short:短整型,占两个字节。
int:整型,占四个字节。
long:长整型,占八个字节。
(2)小数类型:float double
float:浮点型,占四个字节。
double:双精度浮点型,占八个字节。
(3)字符类型:char string
char:字符类型定义。
string:字符串类型定义。 区别一字,天差地别。
(4)真假类型(一般称布尔型):boolean
boolean:进行判断真假,只有 Tule / False 两种。 ps:bsil 1248 fd 48
Java时面向对象的语言,除了基本数据类型,其他都是对象类型。对象就是由基本数据类型、数组和其它对象组合而成的一个东西。
为了操作数据,需要把数据存放到内存当中。eg:声明一个变量:
int a
这是在内存中分配了一块空间,这块空间存放int数据类型,a指向这块内存空间所在的位置;通过对a操作,即可操作a指向的内存空间,比如a = 6这个操作,就是将a指向的内存空间的值改为6。
那么为什么叫做变量呢,这是因为它表示的是内存中的位置,这个位置存放的值是可以变化的。
变量的值可变,但是变量的名不可变。
1.2 赋值
声名变量后,就在内存分配了一块位置,这个位置的内容是未知的,赋值的操作就是将这个位置的内容设定为一个确定的值。
整数类型的取值范围:
byte:(-2)^7 ~ 2^(7-1) short:(-2)^15 ~ 2^(15-1)
int:(-2)^31 ~ 2^(31-1) long:(-2)^63 ~ 2^(63-1)
这些范围只需要了解各大概就好。如果在给long型赋值时,常量超爱过了int的表示范围,需要在常量后面加上大写或小写L。eg:
long a = 6666666666L
数字常量默认为是int类型,所以需要加上L或l。
小数类型的取值范围:
float:1.4E-45 ~ 3.4E+38 double:4.9E-324 ~ 1.7E+308
-3.4E+38 ~ -1.4E-45 -1.7E+308 ~ -4.9E-324
这个也是有个大概的印象即可。E表示以10为底的指数,E后面的 +号 和 -号 代表正指数和负指数。eg:
1.5E - 19表示1.5乘以10的 -19 次方。
这两个类型赋值,有一点点差别:
double d = 159.15;
float f = 159.15f;
float类型需要在后面加上大/小写字母F/f。小数常量默认是double类型,所以浮点型需要加上尾缀。
真假类型:
直接使用ture或false赋值即可,eg:
boolean b = false;
b = ture;
字符类型:
char用于表示一个字符,可英可中,char占用两个字节的内存空间;赋值时要把常量字符使用单引号括起来,切忌使用双引号。
char k = '哇';
char m = 'w';
除了有些特殊的字符用一个char表示不了,大部分都可以。
此外,变量也可以赋值给变量,eg:
int a = 159;
int b = a;
ps:这里说一下运算法则,括号里最优先,其次是乘除加减…正常来即可。
1.2.1 数组类型
基本类型的数组有3种赋值行式:
# 1
int[] arr = {1,2,3};
# 2
int[] arr = new int[]{1,2,3};
# 3
int[] arr = new int[3];
arr[0] = 1; arr[1] = 2; arr[2] = 3
前两种已经预先知道了数组的内容;第三种则是先分配长度,之后再给每个元素赋值;在第三种形式中,即使没有给每个元素赋值,每个元素也都有一个默认的值(与数组类型有关,数值类型的值为0,boolean为false,char为空字符)。
数组长度可以是变量,而长度为new的时候变量值固定,不可变化。
数组有一个length属性,可读不可变。
1.3 基本运算
1.3.1 算术运算
算术运算符加减乘除( + - * / ),取模运算符( % ),自增( ++ ),自减( – )
取模运算符适用于整数和字符类型,其他运算符适用于所有数值类型和字符类型。其实取模就是数学中的求余数。
eg:5 % 3 就是2,10 % 5 就是0
自增和自减是一种快捷、对自己进行加一减一的操作。
整数相除不是四舍五入,而是直接舍去小数位。
a++ 与 ++a 的操作顺序不同,留意。
1.3.2 比较运算
比较操作符:大于 > 大于等于 >= 小于 < 小于等于 <= 等于 == 不等于 ! =
一个等于号表示的是赋值的操作,而不是等于的数学含义。
引用数据类型使用 == 是对该对象的地址值相比较,也就是指向堆内从中空间位置是否相同。
1.3.3 逻辑运算
逻辑运算止咳应用于boolean类型的数据,但比较运算的结果是布尔值,所以其他类型数据的比较结果可进行逻辑运算。
逻辑运算符:
-
与(&):左右两侧都为true才是true,只要有一个是false就是false
-
或(|):只要有一个为true就是true,都是false才是false
-
非( ! ):针对一个变量,true会变成false,false会变成true
-
异或(^):两个相同为false,两个不相同为true
-
短路与(&&):当左侧值为false时,则不会执行后面的语句
-
短路或(||):当左侧值为true时,则不会执行后面的语句
左侧 | 右侧 | 效果 | |
---|---|---|---|
与(&) | true | true | true |
false/true | true/false | false | |
false | false | false | |
或(|) | true | true | true |
true/false | false/true | true | |
false | false | false | |
非( ! ) | 针对单一变量 | ||
异或(^) | true | true | false |
true/false | false/true | true | |
false | false | false | |
短路与(&&) | true | true/false | 参照与同样方法 |
false | true/false | 不执行右侧的 | |
短路或(||) | true | ture/false | 不执行右侧的 |
false | ture/false | 参照或同样方法 |
1.4 条件执行
流程控制中最基本的就是条件执行,一些操作只能在某些条件满足的情况下才执行。在A条件下,执行a操作;在B条件下,执行b操作。
1.4.1 语法和陷阱
Java中表达条件执行的基本语句是if语句,如下:
if(条件语句){
代码块
}
# 或
if(条件语句) 代码:
条件语句必须为布尔值,条件语句为ture,则执行缩进后的代码;如果后面没有 } 来收尾,则执行第一个分号 ;前的语句。
为了避免一次次增删改查,习惯性的加上 {} 比较好。
如果想表达条件不满足时,执行另一种操作,则使用if / else语句,语法如下:
if (条件语句){
代码块1
}else{
代码块2
}
这里还有一个东西叫做三元运算符,语法为:
判断条件 ? 表达式1: 表达式2
三元运算符的得到一个结果,判断条件为真的时候就会返回表达式1的值,否则就会返回表达式2的值。它常常用作对某个变量进行自主赋值,比如说比较哪个数比较大:
int max = x>y ? x : y;
由于三元运算符比if/else语句更为简洁,通常可以用来替代。如果有多个判断条件,还是使用if/else语句。
if(条件1){
代码块1
}else if(条件2){
代码块2
}...
注意,if/else语句中的判断顺序非常重要,后面的只会在前一个为false时执行判断。
除了繁琐的if/else,还有一种简便的方法叫做Switch;根据表达式的值找到匹配的值则执行default后的语句。Switch中的表达式的值只能为byte、short、int、char、枚举、String。
其中,break指跳出switch语句,执行switch后面的语句;每条case语句后面都应该跟break语句,否则会继续执行后面case中的代码,直到遇见break语句或switch结束。语法:
switch(表达式){
case 值1:
代码1; break;
case 值2:
代码2; break;
...
case 值n:
代码n; break;
default: 代码n+1
}
情况:单一条件满足时,执行某操作使用if; 根据一个条件是否满足执行不同分支使用if/else; 表达复杂的条件使用if/else; 条件赋值使用三元运算符,根据某一个表达式的值不同,执行不同的分支使用switch。
1.4.2 实现原理
“程序始终都是一条条的指令,CPU有一个指令指示器,指向下一条要执行的指令,CPU根据指示器的指示加载指令 并且执行。指令大部分是具体的操作和运算,在执行这些操作时,执行完一个操作后,指令指示器会自动指向挨着的下一条指令。有种特殊的指令,叫做跳转指令”
跳转指令分两种:
① 条件跳转:跳转检查某个条件,满足则进行跳转。
② 无条件跳转:直接进行跳转。
跳转表:
条件值 | 跳转地址 |
---|---|
值1 | 代码块1的地址 |
值2 | 代码块2的地址 |
… | … |
值n | 代码块n的地址 |
当使用switch时,如果case的分支过多,会转换成跳转表——编译器会对case进行排序,这也就是switch的类型限定的原因,排序后用二分查找法,可快速找到相应的索引值;如果值是连续的还会有换成数组、随机访问,找到对应的值。
1.5 循环
如题,循环就是多次重复执行某些类似的操作;计算机程序运行时大致只能顺序执行、条件执行和循环执行;通过循环,能使计算机完成许多高效、繁琐、复杂的事情。
1.5.1 循环的4种形式
-
while
while(条件语句){ 代码块 } 或着: while(条件语句) 功能性代码块;
“while和if类似,只要条件语句为真,就会一直执行后续代码,为假就会终止”
-
do/while
do{ 代码块; }while(条件语句)
“不管条件语句是什么,代码块至少执行一次;先执行代码块,然后再判断条件语句,如果成立,则继续循环,否则退出循环”
-
for
for(初始化语句; 循环条件; 步进操作){ 循环体 }
“除了循环条件必须返回一个Boolean类型外,其它语句没有要求;通常情况下第一条语句用于初始化,尤其是循环的索引变量,第三条语句修改循环变量,一般是步进,即递增或递减索引变量,循环体是在循环中执行的语句。”
“步进式操作:计算机程序调试的一种工作方式;在这种方式下, 计算机每执行一条指令后就停下来,这时程序员可以检查或修改寄存器或内存中的内容,无错后再发出下一步执行命令。”
-
foreach
int[] arr = {1,2,3,4}; for(int element : arr){ System.out.println(element); }
“foreach不是一个关键字,冒号 : 前是循环中的每个元素,包括数据类型、变量名称,冒号后是要遍历的数组/集合,每次循环element都会自动更新。不需要使用索引变量的时候,foreach更为简洁。”
1.5.2 循环控制
“有时,需要根据别的条件提前结束循环或跳过一些代码,这是可以使用break或continue关键字对循环进行控制。“
- break
break用于提前结束循环。
- continue
在循环的过程中,有的代码可能不需要每次循环都执行,此时,可以使用continue语句,它会跳过循环体中剩下的代码,然后执行步进操作。
1.5.3 实现原理
”与 if 一样,循环内部也是靠条件转移和无条件转移指令实现“ eg:
int[] arr = {1,2,3,4};
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}
推测其跳转过程:
1. int[] arr ={1,2,3,4};
2. int i=0;
3.条件转换:如果i>=arr.length,跳转到第七行
4.System.out.println(arr[i]);
5.i++
6.无条件跳转,跳转至第三行
7.其它代码
1.6 函数使用方法
”计算机程序使用函数这个概念来解决很多问题,即使用函数来减少重复代码和分解复杂操作。“
1.6.1 基本概念
函数的主要组成部分:
1、函数名:函数名表示函数的功能。
2、参数:参数有0个到多个,每个参数由参数的数据类型和参数名字组成。
3、操作:函数的具体操作代码。
4、返回值:函数可以没有返回值,假如没有返回值,类型写成void, ,如果有,则在函数代码中必须使用return语句返回一个值,这个值的类型需要和声名的返回值类型一致。
5、修饰符:Java中有许多修饰符,分别表示不同的目的,目前主用public static。
1.6.2 深度理解函数
“函数的定义和基本调用容易理解,此后将深入参数传递、返回、函数命名、调用流程等。”
① 参数传递
有两类特殊类型的参数:数组、可变长度的参数。
(1) 数组
list、dictionary等引用类型,在参数传递的过程都是属于传递地址,并且都指向同一块内存,因此函数内部的修改会导致原始 数据内容被修改。
(2) 可变成都的参数
参数的个数都是固定的,但有时我们希望参数个数非固定,比如求若干数的最大值,可以是三个乃至多个。
② 理解返回
函数返回值为void时,return非必需;在没有return情况下,会执行到函数结尾自动返回。
return用于函数内的任意地方;也可以在if语句内,或在for循环内,用于提前结束函数执行,返回调用方。
③ 重复の命名
在不同类里,函数名可以重复;在同一个类中,要分情况。
同一个类中函数名相同但是参数不同的现象,称为函数重载。
④ 调用的匹配过程
参数传递实际上是给参数赋值,调用者传递的数据需要与函数声明的参数类型是匹配的。
⑤ 递归函数
函数调用它自己的函数,叫做递归函数;比如数学中一个数 n 的阶乘,表示为 n ! ,0的阶乘是1,n的阶乘的值是 n-1 的阶乘的值 乘以 n ,这就是递归的定义。为求n的值,需先求n-1的值,直到0,然后依次往回退。用递归表达的计算用递归函数易实现,eg:
public static lonng factorial(int n){
if(n==0){
return 1;
}else{
return n*factorial(n-1);
}
}
1.7 函数调用的基本原理
这里引进一个重要的概念:栈。
1.7.1 栈的概念
首先说一下程序执行的基本原理:CPU有一个指令指示器,指向下一条要执行的命令,要么顺序执行,或进行跳转。
“使用内存来存放这些数据,函数调用方和函数自己就如何存放和使用这些数据达成一个一致的协议或约定。这个约定在各种计算机系统中都是类似的,存放这些数据的内存有一个相同的名字,叫栈。“
”栈是一块内存,但它的使用有特别的约定,一般是先进后出,类似于一个桶,往栈里放数据称为入栈,最下面的称为栈底,最上面的称为栈顶,从栈顶拿出数据通常称为出栈。栈一般是从高位地址向低位地址扩展,换句话说,栈底的内存地址是最高的,栈顶的是最低的。“
”计算机系统主要使用栈来存放函数调用过程中需要的数据,包括参数、返回地址,以及函数内定义的局部变量。计算机系统就如何在栈中存放这些数据,调用者和函数如何协作做了约定。”
“返回值不太一样,它可能放在栈中,但它使用的栈和局部变量不完全一样,有的系统使用CPU内的一个存储器存储返回值,我们可以简单认为存在一个专门的返回值存储器。main函数的相关数据放在栈的最下面,每调用一次函数,都会将相关函数的数据入栈,调用结束会出栈。“
pl:栈先入后出,全局变量最先入栈,最后出栈;局部变量后入栈先出栈。内存分配合理节省空间。
数据和对象类,有两块内存,指向内容空间的地址、内容空间的地址;前者存放在栈上,后者存放在堆上。
”从函数调用的过程可以看出,调用是有成本的,每一次调用都需要分配额外的栈空间用于存储参数、局部变量以及返回地址,需要进行额外的入栈和出栈操作。在递归调用的情况下,如果递归的次数比较多,这个成本是比较可观的,所以,如果程序可以比较容易地改为其他方式,应该考虑其他方式。另外,栈的空间不是无限的,一般正常调用都是没有问题的,但如果栈空间过深,系统就会抛出错误java.lang.StackOverflowError,即栈溢出。”
2、二进制
2.1 整数的二进制表示与位运算
十进制中的 123 的内容是这样的 :1x(10^2) + 2x (10^1) + 3x (10^0) ,它表示的是各个位置数字含义之和,每个位置数字的含义与位置相关 ,从右向左第一位,乘以10的0次方,向左一位,乘以10的1次方,以此类推。
“每个位置都有一个叫做 位权 的东西,从右到左,第一位为1,然后依次乘以10,即第二位为10,第三位为100,以此类推。”
2.1.1 正整数的二进制表示
在十进制中,每个位置有10个数字,为1~9;在二进制中,每个位置只能是0或1。
二进制 | 十进制 | 二进制 | 十进制 |
---|---|---|---|
10 | 2 | 111 | 7 |
11 | 3 | 1010 | 10 |
2.1.2 负整数的二进制表示
在二进制中,最高位表示符号位,用1表示负数,用0表示正数。整数有四种类型:byte、short、int、long,分别占1、2、4、8个字节,也就是占8、16、32、64位,每种类型的符号位都是其最左边的一位。eg:
byte a = -1 #如果只是将最高位变为1,二进制应该是10000001,实际上却是11111111
byte a = -127 #如果只是将最高位变为1,二进制应该是11111111,实际上却是10000001
是不是很奇怪?这类表示法称作补码表示法,符合第一印象的称为原码表示法,补码表示就是在原码表示的基础上取反然后加1.取反就是将0变为1,1变为0。eg:
#-1
1的原码表示是00000001,取反是11111110,然后再加1,就是11111111
#-2
2的原码表示是00000010,取反是11111101,然后再加1,就是11111110
#-127
127的原码表示是01111111,取反是10000000,然后再加1,就是10000001
给定一个负数的二进制表示,要想知道它的十进制,可以采用相同的补码运算。eg:
10010010,step1:取反,变为01101101,加1,结果为01101110,它的十进制为110,所以原值就是-110。
对于byte类型,正数最大表示为01111111,即127,负数最小表示10000000,即-128,范围为-128~127;其它的类型与其一致,负数能多表示一个数。
关于负整数采用这种奇葩的方式,是因为计算机只能做加法,我们看到的1-1其实是1+(-1),只用这样,计算机才能正确实现加减法。
2.1.3 十六进制
由于二进制实在是太长,为了简化写法,将4个二进制位简化为一个015**的数,其中**1015的部分用字母A~F表示,此方法称为十六进制。eg:
二进制 | 十进制 | 十六进制 | 二进制 | 十进制 | 十六进制 |
---|---|---|---|---|---|
1010 | 10 | A | 1101 | 13 | D |
1111 | 11 | B | 1110 | 14 | E |
1100 | 12 | C | 1111 | 15 | F |
十六进制直接写常量数字时,在数字前面加0x即可。
2.1.4 位运算
位运算有移位运算和逻辑运算。移位:
1、左移:操作符为<<,向左移动,右边的低位补0,高位的就舍弃掉,将二进制看作整数,左移1位相当于乘以2。
2、无符号右移:操作符位>>>,向右移动,右边的舍弃掉,左边补0。
3、有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是1就补1,原来是0就补0,将二进制看作整数,右移1位相当于除以2。
eg:
int a = 4; //100
a = >> 2; //001,等于1
a = a << 3; //1000,变为8
逻辑运算都有以下几种:
1、按位与**&:两位都为1**才为1。
2、按位或**|**:只要有一位为1,就为1。
3、按位取反**~**:1变为0,0变为1。
4、按位异或**^**:相异为真,相同为假。
eg:
int a = x;
a = a & 0x1 //返回0或1,也就是a最右边一位的值
a = a | 0x1 //不管a原来最右边一位是什么,都将设为1
2.2 小数的二进制表示
计算机在计算一些基本的小数时,也会出错,所以它的计算也不一定精确。
2.2.1 小数计算出错的原因
计算机本不能精确的表示很多数,比如0.1。小数是用二进制格式储存的,它只能表示一个非常接近0.1但又不是0.1的数。
2.2.2 二进制表示小数
小数在计算机中叫做**“浮点数”,float 和 double 被称为浮点数据类型,小数运算被称作浮点运算**。之所以称之为浮点,是因为小数的二进制表示中,表示那个小数点时,点不是固定的,而是不断浮动的。
十进制中的科学计数法1.2345E2就是1.2345x(102),小数点向左浮动了两位。二进制中也采用类似的**科学表示法**,即为**Mx(2e)**。M是尾数,e称为指数。指数可以是正的,也可以是负的,负指数表示接近0的比较小的数。
2.2.3 字符的编码
编码与乱码听起来复杂,实则反之。编码有两大类,一类为非Unicode编码,另一类为Unicode编码。
2.3.1 常见非Unicode编码
主要包括ASCII、ISO8859-1、Windows-1252、GB2312、GBK、GB18030、Big5。
1.ASCII
”世界上虽然有各种各样的字符,但计算机发明之初没有考虑那么多,基本上只考虑了美国的需求。美国大概只需要128个字符,所以就规定了128个字符的二进制表示方法。这个方法是一个标准,称为ASCII编码,全称是American Standard Code for InformationInterchange,即美国信息互换标准代码。
128个字符用7位刚好可以表示,计算机存储的最小单位是byte,即8位,ASCII码中最高位设置为0,用剩下的7位表示字符。这7位可以看作数字0~127, ASCII码规定了从0~127的每个数字代表什么含义。
先来看数字32~126的含义,如图所示,除了中文之外,我们平常用的字符基本都涵盖了,键盘上的字符大部分也都涵盖了。
可打印字符:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDnQYFdf-1651201455408)(D:\Users\33210\Desktop\微信图片_20220424110928.jpg)]
数字32~126表示的字符都是可打印字符,0~31和127表示一些不可以打印的字符,这些字符一般用于控制目的,这些字符中大部分都是不常用的,表列出了其中相对常用的字符。“
常用不可打印字符:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lryvRNI9-1651201455410)(C:\Users\33210\AppData\Roaming\Typora\typora-user-images\image-20220424110956157.png)]
”ASCII码对美国是够用了,但对其他国家而言却是不够的,于是,各个国家的各种计算机厂商就发明了各种各种的编码方式以表示自己国家的字符,为了保持与ASCII码的兼容性,一般都是将最高位设置为1。也就是说,当最高位为0时,表示ASCII码,当为1时就是各个国家自己的字符。在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312、GBK、GB18030和Big5。”
-
ISO 8859-1
“ISO 8859-1又称Latin-1,它也是使用一个字节表示一个字符,其中0~127与ASCII一样,128~255规定了不同的含义。在128~255中,128~159表示一些控制字符,这些字符也不常用。160~255表示一些西欧字符,如图所示。”[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvNIvX9E-1651201455411)(C:\Users\33210\AppData\Roaming\Typora\typora-user-images\image-20220424111144301.png)] -
Windows-1252
“ISO 8859-1虽然号称是标准,用于西欧国家,但它连欧元(€)这个符号都没有,因为欧元比较晚,而标准比较早。实际中使用更为广泛的是Windows-1252编码,这个编码与ISO 8859-1基本是一样的,区别只在于数字128~159。Windows-1252使用其中的一些数字表示可打印字符,这些数字表示的含义如图所示。”
区别于ISO8859-1的部分:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j68vufIJ-1651201455412)(C:\Users\33210\AppData\Roaming\Typora\typora-user-images\image-20220424111449936.png)]
“这个编码中加入了欧元符号以及一些其他常用的字符。基本上可以认为,ISO 8859-1已被Windows-1252取代,在很多应用程序中,即使文件声明它采用的是ISO 8859-1编码,解析的时候依然被当作Windows-1252编码。”
“HTML5甚至明确规定,如果文件声明的是ISO 8859-1编码,它应该被看作Win-dows-1252编码。为什么要这样呢?因为大部分人搞不清楚ISO 8859-1和Windows-1252的区别,当他说ISO 8859-1的时候,其实他指的是Windows-1252,所以标准干脆就这么强制规定了。”
4.GB2312
“美国和西欧字符用一个字节就够了,但中文显然是不够的。中文第一个标准是GB2312。GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字和一些罕用词和繁体字。”
“GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是ASCII字符。在这两个字节中,其中高位字节范围是0xA1~0xF7,低位字节范围是0xA1~0xFE。”
5.GBK
“GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符和二进制表示,在GBK编码里是完全一样的。GBK增加了14 000多个汉字,共计约21 000个汉字,其中包括繁体字。”
“GBK同样使用固定的两个字节表示,其中高位字节范围是0x81~0xFE,低位字节范围是0x40~0x7E和0x80~0xFE。”
“需要注意的是,低位字节是从0x40(也就是64)开始的,也就是说,低位字节的最高位可能为0。那怎么知道它是汉字的一部分,还是一个ASCII字符呢?其实很简单,因为汉字是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位为1,那么就将下一个字节读进来一起解析为一个汉字,而不用考虑它的最高位,解析完后,跳到第三个字节继续解析。”
6.GB18030
“GB18030向下兼容GBK,增加了55 000多个字符,共76 000多个字符,包括了很多少数民族字符,以及中日韩统一字符。”
“用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节。在两字节编码中,字节表示范围与GBK一样。在四字节编码中,第一个字节的值为0x81~0xFE,第二个字节的值为0x30~0x39,第三个字节的值为0x81~0xFE,第四个字节的值为0x30~0x39。”
“解析二进制时,如何知道是两个字节还是4个字节表示一个字符呢?看第二个字节的范围,如果是0x30~0x39就是4个字节表示,因为两个字节编码中第二个字节都比这个大。”
7.Big5
“Big5是针对繁体中文的,广泛用于我国台湾地区和我国香港特别行政区等地。Big5包括13 000多个繁体字,和GB2312类似,一个字符同样固定使用两个字节表示。在这两个字节中,高位字节范围是0x81~0xFE,低位字节范围是0x40~0x7E和0xA1~0xFE。”
8.编码汇总
“简单汇总一下前面的内容。
ASCII码是基础,使用一个字节表示,最高位设为0,其他7位表示128个字符。其他编码都是兼容ASCII的,最高位使用1来进行区分。
西欧主要使用Windows-1252,使用一个字节,增加了额外128个字符。
我国内地的三个主要编码GB2312、GBK、GB18030有时间先后关系,表示的字符数越来越多,且后面的兼容前面的,GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。
我国香港特别行政区和我国台湾地区的主要编码是Big5。
如果文本里的字符都是ASCII码字符,那么采用以上所说的任一编码方式都是一样的。
但如果有高位为1的字符,除了GB2312、GBK、GB18030外,其他编码都是不兼容的。比如,Windows-1252和中文的各种编码是不兼容的,即使Big5和GB18030都能表示繁体字,其表示方式也是不一样的。”
2.3.2 Unicode编码
世界上字符统一的编码:Unicode编码。Unicode主要做了这么一件事,就是给所有字符分配了唯一数字编号。它并没有规定这个编号怎么对应到二进制表示,这是与上面介绍的其他编码不同的,其他编码都既规定了能表示哪些字符,又规定了每个字符对应的二进制是什么,而Unicode本身只规定了每个字符的数字编号是多少。编号对应到二进制表示有多种方案,主要有UTF-32、UTF-16和UTF-8。
1.UTF-32
这个最简单,就是字符编号的整数二进制形式,4个字节。
但有个细节,就是字节的排列顺序,如果第一个字节是整数二进制中的最高位,最后一个字节是整数二进制中的最低位,那这种字节序就叫“大端”(Big Endian, BE),否则,就叫“小端”(Little Endian, LE)。对应的编码方式分别是UTF-32BE和UTF-32LE。可以看出,每个字符都用4个字节表示,非常浪费空间,实际采用的也比较少。
2.UTF-16
UTF-16使用变长字节表示:
1)对于编号在U+0000~U+FFFF的字符(常用字符集),直接用两个字节表示。需要说明的是,U+D800~U+DBFF的编号其实是没有定义的。
2)字符值在U+10000~U+10FFFF的字符(也叫做增补字符集),需要用4个字节表示。前两个字节叫高代理项,范围是U+D800~U+DBFF;后两个字节叫低代理项,范围是U+DC00~U+DFFF。数字编号和这个二进制表示之间有一个转换算法,本书就不介绍了。
区分是两个字节还是4个字节表示一个字符就看前两个字节的编号范围,如果是U+D800~U+DBFF,就是4个字节,否则就是两个字节。
UTF-16也有和UTF-32一样的字节序问题,如果高位存放在前面就叫大端(BE),编码就叫UTF-16BE,否则就叫小端,编码就叫UTF-16LE。
UTF-16常用于系统内部编码,UTF-16比UTF-32节省了很多空间,但是任何一个字符都至少需要两个字节表示,对于美国和西欧国家而言,还是很浪费的。
3.UTF-8
TF-8使用变长字节表示,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数为1~4不等。具体来说,各个Unicode编号范围对应的二进制格式如表所示。
UTF-8编码的编号范围与对应的二进制格式:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yaUpNmi4-1651201455414)(C:\Users\33210\AppData\Roaming\Typora\typora-user-images\image-20220424113231778.png)]
表中的x表示可以用的二进制位,而每个字节开头的1或0是固定的。
小于128的,编码与ASCII码一样,最高位为0。其他编号的第一个字节有特殊含义,最高位有几个连续的1就表示用几个字节表示,而其他字节都以10开头。
对于一个Unicode编号,具体怎么编码呢?首先将其看作整数,转化为二进制形式(去掉高位的0),然后将二进制位从右向左依次填入对应的二进制格式x中,填完后,如果对应的二进制格式还有没填的x,则设为0。
和UTF-32 / UTF-16不同,UTF-8是兼容ASCII的,对大部分中文而言,一个中文字符需要用三个字节表示。
4.Unicode编码小结
Unicode给世界上所有字符都规定了一个统一的编号,编号范围达到110多万,但大部分字符都在65 536以内。Unicode本身没有规定怎么把这个编号对应到二进制形式。
UTF-32/UTF-16/UTF-8都在做一件事,就是把Unicode编号对应到二进制形式,其对应方法不同而已。UTF-32使用4个字节,UTF-16大部分是两个字节,少部分是4个字节,它们都不兼容ASCII编码,都有字节顺序的问题。UTF-8使用1~4个字节表示,兼容ASCII编码,英文字符使用1个字节,中文字符大多用3个字节。
2.4 char 的含义
Java中进行字符处理的基础char,像Character、String、StringBuilder等都是基于char用于文本处理的。char用于表示一个字符,这个字符可以是中文字符,也可以是英文字符。赋值时把常量字符用单引号括起来,eg:
char a = '玛卡巴卡';
char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符。由于固定占用两个字节,char只能表示编号在65536以内的字符,超出范围的字符用其它表示。char的其它赋值形式,eg:
char b = 114514;
char c = 'U+1F468';
char d = c8cb;
左依次填入对应的二进制格式x中,填完后,如果对应的二进制格式还有没填的x,则设为0。*
和UTF-32 / UTF-16不同,UTF-8是兼容ASCII的,对大部分中文而言,一个中文字符需要用三个字节表示。
4.Unicode编码小结
Unicode给世界上所有字符都规定了一个统一的编号,编号范围达到110多万,但大部分字符都在65 536以内。Unicode本身没有规定怎么把这个编号对应到二进制形式。
UTF-32/UTF-16/UTF-8都在做一件事,就是把Unicode编号对应到二进制形式,其对应方法不同而已。UTF-32使用4个字节,UTF-16大部分是两个字节,少部分是4个字节,它们都不兼容ASCII编码,都有字节顺序的问题。UTF-8使用1~4个字节表示,兼容ASCII编码,英文字符使用1个字节,中文字符大多用3个字节。
2.4 char 的含义
Java中进行字符处理的基础char,像Character、String、StringBuilder等都是基于char用于文本处理的。char用于表示一个字符,这个字符可以是中文字符,也可以是英文字符。赋值时把常量字符用单引号括起来,eg:
char a = '玛卡巴卡';
char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符。由于固定占用两个字节,char只能表示编号在65536以内的字符,超出范围的字符用其它表示。char的其它赋值形式,eg:
char b = 114514;
char c = 'U+1F468';
char d = c8cb;
由于char本质上是一个整数,所以可以进行整数能做的一些运算,进行运算时它被看作 int ,由于占char两个字节,运算结果不能直接赋值给 char类,需要进行强制类型转换;与 byte、short参与整数运算类似。对ASCII字符转换时,A ~ Z编号为65 ~ 90,a ~ z编号为97 ~ 122,相差32,大小写转换只需加减32。