JavaのString类这一篇就够了(包含StringBuffer_Builder)

news2025/2/26 20:04:18

1.🥗String类简介

在我们写代码的时候,String总是充斥着前前后后。

但你会不会经常力不从心,

“这个*** 字符串怎么** 转换不成功啊”

“*** 这个字符串到底是常量还是对象啊”

“这*** 字符串内存结构到底* * * 是什么啊”

“为啥我的字符串数组莫名其妙显示空指针异常啊”

“字符串常量和字符串对象应该怎么比较啊”

“这字符串数据不都是good嘛,怎么比一比又是false了***

“ 字符串怎么转化成int啊啊“

”String类为啥是final修饰的捏?“

”String?StringBuffer??StringBuilder???“

A:”什么?!String不是基本数据类型??它还有什么是我不知道的?“

B:”楼上的,你猜一猜String能被继承嘛?“

A:".....****"

哈哈,知识点看起来又多又杂,但我们好好缕一缕,它还是很好理解的。
在C语言中我们涉及到过字符串,但是在C语言中要表示字符串只能用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方法不符合面向对象的思想,而字符串的应用又非常广泛,从上面的对话中就可以发现,哈哈哈。
因此Java语言专门提供了String类
我们不论是在写代码的时候,还是在校招的笔试当中,字符串都是常客。
面试也会经常问到关于String的问题

面试官:来来小伙子,简单背一下String类的源码呢? 小伙子:...我* 你****,(直接丢offer而去)

那么我们就带着对它的一些问题继续往下看

2.🍗 String常用方法和性质

2.1 🥩 字符串的构造

String类提供给我们的构造方式很多,最常见的有以下三种

//使用常量串来构造
String s1 = "hello Gremmie";
System.out.println(s1);

//直接new出String对象
String s2 = new String("hello 葛玉礼");
System.out.println(s2);

//使用字符数组进行构造
char[] array = {'h','e','l','l','o',' ','玉','米'};
String s3 = new String(array);
System.out.println(s3);

🍲 [注意]:

  1. String是引用类型,内部其实并不存储字符串本身,在String类的实现源码中,String类实例变量是这样的👇
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {

    @Stable
    private final byte[] value;
//字符串实际上就存储在这个用final修饰的byte数组中

    private final byte coder;

    /** Cache the hash code for the string */
    private int hash; // Default to 0


/**
 * @author Gremmie102
 * @date 2022/4/21 15:49
 * @purpose : 比较字符串引用
 */
public class StringTestDemo1 {
    public static void main(String[] args) {
        //s1和s2引用的是不同的对象,s1和s3引用的是同一个对象
        String s1 = new String("hello");
        String s2 = new String("world");
        String s3 = s1;

        System.out.println(s1.length());
        //获取字符串长度--输出5
        System.out.println(s1.isEmpty());
        //判断字符串长度是否为0,即是否为空
    }
}

  1. 在Java中用双引号""引起来的也是String类型对象

//打印"hello"字符串(String对象)的长度 System.out.println("hello".length);

2.2 🍙String对象的比较

字符串的比较是我们常用的操作,比如:字符串排序

  1. ==比较是否引用同一个对象

这里要注意的是,对于内置类型(基础数据),==比较的是变量中的值,对于引用类型==比较的是引用中的地址

public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = 10;

        //对于基本类型变量,==比较两个变量中存储的值是否相同
        System.out.println(a==b);//false
        System.out.println(a==c);//true

        //对于引用型变量,==比较两个引用变量引用的是否为同一个对象
        String s1 = new String("hello");
        String s2 = new String("hello");
        String s3 = new String("world");
        String s4 = s1;
        System.out.println(s1==s2);//false
        System.out.println(s2==s3);//false
        System.out.println(s1==s4);//true
    }

  1. boolean equals(Object object)

按照字符大小的顺序比较
String重写了父类Object中的equals方法,Object中equals默认会按照==来比较,String重写equals方法之后,语法规则为:s1.equals(s2)
我们看看源码

