目录
二. 字符串缓冲类(StringBuilder和StringBuffer)
2.1 深入理解String、StringBuilder、StringBuffer
2.2 不同场景下String、StringBuilder、StringBuffer的性能测试
4.2 Java常用正则表达式验证工具类RegexUtils.java
一. 字符串(String类)
字符是一种通用数据类型,是用单引号括起来的一个字符,如 'a'、'A'、'C'。而跟字符密切相关的通用数据类型是字符串。
字符串常量是用双引号括起来的一串字符,如"你好"、"Hello World !\n"。在Java中,字符串常量是作为String类的一个特定的对象来处理的,而不是一个数据。
1.1 字符串的创建
package cn.test_01;
/*
* 字符串:就是由多个字符组成的一串数据。也可以看成是一个字符数组。
* 通过查看API,我们可以知道
* 1:字符串字面值"abc"也可以看成是一个字符串对象。
* 2:字符串是常量,一旦被赋值,就不能被改变。
* 构造方法:
* public String() 无参构造
* public String(String original) 把字符串常量值转成字符串
* 字符串的方法:
* public int length() 返回此字符串的长度。字符串中有.length()方法,而数组中有.length属性
*/
public class StringTest {
public static void main(String[] args) {
//方法一
String s1 = "abcde"; //字符串后面的值"abc"可以看成是一个字符串对象
System.out.println("s1:"+s7);
System.out.println("s1.length():"+s1.length());
System.out.println("--------------------------");
//方法二
String s2= new String(); //通过无参构造方法,创建一个字符串对象
System.out.println("s2:" + s2);
System.out.println("s2.length():" + s2.length());
System.out.println("--------------------------");
//方法三
//public String(String original) 把字符串常量值转成字符串
String s3 = new String("abcde"); //通过有参构造方法,创建一个字符串对象
System.out.println("s3:" + s3);
System.out.println("s3.length():" + s3.length());
System.out.println("--------------------------");
//方法四
char [] bunch = {'H','e','l','l','o'};
String s4 = new String(bunch); //通过使用一个字符数组语句来创建字符串的对象
System.out.println("s4:" + s4);
System.out.println("s4.length():" + s4.length());
}
}
字符串是常量,一旦被赋值,就不能被改变。
想要了解一个类,最好的办法就是看这个类的实现源代码,
String类的源码在\jdk1.6.0_14\src\java\lang\String.java 文件中。 属于java.lang包
String类本质上是字符数组char[] ,private final char value[];,并且其值不可改变。字符串一旦被赋值,就不能被改变。而且String类是final的,不可被继承,完整语句是public final class String。String类对象有个特殊的创建的方式,就是直接指定,比如String x = "abc","abc"就表示一个字符串对象。而x是"abc"对象的地址,也叫做"abc"对象的引用。String对象可以通过“+”串联,串联后会生成新的字符串。比如String s3 = "ab" + "c"; ,也可以通过concat()方法来串联。
字符串被赋值之后,它在对应方法区中字符串常量池里面的数值就不能被改变,但是这个字符串在栈中所指向方法区的地址(引用),是可以改变的。
1.2 String类中的方法
1.2.1 先查看String类中一些方法实现的源代码
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value); //新的字符串
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf); //新的字符串
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = count;
int i = -1;
char[] val = value; /* avoid getfield opcode */
int off = offset; /* avoid getfield opcode */
while (++i < len) {
if (val[off + i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0 ; j < i ; j++) {
buf[j] = val[off+j];
}
while (i < len) {
char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(0, len, buf); //新的字符串
}
}
return this;
从上面的三个方法可以看出,无论是sub操作、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。在这里要永远记住一点:“对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
1.2.2 字符串比较
比较两个字符串的内容是否一样,可以使用String类提供的equals()和equalsIgnoreCase()两个方法。equals()测试两个字符串的内容是否完全一样,而equalsIgnoreCase()是忽略字符串中的大小写来比较两个字符串。因为Java是区分大小写的,所以两者比较的结果是不一样的。
如果要比较两个字符串的大小关系,即它们按字母顺序排列的时候谁在前谁在后。String 类提供了一个compareTo()方法,其方法原型为: public int compareTo(String str); 它的返回值是一个整型的值,即一个整数。如果返回值为 -1,则该字符串位于str的前面;如果返回值为0,则该字符串与str 内容一样;如果返回值为 1,则该字符串在str 后面。 “负在前”
package cn.test_02;
/*
* String s = new String(“hello”) 和
* String s = “hello”;
* 两者是有区别的。前者会创建2个对象,后者创建1个对象。
*
* ==: 比较引用类型比较的是地址值是否相同
* equals: 比较引用类型默认也是比较地址值是否相同,而String类重写了equals()方法 ,比较的是内容是否相同。
*/
public class StringDemo2 {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = "hello";
System.out.println(s1 == s2); // false,比较地址
System.out.println(s1.equals(s2)); // true ,比较内容
}
}
String s3 = "hello";
String s4 = "hello";
System.out.println(s3 == s4); // true 这两个字符串的字面量,都是直接从内存找,所以true
System.out.println(s3.equals(s4));// true
String s5 = "world";
String s6 = "helloworld";
System.out.println(s6 == s3 + s5); // false 字符串如果是变量相加,先开空间,再拼接
System.out.println(s6.equals(s3 + s5));// true
System.out.println(s6 == "hello"+"world"); // true 字符串如果是常量相加,先相加,再在常量池找,如果有就直接返回,否则就创建
System.out.println(s6.equals("hello"+"world"));// true
1.2.3 hashCode() 方法
String类提供一个hashCode() 方法,返回int 类型的哈希值,其计算公式为:
hashcode = s[0]*31^(n-1) + s[1]*31^(n-2)+ ... + s[n-2]*31^1+s[n-1]*31^0
其中s[i]是字符串中的第i 个字符,n是字符串的长度,^ 是幂运算符。规定空字符串的哈希值为0。
public class TestHashCode{
public TestHashCode(){}
public static void main(String args[]){
String str = "abc";
int code = str.hashcode();
System.out.println("字符串" + str + "的哈希值为:" + code);
}
}
equal() 和hashCode() 这两个方法都是Object类的方法,用于判断对象是否相等。一般来说,如果忽略其中任意一个,就必须同时忽略这两个,因为两者之间有着必须维持的至关重要的紧密联系。特殊情况:根据equals() 方法判断,如果两个对象是相等的,则它们必须有相同的hashCode()值(尽管这中情况通常不是真的)。
1.2.4 String类中其它的常用方法
1. 通过调用length()方法得到String的长度。StringBuffer类的capacity()方法与String类的length()的方法类似,但是它测试是分配给StringBuffer的内存空间的大小,而不是当前被使用了的内存空间。
String str="This is a String";
int len =str.length();
2. 如果想确定字符串中指定字符或子字符串在给定字符串的位置,可以用 indexOf()和lastIndexOf()方法。
String str="This is a String";
Int index1 =str.indexOf(i); //index=2,下标从0开始
Int index2=str.indexOf(‘i‘,index+1); //index2=5
Int index3=str.lastIndexOf(I); //index3=15
Int index4=str.indexOf(String); //index4=10
3. String对象的访问,方法charAt()用以得到指定位置的字符。
String str="This is a String";
char chr=str.charAt(3); //chr=i
4. getChars()方法用以得到字符串的一部分字符串
public void getChars(int srcBegin,int srcEnd,char[]dst,int dstBegin)
String str="This is a String";
Char chr =new char[10];
Str.getChars(5,12,chr,0); //chr=is a St
subString()方法是提取字符串的另一种方法,它可以指定从何处开始提取字符串以及何处结束。
5.操作字符串,replace()方法可以将字符串中的一个字符替换为另一个字符。
String str="This is a String";
String str1=str.replace(‘T‘,‘t‘); //str1=this is a String
6. concat()方法可以把两个字符串合并为一个字符串。
String str="This is a String";
String str1=str.concat(Test); //str1=This is a String Test
7. toUpperCase()和toLowerCase()方法分别实现字符串大小写的转换。
String str="THIS IS A STRING";
String str1=str.toLowerCase(); //str1=this is a string;
8. trim()方法可以将字符串中开头和结尾处的空格去掉.
String str="This is a String" ;
String str1=str.trim(); // str1=This is a String
9. String 类提供静态方法valueOf(),它可以将任何类型的数据对象转换为一个字符串。如
System.out.println(String,ValueOf(math,PI));
10.split()方法进行分割字符串
public String[] splitString(String str,String sdelimiter)...{
String[] array=str.split(sdelimiter);
return array;
}
二. 字符串缓冲类(StringBuilder和StringBuffer)
2.1 深入理解String、StringBuilder、StringBuffer
既然在Java中已经存在了String类,那为什么还需要StringBuilder和StringBuffer类呢?
先看一段代码,如下:
public class Main {
public static void main(String[] args) {
String string = "";
for(int i=0;i<10000;i++){
string += "hello";
}
}
}
这句 string += "hello"; 的过程相当于将原有的string变量指向的对象内容取出与"hello"作字符串相加,再存进另一个新的String对象当中,再让string变量指向新生成的对象。如果还有疑问可以反编译其字节码文件便清楚了:
从这段反编译出的字节码文件可以很清楚地看出:从第8行开始到第35行是整个循环的执行过程,并且每次循环会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。也就是说这个循环执行完毕new出了10000个对象,试想一下,如果这些对象没有被回收,会造成多大的内存资源浪费。从上面还可以看出:string+="hello"的操作事实上会自动被JVM优化成:
StringBuilder str = new StringBuilder(string);
str.append("hello");
str.toString();
再看下面这段代码:
public class Main {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
for(int i=0;i<10000;i++){
stringBuilder.append("hello");
}
}
}
反编译字节码文件得到:
从这里可以明显看出,这段代码的for循环式从13行开始到27行结束,并且new操作只进行了一次,也就是说只生成了一个对象,append操作是在原有对象的基础上进行的。因此在循环了10000次之后,这段代码所占的资源要比上面小得多。
那么有人会问既然有了StringBuilder类,为什么还需要StringBuffer类?查看源代码便一目了然,事实上,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。
下面两段代码分别来自StringBuffer和StringBuilder,insert方法的具体实现:
public StringBuilder insert(int index, char str[], int offset,
int len) //StringBuilder的insert()方法
{
super.insert(index, str, offset, len);
return this;
}
public synchronized StringBuffer insert(int index, char str[], int offset,
int len) //StringBuffer的insert()方法,方法前面有synchronized关键字,是线程安全的
{
super.insert(index, str, offset, len);
return this;
}
2.2 不同场景下String、StringBuilder、StringBuffer的性能测试
先写出一个测试案例,观察String、StringBuilder、StringBuffer三个类的性能区别,代码如下:
public class Main {
private static int time = 50000;
public static void main(String[] args) {
testString();
testStringBuffer();
testStringBuilder();
test1String();
test2String();
}
public static void testString () {
String s="";
long begin = System.currentTimeMillis(); //产生一个当前的毫秒,这个毫秒是自1970年1月1日0时起至今的毫秒数。作为开始时间
for(int i=0; i<time; i++){
s += "java";
}
long over = System.currentTimeMillis(); //又产生一个最当前的毫米。最为结束时间
System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");//字符串的拼接,其中s.getClass().getName()部分结果是java.lang.String
}
public static void testStringBuffer () {
StringBuffer sb = new StringBuffer();
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
sb.append("java");
}
long over = System.currentTimeMillis(); //下一行中输出结果是字符串的拼接,其中sb.getClass().getName()部分得到的结果是java.lang.StringBuffer
System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
}
public static void testStringBuilder () {
StringBuilder sb = new StringBuilder();
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
sb.append("java");
}
long over = System.currentTimeMillis();
System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
}
public static void test1String () {
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
String s = "I"+"love"+"java";
}
long over = System.currentTimeMillis();
System.out.println("字符串直接相加操作:"+(over-begin)+"毫秒");
}
public static void test2String () {
String s1 ="I";
String s2 = "love";
String s3 = "java";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
String s = s1+s2+s3;
}
long over = System.currentTimeMillis();
System.out.println("字符串间接相加操作:"+(over-begin)+"毫秒");
}
}
上文中提到string+="hello"的操作,事实上会自动被JVM优化,在以下程序中可以达到验证,代码如下:
public class Main {
private static int time = 50000;
public static void main(String[] args) {
testString();
testOptimalString();
}
public static void testString () {
String s="";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
s += "java";
}
long over = System.currentTimeMillis();
System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
}
public static void testOptimalString () {
String s="";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
StringBuilder sb = new StringBuilder(s);
sb.append("java");
s=sb.toString();
}
long over = System.currentTimeMillis();
System.out.println("模拟JVM优化操作的时间为:"+(over-begin)+"毫秒");
}
}
对上面的执行结果进行一般性的解释:
1)对于常量字符串直接相加,String效率很高,因为在编译器便确定了它的值,也就是说形如"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。
三. 字符串分解类(StringTokenizer类)
StringTokenizer类的源码在\jdk1.6.0_14\src\java\util\StringTokenizer.java 文件中。 属于java.util包
3.1 StringTokenizer的功能
String类使用split()方法运用正则表达式分解字符串,而StringTokenizer类的对象可以直接分解字符串。 有时需要分析字符串并将字符串分解成可被独立使用的单词,这些单词叫做语言符号。当分析一个字符串并将字符串分解成可被独立使用的单词时,可以用StringTokenizer类。
3.2 StringTokenizer的方法
构造方法:
1. StringTokenizer(String str) :构造一个用来解析str的StringTokenizer对象。Java默认的分隔符有“空格”、“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”。例如:StringTokenizer fenxi=new StringTokenizer("we are student"); //分析器将以空格为分割标记,将字符串中的三个单词分开。
2. StringTokenizer(String str, String delim) :构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符。例如:StringTokenizer fenxi=new StringTokenzier("We,are;student",",;"); / /分析器将一,和;为分割标记,将三个单词分开。
3. StringTokenizer(String str, String delim, boolean returnDelims) :构造一个用来解析str的StringTokenizer对象,并提供一个指定的分隔符,同时,指定是否返回分隔符。其中第一个参数就是要分隔的String,第二个是分隔字符集合,第三个参数表示分隔符号是否作为标记返回。
1. int countTokens():返回nextToken方法被调用的次数。如果采用构造函数1和2,返回的就是分隔符数量(例2)。
2. boolean hasMoreTokens() :返回是否还有分隔符。
3. boolean hasMoreElements() :结果同2。
4. String nextToken():返回从当前位置到下一个分隔符的字符串。
5. Object nextElement() :结果同4。
6. String nextToken(String delim):与4类似,以指定的分隔符返回结果。
核心方法:
import java.util.*;
public class FenXiQi{
public static void main(String[] args){
String s="I love java !";
StringTokenizer tokenizer=new StringTokenizer(s);
int number=tokenizer.countTokens();
while(tokenizer.hasMoreTokens()){
String str=tokenizer.nextToken();
System.out.println(str);
System.out.println("还剩"+tokenizer.countTokens()+"个单词");
}
}
}
四. 正则表达式(Regular Expression)
4.1 概念
待续
4.2 Java常用正则表达式验证工具类RegexUtils.java
待续
------------------------------------------------------------------ 我是低调的分隔线 ----------------------------------------------------------------------
吾欲之南海,一瓶一钵足矣...