当前位置: 首页 > article >正文

JAVA | 聚焦 String 的常见用法与底层内存原理

*个人主页

文章专栏

《赛博算命之梅花易数的JAVA实现》*

在这里插入图片描述

文章目录

    • *[个人主页](https://blog.csdn.net/2401_87533975?spm=1011.2124.3001.5343)
    • 文章专栏
    • 《赛博算命之梅花易数的JAVA实现》*
  • #前言:API
    • 1.定义
    • 2.已经学习过的API
    • 3.如何使用帮助文档:
  • 一、String
    • 1、String概述
    • 2.String的注意点
    • 3.创建String对象的两种方式
    • 4.java的常用方法(比较)
      • 字符串比较的方法:
      • 遍历字符串
      • 拼接字符串
      • 反转字符串
      • 字符串截取
      • 字符串的替换
  • 二、StringBuilder
    • 1.StringBuilder概述
    • 2。StringBuilder构造方法
    • 3.StringBuilder常用方法
  • 三、StringJoiner
    • 1.StringJoiner概述
    • 2.StringJoiner的构造方法
    • 3.StringJoiner的成员方法
      • 总结:String与StringBuilder与StringJoiner
  • 四、字符串原理
    • 扩展底层原理1:字符串存储的内存原理
    • 扩展底层原理2:==号比较的到底是什么?
    • 扩展底层原理3:字符串拼接的底层原理
      • 等号的右边没有变量
      • 等号的右边有变量
      • 总结:
    • 扩展底层原理4:StringBuilder提高效率原理图
      • 总结:
    • 扩展底层原理5:StringBuilder的源码分析
      • 总结:
    • 扩展底层原理5:StringBuilder的源码分析

#前言:API

1.定义

API(Application Programming Interface)应用程序编程接口

简单理解:API就是别人已经写好的东西,我们不需要自己编写,直接使用即可

public static void main (String []args){
    Random r = new Random ();
    int number = r.nextInt (100);
}

Java API : 指的就是JDK提供的各种功能的Java类

这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。

2.已经学习过的API

Scanner 键盘录入

Random 随机数

API和API帮助文档

API:目前是JDK中提供的各种功能的Java类

这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可。

API帮助文档:帮助开发人员更好的使用API和查询API的一个工具

3.如何使用帮助文档:

  • 打开API帮助文档
  • 点击显示,并找到索引下面的输入框、
  • 在输入框中输入类名并点击显示
  • 查看类所在的包
  • 查看类的描述(一般情况下,只需要看第一行)
  • 查看构造方法
  • 查看成员方法

一、String

1、String概述

java.lang.String类代表字符串,java程序中的所有字符串文字(例如:”abc“)都为此类的对象。

String 是java定义好的一个类。定义在java.lang包中,所以使用的时候不需要导包。

String name = "尼古拉斯";
String schoolName = "黑马程序员";

2.String的注意点

字符串的内容是不会发生改变的,它的对象在创建后不能被更改

String name = "爱因斯晨";
String schoolName = "黑马程序员";
System.out.println(name+schoolName);

3.创建String对象的两种方式

  1. 直接赋值

    String name = "爱因斯晨";
    
  2. new

    构造方法说明
    public String ()创建空白字符串,不含任何内容
    public String (String original )根据传入的字符串,创建字符串对象
    public String(char [] chs)根据字符数组,创建字符串对象
    public String (byte [] chs)根据字节数组,创建字符串对象
    public class zifuu {
        public static void main(String[] args) {
            //直接赋值
            String s1 = "abc";
            System.out.println(s1);
    
            //使用new的方式创建字符串
            //空参构造
            String s2 = new String();
            System.out.println("@"+s2+"#");
    
            //传递一个字符串,根据传递的字符串内容再创建一个新的字符串对象
            String s3 = new String(original:"abc");
            System.out.println(s3);
    
            //传递一个字符数组,根据字符数组的内容,再创建一个新的字符串对象
            char[] ch = {'a','b','c'};
            String s4 = new String(ch);
            System.out.println(s4);
            
            //传递一个字节数组,根据字节数组的内容再创建一个新的字符串对象。
            byte [] bytes ={97,98,99};
            String s5 = new String (bytes);
            System.out.println(s5);
    
        }
    }
    
    

3.区别与用法:

Java的内存模型
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:StringTable(串池)在JDK7版本开始,从方法区中挪到了堆内存

举例1:

public class Zicun {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        System.out.println(s1);
        System.out.println(s2);
    }
}

