Java基础-String
Table of Contents generated with DocToc (opens new window)
# String
- String类:代表字符串。Java程序中的所有字符串字面值(如"abc")都作为此类的实例实现
- String是一个final类,代表不可变的字符序列
- 字符串是常量,它们的值在创建之后不能更改
- String对象的字符内容是存储在一个字符数组value[]中的
public final class String
implements 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
2
3
4
5
6
7
String实现了Serializable接口:表示字符串是支持序列化的
实现了Comparable接口:可以比较大小
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串声明在方法区的字符串常量池中(jdk1.8)
字符串常量池中是不会存储相同内容的字符串的
# String不可变性的体现:
当对字符串重新赋值时,需要重写指定内存区域赋值,而不能使用原有的value进行赋值
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
当调用String的replace修改字符串时,也必须重新指定内存区域进行赋值
@Test public void test1(){ //字面量的定义方式 String s1 = "abc"; String s2 = "abc"; //比较s1和s2的地址值 System.out.println("s1==s2 = " + s1 == s2); s1 = "hello"; //hello System.out.println("s1 = " + s1); //abc System.out.println("s2 = " + s2); String s3 = "abc"; s3 += "def"; System.out.println("s3 = " + s3); String s4 = "abc"; String s5 = s4.replace('a', 'm'); System.out.println("s4 = " + s4); System.out.println("s5 = " + s5); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# String对象的创建
String str = "abc";
String s1 = new String(); //this.value = new char[0]
String s2 = new String(String original); //this.value = oringinal.value
String s3 = new String(char[] a); //this.value = Arrays.copyOf(value,value.length)
String s4 = new String(char[] a,int startIndex,int count);
2
3
4
5
# String str = "abc"和new String("abc")的区别
- str是储存在字符串常量池里,实际只创建了一个对象
- 而str2储存在堆里,不仅在堆里创建了一个str2对象,在字符串常量池里也创建一个char[]
面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"
2
@Test
public void test2(){
//String str = "abc";和String str2 = new String("abc");的区别
//此时的s1和s2的数据abc是声明在方法区中的字符串常量池中。
String s1 = "abc";
String s2 = "abc";
//此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
System.out.println("--------------");
Person p1 = new Person("zdk", 12);
Person p2 = new Person("zdk", 12);
System.out.println(p1.name.equals(p2.name));//true
//对象放在堆里
//因为是使用字面量定义,在字符串常量池中
System.out.println(p1.name == p2.name);//true
p1.name="xxx";
System.out.println(p2.name);//zdk
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# String不同拼接操作的对比
@Test
public void test3(){
String s1 = "javaEE";String s2 = "hadoop";
String s3 = "javaEEhadoop" ;
String s4 = "javaEE"+ "hadoop";//都是字面量参与拼接,也是存在于常量池
String s5 = s1 + "hadoop" ;//但如果加上了变量进行拼接,拼接后的字符串是存在于堆空间中
String s6= "javaEE" +s2;
String s7= s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s5.intern();
//通过intern方法返回值得到的s8使用的是常量池中已经存在d的"javaEEhadoop"
System.out.println(s3 == s8);//true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
结论:
- 常量与常量的拼接的结果是储存在常量池中的。且常量池中不会存在相同内容的常量
- 只要拼接的字符串中有一个是变量,拼接结果就存在堆中
- 如果拼接的结果调用intern方法,返回值也会存在常量池中
还有一种情况:变量使用final定义,相当于常量
@Test
public void test4(){
String s1 = "abcdef";
final String s2 = "abc";
String s3 = s2 + "def";
System.out.println(s1==s3);//true
}
2
3
4
5
6
7
面试题:
public class StringTest{
String str = new String("good");
char[] ch= {'t','e','s','t'};
public void change(String str,char ch[]){
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args){
StringTest ex = new StringTest();
ex.change(ex.str,ex.ch);
System.out.println(ex.str);//good
System.out.println(ex.ch);//best
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
因为调用change方法时,传递了str的地址到change方法里面,方法中的str变量的地址也指向了"good",然后方法中的str地址被修改,指向"test ok",但因为原str和方法中的str是两个不同的变量,当进入change方法时,他们都指向"good",当方法中str指向"test ok"后,原str并未改变指向,所以仍是good。还有体现String不可变性的就是,我们不能通过改变它的成员变量value的值去改变它,而是只能通过改变这个变量的地址来改变我们所认为的值!(Integer,Double,Byte,Short,Long,Double,Float,Character,Boolean类中的value属性,都有final修饰符,值均具有不可变性)
# java基本数据类型及其包装类
- byte(Byte),short(Short),int(Integer),long(Long)
- 浮点数类型:float(Float),double(Double)
- 字符类型:char(Character)
- 布尔类型:boolean(Boolean)
# JVM中涉及字符串的内存结构
三种JVM:
- Sun公司的HotSpot
- BEA公司的JRockit
- IBM公司的J9 Vm
Heap 堆
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以便执行器执行。
堆内存分为三部分:
Young Generation Space 新生区 Young
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分︰伊甸区(Eden space)和幸存者区(Survivor pace ),所有的类都是在伊甸区被new出来的。幸存区有两个:0区 ( Survivor 0 space )和1区(Survivor 1 space )。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生Major GC ( FullGC ),进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常"OutOfMemoryError" 如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二: (1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。 (2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。---内存溢出;内存泄漏
1
2
3
4
5
6Tenure Generation Space 养老区 Old
Permanent Space 永久储存区 Perm
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收密回收掉的,关闭JVM才会释放此区域听占用的内存。 如果出现java.lang.OutOfMemoryError:PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如∶在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。 Jdk1.6及之前:常量池分配在永久代,1.6在方法区 Jdk1.7:有永久代,但已经逐步“去永久代”,1.7在堆 Jdk1.8及之后:无永久代,1.8常量池分配在元空间
1
2
3
4
5
6
方法区:
实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态变量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Permanent Gen)”,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
常量池(Constant Pool)是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。
2
3
# String的常用方法
int length():返回字符串的长度:return value.length
char charAt(int index):返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == o
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写string
toUpperCase():使用默认语言环境,将String中的所有字符转换为大写
String trim(:返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalslgnoreCase(String anotherString): 与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginlndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIlndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始 boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
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
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个,如果超过了,剩下的全部都放到最后一个元素中。
# String与基本类型、包装类之间的转换
String--->基本类型、包装类
调用包装类String的静态方法:parseXxx(str)
基本数据类型、包装类-->String
String.valueOf(num)
或是num+""
String转char[]之间的转换
str.toCharArray();
char[]转String
使用String的构造方法 new String(char[])
String--->byte[]
调用str.getBytes() 参数可选择字符编码类型
byte[]---->String
使用String的构造方法 new String(byte[])