[转]Java里的字符串, String类简单介绍

一, Java里的字符串.

首先声明:html

1.1 字符串跟String类是不一样的概念

 

本文涉及两个重点,  1个是字符串, 1个是String类. 它们虽然有联系, 可是倒是彻底不一样的两个概念!java

 

咱们能够参考jdk api中文里对String类的解释:
面试

public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence
编程

 

String 类表明字符串。Java 程序中的全部字符串字面值(如 "abc" )都做为此类的实例实现。
字符串是常量;它们的值在建立以后不能更改。字符串缓冲区支持可变的字符串。由于 String 对象是不可变的,因此能够共享。
api

 

由上面的解析咱们见到几个不易理解的地方:多线程

例如: 函数

字符串是常量?this

它们的值不能改?编码

由于..因此..  这什么逻辑?.net

 

在实际编程当中, 咱们以为字符串变量的值能够更改的呀?

 

本人认为, 你们不必担忧本身的理解能力, 中文jdk api的翻译实在是很没有节操的.

上面解释的最后一句话的原文是这样的:

Strings are constant; their values cannot be changed after they are created.String buffers support mutablestrings. Because String objects are immutable they can be shared

 

本人渣翻:

字符串是常量; 它们的值一旦建立不能更改. 然而String类的引用(变量 or 内存)却能够指向不一样的字符串. 是由于字符串对象虽然是不能修改的, 可是它们的地址能够共享.

 

原文和本人翻译都有两种颜色的单词,  红色就是指上面第一个概念字符串.  而蓝色指的另1个概念String类.

相信即便本人修改了翻译, 仍然会有人以为仍是不能理解, 请往下看:

 

 

 

1.2 java里字符串的定义

 

注意这个不是String类的定义哦, 定义以下:

Java里的字符串就是存放于数据区(静态区)以Unicode编码的字符集合.

可见java里的字符串跟c语言的字符串以下两个本质上的区别:

 

1.2.1 Java里字符串用Unicode编码

c语言中的字符串里面的字符串是用ASCII编码的, ASCII只用1个字节(byte)的内存表示1个字符. 可是1个字节的内存数量不足以表示全世界那么多种字符.

例如1个汉子就须要2个字节来表示.

 

因此c语言里的某些字符处理函数, 若是参数传入1个汉字可能就会出错, 由于毕竟字符的占用内存长度不一样.

 

而Unicdoe也叫万国码, 它用两个字节去表示任何1个字节, 不管是字母仍是汉字. 因此利用java来作内存处理更加方便, 跨平台性很是好.

缺点就是比c语言字符处理更加耗内存.

 

 

 

1.2.2 Java里字符串存放在数据区(静态区).

 

我以前的博文见过, java程序相似于c语言, 运行时会把程序占用的内存大体分割成几个部分.

分别是

stuck(栈区), Heap(堆区), Data(数据区)和代码区 

其中数据区用于存放静态变量和字符串常量.

见下图

 

 

一, Java里的字符串.

 

 

1.3 为何说java里的字符串是常量, 不可修改的.

 

1.3.1 通常的类指向的是变量

 

关于这点, 须要对比才能讲得清楚.

这里咱们利用1个自定的类来举个例子:

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. package String_kng;  
  2.   
  3. class Human_1{  
  4.     int id;  
  5.     int age;  
  6.     public Human_1(int id, int age){  
  7.         this.id = id;  
  8.         this.age = age;  
  9.     }  
  10.     public String toString(){  
  11.         return "id is " + id + ","  + " age is " + age;  
  12.     }  
  13. }  
  14.   
  15. public class String_2{  
  16.     public static void f(){  
  17.         Human_1 h = new Human_1(1,30);  
  18.         Human_1 h2 = h; //  
  19.         System.out.printf("h: %s\n", h.toString());   
  20.         System.out.printf("h2: %s\n\n", h.toString());   
  21.   
  22.         h.id = 3;  
  23.         h.age = 32;  
  24.         System.out.printf("h: %s\n", h.toString());   
  25.         System.out.printf("h2: %s\n\n", h.toString());   
  26.   
  27.         System.out.println( h == h2 );  
  28.     }  
  29. }  


上面例子中定义了1个Human_1的类, 只有2个成员id和age.

 