用代码来给大家演示解释一下👇

 public boolean equals(Object anObject){
        //1.先检测this和anObject是否为同一个对象比较,如果是的化则返回true
        if(this == anObject){
            return true;
        }

        //2.检测anObject是否为String类型的对象,如果是的话那就继续比较
        //不是的话那就返回false
        if (anObject instanceof String){
            String anotherString = (String)anObject;
            int n = value.length;

            //3.判断调用对象的this和anObject两个字符串的长度是否相同
            if (n == anotherString.value.length){
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;

                //4.按照字典顺序,从前往后逐个字符进行比较
                while(n-- != 0){
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

然后我们再写一个main方法来实验一下

    public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        String s3 = new String("HELLO");

        //s1,s2,s3引用的是三个不同的对象,因此==的比较结果都是false
        System.out.println(s1 == s2);//false
        System.out.println(s1 == s3);//false

        //equals比较:String对象中的逐个字符
        //虽然s1和s2引用的不是同一个对象,但是两个对象中放置的内容,所以输出true
        //s1与s3引用的不是同一个对象,而且两个对象中内容也不同,因此输出false
        System.out.println(s1.equals(s2));//true
        System.out.println(s1.equals(s3));//false

    }

  1. int compareTo(String s)方法:按照字典序进行比较

与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。
具体比较方法为:

  1. 先按照字典顺序大小比较,如果出现了不等的字符,则直接返回这两个字符的大小差值
  2. 如果前k个字符相等(k为两个字符长度最小值)返回值为两个字符串的长度差值

源码如下👇

测试代码如下👇

    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = new String("ac");
        String s3 = new String("abc");
        String s4 = new String("abcdef");
        System.out.println(s1.compareTo(s2));//不同,输出字符差值-1
        System.out.println(s1.compareTo(s3));//相同输出0
        System.out.println(s1.compareTo(s4));//前k个字符完全相同,输出长度差值-3
    }

  1. int compareToIgnoreCase(String str)方法:与compareTo方式相同,但是忽略大小写比较

源码👇

public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = new String("ac");
        String s3 = new String("Abc");
        String s4 = new String("abcdef");
        System.out.println(s1.compareToIgnoreCase(s2));//不同,输出字符差值-1
        System.out.println(s1.compareToIgnoreCase(s3));//相同,输出0
        System.out.println(s1.compareToIgnoreCase(s4));//前k个字符完全相同,输出长度差-3

    }

2.3 🍱 字符串查找

这是一个超级实用的功能,String类中提供了一些常用的查找方法:

**方法 **** 功能 **
char charAt(int index)返回index位置上字符,如果index为负数或者越界,抛出 IndexOutOfBoundsException异常
int indexOf(int ch)返回ch第一次出现的位置,没有返回-1
int indexOf(int ch, int fromIndex)从fromIndex位置开始找ch第一次出现的位置,没有返回-1
int indexOf(String str)返回str第一次出现的位置,没有返回-1
int indexOf(String str, int fromIndex)从fromIndex位置开始找str第一次出现的位置,没有返回-1
int lastIndexOf(int ch)从后往前找,返回ch第一次出现的位置,没有返回-1
int lastIndexOf(int ch, int fromIndex)从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返 回-1
int lastIndexOf(String str)从后往前找,返回str第一次出现的位置,没有返回-1
int lastIndexOf(String str, int fromIndex)从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返回-1

这里大家要注意的是,上面表格中有 int ch 的参数,是char类型传进来强转换成int值,以此来方便程序进行,并不是说这个字符就是一个数哦

public static void main(String[] args) {
String s = "aaabbbcccaaabbbccc";
System.out.println(s.charAt(3)); // 'b'
System.out.println(s.indexOf('c')); // 6
System.out.println(s.indexOf('c', 10)); // 15
System.out.println(s.indexOf("bbb")); // 3
System.out.println(s.indexOf("bbb", 10)); // 12
System.out.println(s.lastIndexOf('c')); // 17
System.out.println(s.lastIndexOf('c', 10)); // 8
System.out.println(s.lastIndexOf("bbb")); // 12
System.out.println(s.lastIndexOf("bbb", 10)); // 3

2.4 🥡 字符串的转化

我们在做题的时候,字符串的转化也是非常重要的,主要有以下几个

  1. 数值和字符串转化
public static void main(String[] args) {
    // 数字转字符串
    String s1 = String.valueOf(1234);
    String s2 = String.valueOf(12.34);
    String s3 = String.valueOf(true);
    String s4 = String.valueOf(new Student("Hanmeimei", 18));
    System.out.println(s1);
    System.out.println(s2);
    System.out.println(s3);
    System.out.println(s4);
    System.out.println("=================================");
    // 字符串转数字
    // 注意:Integer、Double等是Java中的包装类型,这个后面会讲到
    int data1 = Integer.parseInt("1234");
    double data2 = Double.parseDouble("12.34");
    System.out.println(data1);
    System.out.println(data2);
}

  1. 大小写转换
public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "HELLO";
    // 小写转大写
    System.out.println(s1.toUpperCase());
    // 大写转小写
    System.out.println(s2.toLowerCase());
}

  1. 字符串转数组
public static void main(String[] args) {
String s = "hello";
// 字符串转数组
char[] ch = s.toCharArray();
for (int i = 0; i < ch.length; i++) {
System.out.print(ch[i]);
}
System.out.println();
// 数组转字符串
String s2 = new String(ch);
System.out.println(s2);
}

个人觉得这个方法是非常实用的!!

  1. String格式化输入
public static void main(String[] args) {
String s = String.format("%d-%d-%d", 2019, 9,14);
System.out.println(s);
}

2.5 🥟 字符串替换

那么在做一些算法题目时,我们需要替换字符串中的内容。
好家伙,直接上手利用toCharArray然后几个循环
没必要!
我们使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:

方法功能
String replaceAll(String regex, String replacement)替换所有的指定内容
String replaceFirst(String regex, String replacement)替换首个内容
String str = "helloworld" ;
System.out.println(str.replaceAll("l", "_"));
System.out.println(str.replaceFirst("l", "_"));
//输出:
//he__owor_d
//he_loworld

🌭 【注意】:由于字符串是不可变的对象,替换不修改当前字符串,而是产生一个新的字符串

2.6 🫕 字符串的拆分

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

方法功能
String[] split(String regex)将字符串全部拆分
String[] split(String regex,int limit)将字符串以指定的格式,拆分为limit组

代码1👇实现字符串的拆分

String str = "hello world hello Gremmie" ;
String[] result = str.split(" ") ; // 按照空格拆分
for(String s: result){
    System.out.println(s);
}

代码2 👇字符串的部分拆分

public static void main(String[] args) {
        String str = "hello world hello Gremmie" ;
        String[] result = str.split(" ",2) ;
        for(String s: result) {
            System.out.println(s);
        }
    }

运行结果👇

拆分是特别常用的操作,一定要牢牢记住。 有一些特殊字符作为分隔符的话可能无法正确区分,需要加上转移符号

代码3👇拆分IP地址

    public static void main(String[] args) {
        String str = "106.14.57.10" ;
        String[] result = str.split("\\.") ;
        for(String s: result) {
            System.out.println(s);
        }
    }

运行结果👇

🍫 【注意事项】:

  1. 字符"|","","+"*都得加上转义字符,前面加上 "" .
  2. 而如果是 "" ,那么就得写成 "\" .
  3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.

代码4👇多次拆分

public static void main(String[] args) {
        String str = "name=Gremmie&age=19" ;
        String[] result = str.split("&") ;
        for (int i = 0; i < result.length; i++) {
            String[] temp = result[i].split("=") ;
            System.out.println(temp[0]+" = "+temp[1]);
        }
    }

运行结果👇

2.7 🥣 字符串的截取

现在我们遇到一种情况,你已经知道了索引,想要截取一段字符串 这个时候,你是不是又想上手循环了? 慢着!substring可以满足你!😋

方法功能
String substring(int beginIndex)从指定索引截取到结尾
String substring(int beginIndex, int endIndex)截取部分的内容

代码👇字符串的截取

public static void main(String[] args) {
        String str = "helloGremmie" ;
        System.out.println(str.substring(5));//是从下标为5截到最后的
        System.out.println(str.substring(0, 5));//下标索引范围左闭右开,[0,5)
    }

🍟 注意事项:

  1. 索引要从0开始
  2. 要注意前闭后开的规定,substring(0,5)表示包含0号下标的字符,不包含5号下标

2.8 🍰 字符串前后的空白删除

String中的trim()方法用来去掉字符串中的左右两个空格,保留中间的空格

代码示例👇观察trim()方法的使用

public static void main(String[] args) {
        String str = "   hello Gremmie  " ;
        System.out.println("["+str+"]");
        System.out.println("["+str.trim()+"]");
    }

运行结果👇

trim 会去掉字符串开头和结尾的空白字符(空格, 换行, 制表符等).

2.9🥫字符串常量池

字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。 如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。 在JDK6.0及之前版本,字符串常量池存放在方法区中在JDK7.0版本以后,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了

2.9.1 🥗 创建对象(一)

我们先来看看下面这段代码,创建String对象的方式是相同的嘛?

public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "hello";
    String s3 = new String("hello");
    String s4 = new String("hello");
    System.out.println(s1 == s2); // true
    System.out.println(s1 == s3); // false
    System.out.println(s3 == s4); // false
}

