String的总结
String类分析
一:简述:
参考官方API来进行学习String类,String中文意思是字符串。
在java中,String是一个类,而并非是基本类型的数据类型。
String
类代表字符串。Java 程序中的所有字符串字面值(如 "abc",“helloworld” )都作为此类的实例,可以理解成是对象。
二:字符和字符串
众所周知,计算机的起源是在美国的。美国的所有的单个字符加起来还不到128个。可以在ASCII码表中查询到,但是世界上每个国家的文字都并非是一样的,所有有了编码格式来对每个国家的文字来进行存储。根据不同的编码类型,可以存储每个国家的文字。
字符串就是将字符串起来的一种表现形式,因为在现实生活中,单个字符不足以用来描述所有的信息,但是字符串是可以的。
官方API中是这样子来进行解释的:
String string = "abc";
等价于
char[] chars = {'a','b','c'};
String s = new String(chars);
三:字符串对象
首先写几个例子
String s1 = ""; // 没有空格
String s2 = " "; // 有空格
String s3 = "X";
在上面的几个案例中,
""," ","X",这几个都是String类的实例。
四:字符串的操作分类
1、转换方法
toLowerCase():将字符串转换为小写形式这里的大小写指的是英文大小写,而不是中文大小写
toUpperCase():将字符串转换为大写形式
toCharArray():将字符串转换为字符数组(需要一个新的char[]数组来保存)
valueOf(xxx):将xxx类型的数据转换为String类型(几乎所有的类型都可以转)
演示:
public static void main(String[] args) {
// 定义用户名字
String username = "LiGuang";
// 将username转换成大写
String s = username.toLowerCase();
System.out.println(s);
// 将username转换成小写
String s1 = username.toUpperCase();
System.out.println(s1);
// 将字符数组转换成字符串
char[] chars = username.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i]);
}
// 将其他类型的数据转换成String类型。valueOf是一个静态方法
int num = 97;
String s2 = String.valueOf(num);
if (s2 instanceof String){
System.out.println(s2);
}
}
注意:
valueOf是一个静态方法,直接用String.valueOf(引用类型的变量)来使用即可;
2、处理方法
trim():去除前字符头部空白和尾部空白,但是无法去除掉字符串中间空白的
split():对字符串进行分割
concat():与其他字符串进行连接(一般不常用)
replace():替换字符串
3、测试方法
startsWith():测试是否以指定的字符串开头
endsWith():测试是否以指定的字符串结尾
4、取值方法
length():返回字符串的长度(字符个数)
subString():返回字符串的子字符串(可指定)
charAt():返回字符串中指定索引处的字符
indexOf():返回指定字符在字符串中第一次出现处的索引值
lastIndexOf():返回指定字符在字符串中最后一次出现处的索引值
getBytes():返回字符串的编码序列(需要一个新的byte[]数组来保存)
5、比较方法
compareTo():以字典顺序比较字符串是否相同,区分大小写
compareToIgnoreCase():以字典顺序比较字符串是否相同,忽略大小写
equals():与指定的对象比较,若相同则返回true,区分大小写
equalsIgnoreCase():与另一个字符串比较,若相同则返回true,忽略大小写(注意不是与对象比较)
五:源码分析
3.1、String类的数据结构
String类的数据结构,最为重要的一点:
private final char value[];
// c++的写法,改变成java写法
private final char[] value;
private:表示只能在本类中进行访问这个数组,在外部类中是无法来进行操作的;
final修饰的的数组(本质上是一个地址,也是一个对象),地址值一旦赋值了,就无法再进行改变了。所以每个字符串一旦赋值之后,那么char就指向了一块内存空间,那么就再也无法再指向其他的内存空间了。
char[]:说明了java中的字符串对象是用字符数组来进行存储的。
既然是数组,那么说明了一点。方法肯定有大量的对数组的操作方法。
3.2、hash属性
既然存在了属性,那么必然存在着对hash属性进行操作的方法。
private int hash; // Default to 0
读到了这里,说明字符串对象要重写Object类中的hashCode方法了,要有这个敏锐的意识。这里直接去看String类中的hashCode方法。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
// 首先将原来的数组的地址赋值给新的数组的值
char val[] = value;
// 将里面的每个字符进行相乘和相加
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
// 将最后的h值赋值给hash值
hash = h;
}
return h;
}
这里说明了什么?重写了hashCode方法,是根据每个字符来进行计算最终的hash值的。
那么这种情况,可能存在着不同的字符串的hash值是一样的。
String a="Aa";
String b="BB";
int aa=a.hashCode();
System.out.println("aa的hash值"+aa);
int bb=b.hashCode();
System.out.println("bb的hash值"+bb);
System.out.println(aa==bb);
System.out.println(a.hashCode()==b.hashCode());
System.out.println(a.hashCode()==b.hashCode());
控制台结果:
aa的hash值2112
bb的hash值2112
true
true
说明了什么问题???根据字符的值来进行计算的hashCode值是一样的。但是因为这里的hashCode值是相同的,但是这里的hashCode算法是重写过的,所以说并不能作为是同一个对象的条件。
在java中,认为Object类中的hashCode相同的是同一个对象。那么String类重写了这个方法,会认为hash值是一样的是同一个对象吗?接着往下看。
看下String类型的equals方法
public boolean equals(Object anObject) {
// 对于引用类型来说,比较的是在内存中的地址值是否是一样的
if (this == anObject) {
return true;
}
// 判断anOjbect是否是String的,如果是的,判断语句中
if (anObject instanceof String) {
// 强制类型转换
String anotherString = (String)anObject;
// 判断length
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;
}
总结:
两个字符串要是相等,底层采用的是在内存中的地址相等或者是每个字符都是一样的;
四、构造函数
第一种:
public String() {
this.value = "".value;
}
如果直接使用:
String string = new String();
那么new出来的是一个空字符串,指向的也是内存中的一个空字符串。
小结:
final修饰的数组指向的地址是无法改变的,也就表示了对象是无法改变的了
但是对于对象的引用来说,它不是固定的。它是可以进行改变的。
第二种:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
看下源码:
public static char[] copyOf(char[] original, int newLength) {
// new了一个新的数组出来
char[] copy = new char[newLength];
// 将原来数组中的元素赋值一份到新建的数组中去
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
所以说,这个字符串底层的数组和原来的数组不是同一个。这也是必须的,不然外界改了数组中的值,那么字符串中的内容也就改了。hash是使用数组中字符依次计算得到的值。
第二个:拿到一部分字符,应该不常用
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
4.1、面试题几道题型
Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder
(或 StringBuffer
)类及其 append
方法实现的
例子一:
String s1 = "helloworld";
String s2 = "helloworld";
例子二:
String s1 = "hello";
String s2 = "world";
String s3 = s1+s2;
System.out.print("helloworld"==s1+s2) // false
相当于:
s1+s2========String s3 =new String("helloworld")
System.out.print(s3=="helloworld");
将堆内存中的地址和字符串常量池的内存进行相比较
例子三:
String s1 = "hello";
final String s2 = "world";
String s3 = s1+s2;
System.out.print(3=="helloworld");
相当于:
s1+s2========String s3 =new String("helloworld")
System.out.print(s3=="helloworld");
将堆内存中的地址和字符串常量池的内存进行相比较
例子四:
final String s1 = "hello";
final String s2 = "world";
System.out.print("helloworld"==s1+s2); //true
对于两个字符串字符串常量来说,JVM对其进行了优化。
例子五:
final String s1 = null
final String s2 = "world";
sout("helloworld"==s1+s2); // true
例子六:
String s1 = null;
String s2 = "hello";
System.out.println(s1+s2);
总结:
1、final修饰的字符串变量相加会优化
2、两个字符串对象相加会优化
3、两个非final修饰的相加,在堆内存中产生了新的对象
4、直接写字符串对象,字符串对象存在于常量池;new的存在于堆内存中。
五、常用方法
一、获取得到字符串长度
public int length() {
return value.length;
}
观察发现,底层调用的是length的值。需要注意的是这个数组,length就是数组中元素的个数,这是静态数组和动态数组的区别。
二、判断字符串是否是空的
public boolean isEmpty() {
return value.length == 0;
}
三、根据下标来取值
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
得到指定下标的元素值
四、将字符串底层数组中的字符转换成字节数组
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
第五个:两个字符串比较。必须都是String类型的字符串
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;
}
五、对hash值进行赋值
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
这里的hashCode计算出来的hash值,是根据字符数组中的字符来进行计算的。
六、转换成字符串对象
public String toString() {
return this;
}
六、Integer源码
整数常量池
主要分析这里的一段代码
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {}}
获取得到值的时候
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);}
优先会从这里来进行查询;
如果没有找到,才会去内存中创建一个新的地址来存储整数。