今日分享开始啦,请大家多多指教~
字符串是我们以后工作中非常常用到的类型.使用起来都非常简单方便,我们一定要使用熟练。
那么C语言中是否有字符串类型?答案是“没有”!!
char*p=hello;
那么p的类型是一个字符串类型么?不是,p是一个指针!!
而在Java当中是有字符串类型的——String
一、定义方式
创建字符串的方式有很多种,常见的构造String的方式如以下:
方式一:直接赋值法
Stringstr1=hello;
方式二:newString()
Stringstr2=newString(hello);
方式三:创建一个字符数组ch,newString(ch)
charchs[]={h,e,l,l,l,o};
Stringstr3=newString(chs);
二、内存
在此之前我们要先引入一个概念字符串常量池
Stingconstantpool字符串常量池的特性
1.在JDK.7开始,字符串常量池被挪到堆里了
2.池内的数据不存在重复
下面我们通过一系列的练习来熟悉字符串常量池以及字符串类型数据在内存中的存放。
我们来看这样的代码,str代表的是引用\地址,请判断两次打印分别是什么?
我们来看结果
这个结果说明str1和str2存放的地址是不一样的,str1和str3存放的地址是一样的。
好的,为什么是这样的结果呢?我们来看一下这几个字符串类型变量的内存。
hello如果存放在常量池当中,就会占用内存,假如这块空间的地址为,那么str1中存放的就是.
str2new一个String对象,那么肯定在堆上开辟内存,假设内存地址是,在这个String对象中,存在一个value[]保存着orginal传入的字符串,这个val==“hello”,因为在字符串常量池中已经有了hello,所以val直接指向常量池中的hello.但是str2指向的依然是在堆中的空间。
所以str1不等于str2。
之后呢,str3也等于hello,他也准备把hello放在常量池当中.此时常量池中已经存在hello,那么之后str3在存放hello地址的时候,就指向的是常量池中原来hello的地址。
所以str1等于str3
再看一组练习
请判断两次打印的结果…
结果如下:
下面我们来分析,这组代码中str变量的内存存放
str1指向字符串常量池中的“hello”
str2是hel与lo组合而成的,常量在编译的时候就已经确定了,所以在编译时,已经被处理为hello,所以也指向常量池中的hello。
所以str1等于str2
str3首先new了一个String(“hel”)对象,在堆中开辟一块空间,这个对象中的hel同时存放在常量池中,之后又在常量池中开辟一块空间存放“lo”。两块部分之间的+,将String的对象与常量池中的lo结合在堆中再次开辟一块新的空间,这块内存中的val==“hello”,str3指向的是合并之后的对象,地址为.
所以str1不等于str3.
请看一下,我们将Stringstr作为参数,改变str的内容,以及传入数组val改变数组元素,其打印结果是什么?
我们看到Stringstr的内容并未改变,但是数组val的元素却改变了。
我们从内存的角度来分析。
str1指向字符串常量区的hello,地址为
val作为数组引用,指向堆中开辟的数组空间,地址为
str作为函数的形参,接收str1实参的值,也就是,此时str指向常量区的”hello“,但是在方法的内部,str=“abcde”,在字符串常量区中有开辟一块abcde的内存,地址为,最后str存放的地址为.
array作为函数的形参,接收val实参的值,也就是,此时array指向堆中开辟的数组空间,此时通过array来改变数组元素的内容,最终改变的也同样是val实参的内容.
三、字符串比较相等
如果现在有两个int型变量,判断其相等可以使用==完成。
如果说现在在String类对象上使用==?
代码1
看起来貌似没啥问题,再换个代码试试,发现情况不太妙.
代码2
在上面的几个练习中,我们用str1==str2比较的是两个字符串的引用/地址,如果比较字符串里面的内容,我们需要用到equals方法。
最后的打印结果
打印的结果符合字符串的内容比较。
常用的比较方式:
我们再来看一种情况,
这时候运行程序,就会出现以下情况:
空指针异常,因为null.任何方法都会出现异常。
所以一定要保证str1不能为null。
那么如果我们改一下,
所以我们知道equals(),括号里可以是null,但是点之前一定不能是null.
当我们写代码遇到以上的情况时,我们应该尽量选方式2,这样保证equals之前一定不为null,以防出现异常.
四、字符串常量池
在上面的例子中,String类的两种实例化操作,直接赋值和new一个新的String.
(1)直接赋值
String类的设计使用了共享设计模式
在JVM底层实际上会自动维护一个对象池(字符串常量池)
如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
理解“池”(pool)
“池”是编程中的一种常见的,重要的提升效率的方式,我们会在未来的学习中遇到各种“内存池”,“线程池”,“数据库连接池”…然而池这样的概念不是计算机独有,也是来自于生活中.
举个例子:现实生活中有一种女神,称为“绿茶”,在和高富帅谈好对象的同时,还可能和别的屌丝搞暧昧.这时候这个屌丝被称为“备胎”.那么为啥要有备胎?因为一旦和高富帅分手了,就可以立刻找备胎接盘,这样效率比较高.如果这个女神,同时在和很多个屌丝搞暧昧,那么这些备胎就称为备胎池.
(2)采用构造方法
类对象使用构造方法实例化是标准做法。分析如下程序:
Stringstr=newString(hello);
这样的做法有两个缺点:
1.如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量“hello”也是一个匿名对象,用了一次之后就不再使用了,就成为垃圾空间,会被JVM自动回收掉).
2.字符串共享问题.同一个字符串可能会被存储多次,比较浪费空间.
(3)intern的使用
Stringstr2=newString(hello).intren();
从上面的由构造方法定义字符串,我们会浪费内存空间,而这里有一个方法,叫做intern(),手动入池。
那这是什么意思呢?
这是先看一下传入构造方法的字符串在字符串常量池中是否存在,如果有的话,就把常量池中的引用传给当前的引用类型变量。
综上所述,我们一般使用直接赋值法来创建String对象。
我们再来看这样一组代码,来画一下它的内存结构
在第一步的代码中,new了两个字符串1,在堆中创建了两个对象,指向常量池中的1,拼接在一起,s3.interb(),s3手动入池,“11在池中没有,所以就把堆中的11的引用传入常量池中。s4指向池中的11”,而这时池中已经有了11的引用,所以s4指向的就是s3在入池的引用。
所以结果为true。
所以呢,我们解决了一个疑问
在常量池当中,可以放字符串的字面值常量,也可以放引用。什么时候放引用,就是类似于上面的那种情况之下,s3.intern(),s3所指向的这个对象在字符串常量池中是不存在的,那么入池的时候就把堆中s3的引用放入。
五、理解字符串不可变
字符串是一种不可变对象.它的内容不可改变.这是什么意思呢?
对于这种代码,乍一看我们以为成功的将str每次与其他的字符串拼接,但是这样是不可以的,str原来指向的是hello,但是在与world拼接之后,又会产生一个新的对象helllworld,再次拼接一个!!!,那么又会产生一个新的对象helloworld!!!,在内存中就会产生多个对象。
我们最后需要的是helloworld!!!,但是却开辟了5块内存空间。
如果在一个循环中拼接,那么会开辟更多的内存空间!!
所以这样的代码是极为不可取的!!!
那么如何拼接呢,具体在之后的StringBuff、StringBuilder中介绍。
六、字符、字节、字符串
(1)字符与字符串
字符串内部包含一个字符数组,String可以和char[]相互转换
1.字符数组转字符串
此时我们的str结果就是“hello”,同时他也可以再给两个参数.
2.将部分字符数组中的内容转换为字符串
offset–偏移量
count--转换几个
此时我们将val中偏移1个,转换之后的两个数组元素为字符串
打印结果应该为el
运行结果如下:
3.将字符串中对应索引转换为字符
索引从0开始,我们输入1,所以转换的为字符串中的e
4.将字符串转换为字符数组
我们用字符数组接收str转换后的字符。
好了,了解了这几种字符与字符串的方法,我们通过几个练习来继续熟悉。
练习一给定字符串一个字符串,判断其是否全部由数字所组成.
思路:将字符串变为字符数组而后判断每一位字符是否是0“~”‘9’之间的内容,如果是则为数字.
(2)字节与字符串
字节常用于数据传输以及编码转换的处理之中,String也能方便地和byte[]相互转换
常用方法:
1.字节数组转换为字符串
运行结果:
字符串中的内容是字节数组与Ascii码表中对应的字符。
2.部分字节数组的内容转换为字符串
3.字符串转换为字节数组
(3)小结
那么何时使用byte[],何时使用char[]呢?
byte[]是把String按照一个字节一个字节的方式处理,这种适合在网络传输,数据存储这样的场景下使用.更适合针对二进制数据来操作.
char[]是吧String按照一个字符一个字符的方式处理,更适合针对文本数据来操作,尤其是包含中文的时候.
七、字符串的常见操作
(1)字符串比较
上面使用过String类提供的equals()方法,该方法本身是可以进行区分大小写的相等判断。除了这个方法之外,String类还提供有如下的比较操作.
1.区分大小写比较
我们常用的equals方法是区分大小写的,这点要注意。
2.不区分大小写的比较
这种不区分大小写的比较还是很常见的,比如应用于验证码上,不区分大小写。
3.比较两个字符串的大小关系
运行时结果
掌握了字符串比较相等的方法,下来我们来做一道练习题
比较字符串是否相等
题解思路:
将word1字符串数组的内容都在str1追加,word2字符串数组的内容在str2追加,最终equals比较str1str2字符串的内容,相等返回true,不等返回false.
注意:参数等问题要考虑全面
(2)字符串查找
判断一个字符串中是否存在子字符串
我们可以先看一下contains方法的源码
contains方法的使用
运行结果
所以可判断在badabc这个字符串中存在这个“abc”的子字符串。
找到子字符串的下标
我们先来看一下一个参数的index方法的源码
带一个参数的index方法的使用
两个参数的index方法的使用
在下面我们又看到了一个index方法,这说明默认情况下,index是从0下标开始查找的,如果再给他一个下标参数,那么就从指定的下标位置进行字符串查找。
使用:
从后往前查找到子字符串的位置
lastIndexOf是从后向前查找子字符串的位置
lastIndexOf方法的使用
同时lastIndexOf也有两个参数的方法,从指定下标开始从后向前进行查找。
判断是否由参数字符串开头的
同时也有两个参数的方法,从指定位置判断是否由指定字符串开头
判断是否由指定字符串进行结尾的
(3)字符串替换
(1)替换所有的指定内容
replaceAll的使用
成功的把所有的“ab”替换成为“AB”.
(2)替换首个要替换的内容.
replaceFirst的使用
注意说明:
由于字符串是不可变对象,替换不修改当前字符串,而是产生一个新的字符串.
(4)字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串
1.将字符串全部拆分
接收的类型是字符串数组类型,传参数时,传一个我们想要分割的符号。split的使用
我们在用split方法是,以空格为分割符,将我们的str字符串进行拆分
2.带两个参数的split方法
还是以上面的字符串为例
我们除了将字符串作为参数,还将limit设置为2,那么拆分后的数组长度就为2,所以运行结果就如上所示。
难点:
拆分是特别常用的操作.一定要重点掌握.另外有些特殊字符作为分割符可能无法正确切分,需要加上转义字符
示例1
拆分IP地址
比如说我们要分割IP地址,..1.1,以“.”分割。
当我们运行时会发现打印为空,这是为什么呢?
有些符号比较特殊,必须用到转义字符
“\.”才能表示一个真正的“.”
同时\也需要进行转义,那么就又要再加一个斜杠。
“\\.”这时字符串才只能被“.”分割。
1.字符
,*,+都得加上转义字符,前面加上\\.
2.而如果是\,那么就得写成\\.
3.如果一个字符串中有多个分隔符,可以用
作为连字符.
连字符“
”的使用
(5)字符串截取
从一个完整的字符串之中截取出部分内容。可用方法如下:
1.从指定下标截取到字符串结束
2.带有两个参数的subString方法,截取指定下标范围内的字符串内容
注意:
1.指定下标范围是左闭右开的区间
2.截取后的字符串是一个新的对象
(6)其他操作方法
字符串操作还有很多其他的方法,在这里我们只进行简单介绍。
八、StringBuffer和StringBuilder
StringBuffer和StringBuilder又是一种新的字符串类型。
通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer和StringBuilder类。
StringBuffer和StringBuilder在功能上大部分是相同的,在这里我们着重介绍StringBuffer.
(1)append方法
在String中使用+来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法。
String和StringBuffer最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用StingBuffer。
我们来看一下StringBuffer的append方法的源码
最后返回的是this,在字符串本身拼接字符串。同时StringBuffer有自己重写的toString方法,可以直接进行打印。
在编译的过程中,我们发现StringBuilder.append方法的出现;
我们将这个过程用StringBuilder写一下:
说明:
String的“+”拼接,会被底层优化为一个StringBuilder,拼接的时候会用到append方法
(2)注意
注意:String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuffer:利用StringBuffer的构造方法或append()方法
StringBuffer变为String:调用toString()方法。
除了append()方法外,StringBuffer也有一些String类没有的方法:
字符串反转:
publicsynchronizedStringBufferreverse();
(3)区别
String和StringBuilder及StringBuffer的区别
String进行拼接时,底层会被优化为StringBuilder
String的拼接会产生临时对象,但是后两者每次都只是返回当前对象的引用。
String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
StringBuilder和StringBuffer的区别
我们来看一下这两个类型的append方法
所以StringBuffer和StringBuilder的区别主要体现在线程安全上。
1.StringBuffer与StringBuilder大部分功能是相似的2.StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
字符串操作是我们以后工作中非常常用的操作,使用起来都非常简单方便,我们一定要使用熟练。
今日份分享已结束,请大家多多包涵和指点!