上述程序创建方式类似,为什么s1和s2引用的是同一个对象,而s3和s4不是呢? 在Java程序中,类似于:1, 2, 3,3.14,“hello”等字面类型的常量经常频繁使用,为了使程序的运行速度更快、 更节省内存,Java为8种基本数据类型和String类都提供了常量池。

在系统设计中,我们尝尝会使用到”池”的概念。Eg:数据库连接池,socket连接池,线程池,组件队列。”池”可以节省对象重复创建和初始化所耗费的时间。对那些被系统频繁请求和使用的对象,使用此机制可以提高系统运行性能。 ”池”是一种”以空间换时间”的做法,我们在内存中保存一系列整装待命的对象,供人随时差遣。与系统效率相比,这些对象所占用的内存空间太微不足道了。

🍳class文件常量池

我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种 字面量 (Literal)和 符号引用 (Symbolic References),每个class文件都有一个class常量池。 其中 字面量 包括:

  1. 文本字符串
  2. 2.八种基本类型的值
  3. 3.被声明为final的常量等;

符号引用 包括:

  1. 类和方法的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符。

🥯运行时常量池

运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,是方法区的一部分。不同之处是:它的字面量可以动态的添加(String类的intern()),符号引用可以被解析为直接引用。 JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们下面要说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

