HOME/Articles/

Java中的字符串

Article Outline

介绍String的本质,相关API,以及StringBuffer/StringBuilder

<!--more-->

字符串

字符串是最重要的引用类型,互联网基本上只做一件事情,那就是处理字符串

所以能处理好字符串是Web服务器的基本要求,比如(PHP,Python,Ruby,Perl,Java),

C/C++对于字符串处理能力不强,所以一般不使用其做Web服务器

字符串的不可变性

Java是如何保证字符串不可变的

从代码实现角度分析,首先String类是final的,且其内部有一个char[] value的指针指向堆中用来存储字符的一个数组,其也是final

如果不使用反射等操作的话,是无法将其改变指向的,且暴露出的方法没有可以进行修改的API,保证了其的不可变性

String类内部存储Hash值

String类的内部实现中有一个private int hash,默认为0,将当前字符串的hash值缓存了起来

因为字符串不可变,所以调用hashCode()时,如果内部的这个hash值不为0的话就不需要再次重复进行计算

不可变的原因及缺点

字符串是不可变的原因:要做到线程安全存储安全

线程安全很好理解,至于存储安全指的是在HashMap中使用String作为key,没有办法在String可变的情况,还继续遵守hashCode的约定

不可变的对象天生就是线程安全

不可变的缺点:每当修改时需要创建新的对象

String/StringBuilder/StringBuffer的相关使用

  • StringBuilder
    • 优先使用,线程不安全,速度快
  • StringBuffer
    • 线程安全,速度相对较慢

StringBuilder/StringBuffer类型的值不可以作为HashMapKey

字符串与编码

为了要把人类世界中的字符转化为计算机世界中的字节,发明了一种映射关系,这种映射即被称为字符集

  • 将人类能看懂的字符变为字节,称为编码
  • 将字节转化为人类能看懂的字符,称为解码

问题的根源就在于,人类发明了很多种字符集,不同的字符集在进行相互的操作的时候,就会发生问题

Unicode

Unicode作为一种最常用的字符集,因为要包括全世界各种语言的字符,所以其内部使用int值进行存储(int可以存储42亿个不同字符,可以满足需求)

然后其通过不同的int值对应不同的字符,这个不同的字符对应的int值被称为码点(code point

举个例子,假设现在的Unicode的对应关系为,值为1int值对应字符1,值为2int值对应字符2等等:

1 -> '1'
2 -> '2'

而由于其是int值,所以字符1对应的其实是00000000 00000000 00000000 00000001 四个字节

在这个例子中,前三个字节是用不到的,所以出现了浪费了一些字节的情况

在此基础上,出现了最常用的2个编码方案:

  • UTF-16 (java程序内部使用的存储方法)
  • UTF-8

Mac/Linux的默认编码是UTF-8,windows默认的中文编码为GBK,这里建议都使用UTF-8,因为其对多语言支持最好

UTF-16基本的编码思路是,对于较小的码点,比如1234 -> 字符1,

那么字符1的编码就直接是1234的十六进制04d2,所以直接使用2个字节04 d2就可以完成存储

对于较大的码点,就需要使用更多的字节,比如123456经过一系列复杂的计算算出一个值,使用更多的字节进行存储

另外多个字节在传输过程中,会有顺序的不同,比如04 d2也会被传输成d2 04,分为big endinglittle ending 针对这种情况,BOM(Byte Order Mark),字节顺序标记,就会出现在文本文件头部,不可见,但是可以用来标识顺序

UTF-8的实现思路还有不同,它将前127个字符(最常用)存储为1个字节,然后按照常用级别,将剩下的字符们分为2个字节存储,

更加不常用的就用3个字节存储(绝大多数中文就是3个字节),然后最多到6个字节存储的字符

GBK

前身为GB2313,非常简单粗暴,指定2个字节为一个对应的中文,完全没管Unicode,是完全不同的2张表

GBK编码的文件被使用Unicode之类的字符集解码时,就会出现乱码