⛳ String 字符串的存储原理及常用方法

⛳ String 字符串的存储原理及常用方法

🏭 一,String 对象介绍

  • String类: 代表字符串。 Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
  • String是一个final类,代表不可变的字符序列。
  • 字符串是常量,用双引号引起来表示。 它们的值在创建之后不能更改。
  • String对象的字符内容是存储在一个字符数组value[]中的。
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

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

🚜二,String 的内存结构

基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的。因为String是一个类,所以Java种的字符串String属于引用数据类型。

:由JVM分配区域,用于保存线程执行的动作和数据引用。栈是一个运行的单位,Java中一个线程就会相应有一个线程栈与之对应。

:由JVM分配的,用于存储对象等数据的区域。

常量池 :在堆中分配出来的一块存储区域,用于存储显式 的String,float或者integer.例如String str=“abc”; abc这个字符串是显式声明,所以存储在常量池。

JDK6 的版本,字符串常量池在永久代(PermGen)中分配;

JDK7 的版本,字符串常量池在堆内存 Heap 中分配;

本文的内存分配图都是基于 JDK7以上的版本

📢 2.1,创建字符串

  • 下面的代码,在JVM的存储方式
String s1 = "abc";  // 字面量的定义方式
String s2 = "abc";
s1 = "hello";

a

  • intern() 方法

    • s2.intern()是一个Java中的方法,用于将字符串对象添加到字符串常量池中。在Java中,字符串常量池是一个存储字符串对象的特殊区域,它的目的是提高字符串的重用性和比较效率。
      当调用s2.intern()方法时,它会首先检查字符串常量池中是否存在与s2值相等的字符串对象。如果存在,则返回常量池中的对象;如果不存在,则将s2添加到常量池中,并返回该新的常量池对象的引用。
    • 这种方法通常用于优化字符串的内存使用和比较操作。通过将字符串对象添加到常量池中,可以减少内存中重复字符串的数量,并且可以使用==运算符来比较字符串的引用是否相等,而不需要比较字符串的内容。
        @Test
        public void test01(){
            String s1 = "hello";
            String s2 = new String("hello");
    
            String internS2 = s2.intern();
    
            System.out.println(s1 == s2);  // false
            System.out.println(s1 == internS2);  // true
        }
    

    说明:创建了一个字符串s1,并直接将其赋值为"Hello"。然后,我们使用关键字new创建了另一个字符串对象s2,它的值也是"Hello",但是它是通过构造函数创建的,因此它在内存中的地址不同于s1

    接下来,我们调用s2.intern()方法,将s2添加到字符串常量池中,并将返回的常量池对象引用赋值给internedS2变量。

    后,我们使用==运算符来比较s1s2internedS2三者之间的引用关系。由于s1指向字符串常量池中的"Hello"对象,而internedS2也指向同一个对象,所以s1 == internedS2表达式的结果为true。而s1s2是两个不同的对象,因此s1 == s2表达式的结果为false

    这个示例说明了s2.intern()方法的作用,它将字符串对象添加到字符串常量池中,使得多个字符串对象可以共享同一个引用,从而节省内存并提高比较效率。

🎉 创建字符串的情况:
空值创建:
  1. String s;String s = null;

    String1.drawio

  2. String s = "";初始化字符串常量(在常量池中有一个空的数组)

    String2.drawio

  3. String s = new String(); (String类中的 value属性指向一个堆内存中的空数组)Strng3.drawio

  4. String s = new String("");

    String4.drawio

非空值创建:
  1. String s = 'abc';

    String2-1.drawio

  2. String s = new String("abc");

    String2-2.drawio

  3. char[] arr = {'a','b'};String s = new String(arr);(把arr的字符数组,复制一份存到常量池里)

    在这里插入图片描述
    下面这个图是错误的:
    String2-3.drawio2

  4. char[] arr = {'a','b','c','d','e'};String s = new String(arr,0,3);(截取arr字符数组的 [0,3) 的字符,复制到常量池中)

在这里插入图片描述
下面这个图是错误的:
String2-4.drawio

String str1 = "abc";String str2 = new String('abc');的区别
  • 字符串常量存储在字符串常量池中,目的是共享。
  • 字符串非常量对象存储在堆中。

String-null-new.drawio

直接创建和new创建字符串的JVM存储结构

分析下面代码的JMM结构:

public void test(){
    String s1 = "javaEE";
    String s2 = "javaEE";
    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
    s4 = new String("python");
}

