字符串广泛应用在Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
String的两点特殊性:
这两点从源码中对String的声明可以体现:
private final char[] value ;
String 类是final类,不可以被继承。
String 底层用char[] 实现,故String的长度为2^16-1
int length(); //获取字符串长度
char charAt(int index); //获取字符
int indexOf(String str); //str字符串第一次出现的位置
boolean equals(str); //判断字符串内容是否相同
boolean contains(str); //判断是否包含某一字符串
String trim(); //去除两端多余空格
String substring(begin,end); //截取一部分,包头不包尾
String replace(oldstr,newstr); //替代
String contact(str); //连接字符串
String toUpperCase(str): //大写
String toLowerCase(str); //小写
举个栗子:
String str1 = "Hello";
String str2 = "World";
str1 = str1 + str2;
内存分析如下:
我们可以看到,初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象。而丢弃的对象仍会在内存中滞留,在被垃圾回收之前都有数据泄露的风险。
String是不可变对象,一旦创建,那么整个对象就不可改变,直到垃圾收集器将它回收走。String值的改变,实际上只是指针引用指向了另一个新的对象。而字符数组中的元素是可以更改的,即可以在使用完之后将其更改,而不会保留原始的数据。所以使用字符数组的话,安全保密性高的信息(如密码之类的)将不会存在于系统中被他人看到。
JVM的常量区(Constant Pool)中维持了大部分创建的string字符串的驻留 ( String Interning ) 。
举例, Sring a=“ABC”;String b=“ABC”;
当 JVM 为 a 赋值时会在常量区生成一个字符串常量(String Constant),当 b 也赋值为“ABC”时,那么会在常量池中查看是否存在值为 “ABC” 的常量,存在的话,则把 b 的指针也指向 “ABC” 的地址,而不是新生成一个字符串常量(String Constant)。
JDK1.6中Interned Strings存储在永久代(Permanent Space)的常量池中;
JDK1.7中Interned Strings已经不再存储在永久代(Permanent Space)中,而是放到了Heap(堆)中;
JDK8中Permanent Space已经被完全移除,Interned Strings也被放到了元空间(MetaSpace)中。
//字符串转数字
String str = "123";
//方法一
Integer integer = new Integer(str);
System.out.println(integer);
//方法二
Integer i = Integer.parseInt(str);
System.out.println(i);
//方法三
Integer num = Integer.valueOf(str);
System.out.println(num);
//将数字转为字符串
Integer age = 123;
//方法一
String string = String.valueOf(age);
System.out.println(string);
//方法二
System.out.println(Integer.toString(age));
使用 StringBuilder 构造
String src = "name";
int len = src.length();
int repeat = 5;
StringBuilder builder = new StringBuilder(len * repeat);
for(int i=0; i<repeat; i++){
builder.append(src);
}
String dst = builder.toString();
补充 StringBuilder 的小知识:
//StringBuilder中封装的是容量(capacity)
StringBuilder builder = new StringBuilder(int capacity); //这就是为什么上面用len*repeat
//当封装字符串时,源码中会执行这样的过程: super(str.length() + 16);
StringBuilder builder = new StringBuilder(String str); //将字符串str放入builder
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
String str = "2013-11-07";
Date date = format.parse(str);
System.out.println(format.format(date));//2013-11-07
将该字符用空字符替换,然后用前后的字符串长度求差
String str = "hfghahhdfhkah";
int oldLength = str.length();
String string = str.replace("h","");
int newLength = string.length();
System.out.println(oldLength-newLength);
String 的 split()方法接收的字符串会被当做正则表达式解析,
“\s” 代表空白字符,如tab制表符 “\t”,换行"\n",回车"\r"。
而编译器在对源代码解析时,也会进行一次字面量转码,所以需要"\\s".
String[] strArray = aString.split("\\s+");
例子:
String string = new String("I am a student");
String[] strings = string.trim().split("\\s");
for(String s:strings){
System.out.println(s);
}
String a = "ab";
String b = "abc";
String c = a +"c";
System.out.println(a==c); //返回false
java支持可以直接用+号对两个字符串进行拼接。其真正实现的原理是:
中间通过建立临时的StringBuilder对象,然后调用append方法实现,最后调用 toString() 将StringBuilder字符串转化为 String 字符串。
大致内存过程
1)常量池创建“ab”对象,并赋值给st1,所以st1指向了“ab”
2)常量池创建“abc”对象,并赋值给st2,所以st2指向了“abc”
3)由于这里走的+的拼接方法,所以第三步是使用StringBuffer类的append方法,得到了“abc”,这个时候内存0x0011表示的是一个StringBuffer对象,注意不是String对象。
4)调用了Object的 toString() 把StringBuffer对象装换成了String对象。
5)把String对象赋值给st3
public class Demo2_String {
public static void main(String[] args) {
String str1 = "a" + "b" + "c"; //常量池中
String str2 = "abc"; //常量池中
System.out.println(st1 == st2); //true
System.out.println(st1.equals(st2)); //true
}
}
“a”,”b”,”c”三个本来就是字符串常量,进行+符号拼接之后变成了“abc”,“abc”本身就是字符串常量(Java中有常量优化机制),所以常量池立马会创建一个“abc”的字符串常量对象,在进行st2=”abc”,这个时候,常量池存在“abc”,所以不再创建。所以,不管比较内存地址还是比较字符串序列,都相等。
public class Demo2_String {
public static void main(String[] args) {
String st1 = "abc";
String st2 = "abc";
System.out.println(st1 == st2); //true
System.out.println(st1.equals(st2)); //true
}
}
分析:
第一个打印语句:
在Java中 == 这个符号是比较运算符,它可以基本数据类型和引用数据类型是否相等。
如果是
基本数据类型,==比较的是值是否相等,
如果是
引用数据类型,==比较的是两个对象的内存地址是否相等
字符串不属于基本数据类型,字符串对象属于引用数据类型,在上面把“abc”同时赋值给了st1和st2两个字符串对象,指向的都是同一个地址,所以第一个打印语句中的==比较输出结果是 true
第二个打印语句:
equals是Object这个父类的方法,在String类中重写了这个equals方法。由于st1和st2的值都是“abc”,两者指向同一个对象,当前字符序列相同,所以第二行打印结果也为true。
public class Demo2_String {
public static void main(String[] args) {
String st1 = new String("abc"); //指向堆
String st2 = "abc"; //指向常量池
System.out.println(st1 == st2); //false
System.out.println(st1.equals(st2)); //true
}
}
String str = new String(“abc”);
答案:
在内存中创建两个对象,一个在堆内存,一个在常量池,堆内存对象是常量池对象的一个拷贝副本,str指向堆内存对象。
简单理解String的 substring() 方法;
简单理解String的 intern() 方法;