在这里插入图片描述

当使用双引号直接赋值的时候,系统会检查该字符串在串池中是否存在。

不存在:创建新的

存在:复用

举例2:

public class fif {
    public static void main(String[] args) {
        char[] ch = {'a','b','c'};
        String str = new String(ch);
        String str1 = new String(ch);
    }
}

在这里插入图片描述

地址值不会复用

综上所述:我们推荐使用直接赋值,不仅因为书写简单而且内存方便

4.java的常用方法(比较)

public class bijiao {
    public static void main(String[] args) {
        String str1 = "abc";//串池里面有地址
        String str2 = "abc";//复用串池里面的地址
        System.out.println(str1==str2);
    }
}
//run:true
public class bijiao {
    public static void main(String[] args) {
        String str1 = "aaa";
        String str2 = "bbb";
        System.out.println(str1==str2);
    }
}
//run:false
public class bijiao {
    public static void main(String[] args) {
        String str1 = new String("abc");//记录的是堆里面的地址
        String str2 = "abc";//记录的是串池里面的地址
        System.out.println(str1==str2);
    }
}
//run:false

==号比的到底是什么?

  1. 基本数据类型:比较的是数据值

    int a = 10;
    int b = 20;
    System.out.println(a==b);//false
    
  2. 引用数据类型:比较的是地址值

    String s1 = new String ("abc");
    String s2 = new String ("abc");
    System.out.println(s1==s2);//false
    

字符串比较的方法:

boolean equals 方法(要比较的字符串)
完全一样结果才是true,否则为false

boolean equalslgnoreCase(要比较的字符串)
忽略大小写的比较
public class Stringd {
    public static void main(String[] args) {
        //1.创建两个字符串对象
        String s1 = new String("abc");
        String s2 = "ABC";
        //2.==号比较
        //基本数据类型:比较的是数据值是否相同
        //引用数据类型:比较的是地址值是否相同
        System.out.println(s1 == s2);
        //3.比较字符串内容是否相同
        boolean result =s1.equals(s2);
        System.out.println(result);
        //4.忽略大小写比较字符串内容是否相同
        //忽略大小写只能是英文
        boolean result2 = s1.equalsIgnoreCase(s2);
        System.out.println(result2);
    }
}

//run:
//false
//false
//true

//键盘录入:
import java.util.Scanner;
public class llu {
    public static void main(String[] args) {
        //1.假设键盘录入一个abc
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String s = sc.next();
        //2.代码中再定义一个字符串abc
        String s2 = "abc";
        //3.比较两个字符串内容是否相同
        boolean result = s.equals(s2);
        System.out.println(result);
    }

}

练习:

//练习:已知正确的用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示。

import java.util.Scanner;
public class yanzheng {
    public static void main(String[] args) {
        //定义正确的用户名和密码
        String rightUserName = "aiyinsichen";
        String rightPassword = "123456";
        Scanner sc = new Scanner(System.in);
        //模拟用户输入名字与密码,键盘录入
        for (int i = 1; i <= 3; i++) {
            System.out.println("请输入用户名:");
            String userName = sc.next();
            System.out.println("请输入密码:");
            String password = sc.next();
            //比较用户名和密码
            if (rightUserName.equals(userName) && rightPassword.equals(password)) {
                System.out.println("登录成功");
                break;
            }else {
                System.out.println("密码或用户名错误,登录失败,请重新输入");
            }
        }
    }
}

遍历字符串

需求:键盘录入一个字符串,使用程序实现在控制台遍历该字符串

public char charAt(int index):根据索引返回字符
public int length():返回此字符串的长度
//遍历字符串
import java.util.Scanner;
public class luru {
    public static void main(String[] args) {
       Scanner sc = new Scanner(System.in);
       System.out.println("请输入一个字符串:");
       String a = sc.nextLine();
       //a.length().fori 回车
       for (int i = 0; i < a.length(); i++) {
            //i依次表示字符串的每一个索引
           char c = a.charAt(i);
           System.out.println(c);
        }
    }
}