结构图:

String-structure.drawio

🎨 2.2,拼接字符串

下面我们通过两个案例来看字符串拼接的存储结构。

  • intern()方法;
  • 例 s4,s41 : 在有变量参与的表达式,其中一个常量(如:“world”, “123”),如果常量池中已经存在,则直接引用常量池中的常量(“world”),如果不存在,则把常量(“123”)先存放到常量池中,再进行计算引用。
📐 案例一:
    @Test
    public void test2(){
        String s1 = "hello";
        String s2 = "world";
        String s3 = "hello" + "world";
        String s4 = s1 + "world";
        String s41 = s1 + "123";
        String s5 = s1 + s2;
        String s6 = (s1 + s2).intern();
        System.out.println(s3 == s4);  // false
        System.out.println(s3 == s5);  // false
        System.out.println(s4 == s5);  // false
        System.out.println(s3 == s6);  //  true
        System.out.println(s41);       // hello123
    }

JMM内存结构图:

String_joint.drawio

javap -v TestDemo.class反编译的结果:

image-20230803135016871

💖 案例二:

循环的方式生成字符串

  • 每循环一次,s1 的引用更新一次。
  • 常量池中只有字符串 “0”;
  • 其他生成的字符串都是在堆里生成的;
        String s1 = "0";
        for (int i = 1; i <= 5 ; i++) {
            s1 += i;
        }
        System.out.println(s);

JMM内存结构图:

String_joint-01.drawio

javap -v TestDemo.class反编译结果:

image-20230804140239389

🎁 三,String 类常用的方法

  • 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):比较两个字符串的大小

    • compareTo(String anotherString)的源码

          public int compareTo(String anotherString) {
              int len1 = value.length;
              int len2 = anotherString.value.length;
              int lim = Math.min(len1, len2);
              char v1[] = value;
              char v2[] = anotherString.value;
      
              int k = 0;
              while (k < lim) {
                  char c1 = v1[k];
                  char c2 = v2[k];
                  if (c1 != c2) {
                      return c1 - c2;
                  }
                  k++;
              }
              return len1 - len2;
          }
      
  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的beginIndex开始截取到最后的一个字符串

  • String substring(int beginIndex, int endIndex):返回一个新字符串,它是此字符串从biginIndex开始截取到endIndex(不包含) 的字符串。

  • 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;

  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束

  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始

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

  • 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个, 如果超过了, 剩下的全部都放到最后一个元素中。

