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

Java基础面试题 目录 解释下什么是面向对象?面向对象和面向过程的区别? 面向对象的三大特性? JDK、JRE、JVM三者之间的关系? 重载和重写的区别? Java中是否可以重写一个private或者static方法? 构造方法有哪些特征? 在Java中定义一个不做事且没有参数的构造方法有什么用? Java中创建对象的几种方式? 抽象类和接口有什么区别? 静态变量和实例变量的区别? short s1 = 1; s1 = s1 + 1;有没有错? short s1 = 1; s1 += 1; 有没有错? 阅读详情

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的方式返回

从上述例子可以看出: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"); // 会创建多少个对象
Java中的final变量 final修饰变量 final修饰方法 final修饰类 final和String的关系 在开发的过程中,经常会使用String类来处理字符串相关的数据,但今天去看String源代码时,发现这个类是用final修饰的,于是,决定去探究inal关键字。final关键字可以修改类,方法和属性,但这个关键字的作用是什么?文章可以带你了解。... 阅读详情

相关推荐

看完这篇爽文我终于学会了示波器(一)

示波器基础篇的介绍,希望对你有用,下次分享一下如何测试。

qq_45508321的博客 974

关于String、StringBuffer、StringBuilder的区别

要想了解清楚他们的区别,就需要从它底层的代码实现去分析它,接下来我们会简单的对底层的代码做了解。 先做总结 String存储的数组被final修饰,一旦创建,其内容和长度就无法改变的。 StringBuffer表示字符串容器,因为StringBuffer继承了AbstractStringBuilder类,AbstractStringBuilder类的数组没有被final修饰。它会继承父类的属性。线程安全,效率低(syncaronized同步)它的方法都被syncaronized修饰 .

weixin_67240798的博客 731

树莓派4B学习笔记(5)—— 来个GPIO操作吧

一. GPIO库介绍 对于树莓派,现在有wiringPi,BCM2835,python三种库可以使用。 python 开发语言:python 简单介绍:树莓派官方资料中推荐且容易上手,毫无疑问的说python非常适合树莓派,树莓派也非常适合python。 wiring PI 开发语言:C、C++ 简单介绍:功能很强大,适合那些具有C语言基础,在接触树莓派之前已经接触过单片机或者嵌入式开发的人群。wiring Pi的API函数和arduino非常相似,且作者也给出了很多示例代码,这也使得它广受欢迎。

weixin_44415639的博客 8756

String、StringBuffer、StringBuilder笔记

根据韩顺平老师视频整理的资料。

m0_64386632的博客 269

YOLOv11改进策略【损失函数篇】| 通过辅助边界框计算IoU提升检测效果(Inner_GIoU、Inner_DIoU、Inner_CIoU、Inner_EIoU、Inner_SIoU)

为了弥补现有 IoU 损失在不同检测任务中泛化性弱和收敛速度慢的问题,·Inner-IoU·通过引入比例因子 “ratio” 来控制辅助边界框的尺度大小,利用不同尺度的辅助边界框来计算损失,从而加速边界框回归过程。

Limiiiing的博客 3893

华为OD机试 - 进制转换 - 二进制(Python/JS/C/C++ 2024 B卷 100分)

华为OD机试 2024E卷题库疯狂收录中,刷题。

学Java,找哪吒 918

String、StringBuffer、StringBuilder的区别

String、StringBuffer、StringBuilder的区别 相同之处:String、StringBuffer、StringBuilder都是final类,不允许被继承。 不同之处:主要是性能和线程安全这两个方面。 性能:StringBuilder>StringBuffer>String String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建就不可更改(对String的值的修改JVM会创建新对象,回收旧对象),后两

w759826115的博客 522

图像处理 | 基于matlab的多尺度Retinex(MSR)和自适应直方图均衡化(CLAHE)算法联合的低照度图像增强(附代码)

本文介绍了一种基于多尺度Retinex(MSR)和CLAHE的低照度图像增强算法。算法首先对图像进行多尺度Retinex处理,通过不同尺度的高斯核去除光照影响;然后采用CLAHE算法进行对比度增强。Matlab实现展示了算法流程,包含灰度转换、多尺度Retinex增强和CLAHE处理。实验结果显示,该方法能有效提升低照度图像的可见性,增强细节表现。最后提出了改进建议,包括引入深度学习方法和优化参数选择。

尘世冰封的专栏 954

十七、StringBuilder类和StringBuffer类【20220630】

Java中的StringBuilder类和StringBuffer类

weixin_52432081的博客 549

姿态传感器/陀螺仪之BMI270应用原理图设计

BMI270是一款由博世(Bosch)推出的高性能、超低功耗惯性测量单元(IMU),专为可穿戴设备、智能设备及物联网应用设计。

corlin6688的博客 2866

uni-app 实现高德 / 百度 / 腾讯地图多端导航跳转(H5最全方案)

本文介绍了一套生产级可复用的多地图导航Hook封装方案。针对开发中常见的经纬度顺序混乱、中文乱码、参数不统一等问题,提供了统一解决方案。方案支持高德、百度、腾讯三大地图平台,封装了各平台的URI参数处理逻辑,包括坐标顺序转换、名称编码、导航模式适配等核心功能。通过组合式API设计,开发者只需调用统一入口即可实现多地图导航跳转,同时保留单独调用各平台的能力。文中还总结了6大常见坑点,并推荐默认使用高德地图作为最稳定的导航方案。该方案适用于车辆定位、物流配送等多种业务场景,可封装为可复用的Hooks组件。