//键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数
import java.util.Scanner;
public class shai {
    public static void main(String[] args) {
        //键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String str = sc.next();
        //定义三个统计变量,初始值都为0
        int bigCount = 0;
        int smallCount = 0;
        int numberCount = 0;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c >= 'A' && c <= 'Z') {
                //char类型的变量在参与计算的时候自动类型提升为int 查询ascii码表
                bigCount++;
            } else if (c >= 'a' && c <= 'z') {
                smallCount++;
            } else if (c >= '0' && c <= '9') {
                numberCount++;
            }
        }
        System.out.println("大写字母字符:" + bigCount);
        System.out.println("小写字母字符:" + smallCount);
        System.out.println("数字字符:" + numberCount);

    }
}

拼接字符串

//拼接
//定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,并在控制台输出结果。
//例如,数组为int[] arr = {1,2,3}; ,执行方法后的输出结果为:[1, 2, 3]
public class pinjie {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        String s = arrayToString(arr);
        System.out.println(s);
    }
    public static String arrayToString(int[] arr){
        if (arr == null){
            return "";
        }
        if (arr.length == 0){
            return "[]";
        }
        String s = "[";
        for (int i = 0; i < arr.length; i++) {
            if (i == arr.length - 1){
                s = s + arr[i];
            }
            else{
                s = s + arr[i] + ",";
            }
        }
        s = s + "]";
        return s;
    }
}

反转字符串

//定义一个方法,实现字符串反转
//键盘录入一个字符串,调用该方法,并在控制台输出结果
import java.util.Scanner;
public class fanZhuan {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String s = sc.next();
        //调用方法
        String s1 = fan(s);
        System.out.println("反转后的字符串为:"+s1);
    }
    public static String fan(String s){
        // 将字符串转换为字符数组便于修改
        char[] arr = s.toCharArray();
        // 修正循环初始化和边界条件
        for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
            char temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        return new String(arr);
    }
}

方法二:
使用自带的反向遍历的方法:
变量名.length().forr 回车
import java.util.Scanner;
public class fanzhuan {  // 字符串反转工具类
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);  
        // 创建控制台输入扫描器
        String n = sc.next();                
        // 读取用户输入的字符串
        String result = fan(n);              
        // 调用反转方法处理输入
        System.out.println(result);           
        // 输出反转结果
    }

    // 字符串反转核心方法
    public static String fan(String s) {    
        // 参数s: 原始字符串
        String s1 = "";
        //s.length().forr回车,就是反向遍历(保留用户原有注释)
        for (int i = s.length()-1; i >= 0; i--) { 
            // 倒序循环:从末位到首位
            char c = s.charAt(i);            
            // 提取当前位置字符
            s1 = s1 + c;                     
            // 反向拼接字符
        }
        return s1;                          
        // 返回反转后的结果
    }
}

综合练习:

金额转换:如键盘录入234.输出大写方法
import java.util.Scanner;
/**
 * Demoutwo 类用于将用户输入的金额转换为中文大写金额表示。
 */
public class Demoutwo {
    /**
     * 程序的入口点,处理用户输入的金额并将其转换为中文大写金额。
     *
     * @param args 命令行参数,在本程序中未使用。
     */
    public static void main(String[] args) {
        // 1. 键盘录入一个金额
        // 创建一个 Scanner 对象,用于从标准输入读取用户输入
        Scanner sc = new Scanner(System.in);
        // 定义变量 money 用于存储用户输入的金额
        int money;
        // 使用 while 循环确保用户输入的金额在有效范围内(0 到 9999999 之间)
        while (true) {
            // 提示用户输入一个金额
            System.out.println("请输入一个金额:");
            // 从标准输入读取一个整数作为金额
            money = sc.nextInt();
            // 检查金额是否在有效范围内
            if (money >= 0 && money <= 9999999) {
                // 如果金额有效,跳出循环
                break;
            } else {
                // 如果金额无效,提示用户并继续循环等待新的输入
                System.out.println("金额无效");
            }
        }

        // 2. 把金额转换为中文
        // 定义一个空字符串 str 用于存储转换后的中文金额
        String str = "";
        // 使用 while 循环将金额逐位转换为中文数字
        while (true) {
            // 获取当前金额的个位数字
            int ge = money % 10;
            // 调用 getChinaNum 方法将个位数字转换为中文数字
            String chinaNum = getChinaNum(ge);
            // 将中文数字拼接到 str 字符串的前面
            str = chinaNum + str;
            // 去掉刚刚处理的个位数字
            money = money / 10;
            // 检查金额是否已经处理完
            if (money == 0) {
                // 如果金额为 0,跳出循环
                break;
            }
        }

        // 3. 补零,补到 7 位
        // 计算需要补充的零的数量
        int count = 7 - str.length();
        // 使用 for 循环在 str 字符串前面补充零
        for (int i = 0; i < count; i++) {
            str = "零" + str;
        }
        // 输出补零后的中文数字字符串
        System.out.println(str);

        // 4. 插入单位
        // 定义一个字符串数组 arr2,包含中文金额的单位
        String[] arr2 = {"佰", "拾", "万", "仟", "佰", "拾", "元"};
        // 使用 for 循环遍历补零后的中文数字字符串
        for (int i = 0; i < str.length(); i++) {
            // 获取当前位置的中文数字字符
            char c = str.charAt(i);
            // 将中文数字字符和对应的单位拼接成一个新的字符串
            String s = c + arr2[i];
            // 输出拼接后的字符串
            System.out.print(s);
        }
    }

