String字符串的存储原理
示例一
要点
1、String表示字符串类型,属于引用数据类型,不属于基本数据类型。
2、在java中随便使用双引号括起来的都是String对象。例如: "abc" , "def" , "hello world!" ,这是3个String对象。
3、java 中规定,双引号括起来的字符串本身,是不可变的,也就是说"abc"自出生到最终死亡都是"abc"。
4、在JDK当中双引号括起来的字符串,例如: "abc" "def" 都是直接存储在“方法区”的“字符串常量池”当中的。
为什么SUN公司把字符串存储在一个“字符串常量池”当中呢? 因为字符串在实际的开发中使用太频繁。为了执行效率,把字符串放到了方法区的字符串常量池当中。
实例
public class StringTest01 {
public static void main(String[] args) {
//这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
//分析:这是使用new的方式创建的字符串对象。这个代码中的"xy"是从哪里来的?
//凡是双引号括起来的都在字符串常量池中有一份。
// new对象的时候一定在堆内存当中开辟空间。
String s3 = new String(original: "xy");
}
}
内存图

示例二
实例(部分代码)
User user = new User(id:110,name:"张三");
内存图

示例三
实例
public class StringTest02 {
public static void main(String[] args) {
String s1 = "he11o";
// "hello"是存储在方法区的字符串常量池当中
//所以这个"hello"不会新建。( 因为这个对象已经存在了! )
String s2 = "he11o";
// ==双等号比较的是变量中保存的内存地址。
System.out.print1n(s1 == s2); // true
String x = new String( original: "xy");
String y = new String( original: "xyz");
System.out.println(x == y); //false
/*
//通过这个案例的学习,我们知道了,字符串对象之间的比较不能使用“==”
// "=="不保险。应该调用String类的equals方法,String类的equals方法已经重写了。
System.out.print1n(x.equals(y)); // true
String k = new String("testString");
//String k = null;
// "testString"这个字符串可以后面加"."吗? 可以。
//因为"testString"是一个String字符串对象,只要是对象都能调用方法。
System.out.println("testString".equals(k)); //建议使用这种方式,因为这个可以避免空指针异常。
System.out.println(k.equals("testString")); //存在空指针异常的风险。不建议这样写。
*/
}
}
内存图

String中的构造方法
要点
关于String类中的构造方法。
第一个:String s = new String("");
第二个:String s ="";//最常用
第三个:String s = new String(char数组);
第四个:String s = new String(char数组,起始下标,长度);
第五个:String s = new String(byte数组);
第六个:String s = new String(byte数组,起始下标,长度)
实例
public class StringTest04 {
public static void main(String[] args) {
//创建宇符串对象最常用的一种方式
String s1 = "he11o wor1d!";
// s1这个变量中保存的是一个内存地址。
System.out.println(s1); //hello world!
System.out.print1n(s1.toString()); //hello world!
//这里只掌握常用的构造方法。
byte[] bytes = {97, 98, 99}; // 97是, 98是b , 99是c
String s2 = new String(bytes);
//输出字符串对象的话,输出的不是对象的内存地址,而是字符串本身。
System.out.println(s2.toString()); //abc
//String(字节数组,数组元素下标的起始位置长度)
//将byte数组中的一部分转换成字符串。
String s3 = new String(bytes,1,2);
System.out.println(s3); //bc
//将char数組全部转换成字符串
char[] chars = {'我','是','中','国','人'};
String s4 = new String(chars);
System.out.println(s4);
//将char数组的部分转换成字符串
String s5 = new String(chars, 2,3);
System.out.print1n(s5);
String s6 = new String("helloworld!");
System.out.println(s6); //helloworld!
}
}
StringBuffer进行字符串拼接
要点
使用“+”进行字符串拼接会在字符串常量池中产生大量程序运行过程中不会被回收的字符串常量,消耗内存。
如果以后需要进行大量字符串的拼接操作.建议使用JDK中自带的:
java.lang.StringBuffer
java.lang.StringBuilder
StringBuffer的append方法底层调用了arraycopy方法进行扩容,扩容后原字符串会被回收。
如何优化StringBuffer的性能?
在创建StringBuffer的时候尽可能给定一个初始化容量。
最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化容量。
关键点:给一个合适的初始化容量。可以提高程序的执行效率。
* StringBuffer和StringBuilder的区别
StringBuffer中的方法都有: synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
StringBuilder中的方法都没有: synchronized关键字修饰,表示StringBuilder在多线程环境下运行是不安全的。
实例
public class StringBufferTest01{
public static void main(String[] args) {
//创建一个初始化容量为16个byte[]数组。(字符串缓冲区对象)
StringBuffer stringBuffer = new StringBuffer();
//拼接字符串,以后拼接字符串统一调用 append()方法。
//append是追加的意思。
stringBuffer.append("a");
stringBuffer.append(3.14);
stringBuffer.append(true);
//append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。
stringBuffer.append(100L);
System.out.println(stringBuffer.toString());
//指定初始化容量的StringBuffer对象(字符申缓冲区对象)
StringBuffer sb = new StringBuffer(capacity: 100);
sb.append("hello");
StringBuilder sb2 = new StringBuilder();
sb2.append("hello");
}
}
String类中的一些方法
    String类中有很多方法例如:StringBuffer和StringBuilder方法、charAt方法、compareTo方法、contains方法、endsWith方法、equals方法、equalsgnoreCase方法、getBytes方法、isEmpty方法、substring方法、toCharArray方法、toLowerCase方法、valueOf方法等等,这里简要介绍Interger、Int和String相互转换的方法,其他方法请查文档。
