目录
- ⛳ String 字符串的存储原理及常用方法
- 🏭 一,`String` 对象介绍
- 🚜二,`String` 的内存结构
- 📢 2.1,创建字符串
- 🎉 创建字符串的情况:
- 空值创建:
- 非空值创建:
- `String str1 = "abc";` 与 `String str2 = new String('abc');`的区别
- 直接创建和`new`创建字符串的JVM存储结构
- 🎨 2.2,拼接字符串
- **📐 案例一:**
- 💖 案例二:
- 🎁 三,`String` 类常用的方法
⛳ String 字符串的存储原理及常用方法
🏭 一,String
对象介绍
- String类: 代表字符串。 Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
- String是一个final类,代表不可变的字符序列。
- 字符串是常量,用双引号引起来表示。 它们的值在创建之后不能更改。
- String对象的字符内容是存储在一个字符数组value[]中的。
public final class Stringimplements 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";
-
intern()
方法s2.intern()
是一个Java中的方法,用于将字符串对象添加到字符串常量池中。在Java中,字符串常量池是一个存储字符串对象的特殊区域,它的目的是提高字符串的重用性和比较效率。
当调用s2.intern()
方法时,它会首先检查字符串常量池中是否存在与s2
值相等的字符串对象。如果存在,则返回常量池中的对象;如果不存在,则将s2
添加到常量池中,并返回该新的常量池对象的引用。- 这种方法通常用于优化字符串的内存使用和比较操作。通过将字符串对象添加到常量池中,可以减少内存中重复字符串的数量,并且可以使用
==
运算符来比较字符串的引用是否相等,而不需要比较字符串的内容。
@Testpublic void test01(){String s1 = "hello";String s2 = new String("hello");String internS2 = s2.intern();System.out.println(s1 == s2); // falseSystem.out.println(s1 == internS2); // true}
说明:创建了一个字符串
s1
,并直接将其赋值为"Hello"。然后,我们使用关键字new
创建了另一个字符串对象s2
,它的值也是"Hello",但是它是通过构造函数创建的,因此它在内存中的地址不同于s1
。接下来,我们调用
s2.intern()
方法,将s2
添加到字符串常量池中,并将返回的常量池对象引用赋值给internedS2
变量。后,我们使用
==
运算符来比较s1
、s2
和internedS2
三者之间的引用关系。由于s1
指向字符串常量池中的"Hello"对象,而internedS2
也指向同一个对象,所以s1 == internedS2
表达式的结果为true
。而s1
和s2
是两个不同的对象,因此s1 == s2
表达式的结果为false
。这个示例说明了
s2.intern()
方法的作用,它将字符串对象添加到字符串常量池中,使得多个字符串对象可以共享同一个引用,从而节省内存并提高比较效率。
🎉 创建字符串的情况:
空值创建:
-
String s;
与String s = null;
-
String s = "";
初始化字符串常量(在常量池中有一个空的数组) -
String s = new String();
(String类中的 value属性指向一个堆内存中的空数组) -
String s = new String("");
非空值创建:
-
String s = 'abc';
-
String s = new String("abc");
-
char[] arr = {'a','b'};
和String s = new String(arr);
(把arr的字符数组,复制一份存到常量池里) -
char[] arr = {'a','b','c','d','e'};
和String s = new String(arr,0,3);
(截取arr字符数组的[0,3)
的字符,复制到常量池中)
String str1 = "abc";
与 String str2 = new String('abc');
的区别
- 字符串常量存储在字符串常量池中,目的是共享。
- 字符串非常量对象存储在堆中。
直接创建和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); // trueSystem.out.println(s1 == s3); // falseSystem.out.println(s1 == s4); // falseSystem.out.println(s3 == s4); // falses4 = new String("python");
}
结构图:
🎨 2.2,拼接字符串
下面我们通过两个案例来看字符串拼接的存储结构。
intern()
方法;- 例 s4,s41 : 在有变量参与的表达式,其中一个常量(如:“world”, “123”),如果常量池中已经存在,则直接引用常量池中的常量(“world”),如果不存在,则把常量(“123”)先存放到常量池中,再进行计算引用。
📐 案例一:
@Testpublic 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); // falseSystem.out.println(s3 == s5); // falseSystem.out.println(s4 == s5); // falseSystem.out.println(s3 == s6); // trueSystem.out.println(s41); // hello123}
JMM内存结构图:
javap -v TestDemo.class
反编译的结果:
💖 案例二:
循环的方式生成字符串
- 每循环一次,s1 的引用更新一次。
- 常量池中只有字符串 “0”;
- 其他生成的字符串都是在堆里生成的;
String s1 = "0";for (int i = 1; i <= 5 ; i++) {s1 += i;}System.out.println(s);
JMM内存结构图:
javap -v TestDemo.class
反编译结果:
🎁 三,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个, 如果超过了, 剩下的全部都放到最后一个元素中。
测试用例:
@Testpublic 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)); // -1System.out.println("indexOf:" + s.indexOf('l', 40)); // -1System.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,不包括7System.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]);}}