    /**
     * 将阿拉伯数字转换为中文大写数字。
     *
     * @param number 要转换的阿拉伯数字(0 到 9 之间)
     * @return 对应的中文大写数字字符串
     */
    public static String getChinaNum(int number) {
        // 定义一个字符串数组 arr,包含中文大写数字
        String[] arr = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
        // 返回数组中对应索引的中文大写数字
        return arr[number];
    }
}

字符串截取

截取:
String substring (int beginlandex , int endlndex )
注意点:包头不包尾,包左不包右
    只有返回值才是截取的小串
String substring (int beginlndex) 截取到末尾
//加密电话号码:
//15552534681
//加密成:155****4681
import java.util.Scanner;
public class demout {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你的手机号码");
        String s = sc.nextLine();
        String s1 = s.substring(0,3);
        String s2 = s.substring(7);
        System.out.println(s1+"****"+s2);
    }
}

//练习:
//读取身份证中的信息:出生年月日还有性别
import java.util.Scanner;
public class demofour {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你的身份证号码");
        String id = sc.next();
        String year = id.substring(6,10);
        String month = id.substring(10,12);
        String day = id.substring(12,14);
        int xing = id.charAt(16);
        String gender;
        if (xing % 2 == 0) {
           gender = "女";
        }else{
            gender = "男";
        }
        System.out.println("人物信息为:");
        System.out.println("出生年月日为:"+year+"年"+month+"月"+day+"日");
        System.out.println("性别为:"+gender);
    }
}

字符串的替换

String replace (旧值,新值)替换
注意点:只有返回值才是替换之后的结果
//练习:敏感词屏蔽
import java.util.Scanner;
public class mingan {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一句话");
        String said = sc.next();
        String [] cyuyan ={"cnm","sb","tmd"};
        for (int i = 0; i < cyuyan.length; i++) {
            said =said.replace(cyuyan[i],"***");

        }
        System.out.println(said);
    }
}

注意:字符串是常量,他们的值在创建之后不能被改变的。

二、StringBuilder

1.StringBuilder概述

StringBuilder可以看成是一个容器,创建之后里面的内容是可变

  • 作用:提高字符串的操作效率

2。StringBuilder构造方法

方法名说明
public StringBuilder ()创建一个空白可变字符串对象,不含有任何内容
public StringBuilder (String str )根据字符串的内容,来创建可变字符串对象

3.StringBuilder常用方法

方法名说明
public StringBuilder append (任意类型)添加数据,并返回对象本身
public StringBuilder reverse ()反转容器中的内容
public int length()返回长度(字符出现的个数)
public String toString通过toString()就可以实现把StringBuilder 转换成String
public class builder {
    public static void main(String[] args) {
        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");
        
        //2.添加功能
        sb.append("def");
        sb.append("ghi");
        sb.append("jkl");
        sb.append("mno");
        
        //3.反转功能
        sb.reverse();
        
        //4.获取长度
        int length = sb.length();
    }
}

