字符串、格式化、正则表达式【上】

(这一个章节,将会提及到Java里面很特殊的一个数据类型:String,其类型的主要麻烦在于我们经常会用到它,而且针对它的“不可变”,很多时候很难去理解。附带这个章节会讲到很多关于处理字符串格式的内容,包括使用正则表达式做验证以及使用日期、货币格式化处理,还会提及到的就是如果在使用JDBC的时候针对SQL的类型[java.sql包内]和针对Java的类型[java.util]的一些相互转换问题。这一个章节涵盖的内容可能比较小范围,但是我们在开发项目过程中会经常遇到,所以是不得不去了解和不得不去学习的一个章节。方便大家看的时候容易查找代码段,代码部分都有[$]作为段落查找前缀,如果有什么笔误请大家来Email告知:[email protected],谢谢!)

本章目录
1.不可变类
2.String详解
3.StringBuilder和StringBuffer详解
4.正则表达式
5.关于转型
6.货币、时间、日期格式化


1.不可变类

  Java中的不可变类最典型的代表就是String类,而不可变类到底具有一个什么样的特性呢:
  • 不可变类的所有成员一般情况下都声明为private访问权限,有必要的情况下最好带上final修饰
  • 不可变类的class的一般使用final修饰
  • 只为该类提供get方法,而不去提供set方法
  • 提供一个构造方法,并且构造方法一般都是带参数的,在构造这个对象的时候一次性初始化它所有的数据
  • 类中如果出现组合的时候,从get方法得到这个组合对象的之前,先克隆这个组合对象
  • 组合对象传递给构造函数的时候,也需要先克隆一份
  • 若浅克隆不能符合不可变对象的行为,就要实现深度克隆【这里不再介绍深度克隆和浅克隆,可参考:http://blog.csdn.net/silentbalanceyh/archive/2009/08/25/4483600.aspx
  简单讲:
  不可变类:当使用Java代码获得了不可变类的一个实例引用的时候,不可以去修改这个实例的内容,不可变类一旦创建了过后,其内在成员变量的值就不可以被修改。
  可变类:当获得该类的一个实例引用的时候,可以改变这个实例的内容。
  常用的数据类型有:
可变类:StringBuffer、StringBuilder、Date
不可变类:Boolean、Byte、Character、Double、Float、Integer、Long、Short、String
  ——[$]关于String——
packageorg.susan.java.string;

public classStringImmutable {
    public static void main(Stringargs[]){
       String strOne = "Hello World";
        String strTwo = strOne;
        strOne = strOne +" LangYu";
       System.out.println(strOne == strTwo);
       System.out.println(strTwo);
       System.out.println(strOne);
    }
}
  上边这段代码可以得到输出:
false
Hello World
Hello World LangYu
  那么上边这段代码究竟在JVM里面发生了什么事情呢?也许我们都会觉得strOne指向的字符串对象“Hello World”在进行+操作的时候发生了改变,实际上不是这样,真正的情况如下图【这里不再解释JVM的内存模型,而且下边的图不考虑字符串池(常量池的子集)】

  上边是三句代码的执行流程,最后一个结果就可以理解的是strTwo和strOne的输出值,而为什么strTwo和strOne使用==的时候会输出false呢?这里不要觉得是strTwo的对象内容是Hello World,而strOne的对象内容是Hello World LangYu因为它们内容不相等才输出为false,针对String类而言==比较的是strTwo和strOne引用是否指向了同一个对象,只有Object类的==和equals方法是等价的,而String类重写了Object类的equals方法,关于String的细节这里不做详细讨论,下边会有说明。这里需要说明的是:String就是一个典型的不可变(Immutable)的类,而针对不可变的类在对它进行操作的过程中,JVM内部是按照上边的图示进行说明的。
  而针对不可变类的自定义过程,上边列举的特性是需要满足的,简单分析一下原理:
  【*:声明了该类为一个final过后,这个类就不能够被子类化了,这样就防止了子类对其内部的内容进行修改,而属性都是private也防止了针对这个类的属性进行直接访问,如果需要访问这个类的属性,只能通过get的方式访问,因为没有set方法,所以一旦通过构造函数初始化了这个类的属性值过后,这个类里面的属性就不能修改了,这就是该类的内容不能修改的本意,所以这个类的内容就是不可变的。而关于里面出现组合对象,为什么需要实现深度克隆而不是浅克隆呢,了解克隆的人都明白,如果仅仅是实现了浅克隆,则该组合的对象在进行了拷贝过后,其组合对象的引用还是指向了同一个对象,那么这种情况下就使得两个不可变对象的内部的组合对象引用指向了同一个对象,如果一个不可变对象里面组合对象引用的对象在初始化的时候改变了对象的内容,那么原来的不可变对象里面组合对象引用的内容也会随之改变,这样这个不可变对象的实现就出现了问题,所以在这种情况下,如果需要克隆的话,需要使用深度克隆。】
  读者可以在看String的讲解之前思考一下下边这段代码的输出:
  ——[$]关于String的混淆点——
packageorg.susan.java.string;

public classStringTester {
    public static void main(Stringargs[]){
       String strOne = "Hello World";
       String strTwo ="Hello World";
       System.out.println(strOne == strTwo);
       System.out.println(strOne.equals(strTwo));

        strOne =new String("Hello World");
       System.out.println(strOne == strTwo);
       System.out.println(strOne.equals(strTwo));
    }
}
  这段代码的输出为:
true
true
false
true

2.String详解
  i.String对象的创建原理:
  上边小节的最后一段代码是一段比较容易混淆的代码,其实了解了String类的对象的创建流程过后就不会混淆了,先看前两句:
StringstrOne = "Hello World";
StringstrTwo = "Hello World";
  在这两句里面,其实JVM做了不同的操作,可能这样讲不容易理解,解释一下:
  JVM中存在一个字符串池,当一个String对象使用=符号创建的时候,JVM会先去检索字符串池里面是否已经存在了对象内容和新创建的对象内容相匹配的对象,如果检索这个对象已经存在了就直接将新创建的引用指向该对象,如果这个对象不存在的时候,就在字符串池中重新分配空间构造一个String对象,然后让这个引用指向该对象(注意这种构造方式为JVM内部默认构造,和new的构造方式不一样,所以这里不能和new操作符构造对象的方式混淆,这种方式构造出来的String对象由字符串池进行维护,而使用new构造出来的String对象是不在String池中的)。若不使用=符号创建该对象而是直接使用new操作符创建对象,就直接省略掉检索对象池的过程。所以StringstrOne = "Hello World";执行的时候,String池里面没有任何String对象,所以JVM构造一个以Hello World为内容的对象,并且将这个对象交给字符串池进行维护,然后让strOne引用指向该对象;当代码运行到StringstrTwo = "Hello World";的时候,JVM会直接检索字符串池里面是否有Hello World对象,而且这个Hello World对象不是通过new操作符构造的,由字符串池直接维护,检索到了过后直接将该引用指向这个对象。所以:
  【*:上边两句话出现在同一个代码段的时候,第一句多了一个步骤就是让字符串池构造一个String对象的步骤。】
  关于字符串池其实从内存模型上理解,字符串池就是JVM内部的常量池的一个子集,也就是直接使用=符号的时候创建的String对象是放在常量池内的,而不是放在内存堆中的,在常量池中的内容是在编译期就被确定下来的,而且常量池维护的对象保证其内容的唯一性,所以在常量池中只可能出现一个内容相同的String对象
  new操作符可以认为是一个比较特殊的操作符,因为一旦使用new操作符,它构造的字符串没有字符串池(常量池)里面,而是运行时的时候在内存堆中动态分配的空间。上边这段代码的分析留给读者自己分析剩余的部分,再看一段代码,这段代码更加有说服力:
  ——[$]关于String的构造——
