String StringBuffer StringBuider

String

1.概述

String:字符串,使用一对""引起来表示。
1.String声明为final的,不可被继承
2.String实现了Serializable接口:表示字符串是支持序列化的。 实现了Comparable接口:表示String可以比较大小
4.通过字面量的方式赋值,则此时的字符串值直接存储在字符串常量池中。
5.字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。

3、String内部定义了private final char[] value用于存储字符串数据,
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 private final byte[] value;

为什么JDK9改变了结构?

节约空间
String类的当前实现将字符存储在char数组中,每个字符使用两个字节(16位 )。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数字符串对象只包含拉丁字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间将不会使用。

于是String不用char[] 来存储了,改成了byte [] 加上编码标记,节约了一些空间

同时基于String的数据结构,例如StringBuffer和StringBuilder也同样做了修改

2.String的不可变性

2.1 说明
1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
2.当对现的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3.当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
2.2 代码举例

String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";

System.out.println(s1 == s2);//false 比较s1和s2的地址值

System.out.println(s1);//hello
System.out.println(s2);//abc

System.out.println("*****************");

String s3 = "abc";
s3 += "def";
System.out.println(s3);//abcdef

String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println(s4);//abc
System.out.println(s5);//mbc

3.String实例化的不同方式

3.1 方式说明
方式一:通过字面量定义的方式
方式二:通过new + 构造器的方式
3.2 代码举例

//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");

System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false

3.3 面试题
String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
3.4 两种方式的区别
在这里插入图片描述

4. 字符串拼接方式赋值的对比

4.1 说明
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中

  1. +操作

编译器将 + 号处理成了StringBuilder.append()方法。也就是说,在运行期间,链接字符串的计算都是通过 创建StringBuilder对象,调用append()方法来完成的,而且是每一个链接字符串的表达式都要创建一个 StringBuilder对象。因此对于循环中反复执行字符串链接时,应该考虑直接使用StringBuilder来代替 + 链接,避免重复创建StringBuilder的性能开销。
并且,编译器有个优点:在编译期间会尽可能地优化代码,所以能由编译器完成的计算,就不会等到运行时计算,常量表达式的计算就是在编译期间完成的。例如 s=s1+s2 s的值在编译期就已经确定了。

4.2 代码举例

String s1 = "javaEE";
String s2 = "hadoop";

String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false

String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
****************************
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false

final String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true

5.常用方法:

int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,左闭右开

boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1

替换:
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

//正则表达式
匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

不少方法看起来是可以改变String对象的,如replace()、replaceAll()、substring()等。我们以substring()为例,看一下源码:

public String substring(int beginIndex, int endIndex) {
        //........
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

从源码可以看出,如果不是切割整个字符串的话,就会新建一个对象。也就是说,只要与原字符串不相等,就会新建一个String对象。所以并未改变原字符串

6与其他结构之间的转换

1 与字符数组之间的转换
String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器/String.valueOf()(底层调用string的构造器),不能用 char.toString 返回的是地址

 String s1="kkwy";
        s1.toCharArray();
        char[] c={'a','v'};
        String s = c.toString();

2 与基本数据类型、包装类之间的转换
String --> 基本数据类型、包装类 :调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)(底层调用相应包装类的toString方法)

3、与字节数组之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
注意:
解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。

