查看原文
其他

字符串详解

树莓蛋黄派 Java规途 2023-07-04
  • 字符串

    • 子串

    • 拼接

    • 不可变性

    • 应用(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本质上是一个类,那么类必定有其实现功能的方法(CC++称方法为函数),下面来简单介绍一下,常见的方法:

方法名方法解释
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返回startIndexendIndex-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 joinCharSequence delimiterCharSequence ..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的字符

这里可能需要提前声明一下,类似于CC++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"相拼接,这点就不得不说JavaString类型的强大了。

也许有人会问,这个是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的另一个让人眼前一亮的特性了。

不可变性

不变的菜技术

不可变性,乍一看,似乎讲的就是个稳定性。其实这么理解也不是不可以,只是有些不够准确。

准确来说,JavaString类没有提供修改字符串中某个字符的方法。即我们不能够直接在原来的String对象上直接修改,而是需要在另一个对象上进行修改,成为一个新的字符串。

来看下面的栗子

String greeting="hello"
//此时如果我们需要将greeting内容修改为“help”,我们是不能直接在原来greeting上直接修改的。
String greeting=greeting.subString(0,3)+"p";
System.out.println(greeting);
//输出结果为help,
//我们其实是将一个新的对象的引用赋给了greeting,并没有对原来的字符串进行操作修改。

大致可以这么理解:

  1. 先在原来的字符串中进行提取子串,即提取“hel”
  2. 再与新的字符串进行拼接+p
  3. 此时就获得了一个与原来的字符串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

如果字符串s1s2相等,则返回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
StringBuilderappend(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删除偏移量从startIndexendIndex-1的代码单元并返回this
String toString()返回一个与构建器或缓冲器内容相同的字符串

以上便是String的所有内容。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存