Java 字符串总结

来源:互联网 发布:sql in和not in的用法 编辑:程序博客网 时间:2024/06/16 01:34


1、不可变String
 
String 类的部分源码;       

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

 

/** use serialVersionUID from JDK 1.0.2 for interoperability */

private static final long serialVersionUID = -6849794470754667710L;

 

 

private static final ObjectStreamField[] serialPersistentFields =

    new ObjectStreamField[0];


由此可以看出String类时final类,因此其成员方法都是final。final类是不能被继承的,final方法是无法被覆盖的。

下面列出部分 String 类的构造函数:

public String() {

    this.value = new char[0];

}

 

public String(String original) {

    this.value = original.value;

    this.hash = original.hash;

}

 

public String(char value[]) {

    this.value = Arrays.copyOf(value, value.length);

}

 

public String(char value[], int offset, int count) {

    if (offset < 0) {

        throw new StringIndexOutOfBoundsException(offset);

    }

    if (count < 0) {

        throw new StringIndexOutOfBoundsException(count);

    }

    // Note: offset or count might be near -1>>>1.

    if (offset > value.length - count) {

        throw new StringIndexOutOfBoundsException(offset + count);

    }

    this.value = Arrays.copyOfRange(value, offset, offset+count);

}

可以看出,String 其实是通过 char[] 数组来保存字符串的。

下面是部分String 类的成员方法:

public String replace(CharSequence target, CharSequence replacement) {

    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(

            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));

}

 

 

