一、三大核心特点
不变性:
String是一immutable模式的对象,不变模式的主要作用是当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
常量池的优化:
String对象创建之后,会在字符串常量池中进行缓存,下次创建同样的对象时,会直接返回缓存的引用。
Final修饰:
String类不可被继承,提高了系统的安全性。
二、类型结构
String不是基本数据类型
三、String 的实例化
String 的实例化有两种方式:
1、直接赋值,
2、通过构造函数,可以直接将字符串的值传入,页可以传入一个char数组。
String str1 = "Hello";//直接赋值
String str2 = new String("hello");//构造函数
char[] c1 = {'你','好'};
String str3 = new String(c1);//构造函数存储char类型数组
直接赋值和构造函数的区别:
存储的区域不同,直接赋值的方式存储字符串常量池当中,构造函数存储在堆内存当中。
String str1 = "hello";
String str2 = "hello";
System.out.println(str1==str2);//true 内存地址一样(根据String常量池的优化特性)
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str3==str4);//false 内存地址不一样(堆内存中指向不同的地址)
四、equals重写
因为Strign类堆equals方法进行了重写,所以我们可以直接调用String的equals方法判断两个字符串的值是否相等。源码先判断两个值的内存地址是不是一样,若是一样的话那么值就相同。若是不一样,先判断两个值的长度是否一样,不一样的直接返回false。一样则使用char[]数组进行一个一个的字符进行对比,若是每个字符都相等则返回true反之。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
五、不可变
当String对象的值发生改变时,栈里面会创建另外一个内存地址,与原来的地址不一致。
String str1 = new String("hello");
String str2 = str1;
System.out.println(str1==str2);//true 两者指向的地址一致
str1 +="world";//值发生了改变,内存地址也会跟着改变
System.out.println(str1==str2);//false
六、intern()方法
当调用某个字符串对象的intern()方法时,会去字符串常量池中寻找,如果已经存在一个值相等的字符串对象的话,则返回改对象的引用。如果不存在,则在字符串常量池中创建该对象,并返回false。堆内存中引用会被gc回收。
分析:因str1的值已经存入到常量池当中,而str2的值则是在栈中开辟一个空间来存储。所以两者的内存是不一致的。当str2使用了intern()方法,首先会默认到常量池当中,寻找有没有相匹配的值。若是有匹配的值,就返回该值的对象引用。因为str1的值和str2的值一致,则返回str1的引用地址。所以str1==str2.intern();
String str1 = "hello world";//存入常量池中
String str2 = new String("hello world");//在栈内存中开辟空间
System.out.println(str1==str2);//false 内存地址不一致
System.out.println(str1==str2.intern());//true 常量池中的值一致
七、常用方法
方法 | 描述 |
---|---|
public String() | 创建一个值为空的对象 |
public String(String original) | 创建一个值为original的对象 |
public String(char value[ ]) | 将一个char型数组转为字符串对象 |
public String(char value[ ], int offset, int count) | 将一个指定范围的char型数组转为字符串对象 |
public String(byte[ ] bytes ) | 将一个byte型数组转为字符串对象 |
public String (byte bytes[ ], int offset, int length) | 将一个指定范围的byte型数组转为字符串对象 |
public int length() | 返回字符串的长度 |
public boolean isEmpty() | 判断字符串是否为空 |
public char charAt(int index) | 返回字符串中指定位置的字符 |
…其余的看文档…
八、经典问题
1、==和equals的区别?
分析:==可以理解为比较栈内存中的值,如果说这个变量是基本数据类型,那么栈内存中存的就是具体的数值。如果是引用类型,栈里面存的就是引用类型的地址。所以说对于基本数据类型来说,就是比较值是否相等,对于引用类型来讲,比较的就是引用地址是否相等。equals方法实际是Object提供的方法,它的本质就是用进行判断。但是,java中任意一个类都可以对它进行重写,根据具体的需求重新定义它的判断逻辑。
2、下述代码的运行结果是?
String str1 = "hello world";
String str2 = "hello"+" world";
System.out.println(str1==str2);
分析:str1首先在字符串常量池当中创建了“hello world”,而str2是把两个字符串字面值进行拼接起来(常量与常量拼接),结果还是存在常量池当中。根据常量池的优化功能,str2会去引用str1的值,所以结果是true。
3、下述代码的运行结果是?
String str1 = "hello world";
String str2 = "hello";
str2 += " world";
System.out.println(str1==str2);
分析:str1存在字符串常量池当中,而str2使用间接拼接,使用变量与常量拼接那么值会存储到堆内存中。所以两者之间的地址不一致,返回false。
4、下述代码的运行结果?
String str1 = "hello world";
String str2 = "world";
String str3 = "hello"+str2;
System.out.println(str1==str3);
分析:str3使用常量与变量进行拼接,那么值会存在堆内存中。而str1的值存在字符串常量池当中,所以两者比较的结果是false。
5、下述代码的运行结果是?
String str1 = "hello world";
final String str2 = " world";
String str3 = "hello"+str2;
System.out.println(str1==str3);
分析:原本str2是变量,使用final修饰后变成常量。str1的值存在字符串常量池当中,而str3使用常量与常量进行拼接,那么结果会存到常量池当中。根据常量池的优化功能,str3会去引用str1的值,所以结果是true。
6、下述代码的运行结果是?
String str1 = "hello world";
final String str2 = new String(" world");
String str3 = "hello"+str2;
System.out.println(str1==str3);
分析:str1的值存在字符串常量池当中。str2使用构建函数创建值那么会在堆中开辟空间存储,加上final修饰成常量任然存储在堆内存当中。str3等于常量加上一个堆内存的数据那么结果还是在堆内存当中。结果是false。
7、下述代码的运行结果是?
String str1 = "hello world";
String str2 = "hello";
String str3 = " world";
String str4 = str2+str3;
System.out.println(str4==str1);
System.out.println(str4.intern()==str1);
分析:str1存储在字符串常量池当中,str2和str3是两个变量,两个变量拼接会存储在堆内存当中,所以str1和str2的内存地址不一样结果false。str4.intern()方法首先会默认到常量池当中,寻找有没有相匹配的值。若是有匹配的值,就返回该值的对象引用。因为str4的值和str1的值一致,则返回str1的引用地址。所以结果true;
8、什么是字符串常量池?
分析:字符串常量池它是位于堆内存里面的,专门用于存储字符串常量的。可以提高内存的使用率,避免开辟多块空间存储相同的字符串。若是没有常量池,每创建一个数据,都需要开辟一个空间。我们在创建字符串对象的时候,JVM首先会检查字符串常量池,如果这个字符串已经存在于常量池当中就直接返回它的引用。如果不存在,就会创建字符串对象存入到字符串常量池当中,再返回它的引用。
9、String是线程安全的吗?
分析:String是线程安全的。因为String是一个不可变的类,一旦创建String对象,我们就不能改变它的值。同一个字符串实例,可以被多个线程共享 。
10、在使用HashMap的时候,用String做key有什么好处?
分析:因为字符串是不可变的,所以说当字符串被创建的时候,它的HashCode的值就被缓存下来了。后续计算的时候,不需要重复去计算它的哈希值,可以直接去获取value值。相对其他类型会快一些。若是其他类型,每次使用key值的时候都会去计算以下的它的哈希值。