[十二]基础数据类型之String
在正式介绍String之前,我们先介绍下CharSequence
char + sequence 就是字符的序列的意思
Java中万事万物都是对象类型
而对于字符的序列,也就是多个char, 这么一种东西, 使用CharSequence这个接口来描述
既然是接口,自然规定了作为字符序列的基本协议
CharSequence简介
char charAt(int index); | 返回指定索引的char |
int length() | 返回字符序列的长度 |
CharSequence subSequence(int start, int end) | 返回子序列 |
String toString() | 返回一个包含此序列中字符的字符串该字符串与此序列的顺序相同 |
default IntStream chars() | 返回此序列的int stream,每个char零位扩展为int |
default IntStream codePoints() | 返回此序列的代码点的stream |
我们都知道1.8的一个亮点就是stream和lambda
default方法也是1.8新增的,默认实现
既然CharSequence表示了 字符序列这么一个概念显然,String内部是char数组,就是一个char的序列
String简介
String 类代表字符串
Java 程序中的所有字符串字面值(如 "abc" )都是String的实例
内部有一个char[]
注意到 上面的final, 字符串是常量;它们的值在创建之后不能更改
String str = "abc";
等效于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持说白了就是+被重载过了,也提供了强大的将对象转换为字符串的能力
char是UTF-16中的代码单元,所以字符序列就是代码单元的序列
仍旧是一个char可能是一个字符,也可能是半个字符
String 类提供处理 Unicode 代码点(即字符)和 Unicode 代码单元(即 char 值)的方法
属性CASE_INSENSITIVE_ORDER
这就是一个比较器
逻辑也很简单,两个String 按照字典顺序进行比较,忽略大小写的
以两者length小的那个作为循环次数,进行循环
如果第一个相等比较第二个,依次类推,直到不一样
如果所有的都相等,那么就比较长度了 return n1 - n2
字符与字节数组
在继续下去之前,再次简单介绍下字符与字节数组的关系
字符到字节,是一个编码的过程
字节到字符是一个解码的过程
|
同样的一个字符,在不同的字符集和编码方式下,实际存储的值,将是不同的
比如前面说的Unicode字符集,UTF8 和UTF16编码后的数据是不同的
这个编码后的数据,也就是字节 , 他们是不一样的
|
同样的一个编码值,在不同的字符集中,可能代表着不同的字符 |
所以字符与字节之间,必然有编码参与其中
这个编码环节是必然存在的,否则,你就没办法把字节与字符联系起来
|
一个字符可以根据 字符集编码 进行多种方式的编码 一个字节数组也可以根据 字符集编码 进行多种方式的解码 对于同一个字符,不管进行何种编码,当他们按照当初编码的方式进行解码时,必然对应的还是同样的那个字符 |
操作系统的文件都是以字节序列的形式存储的,所以任何一个文件都是有编码的 比如你在txt文件中输入了一个字符 这个字符 底层就会使用指定的编码存储到字节中 软件本身又把这个编码以字符的形式呈现出来 所以你才看得到是一个字符 比如这个文件中11111.txt中,存储了一个汉字春天的 " 春" 编码方式是UTF8 二进制软件查看是E6 98 A5 与我们进行UTF8 编码计算的结果是对应的 |
ANSI编码 不同的国家和地区制定了不同的标准 由此产生了 GB2312、GBK、Big5、Shift_JIS 等各自的编码标准 这些使用 1 至 4 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码 在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码; 在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码 再看下面一个例子 使用ultraedit 新建了一个文件,里面写了一个汉字 "春", 其实这个默认格式就是操作系统的编码,也就是ANSI 也就是GBK 查看二进制编码为 B4 BA 然后我们再去对照GBK的码表,你会发现完全对的上 |
任何一个文件,他其实有自带或者说默认的一个编码 凡是呈现字符的地方,都有一个编码在默默地支撑,才能够让你看得见,看得清楚字符 这个字符的保存 , 就是字符按照编码表 编码 成字节序列的过程 这个字符的呈现 , 就是字节序列按照编码表 解码 成字符的过程 当你使用计算机,进行字符处理工作的时候,无时无刻都在进行着编码与解码 |
String构造方法
String是常用类之一,所以提供了非常丰富的方法
String是字符序列 内部是char[] char就是一个十六进制的数 16位表示
所以char[] 可以用来构造String
|
char是16位数能够表示代码单元, int自然可以直接表示一个代码点了,所以也可以使用int来构造String |
另外再加上我们刚才关于字节数组与字符关系的介绍,也可以使用字节数组构造String |
下面表格是几个基本的构造方法
String() | 空String ,没啥必要因为String是不可变的 |
String(char[])
String(char[], int, int)
|
借助于字符数组或者字符数组的一部分创建对象 内部本来就是字符数组 char[] 所以自然可以使用char[]构造 直接进行拷贝,所以对原有字符数组的修改不影响String对象 |
String(int[], int, int) | 使用代码点构造String public String(int[] codePoints, int offset, int count) offset 和 count为范围限制 |
String(String) | |
String(StringBuffer) | |
String(StringBuilder) |
getBytes 方法
先提一下另外一个方法,getBytes
使用指定的字符集将此 String 编码为 byte 序列
我的编辑器环境是UTF8编码的
"春" 的UTF8编码上面已经分析了
也就是说我这边有一个UTF8的字符"春" 源文件中保存的是 E6 98 A5
对于下面所有的getBytes来说,"春" 这个字符形状符号是不变的
获得的字节数组就是 这个字符形状符号 根据不同字符集编码方式, 编码而得到的字节数组
下面的各种转换换一个描述就是:UTF8的字符"春" ,在其他的字符集下面,编码都是多少啊?
|
为什么UTF-8 是-26 -104 -91 ? 而不是e6 98 a5?进制问题 |
getBytes总共三种形式 指定编码或者使用默认 getBytes(String)
getBytes(Charset)
getBytes()
还有一种已经弃用 了
|
通过字节数组 byte[] 构造
String提供了6个跟byte[] 相关的构造方法
getBytes方法是字符是固定的, 固定的以UTF8格式存储在我的源文件中,
然后根据不同的编码方式,转换为字节数组 byte[]
String的构造方法,则是将各个已经编码过的字节数组 byte[] 按照指定的编码方式解析 还原成为一个字符
然后再将这个字符以char[] 也就是UTF-16的方式进行存储的
我的源文件IDE环境是UTF8那么最终构造的String就是UTF8的,不会是其他的
比如下面的构造方法,使用前面示例中的 bytes数组
然后使用 String(byte[], String) 进行构造
看得很清楚
String字符串 s1 中存储的value 是Unicode的代码点U+6695 (0号平面,一个代码单元就是一个代码点)
也就是十进制的26149
使用byte[] 字节数组构造String的过程是下图这样子的 字节数组,根据指定字符编码转换为那个字符 然后在把字符按照UTF16 进行编码 存储到String中的char[] 上面的例子可以很好地印证这一点,字节数组是[-76, -70] 也就是 : ffffffb4 ffffffba 也就是 B4 BA 明明是GBK的"春" 根本就不是6625 对应关系就是他们表示的是同一个字符 |
既然字节数组与字符的转换离不开编码,所以自然通过byte[] 构造String对象时,必须要有编码
不设定并不是没有,而是使用默认的
既然使用字节数组,那么有的时候可能需要指定范围,所以有两个根本的构造方法
|
然后还有默认字符编码的简化形式 |
再然后就是长度为整个字节数组的简化形式 |
这几个构造方法根本在于理解 字节数组与字符的转换 以及必须的byte[] 字节数组 以及 编码 |
valueOf
valueOf 系列用来包装
String中用来将基本类型 以及 Object 转换为String
char相关的都是直接构造String对象
其余(除了boolean,他是转换为字符串 true和false返回)
都是toString
copyValueOf
copyValueOf方法内部就是直接调用的两个构造方法 还不如直接使用new创建来的直接,只不过使用这个方法有更好的可读性 |
获取指定位置代码单元和代码点的方法
charAt(int) 返回指定索引处的 char 值 索引范围为从 0 到 length() - 1 简单粗暴, 不管三七二十一就是代码单元 如果是辅助平面,那就可能是代理项 |
codePointAt(int) 返回指定索引处的代码点, 范围从 0 到 length() - 1 他跟Character中的codePointAt方法逻辑含义是一样的 如果是高代理,如果下一个也在掌控范围内,如果下一个是低代理,那么返回代码点 否则,返回代码单元 也就是一个char |
codePointBefore(int) 返回指定索引之前的字符(Unicode 代码点) 其范围从 1 到 length 他跟Character中的codePointBefore方法逻辑含义是一样的 如果index-1 是低代理,如果在往前一个index-2 也是有效范围内,如果他还恰好是一个高代理,返回代码点 否则,返回代码单元,也就是一个char |
codePointCount(int, int) 此 String 的指定文本范围中的 Unicode 代码点数 文本范围始于指定的 beginIndex,一直到索引 endIndex - 1 处的 char, 包含头不包含尾 该文本范围的长度(用 char 表示)是 endIndex-beginIndex 由于一个代码点的代码单元个数可能是1个可能是2个,所以代码点的个数需要计算,就不直观了 他跟Character中的codePointCount方法逻辑含义是一样的 |
offsetByCodePoints(int, int) 他跟Character中的offsetByCodePoints方法逻辑含义是一样的 返回此 String 中从给定的 index 处偏移 codePointOffset 个代码点的索引 根本原因还是一个代码点的代码单元个数可能是1个可能是2个 所以 偏移codePointOffset个代码点的 代码单元的个数不确定,需要调用方法计算 |
getChars(int, int, char[], int)复制
实例方法 就是一个复制方法,名字不太规范 复制String中指定索引开始的srcBegin 和 srcEnd 包含头不包含尾 到另一个字节数组 char dst[]中, 存放的起始位置为dstBegin public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) |
需要注意的是,复制的是char 代码单元 就是String 内部char[] 的下标索引 |
开始结束匹配校验
startsWith(String, int)
startsWith(String)
|
实例方法 测试String是否以指定的前缀开始 还可以指定起始位置处开始比较 从源代码看得出来,挨个比较内部的char 从头开始,全部一致才返回true 单参数是双参数的简化版本 |
endsWith(String) | endwith就是从最后的指定参数长度的位置开始比较 |
indexOf 和lastIndexOf
indexOf 和XXXIndexOf系列都是获取下标索引相关
需要注意的是,他们的参数都是int或者String
也就是说这些方法都是真正的字符相关的
int indexOf(int ch)
int indexOf(int ch, int fromIndex)
|
返回 指定字符 在此字符串中第一次出现处的索引 返回的匹配的第一个 也可以指定检索的起始位置, 如果指定了索引 那么返回的值将 大于等于 指定的索引 换个说法: 如果是0号平面返回的是那个代码单元也就是代码点的索引 charAt(k) == ch 为 true 的最小 k 值
如果是辅助平面返回的是高代理位的代码单元的索引 codePointAt(k) == ch 为 true 的最小 k 值 |
int indexOf(String str)
int indexOf(String str, int fromIndex)
|
返回 指定子字符串 在此字符串中第一次出现处的索引
返回匹配的第一个
也可以指定检索的起始位置,如果指定了索引
那么返回值需要大于等于 指定的索引
匹配的含义为startsWith(str) 为true
如果指定检索开始的位置, 那么
不仅仅startsWith(str) 为true 还需要索引满足指定的下标范围
否则仍旧是返回-1
|
lastIndexOf(int)
lastIndexOf(int, int)
|
返回指定字符在此字符串中最后一次出现处的索引 返回匹配的最后一个 也可以指定检索位置,但是这个检索位置与indexOf不同 indexOf中指定的索引,是从索引处往后 lastIndexOf指定的索引, 是反向,从索引处往前 指定了索引就要求 返回值 小于等于 指定索引 换个说法 如果是0号平面返回的是那个代码单元也就是代码点的索引
charAt(k) == ch 为 true 的最大 k 值
如果是辅助平面返回的是高代理位的代码单元的索引
codePointAt(k) == ch 为 true 的最大 k 值 并且 k 小于等于 指定的索引
|
lastIndexOf(String)
lastIndexOf(String, int)
|
返回指定 子字符串 在此字符串中最后一次出现处的索引
返回匹配的最后一个
也可以指定检索位置,检索索引的位置也是反向搜索
匹配的含义为startsWith(str) 为true
指定了索引就要求返回值 小于等于 指定索引
|
总共三个维度
匹配第一个或者最后一个 / 匹配字符或者字符串 / 是否指定查找范围
8个方法
indexOf是从前往后匹配 匹配的是第一个 如果指定了下标索引,从索引处往后找
返回的值要 大于等于 索引
lastIndexOf是从后往前匹配 匹配的是最后一个 如果指定了开始下表索引,是从索引处往前,反向查找
返回的值要 小于等于 索引
匹配字符如果是BMP,代码单元就是代码点,返回的就是那个代码单元也是代码点的索引
如果是辅助平面,一个代码点两个代码单元,返回的就是高代理位的索引 lastIndexOf和indexOf都是返回高代理项
length
长度获取,内部char数组的长度
isEmpty()
hashCode
计算公式
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
|
字符串匹配包含
测试两个字符串区域是否相等
toffset 表示当前对象this 开始的位置
other 表示另外一个String对象
ooffset 表示另外对象开始的位置
len 要匹配的长度
两个方法其中一个可以指定是否忽略大小写
|
s1.regionMatches(1,s2,3,4); 读作: 把s1 从索引1开始 同 s2 从索引3开始,比较len个长度,查看这个区域是否相等 |
public boolean regionMatches( int toffset, String other, int ooffset,int len)
public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
|
contains contains也是一种匹配 当且仅当此字符串包含指定的 char 值序列时,返回 true |
matches 此字符串是否匹配给定的正则表达式 public boolean matches(String regex) |
相等比较
equals(Object)
equals方法也进行了重写
比较的是内部的char 序列是否相等
先看是否同一个对象,否则看是否String类
然后再看长度,长度相同挨个比较
|
contentEquals(StringBuffer)
contentEquals(CharSequence)
这两个方法 分别针对参数StringBuffer 和 CharSequence
他们都是 当且仅当表示相同的 char 值序列时,结果才为 true
比较的也是内容
上面的equals方法也是比较的内容
|
equalsIgnoreCase(String) 比较忽略大小写,底层依赖的就是区域的比较 只不过区域是整个字符串而已 |
compareToIgnoreCase(String) 字典顺序比较两个字符串,不考虑大小写 |
compareTo(String) compareTo(String)方法是按照字典序进行排序的 如果字符本身全都相等,但是长度不同,返回长度差 |
子串获取
public String substring(int beginIndex) public String substring(int beginIndex, int endIndex) public CharSequence subSequence(int beginIndex, int endIndex) |
subSequence 就是调用的subString方法 为什么还需要呢?就是为了遵循CharSequence协议 |
subString也是处理的char的索引,不是字符的 所以一个好好地字符,可能被你截取后,就成为乱码了 所以如果你想要截取子串也会出现乱码,可以通过offsetByCodePoints 获取指定个代码点后的索引 那么截取的绝对不会是乱码 看一个例子 0x1f310的高代理位在Character简介中计算过,它的值跟十进制的55356一样的 对于s 截取后,子串中仅仅是高代理项了 |
大小写转换
大小写的转换 可以指定Locale 不指定,等价于 指定默认值Locale.getDefault() 大小写映射关系基于 Character 类指定的 Unicode 标准版 |
toLowerCase(Locale)
toLowerCase()
toUpperCase(Locale)
toUpperCase()
|
split
根据匹配给定的正则表达式来拆分此字符串 子字符串按它们在此字符串中出现的顺序排列 如果表达式不匹配输入的任何部分,那么所得数组只具有一个元素,那就是这个字符串 public String[] split(String regex, int limit) limit 不是什么索引下标,而是表达式模式应用的次数 如果该限制 n 大于 0,则模式将被最多应用 n - 1 次
数组的长度将不会大于 n,而且数组的最后一项将包含所有超出最后匹配的定界符的输入
如果 n 为非正,那么模式将被应用尽可能多的次数,而且数组可以是任何长度
如果 n 为 0,那么模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃
例如,字符串 "boo:and:foo" 使用这些参数可生成以下结果:
|
split(String) 是split(String, int) 的简化形式 |
join
join用于将字符序列使用指定的符号进行拼接 需要注意的是,如果有元素为null ,那么"null" 将会被添加进来 |
public static String join(CharSequence delimiter,
CharSequence... elements)
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements)
|
替换
分为字符/字符序列/正则表达式替换
replace是字符/字符序列的替换
replaceXXX是正则的替换
public String replace(char oldChar, char newChar) | 替换后,返回一个新的字符串 如果 oldChar 不存在,则返回这个 String 对象的引用 否则,创建一个新的 String 对象 所有的 oldChar 都被替换为 newChar |
public String replace(CharSequence target, CharSequence replacement) |
替换后,返回一个新的字符串 使用指定的字符序列进行替换 用 "b" 替换字符串 "aaa" 中的 "aa" 将生成 "ba" 而不是 "ab" |
replaceFirst(String, String)
|
|
replaceAll(String, String) |
concat 连接
将指定字符串连接到此字符串的结尾
如果参数字符串的长度为 0,则返回此 String 对象
否则,创建一个新的 String 对象,返回新创建的连接后的字符串
|
先复制一个到数组中 然后再把参数的复制到那个数组中 然后使用数组创建String |
trim
trim() |
最常用的String方法之一,去掉开头和结尾的空格 |
toString()
|
返回他自己本身 他本来就是一个String了 |
toCharArray() | 将此字符串转换为一个新的字符数组 内部本身就是一个char[] 所以自然可以轻松的转换为char数组 数组拷贝了下 |
format
format
使用指定的格式字符串和参数返回一个格式化字符串
可以指定语言环境
内部还是使用的Formatter
|
intern
intern() String 私有地维护了, 一个初始为空的字符串池
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串
否则,将此 String 对象添加到池中,并返回此 String 对象的引用
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true
|
对于直接定义的 "a" "ab" 会进入这个字符串池 如果是new 创建的字符串对象不进入字符串池 如果使用+ 得到的,两个都是字面的"a" "ab" 形式会进入字符串池,否则如果有变量也不会进入 str5 和 str3 内容相同,String重写了equals方法,比较的是内容,所以true str5 和 str3 一个是new出来的,所以地址不相等 false str5.intern() 查找池中是否有"ab" 有的话返回引用,显然就是str3 所以true str5.intern() 查找池中是否有"ab" 有的话返回引用,显然就是str3的地址 但是str4 是一个对象,他与str3 不是同一个对象所以不相等 false 最后一个都是获取"ab"的引用,显然是相等的 |
总结
String的根本就是字符序列
内部使用char[] 保存数据,而char 是UTF16中的代码单元
所以String中的很多方法自然也避免不了与Unicode UTF16的联系
在实际使用方法的时候,一定要稍微留意代码点与代码单元之间的关系
不过也不必过于担心,因为常用字符大多数都在0号平面内,很多方法用起来并不会有什么问题,哪怕你不曾留意