下面f()中首先实例化了1个Human_1的对象h. 

而后定义另外1个引用h2, 而后把h的地址赋予给h2 ( Human_1 h2 = h)

而后输出h, h2的值, 它们是同样的.

 

而后修改h的值, 

再次输出h h2的值, 发现h2的值也被修改.

最后用 " == " 来比较h 和 h2所指向的地址.

明显它们二者所指向的地址是相同.

 

输出:

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. [java] h: id is 1, age is 30  
  2. [java] h2: id is 1, age is 30  
  3. [java]   
  4. [java] h: id is 3, age is 32  
  5. [java] h2: id is 3, age is 32  
  6. [java]   
  7. [java] true  

 

其实上面例子能够理解成:

首先用1个容器h2 来保存 h1所指向的地址.

而后修改h1的值, 最后h1的地址没变化.

 

也就是说:

h这个对象虽然值被修改了, 可是指向的内存地址没有变化, 变的是该内存的内容(值)

 

画张图便于理解:

 

如上图可见:

1  对象名h 和 h2 自己是都是局部变量, 位于栈区中, 里面存放的是1个地址.

2. 该地址指向的是堆区的一块内存, 这块内存是用new Human()划分出来的. 并且把头部地址赋予对象名h

3. 该内存包括两个部分, 1个用于存放成员id的值, 另1个存放成员age的值.

 

可见, 不管对象h成员的值如何改变, 变的只是堆区内存的存放内容, 而堆区内存地址是没变化的. 对象引用h的指向也没有变化.

咱们通常把这种内存地址不变, 值能够改变的东西成为变量.

意思就是内存地址不变的前提下内存的内容是可变的.

 

注意, 上面的例子不说是对象引用h是1个变量,  而是说h指向的内存是1个变量

 

1.3.2 java里的字符串是常量

 

将上面的例子改一下, 把Human_1类改为String类:

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. package String_kng;  
  2.   
  3. public class String_3{  
  4.     public static void f(){  
  5.         String s = "cat";  
  6.         String s2 = s;  
  7.   
  8.         System.out.printf("s: %s\n", s);   
  9.         System.out.printf("s2: %s\n", s2);   
  10.         System.out.println(s == s2);   
  11.   
  12.         s = "dog";  
  13.         System.out.printf("\ns: %s\n", s);   
  14.         System.out.printf("s2: %s\n", s2);   
  15.         System.out.println(s == s2);   
  16.     }  
  17. }  

 

逻辑跟上面的例子基本没区别, 也是首先实例化1个String对象s, 它的值是s;

而后将s所指向的地址保存在另个引用s2.

 

这时输出s 和 s2的值, 它们固然是相等的.

这时"修改"s的值为"dog"

再输出s 和 s2的值, 却发现s的值变成dog了, 可是s2的值仍是cat..  并且它们的所指向的地址也再也不相等.

输出结果:

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. [java] s: cat  
  2. [java] s2: cat  
  3. [java] true  
  4. [java]   
  5. [java] s: dog  
  6. [java] s2: cat  
  7. [java] false  

 

 

为何s 和 s2所指向的地址一开始是相等的, 一旦s的修改成dog后, s 和 s2所指向的地址就不等呢.

缘由就是这句代码:

s = "dog";  

并非修改s所指向的内存地址, 而是改变了s的指向, 也就是修改了s的所指向的地址啊.

 

画两张图:

s的值"修改"前:

 

由上图可见:

1. String类也是java的类, 因此它的实例化对象也须要在堆区划份内存。

2. 两个对象引用s 和 s2这时都指向了堆区同1块对象内存。因此它们的所指向地址是相等的。

3. 字符串真正的地址不是再堆区中, 是在数据区中的。 而堆区对象内存中有其中1个对象成员保存了该字符串在数据区的真正地址

 

s的值"修改"为dog后:

 

由上图可见:

1. s = "dog" 并非修改s所指向的内容. 而是在堆区和数据区各划分了1个新的内存. 其中数据区划分1个新的字符串"dog" , 堆区划分1个新的String对象内存, 保存了dog的字符串地址.

2. 固然以前那个堆区对象内存和数据"cat"的内存是由 String s = "cat" 这条语句建立,关于String类语法机制后面会再讲。