@Test
public void test3() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
    System.out.println(Arrays.toString(bytes));

    byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
    System.out.println(Arrays.toString(gbks));

    System.out.println("******************");

    String str2 = new String(bytes);//使用默认的字符集,进行解码。
    System.out.println(str2);

    String str3 = new String(gbks);
    System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!


    String str4 = new String(gbks, "gbk");
    System.out.println(str4);//没出现乱码。原因:编码集和解码集一致!


}
[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67] 
-28, -72, -83表示‘中’ -27, -101, -67表示’国‘
[97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
******************
abc123中国
abc123�й�
abc123中国

7、intern():

返回字符串对象的规范化表示形式。
一个初始为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

注意:
虽然String.intern()的返回值永远等于字符串常量。但这并不代表在系统的每时每刻,相同的字符串的intern()返回都会是一样的(虽然在95%以上的情况下,都是相同的)。
因为存在这么一种可能:在一次intern()调用之后,该字符串在某一个时刻被回收,之后,再进行一次intern()调用,那么字面量相同的字符串重新被加入常量池,但是引用位置已经不同。

8、hashcode()方法

String也是遵守equals的标准的,也就是 s.equals(s1)为true,则s.hashCode()==s1.hashCode()也为true。此处并不关注eqauls方法,而是讲解 hashCode()方法,String.hashCode()有点意思,而且在面试中也可能被问到。先来看一下代码:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

为什么要选31作为乘数呢?
一般有如下两个原因:

  1. 31是一个不大不小的质数,是作为 hashCode
    乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了 31呢?请看第二个原因。

  2. 31可以被 JVM 优化,31 * i = (i << 5) - i。

StringBuffer(安全)、StringBuilder

1、改变字符串的常用方法

  sb.append(数据); //将指定数据加在容器末尾
  sb.insert(index ,数据);
  sb.delete(start ,end);  //删除start到end的字符内
  sb.replace(start,end,str1)//将从start开始到end的字符串替换为str1;
  sb.reverse();//将sb倒序
  sb.deleteCharAt(int index);//删除index位置的元素

2、与String的转换
String -->StringBuffer、StringBuilder:
调用StringBuffer、StringBuilder构造器

StringBuffer、StringBuilder -->String:
①调用String构造器;

②StringBuffer、StringBuilder的toString()
底层调用的String构造器

public String toString() {
        return new String(value, 0, count);
    }

3、 扩容问题:
如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。

对于三者使用的总结:

操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

进阶

jvm——13 StringTable

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 之间有什么区别? String 是一个不可变的字符串,一旦创建就不能修改。StringBufferStringBuilder 都是可变的字符串,因为它们的内部实现使用了可变长度的数组来存储字符串,可以通过 append() 方法实现字符串的修改。StringBuffer 是线程安全的,而 StringBuilder 则不是。如果在单线程环境下,建议使用 StringBuilder,因为它的效率更高。 ### 回答2: StringStringBufferStringBuilder都是Java中用来处理字符串的类。 String是不可变的字符串类,一旦创建就不可修改。每次对String对象进行修改时,实际上是创建了一个新的String对象。这会导致频繁的对象创建和销毁,影响性能。因此,在频繁修改字符串的场景下,使用String可能不是最佳选择。 StringBufferStringBuilder都是可变的字符串类。它们可以对字符串进行增删改操作,而不会创建新的对象。两者的区别在于StringBuffer是线程安全的,而StringBuilder是非线程安全的。因为StringBuffer的方法都是同步的,所以在多线程的环境下使用StringBuffer可以保证数据的一致性,但性能相对较低。StringBuilder没有同步方法,所以在单线程环境下使用StringBuilder可以获得更好的性能表现。 总结来说,如果程序中有多个线程同时操作字符串,应该使用StringBuffer来保证线程安全。如果程序是单线程环境,就可以使用StringBuilder来获得更好的性能。而对于不需要频繁修改的字符串,使用String即可。这三个类各有特点,在不同场景下选择适合的类能够提升程序的性能和效率。 ### 回答3: StringStringBufferStringBuilder是Java中用来处理字符串的类。 String是一个不可变的类,也就是说它的值在创建后是不可更改的。每次对String的操作都会创建一个新的String对象,这样会产生大量的临时对象,影响性能。如果需要频繁对字符串进行操作,建议使用StringBufferStringBuilder。 StringBuffer是一个线程安全的可变类,适用于多线程环境下的字符串操作。它的方法在内部使用了synchronized关键字来保证线程安全,但在性能方面会有一些损失。 StringBuilder是一个线程不安全的可变类,适用于单线程环境下的字符串操作。它的方法没有使用synchronized关键字,因此在性能方面比StringBuffer更高效。 在实际应用中,如果需要频繁对字符串进行操作并且需要线程安全,可以使用StringBuffer;如果在单线程环境下操作字符串,可以使用StringBuilder来提高性能。 总之,String类适合处理不需要改变的字符串,StringBuffer适合处理多线程情况下的可变字符串,而StringBuilder适合在单线程情况下的可变字符串处理。根据具体的需求选择适合的类可以提高程序的性能和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值