2.9.2 🐳 字符串常量池(StringTable)

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;在JDK7.0中,StringTable的长度可以通过参数指定。

JDK版本字符串常量池位置大小设置
Java6方法区固定:1009
Java7可设置,无大小限制,默认60013
Java8可设置,有限制,最小1009

总结🍔 :

  1. 单独使用””引号创建的字符串都是常量,编译期就已经确定存储到Constant Pool中
  2. 使用new String(“”)创建的对象会存储到heap中,是运行期新创建的;
  3. 使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中,String pool中存有“aaaa”;但不会存有“aa”。
  4. 使用包含变量的字符串连接符如”aa” + s1创建的对象是运行期才创建的,存储在heap中;只要s1是变量,不论s1指向池中的字符串对象还是堆中的字符串对象,运行期s1 + “aa”操作实际上是编译器创建了StringBuilder对象进行了append操作后通过toString()返回了一个字符串对象存在heap上。
  5. String s2 = “aa” + s1; String s3 = “aa” + s1; 这种情况,虽然s2,s3都是指向了使用包含变量的字符串连接符如”aa” + s1创建的存在堆上的对象,并且都是s1 + “aa”。但是却指向两个不同的对象,两行代码实际上在堆上new出了两个StringBuilder对象来进行append操作。在Thinking in java一书中285页的例子也可以说明。
  6. 对于final String s2 = “111”。s2是一个用final修饰的变量,在编译期已知,在运行s2+”aa”时直接用常量“111”来代替s2。所以s2+”aa”等效于“111”+ “aa”。在编译期就已经生成的字符串对象“111aa”存放在常量池中。

