懵了!看了阿里p7大佬耗时三天三夜整理的String类,我居然不会

158 阅读16分钟

今日分享开始啦,请大家多多指教~

字符串是我们以后工作中非常常用到的类型. 使用起来都非常简单方便, 我们一定要使用熟练。

那么C语言中是否有字符串类型? 答案是 “ 没有 ” !!

char *p = " hello";

那么p的类型是一个字符串类型么? 不是,p是一个指针!!

而在 Java当中是有字符串类型的——String

一、定义方式

创建字符串的方式有很多种,常见的构造 String 的方式如以下:

方式一:直接赋值法

String str1 = "hello";

方式二: new String()

String str2 = new String("hello");

方式三:创建一个字符数组ch,new String ( ch )

char chs[] = {'h','e','l','l','l','o'};

String str3 = new String(chs);

二、内存

在此之前我们要先引入一个概念 字符串常量池

Sting constant pool 字符串常量池 的特性

1.在JDK.7 开始,字符串常量池 被挪到堆里了

2.池内的数据不存在重复

下面我们通过一系列的练习来熟悉 字符串常量池以及 字符串类型数据在内存中的存放。

image.png

我们来看这样的代码,str 代表的是引用\地址,请判断 两次打印分别是什么?

我们来看结果

image.png

这个结果说明 str1 和 str2存放的地址是不一样的, str1 和 str3 存放的地址是一样的。

好的,为什么是这样的结果呢?我们来看一下这几个字符串类型变量的内存。

image.png

"hello"如果存放在常量池当中,就会占用内存,假如这块空间的地址为111,那么str1中存放的就是111.

str2 new一个String对象,那么肯定在堆上开辟内存,假设内存地址是888,在这个String 对象中,存在一个value[] 保存着 orginal传入的字符串,这个val ==“hello”,因为在字符串常量池中已经有了"hello",所以val 直接指向 常量池中的"hello".但是str2 指向的依然是 888在堆中的空间。

image.png

image.png

所以 str1 不等于 str2。

之后呢,str3 也等于"hello",他也准备把hello放在常量池当中.此时常量池中已经存在"hello",那么之后str3 在存放"hello"地址的时候,就指向的是常量池中原来hello的地址。

所以 str1 等于 str3

再看一组练习

image.png

请判断两次打印的结果…

结果如下:

image.png

下面我们来分析,这组代码中str变量的内存存放

image.png

str1 指向字符串常量池中的 “hello”

str2 是"hel"与"lo" 组合而成的,常量在编译的时候就已经确定了,所以在编译时,已经被处理为"hello",所以也指向 常量池中的"hello"。

所以 str1 等于 str2

str3 首先new 了一个String(“hel”)对象,在堆中开辟一块空间,这个对象中的"hel"同时存放在常量池中,之后又在常量池中开辟一块空间存放 “lo”。两块部分之间的"+",将 String 的对象 与常量池中的 "lo"结合在堆中再次开辟一块新的空间,这块内存中的val ==“hello”,str3指向的是合并之后的对象 ,地址为999.

所以 str1 不等于 str3.

再看一组练习

image.png

请看一下,我们将String str 作为参数,改变str 的内容,以及传入 数组 val 改变 数组元素,其打印结果是什么?

image.png

我们看到 String str 的内容并未改变,但是数组 val 的元素却改变了。

我们从内存的角度来分析。

image.png

str1 指向字符串常量区的"hello",地址为888

val 作为数组引用,指向堆中开辟的数组空间,地址为777

str 作为函数的形参,接收str1实参的值,也就是888,此时str指向常量区的”hello“,但是在方法的内部,str = “abcde”,在字符串常量区中有开辟一块"abcde"的内存,地址为000,最后 str 存放的地址为000.

array 作为函数的形参,接收val 实参的值,也就是777,此时array 指向堆中 开辟的数组空间,此时通过array 来改变数组元素的内容,最终 改变的也同样是val 实参的内容.

三、字符串比较相等

