Java中的字符串


一、介绍

“可以证明,字符串的操作是计算机程序设计中最常见的行为”,字符串在我们写程序中有多方位的应用,所以本文将深入研究一下字符串在Java中的一些特性。

二、Java中字符串不变性

我们用一段代码来开头:

 1  public  static String Test(String s){
 2         return s.toUpperCase();
 3     }
 4     public static void main(String[] args) {
 5         String s= "help";
 6         System.out.println(s);
 7         String ss = Test(s);
 8         System.out.println(ss);
 9         System.out.println(s);
10 
11     } 

  我们创建了一个字符串并且将字符串传入函数进行处理,再返回处理后的字符串和原本的字符串,会发现原本的字符串没有发生任何变化,而产生了一个它的“变种”,实际上我们在传字符串进去的时候并没有像传对象那般传入“原本的它”,而是先会复制一份,把这份拷贝的传入函数里面,而原本的字符串一直呆在原地没有变化,而这一点恰恰是我们所需要的,只有这样我们才能方便的维护一个字符串的安全,当函数执行完后,把那份拷贝的字符串丢掉即可。

  那既然说了不变性,那平常字符串的拼接是如何进行的呢?

三、“+”和StringBuilde

  在String不变性的条件下,虽然在某些方面带来了许多便利,你可以用很多变量去指向它、引用它,但由于你不能改变它的值,就无论怎么引用它的值都会变,但这就会出现一点点问题,当我们需要对多个字符串进行拼接的时候,我们就要拷贝多份String,每进行一次拼接,我们都要这么做,在大量的拼接操作下,这种方式会严重的影响到我们代码的效率,Java唯二的重载符号“+”和“+=”就是一个典型的例子,它们可以进行数值运算和字符串的拼接,如:

1 public static void main(String[] args) {
2         String s= "help";
3         String x = "me";
4         String y = "11";
5         String z = "zz";
6         String ss = s + x + y + z;
7     }

  我们最终得到字符串ss的过程可能是s和x先组成一个新对象,再由这个新的字符串对象和y和z进行继续的拼接,依此类推,这样拼接可以拼接成功,但会产生大量的中间字符串对象,要去处理这些额外的垃圾显然会耗费大量的资源,这样的拼接方式肯定是不明智的,那实际是怎样拼接的呢? 我们可以利用反编译手段来查看编译器到底是如何处理我们的字符串的:

1 public class string {
2   public string();
3   public static java.lang.String Test(java.lang.String);
4   public static void main(java.lang.String[]);
5 }

  由于代码过于长原因,这里没有截取全部代码,故事主要发生在底四行,第四行的具体字节码发生了很多事情,对于字符串来说其中起主要作用的是定义了一个StringBuild来拼接字符串,编译器很聪明,并没有单纯使用String来拼接,而是采用StringBuild的append方法,append可以起到拼接的作用,而这里要讲一下StringBuild的拼接方式的优点:StringBulid是一个可以改变大小字符串容器,它不像String那么死板,它可以轻松的进行字符串的拼接,在它需要向字符串进行转换时也十分容易,另外相似的还有StringBuffer(这个线程安全),具体三者的比较我会另外写一篇,这里不做赘述,总之,编译器还是采用了一种高效的方式去拼接字符串,那是否意味着我们可以在代码上对String进行任意对操作?既然编译器能帮我们转成高效的方式?答案是否定的,在有些时候编译器也无法一定的高效,例如:

1  public static void main(String[] args) {
2         String []x = new String[]{"xx","ss","ee","pp"};
3         String result = "";
4         for(int i = 0; i < x.length; i++){
5             result+=x[i];
6         }
7         return;
8     }

  在此段代码中每次循环会进行一次字符串的拼接,我们这里最好的解决方法其实就是在循环外创建一个StringBuild去进行字符串的拼接,那编译器是如何做的?它并没有我们这么聪明,它在循环内创建了一个StringBuild来进行了一次字符串拼接,但循环内部出了一次循环再进入它发现又要进行一次拼接,然后又创建了一个StringBuild进行拼接,就这样,由于是在循环内部进行的拼接,编译器每次拼接都会创建一个StringBuild,而如果直接在外面创建StringBuild,如:

1 public static void main(String[] args) {
2         String []x = new String[]{"xx","ss","ee","pp"};
3         StringBuilder result = new StringBuilder("");
4         for(int i = 0; i < x.length; i++){
5             result.append(x[i]);
6         }
7        String p =  result.toString();
8         return;
9     }

  这样编译器就不用连续创建StringBuild来进行拼接,所以在大量出现字符串的拼接等操作的时候,最好的方法就是先转换为StringBuild进行拼接完成后再利用toString转化为字符串,此外StringBuild还具有丰富的操作函数,可以为我们操作字符串提供便利。

四、String上的操作

  • length:返回String中的字符个数
  • charAt(): 利用索引来访问字符串
  • getChars()和getBytes :得到字符串对应索引的复制,以Char和Byte返回
  • toCharArray() :生成一个char[],包含字符串的所有字符
  • compareTo() :按字典顺序比较两个字符串,返回值为负数、零、正数
  • contains():查看String对象是否含有某内容
  • indexOf()和 lastlndexOf(): 如果String不含有该元素,则返回-1,否则则返回起始索引,lastlndexOf()则是从后向前索引
  • subString():含有两个参数,起始坐标和截止坐标,返回这两个之间的字符串,不包含截止坐标的元素
  • concat(): 参数是另一个字符串,返回的是两个字符串的连接
  • toLowercase()和toUpperCase(): 返回字符串的小写和大写
  • trim():返回字符串去除两端空格的字符串
  • valueOf():有很多重载版本,参数可以是Object、char[]等,返回一个表示参数内容的String
  • intern():为每一个唯一的字符序列生成一个且仅生成一个String引用