🍔 注意:

  1. 在JVM中字符串常量池只有一份,是全局共享的
  2. 刚开始字符串常量池是空的,随着程序不断运行,字符串常量池中元素会越来越多
  3. 当类加载时,字节码文件中的常量池也被加载到JVM中,称为运行时常量池,同时会将其中的字符串常量保存在字符串常量池中
  4. 字符创常量池中的内容:一部分来自运行时常量池,一部分来自程序动态添加

2.9.3 🦐 创建对象(二)

创建一个字符串的过程👇

String str = new String(“abc") 首先定义一个str的String类型的引用并存放在栈中 先在字符串常量池中找到该常量是否存在 如果存在则创建一个引用即可,则在字符串常量池中创建一个内容为"abc"的字符串对象。 执行new操作,在堆中创建一个指定的对象"abc",这里堆的对象是字符串常量池“abc”对象的一个拷贝对象 让str指向堆中“abc”这个对象(也就是存储这个对象的在堆中的地址)

 

 

在字节码文件加载的时候,先要将.Class文件中的常量池加载到内存中称为运行时常量池,此时也会将"hello"字符串保存到字符串常量池当中

 

说明:

  1. 在字节码文件加载的时候,"hello"和"world"就已经创建好了,并保存在字符串常量池当中
  2. 上图是代表直接用字符串常量进行赋值,即:
public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "hello";
    System.out.println(s1 == s2); // true
}

  1. 当使用String s1 = "hello";创建对象的时候,先要在字符串常量池当中找,如果找到就将该字符串引用赋值给s1

🥨 intern方法

intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。

public static void main(String[] args) {
    char[] ch = new char[]{'a', 'b', 'c'};
    String s1 = new String(ch); // s1对象并不在常量池中
    //s1.intern(); //调用之后,会将s1对象的引用放入到常量池中
    String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
    System.out.println(s1 == s2);
}
// 输出false
// 用intern之后,就会输出true

不同版本的Java会有不同的Intern来实现

🍜 面试题

请解释String类当中两种对象实例化的区别 JDK1.8中

  1. String str = "hello"

只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象

2. String str = new String("hello")

会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟的String对象赋值。

3. String str = new String(new char[]{'h', 'e', 'l', 'l', 'o'})

先在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到String对象中

2.10 🦑 字符串的不可变性

String是一种不可变对象. 字符串中的内容是不可改变的。

不可变性的体现在: 当对字符串重新赋值时我们需要重新制定一个内存区域,然后才能赋值,不能对原有的value进行赋值(也就是我们不能改变原有的value) 这里其实就是我们每当有一个对象就有一次给value赋值的机会,这次机会用了之后,也就是赋值之后,就不可以再改变了 所以我们在这里说,字符串的值一旦给定就不可以再修改,一旦尝试修改,就会创建一个新的字符串对象,让这个新的字符串去接收你想要成为的值。

  1. String类在设计时就是不可改变的,String类实现描述中已经说明了

String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:

  1. String类被final修饰,表明该类不能被继承
  2. value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。
  1. 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象

它是需要新创建一个对象再对其进行修改的,并不是在原有的基础上进行修改

有些人说:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变。 这种说法是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。 final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。

public static void main(String[] args) {
    final int array[] = {1,2,3,4,5};
    array[0] = 100;
    System.out.println(Arrays.toString(array));
// array = new int[]{4,5,6}; // 编译报错:Error:(19, 9) java: 无法为最终变量array分配值
}

为什么 String 要涉及成不可变的?(不可变对象的好处是什么?)

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.

2.11 🐙 字符串的修改

**注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率 非常低下。 **

比如这么做👇

public static void main(String[] args) {
    String s = "hello";
    s += " world";
    System.out.println(s); // 输出:hello world
}