3. s = "dog" 不但在数据区和堆区都各自建立1个新内存, 并且还改变了本身所指向的地址, 因此这时s 和 s2 再也不相等.

4. 关键是原来数据的字符"cat" 并无被修改! 也不可能被修改.

 

咱们通常把这种内存值不能改变, 只能经过引用去指向另1块的东西叫作常量.

 

 

1.3.3 java里字符串不能修改的一些小结.

 

其实从另一些方面一也能体现出java字符串不能修改的.

例如一些java的内部类, 如Calendar(日期)通常都会提供一些setXXXX的方法让程序猿去修改对应的值. 例如 setYear(), setDate().等等

而String类是没有这类setXXXX方法.

 

虽然字符串在数据区中的内存不能修改, 可是咱们能够为String类的对象指向另外一块内存. 因此这个特性在编程形成的影响不大.

那么原来的内存怎么办呢? 放心, java的内存回收机制会收拾它们的..

 

 

 

二, Java里的String类.

 

2.1 java里 String 类的 本质

 

String类的书面解释在本文的1.1 章里提升过了, 是1个用于字符串的类.

可是这个解释并无指明String类的本质.

 

咱们知道, Java类的本质大体上能够理解为 成员(属性) 和 方法的集合体.

String类也同样, 只不过String类有1个关键的成员, 这个成员保存着数据区的某个字符串的内存地址. 能够理解为1个指针.

而String类的方法是一些对对应字符串的查询方法(例如indexOf(), charAt()等). 注意, 并无对这个字符串进行修改的方法哦, 字符串是常量, 不能修改.

虽然String类不能修改字符串, 可是上面保存字符串地址的成员倒是能够被改变的, 也就是说String类的对象能够指向另1个字符串.

 

画个图:

 

 

见上图, java的String类实例化1个对象后, 会在堆区划分一块对象的内存, 其中1个关键成员存放的是数据区字符串的地址.

而下面若干个方法内存, 存放的是该函数(方法)在代码区的2进制代码的地址.

 

 

 

2.2 String类实例化对象的第一个方法. new String("abc")

 

固然, String类的构造函数有不少个(参数不一样), 可是在coding中,经常使用的实例化对象方法无非是两种.

第一种就是与其余类同样, 利用构造方法.

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. String s = new String("abc");  


上面的代码作了下面若干个事情.

1. 在数据区中划分一块内存存放字符串, 值是"abc", 这块内存一旦建立, 值"abc" 不能被修改.

2. 在堆区划分1块对象内存, 其中小块用于存放上面字符串的地址, 另外一些用于存放函数指针.

3. 在栈区划分一块内存, 存放上面堆区的头部地址.

 

下面是1个例子:

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. package String_kng;  
  2.   
  3. public class String_4{  
  4.     public static void f(){  
  5.         String s = new String("cat");  
  6.         String s2 = new String("cat");  
  7.   
  8.         System.out.printf("s: %s\n", s);   
  9.         System.out.printf("s2: %s\n", s2);   
  10.         System.out.println(s == s2);   
  11.         System.out.println(s.equals(s2));  
  12.     }  
  13. }  

 

上面利用new 实例化了两个对象s和s2 , 它们所指向的字符串值都是"cat"

而后用 "==" 和 equals来比较二者

输出:

 

[plain]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. [java] s: cat  
  2. [java] s2: cat  
  3. [java] false  
  4. [java] true  

 

 

可见用equals 来比较s 和 s2, 它们是相等的, 由于它们的内容相同. 并且equals方法在String类里重写过了.

而用 "==" 比较的是两个对象s 和 s2所指向的地址, 它们所指向的地址是不一样的. 

以下图:

 

亦即系讲, 两个new语句分别在数据区和堆区各自都划分2个内存.

数据区中有两个字符串内存, 它们的值是同样的都是"cat".

堆区有两个对象内存, 它们分别保存了各自对应的字符串地址.

而stuck区中两个s1 s2 保存了各自的堆区内存地址.  这两个地址明显是不一样的. 也就是 s == s2 返回false的缘由.

 

 

 

2.3 String类实例化对象的另外一个方法.  = "abc"

 

事实上, 咱们在编程中新建1个字符串更多状况下会用以下的方式:

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. String s = "abc";  

 

这种方式更上面那种有什么区别呢?

 