示例
public class IntegerTest{
public static void main(String[] args) {
// String --> int
int i1 = Integer.parseInt( s:"100"); //让i1是100数字
System.out.println(i1 + 1); // 101
// int --> String
String s2 = i1 + " "; // "100"字符串
System.out.print1n(s2 + 1); // "1001"
// int --> Integer
//自动装箱
Integer x = 1000;
// Integer --> int
//自动拆箱
int y=x;
// String --> Integer
Integer k = Integer.valueOf("123");
// Integer --> String
String e = String.valueOf(k);
}
}
面试题
1、分析以下程序,一共创建了几个对象
public class StringTest03 {
public static void main(String[] args) {
/*
一共3个对象:
方法区字符串常量池中有1个: "hello"
堆内存当中有两个String对象。
一共3个。
*/
String s1 = new String( original: "hello");
String s2 = new String( original: "hello");
}
}
2、String为什么是不可变的?
我看过源代码, String类中有一个byte[]数组,这个byte[]数组采用了final修饰。
因为数组一旦创建 长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,
所以String是不可变的! "abc"无法变成"abcd"
3、StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量应该是16,
当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy(),
所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
public class IntegerTest{
public static void main(String[] args) {
// String --> int
int i1 = Integer.parseInt( s:"100"); //让i1是100数字
System.out.println(i1 + 1); // 101
// int --> String
String s2 = i1 + " "; // "100"字符串
System.out.print1n(s2 + 1); // "1001"
// int --> Integer
//自动装箱
Integer x = 1000;
// Integer --> int
//自动拆箱
int y=x;
// String --> Integer
Integer k = Integer.valueOf("123");
// Integer --> String
String e = String.valueOf(k);
}
}
面试题
1、分析以下程序,一共创建了几个对象
public class StringTest03 {
public static void main(String[] args) {
/*
一共3个对象:
方法区字符串常量池中有1个: "hello"
堆内存当中有两个String对象。
一共3个。
*/
String s1 = new String( original: "hello");
String s2 = new String( original: "hello");
}
}
2、String为什么是不可变的?
我看过源代码, String类中有一个byte[]数组,这个byte[]数组采用了final修饰。
因为数组一旦创建 长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,
所以String是不可变的! "abc"无法变成"abcd"
3、StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量应该是16,
当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy(),
所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
1、分析以下程序,一共创建了几个对象
public class StringTest03 {
public static void main(String[] args) {
/*
一共3个对象:
方法区字符串常量池中有1个: "hello"
堆内存当中有两个String对象。
一共3个。
*/
String s1 = new String( original: "hello");
String s2 = new String( original: "hello");
}
}
2、String为什么是不可变的?
我看过源代码, String类中有一个byte[]数组,这个byte[]数组采用了final修饰。
因为数组一旦创建 长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,
所以String是不可变的! "abc"无法变成"abcd"
3、StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量应该是16,
当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy(),
所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
public class StringTest03 {
public static void main(String[] args) {
/*
一共3个对象:
方法区字符串常量池中有1个: "hello"
堆内存当中有两个String对象。
一共3个。
*/
String s1 = new String( original: "hello");
String s2 = new String( original: "hello");
}
}
2、String为什么是不可变的?
我看过源代码, String类中有一个byte[]数组,这个byte[]数组采用了final修饰。
因为数组一旦创建 长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,
所以String是不可变的! "abc"无法变成"abcd"
3、StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量应该是16,
当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy(),
所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
我看过源代码, String类中有一个byte[]数组,这个byte[]数组采用了final修饰。
因为数组一旦创建 长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,
所以String是不可变的! "abc"无法变成"abcd"
3、StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量应该是16,
当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy(),
所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量应该是16,
当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy(),
所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。