public String toLowerCase(Locale locale) {

    if (locale == null) {

        throw new NullPointerException();

    }

 

    int firstUpper;

    final int len = value.length;

 

    /* Now check if there are any characters that need to be changed. */

    scan: {

        for (firstUpper = 0 ; firstUpper < len; ) {

            char c = value[firstUpper];

            if ((c >= Character.MIN_HIGH_SURROGATE)

                    && (c <= Character.MAX_HIGH_SURROGATE)) {

                int supplChar = codePointAt(firstUpper);

                if (supplChar != Character.toLowerCase(supplChar)) {

                    break scan;

                }

                firstUpper += Character.charCount(supplChar);

            } else {

                if (c != Character.toLowerCase(c)) {

                    break scan;

                }

                firstUpper++;

            }

        }

        return this;

    }

 

    char[] result = new char[len];

    int resultOffset = 0;  /* result may grow, so i+resultOffset

                            * is the write location in result */

 

    /* Just copy the first few lowerCase characters. */

    System.arraycopy(value, 0, result, 0, firstUpper);

 

    String lang = locale.getLanguage();

    boolean localeDependent =

            (lang == "tr" || lang == "az" || lang == "lt");

    char[] lowerCharArray;

    int lowerChar;

    int srcChar;

    int srcCount;

    for (int i = firstUpper; i < len; i += srcCount) {

        srcChar = (int)value[i];

        if ((char)srcChar >= Character.MIN_HIGH_SURROGATE

                && (char)srcChar <= Character.MAX_HIGH_SURROGATE) {

            srcChar = codePointAt(i);

            srcCount = Character.charCount(srcChar);

        } else {

            srcCount = 1;

        }

        if (localeDependent || srcChar == '\u03A3') { // GREEK CAPITAL LETTER SIGMA

            lowerChar = ConditionalSpecialCasing.toLowerCaseEx(this, i, locale);

        } else {

            lowerChar = Character.toLowerCase(srcChar);

        }

        if ((lowerChar == Character.ERROR)

                || (lowerChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) {

            if (lowerChar == Character.ERROR) {

                lowerCharArray =

                        ConditionalSpecialCasing.toLowerCaseCharArray(this, i, locale);

            } else if (srcCount == 2) {

                resultOffset += Character.toChars(lowerChar, result, i + resultOffset) - srcCount;

                continue;

            } else {

                lowerCharArray = Character.toChars(lowerChar);

            }

 

            /* Grow result if needed */

            int mapLen = lowerCharArray.length;

            if (mapLen > srcCount) {

                char[] result2 = new char[result.length + mapLen - srcCount];

                System.arraycopy(result, 0, result2, 0, i + resultOffset);

                result = result2;

            }

            for (int x = 0; x < mapLen; ++x) {

                result[i + resultOffset + x] = lowerCharArray[x];

            }

            resultOffset += (mapLen - srcCount);

        } else {

            result[i + resultOffset] = (char)lowerChar;

        }

    }

    return new String(result, 0, len + resultOffset);

}

可以看出:String类中每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫不动。但是如果内容没有改变,String的方法只是返回指向原对象的引用而已。这可以节约存储空间以避免开销。

        String对象时不可变的:

public class Immutable {

 

   //每当把String对象作为方法的参数时,都会复制一份引用

   //而该引用所指的对象其实一直待在单一的物理位置上,从未动过。

   public static String upCase(String s){

      return s.toUpperCase();

   }

 

  

   public static void main(String[] args) {

 

      String q = "howdy";

      System.out.println(q);

      String qq = upCase(q);

      System.out.println(qq);

      //String can't change

      System.out.println(q);

   }

}


2、String Pool

String Pool,字符串池。(位于堆中)
  1. 常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。
  2. 它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;
  3. 当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。

java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,常量池存在于方法区中。

每一个都是常量池中的一个常量表(常量项)。而这些常量表之间又有不同,class文件共有11种常量表,如下所示:
常量表类型
标志值(占1 byte)
描述
CONSTANT_Utf8
1
UTF-8编码的Unicode字符串
CONSTANT_Integer
3
int类型的字面值
CONSTANT_Float
4
float类型的字面值
CONSTANT_Long
5
long类型的字面值
CONSTANT_Double
6
double类型的字面值
CONSTANT_Class
7
对一个类或接口的符号引用
CONSTANT_String
8
String类型字面值的引用
CONSTANT_Fieldref
9
对一个字段的符号引用
CONSTANT_Methodref
10
对一个类中方法的符号引用
CONSTANT_InterfaceMethodref
11
对一个接口中方法的符号引用
CONSTANT_NameAndType
12
对一个字段或方法的部分符号引用

2.1、采用字面值赋值方式创建对象

     String str1 = "aaa";

     String str2 = "aaa";

  当以字符串直接创建String类的对象时,会在字符串池中查找是否已经存在该常量

  如上例中,会在String Pool中查找是否存在"aaa"这个对象,如果不存在,则在String Pool中创建一个"aaa"对象,然后将其地址返回,赋给变量str1,这样str1会指向String Pool中的这个"aaa"对象。

  如果在String Pool中查找时已经存在该对象,则不创建任何对象,直接将String Pool中的这个对象地址返回,如上面str2直接得到"aaa"对象的地址,它和str1实际上指向同一个对象。

 

2.2采用new的形式创建对象

  采用new的形式创建对象:

      String str = new String("aaa");

  实际上创建了两个String对象,一个是”aaa”对象,存储在常量空间中一个是使用new关键字为对象申请的堆空间

  这种形式创建字符串对象时仍然会在String Pool中查找。

  如果没有,则首先在String Pool中创建相应的对象,然后再在堆中创建一个对象

  若相同对象已经存在,则在堆中创建对象。

  无论哪种情况,最后返回的地址都是堆中的地址

  如上例,首先在String Pool中查找有没有"aaa"这个字符串对象,如果有,则不在String Pool中再去创建"aaa"这个对象了,直接在堆(heap)中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回来,赋给str引用。

  如果没有,则首先在String Pool中创建一个"aaa"对象,然后再在堆中创建一个"aaa"对象,然后将堆中的这个"aaa"对象的地址返回来,赋给str引用。


3、关于String创建对象的数量

(1) 、 String a = "abc";
  1. 这样创建 1 个对象。
  2. JVM先到字符串池中查找,看是否已经存在值为"abc"的对象,如果存在,则不再创建新的对象,直接返回已存在对象的引用;
  3. 如果不存在,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。
(2)、String a = new String("abc");

  1. 这样创建 2 个对象。
  2. 一个 是放在常量池中的”abc“对象。
  3. 另一个是由 new String()构造器在堆中创建一个“abc”对象。
  4. a本身只是一个引用,放在栈里,用来指向堆中创建出来的对象。
(3)   String a = "abc";

       String b = "abc";

  1. 这样也只创建 1 个对象。
  2. 只在常量池中创建一个对象 "abc",然后将引用分别返回给a 和b。
(4)    String a = "abc" + "def";
  1. 这样创建 1 个对象。
  2. 由于常量字符串在编译时候也就确定的,又因为“abc”和“def”都是字符串常量,因此变量 a 的值在编译时就可以确定了。与 String a = "abcdef"一样。
(5) :

public class Concatenation2 {

 

   public static void main(String[] args) {

     

       String a = "abc";

       String b = "def";

       String c = "abcdef";

       

       String d = "abc" + "def"//不会产生新的字符串

       System.out.println(c == d); //true

       

       String e = a + "def";//会产生新的字符串对象

       System.out.println(e == c); //false

       

       String f = a + b; //会产生新的字符串

       System.out.println(f == c); //false

   }

}

结论:如果“+”连接的两字符串中只要有一个不是字面常量串(即定义过的),是会产生新的字符串对象

(6)、
            如果将 a 和 b 都定义为常量,则:            

public class Concatenation2 {

 

   public static void main(String[] args) {

     

       final String a = "abc";

       final String b = "def";

       String c = "abcdef";

       /*

        * 因为 a  b 都被定义成了常量,所以编译时就确定。

        * 编译时会将常量替换,等同于 String d = "abc" + "def";

        */

       String d = a + b;

       System.out.println(d == c); //true

   }

}

            如果将 a 和 b 只有一个定义为常量,则:
        public class Concatenation2 {

 

   public static void main(String[] args) {

       final String a = "abc";

       String b = "def";

       String c = "abcdef";

       /*

        * 因为 a 为常量 编译时就确定,b为不是常量不能编译时确定

        * 因此 等同于 String d = "abc" + b"

        */

       String d = a + b;

       System.out.println(d == c); //false

   }

}

(7)、如果 a 和 b 都定义为 final ,但不在定义时候初始化,而是在static 块中初始化:

public class Concatenation2 {

 

   final static String a;

   final static String b;

   //此时 a  b 类似于变量,在类首次加载时才进行初始化

   static{

      a = "abc";

      b = "def";

   }

  

   public static void main(String[] args) {

     

      String c = "abcdef";

      String d = a + b;

      System.out.println(c == d);  //false

   }

}



4、重载 “ + ” 与 StringBuilder

4.1、+ 和 += 是Java仅有的两个重载过的操作符

4.2、String  “+” 的神奇之处

首先看如下简单代码:

public class Concatenation {

 

   public static void main(String[] args) {

 

      String a = "abc";

      String b = "def";

     

      String c = a + b;

      String d = "abc" + "def";

     

      String e = new String("abc");

     

      System.out.println(a == e);

      System.out.println(a.equals(e));

      System.out.println(a == "abc");

      System.out.println(a == e.intern());

      System.out.println(c == "abcdef");

      System.out.println(d == "abcdef");

   }

}


/*

 * 1String a = "abc",会在堆中的常量池中创建"abc"这个对象然后把地址赋值给a

 *  String e = new String("abc"),首先在常量池里查找"abc"对象,

 *   如果没有找到,就在常量池中 新创建"abc"对象,然后再将"abc"作为参数值传给String的构造函数在堆中 new 一个对象   

 *    如果找到该对象,则直接在堆中创建new一个"abc"对象,然后将引用返回。

 * 因此 a   e 是两个不同的地址 ,所以 false

 * 2a.equals(e) 比较的是字符串中的内容,因此 true

 * 3a == "abc" , == 比较的是地址。 a  "abc"都是指向常量池对应对象的首地址  因此 true

 * 4e.intern()从官方文档的说明可以看出:

 *    首先在常量池中查找和e这个字符串等值的字符串(比较的方法是equals())

 *    如果没有找到该字符串,则在常量池中创建一个字符串,然后返回该字符串的引用。

 *     因为"abc"已经在常量池中,因此true

 * 5后面讨论

 */


再看为什么5为false 而6为 true呢,首先看如下简单代码:

public class Concatenation2 {

 

   public static void main(String[] args) {

       String a = "abc";

       String b = "def";

     

       String c = a + b ;

   }

}

用 javap -c Concatenation2 或  javap -verbose Concatenation2反编译工具查看:

public class Concatenation2 {

     public Concatenation2();

       Code:

          0: aload_0

          1: invokespecial #1                  // Method java/lang/Object."<init>":()V

          4: return

 

     public static void main(java.lang.String[]);

       Code:

          0: ldc           #2                  // String abc

          2: astore_1

          3: ldc           #3                  // String def

          5: astore_2

          6: new           #4                  // class java/lang/StringBuilder

          9: dup

         10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V

         13: aload_1

         14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

         17: aload_2

         18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

         21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

         24: astore_3

         25: return

   }


  1. 首先看到是使用一个指针指向一个常量池的的对象内容为"abc",而另一个指针指向"def"
  2. 此时,通过new一个StringBuilder,然后对其进行初始化,接着调用两次append方法添加“abc”和“def”
  3. 然后调用toString()操作返回String类型的引用。
注意:凡是new出来的对象绝对不会放在常量中,toString会发生一次内容拷贝,但是也不会在常量池中。
因此在这里常量池String + 常量池String 放在了中。

因此如上的第5 :“abcdef“是在常量池中的字符串对象。而 c 则是在堆中的字符串。 == 比较的是引用。因此为false。

再看另一种情况(6):代码如下,也很简单:

public class Concatenation2 {

 

   public static void main(String[] args) {

     

       String c = "abc" + "def";

   }

}

用javap反编译工具得:

public class Concatenation2 {

     public Concatenation2();

       Code:

          0: aload_0

          1: invokespecial #1    // Method java/lang/Object."<init>":()V

          4: return

 

     public static void main(java.lang.String[]);

       Code:

          0: ldc           #2    // String abcdef

          2: astore_1

          3: return

   }

JVM在编译时就认为这个 ”+“是没有用处的,编译时候就直接合并成:

String c = "abcdef";


因此(6)中 d = ”abcdef“ 都是指向常量池中的同一个地址。因此true。

在例如:

public class Concatenation2 {

 

   public static void main(String[] args) {

     

       final String a = "abc";

       final String b = "def";

 

       String c = a + b;

   }

}


用反编译工具 jvaap 查看:

public class Concatenation2 {

     public Concatenation2();

       Code:

          0: aload_0

          1: invokespecial #1       // Method java/lang/Object."<init>":()V

          4: return

 

     public static void main(java.lang.String[]);

       Code:

          0: ldc           #2        // String abcdef

          2: astore_3

          3: return

   }

 在编译的时候,c 部分会被编译为:String c = "abc" + "def";

但是如果a或b有任意一个不是 final 的都会 new 一个新对象出来:

public class Concatenation2 {

 

   public static void main(String[] args) {

     

       String a = "abc";

       final String b = "def";

 

       String c = a + b;

   }

}

反编译结果:

public class Concatenation2 {

     public Concatenation2();

       Code:

          0: aload_0

          1: invokespecial #1                  // Method java/lang/Object."<init>":()V

          4: return

 

     public static void main(java.lang.String[]);

       Code:

          0: ldc           #2                  // String abc

          2: astore_1

          3: new           #3                  // class java/lang/StringBuilder

          6: dup

          7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V

         10: aload_1

         11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

         14: ldc           #6                  // String def

         16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

         19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

         22: astore_3

         23: return

   }


其次,如果a和b,是某个方法返回回来的,不论方法中是final类型还是常量什么的,都不会被编译时

将数据编译到常量池。因此编译器不会跟踪到方法里面看你做了什么。


总结:并不是 “常量池String” + “常量池String” 的结果一定还在常量池。

4.3、String 和 StringBuilder 的效率问题

4.3.1、String 和 StringBuilder 和 StringBuffer 的区别:
(1)、String 、看如下代码

      String result = "";

      for (int i = 0; i < 10000; i++) {

         result += "hello ";

      }

用 javap 反编译工具查看:

public static void main(java.lang.String[]);

Code:

   0: ldc           #2                  // String

   2: astore_1

   3: iconst_0

   4: istore_2

   5: iload_2

   6: sipush        10000   //循环次数

   9: if_icmpge     38

  12: new           #3                  // class java/lang/StringBuilder

  15: dup

  16: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V

  19: aload_1

  20: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  23: ldc           #6                  // String hello

  25: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  28: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  31: astore_1

  32: iinc          2, 1

  35: goto          5

  38: return

}