这么做中间创建了很多临时对象,非常浪费资源
那么我们应该怎么做呢?
在对String类进行修改时,效率是非常慢的,因此:尽量避免对String的直接需要,如果要修改建议尽量 使用StringBuffer或者StringBuilder。

3. 🦞 Java StringBuffer 和 StringBuilder 类

当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
[图片上传失败...(image-b74cd7-1665193486312)]

在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。 StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。

那么StringBuilder在append的时候具体是怎么做的呢?

 public static void main(String args[]){
        StringBuilder sb = new StringBuilder(10);
        sb.append("Runoob..");
        System.out.println(sb);  
        sb.append("!");
        System.out.println(sb); 
        sb.insert(8, "Java");
        System.out.println(sb); 
        sb.delete(5,8);
        System.out.println(sb);  
    }

运行结果为: Runoob.. Runoob..! Runoob..Java! RunooJava!

而如果我们要求线程安全的话,就必须使用StringBuffer类了

public class Test{
  public static void main(String args[]){
    StringBuffer sBuffer = new StringBuffer("Gremmie:");
    sBuffer.append("http://");
    sBuffer.append("106.14.57.");
    sBuffer.append("10/");
    System.out.println(sBuffer);  
  }
}

运行结果为👇

那么总结一下StringBuilder常用的一样方法:

方法说明
StringBuff append(String str)在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、 double、float、int、long、Object、String、StringBuff的变量
char charAt(int index)获取index位置的字符
int length()获取字符串的长度
int capacity()获取底层保存字符串空间总的大小
void ensureCapacity(int mininmumCapacity)扩容
void setCharAt(int index, char ch)将index位置的字符设置为ch
int indexOf(String str)返回str第一次出现的位置
int indexOf(String str, int fromIndex)从fromIndex位置开始查找str第一次出现的位置
int lastIndexOf(String str)返回最后一次出现str的位置
int lastIndexOf(String str, int fromIndex)从fromIndex位置开始找str最后一次出现的位置
StringBuff insert(int offset, String str)在offset位置插入:八种基类类型 & String类型 & Object类型数据
StringBuffer deleteCharAt(int index)删除index位置字符
StringBuffer delete(int start, int end)删除[start, end)区间内的字符
StringBuffer replace(int start, int end, String str )将[start, end)位置的字符替换为str
String substring(int start)从start开始一直到末尾的字符以String的方式返回
String substring(int start,int end)将[start, end)范围内的字符以String的方式返回
StringBuffer reverse()反转字符串
String toString()将所有字符按照String的方式返回
public static void main(String[] args) {
    StringBuilder sb1 = new StringBuilder("hello");
    StringBuilder sb2 = sb1;
    // 追加:即尾插-->字符、字符串、整形数字
    sb1.append(' '); // hello
    sb1.append("world"); // hello world
    sb1.append(123); // hello world123
    System.out.println(sb1); // hello world123
    System.out.println(sb1 == sb2); // true
    System.out.println(sb1.charAt(0)); // 获取0号位上的字符 h
    System.out.println(sb1.length()); // 获取字符串的有效长度14
    System.out.println(sb1.capacity()); // 获取底层数组的总大小
    sb1.setCharAt(0, 'H'); // 设置任意位置的字符 Hello world123
    sb1.insert(0, "Hello world!!!"); // Hello world!!!Hello world123
    System.out.println(sb1);
    System.out.println(sb1.indexOf("Hello")); // 获取Hello第一次出现的位置
    System.out.println(sb1.lastIndexOf("hello")); // 获取hello最后一次出现的位置
    sb1.deleteCharAt(0); // 删除首字符
    sb1.delete(0,5); // 删除[0, 5)范围内的字符

    String str = sb1.substring(0, 5); // 截取[0, 5)区间中的字符以String的方式返回
    System.out.println(str);
    sb1.reverse(); // 字符串逆转
    str = sb1.toString(); // 将StringBuffer以String的方式返回
    System.out.println(str);
}

