Java基础知识总结(二)
《Java基础知识总结》系列是对自己学习Java历程中知识的一个总结,也是为自己找工作前知识的回顾,为找工作奠定基础。
String类学习总结
1、String 字符串API 解读
我们要想真正了解String类,最简单、最直接的办法就是读懂API帮助文档中对String类的介绍,API是SUN公司写的,同时这些信息也是最底层的,更有助于帮助我们从根本上了解String类!
首先打开API帮助文档,我们一起来解读……
1、public final class String extends Object
1)这句话告诉我们String 类是最终类(final类)不能被继承。
2)它继承与Object,它拥有Object类的所有属性和方法,比如:Object类的toString()、hashCode()、equals()等方法。当然Object类是所有类的父类,即所有类都拥有Object类的属性和方法,不仅仅是String 类。
2、The String class represents character strings. All string literals in Java programs, such as "abc", are implemented as instances of this class.
1)这句话告诉我们Java中所有字符串的字面值都是String类的实例,即时一个String类的一个具体对象。例如以下程序1:
1 public class StringDemo { 2 public static void main(String[] args) { 3 System.out.println("abc".length());//3 4 System.out.println("abc".equals("abc"));//true 5 } 6 }
如上面的程序,"abc"拥有String类的方法,因此它是一个String类的一个实例化对象。
3、String are constant;their values cannot be changed after they are created.
1) 字符串是常量,一旦被创建,他们的值就不能改变。如以下程序2:
1 public class StringDemo { 2 public static void main(String[] args) { 3 String str="Hello"; 4 String str2=",World!"; 5 String str3=str+str2; 6 System.out.println(str3);// Hello,World! 7 str="Linux"; 8 System.out.println(str);//Linux 9 } 10 }
从上面程序我们可知:str和str2拼接之后形成了一个新的字符串,str和str2本身并没有变化。有人会有这样的疑问,当str="Linux";这条语句执行时,str的值不是变了吗?我们要区分清楚,字符串是引用类型变量,当执行完str="Linux";这条语句后,"Hello"这个字符串常量(也是一个字符串对象)并没有改变,知识它的句柄str指向了新的字符串对象"Linux"而已!
4、String buffers support mutable strings. Because String objects are immutable they can be shared.For example:
字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享它们。
1)String str="abc";
is equivalent to:
char data[] ={'a','b','c'};
String str=new String(data);
这也给我们提供了把数组转化成字符串的一种方法!
5、The Java language provides special support for the string concatenation operator ( + ),and for conversion of other objects to strings.
String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method.
String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java.
1)Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。
6、Constant Pool(常量池)
常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(String)的常量值(final)还包含一些以文本形式出现的符号引用,比如:
◆类和接口的全限定名;
◆字段的名称和描述符;
◆方法的名称和描述符。
虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。
对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。
1 public class StringDemo { 2 public static void main(String[] args) { 3 String str="Hello"; 4 String str2="Hello"; 5 System.out.println(str==str2);//true 6 char []ch={'a','b','c'}; 7 char []ch2={'a','b','c'}; 8 System.out.println(ch==ch2);//false 9 } 10 }
由程序可知对象型的只有String类型,数组不可以!
上面程序的执行过程是:想创建一个"Hello"对象,然后str指向它,当执行String str2="Hello";这条语句时,先到常量池中查找看是否有"Hello"这个字符串对象,如果有的话,就把str2指向它,如果没有,就把新的字符串对象写入常量池中。
2、String字符串的本质
String字符串 = char[] + 操作(toUpperCase(),concat())
String的底层就是char[]数组,是char[]数组的封装类
类: 数据+相关的操作
char数组只是数据, 没有操作
String用来表示文本,即一系列 Unicode 字符。字符串是我们开发过程中频繁使用的对象,我们在软件界面上提示用户的所有一切都是字符串:不管是发票的日期还是发票的编号,或者是发票的金额虽然在 定义数据类型时候分别应该是DateTime、double或string,但当界面呈现的时候,都是以文本的形式呈现,也就是string格式。
字符串是 Unicode 字符的有序集合,用于表示文本。所以String 对象是 System.Char 对象的有序集合,用于表示字符串。String 对象的值是该有序集合的内容,并且该值是不可变的。字符串本质是字符数组——这是一个非常重要的概念,了解这个概念就可以全面的理解和把握字符串的各种特 征。
public static void arraycopy {Object src,
int srcPos,
Object dest,
int destPos,
int length)
- 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从
src
引用的源数组到dest
引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于length
参数。源数组中位置在srcPos
到srcPos+length-1
之间的组件被分别复制到目标数组中的destPos
到destPos+length-1
位置。如果参数
src
和dest
引用相同的数组对象,则复制的执行过程就好像首先将srcPos
到srcPos+length-1
位置的组件复制到一个带有length
组件的临时数组,然后再将此临时数组的内容复制到目标数组的destPos
到destPos+length-1
位置一样。If 如果
dest
为null
,则抛出NullPointerException
异常。如果
src
为null
, 则抛出NullPointerException
异常,并且不会修改目标数组。否则,只要下列任何情况为真,则抛出
ArrayStoreException
异常并且不会修改目标数组:src
参数指的是非数组对象。dest
参数指的是非数组对象。src
参数和dest
参数指的是那些其组件类型为不同基本类型的数组。src
参数指的是具有基本组件类型的数组且dest
参数指的是具有引用组件类型的数组。src
参数指的是具有引用组件类型的数组且dest
参数指的是具有基本组件类型的数组。
否则,只要下列任何情况为真,则抛出
IndexOutOfBoundsException
异常,并且不会修改目标数组:srcPos
参数为负。destPos
参数为负。length
参数为负。srcPos+length
大于src.length
,即源数组的长度。destPos+length
大于dest.length
,即目标数组的长度。
否则,如果源数组中
srcPos
到srcPos+length-1
位置上的实际组件通过分配转换并不能转换成目标数组的组件类型,则抛出ArrayStoreException
异常。在这种情况下,将 k 设置为比长度小的最小非负整数,这样就无法将src[srcPos+
k]
转换为目标数组的组件类型;当抛出异常时,从srcPos
到srcPos+
k-1
位置上的源数组组件已经被复制到目标数组中的destPos
到destPos+
k-1
位置,而目标数组中的其他位置不会被修改。(因为已经详细说明过的那些限制,只能将此段落有效地应用于两个数组都有引用类型的组件类型的情况。) - 参数:
src
- 源数组。srcPos
- 源数组中的起始位置。dest
- 目标数组。destPos
- 目标数据中的起始位置。length
- 要复制的数组元素的数量。
public static char[] copyOf(char[] original,int newLength)
- 复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。对于在副本中有效而在原数组无效的所有索引,副本将包含 '\\u000'。当且仅当指定长度大于原数组的长度时,这些索引存在。
- 参数:
original
- 要复制的数组newLength
- 要返回的副本的长度- 返回:
- 原数组的副本,截取或用 null 字符填充以获得指定的长度
1 public class P2_CharArrayDemo { 2 public static void main(String[] args) { 3 //Java 可以将char[]作为字符串处理 4 char[] chs1={'北','京'}; 5 char[] chs2={'我','爱','你'}; 6 System.out.println(chs1);//北京 7 //char[]运算需要编程处理,如连接: 8 char[] chs3=Arrays.copyOf(chs1, chs1.length+chs2.length); 9 System.arraycopy(chs2, 0, chs3, chs1.length, chs2.length); 10 System.out.println(chs3);//北京我爱你 11 //String API提供了简洁的连接运算: 12 String s1="北京"; 13 String s2="我爱你"; 14 String s3=s1.concat(s2);//String API方法 15 //字符串转大写底层原理: 16 char[] chs4={'A','a','c','f'}; 17 char[] chs5=Arrays.copyOf(chs4, chs4.length); 18 for(int i=0;i<chs5.length;i++){ 19 char c=chs5[i]; 20 if(c>='a'&&c<='z'){ 21 chs5[i]=(char)(c+'A'-'a');// 22 } 23 } 24 } 25 }
1 public class P2_StringAPIDemo { 2 public static void main(String[] args) { 3 //String API不改变String对象内容: 4 String s1="ABCD"; 5 String s2="Abcd"; 6 //字符串(原)对象是不变的!String API不改变String对象内容 7 String s3=s1.toUpperCase();//不需要改变,返回原对象 8 String s4=s2.toUpperCase();//需要改变,返回新对象 9 System.out.println("s1==s3:"+(s1==s3));//true 10 System.out.println("s2==s4:"+(s2==s4));//false 11 //String 和 char[] 可以相互转换: 12 char[] chs={'我','爱','中','国'}; 13 String china=new String(chs); 14 System.out.println(china); 15 String str="龙年吉祥"; 16 chs=str.toCharArray(); 17 } 18 }
理解String内存存储机制的一个经典程序
1 public class P2_StringParamDemo { 2 public static void main(String[] args) { 3 String s="AA"; 4 char[] chs={'A','A'}; 5 test(s,chs); 6 System.out.println(s);//AA 7 System.out.println(chs);//SA 8 } 9 public static void test(String str,char[] chs){ 10 str="BC"; 11 chs[0]='S'; 12 chs=str.toCharArray(); 13 } 14 }
StringBuilder和String 的连接性能测试
1 public class P4_StringBuilderVSStringDemo { 2 public static void main(String[] args) { 3 String str="Hello,"; 4 String str2=str+"World"; 5 //String 字符串常量,创建之后不能再改变。 6 System.out.println(str==str2);//false 7 8 StringBuilder sb=new StringBuilder(); 9 sb.append('中'); 10 sb.append('国'); 11 sb.append('我'); 12 sb.append('爱'); 13 sb.insert(1, '你'); 14 StringBuilder sb2=sb.replace(0,1,"北京"); 15 //StringBuilder 创建之后可以再改变。 16 System.out.println(sb==sb2);//true 17 System.out.println("*************************************"); 18 /* 19 * String 字符串常量,创建之后不能再改变;StringBuilder 创建之后可以再改变。 20 * 当对String 类进行连接时,会产生新的对象; 21 * 而对StringBuilder 进行连接操作不会产生新的对象。 22 * 因此StringBuilder的连接操作比String块很多。 23 */ 24 System.out.println(testString(50000));//5063 25 System.out.println(testStringBuilder(50000));//0 26 } 27 public static long testString(int n){ 28 long start = System.currentTimeMillis(); 29 String s=""; 30 for(int i=0;i<n;i++){ 31 s+="A"; 32 } 33 long end=System.currentTimeMillis(); 34 return end-start; 35 } 36 public static long testStringBuilder(int n){ 37 long start=System.currentTimeMillis(); 38 StringBuilder buf=new StringBuilder(); 39 for(int i=0;i<n;i++){ 40 buf.append("A"); 41 } 42 long end=System.currentTimeMillis(); 43 return end-start; 44 } 45 }
经典面试题
1 public class Test2 { 2 public static void main(String[] args) { 3 System.out.println('0'+0);//48 4 System.out.println('1'+0);//49 5 System.out.println('a'+0);//97 6 System.out.println('A'+0);//65 7 } 8 }
1 public class StaticStringDemo { 2 public static final int I=123; 3 public static final String S="123ABC"; 4 public static void main(String[] args) { 5 String s1="123ABC";//s1= 123ABC 6 String s2="123"+"ABC";//s2= 123ABC 7 String s3='1'+"23ABC";//s3= 123ABC 8 String s4='1'+'2'+'3'+"ABC";//s4= 150ABC 9 String s5="1"+"2"+"3"+"ABC";//s5= 123ABC 10 String s6=123+"ABC";//s6= 123ABC 11 String s7=1+2+3+"ABC";//s7= 6ABC 12 String s8=S+"";//s8= 123ABC 13 String s9=I+"ABC";//s9= 123ABC 14 15 String ss="ABC"; 16 String s10=123+ss;//编译期,不处理,运算期间运算。 17 String s11=new String("123ABC");//运行期间运算。 18 ////new String("123ABC");运行期间运算 19 String s12=new String("123"+"ABC"); 20 System.out.println(S==s1);//true 21 System.out.println(S==s2);//true 22 System.out.println(S==s3);//true 23 System.out.println(S==s4);//false 24 System.out.println(S==s5);//true 25 System.out.println(S==s6);//true 26 System.out.println(S==s7);//false 27 System.out.println(S==s8);//true 28 System.out.println(S==s9);//true 29 System.out.println(S==s10);//false 30 System.out.println(S==s11);//false 31 System.out.println(S==s12);//false 32 } 33 }
2、String的两种实例化方式
1、通过接收一个String类的对象,并重新实例化这个对象(这句话比较拗口,慢慢理解)
String本身是一个类,在String类中定义了如下的构造方法:
String(String original):初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
例如:String str=new String("Hello");
2、采用直接赋值的方式进行对象的实例化
例如:String str2="Hello";
1 public class StringDemo { 2 public static void main(String[] args) { 3 String str=new String("Hello"); 4 System.out.println(str=="Hello"); //false 5 String str1="Hello"; 6 System.out.println(str1=="Hello"); //true 7 String str2=new String("Hello"); 8 System.out.println(str==str2); //false 9 } 10 }
你可能对上述层序中的结果产生了疑惑,为什么会是这种结果呢?
下面让我们引入srueyonder的一篇博客,一切疑惑就会迎刃而解:JAVA String对象和字符串常量的关系解析
3、Java String对象和字符串常量的关系解析
1、字符串内部列表
JAVA中所有的对象都存放在堆里面,包括String对象。字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了。 虚拟机为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合,包括直接常量(string、integer和float point常量)和对其他类型、字段和方法的符号引用。
例如,String s = new String( "myString" );
其中"myString"是字符串常量,在编译时被存储在常量池的某个位置。 在解析阶段,虚拟机发现字符串常量"myString",它会在一个内部字符串常量列表中查找,如果没有找到,那么会在堆里面创建一个包含字符序列 [myString]的String对象s1,然后把这个字符序列和对应的String对象作为名值对( [myString], s1 )保存到内部字符串常量列表中。如下图所示:
引用的这篇博客到此结束,因为不是太理解,对其正确性保持质疑态度!
接下来我们引用另一篇博客来解释上面的疑惑:http://www.bianceng.cn/Programming/Java/201101/22511.htm
4、Java String字符串对象的创建及管理
Constant Pool常量池的概念:
在讲到String的一些特殊情况时,总会提到String Pool或者Constant Pool,但是我想很多人都不太明白Constant Pool到底是个怎么样的东西,运行的时候存储在哪里,所以在这里先说一下Constant Pool的内容。String Pool是对应于在Constant Pool中存储String常量的区域.习惯称为String Pool,也有人称为String Constant Pool.好像没有正式的命名。在java编译好的class文件中,有个区域称为Constant Pool,他是一个由数组组成的表,类型为cp_info constant_pool[],用来存储程序中使用的各种常量,包括Class/String/Integer等各种基本Java数据类型。
对于Constant Pool,表的基本通用结构为:
1 cp_info { 2 u1 tag; 3 u1 info[]; 4 }
tag是一个数字,用来表示存储的常量的类型,例如8表示String类型,5表示Long类型,info[]根据类型码tag的不同会发生相应变化。
对于String类型,表的结构为:
1 CONSTANT_String_info { 2 u1 tag; 3 u2 string_index; 4 }
tag固定为8,string_index是字符串内容信息,类型为:
1 CONSTANT_Utf8_info { 2 u1 tag; 3 u2 length; 4 u1 bytes[length]; 5 }
tag固定为1,length为字符串的长度,bytes[length]为字符串的内容。
(以下代码在jdk6中编译)
为了详细理解Constant Pool的结构,我们参看一些代码:
1 String s1 = "sss111"; 2 String s2 = "sss222"; 3 System.out.println(s1 + " " + s2);
由于"sss111"和"sss222"都是字符串常量,在编译期就已经创建好了存储在class文件中。
在编译后的class文件中会存在这2个常量的对应表示:
1 08 00 11 01 00 06 73 73 73 31 31 31 ; ......sss111.... 2 08 00 13 01 00 06 73 73 73 32 32 32 ; ......sss222....
根据上面说的String常量结构,我们分析一下:
开始的08为CONSTANT_String_info结构中的tag,而11应该是它的相对引用,01为CONSTANT_Utf8_info的 tag,06为对应字符串的长度,73 73 73 31 31 31为字符串对应的编码,接着分析,会发现后面的是对应"sss222"的存储结构。
经过上面分析,我们知道了11和13是两个字符串的相对引用,就可以修改class文件来修改打印的内容,把class文件中的00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 12 4D改成00 6E 00 04 00 03 00 00 00 24 12 10 4C 12 10 4D,程序就会输出sss111 sss111,而不是和原程序一样输出sss111 sss222,因为我们把对"sss222"的相对引用12改成了对"sss111"的相对引用10。
1 public class Test { 2 public static void main(String[] args) { 3 String s1 = "sss111"; 4 String s2 = "sss111"; 5 } 6 }
在上面程序中存在2个相同的常量"sss111",对于n个值相同的String常量,在Constant Pool中只会创建一个,所以在编译好的class文件中,我们只能找到一个对"sss111"的表示:
000000abh: 08 00 11 01 00 06 73 73 73 31 31 31 ; ......sss111
在程序执行的时候,Constant Pool会储存在Method Area,而不是heap中。另外,对于""内容为空的字符串常量,会创建一个长度为0,内容为空的字符串放到Constant Pool中,而且Constant Pool在运行期是可以动态扩展的。
关于String类的说明
1.String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,就是因为如此,才说String类型是不可变的(immutable)。
2.String类有一个特殊的创建方法,就是使用""双引号来创建.例如new String("i am")实际创建了2个String对象,一个是"i am"通过""双引号创建的,另一个是通过new创建的.只不过他们创建的时期不同, 一个是编译期,一个是运行期!
3.java对String类型重载了+操作符,可以直接使用+对两个字符串进行连接。
4.运行期调用String类的intern()方法可以向String Pool中动态添加对象。
String的创建方法一般有如下几种
1.直接使用""引号创建;
2.使用new String()创建;
3.使用new String("someString")创建以及其他的一些重载构造函数创建;
4.使用重载的字符串连接操作符+创建。
例1
1 String s1 = "sss111"; 2 //此语句同上 3 String s2 = "sss111"; 4 System.out.println(s1 == s2); //结果为true
例2
1 String s1 = new String("sss111"); 2 String s2 = "sss111"; 3 System.out.println(s1 == s2); //结果为false
例3
1 String s1 = new String("sss111"); 2 s1 = s1.intern(); 3 String s2 = "sss111"; 4 System.out.println(s1 == s2); //结果为true
public String intern()
返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类 String
私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String
对象的字符串(该对象由 equals(Object)
方法确定),则返回池中的字符串。否则,将此 String
对象添加到池中,并且返回此 String
对象的引用。
它遵循对于任何两个字符串 s
和 t
,当且仅当 s.equals(t)
为 true
时,s.intern() == t.intern()
才为 true
。
例4
1 String s1 = new String("111"); 2 String s2 = "sss111"; 3 String s3 = "sss" + "111"; 4 String s4 = "sss" + s1; 5 System.out.println(s2 == s3); //true 6 System.out.println(s2 == s4); //false 7 System.out.println(s2 == s4.intern()); //true
例5
这个是The Java Language Specification中3.10.5节的例子,有了上面的说明,这个应该不难理解了
public class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.print((hello == "Hello") + " "); System.out.print((Other.hello == hello) + " "); System.out.print((hello == ("Hel"+"lo")) + " "); System.out.print((hello == ("Hel"+lo)) + " "); System.out.println(hello == ("Hel"+lo).intern()); //运行结果:true true true false true } } class Other { static String hello = "Hello"; }
输出结果为true true true true false true,请自行分析!
结果上面分析,总结如下:
1.单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool中;
2.使用new String("")创建的对象会存储到heap中,是运行期新创建的;
3.使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中;
4.使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;
5.使用"aa" + s1以及new String("aa" + s1)形式创建的对象是否加入到String Pool中我不太确定,可能是必须调用intern()方法才会加入。
还有几个经常考的面试题:
1.
String s1 = new String("s1") ;
String s2 = new String("s1") ;
上面创建了几个String对象?
答案:3个 ,编译期Constant Pool中创建1个,运行期heap中创建2个.
2.
String s1 = "s1";
String s2 = s1;
s2 = "s2";
s1指向的对象中的字符串是什么?
答案: "s1"
5、Java的String例解
引自博客:http://blog.csdn.net/zk_zhangkai/article/details/7927247
引用理由:简单明了,值得推荐
1 String a="a"; 2 String b="b"; 3 String c="ab"; 4 String d="ab"; 5 String e=a+b;
程序中用来存放数据的内存分为四块
1、全局区(静态区)(static)
2、文字常量区 :常量字符串就是放在这块区域,即是我们常说起的常量池。
3、栈区(stack):存放函数的参数值,局部变量的值等。
4、堆区(heap) : 存放对象
当我们定义字符串
String a = "a";
a在栈区,“a”是字符串常量,在常量池中
String b = "b";
b在栈区,“b”在常量池
String c="ab";
c在栈区,“ab”在常量池
String d="ab";
d在栈区,这个时候常量池里已经有"ab",所以直接使用已经有的那个“ab”
所以这个时候c和d都指向的常量池里面的同一个“ab”
String e=a+b;
e在栈区,a+b实际上产生了一个新的String对象,既然是String对象,所以结果“ab”放在堆区中,即e指向的是堆里的“ab”
这样的情况下,c==d为true,c==e为false
另外,如果定义的是字符串对象
String str1 = new String("ab");
str1在栈区,创建的“ab”字符串对象在堆区
String str2 = new String("ab");
str2在栈区,又创建的一个新的“ab”对象也在堆区,不过和刚才的“ab”不是同一个。
相当于堆区中有两个字符串对象,不过正好内容都是“ab”而已。
所以str1==str2为false
常量池里面放着的常量字符串可以重复使用,但是必须是你直接使用的该字符串,像a+b这种形式虽然得到的结果是“ab”,但并不是使用的字符串常量“ab”