如果现在有两个int型变量,判断其相等可以使用 == 完成。

image.png

如果说现在在String类对象上使用 == ?

代码1

image.png

看起来貌似没啥问题, 再换个代码试试, 发现情况不太妙.

代码2

image.png

在上面的几个练习中,我们 用 str1 == str2 比较的是两个字符串的引用/地址,如果比较字符串里面的内容,我们需要用到 equals 方法。

image.png

最后的打印结果

image.png

打印的结果符合字符串的内容比较。

常用的比较方式:

我们再来看一种情况,

image.png

这时候运行程序,就会出现以下情况:

image.png

空指针异常,因为 null. 任何方法都会出现异常。

所以一定要保证 str1 不能为null。

那么如果我们改一下,

image.png

所以我们知道 equals(),括号里可以是null,但是 点之前一定不能是 null.

image.png

当我们写代码遇到以上的情况时,我们应该尽量选方式2,这样保证 equals之前一定不为null,以防出现异常.

四、字符串常量池

在上面的例子中, String类的两种实例化操作, 直接赋值和 new 一个新的 String.

(1) 直接赋值

image.png

String类的设计使用了共享设计模式

在JVM底层实际上会自动维护一个对象池(字符串常量池)

如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.

如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用

如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用

理解 “池” (pool)

“池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据库连接池” …然而池这样的概念不是计算机独有, 也是来自于生活中.

举个例子:现实生活中有一种女神, 称为 “绿茶”, 在和高富帅谈好对象的同时, 还可能和别的屌丝搞暧昧. 这时候这个屌丝被称为 “备胎”. 那么为啥要有备胎? 因为一旦和高富帅分手了, 就可以立刻找备胎接盘, 这样 效率比较高.如果这个女神, 同时在和很多个屌丝搞暧昧, 那么这些备胎就称为 备胎池.

(2)采用构造方法

类对象使用构造方法实例化是标准做法。分析如下程序:

String str = new String("hello");

image.png

这样的做法有两个缺点:

1.如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).

2.字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.

(3)intern 的使用

String str1 = "hello";

String str2 = new String("hello").intren();

从上面的由 构造方法定义字符串,我们会浪费内存空间,而这里有一个方法 ,叫做intern(),手动入池。

那这是什么意思呢?

这是先看一下传入构造方法的字符串在字符串常量池中是否存在,如果有的话,就把常量池中的引用传给当前的引用类型变量。

image.png

综上所述,我们一般使用 直接赋值法来 创建 String 对象。

image.png

我们再来看这样一组代码,来画一下它的内存结构

image.png

在第一步的代码中,new 了两个字符串"1",在堆中创建了两个对象,指向常量池中的"1",拼接在一起,s3.interb(),s3手动入池,“11"在池中没有,所以就把 堆中的"11"的引用 555 传入常量池中。s4 指向池中的"11”,而这时池中已经有了"11"的引用,所以s4 指向的就是 s3在入池的引用。

所以结果为 true。

image.png

所以呢,我们解决了一个疑问

在常量池当中,可以放 字符串的字面值常量,也可以放引用。什么时候放引用,就是类似于上面的那种情况之下,s3.intern(),s3所指向的这个对象在字符串常量池中是不存在的,那么入池的时候就把堆中s3的引用放入。

五、理解字符串不可变

字符串是一种不可变对象. 它的内容不可改变.这是什么意思呢?

image.png

对于这种代码,乍一看我们以为成功的将str 每次与其他的字符串拼接,但是这样是不可以的, str 原来指向的是"hello",但是 在与" world"拼接之后,又会产生一个新的对象"helll world",再次拼接一个"!!!",那么又会产生一个新的对象"hello world!!!",在内存中就会产生多个对象。

image.png

我们最后需要的是"hello world!!!",但是却开辟了5块内存空间。

如果在一个循环中拼接,那么会开辟更多的内存空间!!

所以这样的代码是极为不可取的!!!

那么如何拼接呢,具体在之后的StringBuff、StringBuilder中介绍。