解释: 
  1. if_icmpge 对堆栈中的操作数进行“大于或等于的整数比较运算” 。 循环结束跳转到第38行。
  2. 9-38行为一个循环体,在循环体中每循环一次就new一个StringBuilder对象然后进行初始化,接着进行两次append,最后调用oString()方法。因此循环10000次就new了10000个对象。
  3. result += "hello "; 实际会被JVM优化成:
      StringBuilder stringBuilder = 
      stringBuilder.append(
      stringBuilder.append(
      stringBuilder.toString();

(2)、StringBuilder 、看如下代码:

   public String stringBuilder(){

      StringBuilder stringBuilder = new StringBuilder();

      for (int i = 0; i < 10000; i++) {

         stringBuilder.append("hello");

      }

      return stringBuilder().toString();

   }


用 javap 反编译工具查看:

public java.lang.String stringBuilder();

Code:

   0: new           #2                  // class java/lang/StringBuilder

   3: dup

   4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V

   7: astore_1

   8: iconst_0

   9: istore_2

  10: iload_2

  11: sipush        10000   //循环次数

  14: if_icmpge     30

  17: aload_1

  18: ldc           #4                  // String hello

  20: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  23: pop

  24: iinc          2, 1

  27: goto          10

  30: aload_0

  31: invokevirtual #6                  // Method stringBuilder:()Ljava/lang/String;

  34: invokevirtual #7                  // Method java/lang/String.toString:()Ljava/lang/String;

  37: areturn


解释:
  • if_icmpge 对堆栈中的操作数进行“大于或等于的整数比较运算” 。 循环结束跳转到第30行。
  • 14 - 30 行为一个循环体。StringBuilder 只在循环体外new一次和初始化一次。在循环体中每循环一次就append一次。
  • 最后循环结束,将调用toString()方法返回。
  • 因此,StringBuilder在如上这样的循环中销量比String += 高出很多。

 

        那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilderStringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的


StringBuffer 的 insert 方法:

@Override

public synchronized StringBuffer insert(int index, char[] str, int offset,

                                        int len)

{

    toStringCache = null;

    super.insert(index, str, offset, len);

    return this;

}

StringBuilder 的 insert 方法:

@Override

public StringBuilder insert(int index, char[] str, int offset,

                            int len)

{

    super.insert(index, str, offset, len);

    return this;

}

4.3.2、String 和 StringBuilder 和 StringBuffer 的效率测试:

public class Concatenation2 {

   private final static int time = 50000;

  

   public static void stringBuilder()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      StringBuilder stringBuilder = new StringBuilder();

      for (int i = 0; i < time; i++)

      {

         stringBuilder.append("java");

      }

      //结束时间

      long end = System.currentTimeMillis();

      System.out.println(stringBuilder.getClass().getSimpleName()

            + 所耗时间 " + (end-begin) + 毫秒");

   }

  

   public static void stringBuffer()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      StringBuffer stringBuffer = new StringBuffer();

      for (int i = 0; i < time; i++)

      {

         stringBuffer.append("java");

      }

      //结束时间

      long end = System.currentTimeMillis();

      System.out.println(stringBuffer.getClass().getSimpleName()

            + 所耗时间 " + (end-begin) + 毫秒");

   }

  

   public static void string()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      String s = "";

      for (int i = 0; i < time; i++)

      {

         s += "java";

      }

      //结束时间

      long end = System.currentTimeMillis();

      System.out.println(s.getClass().getSimpleName()

            + 所耗时间 " + (end-begin) + 毫秒");

   }

  

   public static void main(String[] args) {

 

      string();

      stringBuilder();

      stringBuffer();

   }

}