[java]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. package String_kng;  
  2.   
  3. public class String_5{  
  4.     public static String g(){  
  5.         String s4 = "cat";  
  6.         return s4;  
  7.     }  
  8.   
  9.     public static void f(){  
  10.         String s = new String("cat");  
  11.         String s2 = "cat";  
  12.         String s3 = "cat";  
  13.         System.out.printf("s: %s\n", s);   
  14.         System.out.printf("s2: %s\n", s2);   
  15.         System.out.printf("s3: %s\n", s3);   
  16.           
  17.         System.out.println(s == s2);   
  18.         System.out.println(s2 == s3);   
  19.         System.out.println(s2 == g());   
  20.     }  
  21. }  




这个例子步骤也不复杂:

 

 

首先f()方法里 利用第一种方法实例化了1个值为"cat"的对象s

而后里利用第二种方法 又 建立了两个String 对象s2 和 s3, 它们的值都是"cat".

而后用"==" 来比较它们.

而后f()方法调用g()方法, g()方法利用第二方式实例化了1个值为"cat"的String 对象s4 
最后用 "==" 比较s2 和 s4 的地址.

 

 

输出:

 

[plain]  view plain copy 在CODE上查看代码片 派生到个人代码片
 
  1. [java] s: cat  
  2. [java] s2: cat  
  3. [java] s3: cat  
  4. [java] false  
  5. [java] true  
  6. [java] true  

 

由结果得知, s4 和 s2 和 s3的地址是相同的! 而由第一种方法建立的s 跟前面三者地址不一样.

 

因此结论以下:

利用 = "cat" 方式建立1个String对象时, java 首先会检测当前进程的数据区是否有1个以相同方式建立的值是同样的字符串存在.

若是无, 则相似 new Sring("cat")方式, 在数据区和堆区都各自划分一块新内存, 用于该建立的对象.

若是有, 则直接把该对象的地址指向 已存在的堆区内存地址.

 

也就是讲, 在f() 里的String s2 = "cat" 至关于执行了 String s2 = new String("cat");

而在f()里的 String   s3 = "cat" 至关执行了String s3 = s2;

而在g()里, 理论上g()是不能访问f()里的 局部变量的, 可是g()仍是检测到数据区存在用相同方式建立并且值1个样的字符串.

因此s4 也指向了堆区的那一块内存.

 

以下图:

 

这个例子说明了, 在同1个java程序中, 全部用 " = "abc" " 方式建立的并且具备相同值的多个String对象其实都是同1个对象. 由于它们指向同一块堆区的内存.

因为这种特性, 因此这种用" = "abc"" 方式建立的对象十分适合作 synchronized对象锁 要锁的对象.   不用担忧锁的是两个不一样的对象致使 多线程同步失败.

 

 

 

三, String类的经常使用方法.

 

下面是应付面试的, 你们看看就好.

1. 

public char charAt(int index) //返回字符串中第index个字符

 

2. 

public int length()  //返回字符串的长度

 

3.

public int indexOf(String str)

返回字符串中出现str的第1个位置

 

4.

public int indexOf(String str, int fromIndex)

返回字符串中, 从第fromIndex个字符数起, 出现str的第1个位置, 这个方法是上面方法的重载

 

5.

public boolean equalsIgnoreCase(String str)

忽略大小写, 比较两个字符是否相等.

 

6.

public String replace(char oldChar, char newChar)

返回1个新字符串, 该新字符串内的oldChar被newChar替换掉, 注意旧字符串没有被修改.

 

7.

public boolean startsWith(String prefix)

判断字符串是否以 prefix 开头

 

8.

public boolean endsWith(String suffix)

判断字符产是否以suffix 结尾

 

9.

public String subString(int beginIndex)

截取从第beginIndex个字符开始到最后1个字符, 返回1个新字符串

 

10.

public String subString(int beginIndex, int endIndex)

截取从第beginIndex个字符开始, 第endIndex个字符, 返回1个新字符串, 是上面方法的重载

 

11.

public static String valueOf(...)

注意这个是静态方法. 能够把其余基本数据类型转换成String对象

 

12. 

Integer.parseInt(String s)

这个是另1个类Integer 的敬爱函数, 能够把字符串转换成int类型.  会抛出异常..

相关文章
相关标签/搜索