六、字符、字节、字符串

(1)字符与字符串

字符串内部包含一个字符数组,String 可以和 char[] 相互转换

image.png

1.字符数组转字符串

image.png

image.png

此时我们 的 str 结果就是 “hello”,同时他也可以再给两个参数.

2.将部分字符数组中的内容转换为字符串

image.png

offset–偏移量

count-- 转换几个

image.png

此时我们将val 中偏移1个,转换之后的两个数组元素为字符串

打印结果应该为 el

运行结果如下:

image.png

3.将字符串中对应索引转换为字符

image.png

image.png

索引从0开始,我们输入1,所以转换的为字符串中的e

运行结果如下:

image.png

4.将字符串转换为字符数组

image.png

image.png

我们用字符数组接收 str转换后的字符。

运行结果如下:

image.png

好了,了解了这几种字符与字符串的方法,我们通过几个练习来继续熟悉。

练习一给定字符串一个字符串, 判断其是否全部由数字所组成.

思路: 将字符串变为字符数组而后判断每一位字符是否是" 0 “~”‘9’"之间的内容,如果是则为数字.

image.png

(2)字节与字符串

字节常用于数据传输以及编码转换的处理之中,String 也能方便地和 byte[] 相互转换

常用方法:

image.png

1.字节数组转换为字符串

image.png

image.png

运行结果:

image.png

字符串中的内容是字节数组与Ascii 码表中对应的字符。

2.部分字节数组的内容转换为字符串

image.png

image.png

运行结果:

image.png

3.字符串转换为字节数组

image.png

image.png

运行结果:

image.png

(3) 小结

那么何时使用 byte[], 何时使用 char[] 呢?

byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.

char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候.

七、字符串的常见操作

(1)字符串比较

上面使用过String类提供的equals()方法,该方法本身是可以进行区分大小写的相等判断。除了这个方法之外,String类还提供有如下的比较操作.

1.区分大小写比较

image.png

image.png

运行结果:

image.png

我们常用的equals 方法 是区分大小写的,这点要注意。

2.不区分大小写的比较

image.png

image.png

运行结果:

image.png

这种不区分大小写的比较还是很常见的,比如应用于验证码上,不区分大小写。

3.比较两个字符串的大小关系

image.png

image.png

运行时结果

image.png

image.png

掌握了字符串比较相等的方法,下来我们来做一道练习题

比较字符串是否相等

image.png

题解思路:

将word1 字符串数组的内容都在str1 追加,word2 字符串数组的内容在str2 追加,最终equals 比较str1 str2 字符串的内容,相等返回 true,不等返回 false.

注意:参数等问题要考虑全面

image.png

image.png

(2)字符串查找

从一个完整的字符串之中可以判断指定内容是否存在,对于查找方法有如下定义:

image.png

判断一个字符串中是否存在子字符串

image.png

我们可以先看一下 contains 方法的源码

image.png

contains 方法的使用

image.png

运行结果

image.png

所以可判断在"badabc" 这个字符串中存在 这个 “abc” 的子字符串。

找到子字符串的下标

image.png

我们先来看一下一个参数的 index 方法的源码

image.png

带一个参数的 index 方法的使用

image.png

运行结果:

image.png

两个参数的index 方法的使用

image.png

在下面我们又看到了一个index 方法,这说明 默认情况下,index 是从0下标开始查找的,如果再给他一个下标参数,那么就从指定的下标位置进行字符串查找。

使用:

image.png

运行结果:

image.png

从后往前查找到子字符串的位置

image.png

lastIndexOf 是从后向前查找 子字符串的位置

lastIndexOf 方法的使用

image.png

运行结果:

image.png

同时 lastIndexOf 也有两个参数的方法,从指定下标开始从后向前进行查找。

判断是否由 参数字符串开头的

image.png

同时也有两个参数的方法,从指定位置判断是否由 指定字符串开头

image.png

判断是否由指定字符串进行结尾的

image.png

(3)字符串替换

image.png

