Java编程思想 第十三章:字符串

1.不可变String

String对象是不可变的,每一个看似修改了String值的方法,实际上都是创建了一个全新的String对象。

public class Immutable {
	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);
		System.out.println(q);
	}
}

运行结果:
howdy
HOWDY
howdy

当把q传给upCase方法时,实际上传递的是引用的一个拷贝。其实每当String对象作为参数传递时,传递的都是一个拷贝。再看upCase方法,只有当该方法运行时,局部引用s才存在,一旦该方法结束,引用s就消失了。该方法的返回值,实际上是最终值的引用,也就是upCase返回的引用已经指向了一个新的对象,而原本的q并没有发生任何变化。比如如下的方法,我们并不希望在经过一个操作之后,改变原有的对象。

因为方法的参数是用来传递信息的,而不是用来改变原有对象本身的。

2.重载“+”与StringBuilder

String对象是不可变的,所以指向它的任何引用都不会改变该对象的值。不可变性会对效率带来一个问题,为String对象重载“+”操作符就是一个例子,重载的意思是一个操作符应用与特定的类时被赋有特殊的意义。(用于String的“+”和"+="是Java中仅有的两个重载过的操作符,Java不允许程序员自己重载操作符)

操作符“+”可以用来连接两个String

public class Concatetion {
	public static void main(String[] args) {
		String mango = "mango";
		String s = "abc" + mango + "def" + 42;
		System.out.println(s);
	}
}

上述代码的运行过程可能是这样的:String可能有一个append方法,然后它会生成一个新的String对象用来连接abc和mango,然后该对象再与def相连生成新的对象,依次类推。这样做的话会产生很多中间垃圾需要清理因此它效率极低。

我们代码中并没有使用StringBuilder类,然而编译器却自动的引入了StringBuilder类,从编译后的代码可以看出,字符串连接工作主要的操作是编译器创建一个StringBuilder对象,然后调用该对象的append()方法将所有的要连接的字符串连接到后边,最后调用toString()方法转换成String对象存给s。

因此在编写一个类似toString()的方法时,如果字符串较短时,我们可以使用普通的拼接方式,当字符串操作较为复杂的时候,我们在代码中直接创建一个StringBuilder对象进行操作效率会更加优异。

3. 无意识的递归

我们希望使用toString()方法打印出对象的内存地址,那么我们可能会考虑使用this关键字:

import java.util.ArrayList;
import java.util.List;
public class InfiniteRecursion {
	public String toString(){
		return "InfiniteRecursion address" + this;
	}
	public static void main(String[] args) {
		List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>();
		for(int i = 0;i<10;i++){
			v.add(new InfiniteRecursion());
		}
		System.out.println(v);
	}
}

此时发生了类型转换,编译器发现“+”后边不是String类型,会试图转换成String类型,转换的方式就是调用toString方法,因此会递归调用,此处如果想正确打印地址,那么需要使用其基类Object的toString()方法。

4. String上的操作