专注于前端开发与实战技巧,记录从零到一的学习与项目经验。分享 Vue3 / JavaScript / TypeScript / Flutter /Uniapp等前端技术,以及 性能优化、工程化、最佳实践。 538

关于String、StringBuffer、StringBuilder

字符串的私有和共享问题:字符串对象是私有的,要独立分配内存字面量是共享的,内存可复用StringBuffer和StringBuilder类的优化:优化了用 + 拼接字符串的情况Java8 时,其底层使用预估长度的char数组,用 + 连接字符串时就是向数组中追加字符串,追加的字符串超过数组长度时,需要对数组扩容Java9 时,底层使用byte数组,用 + 拼接前,先计算拼接字符串的总长度,根据总长度定义数组大小,避免了数组的扩容。

Best_Basic的博客 360

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

String、StringBuilder、StringBuffer的区别及适用场景。final、finally、finalize的区别。

类别String可变性不可变可变可变线程安全是(通过不可变性)否是(通过同步锁)性能较低(频繁创建新对象)较高(无需创建新对象,非线程安全)中等(需要同步锁,但支持多线程)适用场景字符串常量、少量运算单线程环境下频繁修改字符串多线程环境下频繁修改字符串在选择使用String、StringBuilder还是StringBuffer时,应根据具体的应用场景和需求来决定。如果字符串不需要修改,应优先考虑使用String;

weixin_53180424的博客 1160

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

String和StringBuilder的区别(final)?StringBuffer和StringBuilder的区别?

String是内容不可变的字符串 。String底层使用了一个不可变的字符数组 (final char[]) String str =new String(“bbbb”); 而StringBuillder StringBuffer,是内容可以改变的字符串 。StringBuillder StringBuffer底层使用的可变的字符数组(没有使用 final来修饰) 2.最经典就是拼接字符串。 String进行拼接 .String c = “a”+”b”; StringBuilder或者StringBuffe

qq_50913934的博客 306

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Java学习笔记之StringBuilder类

StringBuilder和String可以相互转换: 1.String–>StringBuilder:可以使用StringBuilder的构造方法。 StringBuilder(String str):构造一个字符串生成器,并初始化为指定的字 符串内容。 2.StringBuilder–>String:可以使用StringBuilder中的toString方法。 public String toString():将当前StringBuilder对象转换为String对象。 public

qq_46113679的博客 1万+

Java基础之final(最简单最详细)

final修饰的类表示最终类,不可以被继承。 (出于对安全问题的考虑,如果我们的某个类很重要不想被别人访问修改,就用final修饰,不允许被继承)

weixin_47543906的博客 1024

科普文:为什么String要用final修饰?

被final修饰的类,默认其下属性和方法都是final。被final修饰后,在jvm中独一份,可以更换的被共享。1.final修饰类,被final修饰的类不能被继承,即它不能拥有自己的子类,否在会在编译期间报错;2.final修饰方法,被final修饰的方法不能被重写;3.final修饰变量,final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。String是引用类型,也就是每个字符串都是一个String实例。

为无为,事无事,味无味。 2065

2018-4-23 JVM String与StringBuilder final和static

1. JVM的基础结构知识 JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配; 从更高的维度来看: Java堆(Heap) 对于大多数应用来说,Java堆(Java Heap)是Java...

weixin_38967434的博客 667

Java常用类:StringBuilder

一、介绍StringBuilder是一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)StringBuilder类可以用于在无需创建一个新的字符串对象情况下修改字符串。StringBuilder不是线程安全的,而StringBuffer是线程安全...

weixin_30699443的博客 159

Java:String、StringBuffer、StringBuilder

1.String:从String类的源代码可以发现String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法并且String类是通过char数组来保存字符串的,对String对象的任何改变都不影响到原对象,对其相关的任何操作都会生成新的对象。 2.StringBuilder:从StringBuilder类的源代码可以发现StringBuilder类是final类,也即意味着StringBuilder类不能被继承,并且它的成员方法都默认为final方法并且Str

weixin_48493408的博客 786

java 的String, Stringbuffer, StringBuilder的区别以及对final关键字的理解

本文参考了一些文章,链接如下: http://www.cnblogs.com/xudong-bupt/p/3961159.html http://www.cnblogs.com/mengdd/archive/2012/12/26/2834711.html 但是我也看了好一会儿才完全弄明白,比如文章中的“对象不可变”有点难理解,高手请忽略。 java中String、S

缎锦小妹的专栏 3372

Java-StringBuffer 与 StringBuilder

和的出现是为了解决String不可变性带来的性能问题。在需要频繁进行字符串操作的场景中,它们能显著提升程序性能。记住 "单线程用 Builder,多线程用 Buffer" 的原则在算法题中,几乎是字符串拼接操作的首选合理设置初始容量可以减少扩容带来的性能损耗。

qq_74743057的博客 484
上一篇: rollback-only异常令我对事务有了新的认识
下一篇: 10W字!推荐一个牛逼的Redis面试神技!
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值