packageorg.susan.java.string;

public classNewStringCreate {
    public static void main(Stringargs[]){
       String strOne = newString("Hello World");
       String strTwo = newString("Hello World");
       System.out.println(strOne == strTwo);
       System.out.println(strOne.equals(strTwo));

        strOne ="Hello World";
       System.out.println(strOne == strTwo);
       System.out.println(strOne.equals(strTwo));

        strTwo ="Hello World";
       System.out.println(strOne == strTwo);
       System.out.println(strOne.equals(strTwo));
    }
}
  这段代码的输出为:
false
true
false
true
true
true
  仔细分析一下就可以知道:其实JVM内部存在的对象池里面的String对象和使用new操作符构造的String对象是有区别的,针对同样String内容的对象的检索过程是需要仔细思考的,上边这段代码实际上在JVM里面存在三个内容一样的String对象,有两个是通过new操作符创建的,另外一个是JVM默认创建的,使用new创建的String对象是在内存堆空间中,是运行时动态分配,而使用=符号创建的对象是在String的常量池中,是编译时就已经确定好的。这里还有一种情况需要简单说明一下:
  ——[$]关于String中带+的构造——
packageorg.susan.java.string;

public classAddStringCreate {
    public static void main(Stringargs[]){
       String strOne = "Hello ";//注意这里Hello后有个空白
       String strTwo = "World";
       String strThree ="Hello " + "World";//注意这里Hello后有个空白
       String strFour = "Hello World";
       String strFive = newString("Hello ") +"World";//注意这Hello后里有个空白
       String strSix = newString("Hello ") + strTwo;//注意这Hello后里有个空白
       String strSeven = "Hello "+ strTwo;//注意这Hello后里有个空白
       System.out.println(strThree == strFour);
       System.out.println(strFour == (strOne + strTwo));
       System.out.println(strThree == (strOne + strTwo));
       System.out.println(strFour == strFive);

       System.out.println(strThree.equals(strFive));
       System.out.println(strThree.equals(strOne + strTwo));

       System.out.println(strFive == strSix);
       System.out.println(strSeven == strFour);
       System.out.println(strSeven.equals(strFour));
    }
}
  仔细分析上边这段代码的输出:
true
false
false
false
true
true
false
false
true
  其实很清楚一点就是上边这段代码在构造strOne、strTwo、strThree的时候,除了请求字面量不一样,步骤是一模一样的,而strFour的构造步骤比strThree少了一个创建步骤,而且strThree有可能会有冗余内存开销【*:这里提及的步骤是JVM从编译java源代码一直到执行字节码两个过程,也就是从JVM接触这段代码开始,不要混淆和误解】,但是strFour和strThree的请求字面量都是一样的,所以这里的引用strOne、strTwo、strThree和strFour指向的对象都是存在常量池中的字符串池里面的,它们的值都是直接在编译时就已经决定好了。strFive和strSix的构造结果在进行==比较的时候会返回false,因为strFive和strSix使用的构造方式是运行时在堆内分配的,而strSeven虽然使用的是"Hello " + strTwo的格式,但是JVM会认为它的字面量值是不确定的,因为JVM并不知道在这句话执行之前,strTwo引用的对象是否一定是原来常量池里面内容。但是如果修改一段代码:
StringstrTwo = "World";
改成:
finalString strTwo ="World";
  就会发现System.out.println(strSeven == strFour);这句话的输出会为true,也就是倒数第二个输出为true。这里需要解释,也就是当strTwo的引用加上了final修饰过后,证明该引用是不能够改变它所指向的对象的,那么这样的情况下就是strTwo称为了一个不可变引用(不能改变引用指向其他对象),而又因为strTwo本身指向的是在常量池里面的一个不可变对象String,那么这样的情况就使得JVM在编译时已经可以知道strTwo的内容是不可变的了,所以这样的情况下strSeven在编译的时候,右边实际上是两个常量进行相加,而且不论发生任何代码改变都不会影响这句话,所以这种情况下strSeven实际上指向的就是常量池中的对象“Hello World”,所以这样的改动使得倒数第二句为true。但是如果像下边这样修改还是不会得到true的输出:
finalString strTwo =new String("World");
  因为一旦使用了new操作符过后,JVM还是会在运行时来分配空间,只要是运行时分配空间的话,就会将字符串对象分配到堆空间上去,这样==就会返回false。
  【*:常量池和对象池不一样,在Java内存模型一章有JVM存储对象以及相关属性的存储方式,所以一定要区分对象池和常量池在内存模型中的概念!】
  接下来用详细的图示来解析一下上边的代码以及后边这种修改过后的方式:
  ——[$]直接使用字面量赋值【*:这里需要注意的是整个这步操作会在编译时完全完成】——
语句:StringstrOne = "Hello ";//Hello后边有个空白

  ——[$]strFive的初始化代码示例子【*:该操作是结合编译时和运行时同时完成,但是strFive是在运行时初始化的】——
语句:StringstrFive = new String("Hello ") + "World";//Hello后边有个空白

  上边两个图里面的引用都是使用的紫色,这里代表这个引用是可以指向其他对象的,也就是该引用为可改变引用,非final的。
  ——[$]strFour和strThree的构造例子——
语句:StringstrThree = "Hello "+ "World";
   StringstrFour = "Hello World";

  理解了上边的几张图形就可以理解上边所有的输出为什么是false和true了,这里还是需要说明的是下边的语句:
StringstrTwo = "World";
finalString strTwo ="World";
finalString strTwo =new String("World");
  这三句话对于JVM而言是有区别的:
  [1]针对JVM而言,直接使用字面量赋值,会在常量池中进行内存对象的创建操作,而且在不声明final的时候,JVM会认为该引用是可以变化的,一旦和该引用有关的表达式构建字符串的时候都需要在运行时在内存堆里面进行操作
  [2]声明了final而不使用new操作符赋值的时候,同样在常量池中进行内存对象的创建,JVM认为该引用是不可以指向其他对象的,也可以这样认为,这种情况创建的字符串对象也可以认为是匿名的直接通过字面量赋值的不带final的字符串对象,而JVM在表达式中处理这类型的对象的时候是一致的,也就是说:
finalString strTwo ="World";
  定义了上边这样一个变量过后,下边两句话是等价的:
StringstrSeven = "Hello " + strTwo;
StringstrSeven = "Hello "+ "World";
  [3]可是即使声明了final引用,但是使用的是new操作符的时候,这种情况的对象本身就是在堆空间分配内存而创建的对象,所以这种情况下和普通的new操作符一样,唯一不同的是声明了final,那么这个引用就不能指向其他对象了,也就是说下边的代码无法通过编译:
finalString strTwo =new String("World");
strTwo ="Hello " + strTwo;
  总而言之,根据上边所有关于String创建的讲解,就可以知道String和Java里面所有的不可变对象一样的特性,针对不可变的特性需要仔细理解该内容。总之记住一点,不论是在常量池里面的String对象,还是处于Java内存堆里面的String对象,一旦初始化了过后就不可以更改,一旦要更改只能新建或者使用该引用指向本身就存在的对象
  【*:这里衍生一点看似重要其实不需要详细理解的问题,上边最容易迷惑的是这句话:StringstrThree = "Hello "+ "World";这句话其实有两种理解,有些JVM里面进行了预处理操作,就是真正在编译的时候进行了常量池的简单初始化,而初始化过程因为使用的算法不一样,所以有可能这里实际上没有Hello 和World两个字符串在常量池里面,也就是说这种情况直接使用了字面量的整合赋值,这种情况就和下边那种情况是一样的了,也就是StringstrFour = "Hello World";其实从编程本质上讲,到达这个地步这个内容已经不是很重要了,因为到这个级别的理解过后,我们不会再过于去关注它,也就是说,上边图的解释里面strThree的图解是值得争议的。有些古老的算法里面在编译过程一旦遇到一个String的字符串就直接进行常量池的内存分配,上边的图示就是这种模式,如果是将二者相加然后进行字面量直接赋值的话,可能strThree和strFour的内存存储上应该是差不多的。其实最后二者进行==比较之所以能够为true,主要是因为常量池的常量的唯一性决定的,这里重复一下就可以证明常量池里面的量只有一个没有多的,这一点是我们真正要在这个例子里面去理解的部分。上边的图示我是觉得更加容易理解String在这个过程的每一个步骤细节,以不至于有什么理解上的歧义,这里还需要讲明的是这里的所有的图片都是自绘的,所以也是做一个图示理解,唯独不能确定的也仅仅是上边strThree的图解,因为有可能Hello 和World两个字符串确实不存在,因为JVM有办法仅仅通过字面量的运算来决定是否真正需要为出现的字符串进行内存空间分配。】
  最后需要说明一点的是,如果对象都在堆里面需要让两个引用指向同样对象的时候,需要进行赋值操作,而不是使用字面量值来进行引用同对象的操作:
StringstrOne = new String("Hello");
StringstrTwo = "Hello";// strTwo == strOne返回为false
StringstrThree = strOne; // strThree == strOne返回为true
  上边代码就可以说明这一点了。
  根据String的创建过程做一个关于String的简单的小节:
  • String类型在Java里面不属于基础类型,而是一个继承于Object的类类型,所以遵循Java里面类类型的存储原理,因为它的不可变性所以常量池里面会有一个类型池,这里就是字符串池
  • JVM中存在一个字符串池,该池属于常量池的一个子集,常量池里面不仅仅存放了字符串常量,还会存在其他常量,所以字符串池仅仅是常量池空间的一个子集
  • 当使用=创建字符串对象的时候,会直接检索常量池中的字符串池,如果字符串池存在请求字面量相匹配的常量字符串,就直接将引用指向常量池中的对象,如果不存在请求字面量相匹配的常量字符串,就由常量池自己创建一个String的字符串常量(也是一个对象),然后将引用指向该常量
  • 使用new创建字符串对象的时候,直接在内存堆空间(非常量池中)中分配一个该字符串对象的内存空间,然后构造该对象,让引用指向该对象
  • =操作符创建的字符串对象在编译时就可以确定下来,因为JVM在编译代码的时候会直接初始化常量池,而字符串池也是在编译时就确定下来的
  • 常量池中的对象是唯一的,也就是说如果字符串池中存在一个“Hello World”常量字符串,绝对不会有第二个,任何使用="Hello World"的String的引用在进行==判断的时候它们都会返回true
  • 使用new创建的字符串是分配在堆空间,是运行时的时候确定下来的,而且一旦使用new,都是重新在堆中构造该字符串,任何使用new String("Hello World")创建的String的引用在==判断的时候它们都会两两之间返回false,哪怕其内容一样
  • ==针对String比较的是两个对象的引用是否指向同一对象,而equals方法比较的是两个字符串对象的内容是否相等
  • 当=符号的右边不是一个字符串常量,是一个指向String对象的引用的时候,直接将新创建的引用指向该对象,如上边地一段代码里面的:StringstrTwo = strOne;
  • 请求字面量为代码里面的可见字符串,如:
    String strOne ="Hello One"
    ;
    String strTwo =
    newString("Hello Two");
    上边两句代码里面的Hello One和Hello Two称为字符串对象构造的时候的请求字面量,在字符串池里面构造String对象的时候,请求字面量作为和字符串池里面是否构造String的判断标准,而在堆中构造String对象的时候,如果使用new操作符,就直接将请求字面量存储为String对象的内容,如果是:
    String strTwo = strOne;
    上边这种格式不会存在请求字面量,而且也没有构造新的对象
  • 带上final声明的时候,代表的是引用不能够再指向对象,而并不代表对象内容不能更改,这里需要理解的是声明的final引用和不可变对象是两个独立的概念【这个概念小心String】
  • 从上边的代码可以知道,使用final过后直接赋值字面量JVM会直接在编译时确定,因为这种情况表达式里面写字面量和写该引用变量已经没有任何区别了,不论怎样都不会影响其赋值,类似:
    final StringstrTwo = "World";
    String strSeven ="Hello " + strTwo;
    String strSeven ="Hello " +"World";两句
    只有按照上边声明的时候是等价的
  • String类的+串联的实现原理为使用StringBuilder类以及append方法实现,最后使用toString方法转换为字符串的
  • String的初始化值为null,注意new String()构造出来的空字符串和null值是不一样的,前者是指向了构造好的对象,对象里面的内容是空串,后者是该引用没有指向任何对象
  ii.String的API详解【不论什么方法拿到什么结果,因为String是不可变的,做的过程只是创建了新对象,这一点一定要理解】
  1)常见的String构造函数:
String():
——初始化新创建的一个String对象,使其表示一个空字符序列
String(byte[] bytes):
——通过使用平台默认的字符集编码指定的byte数组,构造一个新的String
String(byte[] bytes,Charset charset):
——通过使用指定的charset编码指定的byte数组,构造一个新的String
String(byte[] ascii,inthibyte):
——已过时。该方法无法将字节正确转换为字符,从JDK 1.1开始,该方法已经被废弃了
String(byte[] bytes,intoffset,int length):
——通过使用平台的默认字符编码指定byte子数组,构造一个新的String,offset为byte数组的索引
String(byte[] bytes,intoffset,int length,Charset charset):
——通过指定的charset编码指定的byte子数组,构造一个新的String
String(byte[] ascii,inthibyte,int offset,intcount):
——已过时。该方法在JDK 1.1开始已经被废弃
String(byte[] bytes,intoffset,int length,StringcharsetName):
——通过使用指定的字符集编码指定的byte子数组,构造一个新的String
String(byte[] bytes,StringcharsetName):
——通过使用指定的charset解码指定的byte数组,构造一个新的String
String(char[] value):
——分配一个新的String,表示字符数组参数中当前包含的字符序列
String(char[] value,intoffset,int count):
——分配一个新的String,包含取自字符数组参数一个子数组的字符
String(int[] codePoints,intoffset,int count):
——分配一个新的String,它包含Unicode代码点数组参数一个子数组的字符
String(Stringoriginal)
——使用字符串申请字面量初始化一个String,也就是说新创建的字符串是该参数字符串的副本
String(StringBuffer buffer)
——分配一个新的字符串,包含字符串缓冲区参数中当前包含的字符序列
String(StringBuilder builder)
——分配一个新的字符串,包含字符串生气起参数中当前包含的字符序列
  2)String的方法列表:
charcharAt(int index):
——返回指定索引处的char值,也就是指定索引处的字符
intcodePointAt(int index):
——返回指定索引处的字符(Unicode代码点)
intcodePointBefore(intindex):
——返回指定索引处之前的字符(Unicode代码点)
intcodePointCount(int beginIndex,intendIndex):
——返回此String的指定文本范围中的Unicode代码点
intcompareTo(String anotherString):
——按字典顺序将此String 对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按字典顺序此 String对象位于参数字符串之前,则比较结果为一个负整数。如果按字典顺序此 String 对象位于参数字符串之后,则比较结果为一个正整数。如果这两个字符串相等,则结果为 0;compareTo只在方法 equals(Object) 返回 true 时才返回 0。
intcompareToIgnoreCase(String str):
——按字典顺序比较两个字符串,不考虑大小写。此方法返回一个整数,其符号与使用规范化的字符串调用compareTo 所得符号相同,规范化字符串的大小写差异已通过对每个字符调用 Character.toLowerCase(Character.toUpperCase(character))消除。
Stringconcat(String str):
——将指定字符串连接到此字符串的结尾,如果参数字符串的长度为 0,则返回此 String 对象。否则,创建一个新的 String 对象,用来表示由此 String 对象表示的字符序列和参数字符串表示的字符序列连接而成的字符序列。  
booleancontains(CharSequence s):
——当且仅当此字符串包含指定的 char 值序列时,返回 true。
booleancontentEquals(CharSequence cs):
——将此字符串与指定的CharSequence比较
booleancontentEquals(StringBuffer sb):
——将此字符串与指定的StringBuffer比较
staticString copyValueOf(char[] data):
——返回指定数组中标识该字符序列的String
staticString copyValueOf(char[] data,intoffset,int count):
——返回指定数组中标识该字符序列的String
booleanendsWith(String suffix):
——测试此字符串是否指定的后缀结束
booleanequals(Object anObject):
——将此字符串与指定对象的内容比较
booleanequalsIgnoreCase(String anotherString):
——将此String与另外一个String比较,不考虑大小写
staticString format(Locale l,Stringformat,Object... args):
——使用指定的语言环境、格式字符串和参数返回一个格式化字符串
staticString format(Stringformat,Object... args):
——使用指定的格式字符串和参数返回一个格式化字符串
byte[] getBytes():
——使用平台的默认字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中
byte[] getBytes(Charset charset):
——使用给定的charset将此String编码到byte序列,并将结果存储到新的byte数组
voidgetBytes(int srcBegin,intsrcEnd,byte[] dst,intdstBegin):
——已过时。该方法无法将字符正确转换为字节,已经在JDK 1.1开始被废弃
byte[] getBytes(StringcharsetName):
——使用指定的字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中
voidgetChars(int srcBegin,intsrcEnd,char[] dst,intdstBegin):
——将字符从此字符串赋值到目标字符数组
inthashCode():
——返回此字符串的哈希码,返回此字符串的哈希码。String 对象的哈希码根据以下公式计算:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]使用 int 算法,这里 s[i] 是字符串的第i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希值为 0。)
intindexOf(int ch):
——返回指定字符在此字符串中第一次出现的索引位置
intindexOf(int ch,intfromIndex):
——返回此字符串中第一次出现指定字符处索引,从指定的索引开始搜索
intindexOf(String str):
——返回指定子字符串在此字符串中第一次出现的索引
intindexOf(String str,intfromIndex):
——返回指定子字符串在此字符串中第一次出现的索引,从指定的索引开始
Stringinern():
——返回字符串对象的规范化表示形式
booleanisEmpty():
——当且仅当length()为0的时候返回true
intlastIndexOf(int ch):
——返回指定字符在此字符串中最后一次出现的索引
intlastIndexOf(int ch,int fromIndex):
——返回指定字符在此字符串中最后一次出现的索引,从指定的索引处开始进行反向搜索
intlastIndexOf(String str):
——返回指定字符串在此字符串中最右边出现处的索引
intlastIndexOf(String str,int fromIndex):
——返回指定字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
intlength():
——返回此字符串的长度
booleanmatches(String regex):
——告知此字符串是否匹配给定的正则表达式
intoffsetByCodePoints(int index,intcodePointOffset):
——返回此 String 中从给定的 index 处偏移 codePointOffset 个代码点的索引。文本范围内由 index 和 codePointOffset 给定的未配对代理项各计为一个代码点。
booleanregionMatches(boolean ignoreCase,inttoffset,String other,intooffset,int len):
——将此 String 对象的子字符串与参数 other 的子字符串进行比较。如果这两个子字符串表示相同的字符序列,则结果为 true,当且仅当 ignoreCase 为 true 时忽略大小写。要比较的此 String 对象的子字符串从索引 toffset 处开始,长度为 len。要比较的 other 的子字符串从索引 ooffset 处开始,长度为 len。当且仅当下列至少一项为 true 时,结果才为 false:
  • toffset 为负。
  • ooffset 为负。
  • toffset+len 大于此 String 对象的长度。
  • ooffset+len 大于另一个参数的长度。
  • ignoreCase 为 false,且存在某个小于 len 的非负整数k,即:this.charAt(toffset+k) != other.charAt(ooffset+k)
  • ignoreCase 为 true,且存在某个小于 len 的非负整数k,即:Character.toLowerCase(this.charAt(toffset+k)) != Character.toLowerCase(other.charAt(ooffset+k))
  • 以及:Character.toUpperCase(this.charAt(toffset+k)) != Character.toUpperCase(other.charAt(ooffset+k))
booleanregionMatches(int toffset,String other,intooffset,int len):
——测试两个字符串区域是否相等。将此 String 对象的一个子字符串与参数 other 的一个子字符串进行比较。如果这两个子字符串表示相同的字符序列,则结果为 true。要比较的此 String 对象的子字符串从索引 toffset 处开始,长度为 len。要比较的 other 的子字符串从索引 ooffset 处开始,长度为 len。当且仅当下列至少一项为 true 时,结果才为 false :
  • toffset 为负。
  • ooffset 为负。
  • toffset+len 大于此 String 对象的长度。
  • ooffset+len 大于另一个参数的长度。
  • 存在某个小于 len 的非负整数k,它满足:this.charAt(toffset+k) != other.charAt(ooffset+k)
Stringreplace(char oldChar,charnewChar):
——返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。如果 oldChar 在此 String 对象表示的字符序列中没有出现,则返回对此 String 对象的引用。否则,创建一个新的 String 对象,它所表示的字符序列除了所有的 oldChar 都被替换为 newChar 之外,与此 String 对象表示的字符序列相同
Stringreplace(CharSequence target,CharSequence replacement):
——使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。该替换从字符串的开头朝末尾执行,例如,用 "b" 替换字符串 "aaa" 中的 "aa" 将生成 "ba" 而不是 "ab"