public class demoo {
    public static void main(String[] args) {
        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");
        //2.添加功能
        sb.append("def");
        sb.append("ghi");
        sb.append("jkl");
        sb.append("mno");
        System.out.println(sb);
        //3.再把StringBuilder变回String
        //为什么要把StringBuilder变回String呢?
        //因为StringBuilder是一个可变的字符串,而String是一个不可变的字符串,所以我们需要把StringBuilder变回String,才能使用String的方法
        String str = sb.toString();
        System.out.println(str);
    }
}

链式编程

当我们在调用一个方法的时候,不需要用变量接受他的结果,可以继续调用其他方法

import java.util.Scanner;

public class lianshi {
    public static void main(String[] args) {
        //链式编程:
        //当我们在调用一个方法的时候,这个方法的返回值是一个对象,我们可以继续调用这个对象的方法。
        //这种方式叫做链式编程。
        int len = getString().substring(1).length();
        System.out.println(len);
    }
    public static String getString() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String s = sc.next();
        return s;
    }
}
//链式编程练习
public class lioain {
    public static void main(String[] args) {
        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");

        //2.添加功能
        sb.append("def").append("ghi").append("jkl").append("mno");
        System.out.println(sb);
        String s = sb.toString();
        System.out.println(s);

    }
}

//判断是不是回文串
import java.util.Scanner;
public class xio {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        //键盘录入一个字符串
        String str = sc.next();
        //将字符串反转
        String sb = new StringBuilder().append(str).reverse().toString();
        //判断是否是回文串
        if (sb.equals(str)) {
            System.out.println("是回文串");
        }else {
            System.out.println("不是回文串");
        }
    }
}
//需求:定义方法,把int数组中的数据类型按照指定的格式拼接换成一个字符串返回,调用方法,并在控制台输出结果
public class ding {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8,9,10};
        System.out.println(getArray(arr));
    }
    public static String getArray(int[] arr) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < arr.length; i++) {
            if (i == arr.length - 1) {
                sb.append(arr[i]);
            }else {
                sb.append(arr[i]).append(",");
            }
        }
        sb.append("]");

        return sb.toString();
    }
}

三、StringJoiner

我们为什么要学StringJoiner?

我们原来在学习String时拼接元素太耗费时间,于是有了StringBuilder

但是我们在上面练习中,在打印字符串格式时,要做诸多限制,不是很方便于是

就有了StringJoiner。
在这里插入图片描述

1.StringJoiner概述

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的
  • 作用:提高字符串的操作效率,而且代码编写的特别简洁,但是市场上很少有人看
  • JDK8出现的

2.StringJoiner的构造方法

没有空参构造

方法名说明
pubilc StringJoiner(间隔符号)创建一个StringJoiner对象,指定拼接时的间隔符号
pubilc StringJoiner(间隔符号,开始符号,结束符号 )创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号
StringJoiner sj = new StringJoiner("---");
1--2--3
StringJoiner sj = new StringJoiner(",","[","]");
[1,2,3]

3.StringJoiner的成员方法

方法名说明
public StringJoiner add (添加的内容)添加数据,并返回对象本身
public int length()返回长度(字符出现的个数)
public String toString()返回一个字符串(该字符串就是拼接之后的结果)
import java.util.StringJoiner;

public class joner {
    public static void main(String[] args) {
        //1.创建对象
        StringJoiner sj = new StringJoiner("---");
        //2.添加功能
        sj.add("adc").add("def").add("ghi");
        System.out.println(sj);
        
        
        //3.创建对象2
        //delimiter: 分隔符
        //prefix: 前缀
        //suffix: 后缀
        StringJoiner sj2 = new StringJoiner(" ", "[", "]");
        //4.添加功能
        sj2.add("adc").add("def").add("ghi");
        System.out.println(sj2);

    }

}

//adc---def---ghi
//[adc def ghi]
import java.util.StringJoiner;

public class zifu {
    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner(",","[","]");
        int len = sj.add("aaa").add("bbb").add("ccc").length();
        //len不仅是字符串的长度,还包括了[]和,的长度
        System.out.println(len);
    }
}
//13

length不仅是字符串的长度,还包括符号的长度

总结:String与StringBuilder与StringJoiner

String:

表示字符串的类,定义了很多操作字符串的方法

StringBuilder:

一个可变的操作字符串的容器。