4.3.3、String 的直接和间接相加操作效率测试:

public class StringAdd {

 

   private final static int  time = 50000;

  

   public static void stringAddDirect()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      String s = "";

      for (int i = 0; i < time; i++)

      {

         s = "a" + "b" + "c" + "d";

      }

      //结束时间

      long end = System.currentTimeMillis();

      System.out.println("String直接相加测试时间为 " + (end - begin) + 毫秒");

   }

  

   public static void stringAddIndirect()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      String s = "";

      String a = "a" , b = "b" , c = "c" , d = "d";

      for (int i = 0; i < time; i++)

      {

         s = a + b + c + d;

      }

      //结束时间

      long end = System.currentTimeMillis();

      System.out.println("String间接相加测试时间为 " + (end - begin) + 毫秒");

   }

  

   public static void main(String[] args) {

      stringAddDirect();

      stringAddIndirect();

   }

}



4.3.4、JVM对 String  的 “ + =” 操作的优化

public class AddOptimize {

 

   private final static int time = 50000;

  

   public static void testString()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      String s = "";

      for (int i = 0; i < time; i++)

      {

         s += "java";

      }

      long end = System.currentTimeMillis();

      System.out.println(s.getClass().getSimpleName()

            + 类型的 += 操作所使用的时间:" + (end - begin) + 毫秒");

   }

 

   public static void testOptimizeString()

   {

      //开始时间

      long begin = System.currentTimeMillis();

      String s = "";

      for (int i = 0; i < time; i++)

      {

         StringBuilder stringBuilder = new StringBuilder();

         stringBuilder.append(s);

         stringBuilder.append("java");

         stringBuilder.toString();

      }

      long end = System.currentTimeMillis();

      System.out.println("模拟JVM优化操作的时间:" + (end - begin) + 毫秒");

   }

  

   public static void main(String[] args) {

     

      testString();

      testOptimizeString();

   }


4.3.4、JVM对 String  的 “ + =” 和+ 的的比较

public class AddandAddEqual {

 

   private final static int time = 50000;

  

   public static void testAddString()

   {

      long begin = System.currentTimeMillis();

      String s = "a";

      for (int i = 0; i < time; i++)

      {

         s = s + "abc" + "def";

      }

      long end = System.currentTimeMillis();

      System.out.println("s =" + " s +" + " abc " + "+" + " def 所使用时间:"

            + (end - begin) + 毫秒");

   }

  

   public static void testAddEqualString()

   {

      long begin = System.currentTimeMillis();

      String s = "a";

      for (int i = 0; i < time; i++)

      {

         s += "abc" + "def";

      }

      long end = System.currentTimeMillis();

      System.out.println("s +=" + " abc " + "+" + " def 所使用时间: "

            + (end - begin) + 毫秒");

   }

  

   public static void main(String[] args) {

 

      testAddString();

      testAddEqualString();

   }

}

用javap 工具查看:

         public static void testAddString();

Code:

   0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J

   3: lstore_0

   4: ldc           #3                  // String a

   6: astore_2

   7: iconst_0

   8: istore_3

   9: iload_3

  10: ldc           #5                  // int 50000

  12: if_icmpge     46

  15: new           #6                  // class java/lang/StringBuilder

  18: dup

  19: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V

  22: aload_2

  23: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  26: ldc           #9                  // String abc

  28: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  31: ldc           #10                 // String def

  33: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  36: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  39: astore_2

  40: iinc          3, 1

  43: goto          9

  46: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J

  49: lstore_3

  50: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;

  53: new           #6                  // class java/lang/StringBuilder

  56: dup

  57: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V

  60: ldc           #13                 // String s = s + abc + def 所使用时间:

  62: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  65: lload_3

  66: lload_0

  67: lsub

  68: invokevirtual #14                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;

  71: ldc           #15                 // String  毫秒

  73: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  76: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  79: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

  82: return

 

public static void testAddEqualString();

Code:

   0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J

   3: lstore_0

   4: ldc           #3                  // String a

   6: astore_2

   7: iconst_0

   8: istore_3

   9: iload_3

  10: ldc           #5                  // int 50000

  12: if_icmpge     41

  15: new           #6                  // class java/lang/StringBuilder

  18: dup

  19: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V

  22: aload_2

  23: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  26: ldc           #17                 // String abcdef

  28: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  31: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  34: astore_2

  35: iinc          3, 1

  38: goto          9

  41: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J

  44: lstore_3

  45: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;

  48: new           #6                  // class java/lang/StringBuilder

  51: dup

  52: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V

  55: ldc           #18                 // String s += abc + def 所使用时间:

  57: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  60: lload_3

  61: lload_0

  62: lsub

  63: invokevirtual #14                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;

  66: ldc           #15                 // String  毫秒

  68: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  71: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  74: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

  77: return


可以看出 += 效率比 + 高。
因为 s += "abc" + "def";  的 “abc” +“def” 在编译期间就确定了。每次执行循环就少append一次。

下面对上面的执行结果进行一般性的解释:

  1)对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加,在编译期间便被优化成了"Ilovejava"。这个可以用javap -c命令反编译生成的class文件进行验证。

  对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

  2)String、StringBuilder、StringBuffer三者的执行效率:

  StringBuilder > StringBuffer > String

  当然这个是相对的,不一定在所有情况下都是这样。

  比如String str = "hello"+ "world"的效率就比 StringBuilder st  = new StringBuilder().append("hello").append("world")要高。

  因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

  当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;

  当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。



5、正则表达式

1、基础

-?     -->>  0个或1个负号
\\     -->>  意思是我要插入一个正则表达式的反斜杠
\\d   -->> 表示一位数字
\\\\  -->> 插入一个普通的反斜杠
\\+  -->> 插入一个普通的加号
\\d+  -->> 一个或多个数字(“+”表示“一个或多个之前的表达式”)
( - | \\+ ) ?    -->> 表示字符串的起始字符可能是一个“-”或者“+”

String类自带了一个非常有用的正则表达式工具——split()方法,其功能是“将字符串从正则表达式匹配的地方切开”:

public class Splitting {

 

   public static String knights =

         "Then, when you hava found the shrubbery,you must  " +

         "cut dowm the migjties tree in the forest..." +

         "with...a herring!";

  

   public static void split(String regex)

   {

      System.out.println(

            Arrays.toString(knights.split(regex)));

   }

  

   public static void main(String[] args) {

 

      //将字符串从正则表达式匹配的地方切开

      split(" "); //Does't hava to contain regex chars

      split("\\W+");  //Non-word characters

      split("n\\W+"); // 'n' followed by non-word characters

     

      //替换

      System.out.println(knights.replaceFirst("f\\w+""located"));

      System.out.println(knights.replaceAll("shrubbery|tree|herring""banana"));

     

      // Pattern.matches(String regex,CharSequence input)

      for(String pattern : new String[]{

            "Rudolph","[rR]udolph","[rR][aeiou][a-z]ol.*","R.*"})

      {

         System.out.println("Rudolph".matches(pattern));

      }

     

   }

}


如果正则表达式不只是一次使用的话,非String对象的正则表达式明显具备更佳的性能。

2、创建正则表达式

Predefined character classes.Any character (may or may not match line terminators)\dA digit: [0-9]\DA non-digit: [^0-9]\hA horizontal whitespace character: [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]\HA non-horizontal whitespace character: [^\h]\sA whitespace character: [ \t\n\x0B\f\r]\SA non-whitespace character: [^\s]\vA vertical whitespace character: [\n\x0B\f\r\x85\u2028\u2029]\VA non-vertical whitespace character: [^\v]\wA word character: [a-zA-Z_0-9]\WA non-word character: [^\w]
Character classes[abc]ab, or c (simple class)[^abc]Any character except ab, or c (negation)[a-zA-Z]a through z or A through Z, inclusive (range)[a-d[m-p]]a through d, or m through p[a-dm-p] (union)[a-z&&[def]]de, or f (intersection)[a-z&&[^bc]]a through z, except for b and c[ad-z] (subtraction)[a-z&&[^m-p]]a through z, and not m through p[a-lq-z](subtraction)
Logical operatorsXYX followed by YX|YEither X or Y(X)X, as a capturing group
Boundary matchers^The beginning of a line$The end of a line\bA word boundary\BA non-word boundary\AThe beginning of the input\GThe end of the previous match\ZThe end of the input but for the final terminator, if any\zThe end of the input

Greedy quantifiers
Reluctant quantifiers
Possessive quantifiers

X?X??
X?+
X,once or not at all
X*
X*?
X*+
X,zero or more times
X+
X+?
X++
X,one or more times
X{n}
X{n}?
X{n}+
X,exactly n times
X{n, }X{n, }?
X{n, }+
X,at least n times
X{n,m}
X{n,m}?X{n,m}+X,at least n but not more than m times

量词:描述了一个模式吸收输入文本的方式:
  1. 贪婪型:量词总是贪婪的,除非有其他选项被设置。贪婪表达式会为所有可能的模式发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。
  2. 勉强型:用问号来指定,这个量词匹配满足模式所需要的最少字符数。一次也称作:懒惰的、最少匹配的、非贪婪的或不贪婪的。
  3. 占有型:目前,这种类型的量词只有在Java语言中才可用(在其他语言中不可用),并且也更高级。当正则表达式被应用于字符串时,它会产生相当多的状态,以便在匹配失败时可以回溯。而”占有的“量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式的失控,因此可使正则表达式执行起来更有效。
CharSequenceCharBufferSegmentStringStringBufferStringBuilder 类之中抽象出了字符序列的一般化定义

public interface CharSequence {

 

    int length();

    char charAt(int index);

    CharSequence subSequence(int start, int end);

    public String toString();

}  

因此,这些类都实现了该接口。多数正则表达式操作都接受CharSequence类型的参数。

Pattern 与 Matcher 的使用

public class TestRegularExpression {

 

   public static void main(String[] args) {

 

      if(args.length < 2)

      {

         System.out.println("Usage:\njava TestRegularExpression" +

                "characterSequence regularExpression");

         System.exit(0);

      }

      System.out.println("Input:\" " + args[0] + " \"");

      for(String arg : args)

      {

         System.out.println("Regular expression:\" " + arg + " \"");

         // static Pattern.compile(String regex) -->>编译正则表达式,生成Pattern对象

         // Pattern.split(CharSequence input) -->> 用于进行字符串分割

         /*

          * Pattern pattern = Pattern.compile(arg);  参数 arg 表示正则表达式的字符串

            Matcher m = pattern.matcher(args[0]);    参数 args[0] 表示将要进行匹配的字符串

          */

         Pattern pattern = Pattern.compile(arg);

         Matcher m = pattern.matcher(args[0]);

         while(m.find())

         {

            System.out.println("Match \" " + m.group() + " \" at positions "

                   + m.start() + " - " + m.end());

         }

      }

   }

}


find( ) 的使用:与 lookingAt( ) 、 maches( ) 的区别见文档

public class Finding {

 

   public static void main(String[] args) {

 

      Matcher m = Pattern.compile("\\w+").matcher("" +

            "Evening is full of the full linnet's wings");

      while(m.find())

      {

         System.out.print(m.group() + " ");

      }

      System.out.println();

      int i = 0;

      while(m.find(i))

      {

         System.out.print(m.group() + " ");

         i++;

      }

   }

}


组(Group):组号 0 表示整个表达式。 组号 1 表示被第一对括号括起的组,依次类推 · · · · · · · ·
            A(B(C))D  --->> 共3组、组0是ABCD、组1是BC、组2是C
            ((A)B)(C)  --->> 共4组、组0是ABC、组1是AB 、组2是A、组3是C

public class Groups {

 

   static public final String POEM =

         "Twas brilligand the slithy toves\n" +

         "Did gyre and gimble in the wabe.\n" +

         "All mimsy were the borogoves,\n" +

         " And the mome raths outgrabe.\n" +

         " 'Beware the Jabberwockmy son,\n" +

         " The jaws that bitethe claws that catch.\n" +

         " Beware the Jubjub birdand shun\n" +

         " The frumious Bandersnatch.";

  

   public static void main(String[] args) {

 

      Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$").matcher(POEM);

      System.out.println(m.groupCount());

      while (m.find())

      {

         for (int i = 0; i < m.groupCount(); i++)

         {

            System.out.print("[ " + m.group(i) + " ]");

         }

         System.out.println();

      }

   }

}


3、Pattern标记

        public static Pattern compile(String regex,int flags)

注意:可以通过 ”| “ 操作组合多个标记的功能。
Modifier and TypeField and Descriptionstatic intCANON_EQ
Enables canonical equivalence.
static intCASE_INSENSITIVE     (?i)
Enables case-insensitive matching.
static intCOMMENTS      (?x)
Permits whitespace and comments in pattern.
static intDOTALL     (?s)
Enables dotall mode.
static intLITERAL
Enables literal parsing of the pattern.
static intMULTILINE     (?m)
Enables multiline mode.
static intUNICODE_CASE     (?u)
Enables Unicode-aware case folding.
static intUNICODE_CHARACTER_CLASS
Enables the Unicode version of Predefined character classes and POSIX character classes.
static intUNIX_LINES     (?d)
Enables Unix lines mode.

-->>>>>replaceAll(String replacement)  replaceFirst(String replacement)  appendReplacement(StringBuffer sb, String replacement)  appendTail(StringBuffer sb) 的使用

public class TheReplacements {

  

   public static void main(String[] args) {

 

      String s =

            "/*!Here's a block of text to use as input to      " +

            "   the ragular expression matcher. Note that we'll     " +

            "  first special delimiters,then process the    " +

            "  extrated block.!*/";

      System.out.println(s);

      Matcher mInput = Pattern.compile("/\\*!(.*)!\\*/",Pattern.DOTALL).matcher(s);

      if(mInput.find())

      {

         s = mInput.group(1);

      }

      System.out.println(s);

      // 替换所有匹配成功的

      s = s.replaceAll(" {2,}"" ");

      s.replaceAll("(?m)^ +""");

      System.out.println(s);

      // 替换第一次匹配成功的

      s = s.replaceFirst("[aeiou]""(VOWEL1)");

      System.out.println(s);

 

      StringBuffer stringBuffer = new StringBuffer();

      Pattern pattern = Pattern.compile("[aeiou]");

      Matcher matcher = pattern.matcher(s);

      // 从位置0开始匹配

      while(matcher.find())

      {

         // Matcher.appendReplacement() 允许在执行替换的过程中,操作用来替换的字符串

         // 将匹配后的字符串(即匹配并操作到的最后位置的结果)放入stringBuffer

         matcher.appendReplacement(stringBuffer, matcher.group().toUpperCase());

      }

      System.out.println(stringBuffer);

      // 将匹配截止位置后的字符串添加到stringBuffer

      matcher.appendTail(stringBuffer);

      System.out.println(stringBuffer);

   }

}

结果:
/*!Here's a block of text to use as input to the ragular expression matcher. Note that we'll first special delimiters,then process the extrated block.!*/
Here's a block of text to use as input to the ragular expression matcher. Note that we'll first special delimiters,then process the extrated block.
Here's a block of text to use as input to the ragular expression matcher. Note that we'll first special delimiters,then process the extrated block.
H(VOWEL1)re's a block of text to use as input to the ragular expression matcher. Note that we'll first special delimiters,then process the extrated block.
H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO thE rAgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll fIrst spEcIAl dElImItErs,thEn prOcEss thE ExtrAtEd blO
H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO thE rAgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll fIrst spEcIAl dElImItErs,thEn prOcEss thE ExtrAtEd blOck.

------->>>>> reset() 方法的使用

public class Resetting {

 

   public static void main(String[] args) {

 

      Matcher matcher = Pattern.compile("[frb][aiu][gx]")

            .matcher("fix the rug with bags");

      while (matcher.find())

      {

         System.out.print(matcher.group() + " ");

      }

      System.out.println();

      // 将位置重新移到开始位置

      matcher.reset();

      while (matcher.find())

      {

         System.out.print(matcher.group() + " ");

      }

      System.out.println();

      // 将现有Matcher对象应用于一个新的字符序列

      matcher.reset("fix the rig with rags");

      while (matcher.find())

      {

         System.out.print(matcher.group() + " ");

      }

   }

}


4、扫描输入

public class SimpleRead {

 

   public static BufferedReader input = new BufferedReader(

         new StringReader("Sir Robin of Camelot\n22 1.61038"));

  

   public static void main(String[] args) {

 

      try{

         System.out.println("What is your name ?");

         String name = input.readLine();

         System.out.println(name);

        

         System.out.println("How old are you ?  What is your favorite double ?");

         System.out.println("input : <age> <double");

         String numbers = input.readLine();

         System.out.println(numbers);

        

         String[] numArray = numbers.split(" ");

         int age = Integer.parseInt(numArray[0]);

         double favorite = Double.parseDouble(numArray[1]);

         System.out.println("age = " + age + "  ,  " + "favorite = " + favorite);

      }catch(IOException e){

         System.out.println(e);

      }

   }

}



用 Scanner,减轻扫描的工作负担:

public class BetterRead {

 

   public static void main(String[] args) {

 

      Scanner stdin = new Scanner(SimpleRead.input);

     

      System.out.println("What is your name ?");

      // Scanner.nextLine()

      //-->Advances this scanner past the current line and returns the input that was skipped.

      String name = stdin.nextLine();

      System.out.println(name);

     

      System.out.println("How old are you ?  What is your favorite double ?");

      System.out.println("input : <age> <double");

      int age = stdin.nextInt();

      double favorite = stdin.nextDouble();

      System.out.println(age);

      System.out.println(favorite);

      System.out.println("age = " + age + "  ,  " + "favorite = " + favorite);

   }

}

用正则表达式扫描:

public class ThreatAnalyzer {

 

   static String threatData =

         "58.27.82.161@02/10/2005\n" +

         "204.45.234.40@02/11/2005\n" +

         "58.27.82.161@02/11/2005\n" +

         "58.27.82.161@02/12/2005\n" +

         "58.27.82.161@02/12/2005\n";

  

   public static void main(String[] args) {

 

      Scanner scanner = new Scanner(threatData);

      String pattern = "((\\d+[.]){3}\\d+)@" +

            "(\\d{2}/\\d{2}/\\d{4})";

      while (scanner.hasNext(pattern))

      {

         // 找到下一个匹配该模式的输入部分

         scanner.next(pattern);

         // 将匹配结果赋值给 MatchResult 对象

         MatchResult matchResult = scanner.match();

         String ip = matchResult.group(1);

         String date = matchResult.group(3);

         System.out.println("Threat on " + date + " form " + ip);

      }

   }

}





































0 0
原创粉丝点击