StringreplaceAll(String regex,Stringreplacement):
——使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串
StringreplaceFirst(String regex,Stringreplacement):
——使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串
String[] split(Stringregex):
——根据给定正则表达式的匹配拆分此字符串
String[] split(Stringregex, int limit):
——根据匹配给定的正则表达式来拆分此字符串
booleanstartsWith(String prefix):
——测试此字符串是否以指定的前缀开始
booleanstartsWith(String prefix,inttoffset):
——测试此字符串从指定索引开始的子字符串是否以指定前缀开始
CharSequence subSequence(intbeginIndex,int endIndex):
——返回一个新的字符序列,它是此序列的一个子序列
Stringsubstring(int beginIndex):
——返回一个新的字符串,它是此字符串的一个子字符串
Stringsubstring(int beginIndex,intendIndex):
——返回一个新字符串,它是此字符串的一个子字符串
char[] toCharArray():
——将此字符串转换为一个新的字符数组
StringtoLowerCase():
——使用默认语言环境的规则将此 String 中的所有字符都转换为小写
StringtoLowerCase(Locale locale):
——使用给定 Locale 的规则将此 String 中的所有字符都转换为小写
StringtoString():
——返回此对象本身,因为它已经是个字符串
StringtoUpperCase():
——使用默认语言环境的规则将此 String 中的所有字符都转换为大写
StringtoUpperCase(Locale locale):
——使用给定 Locale 的规则将此 String 中的所有字符都转换为大写
Stringtrim():
——返回字符串的副本,忽略前导空白和尾部空白
  *:字符串这里的valueOf方法就不做说明,后边在使用的时候会简单说明valueOf的用法,这里只写清单,这些方法全部为静态方法
staticString valueOf(booleanb):
staticString valueOf(charc):
staticString valueOf(char[] data):
staticString valueOf(char[] data,intoffset,int count):
staticString valueOf(doubled):
staticString valueOf(floatf):
staticString valueOf(inti):
staticString valueOf(longl):
staticString valueOf(Object obj):
  iii.String类的基本应用:
  1)一个例子说明它的用法:
  ——[$]String基本用法——
packageorg.susan.java.string;
/**
*String 最基本的一些用法
**/
public classStringBasic {
    public static void main(Stringargs[]){
       // 获取字符串对象的Hash值;
       String strOne = "abc";
       String strTwo ="abc";
       String strThree = "def";
       System.out.println("strOne hash code = "+ strOne.hashCode());
       System.out.println("strTwo hash code = "+ strTwo.hashCode());
       System.out.println("strThree hash code = "+ strThree.hashCode());
       printSplitChar();
       // 获取字符串的ASCII值
       String testString = "AB";
       for( inti = 0; i < testString.length(); ++i ){
           char c = testString.charAt(i);
           System.out.println("Int Value:"+ (int)c + "--Char Value:"+ c);
        }
       printSplitChar();
       // 字符串的长度
       int intArray[] = new int[3];
        Object objArray[] =new Object[3];
       String strFour = "Hello World";
       System.out.println("Length of intArray:"+ intArray.length);
       System.out.println("Length of objArray:"+ objArray.length);
       System.out.println("Length of strFour:"+ strFour.length());
       printSplitChar();
       // 转义字符串
       System.out.println("An alarm entered in Octal:/007");
       System.out.println("A tab key:/t(what comes after)");
       System.out.println("A newline:/n(what comes after)");
       System.out.println("A /"Hello/" // backslash");
       printSplitChar();
       // 转换为字符数组
       String strFive = "Examples";
       char[] temp = strFive.toCharArray();
       for( inti = 0; i < temp.length; i++ ){
           System.out.print(temp[i] + ",");
        }
       printSplitChar();
       // trim()方法
       String strSix = "        This is a test String!       ";
       System.out.println(strSix);
       System.out.println(strSix.trim());
    }
   
    private static void printSplitChar(){
       System.out.println("--------------------------------");
    }
}
  该代码的输出为:
strOne hash code = 96354
strTwo hash code = 96354
strThree hash code = 99333
--------------------------------
Int Value:65--Char Value:A
Int Value:66--Char Value:B
--------------------------------
Length of intArray:3
Length of objArray:3
Length of strFour:11
--------------------------------
An alarm entered in Octal:
A tab key: (what comes after)
A newline:
(what comes after)
A "Hello" / backslash
--------------------------------
E,x,a,m,p,l,e,s,--------------------------------
        This is a test String!      
This is a test String!
  这是String的最基本的一些用法,从上边可以看到两个String的引用如果指向同一个对象的时候,其hashCode的值是相等的,而在String里面使用==是比较的两个引用是否指向同一个对象,不去对比它们对象内的内容是否相同,而针对大部分对象而言都是使用equals来比较两个对象的内容的。接下来看一小段代码:
packageorg.susan.java.string;

classTestObject{
    private String name ="Test Hello";
    public TestObject(String testName){
       this.name = testName;
    }
    public void println(){
       System.out.println(this.name);
    }
    @Override
    public int hashCode(){
       return 1;
    }
}

public classStringEquals {
    public static void main(Stringargs[]){
        TestObject testObject =new TestObject("Hello One");
        TestObject testObject2 =new TestObject("Hello One");
       System.out.println(testObject == testObject2);
       System.out.println(testObject.equals(testObject2));
    }
}
  输出为:
false
false
  可以发现这段代码里面equals方法失去作用了,而且上边的代码实际是很不良的代码,其实在《Java的类和对象》一个章节已经讲清楚了,如果是自定义类,它的==和equals方法都是继承于Object,而Object源代码里面==和equals的实现是等价的,都是比较两个引用是否指向同一个对象,所以上边testObject引用和testObject2引用的==和equals方法永远是同步的,它们二者的输出永远是相等的。而针对这里讲到的String而言就不一样了,比较下边这段代码,关于==和equals方法参考一下前边Java的类和对象章节:
public classStringEquals {
  public static void main(Stringargs[]){
        String testObject =new String("Hello One");
        String testObject2 =new String("Hello One");
       System.out.println(testObject == testObject2);
       System.out.println(testObject.equals(testObject2));
    }
}
  输出为:
false
true
  2)关于concat和+操作符:
  ——[$]concat和+符号——
packageorg.susan.java.string;
/**
*探索concat和+的存储原理
**/
public classConcatBasic {
    public static void main(Stringargs[]){
       final StringstrOne = "Hello ";
       final StringstrTwo = "World";
       String strThree = strOne + strTwo;
       String strFour = strOne.concat(strTwo);
       System.out.println("strThree:"+ strThree);
       System.out.println("strFour:"+ strFour);
       System.out.println(strThree == strFour);
       System.out.println(strThree.equals(strFour));
        strThree ="Hello " + "World";
        strFour ="Hello ".concat("World");
       System.out.println("strThree:"+ strThree);
       System.out.println("strFour:"+ strFour);
       System.out.println(strThree == strFour);
       System.out.println(strThree.equals(strFour));
    }
}
  上边这段代码段的输出为:
strThree:Hello World
strFour:Hello World
false
true
strThree:Hello World
strFour:Hello World
false
true
  从这里的代码输出可以看到,concat和+除了生成的结果字面量一样,其他的是截然不同,甚至可以判断的是这两者在对象的存储方式上都不一样,因为从上边的讲解可以知道:
"Hello "+ "World";只会生成一个对象
"Hello ".concat("World");却会生成三个不同的String对象,最终结果和+生成的对象是一样的
  如果不考虑这个问题,仅仅是考虑两个操作的结果,两者没有太大的区别,至于最终生成字符串是如何存储的分析最开始的流程就知道二者的图,实际上strThree是存储在字符串池中的,而strFour是存储在内存堆空间中的,注意这里定义的最初两个字符串:strOne和strTwo,这两个的修饰符都是finalString,而且没有使用new操作符,主要是使得这两个称为标准常量字符串,使用的的时候用字面常量表达式和变量表达式都可以使JVM直接在编译期将其确定下来,这也是为了测试+和concat函数一个典型的方式。理解上边这段代码的时候,一定要彻底理解上边String的创建过程,在第一个小节里面一个String通过什么方式创建已经分析得很清楚了,如果不理解它的创建过程,上边这段代码可能使得读者不能区分+和concat函数。
  3)关于函数split函数:
packageorg.susan.java.string;
/**
*关于Split的参数问题
**/
public classSplitTester {
    public static void main(Stringargs[]){
       String strOne = "a|b";
       String []arrayOne = strOne.split("|");
       System.out.println(arrayOne.length); // 结果似乎不如我们初衷,不抛异常
       for(String item:arrayOne){
           System.out.print("("+ item + ")");
        }
       System.out.println("/n----End----");
       String []arrayTwo = strOne.split("[|]");// 和strOne.split("//|");等价
       System.out.println(arrayTwo.length);
       for(String item:arrayTwo){
           System.out.print("("+ item + ")");
        }
       System.out.println("/n----End----");
       String strTwo = "a.b.c";
       String []arrayThree = strTwo.split(".");// 这样写是有问题的,不抛异常
       System.out.println(arrayThree.length);
       for(String item:arrayThree){
           System.out.print("("+ item + ")");
        }
       System.out.println("/n----End----");
       String []arrayFour = strTwo.split("//.");
       System.out.println(arrayFour.length);
       for(String item:arrayFour){
           System.out.print("("+ item + ")");
        }
       System.out.println("/n----End----");
       String strThree = "a + b * c";
        strThree.split("//+");//strThree.split("+");会抛异常 java.util.regex.PatternSyntaxException
        strThree.split("//*");// strThree.split("*");会抛异常 java.util.regex.PatternSyntaxException
       String []opterationArray = strThree.split("//+|//*");
       System.out.println(opterationArray.length);
       for(String item:opterationArray){
           System.out.print("("+ item + ")");
        }
       System.out.println("/n----End----");
    }
}
  先看这段代码的输出:
4
()(a)(|)(b)
----End----
2
(a)(b)
----End----
0

----End----
3
(a)(b)(c)
----End----
3
(a )( b )( c)
----End----
  相信细心的读者已经看到上边这段代码的原因了,其实不仅仅是split函数,只要参数传入的是regex参数(这里指的是API中形参的参数名称),传入的不仅仅代表一个字符串,而是一个正则表达式的匹配模式,常见的函数为:split、replaceAll、replaceFirst、matches这几个函数,而在传入的regex参数中,如果仅仅传入"|",因为|为正则表达式的模式字符,所以第一个End之前的length输出是4,而且接着输出:()(a)(|)(b)。如果本身分隔符就属于正则表达式字符,那么需要使用[]将这些字符括起来或者使用转义字符,否则这些字符会无效,而且从上边可以知道"+"和"*"号还会抛出异常。
  再看一段代码彻底理解split函数,一旦理解了split,那么replaceAll、replaceFirst和matches函数就随之能够理解了:
packageorg.susan.java.string;

public classSplitAdvanced {
    public static void main(Stringargs[]){
       String strOne = "a+b*c";
       String[] arrayOne = strOne.split("[+]|[*]");
       // 和strOne.split("//+|//*");等价
        for(String item:arrayOne){
            System.out.print("(" + item + ")");
        }
       System.out.println();
       String strTwo = "a + [ b + c ]";
       String[] arrayTwo = strTwo.split("[//[]|[//]]");
       // 和strTwo.split("//[|//]");等价
       for(String item:arrayTwo){
           System.out.print("("+ item + ")");
        }
       System.out.println();
    }
}
  这段代码的输出为:
(a)(b)(c)
(a + )( b + c )
  |>从上边的代码就可以总结几点:(针对split、replaceAll、replaceFirst)
  • 当API里面的参数名为regex的时候,传入的不是一个字符串来标识分割符,而是一个正则表达式的模式,假设有一个字符a,如果a不属于正则表达式字符,那么最简单的以a为分隔符的写法就为"a"
  • 接着上边,如果a为一个正则表达式字符的时候,需要进行转义,比如a为:+、*、[、]、.、|等正则表达式字符的时候,需要进行转义才能正确表达分隔符(针对split)或者替换的匹配符(针对replaceAll和replaceFirst),而且转义的方式常用的有两种,分别为://a或者[a](其实这里只要了解了正则表达式怎么书写了这个问题就迎刃而解了)
  • 如果不是按照转义来写的时候,输入"+"和"*"会因为模式语法错误抛出异常java.util.regex.PatternSyntaxException,而针对"["和"]"的转义写法只有一种就是上边用的"//["和"//]",如果直接使用"[[]"或者"[]]"会因为缺乏闭合模式符号抛出异常java.util.regex.PatternSyntaxException
  • 再次强调一点:split主要用于分割字符串,传入的regex参数是正则表达式的匹配模式,replaceAll和replaceFirst函数的参数regex同样也表示正则表达式的匹配模式,常见的会出现异常的就是上边几种情况,仔细分析上边两端代码基本会规避掉使用split的普通错误问题
  |>好了,关于String的基本的东西这里先做一个简单的小结,后边几个章节都会用到String的,其他的一些特性保留到下边介绍,而本小节需要注意的有以下几点:
  • 什么叫做String的不可变性,注意String的不可变性的理解
  • String的创建过程(主要区分常量池中对象和内存堆中对象)
  • String中带regex参数的方法的使用的一些特殊情况

3.StringBuilder和StringBuffer详解
  i.基本介绍:
  如果说String类是不可变类、那么常用的可变字符串就是StringBufferStringBuilder,先简单介绍一下:
  StringBuffer和StringBuilder是可变字符序列,它和String不一样的是,它内部的对象内容是可以改变的,从上边可以知道,String在进行更改的时候,实际上是创建了新的String对象,而原来的String对象内容并没有更改过,而针对StringBuffer和StringBuilder而言,它在修改的时候却改变了字符串对象的内容,也就是说当使用StringBuilder和StringBuffer的时候,并没有创建新的字符串对象。从这一点上来讲,在选择使用String、StringBuilder和StringBuffer的时候,其性能差异比较大的。下边提供一个StringBuffer和StringBuilder的图示,图上是StringBuilder:

  这里先说说StringBuilder和StringBuffer的区别,再针对这几个字符串对象进行逐一分析,以方便读者理解:
  StringBuilder和StringBuffer的主要区别在于线程同步StringBuffer是线程安全的,而StringBuilder是线程不安全的,也就是说如果在多线程程序里面需要同步的话最好是选择使用StringBuffer而不是StringBuilder,因为有此区别所以在不考虑多线程环境的程序里面,使用StringBuilder的效率可能会更高,而且StringBuffer和StringBuilder的API里面除了某些方法多了修饰而已,其他的方法功能和操作都是一样的,因为StringBuffer支持线程同步,所以StringBuffer的大部分API方法都带有关键字:synchronized
  再看看API文档里面针对两种可变字符序列的介绍:
  StringBuffer定义:
public final classStringBuffer extendsObject implements Serializable,CharSequence
  线程安全的可变字符序列。一个类似于String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。StringBuffer上的主要操作是 append insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。

  例如,如果z 引用一个当前内容为 "start" 的字符串缓冲区对象,则此方法调用z.append("le")会使字符串缓冲区包含 "startle",而z.insert(4, "le")将更改字符串缓冲区,使之包含 "starlet"。

  通常,如果 sb 引用 StringBuilder的一个实例,则 sb.append(x) sb.insert(sb.length(), x)具有相同的效果。当发生与源序列有关的操作(如源序列中的追加或插入操作)时,该类只在执行此操作的字符串缓冲区上而不是在源上实现同步。每个字符串缓冲区都有一定的容量。只要字符串缓冲区所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区数组。如果内部缓冲区溢出,则此容量自动增大。从 JDK 5 开始,为该类补充了一个单个线程使用的等价类,即StringBuilder。与该类相比,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。

  StringBuilder定义:

public final classStringBuilder extendsObject implementsSerializable,CharSequence

  一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer要快。在 StringBuilder 上的主要操作是 append insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串生成器中。append方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。

  例如,如果z 引用一个当前内容为 "start" 的字符串的生成器对象,则该方法调用z.append("le")将使字符串生成器包含 "startle",而z.insert(4, "le")将更改字符串生成器,使之包含 "starlet"。

  通常,如果 sb 引用 StringBuilder的实例,则 sb.append(x) sb.insert(sb.length(), x)具有相同的效果。每个字符串生成器都有一定的容量。只要字符串生成器所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区。如果内部缓冲区溢出,则此容量自动增大。将StringBuilder 的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用 StringBuffer。

  ii.API简介【这里只列举StringBuilder的,和StringBuffer有区别的地方会标注,没有特殊说明就是两个类都有的共同方法。】
  其实这两个类最主要的方法有两个:append和insert
  1)append方法:
StringBuilder append(booleanb):将 boolean 参数的字符串表示形式追加到序列
StringBuilder append(charc):将 char 参数的字符串表示形式追加到此序列
StringBuilder append(char[] str):将 char 数组参数的字符串表示形式追加到此序列
StringBuilder append(char[] str,intoffset,int len):
——char 数组参数的子数组的字符串表示形式追加到此序列。将 char 数组 str 中的字符按顺序追加到此序列的内容中,从索引 offset 开始。此字符的长度将增加 len。该方法的最终效果与以下操作过程的效果相同:先使用 String.valueOf(char[])方法将参数转换为字符串,然后将所得字符串的字符追加到此字符序列。
StringBuilder append(CharSequence s):向此 Appendable 追加到指定的字符序列
StringBuilder append(CharSequence s,intstart,int end)throws IndexOutOfBoundsException:
——将指定CharSequence 的子序列追加到此序列。按顺序追加参数 s 中的字符,即从索引start 开始到索引 end 结束的此序列的内容。此序列增加的长度为end - start。假设此字符序列的长度在执行 append 方法前为 n。如果 k 小于 n,则新字符序列中索引k 处的字符等于原序列中索引 k 处的字符;否则它等于参数 s 中索引 k+start-n处的字符。

如果 s 为 null,则认为 s 参数包含 4 个字符 "null",并以此为根据追加字符。

StringBuilder append(doubled):double 参数的字符串表示形式追加到此序列
StringBuilder append(float f):float 参数的字符串表示形式追加到此序列
StringBuilder append(int i): int 参数的字符串表示形式追加到此序列
StringBuilder append(long lng):long 参数的字符串表示形式追加到此序列
StringBuilder append(Object obj):追加Object 参数的字符串表示形式
StringBuilder append(String str):将指定的字符串追加到此字符序列
StringBuilder append(StringBuffer sb):将指定的 StringBuffer 追加到此序列
  【*:注意一个方法就是append(StringBuffer sb),当参数为StringBuffer的时候,StringBuffer和StringBuilder两个类的这个方法是相同的,也就是说没有append(StringBuilder builder)这样一个方法存在,其他方法都是大同小异,而还需要注意一点就是StringBuffer永远是线程安全的,而StringBuilder是线程不安全的。从这一点可以知道在同步要求不严格的情况下,使用StringBuilder的效率明显高过StringBuffer的效率,但是若要使用线程同步的时候为了保证线程安全最好还是使用StringBuffer,而不是选择使用StringBuilder。还有一个区别就使用StringBuffer类的时候,返回值不是StringBuilder,而是StringBuffer,上边的方法表里面返回值全部是StringBuilder,而使用StringBuffer的时候返回的应该就是一个StringBuffer对象,这也是需要注意的地方。】
  三者的性能比较为:StringBuilder > StringBuffer > String(有时候StringBuilder和StringBuffer的性能差异不是很明显!)
  2)insert方法:
StringBuilder insert(intoffset,boolean b):将 boolean 参数的字符串表示形式插入此序列中
StringBuilder insert(intoffset,char c):将 char 参数的字符串标识形式插入此序列中
StringBuilder insert(intoffset,char[] str):将 char 数组参数的字符串表示形式插入此序列中
StringBuilder insert(intindex,char[] str,intoffset,int len):将数组参数 str 子数组的字符串表示形式插入此序列中
StringBuilder insert(intdstOffset, CharSequence s):将指定 CharSequence 插入此序列中
StringBuilderinsert(int dstOffset, CharSequence s,int start, intend):将指定 CharSequence 的子序列插入此序列中
StringBuilderinsert(int offset, double d):将 double 参数的字符串表示形式插入此序列中 
StringBuilderinsert(int offset, float f):将 float 参数的字符串表示形式插入此序列中
StringBuilderinsert(int offset, int i):将 int 参数的字符串表示形式插入此序列中 
StringBuilderinsert(int offset, long l):将 long 参数的字符串表示形式插入此序列中 
StringBuilderinsert(int offset, Object obj):将 Object 参数的字符串表示形式插入此字符序列中 
StringBuilderinsert(int offset, String str):将字符串插入此字符序列中
  【*:insert方法不包含直接插入StringBuilder对象或者StringBuffer对象,这一点可能是和append比较大的一个区别,而至于StringBuilder和StringBuffer两个类的区别还是在于线程同步上,这一点牢记。】
  3)其他常用方法:
intcapacity():返回当前的容量
charcharAt(int index):返回此序列中指定索引处的char值
StringBuilder delete(intstart,int end):移除此序列的子字符串中的字符
StringBuilder deleteCharAt(intindex):移除此索引位置上指定的字符
voidensureCapacity(int minimumCapacity):确保容量至少等于指定的最小值
intindexOf(String str):返回第一次出现的指定字符串在该字符串中的索引
intindexOf(String str,intfromIndex):从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引
intlastIndexOf(String str):返回最右边出现的指定子字符串在此字符串中的索引
intlastIndexOf(String str,int fromIndex):返回最后一次出现的指定子字符串在此字符串中的索引
intlength():返回字符数,该字符串的长度
StringBuilder replace(intstart,int end,Stringstr):使用给定 String 中的字符替换此序列的子字符串中的字符
StringBuilder reverse():将此字符序列用其反转形式取代,常用于逆序排列字符串
voidsetCharAt(int index,charch):将给定索引处的字符设置为 ch
voidsetLength(int newLength):设置字符序列的长度
Stringsubstring(int start):返回一个新的 String,它包含此字符序列当前所包含字符的子序列
Stringsubstring(int start,intend):返回一个新的 String,它包含此序列当前所包含字符的子序列
StringtoString():返回此序列中数据的字符串表示形式
  iii.相关说明:
  ——[$]Length、Capacity、MaxCapacity——
packageorg.susan.java.string;

public classCapacityTester {
    private static void printCapacity(StringBuilder builder){
       System.out.println("Capacity:"+ builder.capacity());
       System.out.println("Length:"+ builder.length());
    }
    public static void main(Stringargs[]){
        StringBuilder builderOne =new StringBuilder();
        StringBuilder builderTwo =new StringBuilder(10);
        StringBuilder builderThreee =new StringBuilder(100);
       printCapacity(builderOne);
       printCapacity(builderTwo);
       printCapacity(builderThreee);

        StringBuilder builder =new StringBuilder("abcde");
       printCapacity(builder);   

        builder.ensureCapacity(40);
       printCapacity(builder);

        StringBuilder testBuilder =new StringBuilder("Hello World");
       printCapacity(testBuilder);

        testBuilder.append("Lang Yu");
       printCapacity(testBuilder);

        testBuilder.append("I'm a pig named bajie!");
       printCapacity(testBuilder);
    }
}
  先看看上边的输出:
Capacity:16
Length:0
Capacity:10
Length:0
Capacity:100
Length:0
Capacity:21
Length:5
Capacity:44
Length:5
Capacity:27
Length:11
Capacity:27
Length:18
Capacity:56
Length:40
  从上边的输出可以看出length()方法和capacity()方法的区别,每一个StringBuilder对象和StringBuffer对象都存在一个capacity概念,也就是容量的概念,因为这两个对象是可变字符串,所以系统会在操作指出进行容量的自动分配使得该字符串对象有一个初始化的容量,该容量是可以更改的,而且也是根据字符串的增长会自动增长。看整个字符串的最右一个用例可以知道,当字符串为“Hello World”的时候,该容量为系统自动分配值27,在进行append操作的时候,如果修改字符串内容过后没有超过总容量,即使Length有所修改,其capacity是不会改变的,只有等到Length超越了本身拥有的容量的时候才会由系统重新分配而扩大该容量;这里有一点需要指出,针对StringBuilder和StringBuffer操作,如果进行了delete操作或者deleteCharAt操作过后,该容量不会自动缩小,而缩小的方法就是使用ensureCapacity,这是关于StringBuilder和StringBuffer的容量的简单说明。
  ——[$]关于int、double类型的操作——
packageorg.susan.java.string;

public classIntDoubleStringBuilder {
    public static void main(Stringargs[]){
        StringBuilder builder =new StringBuilder(20);
        StringBuilder builder2 =new StringBuilder("20");
       //StringBuilder builder2 = new StringBuilder(12.11);  构造失败
        builder.append(11.1);
        builder.append(22);
       System.out.println(builder);
        builder2.append(builder);
       System.out.println(builder2);
    }
}
  上边代码的输出为:
11.122
2011.122
  • 由上边的代码可以看出,如果要构造一个int或者double的可变字符串,需要在传入的参数里面添加",因为传入double会失败,传入int的话会调用capacity作为参数的构造子,所以需要使用上边展示的合理的构造方法才能构造想要的字符串
  • 在进行append操作的时候,可变字符串对象是完全把int和double当成String对待的,并不会出现两个数值相加或者相减的概念,这一点希望初学者能够理解。
  ——[$]charAt、setCharAt、getChars——
packageorg.susan.java.string;

public classCharTester {
    public static void main(Stringargs[]) {
        StringBuilder builder = new StringBuilder("Hello There");
       System.out.printf("builder = %s/n", builder.toString());
       System.out.printf("Character at 0: %s/nCharacter at 4: %s/n/n", builder
        .charAt(0), builder.charAt(4));

       char charArray[] = new char[builder.length()];
        builder.getChars(0, builder.length(), charArray, 0);
       System.out.print("The characters are: ");

       for (charcharacter : charArray)
           System.out.print(character);

        builder.setCharAt(0,'H');
        builder.setCharAt(6,'T');
       System.out.printf("/n/nbuf = %s", builder.toString());
    }
}
  该段代码的输出为:
builder = Hello There
Character at 0: H
Character at 4: o

The characters are: Hello There

buf = Hello There
  相信不需要详细解释,读者完全可以理解上边这段代码,这段代码演示了可变字符串里面和字符相关的方法的使用
  ——[$]Replace、SubString和Reverse——
packageorg.susan.java.string;

public classOtherTester {
    public static void main(Stringargs[]){
        StringBuilder builder =new StringBuilder("One Two Three Four");
       String subString = "Two";
       String replacementString ="Twenty";
       System.out.println(builder);
       int position = builder.lastIndexOf(subString);
        builder.replace(position, position + subString.length(), replacementString);
       System.out.println(builder);
       System.out.println(builder.substring(4,10));
       System.out.println(builder.reverse());
    }
}
  上边的输出为:
One Two Three Four
One Twenty Three Four
Twenty
ruoF eerhT ytnewT enO
  仔细理解这些内容就可以明白这些特殊的方法的一些使用以及相关问题了,在这里就不介绍delete了,有什么需要理解的读者可以去查阅API文档,提供这些例子只是为了演示这两个可变字符串里面常用的一些方法以及相关用法,需要注意的一点是,真正在项目开发过程中可能需要根绝这些方法进行对应的组合,才能真正完成我们的需求,而不是像上边这些代码这么小打小闹。关于Java里面可变字符串的内容这里就讲这么多。针对Java里面的String、StringBuilder、StringBuffer的内容,详细理解过后对我们而言是得不偿失的,因为这可能是我们项目开发过程使用频率最高的三个对象,因为经常会触及到,所以希望读者仔细阅读这里的相关内容。【*:这里只使用了StringBuilder,如果直接替换称为StringBuffer,上边的代码也是有效的,这里不做演示。有一点需要理解的是StringBuilder设计的出台才是真正作为不需要进行线程安全考虑的可变字符串的使用,StringBuffer大多数情况下是用来作为缓冲区,从字面上翻译就可以知道StringBuilder是字符串构造器,而StringBuffer是字符串缓冲区,也就是说在不考虑线程安全的情况下,最优化选择应该是StringBuilder,因为它的效率是最高的,而特殊的需求除外,至于使用String、StringBuilder还是StringBuffer都应该根据具体的开发需求来决定。】

猜你喜欢

转载自blog.csdn.net/zy7812/article/details/39893077
今日推荐