从上述例子可以看出:String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可 以修改。频繁修改字符串的情况考虑使用StringBuilder

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

  • String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
  • StringBuilder变为String: 调用toString()方法

🍜 面试题

String、StringBuffer、StringBuilder的区别

  1. String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  2. StringBuffer与StringBuilder大部分功能是相似的
  3. StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
  1. 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】 String str = new String("ab"); // 会创建多少个对象 String str = new String("a") + new String("b"); // 会创建多少个对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/347477.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 在二叉树中增加一行

623. 在二叉树中增加一行中等给定一个二叉树的根 root 和两个整数 val 和 depth &#xff0c;在给定的深度 depth 处添加一个值为 val 的节点行。注意&#xff0c;根节点 root 位于深度 1 。加法规则如下:给定整数 depth&#xff0c;对于深度为 depth - 1 的每个非空树节点 cur…

LeetCode 83. 删除排序链表中的重复元素

原题链接 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 给定一个已排序的链表的头 headheadhead &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;…

Springboot+ElasticSearch构建博客检索系统-学习笔记01

课程简介&#xff1a;从实际需求分析开始&#xff0c;打造个人博客检索系统。内容涵盖&#xff1a;ES安装、ES基本概念和数据类型、Mysql到ES数据同步、SpringBoot操作ES。通过本课&#xff0c;让学员对ES有一个初步认识&#xff0c;理解ES的一些适用场景&#xff0c;以及如何使…

C语言实例|编写C程序在控制台打印余弦曲线

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

30岁了,说几句大实话

是的&#xff0c;我 30 岁了&#xff0c;还是周岁。 就在这上个月末&#xff0c;我度过了自己 30 岁的生日。 都说三十而立&#xff0c;要对自己有一个正确的认识&#xff0c;明确自己以后想做什么&#xff0c;能做什么。 想想时间&#xff0c;过得真快。 过五关斩六将&…

基于圆展开自适应三边测量算法的室内定位

基于圆展开自适应三边测量算法的室内定位 具有无线通信功能的移动设备的日益普及刺激了室内定位服务的增长。室内定位用于实时定位设备位置&#xff0c;方便访问。然而&#xff0c;由于大量障碍物&#xff0c;与室外定位相比&#xff0c;室内定位具有挑战性。全球定位系统非常适…

【MyBatis】| MyBatis分页插件PageHelper

目录 一&#xff1a;MyBatis使⽤PageHelper 1. limit分⻚ 2. PageHelper插件 一&#xff1a;MyBatis使⽤PageHelper 1. limit分⻚ &#xff08;1&#xff09;概念&#xff1a; ①页码&#xff1a;pageNum&#xff08;用户会发送请求&#xff0c;携带页码pageNum给服务器&am…

Pom.xml详解

目录 1、Maven的下载安装 2、什么是pom&#xff1f; 3、较完整的pom元素 4、默认生成Maven工程的pom内容 5、自定义的属性变量 6、依赖管理 6.1、整体依赖关系列表 6.2、依赖关系的传递性 6.3、依赖传递可能造成的问题 6.3.1、scope依赖范围 6.3.2、依赖调节 6.3.3…

【分享】如何通过集简云将ChatGPT人工智能接入到我们的飞书机器人中?

ChatGPT是一款非常强大的人工智能产品&#xff0c;可以有创造性的回复和创作文字&#xff0c;图片&#xff0c;适用于很多办公场景。这篇文章将介绍如何将ChatGPT接入到我们的飞书机器人中。 在集简云中的ChatGPT应用 目前集简云提供了两个ChatGPT应用: OpenAI(ChatGPT&#x…

EdgeCOM嵌入式边缘计算机的参数配置

EdgeCOM嵌入式边缘计算机的参数配置&#xff1a; 下面以 eth0 为例进行命令说明。 在 Linux 系统下&#xff0c;使用 ifconfig 命令可以显示或配置网络设备&#xff0c;使用 ethtool 查询及 设置网卡参数。 设置 IP 地址&#xff0c;查看当前网卡详情&#xff1a; rootfl-imx6u…

