Java字符串的截取操作可以通过substring来完成。有意思的是,这个方法从jdk1.0开始,一直到1.6都没有变化,但到了1.7实现方式却发生了改变。你可能会认为之所以要对一个成熟且稳定的方法做修改,一定是因为新的实现更好、效率更高吧?然而正好相反,修改后的substring的效率变低了,并且占用了更多的内存,无论是从时间上还是空间上都比不上原有的实现。下面我们来做一个比较,看看到底哪一个更好,以及为什么新版Java中要对其进行修改。
  原有实现
  我们首先来看看原来的substring方法。前面是对参数进行检查,重点是后一句:
  return ((beginIndex == 0) && (endIndex == count)) ? this :
  new String(offset + beginIndex, endIndex - beginIndex, value);
  这里通过调用下面这个构造方法来创建一个新的字符串:
  // Package private constructor which shares value array for speed.
  String(int offset, int count, char value[]) {
  this.value = value;
  this.offset = offset;
  this.count = count;
  }
  我们知道,Java的字符串实际上是用一个字符数组来实现的,这个构造方法通过复用字符数组value,省去了数组拷贝的开销,仅通过3个赋值语句创建了一个新的字符串对象。从注释也可以看出这个构造方法的意图是为了提升性能。
  新的实现
  我们再来看看1.7中新的substring实现。前面一堆还是参数检查,直接看后一句:
  return ((beginIndex == 0) && (endIndex == value.length)) ? this
  : new String(value, beginIndex, subLen);
  与原来的差不多,但是请注意,这次调用的是另一个构造方法:
  public String(char[], int, int)
  这个公有的构造方法和前面那个很相似(那个是包私有的),从方法签名上看区别仅仅是参数顺序不同。不过这只是表面现象,它们的内部实现却是完全不同的,这个公有的构造方法不会复用char[]数组,而是将其拷贝到一个新数组,从而创建一个新字符串。
  this.value = Arrays.copyOfRange(value, offset, offset+count);
  对公有的构造方法来说,必须采用这种方式,如果仍然采用复用数组的方法,会发生安全性问题,别人可以对字符串中的字符进行任意的修改。后面会对此进行分析。
  复用字符数组有没有安全隐患
  Java的字符串是不可变的,原因是作为字符串底层实现的字符数组是私有的,从外面无法访问。另一方面,String类的每一个可以创建新字符串的公有方法(构造方法、valueOf等),如果其接受一个字符数组作为参数,会对该数组执行拷贝操作,这进一步保证了只有String对象才会持有它的字符数组,因此断绝了从外部修改数组的一切可能。
  如果不这么做会带来问题,字符串的不可变性也不复存在了。比如下面这个假想的程序:
  char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
  String s = new String(0, arr.length, arr); // "hello world"
  arr[0] = 'a'; // replace the first character with 'a'
  System.out.println(s); // aello world
  如果构造方法没有对arr进行拷贝,那么其他人可以在字符串外部修改该数组,由于它们引用的是同一个数组,因此对arr的修改相当于修改了字符串。(可以通过反射来真正地实现这个假想的程序)
  还有一些方法,比如原来的substring方法,它们没有进行数组拷贝,而是直接复用另一个字符串的内部数组。这样做会导致安全问题吗?答案是不会,因为所有这些方法所执行的操作都是私有操作或包私有操作,属于内部实现,因此只要不对外暴露这些操作的接口仍然是安全的。
  例如对substring来说,由于无论是原字符串还是新字符串,其value数组本身都是String对象的私有属性,从外部是无法访问的,因此对两个字符串来说都很安全。