public int java.lang.String.hashCode()
//比较两个字符串 重载了Object的方法 当两个字符串值相同、类型相同则认为相同 忽略了引用
public boolean java.lang.String.equals(java.lang.Object)
public java.lang.String java.lang.String.toString()
//获取指定索引下标位置上的字符
public char java.lang.String.charAt(int)
public int java.lang.String.codePointAt(int)
public int java.lang.String.codePointBefore(int)
public int java.lang.String.codePointCount(int,int)
public int java.lang.String.compareTo(java.lang.Object)
//按词典顺序比较两个字符串 大小写并不等价
public int java.lang.String.compareTo(java.lang.String)
public int java.lang.String.compareToIgnoreCase(java.lang.String)
//字符串连接 返回一个新的String为指定String连接参数
public java.lang.String java.lang.String.concat(java.lang.String)
//查找字符串中是否包含指定字符 存在返回true
public boolean java.lang.String.contains(java.lang.CharSequence)
//比较两个字符串
public boolean java.lang.String.contentEquals(java.lang.StringBuffer)
public boolean java.lang.String.contentEquals(java.lang.CharSequence)
public static java.lang.String java.lang.String.copyValueOf(char[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public boolean java.lang.String.endsWith(java.lang.String)
//比较字符串是否相同 忽略大小写
public boolean java.lang.String.equalsIgnoreCase(java.lang.String)
public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[])
public void java.lang.String.getBytes(int,int,byte[],int)
public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException
public byte[] java.lang.String.getBytes(java.nio.charset.Charset)
public byte[] java.lang.String.getBytes()
// s.getChars(1,2,char,3) 赋值s串中下标为1-2的到char中 char中起始位置为3
public void java.lang.String.getChars(int,int,char[],int)
public int java.lang.String.indexOf(int,int)
public int java.lang.String.indexOf(java.lang.String,int)
//是否包含该字符 包含返回下标 否则返回-1
public int java.lang.String.indexOf(java.lang.String)
public int java.lang.String.indexOf(int)
public native java.lang.String java.lang.String.intern()
public boolean java.lang.String.isEmpty()
public int java.lang.String.lastIndexOf(int,int)
public int java.lang.String.lastIndexOf(java.lang.String)
public int java.lang.String.lastIndexOf(java.lang.String,int)
public int java.lang.String.lastIndexOf(int)
//String 中字符的个数
public int java.lang.String.length()
public boolean java.lang.String.matches(java.lang.String)
public int java.lang.String.offsetByCodePoints(int,int)
public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int)
public boolean java.lang.String.regionMatches(int,java.lang.String,int,int)
//把字符串中的第一个参数字符替换成第二个
public java.lang.String java.lang.String.replace(char,char)
public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)
public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String)
public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String,int)
public java.lang.String[] java.lang.String.split(java.lang.String)
public boolean java.lang.String.startsWith(java.lang.String,int)
//字符串起始串
public boolean java.lang.String.startsWith(java.lang.String)
public java.lang.CharSequence java.lang.String.subSequence(int,int)
//字符串截断
public java.lang.String java.lang.String.substring(int)
public java.lang.String java.lang.String.substring(int,int)
//返回一个字符数组,该字符数组包含字符串的所有字符
public char[] java.lang.String.toCharArray()
//字符串字符转换成小写
public java.lang.String java.lang.String.toLowerCase()
public java.lang.String java.lang.String.toLowerCase(java.util.Locale)
//字符串字符转换成大写
public java.lang.String java.lang.String.toUpperCase()
public java.lang.String java.lang.String.toUpperCase(java.util.Locale)
//删除String两端的空白字符
public java.lang.String java.lang.String.trim()
public static java.lang.String java.lang.String.valueOf(char)
public static java.lang.String java.lang.String.valueOf(float)
public static java.lang.String java.lang.String.valueOf(int)
public static java.lang.String java.lang.String.valueOf(long)
public static java.lang.String java.lang.String.valueOf(double)
public static java.lang.String java.lang.String.valueOf(java.lang.Object)
public static java.lang.String java.lang.String.valueOf(char[])
public static java.lang.String java.lang.String.valueOf(char[],int,int)
public static java.lang.String java.lang.String.valueOf(boolean)
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException

5. 格式化输出

5.1 printf

JavaSE5提供了格式化输出功能,这一功能使得控制输出的功能变得更加简单,同时也给开发者带来了更加强大的代码输出控制能力。JavaSE5引入的format方法可以用于PrintStream或PrintWriter对象,其中也包括System.out对象。format()方法模仿在C语言的printf()如下简单示例:


public class SimpleFormat {
	public static void main(String[] args) {
		int x = 5;
		double y = 5.333221;
		System.out.println("Row1: [" + x + " " + y + "]");
		System.out.format("Row1: [%d %f]\n",x,y);
		System.out.printf("Row1: [%d %f]\n",x,y);
	}
}
运行结果:
Row 1: [5 5.332542]
Row 1: [5 5.332542]
Row 1: [5 5.332542]

5.2 System.out.format

System.out.format("Row1: [%d %f]\n",x,y);

5.3 Formatter

Java中所有新的格式化功能都由java.util.Formatter类处理,可以将其看做是一个翻译器,它将你的格式化字符串和数据翻译成想要的结果。当你创建了一个Formatter对象的时候,需要向编译器传递一些信息,告诉他最终的结果将向哪里输出。

5.4 格式化说明符

%[argument_index$][flags][width][precision] conversion

有的时候我们希望做一些更精致的格式化信息,比如控制空格与对齐,最常见的是控制域的最小尺寸,这可以通过指定width实现,Formatter对象通过在必要时添加空格,来确保一个域至少达到某个长度。默认情况下域是右对齐的,不过也可以通过“-”指定数据的对齐方式。

7.扫描输入

目前来说,读取文本或者从标准输入读取数据一般的解决方法是读入一行文本,然后对其进行分词,然后使用Integer、Double的各种解析方法来解析数据。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
 
