字符串详解
字符串
子串
拼接
不可变性
应用(equals)
空串与null串
构建字符串
字符串String详解
在正式开始之前,祝大家520 521快乐啊!
前面我们已经了解了变量与运算,现在让我们来了解一下操作数的常见类型字符串。
字符串
字符串,顾名思义,就是由字符(char)串联或者连起来的一种引用类型。当然这些只是我个人的理解,方便记忆。因为也存在一个字母或汉字成为一个字符串的情况,下面来看看官方解释:
概念上说,Java字符串就是Unicode字符序列
官方就是官方,言简意赅,还不理解?
来举个栗子:字符串``“Java\u2122”`就是由Unicode字符J 、a、v、a和TM组成。
这个\u2122
就是转义字符,即:TM无法直接表示。
说了那么多,也许很多人会问,字符串是不是与int
那些类型一样?
很遗憾,并不是,Java没有内置的字符串类型,而是在Java标准库中提供了一个预定义类String
。
每个用双引号包起来的都叫字符串String。
下面举个栗子:
String e="";//an empty String
String greeting="Heloo";
当然字符串还有另一种定义方式:通过new关键字创建对象的形式来创建字符串变量
String e=new String();
e=" ";
这两种创建方式的区别在于:
第一种方式获得的对象来源于公共存储池中,
而第二种方式是将公共存储池中的对象复制一份放入堆内存中。
由上面我们得知String本质上是一个类,那么类必定有其实现功能的方法(C
和C++
称方法为函数),下面来简单介绍一下,常见的方法:
方法名 | 方法解释 |
---|---|
char charAt (int index) | 返回给定位置(index)的代码单元,除非对底层感兴趣,否则尽量不要使用这个 |
int codePonintAt (int index) | 返回从给定位置(index)开始的码点 |
int offByCodePoints (int startIndex ,int cpCount ) | 返回从startIndex 开始,cpCount 个码点后的码点索引 |
int compareTo (String other) | 按照字典顺序比较。如果位于other之前,返回一个负数。如果位于other之后,返回一个正数,如果相等,返回0。 |
IntStream codePoints () | 将这个字符串的码点作为一个流返回,调用toArray 将他们放在一个数组中 |
new String(int[] codePonints ,int offset,int count) | 用数组中从offset开始的count个码点构造一个字符串 |
boolean empty () | 如果字符串为空,则返回true |
boolean blank () | 如果字符串为空格,则返回true |
booleanequals (object other) | 如果字符串与other相等,则返回true(此时已经重写equals方法) |
boolean equalsIgnoreCase (String other) | 如果字符串与other相等(忽略大小写),则返回true |
boolean startWith (String prefix) | 如果字符串以prefix开头, 则返回true |
boolean endWith (String suffix) | 如果字符串以suffix结尾,则返回true |
int length () | 返回字符串代码单元的个数(即字符串中字符个数) |
int codePointCount (int startIndex ,int endIndex ) | 返回startIndex 到endIndex-1 之间的码点个数 |
String replace (Charsequence oldString ,Charsequence newString ) | 返回一个新字符串,这个字符串用newString 代替原始字符串中所有的oldString ,可以用String 或``StringBuilder对象代替 CharSequence`参数 |
String subString (int beginIndex ) | 返回一个新字符串,这个字符串中包含原始字符串从beginIndex 到字符串末尾的所有代码单元。 |
String subString (int beginINdex ,int endIndex ) | 返回一个新字符串,这个字符串中包含原始字符串从beginIndex 到字符串endIndex-1 的所有代码单元。 |
String toLowerCase () | 返回一个新字符串。这个字符串将原始字符串中的大写字母改为小写。 |
String toUpperCase () | 返回一个新字符串。这个字符串将原始字符串中的小写字母改为大写。 |
String trim () | 返回一个新字符串。这个字符串删除原始字符串头部和尾部小于等于U+0020 的字符 |
String Strip () | 返回一个新字符串。这个字符串删除原始字符串头部和尾部小于等于U+0020 的空格 |
String join (CharSequence delimiter ,CharSequence ..elements ) | 返回一个新字符串。用给定的定界符连接所有元素 |
String repeat (int count) | 返回一个字符串,将当前字符串重复count次 |
需要注意的是Java
中String含有五十多种方法,以上的统称为String API
注释,可以帮助你理解某个方法或者其表示的含义与内容。
下面来看看oracle官网的有关String的API
看完了String的API
是不是感叹,原来这么多的方法,其实并不是所有的方法都必须记住,只需要记得常用的几个即可。(需要注意此处的String基于JDK16
)
下面我们来对String进行拆分来理解。
子串
所谓子串,就是从String中抽取一部分出来,作为一个新的字符串。
下面我们来看一个方法,该方法可以提取String中的某些位置字符,将其组合成一个新的字符串。
String greeting="Hello";
String s=greeting.subString(0,3);
System.out.println(s);
//最终结果输出为Hel组成的一个字符串。
我们仔细观察一下subString
方法及其参数,重点是参数部分,即小括号中的内容(0,3)。
非常细心的同学可能会发现,输出了Hel
这个字符串,只有3个字符,但0~3却有4个字符,这是怎么一回事?
其实sunString
方法的第二个参数是不想复制的一个位置,即复制的内容不包括位置为3的字符
这里可能需要提前声明一下,类似于C
和C++
,Java
中字符串的代码单元和代码点都是从0开始计数的。这里可能和我们日常的行为习惯有点不同,因此这是需要注意的地方。
细心的人可能会发现,subString
方法有一个很明显的优点:极其容易计算子串的长度(你细品)
好了, 子串就暂时说到这里,下面来介绍一个符号,也许很多人都觉得不陌生,见过很多次,极其容易混淆。
拼接
听到这个名词,也许很多人都有点云里雾里的,拼接是啥意思?
我们先来了解一下比较官方的解释:
拼接,动词,汉语拼音为
pīnjiē
,英文为put together
,意思是接在一起。在以前技术不发达的情况下,各种图像不能有效地拼接,随着科技的发展,液晶屏及其他各种显示设别已经很好的解决了这个问题。(来源于百度百科,有图有真相)
百度
说点人听的懂的话就是:把两样同类型的东西连接在一起。在Java
中,拼接就是把两个本没有关系的字符串连接在一起。
有没有像哲学中的联系一样,联系是普遍存在的。不过这个比喻貌似有点不太恰当。
好了,言归正传,Java
语言允许用+
来表示拼接两个字符,注意哦,这里是做拼接符号用,而不是算术运算符的求和运算。
来,我们举个栗子;
String expletive="Expletive";
String PG13="deleted";
String message=expletive+PG13;
System.out.println(message);
//输出的结果为:Expletivedeleted
显而易见,message中的两个单词之间的+
就是拼接符,将两者结合之后再重新赋值给了message。
需要注意的是,单词之间是没有空格的,+
完全是按照给定的次序进行拼接的。
也许有人会提出疑问了,如果需要拼接的两方有一方是字符串,另一方不是字符串,还能够依照String的规则来进行拼接吗?
答案是肯定的,并且在Java
中,任何的对象都可以转换成字符串,来看一个栗子:
int age=13;
String rating="pg"+age;
System.out.println(rating);
//输出结果是:pg13
是不是很意外?13并不是String类的,却能够和"pg"相拼接,这点就不得不说Java
String类型的强大了。
也许有人会问,这个是String+int
的格式,那么,如果是int+String
呢?结果是否会存在区别呢?
为表示对问题的尊重,我们上idea
一探究竟:
结果输出还是一个字符串,因为String sum=age+s
处没有出现编译时错误,因此,两者的结果是一个String类型的字符串。
并且这种特性在以后经常会遇到,大多用在输出语句中:
System.out.println("The answer is:"+answer)
这是一条合法语句,并且会出现The answer is something的情况。
当然如果你需要将很多的字符串放在一起,并且将其整合成一个字符串的话,需要用一个界定分隔符来表示分隔,可以使用静态join方法:
我们来看看实际的运行结果:
如图所示,方法参数有两种类型,delimiter表示界定符,可以用它来分隔后续的每个元素。elements表示字符串的主体内容。
String这个类在Java11
中增加了一个方法repeat:
String repeated="java".repeat(3)
System.out.println(repeated);
//输出结果是“javajavajava”重复三遍
也许它比较适合当复读机。
说完了拼接。我们需要了解String的另一个让人眼前一亮的特性了。
不可变性
不可变性,乍一看,似乎讲的就是个稳定性。其实这么理解也不是不可以,只是有些不够准确。
准确来说,Java
String类没有提供修改字符串中某个字符的方法。即我们不能够直接在原来的String对象上直接修改,而是需要在另一个对象上进行修改,成为一个新的字符串。
来看下面的栗子
String greeting="hello"
//此时如果我们需要将greeting内容修改为“help”,我们是不能直接在原来greeting上直接修改的。
String greeting=greeting.subString(0,3)+"p";
System.out.println(greeting);
//输出结果为help,
//我们其实是将一个新的对象的引用赋给了greeting,并没有对原来的字符串进行操作修改。
大致可以这么理解:
先在原来的字符串中进行提取子串,即提取 “hel”
再与新的字符串进行拼接 +p
此时就获得了一个与原来的字符串 hello
不同的字符串序列,也就是我们需要的help字符串。这一过程中,其实hello其实本身并没有任何的变化。只是将自己的子串引用赋给了另一个同名的对象罢了。
如果还不理解,我们可以在idea上测试一下:
此时我们将子串和拼接的字符串合并并重新赋给了s对象,然后分别输出greeting和s,得到的结果一个是hello,一个是help。
如果说String是可变的,那么此时输出的greeting应该是被修改过的,但实际上并没有被修改过。
说到这里也许有很多小伙伴会想,你修改一个代码单元似乎比重新创建一个字符串的效率要高很多,为什么设计者要设计成不可变的呢?
的确,现在看来,似乎修改一个代码单元似乎效率真的很高,但是设计者自然也是知道这点的,他们所遵从的理念就是共享。
什么意思呢?
***即想象一下,将所有的字符串都存放在公共存储池中,字符串变量指向对应的位置,如果复制一个字符串变量,那么原来的字符串变量和复制的字符串变量共享相同的字符串。***
回到上面的例子,我们提取的子串其实就是复制的一部分,其实是由原来的字符串变量greeting和复制后的字符串变量”hel“
共享,这样就提高了效率。
并且我们在程序中几乎很少看到修改字符串,一般都是字符串进行比较。
当然凡事都不是绝对的,Java
考虑到日后应用场景的千变万化,特定提供了一种类(StringBuilder
)来弥补String不可变性所带来的缺陷。
说完了特性,下面说说实际应用吧,应用也是十分广泛。
应用(equals)
对于字符串,我们往往不修改,而拿来比较。这是应用的比较广泛的。
那么如何比较呢?
下面,请出我们的主角equals()
方法
String常常用equals()方法来比较两个字符串是否相等。
在介绍equals方法之前,我们需要了解一个概念,这个概念也许学过C
语言的同学都会很清楚,也是噩梦一般的存在。那就是指针。
指针
这个概念也许对没学过C
语言的人来说很陌生,甚至会想出很多奇怪的事物出来。
我们还是看看官方解释叭
指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。(来源于百度百科)
指针
我们即将要讲的equals()方法其实也就类似于C
语言的指针。
内存地址
内存地址说的简单点就是类似于每个人家的门牌号,都是通过门牌号来找人,只不过,这一串序列出现在内存中。
因为计算机中所有的运行程序都必须在内存中进行,因为效率较高,
因此所有数据都会在内存中占据一块空间,这块空间就被称为内存地址。
==比较
在介绍equals()方法之前,我们还需要介绍一个比较运算符==
,两者可以用来对比理解,并且用的情景也比较多。
对于基本数据类型,如int等,则是直接比较他们的值
int a=5;
int b=5;
System.out.println(a==b)
//输出结果是一个true,说明a与b相等
对于引用类型(除基本类型之外的),则是比较他们的地址。
String s1=new String("hello");
String s2=new String("hello");
System.out.println(s1==s2);
//返回的结果是false,说明他们的地址不相等。
第一个好理解,第二个就有点难理解了,为什么同样的操作,地址却不一样?
这是因为new操作符使用时将会在内存中开辟两个不同的存储空间,
因此,尽管内容相同,但==
比较的结果却是不同的。
这就好像虽然两家的人口数是相同的,但家庭住址不同,也不能说明他们是完全一样的。
当然如果字符串在同一个位置上,他们必然相等。
前面提到了共享
,其实在Java
中只有字符字面量是共享的,而+
和subString
方法获得的字符串是不共享的,因此不要用==
来测试字符串的相等性。
equals方法详解
下面回到equals()方法,这里还需要说明一下,Java
中所有的类都继承自Object类(关于继承后续展开)
只需要知道,是Object这个类把方法传给了其他的类,比如String
而且需要明确的是Object的equals()方法默认比较的是内存地址(类似于指针),并不是比较的具体内容是什么,当然我们可以将它进行修改,将比较地址改为比较内容差异。
这么说似乎不太明白,我们今天来看看始祖(Object)的源码吧!
public boolean equals(Object obj) {
return (this == obj);
}
可以看出底层实现的还是比较的是地址值是否相等。
那么String又是如何实现比较值的呢?
还是看看String底层源码叭
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (!COMPACT_STRINGS || this.coder == aString.coder) {
return StringLatin1.equals(value, aString.value);
}
}
return false;
}
这里便不再多赘述了,后续会慢慢了解底层实现。
下面来正式介绍一下equals方法如何使用吧!
String s1="hello";
String s2="hello";
s1.equals(s2);
//输出结果为true
"hell".equals(s1);
//输出结果为false
如果字符串s1
与s2
相等,则返回true,否则返回false
我们看第二个表达式,发现字符串变量和字符串字面量也可以比较,也是合法的。
一般比较字符串都会存在大小写的区别,那么就可以用equalsIgnoreCase()
方法
举个栗子:
"Hello".equalsIgnoreCase("hello");
//输出结果是true,即表示两者内容相同的。
这样程序只会比较两者的具体内容是否相等,而忽略大小写。
接下来我们看看String字符串的一些特殊的形式。
空串与null串
相信看到这里很多人都是一头雾水,空串能够理解,那么null串不也是表示空串吗?
事实上不是的,虽然null这个英文单词也表示空,但它绝对不是表示空串的含义。
所谓空串,即表示字符串长度为0的字符串,表示形式是" ",并且空串本身也是一种
Java
对象,它有着自己的串长和内容。
因此以后如果以后看到此种形式的字符串String =" "
那么它必是空串无疑。
而且我们常用以下代码来检测字符串是否为空
String str=" ";
//判断字符串str的串长
if(str.length==0){
return .....;
}
//判断字符串str的内容
if(str.equals(" ")){
return....;
}
以上的栗子运用了我们在StringAPI
中的方法,一个判断字符串值是否为空,一个判断字符串长度是否为0
好了,我们对空串有了一定了解,那么再来看看null串。
所谓null串表示字符串变量没有与任何的对象相关联,它只有一个变量名,并没有实际的含义。
如果要检查一个字符串是否为null,则需要使用以下条件:
String str=null;
if(str==null){
return .....;
}
有时候需要检测一个字符串既不是空串也不是null串时,应该这么做:
String str="hello";
if(str!=null && str.length()!=0){
return......;
}
这里有一个细节,即:应该首先判断字符串是否为null,因为如果字符串为null,那么它就没有长度。
好了,说完了空串等相关内容,下面我们来说说怎么构建新符号串。
构建字符串
现实的程序世界中,字符串的使用频率很高,很多时候,我们需要将很短的字符串构建字符串。
例如按键或者来自文件的单词,倘若用字符串拼接的方式来达到目的,效率很低,每次拼接一个字符串都会得到一个新的字符串
这时StringBuilder
类横空出世,这么说似乎不是很清楚,我们看代码叭
StringBuilder sb=new StringBuilder();
sb.append("ch");
sb.append("ar");
String s=sb.toString();
System.out.println(s)
//输出结果为char
这段程序中有很多我们需要深思的地方:
StringBuilder sb=new StringBuilder();
与String=”sb“
有着很明显的不同,通过new操作符来创建了一个新的StringBuilder
对象。append()
方法允许在原有的字符串中直接在尾部添加其他的字符串,而不用再重新创建StringBuilder
对象,这点相比String的效率要高toString()
方法的使用,给了我们一个信号,那就是String与StringBuilder
是两个不同的类,并且通过方法名可以看出,最后还是可以转换回String对象。
下面介绍几个StringBuilder
常用的方法:
方法名 | 方法解释 |
---|---|
StringBuilder | 构造一个空的字符串构建器 |
int length () | 返回构建器或者缓冲器中代码单元的数量 |
StringBuilder append(String string) | 追加一个字符串并返回this |
StringBuilder append(char c) | 追加一个代码单元并返回this |
StringBuilder appendCodePoint (int c) | 追加一个码点,并将其转换为一个或两个代码单元并返回this |
void setCharAt (int i,char c) | 将第i个代码单元设置为c |
StringBuilder insert(int offset,String s) | 在offset位置插入一个字符串并返回this |
StringBuilder insert(int offset,char c) | 在offset位置插入一个代码单元并返回this |
StringBuilder delete(int startIndex ,int endindex ) | 删除偏移量从startIndex 到endIndex-1 的代码单元并返回this |
String toString () | 返回一个与构建器或缓冲器内容相同的字符串 |
以上便是String的所有内容。