(1)替换所有的指定内容

image.png

replaceAll 的使用

image.png

运行结果:

image.png

成功的把所有的 “ab” 替换成为 “AB”.

(2)替换首个要替换的内容.

image.png

replaceFirst 的使用

image.png

运行结果:

image.png

注意说明:

由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串.

(4)字符串拆分

可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串

image.png

1.将字符串全部拆分

image.png

接收的类型是字符串数组类型,传参数时,传一个我们想要分割的符号。split 的使用

image.png

我们在用 split 方法是, 以 空格 为分割符,将我们的str 字符串 进行拆分

我们来看拆分的效果

image.png

2.带两个参数的split 方法

image.png

还是以上面的字符串为例

image.png

运行结果:

image.png

我们除了将字符串作为参数,还将limit 设置为2,那么拆分后的数组长度就为2,所以运行结果就如上所示。

难点:

拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义字符

示例1

拆分 IP 地址

比如说我们要分割IP 地址,192.168.1.1,以 “.” 分割。

image.png

当我们运行时会发现 打印为空,这是为什么呢?

有些符号比较特殊,必须用到转义字符

“ . ”才能表示一个真正的 “.”

同时""也需要进行转义,那么就又要再加一个斜杠。

“\.”这时字符串才只能被 “ . ”分割。

运行结果

image.png

  1. 字符"|","*","+"都得加上转义字符,前面加上"\".

  2. 而如果是"",那么就得写成"\".

  3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.

连字符 “ | ” 的使用

image.png

运行结果:

image.png

(5)字符串截取

从一个完整的字符串之中截取出部分内容。可用方法如下:

image.png

1.从指定下标截取到字符串结束

image.png

方法的使用

image.png

运行结果:

image.png

2.带有两个参数的subString 方法,截取指定下标范围内的字符串内容

image.png

方法的使用

image.png

运行结果

image.png

注意:

1.指定下标范围 是 左闭右开的区间

2.截取后的字符串是一个新的对象

(6)其他操作方法

字符串操作还有很多其他的方法,在这里我们只进行简单介绍。

八、StringBuffer 和 StringBuilder

StringBuffer 和 StringBuilder 又是一种新的字符串类型。

通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供 StringBuffer 和 StringBuilder 类。

StringBuffer 和 StringBuilder 在功能上大部分是相同的,在这里我们着重介绍 StringBuffer.

(1)append 方法

在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法。

String和StringBuffer最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用 StingBuffer。

image.png

运行结果:

image.png

我们来看一下 StringBuffer 的 append 方法的源码

image.png

最后返回的是 this,在字符串本身拼接字符串。同时StringBuffer 有自己重写的 toString 方法,可以直接进行打印。

我们来看一下 以下的代码:

image.png

我们对以上代码进行编译一下:

image.png

在编译的过程中,我们发现StringBuilder.append 方法的出现;

我们将这个过程用 StringBuilder 写一下:

image.png

说明:

String 的“+” 拼接,会被底层优化为一个 StringBuilder ,拼接的时候会用到 append 方法

(2)注意

注意:String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:

String变为StringBuffer:利用StringBuffer的构造方法或append()方法

StringBuffer变为String:调用toString()方法。

除了append()方法外,StringBuffer也有一些String类没有的方法:

字符串反转:

public synchronized StringBuffer reverse();

(3)区别

String 和 StringBuilder 及 StringBuffer 的区别

String 进行拼接时,底层会被优化为StringBuilder

image.png

String的拼接会产生临时对象,但是后两者每次都只是返回当前对象的引用。

String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.

StringBuilder 和 StringBuffer 的区别

我们来看一下这两个类型的 append 方法

所以 StringBuffer 和 StringBuilder 的区别主要体现在线程安全上 。

1.StringBuffer与StringBuilder大部分功能是相似的2.StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作

字符串操作是我们以后工作中非常常用的操作. 使用起来都非常简单方便, 我们一定要使用熟练.

今日份分享已结束,请大家多多包涵和指点!