前言
java是面向对象的一门编程语言,通过实例化对象,对象间的交互实现相应的功能,决解相应的问题。,java中实现了很多的类供我们使用,我们要学习java一定要学好一些常用的类,去学习里面的构造方法,成员方法,接口方法等,所以今天我就简单的向大家介绍String类,和类里面的一些构造方法及常用的方法。首先我打开java使用帮助文档,针对的是JDK1.8版本,看文档对Sting这个类的介绍。这里简单的说明了Sting这个类是在java.lang这个包底下的,它继承了object类,以及这个类实现了哪些接口,有哪些构造方法等许多的描述,我们有时间可以多多阅读这个中文帮助文档。在使用其他的类有什么不明白的也可以查阅该文档。
1.创建字符串
🎇常见的构造 String 的方式:
public static void main(String[] args) {
String str1 = "hellobit";
System.out.println(str1);
String str2 = new String("hellobit");
System.out.println(str2);
char[] chars = {'a','b','c'};
String str3 = new String(chars);
System.out.println(str3);
}
??方式三String里面重写了toString方法,再通过println进行打印,就打印了str3这个引用所指向的对象了,所以这里打印的是字符串的内容,而不是引用里面的地址。对于字符串的创建,我们需要重点理解创建时的内存布局情况,这样可以帮助我们更好地理解字符串的创建,方式一,方式二创建字符串的内存布局比较简单,直接就是栈上的引用变量指向堆上的对象。后面有构造String的内存布局图,接下来我简单地画一下方式三创建字符串的内存布局图。 首先我们在堆区创建一个字符数组,由引用变量chars来指向它,之后我们再new了一个对象String(chars),对象里面的成员变量有一个char类型的数组value,value是一个引用变量,构造这个匿名对象的时候我们要传一个参数,参数是数组或者字符串都行,调用String的其中一个构造方法public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }该方法复制产生了一个新的数组,这个数组由value这个引用来维护。
2.字符串比较相等
??📃如果现在有两个int型变量,判断其相等可以使用 == 完成。
int x = 10 ;
int y = 10 ;
System.out.println(x == y);
true
如果说现在在String类对象上使用 == ? 代码1
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
true
看起来貌似没啥问题, 再换个代码试试, 发现情况不太妙. 代码2
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
false
代码一的内存布局:
代码二的内存布局: 📝📐我们来分析上面两种创建 String 方式的差异.我们发现, 代码1中str1 和 str2 是指向同一个对象的. 此时如 “Hello” 这样的字符串常量是在 字符串常量池 中,代码2中str1 和 str2 是指向的不是同一个对象,但最终指向的字符串内容都是 “Hello” ,通过 String str1 = new String(“Hello”); 这样的方式创建的 String 对象相当于再堆上另外开辟了空间来存储"Hello" 的内容, 也就是内存中存在两份 “Hello”. 🔍?? String 是引用数据类型,如果使用“==”来比较两个引用的值,比较的还是值相同不相同,但是这个值不是字符串值,是引用值,str1和str2分别指向堆上两个不同地址处,代码2 str1和str2的值不同,所以结果为false。如果要比较字符串的内容可以使用equals方法进行比较。上图中代码2首先第一行代码传入字符串"Hello",发现字符串常量池里面没有该字符串,就将其入池,value是String里面的一个数组引用,是new产生的匿名对象的一个成员,new String("Hello")产生对象时要传入一个参数Hello,传入参数后调用构造方法,这样Srting字符串才能完成构造,这样value这个引用指向了字符串常量池里面的"Hello",str1指向value。同理str2也指向它创建的对象的成员value,创建对象时也要传入参数Hello,但发现常量池里面有Hello了就直接用了,value就直接指向常量池里面唯一的那个Hello了。
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
true
equals 使用注意事项 现在需要比较 str 和 “Hello” 两个字符串是否相等, 我们该如何来写呢?
String str = new String("Hello");
System.out.println(str.equals("Hello"));
System.out.println("Hello".equals(str));
在上面的代码中, 哪种方式更好呢???? 🎇🎆那推荐使用 “方式二”. 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会。
String str = null;
System.out.println(str.equals("Hello"));
常
System.out.println("Hello".equals(str));
2.1字符串传参
Java 中数组, String, 以及自定义的类都是引用类型,由于 String 是引用类型, 因此对于以下代码,我们应该注意传引用不一定改变字符串的值。
String str1 = "Hello";
String str2 = str1;
它的内存布局图如上面代码1,, 这时我们可能会想是不是修改 str1 , str2 也会随之变化呢?
str1 = "world";
System.out.println(str2);
Hello
我们发现, “修改” str1 之后, str2 也没发生变化, 还是 hello? 事实上, str1 = “world” 这样的代码并不算 “修改” 字符串, 而是让 str1 这个引用指向了一个新的 String 对象. 在如下面的代码:
public static void func(String str) {
str = "bit";
}
public static void main(String[] args) {
String str = "gaobo";
func(str);
System.out.println(str);
}
下面的代码在函数传参的时候,传引用不会改变原来的值 这里有两个引用,一个main函数里面的,一个func函数里面的,开始的时候这两个引用指向同一内存空间0x123,之后被调函数里的引用指向一个新的地址,但不影响调用函数里的引用。
3.字符串常量池
在上面的例子中, String类的两种实例化操作, 直接赋值和 new 一个新的 String. 直接赋值
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
true
🎇为什么现在并没有开辟新的堆内存空间呢??因为String类的设计使用了共享设计模式 在JVM底层实际上会自动维护一个对象池(字符串常量池)
- 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存 到这个对象池之中.
- 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
- 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用 也就是简单地说该字符串常量池中只会有不同的String类对象,不会出现相同的String类对象,常量池的一个功能帮助我们节省内存空间,同样的东西我们存储一份就行了。
采用构造方法
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);
false
📝由上面的代码二的内存布局图可知: 这样的做法有两个缺点:
- 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
- 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间
4.字符串创建实例分析
下面我将通过一些简单的例子,进一步地带老铁们熟悉直接赋值,采用构造方法构造字符串及字符串常量池在构造字符串时的作用。 代码1:
String str1 = "helloboy";
String str2 = new String("helloboy");
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
内存布局图: 📝该代码的内存布局图,可以结合最上面上面代码1,代码2的内存布局图来看,首先直接赋值创建了一个字符串 String str1 = “helloboy”;,放在字符串常量池,之后通过构造的方式,str2指向在堆区实例化的对象,这个对象的产生需要传一个字符串参数,刚好字符串常量池有这个参数,,直接用就行了,value就指向了这个参数。 代码2:
String str1 = "hello";
String str2 = "hello" + "world";
System.out.println(str1 == str2);
像"helloworld",“world”,"hello"这些量被称为字符串字面值常量 ,是一个常量,代码在进行字符串的拼接的时候,编译期间会进行优化,把它优化为一个常量,由常量池的知识可知在运行期间就只开辟一块空间存储优化过的常量了 。因此str1和str2就指向了字符串常量池的同一块空间了。 代码3:
String str1 = "goodboy";
String str2 = "good";
String str3 = str2+"boy";
System.out.println(str1 == str3);
直接赋值构造两个字符串,“goodboy”,"good"放在字符串常量池中,str1和str2分别指向这两个字符串,定义变量str3在堆区开辟空间,它的内容等于str2+“boy”;,str3最终指向拼接好的字符串。 代码4:
String str1 = "goodboy";
String str4 = "good"+new String("boy");
System.out.println(str1 == str4);
??📘"goodboy","good","boy"这些字符串之前没有的首先都是放入常量池, new String(“boy”)新产生一个对象,对象里的引用指向传入的参数,之后在堆上开辟一块内存,存拼接好的字符串"goodboy",拼接好的字符串虽然也是"goodboy",但是和常量池里的不一样。 代码5:
String str1 = "goodboy";
String str5 = new String("good)+new String("boy");
System.out.println(str1 == str5);
System.out.println(str1.equals(str5));
这代码和上面的差不多,用脚指头想想也明白,哈哈!!(借助上面的代码和图秒懂),首先常量池里放入"goodboy",“good”,“boy”,通过构造的方式在堆区构造两个字符串,之后两个字符串在堆区拼接形成和常量池内容一样的字符串,str5指向该字符串。 代码6:
String str1 = "goodboy";
String str2= new String("goodboy");
str2.intern();
System.out.println(str1 == str2);
📝这个代码和代码1的内存布局图一样,只是多了一步对字符串的入池操作 str2.intern();对于我们在堆区构造产生的字符串,str2 拿到对象里面的字符串的值,和常量池里面的字符串比较,如果常量池有该字符串就不如池,如果没有该字符串就把该字符串放入常量池中。因为原来字符串常量池里有我们构造出来的字符串,所以堆区的字符串就不如池了,内存代码布局就像开始一样的、 代码7:
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
上面的代码是字符串要入池的时候,池子里有该字符串,现在的这个代码是入池的时候,池子里没有这个字符串,首先在堆上创建两个字符串对象,之后拼接形成字符串“11”,s3这个引用指向指向“11”,之后将s3指向的字符串做入池操作,池子里没有就入进去,入的时候在常量池里面开辟一块内存,存储要入的字符串在栈区的地址,而不是内容, 之后又创建一个对象“11”,因为产量池里面有这个对象,所以s4这个引用就直接存储了常量池里那个字符串的地址 了,文字和上面的图一起食用效果更佳!!! 代码8:
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
这代码和上面的代码只是有几行代码颠倒了,就产生了不一样的效果了,s3在s4放入常量池后再入池,就入不了池了,就什么操作也不发生。
5.理解字符串不可变
🌈?字符串是一种不可变对象. 它的内容不可改变. 从源代码可知String 类的内部实现也是基于一个 char value[]的数组来实现的,这个数组被final 和private 修饰 但是 String 类并没有提供 set 方法来修改内部的字符数组,所以简单的认为字符串是一种不可变对象。 感受下形如这样的代码
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
hello world!!!
形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是. 内存变化如下:
从上面的内存变化图中,我们可以看到,最终形成了一个新的字符串,但是这个字符串不是在原来字符串的后面添加字符串形成的,没有修改字符串的内容,是通过字符串的拼接形成一个新的对象,第一次产生一个对象hello world并且str指向这个对象,第二次产生一个hello world!!!对象,str1又指向这个新的对象。每次拼接完成后,str这个引用里面的地址都发生了改变。 ?🚫因此我们在开发中要注意不要出现类似如下的代码,会产生大量的临时对象, 效率比较低.
String str = "hello" ;
for(int x = 0; x < 1000; x++) {
str += x ;
}
System.out.println(str);
那么如果实在需要修改字符串, 例如, 现有字符串 str = “Hello” , 想改成 str = “hello” , 该怎么办? 💯常见办法: 借助原字符串, 创建新的字符串 substring()方法截取一个字符串的子串,括号里面的参数表示从主串的什么位置开始截取,主串的第一个位置默认为0位置,之后又进行字符串的截取,那么情况就和上面的情况一样了。
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
hello
?特殊办法: 使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员,但今天的这篇博文我们不详讲,只是提一下,后面我的博文会详细讲到。 为什么 String 要不可变?(不可变对象的好处是什么?) ??
- 方便实现字符串常量池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
6.字符, 字节与字符串
6.1字符与字符串
字符串内部包含一个字符数组,String 可以和 char[] 相互转换.
字符与字符串常用的一些方法如下表:
No | 方法名称 | 类型 | 描述 |
---|
1 | public String(char value[]) | 构造 | 将字符数组中的所有内容变为字符串 | 2 | public String(char value[], int offset, int count) | 构造 | 将部分字符数组中的内容变为字符串,offset 为偏移量,从0开始 ,count为转换的字符个数 | 3 | public char charAt(int index) | 普通 | 取得指定索引的字符,索引从0开始 | 4 | public char[] toCharArray() | 普通 | 将字符串变为字符数组返回 |
方法代码简单演示: 方法一:将字符数组中的所有内容变为字符串
char[] value = {'a', 'b', 'c', 'd', 'e'};
String str = new String(value);
System.out.println(str);
结果为:abcde 方法二:将部分字符数组中的内容变为字符串
char[] value = {'a','b','c','d','e'};
String str = new String(value,1,2);
System.out.println(str);
结果为:bc 方法三: 取得指定索引的字符,索引从0开始
String str2 = "hello";
char ch = str2.charAt(1);
System.out.println(ch);
结果为:e 方法四:将字符串变为字符数组返回
String str3 = "hello";
char[] chars = str3.toCharArray();
System.out.println(Arrays.toString(chars));
结果为:[h,e,l,l,o] ???注意:对于上述方法给定的数字位置和数字范围,一定要合理,不然就会产生异常。
6.2字节与字符串
字节常用于数据传输以及编码转换的处理,字符串 String 也能和字节数组 byte[] 相互转换
No | 方法名称 | 类型 | 描述 |
---|
1 | public String(byte bytes[]) | 构造 | 将字节数组变成字符串 | 2 | public String(byte bytes[], int offset, int length) | 构造 | 将部分字节数组中的内容变为字符串 | 3 | public byte[] getBytes() | 普通 | 将字符串以字节数组的形式返回 | 4 | public byte[] getBytes(String charsetName)throws java.io.UnsupportedEncodingException | 普通 | 编码转换处理 |
方法代码简单演示: 方法一:将字节数组变成字符串
byte[] bytes = {97, 98 ,99 ,100};
String str = new String(bytes);
System.out.println(str);
结果为:abcd 方法二:将部分字节数组中的内容变为字符串
byte[] bytes = {97, 98 ,99 ,100};
String str = new String(bytes, 1, 2);
System.out.println(str);
结果为:bc 方法三:将字符串以字节数组的形式返回
public static void main(String[] args) {
String str = "abcde";
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes));
}
结果为:[97, 98, 99, 100, 101] 方法四:编码转换处理(很少使用,了解即可)
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "李敏敏";
byte[] bytes = str.getBytes("utf-8");
System.out.println(Arrays.toString(bytes));
}
结果为:[-26, -99, -114, -26, -107, -113, -26, -107, -113] 如果我们将编码方式“utf-8”,改为“gbk”则会有不同的结果
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "李敏敏";
byte[] bytes = str.getBytes("gbk");
System.out.println(Arrays.toString(bytes));
}
结果为:[-64, -18, -61, -12, -61, -12] 其实这个方法就是把我们给的字符串,以我们指定的编码规则转换为字节数组的,编码规则不同则转换的内容不同,在utf-8中一个汉字占3个字节,gbk中占一个字节。
小结: 那么何时使用 byte[], 何时使用 char[] 呢?
- byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
- char[] 是把 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作,尤其是包含中文的时候.
- 一个简单粗暴的区分方式就是用记事本打开能不能看懂里面的内容. 如果看的懂, 就是文本数据(例如 .java 文件), 如果看不懂, 就是二进制数据(例如 .class 文件)。
7.字符串常见操作
7.1字符串比较
上述介绍到 equals 可以比较字符串是否相等,并且是区分大小写的。而除了它,String 类还有其他比较字符串的方法
No | 方法名称 | 类型 | 描述 |
---|
1 | public boolean equals(Object anObject) | 普通 | 区分大小写的比较 | 2 | public boolean equalsIgnoreCase(String anotherString) | 普通 | 不区分大小写的比较 | 3 | public int compareTo(String anotherString) | 普通 | 比较两个字符串大小关系 |
方法代码简单演示: 方法一:区分大小写的比较
String str1 = "hello";
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
结果为:false 方法二:不区分大小写的比较
String str1 = "hello";
String str2 = new String("Hello");
System.out.println(str1.equalsIgnoreCase(str2));
结果为:true 方法三: 比较两个字符串大小关系
String str1 = "hello";
String str2 = new String("hella");
System.out.println(str1.compareTo(str2));
结果为:14 在String类中compareTo()方法是一个非常重要的方法,比较规则大致是,都是以第一个字符串为比较基准,首先比较字符串长度,第一个字符串的长度大于第二个字符串的长度则返回一个正数,长度相同返回0,第一个的长度小于第二个的长度返回一个负数,长度相同之后从前往后比较,字符串里面第一个不相同的字符 的大小关系就是整个字符串大小的关系,比较的返回值也是和比较长度的返回情况一样的。
7.2字符串查找
No | 方法名称 | 类型 | 描述 |
---|
1 | public boolean contains(CharSequence s) | 普通 | 判断一个字符串是否存在 | 2 | public int indexOf(String str) | 普通 | 从头开始查找指定字符串的位置,查到了返回位置的开始索引,查不到返回-1 | 3 | public int indexOf(String str, int fromIndex) | 普通 | 从指定位置开始查找子字符串位置 | 4 | public int lastIndexOf(String str) | 普通 | 由后向前查找子字符串位置 | 5 | public int lastIndexOf(String str, int fromIndex) | 普通 | 从指定位置由后向前查找 | 6 | public boolean startsWith(String prefix) | 普通 | 判断是否以指定字符串开头 | 7 | public boolean startsWith(String prefix, int toffset) | 普通 | 从指定位置开始判断是否以指定字符串开头 | 8 | public boolean endWith(String suffix) | 普通 | 判断是否以指定字符串结尾 |
方法代码简单演示: 方法一:判断一个字符串是否存在
String str = "helloworld";
System.out.println(str.contains("hello"));
结果为:true 方法二:从头开始查找指定字符串的位置,查到了返回位置的开始索引,查不到返回-1
String str = "helloworld";
System.out.println(str.indexOf("world"));
结果为:5 方法三:从指定位置开始查找子字符串位置
String str = "helloworldhelloworld";
System.out.println(str.indexOf("world", 6));
结果为:15 方法四: 由后向前查找子字符串位置
String str = "helloworld";
System.out.println(str.lastIndexOf("world"));
结果为:5 方法五: 从指定位置由后向前查找
String str = "helloworld";
System.out.println(str.lastIndexOf("world", 5));
结果为:5 方法六:判断是否以指定字符串开头
String str = "helloworld";
System.out.println(str.startsWith("hello"));
结果为:true 方法七:从指定位置开始判断是否以指定字符串开头
String str = "helloworld";
System.out.println(str.startsWith("hello", 2));
结果为:false 方法八:判断是否以指定字符串结尾
String str = "helloworld";
System.out.println(str.endsWith("world"));
结果为:true
7.3字符串替换
String 类中也有方法将一个指定的新字符串替换掉已有的字符串数据
No | 方法名称 | 类型 | 描述 |
---|
1 | public String replaceAll(String regex, String replacement) | 普通 | 替换所有的指定内容 | 2 | public String replaceFirst(String regex, String replacement) | 普通 | 替换首个内容 | 3 | public String replace(char oldChar, char newChar) | 普通 | 替换所有的指定字符 |
方法一: 替换所有的指定内容 将原来字符串里面的某一字符串替换为另一字符串
String str = "ababcabcd";
String str3 = str.replaceAll("ab","gg");
System.out.println(str3);
结果为:ggggcggcd 方法二:替换首个内容 将字符串的开始几个字符替换为另外的几个字符
String str = "ababcabcd";
String str4 = str.replaceFirst("ab","gg");
System.out.println(str4);
结果为:ggabcabcd 方法三:替换所有的指定字符 将字符串中某一个字符全部替换为另一个新的字符
String str = "ababcabcd";
String str2 = str.replace('a','g');
System.out.println(str2);
结果为:ggabcabcd
7.4字符串拆分
String 类中也有方法将一个完整的字符串按照指定的分隔符划分为若干子字符串
No | 方法名称 | 类型 | 描述 |
---|
1 | public String[] split(String regex) | 普通 | 将字符串全部拆分 | 2 | public String[] split(String regex, int limit) | 普通 | 将字符串部分拆分,该数组长度就是 limit 极限 |
方法一:将字符串全部拆分,只有一个参数的
String str = "ab#abc#abcd";
String[] strings = str.split("#");
for ( String s : strings) {
System.out.println(s);
}
结果为: ab abc abcd 方法二:将字符串部分拆分,该数组长度就是 limit 极限
String str = "ab#abc#abcd";
String[] strings = str.split("#",5);
for ( String s : strings) {
System.out.println(s);
}
结果为: ab abc abcd 注意:以参数进行分割 ,参数不存在 不分割 。 第二个参数 是分割的极限组数,分割的数组小于等于这个极限数组。 方法三:拆分 IP 地址 拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义 ,如分割Ip地址的点号就是一个特殊的运算符,要特殊处理。
String str = "192.168.1.1";
String[] strings = str.split("\\.");
for ( String s : strings) {
System.out.println(s);
}
结果: 192 168 1 1 🚭?注意事项:
- 字符"|","*","+“都得加上转义字符,前面加上”".
- 而如果是".",那么就得写成"\."
- 如果一个字符串中有多个分隔符,可以用
"|" 作为连字符.
7.5字符串截取
No | 方法名称 | 类型 | 描述 |
---|
1 | public String substring(int beginIndex) | 普通 | 从指定索引截取到结尾 | 2 | public String substring(int beginIndex, int endIndex) | 普通 | 截取部分内容 |
方法一:从指定索引截取到结尾
String str1 = "helloworld";
String str2 = str1.substring(5);
System.out.println(str2);
结果为:“world” 方法二:截取部分内容
String str1 = "helloworld";
String str2 = str1.substring(2,5);
System.out.println(str2);
结果为:“llo” 注意事项:
- 索引从0开始
- 截取的范围为左闭右开, 如substring(2, 5) 表示包含 02号下标的字符, 不包含 5 号下标
7.6其他操作
除了上述的一些操作字符串的方法,其实还有很多其他的方法,我们可以使用那个API文档查看方法的使用,下面以表格的形式列举其他一些常见的方法,没有演示它的使用,老铁们可以自己用用。🤓😎
No | 方法名称 | 类型 | 描述 |
---|
1 | public String trim() | 普通 | 去掉字符串中的左右空格,保留中间空格 | 2 | public String toUpperCase() | 普通 | 字符串转大写 | 3 | public String toLowerCase() | 普通 | 字符串转小写 | 4 | public native String intern() | 普通 | 字符串入池操作 | 5 | public int length() | 普通 | 取得字符串长度 | 6 | public boolean isEmpty() | 普通 | 判断字符串是否为空(空不是 null,而是长度为0) |
这里的方法就不一一展示示例了,如果大家能够把上述字符串的方法都记得的话,那么去做一些题目那真的可以说是更加轻松了。并且字符串的方法不止这些,大家可以去 Java 的 api 中去查看学习。
8. StringBuffer 和 StringBuilder
8.1String 和 StringBuffer 和StringBuilder的区别
👀🔅String 和 StringBuffer 和StringBuilder有相同也有不同,其实 StringBuffer 和StringBuilder就是String的plus版本,是对String的一些完善。StringBuffer 和 StringBuilde几乎没差别,我们先以StringBuffer 为例讨论前两者的不同,再讨论后两者的不同。
1.String 和 StringBuffer 都可以产生字符串,但产生的方式不一样,前者即可以通过直接赋值的方式产生,也可以通过实例化对象的方式产生。
StringBuffer sb = new StringBuffer("hello");
String sb2="hello";
String string =new String("hello");
2.在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer 类中需要更改为append()方法:
StringBuffer sb = new StringBuffer("hello");
sb.append("bit");
sb.append(1).append("!!!");
System.out.println(sb);
String s = "hello";
s = s + "bit";
3.除了append()方法外,StringBuffer也有一些String类没有的方法, 如字符串反转reverse()等。 4.String和StringBuffer最大的区别在于:String的内容无法修改,而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用StingBuffer。如之前的字符串的拼接: 这个代码之前说了它会产生很多的对象,我们它执行的汇编代码时是通过StingBuffer对其进行优化的,但还是产生了好多对象,这也说明了String的局限性,如果直接使用StingBuffer就只产生了一个对象,就完成了字符串的修改。
String str = "hello";
for(int i = 0;i < 10;i++) {
str = str + i;
}
System.out.println(str);
如果我们直接使用StringBuilder代码改成下面的代码不就高效和节省内存了嘛
String str = "hello";
StringBuilder sb = new StringBuilder();
sb.append(str);
for(int i = 0;i < 10;i++) {
sb.append(i);
}
str = sb.toString();
System.out.println(str);
我们也可以在结合内存布局图和源码截图大致理解下: 再进一步说就是String产生的对象不可以变,每次都要产生新对象,在新对象里进行拼接 StringBuilder产生的对象是可以变的,进行拼接时 ,我们在StringBuilder对象的字符串里添字符串,他每次返回的都是当前对象,它只new了一个对象,没动过常量池里面的字符串,没毛病吧!
8.2StringBuffer 和 StringBuilder的区别:
StringBuffer有关键字synchronized 修饰,采用同步处理,在进行当前的字符串拼接时不会受到其它拼接的的影响,等当前拼接完成后才进行其他拼接,属于线程安全操作;而StringBuilder未采用同步处理,会受到其他拼接的干扰,属于线程不安全操作 🔅**注意:**String和StringBuffer类不能直接转换。如果要想互相转换,可以采用如下原则:
- String变为StringBuffer:利用StringBuffer的构造方法或append()方法
- StringBuffer变为String:调用toString()方法。
如下代码:
以上两种方式 就是 String转换为StringBuilder、StringBuffer类型字符串。
String str = "abcd";
StringBuilder sb = new StringBuilder(str);
StringBuilder sb2 = new StringBuilder();
sb2.append(str);
StringBuilder、StringBuffer类型字符转换为String。(以StringBuilder为例)
StringBuilder sb2 = new StringBuilder("abc");
String s = sb2.toString();
System.out.println(s);
好了,本期有关String类的文章就介绍到这,下篇博文又见,感谢点赞的友友们。
|