public class SimpleRead {
	public static BufferedReader input = new BufferedReader(new StringReader("Sir Robin of Camelot\n22 1.523222"));
	public static void main(String[] args) {
		try{
			System.out.println("What's your name?");
			String name = input.readLine();
			System.out.println(name);
			System.out.println("How old are you?What's your favorite 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.format("Hi %s.\n",name);
			System.out.format("In 5 years you will be %d. \n",age+5);
			System.out.format("My Favorite double is %f.",favorite/2);
			}catch(IOException e){
				System.err.println("I/O Exception");
			}
	}
}

运行结果:
What is your name?
Sir Robin of Camelot
How old are you? What is your favorite double?
(input: <age> <double>)
22 1.61803
Hi Sir Robin of Camelot.
In 5 years you will be 27.
My favorite double is 0.809015.

以上代码中,使用了IO中的readLine()方法读取输入流中的一行,然后进行解析。终于在JavaSE5新增了Scanner类,它可以大大的减轻扫描输入的工作。

import java.util.Scanner;
public class BetterRead {
	public static void main(String[] args) {
		Scanner stdin = new Scanner(SimpleRead.input);
		System.out.println("What's your name?");
		String name = stdin.nextLine();
		System.out.println(name);
		System.out.println("How old are you?What's 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.format("Hi %s.\n",name);
		System.out.format("In 5 years you will be %d. \n",age+5);
		System.out.format("My Favorite double is %f.",favorite/2);
	}
}

运行结果:
What is your name?
Sir Robin of Camelot
How old are you? What is your favorite double?
(input: <age> <double>)
22 1.61803
Hi Sir Robin of Camelot.
In 5 years you will be 27.
My favorite double is 0.809015.

Scanner的构造器可以接受任意类型的输入对象,其普通的next方法将返回一个String,而所有的基本类型都有一个next方法,该方法的作用是,读取到下一个完整的指定类型的分词之后才返回。同时Scanner还有对应的hasNext方法,用来判断是否有所输入分词的类型。

Scanner默认的定界符为空白符,我们也可以自己使用正则表达式指定分隔符

Scanner除了可以扫描基本类型外,还可以扫描正则表达式,这在扫描复杂数据类型的时候非常有用:


import java.util.Scanner;
import java.util.regex.MatchResult;
 
public class ThreadAnalyzer {
	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/11/2005\n" +
								"58.27.82.161@02/11/2005\n" +
								"[Next log section width different data format]";
	public static void main(String[] args) {
		Scanner scanner = new Scanner(threatData);
		String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" + "(\\d{2}/\\d{2}/\\d{4})";
		while(scanner.hasNext(pattern)){
			scanner.next(pattern);
			MatchResult match = scanner.match();
			String ip = match.group(1);
			String date = match.group(2);
			System.out.format("Thread on %s from %s\n",date,ip);
			
		}
	}
	
}

运行结果:
Threat on 02/10/2005 from 58.27.82.161
Threat on 02/11/2005 from 204.45.234.40
Threat on 02/11/2005 from 58.27.82.161
Threat on 02/12/2005 from 58.27.82.161
Threat on 02/12/2005 from 58.27.82.161

8. StringTokenizer

在正则表达式和Scanner引入之前,我们使用的是StringTokenizer来进行字符串匹配,下面演示这种方式并与其它方式进行比较:

package com.chenxyt.java.practice;
 
import java.util.Arrays;
import java.util.Scanner;
import java.util.StringTokenizer;
 
public class ReplacingStringTokenizer {
	public static void main(String[] args) {
		String input = "But I'm not dead yet!I feel happy!";
		StringTokenizer stoke = new StringTokenizer(input);
		while(stoke.hasMoreElements()){
			System.out.print(stoke.nextToken() + " ");
		}
		System.out.println();
			System.out.println(Arrays.toString(input.split(" ")));
			Scanner scanner = new Scanner(input);
			while(scanner.hasNext()){
				System.out.print(scanner.next() + " ");
			}
	
	}
}

运行结果:
But I'm not dead yet! I feel happy! 
[But, I'm, not, dead, yet!, I, feel, happy!]
But I'm not dead yet! I feel happy! 

使用正则表达式或者Scanner我们可以使用更加复杂的模式匹配字符串,而使用StringTokenizer则很困难了,因此实际上StringTokenizer这个类基本上已经被废弃了。

9. 总结

String类型作为程序设计中最为常见的一种操作类型,它是一种不可变的操作类型。它内部重载了“+”操作符,使得可以使用"+"操作符完成字符串的拼接工作,拼接的原理是编译器为我们自动生成了StringBuilder类来完成。因此对于复杂的字符串拼接操作,我们自己使用StringBuilder效率会更高一些。String还有很多常用的方法,正则表达式为我们提供了字符串匹配的多种形式。

猜你喜欢

转载自blog.csdn.net/qq_21125183/article/details/85019010