数字源表在二极管特性参数分析中的应用

分立器件特性参数测试是对待测器件&#xff08;DUT&#xff09;施加电压或电流&#xff0c;然后测试其对激励做出的响应&#xff0c;通常分立器件特性参数测试需要几台仪器完成&#xff0c;如数字万用表、 电压源、电流源等。然而由数台仪器组成的系统需要分别进行编程、同步、…

ShardingSphere-Proxy5 根据时间分表

0、软件版本 ShardingSphere-Proxy&#xff1a; 5.2.0 MySQL&#xff1a; 8.0.30 系统&#xff1a; win10 1、ShardingSphere-Proxy下载 我们可以在 官网 找到最新版ShardingSphere-Proxy下载&#xff0c;也可以在ShardingSphere仓库中下载 2、ShardingSphere-Proxy配置 …

MySQL存储引擎、事务、索引 | 老杜

目录 一、存储引擎 1、什么是存储引擎 2、怎么设存储引擎 3、常用存储引擎 MyISAM存储引擎 InnoDB存储引擎 MEMORY存储引擎 二、事务 1、什么是事务 2、怎么做到同时成功同时失败 3、怎么提交和回滚呢 4、事务4个特性 A&#xff1a;原子性 C&#xff1a;一致性 …

适用于媒体行业的管理数据解决方案—— StorageGRID Webscale

主要优势 1、降低媒体存储库的复杂性 • 借助真正的全局命名空间在全球范围内存储数据并在本地进行访问。 • 实施纠删编码和远程复制策略。 • 通过单一管理平台管理策略和监控存储。 2、优化媒体工作流 • 确认内容在合适的时间处于合适的位置。 • 支持应用程序直接通过 A…

研报精选230215

目录 【行业230215开源证券】电力设备行业投资策略&#xff1a;特高压建设有望迎来高峰期&#xff0c;解决清洁能源跨区互济瓶颈【行业230215浙商证券】计算机行业【AIGC算力时代系列报告】&#xff1a;ChatGPT研究框架【个股230215国信证券_公牛集团】民用电工行业领军者&…

SpringBoot08:Shiro

什么是Shiro&#xff1f; 一个Java的安全&#xff08;权限&#xff09;框架&#xff0c;可以完成认证、授权、加密、会话管理、Web集成、缓存等 下载地址&#xff1a;Apache Shiro | Simple. Java. Security. 快速启动 先在官网找到入门案例&#xff1a;shiro/samples/quick…

leaflet 鼠标点击弹出popup,显示明星名片(068)

第068个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中显示名人信息,这里给marker绑定popup,同时给每一个icon设定不同的图片。bindPopup(popup) 开始时不显示弹出框。而bindPopup(popup) .openOn(this.map); 开始时候会弹出一个pop 直接复制下面的 vue+leaf…

不可错过的SQL优化干货分享-sql优化、索引使用

本文是向大家介绍在sql调优的几个操作步骤&#xff0c;它能够在日常遇到慢sql时有分析优化思路&#xff0c;能够让开发者更好的了解sql执行的顺序和原理。一、前言在日常开发中&#xff0c;我们经常遇到一些数据库相关的问题&#xff0c;比方说&#xff1a;SQL已经走了索引了&a…

java的双亲委派模型-附源码分析

1、类加载器 1.1 类加载的概念 要了解双亲委派模型&#xff0c;首先我们需要知道java的类加载器。所谓类加载器就是通过一个类的全限定名来获取描述此类的二进制字节流&#xff0c;然后把这个字节流加载到虚拟机中&#xff0c;获取响应的java.lang.Class类的一个实例。我们把实…

边界层气象学期末复习笔记

边界层气象学期末复习笔记 什么是边界层 广义上的边界层是&#xff1a;气体流动于平板上方&#xff0c;平板表面的摩擦力和垂直速度切变产生的流体内摩擦力阻滞了固体边界处的气体流动&#xff0c;这样一个流速减少&#xff0c;并低于自由气流速度的区域称为边界层 在厚度较少…