最近在阅读《Java编程的逻辑》一书,受益良多,在此对作者(马俊昌)万分的感谢。
计算机是一个机器,只能处理二进制数据,比如0100 1101。
但直接使用二进制进行编程开发,非常的不友好,所以有了各种高级开发语言,如 C、C++、C#、Java、PHP等。
为了更好的操作二进制数据,高级开发语言大多引入了数据类型的概念,使用不同的类型来表示数据。
基本数据类型
对于 Java 语言来讲,有如下基本数据类型:
1、整数类型
byte
占1个字节,8位二进制,第1位是符号位,取值范围为:-27(即1000 0000) ~ 27-1(即0111 1111)。
byte b = 23;
short
占2个字节,16位二进制,第1位是符号位,取值范围为:-215(即1000 0000 0000 0000) ~ 215-1(即0111 1111 1111 1111)。
short s = 3333;
int
占4个字节,32位二进制,第1位是符号位,取值范围为:-231(即1000 0000 0000 0000 0000 0000 0000 0000) ~ 231-1(即0111 1111 1111 1111 1111 1111 1111 1111)。
int i = 9999;
long
占8个字节,64位二进制,第1位是符号位,取值范围为:-263 ~ 263-1。
整数值默认为int类型,给long类型变量赋值时需要在后面加小写字母 l 或大写字母 L,防止值超出了int的取值范围。
long l = 32323L;
2、浮点数类型
浮点数,即数学中的“小数”。
因为在计算机中表示小数的点不是固定的,而是浮动的,所以被称为“浮点数”。
浮点数在计算机中采用类似十进制的科学计数法来表示的,即m×2n,其中m称为“尾数”,n称为“指数”。
计算机中保存的小数,即保存的m和n。
由此可见,计算机只能精确的表示可以表述为2的几次方的小数(比如1×2-1:0.5、3×2-2:0.75、6×2-3:0.75),其他小数则无法精确的表示(类比一下十进制中的小数只能精确表示10的几次方的小数)。
float
占4个字节,32位二进制,第1位是符号位,中间23位保存尾数,最后8位保存指数。
浮点数的值默认为double类型,给float类型变量赋值时需要在后面加小写字母 f 或大写字母 F。
float f = 2.11f;
double
占8个字节,64位二进制,第1位是符号位,中间52位保存尾数,最后11位保存指数。
double d = 2.11;
3、boolean类型
用boolean表示,值只有true或false两种。
boolean b = true;
boolean类型的大小没有明确定义。
下面参考Java中boolean类型到底占用多少个字节?:
单独的boolean类型编译后会用int代替,boolean值占用4个字节(32位二进制)。
boolean类型数组会被编译为byte数组,每个boolean值占用1个字节(8位二进制)。
使用int代替boolean,而不用byte或short的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),32 位 CPU 使用 4 个字节是最为节省的,哪怕你是 1 个 bit 他也是占用 4 个字节。因为 CPU 寻址系统只能 32 位 32 位地寻址,具有高效存取的特点。
4、字符(char)类型
用char表示,值是一个字符,中文或英文字符都行。
使用单引号把字符括起来。
char c1 = 'a';
char c2 = '我';
Java内部处理字符时,使用的是Unicode编码,具体编码格式为 UTF-16BE(UTF-16大端表示)。
所以字符类型本质上也是一个整数,占2个字节或4个字节,下面有具体说明。
Unicode编码
Unicode编码为世界上所有字符都分配了一个唯一的数字编号,范围从0x000000~0x10FFFF,前缀使用U+。
Unicode编码并没有规定这个编号怎么和二进制对应,下面是几个主要方案:
UTF-32
这个简单,直接使用数字编号的二进制表示,占4个字节,32位二进制。
缺点是浪费空间,实际采用比较少。
这里需要注意字节的排列顺序(字节序):
大端(Big Endian,BE)
对应编码:UTF-32BE。
第一个字节是二进制表示中的最高位,最后一个字节是二进制表示中的最低位。
小端(Little Endian,LE)
对应编码:UTF-32LE。
第一个字节是二进制表示中的最低位,最后一个字节是二进制表示中的最高位。-
UTF-16
使用变长字节表示:
常用字符集
在U+0000~U+FFFF之间,即65536个数字之内,用2个字节可表示(16位二进制)。
U+D800~U+DBFF之间的编号其实没有定义。
增补字符集
在U+10000~U+10FFFF之间,用4个字节(32位二进制)表示。
前两个字节叫高代理项,范围是U+D800~U+DBFF,后两个字节叫低代理项,范围是U+DC00~U+DFFF。
数字编号与这个二进制之间有一个转换算法。区分是2个字节还是4个字节,就看前两个字节的编号范围。
如果在U+D800~U+DBFF范围内,就是4个字节。
这里也有字节序的问题,有 UTF-16BE 和 UTF-16LE 两种编码。
UTF-16常用于系统内部编码,比UTF-32节省空间,但也至少需要2个字节表示,对于美国和西欧国家来说还是有点浪费。 UTF-8
也使用变长字节表示,占用1~4个字节,每个字符的字节个数与字符在Unicode中的编号大小有关。
对于编号小于128的字符,即ASCII码表中的字符,占用1个字节(8位二进制),最高位固定为0,剩余7位表示字符。
对于编号大于128的字符,最高位字节(即BE表示中的第一个字节)中有几个连续的1,就表示该字符一共有几个字节(包括最高位字节,最高字节后面的字节都是10开头)。
UTF-8是兼容ASCII码的,对于大部分中文,一个中文字符需要用3个字节表示(即1110 xxxx 10xx xxxx 10xx xxxx,其中x填补字符的Unicode编号的二进制形式(去掉最高位的0))。
非Unicode编码
-
ASCII(American Standard Code for Information Interchange)
美国用,占用1个字节(8位二进制),最高位固定为0,剩余7位表示字符。
数字32~126对应可打印字符,如大小写字母,各种特殊符号(!@#%^&*-+./)等。
数字0~31和127表示控制字符,如空格,换行符,制表符等。 -
ISO 8859-1 / Latin-1
西欧用,兼容ASCII,占用1个字节(8位二进制),最高位固定为1,剩余7位表示字符。0~127与ASCII一样,128~255有其他含义。 -
Windows-1252(现在主流使用)
西欧用,兼容ASCII,与ISO 8859-1基本一样,区别在于数字128~159,增加了欧元等特殊字符。 -
GB2312
中国用,主要针对简体中文(约7000个)和个别罕见字和繁体字。
兼容ASCII。
占用2个字节(16位二进制),最高位固定为1。
如果最高位是0,按ASCII解析。
高位字节范围0xA1~0xF7,低位字节范围0xA1~0xFE。 -
GBK
中国用,向下兼容GB2312,增加了更多的简体字和繁体字,共约约21000个字符。
兼容ASCII。
占用2个字节(16位二进制),最高位固定为1。
高位字节范围0x81~0xFE,低位字节范围0x40~0x7E和0x80~0xFE。
低位字节从0x40开始,即低位字节最高位可能是0。
使用高位字节(第一个字节)的最高位是否是1,来区分低位字节是否是汉字的一部分。
如果第一个字节最高位是0,第一个字节按ASCII解析,然后再解析后面的字节是ASCII还是汉字。
如果第一个字节最高位是1,则把第一个字节和第二个字节一起解析为一个汉字,解析完,跳到第三个字节解析。 -
GB18030
中国用,向下兼容GBK、GB2312,增加了少数民族字符,中日韩统一字符,共约76000个字符。
使用变长字节。
兼容ASCII。
对于2字节编码,GBK一摸一样,占用2个字节(16位二进制),高位字节范围0x81~0xFE,低位字节范围0x40~0x7E和0x80~0xFE。
对于4字节编码,第一个字节范围为0x81~0xFE,第二个字节范围为0x30~0x39,第三个字节范围为0x81~0xFE,第一个字节范围为0x30~0x39。
解析二进制,通过第二个字节的范围来确定是4字节编码还是2字节编码,因为2字节编码中的第二个字节比4字节编码中的第二个字节大。 -
Big5
香港、台湾用,针对繁体中文的。
兼容ASCII。
占用2个字节(16位二进制),最高位固定为1。
高位字节范围0x81~0xFE,低位字节范围0x40~0x7E和0xA1~0xFE。
⚠️ 字符乱码的问题,都是读取字符时用的编码与写字符时用的编码不一致造成的。
数组类型
数组中的元素是连续存放的。
数组的长度一旦确定,就不可更改。
基本数据类型的变量,在内存中只有一个对应的内存空间,直接存储变量的值。
而数组类型的变量,在内存中有两个对应的内存空间:一个存放该数组变量的值的起始位置,一个存放该数组变量的值的内容。
类似数组类型存储方式的类型,一般也称为引用类型。
在Java中,许多复杂的类型,都是使用基本数据类型和数组类型组合而成,比如:
String/StringBuilder/StringBuffer:内部使用char数组,所有字符串的操作都是基于该数组的。
ArrayList/ArrayDeque:内部使用泛型数组,所有Array操作都是基于该数组的。
Date:内部使用long值表示距离纪元时的毫秒数,所有时间操作都是基于该毫秒数的。