可以高效的拼接字符串,还可以将容器里面的内容反转

StringJoiner:

JDK8出现的一个可变的操作字符串的容器,可以高效,方便的拼接字符串。

在拼接的时候,可以指定间隔符号,开始符号,结束符号。

四、字符串原理

扩展底层原理1:字符串存储的内存原理

  • 直接赋值会复用字符串常量池中的
  • new出来不会复用,而是开辟一个新的空间

扩展底层原理2:==号比较的到底是什么?

  • 基本数据类型比较数据值
  • 引用数据类型比较地址值

扩展底层原理3:字符串拼接的底层原理

等号的右边没有变量

public class Test {
    public static void main (String [] args){
        String s = "a"+ "b"+"c";
        System.out.println(s);
    }
}

拼接的时候没有变量,都是字符串。触发字符串的优化机制。在编译的时候就已经是最终的结果了。

在这里插入图片描述

等号的右边有变量

public class Test {
    public static void main (String [] args ){
        String s1 = "a";
        String s2 = s1 + "b";
        String s3 = s2 + "c";
        System.out.println (s3);
        
    }
} 

在拼接的时候有变量,JDK8 以前底层会使用StringBuilder

在这里插入图片描述

内存分析:

1、进入 main 方法

在栈为 main 方法创建帧,用于存局部变量

2、执行变量赋值

String s1 = “a”;:

栈中创建 s1,若字符串常量池无 "a" 则创建,将其引用赋给 s1

String s2 = s1 + “b”;:

栈中创建 s2,堆中创建 StringBuilder 拼接 s1"b"(若常量池无 "b" 则创建),生成新字符串 "ab" 存于堆,引用赋给 s2

String s3 = s2 + “c”;:

栈中创建 s3,堆中新建 StringBuilder 拼接 s2 "c"(若常量池无 "c" 则创建),生成新字符串 "abc" 存于堆,引用赋给 s3

3、输出结果

从栈取s3引用,找到堆中 "abc" 输出。

4、方法结束

main 栈帧销毁,局部变量消失,堆对象等待垃圾回收,常量池字符串保留至程序结束。

**总结:**一个加号,堆内存中俩对象

public class Test {
    public static void main (String [] args ){
        String s1 = "a";
        String s2 = s1 + "b";
        String s3 = s2 + "c";
        System.out.println (s3);
        
    }
} 

字符串拼接的时候有变量参与:

在内存中创建了很多对象,浪费空间,时间也非常的慢

结论:如果很多字符串变量拼接,不要直接+。在底层会创建多个对象,浪费时间,浪费性能

总结:

  • 如果没有变量参与,都是字符串直接相加,编译之后就是拼接之后的结果,会复用串池中的字符串
  • 如果没有变量参与,每一行拼接的代码,都会在内存中创建新的字符串,浪费内存

扩展底层原理4:StringBuilder提高效率原理图

public class Test {
    public static void main (String [] args){
        StringBuilder sb = new StringBuilder();
        sb.append("a");
        sb.append("b");
        sb.append("c");
        System.out.println(sb); 
    }
}

在这里插入图片描述

StringBuilder 是一个可变的容器

内存分析

1、进入 main 方法

JVM 在栈为 main 方法创建帧,用于存储局部变量。

2、执行代码

StringBuilder sb = new StringBuilder();:

栈中创建局部变量 sb,堆中创建 StringBuilder 对象,sb 指向该对象。

sb.append(“a”);:

检查字符串常量池,若没有 "a" 则创建,StringBuilder 对象将 "a" 内容添加到自身存储区域。

sb.append(“b”);:

同理,若常量池无 "b" 则创建,StringBuilder 追加 "b"

sb.append(“c”);:

若常量池无 "c" 则创建,StringBuilder 追加 "c"

System.out.println(sb);:

从栈中获取 sb 引用,找到堆中 StringBuilder 对象,输出其内容。

2、方法结束

main 方法栈帧销毁,局部变量 sb 消失,堆中 StringBuilder 对象等待垃圾回收,字符串常量池中的 "a""b""c" 保留到程序结束。

//面试水题:
//问题:下列代码的运行结果是什么?
public static void main (String [] args ){
    String s1 = "abc";//记录串池中的地址值
    String s2 = "ab";
    String s3 = s2 + "c";//新new出来的对象,地址值不一样
    System.out.println(s1==s3);
    //==比较的是引用局部变量的地址值
}
//flase