测试用例:

    @Test
    public void test4() {

        // 定义一个字符串对象
        String s = "helloworld";

        // int length():获取字符串的长度。
        System.out.println("s.length:" + s.length());
        System.out.println("----------------------");

        // char charAt(int index):获取指定索引位置的字符
        System.out.println("charAt:" + s.charAt(0));
        System.out.println("----------------------");

        // int indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引。
        System.out.println("indexOf:" + s.indexOf('l'));
        System.out.println("----------------------");

        // int indexOf(String str):返回指定字符串在此字符串中第一次出现处的索引。
        System.out.println("indexOf:" + s.indexOf("owo"));
        System.out.println("----------------------");

        // int indexOf(int ch,int fromIndex):返回指定字符在此字符串中从指定位置后第一次出现处的索引。
        System.out.println("indexOf:" + s.indexOf('l', 4));
        System.out.println("indexOf:" + s.indexOf('k', 4)); // -1
        System.out.println("indexOf:" + s.indexOf('l', 40)); // -1
        System.out.println("----------------------");

        // 自己练习:int indexOf(String str,int
        // fromIndex):返回指定字符串在此字符串中从指定位置后第一次出现处的索引。

        // String substring(int start):从指定位置开始截取字符串,默认到末尾。包含start这个索引
        System.out.println("substring:" + s.substring(5));
        System.out.println("substring:" + s.substring(5,7)); //开始时5,结束是7,不包括7
        System.out.println("substring:" + s.substring(0));
        System.out.println("----------------------");

        // String substring(int start,int
        // end):从指定位置开始到指定位置结束截取字符串。包括start索引但是不包end索引
        System.out.println("substring:" + s.substring(3, 8));
        System.out.println("substring:" + s.substring(0, s.length()));

        String s3 = "  abc 123  ";
        System.out.println(s3.trim().length()); //去掉前后两端的空格

        String s4 = "a,b,c,d,e,123";
        String[] s5 =  s4.split(",");
        for (int i = 0; i < s5.length; i++) {
            System.out.println(s5[i]);
        }
    }
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JavaString类是用于表示字符串的类,它包含许多常用方法来处理字符串,以下是一些常用字符串方法及其返回值: 1. length()方法:返回字符串的长度,即包含的字符数。 2. charAt(int index)方法:返回字符串指定位置的字符。 3. substring(int beginIndex, int endIndex)方法:返回从指定位置开始到指定位置结束的子字符串,其beginIndex表示开始位置(包含),endIndex表示结束位置(不包含)。 4. indexOf(char ch)方法:返回指定字符在字符串第一次出现的位置,如果不存在则返回-1。 5. indexOf(String str)方法:返回指定字符串字符串第一次出现的位置,如果不存在则返回-1。 6. replace(char oldChar, char newChar)方法:用指定的新字符替换字符串所有的旧字符,并返回替换后的新字符串。 7. replaceAll(String regex, String replacement)方法:用指定的新字符串替换字符串符合正则表达式的所有子串,并返回替换后的新字符串。 8. toUpperCase()方法:将字符串的所有字母转换为大写字母,并返回新字符串。 9. toLowerCase()方法:将字符串的所有字母转换为小写字母,并返回新字符串。 10. trim()方法:去除字符串两端的空格,并返回新字符串。 以上这些方法String常用的一些字符串方法,能够满足大多数字符串操作的需求。 ### 回答2: JavaString类是非常重要的一个类,常用存储和操作字符串,其字符串长度是一个基本概念。String提供了一些常用方法来获取字符串长度。 1. length()方法:该方法返回一个字符串的长度,即字符串字符的个数。例如: ```java String str = "Hello World!"; int len = str.length(); ``` 上面的代码,len的值为12,因为字符串一共有12个字符。 2. isEmpty()方法:该方法返回一个布尔值,判断一个字符串是否为空。如果一个字符串的长度为0,则认为它是空的。例如: ```java String str = ""; boolean isEmpty = str.isEmpty(); ``` 上面的代码,isEmpty的值为true,因为str是一个空字符串。 3. trim()方法:该方法去除字符串两端的空格,并返回去除空格后的字符串。例如: ```java String str = " Hello World! "; str = str.trim(); ``` 上面的代码,在执行str.trim()方法之后,str的值变为了"Hello World!",去除了两端的空格。 4. getBytes()方法:该方法返回一个字节数组,表示该字符串每个字符的字节编码。例如: ```java String str = "Hello World!"; byte[] bytes = str.getBytes(); ``` 上面的代码,bytes的长度为12,因为字符串一共有12个字符,每个字符占用1个字节。 5. length vs length():需要注意的是,length和length()虽然都可以用来获取字符串长度,但是它们的返回值类型不同。length是一个数组的属性,返回该数组的长度;而length()是String类的方法,返回字符串的长度。因此,我们需要根据具体的情况选择使用哪个方法。 在实际开发,我们会频繁地使用到这些方法,进行字符串长度的计算和判断,从而达到我们想要的程序效果。 ### 回答3: Java字符串是一种非常常见的数据类型,可以使用String类来创建和处理字符串。其字符串长度是String的一个非常基本的方法,对于字符串的处理和操作都有着非常重要的意义。 字符串长度可以通过调用String的length()方法来获取。该方法返回的是一个整数值,表示该字符串字符的数量,包括空格和特殊字符。例如,对于下面的字符串String str = "Hello World!"; 调用str.length()方法将返回整数值12,它是该字符串字符的数量。如果该字符串为空字符串,即“”,那么调用该方法将返回整数值0。 在实际的开发字符串长度的获取非常常见,比如在字符串的截取、比较、连接等操作都需要用到字符串长度。另外,在一些具有字符个数限制的场景(如数据库的字段),该方法也可以用来判断字符串的长度是否符合规定。 除了length()方法,还有一个类似的属性可以获取字符串的长度,就是String的length属性。该属性是一个final类型的整数,通过直接访问该属性可以获取字符串的长度。例如: String str = "Hello World!"; int len = str.length; 调用str.length将返回整数值12,它等价于调用str.length()方法。不过,需要注意的是,该属性是final类型的,即一旦获取了长度值,就不能再修改了。 因此,对于字符串的操作和处理,获取字符串长度是一项非常基本的操作。Java提供了String的length()方法和length属性来获取字符串的长度,能够满足我们大部分的需求。对于需要频繁使用的场景,也可以将获取到的长度值存储在变量,以便下次使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值