字符串拼接的时候,如果有变量:

JDK8:系统底层会自动创建一个StringBuilder对象,然后再调用其append方法完成拼接。拼接后,再调用其toString方法转换为String类型,而toString方法的底层是直接new了一个字符串对象。

JDK8版本:系统会预估要字符串拼接之后的总大小,把要拼接的内容都放在数组中,此时也是产生一个新的字符串。

public class Test{
    public static void main (String [] args ){
        String s1 = "abc";//记录串池中的地址值
        String s2 = "a"+"b"+"c";
        //没有变量,触发优化机制,编译时,就会将“a”+"b"+"c"拼接为"abc"
        //复用串池中的字符串
        System.out.println(s1==s2);
    }
}
//ture

总结:

  • 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存

扩展底层原理5:StringBuilder的源码分析

  • 默认创建一个长度为16的字节数组
  • 添加的内容长度小于16,直接存
  • 添加的内容大于16会扩容 (原来的容量 *2 +2)
  • 如果扩容之后还不够,以实际长度为准

System.out.println(sb);:

从栈中获取 sb 引用,找到堆中 StringBuilder 对象,输出其内容。

2、方法结束

main 方法栈帧销毁,局部变量 sb 消失,堆中 StringBuilder 对象等待垃圾回收,字符串常量池中的 "a""b""c" 保留到程序结束。

//面试水题:
//问题:下列代码的运行结果是什么?
public static void main (String [] args ){
    String s1 = "abc";//记录串池中的地址值
    String s2 = "ab";
    String s3 = s2 + "c";//新new出来的对象,地址值不一样
    System.out.println(s1==s3);
    //==比较的是引用局部变量的地址值
}
//flase

字符串拼接的时候,如果有变量:

JDK8:系统底层会自动创建一个StringBuilder对象,然后再调用其append方法完成拼接。拼接后,再调用其toString方法转换为String类型,而toString方法的底层是直接new了一个字符串对象。

JDK8版本:系统会预估要字符串拼接之后的总大小,把要拼接的内容都放在数组中,此时也是产生一个新的字符串。

public class Test{
    public static void main (String [] args ){
        String s1 = "abc";//记录串池中的地址值
        String s2 = "a"+"b"+"c";
        //没有变量,触发优化机制,编译时,就会将“a”+"b"+"c"拼接为"abc"
        //复用串池中的字符串
        System.out.println(s1==s2);
    }
}
//ture

总结:

  • 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存

扩展底层原理5:StringBuilder的源码分析

  • 默认创建一个长度为16的字节数组
  • 添加的内容长度小于16,直接存
  • 添加的内容大于16会扩容 (原来的容量 *2 +2)
  • 如果扩容之后还不够,以实际长度为准

http://www.kler.cn/a/589614.html

相关文章:

  • 无人机吊舱模块更换技术难点分析!
  • UFS Link Startup 介绍
  • 怎么在centos7中搭建一个mqtt服务
  • 设计模式(行为型)-状态模式
  • 【CVPR 2025】局部区域自注意力LASA,用重叠补丁增强区域特征交互,即插即用!
  • 【Mac 从 0 到 1 保姆级配置教程 08】08. 快速配置 Neovim、LazyVim 以及常用开发环境,如果之前有人这么写就好了
  • 【JavaEE】Spring Boot 日志
  • Qt:槽函数与信号
  • 下载 CSS 文件阻塞,会阻塞构建 DOM 树吗?会阻塞页面的显示吗?
  • python项目一键加密,极度简洁
  • 使用Appium的W3C Actions实现多指触控行为
  • C++ STL 之常用拷贝和替换算法①copy();②replace();③replace_if();④swap();
  • C++ STL map
  • Spring Boot 动态配置管理:ZooKeeper 集成与 Redis 配置覆盖实践
  • easypoi导入Excel兼容日期和字符串格式的日期和时间
  • OpenCV计算摄影学(23)艺术化风格化处理函数stylization()
  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 的自动配置:约定优于配置的设计美学
  • Vue 登录 记住密码,设置存储时间
  • ROS学习过程记录(二)
  • Spark 优化作业性能以及处理数据倾斜问题