《Java黑皮书基础篇第10版》 第12章【习题】

这篇具有很好参考价值的文章主要介绍了《Java黑皮书基础篇第10版》 第12章【习题】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java语言程序设计 习题第十二章

12.2章节习题

12.1 使用异常处理的优势是什么?

如果没有异常处理,方法执行(called method)出错时,调用者(caller)没有办法进行处理(比如方法所在的类被封装,调用者无法访问),而方法通常也不具备处理异常的能力,导致程序执行被终止

通过异常处理,方法执行出错时,异常会被方法抛出,并由调用者捕获,调用者可以自行调试,改正异常使程序继续执行

12.2 下面哪些语句会抛出一个异常?

System.out.println(1 / 0); //throw an exception
System.out.println(1.0 / 0); //Output: Infinity

12.3 指出下面代码中的问题。代码会抛出任何异常吗?

long value = Long.MAX_VALUE + 1;
System.out.println(value);

不会,计算机使用二进制储存数字,其中首位用1表示整数,0表示负数。最大值+1会使首位的1变成0,从而输出一个很大的负数

System.out.println(Long.toUnsignedString(value));

可以使用以上方法输出无符号整数值

12.4 当产生一个异常的时候,JVM 会做什么?如何捕获一个异常?

当遇到异常的时候,JVM会寻找catch语句来捕获异常。应该使用try-catch语句(statement)来捕获异常

12.5 下面代码的输出是什么?

public class Test {
 public static void main(String[] args) {
	try {
 	int value = 30;
 	if (value < 40)
 		throw new Exception("value is too small");
  }
	catch (Exception ex) {
		System.out.println(ex.getMessage()) ; 
  }
  System.out.println("Continue after the catch block");
	}
}
value is too small
Continue after the catch block

如果把语句

int value = 30;

换成

int value = 50;

会输出什么结果?

Continue after the catch block

12.6 给出下面代码的输出。

public class Test {
	public static void main(String[] args) {
			for (int i = 0; i < 2; i++) { 
				System.out.print(i + " "); 
				try {
					System.out.println(1 / 0); 
					}
				catch (Exception ex) {
				} 
			}
		} 
	}
0 1
public class Test {
	public static void main(String[] args) {
		try {
			for (int i = 0; i < 2; i++) {
				System.out.print(i + " ");
				System.out.println(1 / 0); 
				}
			}
		catch (Exception ex) { 
		}
	} 
}
0

try遇到异常时,try块内剩余的代码就不再执行

12.3章节习题

12.7 描述Java的Throwable类,它的子类以及异常的类型。

Throwable类是所有异常类直接或间接的父类,他有两个子类,Error和Exception。

Error是指错误,通常是系统内部的几乎不可修复的错误。

Exception是由程序或外部环境引起的错误,程序猿可以捕获抛出的异常并处理

在Exception中,有一个runtime exception,是指程序的设计错误,通常是程序的编写逻辑错误

12.8 下面的程序如果将抛出RuntimeException,那么会抛出哪种?

public class Test {
	public static void main(String[] args) {
    //ArithmeticException
		System.out.println(1 / 0); 
	}
}
public class Test {
	public static void main(String[] args) {
    //IndexOutOfBoundException
		int[] list = new int[5];
		System.out.println(list[5]);
	}
}
public class Test {
	public static void main(String[] args) {
    //IndexOutOfBoundException
		String s = "abc";
		System.out.println(s.charAt(3));
	}
}
public class Test {
	public static void main(String[] args) {
    //ClassCastException
		Object o = new Object();
		String d = (String)o;
	}
}
public class Test {
	public static void main(String[] args) {
    //NullPointerException
		Object o = null;
		System.out.println(o.toString());
	}
}
public class Test {
	public static void main(String[] args) {
    //No Exception
		System.out.println(1.0 / 0); 
  }
}
12.4章节习题

12.9 声明异常的目的是什么? 怎样声明一个异常,在哪里声明? 在一个方法头中可以声明多个异常吗?

如果一个必检异常在方法头使用throws被声明,那么调用者必须显示的捕获它。

可以用逗号隔开,在方法头声明多个异常

12.10 什么是必检异常?什么是免检异常?

必检异常是除了Error和runtimeException外的异常

12.11 如何抛出一个异常? 可以在一个throw语句中抛出多个异常吗?

使用throw在方法体中抛出一个异常,1个throw语句只能抛出1个异常

12.12 关键字throw的作用是什么?关键字throws的作用是什么?

抛出异常和声明异常

12.13 假设下面的trycatch块中的statement2引起一个异常:

try { 
  statement1; 
  statement2; 
  statement3;
}
catch (Exception1 ex1) { 
}
catch (Exception2 ex2) { 
}
statement4;

回答下列问题:
• 会执行statement3吗?

不会

• 如果异常未被捕获,会执行statement4吗?

不会,程序直接报错

• 如果在catch块中捕获了异常,会执行statement4吗?

12.14 运行下面程序时会显示什么?

public class Test {
	public static void main(String[] args) {
		try {
			int[] list = new int[10]; System.out.println("list[10] is " + list[10]);
		}
		catch (ArithmeticException ex) {
			System.out.println("ArithmeticException"); 
		}
		catch (RuntimeException ex) { 
			System.out.println("RuntimeException");
		}
		catch (Exception ex) {
			System.out.println("Exception"); }
	} 
}
RuntimeException

12.15 运行下面程序时会显示什么?

public class Test {
	public static void main(String[] args) {
		try {
			method();
			System.out.println("After the method call");
		}
		catch (ArithmeticException ex) {
			System.out.println("ArithmeticException");
		}
		catch (RuntimeException ex) { 
			System.out.println("RuntimeException");
		}
		catch (Exception e) {
			System.out.println("Exception"); 
		}
	}

	static void method() throws Exception {
		System.out.println(1 / 0); 
	}
}
ArithmeticException

12.16 运行下面程序时会显示什么?

method方法中的异常已经在method方法内部被捕获了,所以main方法就可以正常执行了

public class Test {
	public static void main(String[] args) {
		try {
			method();
			System.out.println("After the method call");
		}
		catch (RuntimeException ex) {
			System.out.println("RuntimeException in main"); 
		}
		catch (Exception ex) { 
			System.out.println("Exception in main");
		} 
	}

	static void method() throws Exception { 
		try {
			String s = "abc";
			System.out.println(s.charAt(3)); 
		}
		catch (RuntimeException ex) { 
			System.out.println("RuntimeException in method()");
		}
		catch (Exception ex) {
			System.out.println("Exception in method()"); 
		}
	} 
}
RuntimeException in method()
After the method call

12.17 方法getMessage()可以做什么?

可以得到抛出异常时的异常消息

12.18 方法printStackTrace()可以做什么?

打印出方法调用的栈

12.19 没有异常发生时,try-catch块的存在会引起额外的系统开销吗?

不会

12.20 修改下面代码中的编译错误:

public void m(int value) { 
  if (value < 40)
		throw new Exception("value is too small"); 
}

方法拋出的必检异常必须在方法头中使用throws显式声明

public void m(int value) throws Exception {
  if (value < 40)
    throw new Exception("value is too small");    
}
12.5章节习题

本节章节习题有歧义,因此找来了原版英文书,发现题目是有差别的,在这里放上原版英文书的题目,原版英文书一共有两道题

12.21.1

Suppose you run the following code:

public static void main(String[] args) throws Exception2 {
  m(); 
  statement7;
}
     
public static void m() {
  try {
    statement1;
    statement2;
    statement3;
  }
  catch (Exception1 ex1) {
    statement4;
  }
  finally {
    statement5;
  }
  statement6;
}

answer the questions:

  1. If no exception occurs, which statements are executed?
  2. If statement2 throws an exception of type Exception1, which statements are executed?
  3. If statement2 throws an exception of type Exception2, which statements are executed?
  4. If statement2 throws an exception that is neither Exception1 nor Exception2, which statements are executed?
1. statement1, statement2, statement3, statement5, statement6, statement7.
2. statement1, statement2, statement4, statement5, statement6, statement7.
3. statement1, statement2, statement5.
4. statement1, statement2, statement5.

12.21.2

Suppose you run the following code:

public static void main(String[] args) {
  try {
    m(); 
    statement7;
  }
  catch (Exception2 ex {
    statement8;
  }
}
     
public static void m() {
  try {
    statement1;
    statement2;
    statement3;
  }
  catch (Exception1 ex1) {
    statement4;
  }
  finally {
    statement5;
  }
  statement6;
}

answer the questions:

  1. If no exception occurs, which statements are executed?
  2. If statement2 throws an exception of type Exception1, which statements are executed?
  3. If statement2 throws an exception of type Exception2, which statements are executed?
  4. If statement2 throws an exception that is neither Exception1 nor Exception 2, which statements are executed?
1. statement1, statement2, statement3, statement5, statement6, statement7.
2. statement1, statement2, statement4, statement5, statement6, statement7.
3. statement1, statement2, statement5, statement8.
4. statement1, statement2, statement5.
12.6章节习题

12.22 下面的方法检査一个字符串是否是数值宇符串:

public static boolean isNumeric(String token) {
  try {
    Double.parseDouble(token);
    return true;
  }
  catch (java.lang.NumberFormatException ex) {
    return false;
  }
}

代码是正确的,但是最好使用if来判断

12.7章节习题

12.23 假设下面的语句中,statement2会引起一个异常:

try {
  statement1;
  statement2;
  statement3;
}
catch (Exception1 ex1) {
}
catch (Exception2 ex2) {
  throw ex2;
}
finally {
  statement4;
}
statement5;

回答以下问题:

• 如果没有异常发生,会执行语句 statement4 吗? 会执行语句 statement5 吗?

都会

• 如果异常类型是 Exception1,那么会执行 statement4 吗? 会执行 statement5 吗?

都会

• 如果异常类型是 Exception2,那么会执行 statement4 吗? 会执行 statement5 吗?

会执行4,不会执行5

• 如果异常类型不是 Exception1以及 Exception2 类型的,那么会执行 statement4 吗? 会执行 statement5 吗?

会执行4,不会执行5

12.8章节习题

12.24 如果第 16 行被下面一行所替代,将输出什么?

throw new Exception(New info from method1");
public class Test {
	public static void main(String[]args) { 
		try {
			method1();
		}
		catch (Exception ex) { 
			ex.printStackTrace();
		}
	}

	public static void method1() throws Exception {
		try {
			method2();
		}
		catch (Exception ex) {
      //第16行,添加了原始异常的实例ex,打印异常栈时,异常不会丢失原始信息
			throw new Exception("New info from methodl", ex);
		}
	}
	
	public static void method2() throws Exception {
		throw new Exception("New info from method2");
	}
}
12.9章节习题

12.25 如何定义一个自定义异常类?

继承一个Exception类或者他的子类

12.26 假定setRadius方法抛出程序清单12-10中定义的InvalidRadiusException异常,那么运行下面的程序时会显示什么?

public class Test {
	public static void main(String[] args) {
		try {
			method();
			System.out.println("After the method call");
		}
		catch (RuntimeException ex) {
			System.out.println("RuntimeException in main"); 
		}
		catch (Exception ex) { 
			System.out.println("Exception in main");
		} 
	}

	static void method() throws Exception { 
		try {
			Circle c1 = new Circle(1); 
			c1.setRadius(-1); 
			System.out.println(c1.getRadius());
		}
		catch (RuntimeException ex) {
			System.out.println("RuntimeException in method()"); 
		}
		catch (Exception ex) { 
			System.out.println("Exception in method()"); throw ex;
		} 
	}
}
Exception in method()
Exception in main
12.10章节习题

12.27 使用下面的语句创建File对象时,错在哪里?

new File("c:\book\test.dat");

在Java中单个反斜杠是一个特殊的转义字符,应该使用双反斜杠来表示文件路径

12.28 如何检査一个文件是否已经存在? 如何删除一个文件? 如何重命名一个文件? 使用File类能够获得文件的大小(字节数)吗? 如何创建一个目录 ?

使用exists()方法

使用delete()方法

使用renameTo(File)方法

使用length()方法

使用mkdir()方法

12.29 能够使用File类进行输人/输出吗? 创建一个File对象就是在磁盘上创建一个文件吗?

不可以,不是

12.11章节习题

12.30 如何创建一个 PrintWriter 以向文件写数据? 在程序清单12-13 中,为什么要在 main 方法中声明 throws Exception? 在程序清单12-13中,如果不调用 close() 方法,将会发生什么?

PrintWriter output = new PrintWriter(filename);

因为I/O异常是必检异常,必须捕获或声明

数据就不能正确的保存

12.31 给出下面的程序执行之后,文件 temp.txt 的内容。

public class Test {
	public static void main(String[] args) throws Exception {
		java.io.PrintWriter output = new java.io.PrintWriter("temp.txt");
		output.printf("amount is %f %e\r\n", 32.32, 32.32); 			
		output.printf("amount is %5.4f %5.4e\r\n", 32.32, 32.32); 
		output.printf("%6b\r\n", (1 > 2)); 		
		output.printf("%6s\r\n", "Java");
		output.close(); 
	}
}
amount is 32.320000 3.232000e+01
amount is 32.3200 3.2320e+01
 false
  Java

12.32 使用 try-with-resource 语法重写前一题中的代码。

public class Test {
	public static void main(String[] args) throws Exception {
		try (
			java.io.PrintWriter output = new java.io.PrintWriter("temp.txt");
		) 	{
			output.printf("amount is %f %e\r\n", 32.32, 32.32); 			
			output.printf("amount is %5.4f %5.4e\r\n", 32.32, 32.32); 
			output.printf("%6b\r\n", (1 > 2)); 		
			output.printf("%6s\r\n", "Java");
		}
	}
}

12.33 如何创建一个Scanner从文件读数据? 在程序清单12-15中,为什么要在main方法中声明throws Exception? 在程序清单 12-15 中,如果不调用 close() 方法,将会发生什么?

Scanner input = new Scanner(fileName);

因为I/O异常是必检异常,必须捕获或声明

不会对程序运行有影响,但是释放文件占用的资源是一个好习惯

12.34 如果试图对一个不存在的文件创建Scanner, 将会发生什么情况? 如果试图对一个已经存在的文件创建PrintWriter, 会发生什么情况?

Scanner会报FileNotFoundException

文件会被新内容覆盖,原内容丢失

12.35 是否所有平台上的行分隔符都是一样的? Windows 平台上的行分隔符是什么?

Windows是\r\n,UNIX是\n

12.36 假设输入45 57.8 789, 然后点击回车键。显示执行完下面的代码之后变量的内容。

Scanner input = new Scanner(System.in); 
//45
int intValue = input.nextInt();
//57.8
double doubleValue = input.nextDouble(); 
//789
String line = input.nextLine();

12.37 假设输人45、回车键、57.8、回车键、789、回车键。显示执行完下面的代码之后变量的内容。

Scanner input = new Scanner(System.in); 
//45
int intValue = input.nextInt();
//57.8
double doubleValue = input.nextDouble(); 
//empty
String line = input.nextLine();

对于nextInt()和nextDouble()方法,首先,他们会从当前的输入缓冲区开始,忽略所有的空格和换行符,直到遇到一个非空格非换行符的字符。

其次,开始记录接下来所有的字符,直到再次遇到空格或换行符时停止,并返回所有收集到的字符(只返回字符,不返回空格或换行符)

对于nextLine()方法,首先,他们会从当前的输入缓冲区开始,记录并返回所有的内容(包括空格和换行符),直到遇到换行符时停止,也就是读取当前行的全部剩余部分

注意,读取完成后,这个方法会将读取指针转入新的下一行

12.12章节习题

12.38 如何为从一个 URL 读取的文本创建一个 Scanner 对象?

Scanner input = new Scanner(url.openStream());
12.13章节习题

12.39 在一个 URL 添加到listOfPendingURLs之 前,第25行检査它是否被遍历过了。listOfPendingURLs是否可能包含重复的URLs呢?如果是,给出一个例子。

是的,如果说在查找URL A时,发现了在A中的两个URL B。这两个B都没有进入到TraversedList中,但是却都会进入PendingList中

编程练习题

*12.1 (NumberFormatException异常)

程序清单7-9是一个简单的命令行计算器。注意,如果某个操作数是非数值的,程序就会中止。编写一个程序,利用异常处理器来处理非数值操作数;然后编写另一个不使用异常处理器的程序,达到相同的目的。程序在退出之前应该显示一条消息,通知用户发生了操作数类型错误

//这个异常类在java.util包中需要导入,如果是java.lang包中的异常类就不需要导入了
import java.util.InputMismatchException;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        try {
        System.out.println("请输入第一个数:");
        double num1 = scanner.nextDouble();
        System.out.println("请输入运算符(+、-、*、/):");
        String operator = scanner.next();
        System.out.println("请输入第二个数:");
        double num2 = scanner.nextDouble();
        calculator(num1, operator, num2);
        }
        catch (ArithmeticException ex){
        	System.out.println(ex);
        }
        catch (InputMismatchException ex) {
        	System.out.println("Error: Input Mismatch");
        }
    }
    
    public static void calculator(double num1, String operator, double num2) throws ArithmeticException{
    double result = 0;
    switch (operator) {
        case "+":
            result = num1 + num2;
            break;
        case "-":
            result = num1 - num2;
            break;
        case "*":
            result = num1 * num2;
            break;
        case "/":
            if (num2 == 0) {
                throw new ArithmeticException("The second number cannot be 0");
            }
            result = num1 / num2;
            break;
        default:
            System.out.println("输入的运算符有误");
            return;
    }
    System.out.println("结果为:" + result);
    }
}

输出结果:

请输入第一个数:
3
请输入运算符(+、-、*、/):
/
请输入第二个数:
0
java.lang.ArithmeticException: The second number cannot be 0
*12.2 (InputMismatchException异常)

编写一个程序,提示用户读取两个整数,然后显示它们的和。程序应该 在输人不正确时提示用户再次读取字。

//这个异常类在java.util包中需要导入,如果是java.lang包中的异常类就不需要导入了
import java.util.InputMismatchException;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        boolean inputValid = false;
        //while循环条件为true时进入循环,!是逻辑运算符:非
        //inputValid被初始化为false,!inputValid则为true,因此进入while循环
        //也可以使用do-while循环,不论条件先运行一遍,就不需要!逻辑运算符了
        while (!inputValid) {
        	try {
        		System.out.println("请输入第一个数:");
        		double num1 = scanner.nextDouble();
        		System.out.println("请输入第二个数:");
        		double num2 = scanner.nextDouble();
        		calculator(num1, num2);
        		//如果程序顺利计算了两数之和,就把inputValid改为true,!inputValid则为false,就会退出循环
        		//如果程序被异常捕获,就不会执行下面的代码,重新进入循环
        		inputValid = true;
        	}
        	catch (InputMismatchException ex) {
        		System.out.println("Error: Input Mismatch");
        		//下面这一行代码很关键。如果没有这行代码,程序会陷入死循环
        		//我们在输入完一个非法字符之后会按回车键,会被异常捕获,此时输入缓冲区留下一个回车字符,但nextDouble()方法只能读取数字,不读取回车字符,当前的输入缓冲区依然有一个回车符
        		//再次进入循环时,程序会认为这个遗留的回车符是一个用户输入的非法字符,就直接被异常捕获,由于nextDouble()方法只会读取数字,回车字符不会被清空,所以陷入死循环
        		scanner.nextLine();
        	}
        }
        scanner.close();
    }
    
    public static void calculator(double num1, double num2) {
    	System.out.println(num1 + num2);
    }
}

输出结果:

请输入第一个数:
a
Error: Input Mismatch
请输入第一个数:
1
请输入第二个数:
1
2.0
*12.3 (ArraylndexOutBoundsException异常)

编写一个满足下面要求的程序:

• 创建一个由 100 个随机选取的整数构成的数组。

• 提示用户输人数组的下标,然后显示对应的元素值。如果指定的下标越界,就显示消息Out of Bounds.

import java.util.Scanner;

public class Test {
	public static void main(String[] args) {
		int[] test = new int[100];
		for (int i = 0; i < test.length; i++ ) {
			test[i] = (int)(Math.random() * 100);
		}
		Scanner scanner = new Scanner(System.in);
		System.out.print("Input an index to check: ");
		int number = scanner.nextInt();
		try {
			System.out.print(test[number]);
		}
		catch (ArrayIndexOutOfBoundsException ex){
			System.out.print("Out of Bounds");
		}
 	}
}

输出结果:

Input an index to check: 100
Out of Bounds
*12.4 (IllegalArgumentException异常)

修改程序清单10-2中的Loan类,如果贷款总额、利率、年数小于或等于零,则抛出IllegalArgumentException异常

Test.java

public class Test {
	public static void main(String[] args) {
		try {
			Loan loan1 = new Loan(0, 4, 1000);
		}
		catch (IllegalArgumentException ex){
			ex.printStackTrace();
		}
		
		try {
			Loan loan2 = new Loan(4, 0, 1000);
		}
		catch (IllegalArgumentException ex){
			ex.printStackTrace();
		}
		
		try {
			Loan loan3 = new Loan(4, 4, 0);
		}
		catch (IllegalArgumentException ex){
			ex.printStackTrace();
		}
		
		Loan loan4 = new Loan(4, 1, 1000);
		
		try {
			loan4.setAnnualInterestRate(0);
		}
		catch (IllegalArgumentException ex) {
			ex.printStackTrace();
		}
		
		try {
			loan4.setLoanAmount(0);
		}
		catch (IllegalArgumentException ex) {
			ex.printStackTrace();
		}
		
		try {
			loan4.setNumberofYears(0);
		}
		catch (IllegalArgumentException ex) {
			ex.printStackTrace();
		}
	}
}

Loan.java

public class Loan {
	private double annualInterestRate; 
	private int numberofYears; 
	private double loanAmount;
	private java.util.Date loanDate;

	//Default constructor	
	public Loan() {
		this(2.5, 1, 1000);
	}
		
	public Loan(double annualInterestRate, int numberofYears, double loanAmount) {
		if (annualInterestRate <=0 || numberofYears <= 0 || loanAmount <= 0) {
			throw new IllegalArgumentException("Index <= 0");
		}
		this.annualInterestRate = annualInterestRate; 
		this.numberofYears = numberofYears; 
		this.loanAmount = loanAmount; 
		loanDate = new java.util.Date();
	}

	//Return annualInterestRate
	public double getAnnualInterestRate() { 
		return annualInterestRate;
	}

	//Set a new annualInterestRate *
	public void setAnnualInterestRate(double annualInterestRate) { 
		if (annualInterestRate <=0) {
			throw new IllegalArgumentException("Index <= 0");
		}
		this.annualInterestRate = annualInterestRate;
	}

	//Return numberofYears
	public int getNumberofYears() { 
		return numberofYears;
	}

	//Set a new numberofYears
	public void setNumberofYears(int numberofYears) { 
		if (numberofYears <= 0) {
			throw new IllegalArgumentException("Index <= 0");
		}
		this.numberofYears = numberofYears;
	}

	//Return loanAmount
	public double getLoanAmount() { 
		return loanAmount;
	}

	//Set a new loanAmount

	public void setLoanAmount(double loanAmount) { 
		if (loanAmount <= 0) {
			throw new IllegalArgumentException("Index <= 0");
		}
		this.loanAmount = loanAmount;
	}

	//Find monthly payment
	public double getMonthlyPayment(){
		double monthlyInterestRate = annualInterestRate / 1200;
		double monthlyPayment = loanAmount * monthlyInterestRate / (1 - (1 / Math.pow(1 + monthlyInterestRate, numberofYears * 12))); 
		return monthlyPayment;
	}

	//Find total payment
	public double getTotalPayment(){
		double totalPayment = getMonthlyPayment() * numberofYears * 12; 
		return totalPayment;
	}

	//Return loan date
	public java.util.Date getLoanDate(){ 
		return loanDate;
	}
}

输出结果:

java.lang.IllegalArgumentException: Index <= 0
	at Loan.<init>(Loan.java:14)
	at Test.main(Test.java:4)
java.lang.IllegalArgumentException: Index <= 0
	at Loan.<init>(Loan.java:14)
	at Test.main(Test.java:11)
java.lang.IllegalArgumentException: Index <= 0
	at Loan.<init>(Loan.java:14)
	at Test.main(Test.java:18)
java.lang.IllegalArgumentException: Index <= 0
	at Loan.setAnnualInterestRate(Loan.java:30)
	at Test.main(Test.java:27)
java.lang.IllegalArgumentException: Index <= 0
	at Loan.setLoanAmount(Loan.java:57)
	at Test.main(Test.java:34)
java.lang.IllegalArgumentException: Index <= 0
	at Loan.setNumberofYears(Loan.java:43)
	at Test.main(Test.java:41)
*12.5 (IllegalTriangleException异常)

编程练习题 11.1定义了带三条边的Triangle类,在三角形中,任意两边之和总大于第三边,三角形类 Triangle 必须遵从这一规则。创建一个 IllegalTriangleException 类,然后修改 Triangle 类的构造方法,如果创建的三角形的边违反了这一规则,抛出一个 IllegalTriangleException 对象

Test.java

public class Test {
	public static void main(String[] args) {
		try {
			new Triangle(1, 2, 3);
		}
		catch (IllegalTriangleException ex) {
			System.out.print(ex);
		}
	}
}

Triangle.java

public class Triangle {
	private double side1;
	private double side2;
	private double side3;
	
	public Triangle(double side1, double side2, double side3) throws IllegalTriangleException {
		if (side1 + side2 <= side3 || side1 + side3 <= side2 || side2 + side3 <= side1) 
			throw new IllegalTriangleException();
		this.side1 = side1;
		this.side2 = side2;
		this.side3 = side3;
	}
}

IllegalTriangleException.java

public class IllegalTriangleException extends Exception {
	
	public IllegalTriangleException() {
		super("Invalid Triangle");
	}
}

输出结果:

IllegalTriangleException: Invalid Triangle
*12.6(NumberFormatException异常)

程序清单6-8实现了hexToDec(String hexString)方法,它将一个十六进制字符串转换为一个十进制数。实现这个hexToDec方法,在字符串不是一个十六进制字符串时抛出NumberFormatException异常

public class Test { 
	public static void main(String[] args) { 
		try {
			String hex = "xyz";
			System.out.println("The decimal value for hex number hex is " + hexToDecimal(hex.toUpperCase()));
		}
		catch (NumberFormatException ex) {
			System.out.println(ex);
		}
	}

	public static int hexToDecimal(String hex) { 
		//使用正则表达式判断字符串是不是16进制
		if (!hex.matches("[0-9A-Fa-f]+")) {
			throw new NumberFormatException("Not a hex number");
		}
		int decimalValue =0;
		for (int i =0; i < hex.length(); i++) { 
			char hexChar = hex.charAt(i);
			decimalValue = decimalValue * 16 + hexCharToDecimal(hexChar);
		}
		return decimalValue;
	}

	public static int hexCharToDecimal (char ch) { 
		if (ch >= 'A' && ch <='F') 
			return 10 + ch - 'A';
		else 
			return ch -'0';
	}
}

输出结果:

java.lang.NumberFormatException: Not a hex number
*12.7 (NumberFormatException异常)

编写bin2Dec(String binaryString)方法,将一个二进制字符串转换为一个十进制数。实现bin2Dec方法,在字符串不是一个二进制字符串时抛出NumberFormatException异常

public class Test { 
	public static void main(String[] args) { 
		try {
			String bin = "0210";
			System.out.println("The decimal value for binary number bin is " + binToDecimal(bin));
		}
		catch (NumberFormatException ex) {
			System.out.println(ex);
		}
	}

	public static int binToDecimal(String bin) { 
		//使用正则表达式判断字符串是不是2进制
		if (!bin.matches("^[01]+$")) {
			throw new NumberFormatException("Not a binary number");
		}
		//使用parseInt方法将二进制转化为十进制
		return Integer.parseInt(bin, 2);
	}
}

输出结果:

java.lang.NumberFormatException: Not a binary number
*12.8 (HexFormatException异常)

编程练习题12.6实现hex2Dec方法,在字符串不是一个十六进制字符串时抛出NumberFormatException异常. 定义一个名为HexFormatException的自定义异常. 实现hex2Dec方法, 在字符串不是一个十六进制字符串时抛出HexFormatException 异常.

Test.java

public class Test { 
	public static void main(String[] args) { 
		try {
			String hex = "xyz";
			System.out.println("The decimal value for hex number hex is " + hexToDecimal(hex.toUpperCase()));
		}
		catch (HexFormatException ex) {
			System.out.println(ex);
		}
	}

	public static int hexToDecimal(String hex) throws HexFormatException { 
		//使用正则表达式判断字符串是不是16进制
		if (!hex.matches("[0-9A-Fa-f]+")) {
			throw new HexFormatException();
		}
		int decimalValue =0;
		for (int i =0; i < hex.length(); i++) { 
			char hexChar = hex.charAt(i);
			decimalValue = decimalValue * 16 + hexCharToDecimal(hexChar);
		}
		return decimalValue;
	}

	public static int hexCharToDecimal (char ch) { 
		if (ch >= 'A' && ch <='F') 
			return 10 + ch - 'A';
		else 
			return ch -'0';
	}
}

HexFormatException.java

public class HexFormatException extends Exception {
	
	public HexFormatException() {
		super("Not a hex number");
	}
}

输出结果:

HexFormatException: Not a hex number
*12.9 (BinaryFormatException异常)

编程练习题12.7实现bin2Dec方法, 在字符串不是一个二进制字符串时抛出BinaryFormatException异常. 定义一个名为BianryFormatException的自定义异常. 实现bin2Dec方法, 在字符串不是一个二进制字符串时抛出BinaryFormatException 异常.

Test.java

public class Test { 
	public static void main(String[] args) { 
		try {
			String bin = "0210";
			System.out.println("The decimal value for binary number bin is " + binToDecimal(bin));
		}
		catch (BinaryFormatException ex) {
			System.out.println(ex);
		}
	}

	public static int binToDecimal(String bin) throws BinaryFormatException{ 
		//使用正则表达式判断字符串是不是2进制
		if (!bin.matches("^[01]+$")) {
			throw new BinaryFormatException();
		}
		//使用parseInt方法将二进制转化为十进制
		return Integer.parseInt(bin, 2);
	}
}

BianryFormatException.java

public class BinaryFormatException extends Exception {
	
	public BinaryFormatException() {
		super("Not a binary number");
	}
}

输出结果:

BinaryFormatException: Not a binary number
*12.10 (OutOfMemoryError错误)

编写一个程序,它能导致 JVM 抛出一个 OutOfMemoryError, 然后捕获和处理这个错误。

public class Test {
    public static void main(String[] args) throws Exception {
        try {
            long[] testHeap = new long[50];
            for (int loop = 0; ; loop++) {
            	//Indefinitely increase array size inside of loop
                testHeap = new long[testHeap.length * 4]; 
            }
        } catch (OutOfMemoryError e) {
            System.out.println("Out of memory error: " + e);
        }
    }
}

输出结果:

Out of memory error: java.lang.OutOfMemoryError: Java heap space
**12.11 (删除文本)

编写一个程序,从一个文本文件中删掉所有指定的某个字符串。例如,调用

java Exercisel2_11 John filename

从指定文件中删掉字符串John。程序从命令行获得参数。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
      //使用java Test.java file.txt从命令行读取文件,起始命令行的目录是Users/kevinwang,所以也需要将Test.java和file.txt文件复制到这个起始目录才可以运行程序
    	File sourceFile = new File(args[0]);
    	 if (!sourceFile.exists()) {
             System.out.println("The file " + sourceFile.getName() + "does not exist.");
             System.exit(1);
         }
    	 
        String targetString = "xx";
        String replacementString = "";

        try {
            // 读取源文件内容,大意为使用可变字符串StringBuilder逐行提取信息并替换
            StringBuilder stringBuilder = new StringBuilder();
            BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line).append(System.lineSeparator());
            }
            reader.close();

            // 执行字符串替换
          	//这里需要注意的是,如果写成
          	//String content = stringBuilder.toString();
            //content = content.replace(targetString, replacementString);
            //是可以的
          	//但是如果写成
            //String content = stringBuilder.toString();
            //content.replace(targetString, replacementString);
            //就不可以了
          	//原因是字符串一旦创建,内容就不可被修改。但是,字符串不可变仅仅指的是,字符串的内容不可变,字符串的引用是可以改变的,如果重新引用给content,就是改变了字符串的引用,那么代码依旧是生效的。
            String content = stringBuilder.toString();
            String modifiedContent = content.replace(targetString, replacementString);

            // 将替换后的内容写回源文件
            BufferedWriter writer = new BufferedWriter(new FileWriter(sourceFile));
            writer.write(modifiedContent);
            writer.close();

            System.out.println("字符串替换完成。");

        } catch (IOException e) {
            System.out.println("发生IO异常:" + e.getMessage());
        }
    }
}

输出结果:

《Java黑皮书基础篇第10版》 第12章【习题】

源文件:

javaxx javaxx
javaxx 

替换后:

java java
java
**12.12 (重新格式化 Java 源代码)

编写一个程序,将 Java 源代码的次行块风格转换成行尾块风格。例如,图 a 中的 Java 源代码使用的是次行块风格。程序将它转换成图 b 中所示的行尾块形式。

《Java黑皮书基础篇第10版》 第12章【习题】

import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;


public class Test {
    public static void main(String[] args) {
    	//创建一个ArrayList数组
        ArrayList<String> lines = new ArrayList<>();
        //从命令行读取文件,起始命令行的目录是Users/kevinwang,所以也需要将文件复制到这个起始目录才可以运行程序
        File file = new File(args[0]);

        if (file.exists()) {
        	//IOException必须捕获
            try {
            	//利用Scanner对象,逐行读取文件内容并赋值到ArrayList数组中
                Scanner scanner = new Scanner(file);
                while (scanner.hasNextLine()) {
                    String line = scanner.nextLine();
                    lines.add(line);

                }
                //进行'{'的位置移动
                for (int i = 1; i < lines.size(); i++) {
                    if (lines.get(i).contains("{")) {
                        String orgLine = lines.get(i);
                        orgLine = orgLine.replace('{', ' ');
                        String targetLine = lines.get(i - 1); 
                        targetLine = targetLine + " {"; 
                        lines.set(i, orgLine);
                        lines.set(i - 1, targetLine);
                    }
                }
                
                //重新写入文件内容
                PrintWriter writer = new PrintWriter(file);

                for (String line : lines) {
                    writer.println(line);
                }
                writer.close();


            } catch (FileNotFoundException e) {
                System.out.println("File could not be found to exist: " + e);

            }
        }
    }
}

输出结果:

>>>:

public class Test 
{
	public static void main(String[] args) 
	{

	}
}

>>>变成:

public class Test  {
 
	public static void main(String[] args)  {
	 

	}
}
*12.13 (统计一个文件中的字符数、单词数和行数)

编写一个程序,统计一个文件中的字符数、单词数以及行数。单词由空格符分隔,文件名应该作为命令行参数被传递

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Test {
	public static void main(String[] args) {
		//从命令行输入,要注意的是,起始命令行的目录是Users/kevinwang,所以也需要将Test.java和file.java文件复制到这个起始目录才可以运行程序
		File file = new File(args[0]);
	        if (!file.exists()) {
	            System.out.println("Please check your file name and try again");
	            System.exit(1);
	        }
		
		try {
		StringBuilder stringBuilder = new StringBuilder();
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line;
		int numLine =0;
		//在输入的文件中,有字符的部分一共有10行,在第10行末尾按了回车,输入光标来到第11行开头,看上去文件一共有11行
		//但是,第10行按下回车后,没有输入任何字符,readLine()方法会将这个回车符当作本行的终止符,同时也是整个文件的终止符
		//文档在屏幕上显示有11行,只是为了提醒用户输入,但是计算机内部并没有这个11行,所以最终的行数是10
		//到达第10行末尾后,readLine()方法会返回null表示文件的结尾,退出while循环
		//但是,如果在11行打了一个空格,就算做一个字符,计算机就会识别到这个空格,从而让文件行数变成11行
		while ((line = reader.readLine()) != null) {
			//把文件逐行遍历并储存到StringBuilder
			stringBuilder.append(line).append(System.lineSeparator());
			//计算文件行数
            numLine++;
		}
		 
		System.out.println("File " + file + " has");
		//把StringBuilder转化成String,输出字符数量
		String a = stringBuilder.toString();
		System.out.println(a.length() + " characters");
		
		//使用正则表达式作为分隔符,分隔一个或多个连续的空白字符(包括空格,制表符,换行符)
		String[] words = a.split("\\s+");
		int numWords = words.length;
		System.out.println(numWords + " words");
				
		//输出文件行数
		System.out.println(numLine + " lines");
		
		} catch(IOException ex) {
			System.out.println("File not found.");
		}
	}
}

输出结果:

(base) kevinwang@KevindeMacBook-Pro ~ % java Test.java file.java
File file.java has
181 characters
20 words
10 lines
*12.14 (处理文本文件中的分数)

假定一个文本文件中包含未指定个数的分数,用空格分开。编写一个程序,提示用户输人文件,然后从文件中读入分数,并且显示它们的和以及平均值。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
    	//从命令行读入文件
    	File file = new File(args[0]);
    	if (!file.exists()) {
    		System.out.println("The file doesn't not exist.");
    		System.exit(1);
    	}
    	//大概思路是利用正则表达式,将文件中的数字写入ArrayList,然后算和和平均数
    	try {
    		ArrayList<Double> list = new ArrayList<>();
    		String line;
    		//通过BufferedReader,利用正则表达式,按行读取分数,并储存到String数组中
    		BufferedReader reader = new BufferedReader(new FileReader(file));
    		while((line = reader.readLine()) != null) {
    			String[] string = line.split("\\D+");
    			//通过参数a遍历String数组,用方法parseDouble()将String类型的数字转换成Double类型的数字,并逐一添加到ArrayList中
    			for (String a : string) {
    				if(!a.isEmpty()) {
    					double score = Double.parseDouble(a);
    					list.add(score);
    				}
    			}
    		}
    		//在ArrayList中进行操作就简单多了
    		double sum = 0;
    		int size = list.size();
    		for (int i = 0; i < size; i++) {
    			sum = sum + list.get(i);
    		}
    		double average = sum / size;
    		System.out.println("The sum of these scores are " + sum + ",\nand the average of these scores are " + average);
    	} catch(IOException ex) {
    		System.out.println("An IOException");
    	}
    }
}

小思考:

//这个方法更为简单,不需要BufferedReader,不需要String数组,只需要Scanner即可完成。
//用Scanner读取file直接进入String变量,然后转化成Double写入ArrayList
Scanner fileScanner = new Scanner(file));
ArrayList<Double> scores = new ArrayList<>();
while (fileScanner.hasNext()) {
	String value = fileScanner.next();
	scores.add(Double.parseDouble(value));
}

输出结果:

《Java黑皮书基础篇第10版》 第12章【习题】

//file.txt
40 40 40 40
30 30 30 30
20 20 20 20
10 10 10 10 
*12.15(写/读数据)

编写一个程序,如果名为Exercise12_15.txt的文件不存在,则创建该文件。使用文本I/O将随机产生的100个整数写入文件,文件中的整数由空格分开。从文件中读回数据并以升序显示数据。

import java.io.File;
import java.io.PrintWriter;
import java.util.Scanner;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
    	//这个代码由两段try-catch块组成。第一个块写入数据,为方便阅读,数据每写入10个就换一行;第二个块读取数据,并对数据进行排序后输出
    	File file = new File("Score.txt");
    	try(PrintWriter writer = new PrintWriter(file);) {
    		for(int i = 0; i < 100; i++) {
    			writer.print((int)(Math.random() * 1000) + " ");
    			if ((i + 1) % 10 == 0) {
                    writer.println(); // 换行
                }
    		}
    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
    	
    	try(Scanner scanner = new Scanner(file);) {
    		int[] score = new int[100];
        	for(int i = 0; i < 100; i++) {
        		if(scanner.hasNextInt()) {
            		int a = scanner.nextInt();
            		score[i] = a;
        		}
        	}
        	bubbleScore(score);
            for (int i = 0; i < score.length; i++) {
            	System.out.print(score[i] + " ");
                     if ((i + 1) % 10 == 0) {
                         System.out.println(); // 换行
                     }
            }
                 
    	} catch (IOException ex){
    		ex.printStackTrace();
    	}
    }
    
    public static int[] bubbleScore(int[] arr) {
    	//
    	for (int i = arr.length - 1; i > 0; i--) { //外圈决定循环次数:规律是(length-1)次
            for (int j = 0; j < i; j++) { // 内圈决定交换次数,规律一个length为n的数组只需交换(n-1)次(i次,j初始值为0所以无“=”)即可实现最大值冒泡
                
            	if (arr[j] > arr[j + 1]) {
                    // 交换
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
    	}
    	return arr;
    }
}

输出结果:

生成的文件:

210 679 717 94 980 871 57 472 449 775 
456 246 284 724 237 703 439 288 83 245 
348 238 337 450 71 349 266 680 6 428 
27 5 232 482 594 977 444 241 369 773 
655 131 517 298 576 333 357 128 631 833 
266 579 66 178 649 762 499 228 482 225 
578 351 833 663 95 370 532 219 36 48 
865 117 580 55 772 684 941 198 518 392 
337 104 225 153 65 734 977 979 837 827 
421 72 54 853 927 545 682 109 394 302 

排序后打印在控制台:

5 6 27 36 48 54 55 57 65 66 
71 72 83 94 95 104 109 117 128 131 
153 178 198 210 219 225 225 228 232 237 
238 241 245 246 266 266 284 288 298 302 
333 337 337 348 349 351 357 369 370 392 
394 421 428 439 444 449 450 456 472 482 
482 499 517 518 532 545 576 578 579 580 
594 631 649 655 663 679 680 682 684 703 
717 724 734 762 772 773 775 827 833 833 
837 853 865 871 927 941 977 977 979 980 
**12.16(替换文本)

程序清单12-16给出一个程序,替换源文件中的文本,然后将这个变化存储到一个新文件中。改写程序,将这个变化存储到原始文件中。例如:调用

java Exercisel2_16 file oldString newString

用newString代替源文件中的oldString

import java.io.*;
import java.util.*;

public class Test {
	public static void main(String[] args) throws Exception {
		if (args.length != 3){
			System.out.println("sourceFile oldStr newStr");
			System.exit(1);
		}
		//代码会先将文件的内容读取到ArrayList列表中,然后使用追加模式重新写入文件。
		File sourceFile = new File(args[0]); 
		List<String> lines = new ArrayList<>();

	    try (Scanner input = new Scanner(sourceFile)) {
	    	while (input.hasNextLine()) {
	        String line = input.nextLine();
	        String modifiedLine = line.replaceAll(args[1], args[2]);
	        lines.add(modifiedLine);
	        }
	    }

	    try (PrintWriter output = new PrintWriter(new FileWriter(sourceFile, false))) {
	        for (String line : lines) {
	        output.println(line);
	        }
	    }
	}
}

输出结果:

命令:

java Test.java Score.txt xx ""

源文本:

javaxx javaxx
javaxx

新文本:

java java
java
***12.17 (游戏:刽子手)

改写编程练习题7.35。程序读取存储在一个名为 hangman.txt 的文本文件中的单词。这些单词用空格分隔。

import java.io.*;
import java.util.Arrays;
import java.util.Scanner;

public class Test {

    static int missed = 0;
    final static String dataFileName = "Score.txt";
    static String[] words = getWordsList();

    private static String[] getWordsList() {
        File file = new File(dataFileName);
        try {
            FileReader fileReader = new FileReader(file);
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            String line = bufferedReader.readLine();
            return line.split("\\s");
        } catch (FileNotFoundException fileNotFoundException) {
            fileNotFoundException.printStackTrace();
            System.exit(1);
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
        throw new RuntimeException("getWordsList() did not return the file content for the game to continue.....");
    }

    static boolean wordLoop = true;
    static boolean gameLoop = true;
    final static String prompt = "(Guess) Enter a letter in word ";
    static String currentWord;
    static int[] guessed;
    static String display;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (gameLoop) {
            currentWord = getRandomWord();
            handleNewWord();
            do {
                System.out.print(prompt + display + " >");
                String guess = in.next();
                handleGuess(guess.charAt(0));
                wordLoop = validateWord();
                if (!wordLoop) {
                    System.out.println("The word is " + currentWord + " you missed " + missed + " times.");
                }
            } while (wordLoop);
            System.out.println("Do you want to guess another word? y or n >");
            gameLoop = in.next().charAt(0) == 'y';
        }
        System.out.println("Thanks for playing hangman!");
        in.close();
    }

    private static boolean validateWord() {
        boolean gameNotOver = false;
        for (int pos : guessed) {
            if (pos == 0) {
                gameNotOver = true;
                break;
            }
        }
        return gameNotOver;
    }

    static String getRandomWord() {
        return words[(int) (Math.random() * words.length)];
    }

    static void handleAsterisk() {
        char[] letters = currentWord.toCharArray();
        for (int i = 0; i < letters.length; i++) {
            if (guessed[i] == 0) {
                letters[i] = '*';
            }
        }
        display = String.valueOf(letters);
    }

    static void handleNewWord() {
        guessed = new int[currentWord.length()];
        char[] d = new char[currentWord.length()];
        Arrays.fill(d, '*');
        display = String.valueOf(d);
    }

    static void handleGuess(char guess) {
        char[] letters = currentWord.toCharArray();
        for (int i = 0; i < letters.length; i++) {
            if (letters[i] == guess) {
                if (guessed[i] != 0) {
                    System.out.println(guess + " is already in the word.");
                } else {
                    guessed[i] = 1;
                    handleAsterisk();
                    return;
                }
                break;
            }
        }
        missed++;
        System.out.println(guess + " is not in the word.");
    }
}

输出结果:

//Score.txt
words bread signal time display friend computer forget
(Guess) Enter a letter in word ***** >b
b is not in the word.
(Guess) Enter a letter in word ***** >w
(Guess) Enter a letter in word w**** >o
(Guess) Enter a letter in word wo*** >r
(Guess) Enter a letter in word wor** >d
(Guess) Enter a letter in word word* >s
The word is words you missed 1 times.
Do you want to guess another word? y or n >
n
Thanks for playing hangman!
**12.18 (添加包语句)

假设在目录 chapter1, chapter2, …, chapter34 下面有 Java源文件。编写一个程序,对在目录 chapteri 下面的 Java 源文件的第一行添加语句 “ package chapteri;"。假定 chapter1, chapter2, …, chapter34在根目录 srcRootDirectory 下面。根目录和 chapteri 目录可能包含其他目录和文件。

Test.java

这道题我新建了2个项目(project),第1个项目存放题中描述的目录,第2个项目存放Test.java用来测试,详情请看后面的截图

本代码没有涉及命令行参数,直接运行即可

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
    	//本段代码由6部分组成。大意是,新建一个临时文件,在第一行添加需要的信息后,从第二行开始,复制原有文件的代码,最后用临时文件替代原有文件
    	//1. 打开目标文件进行读取。
    	//2. 创建一个临时文件用于保存修改后的内容。
    	//3. 在临时文件的第一行写入要添加的语句。
    	//4. 将读取的原始文件内容写入临时文件的后续行。
    	//5. 删除原始文件。
    	//6. 将临时文件重命名为原始文件的名称,以替换原始文件。
    	
    	//使用循环给所有文件的首行添加package信息,这里为了简化,只做了3个包,每个包2个java文件和1个txt文件
    	for(int chapter = 1; chapter < 4; chapter++) {
    		for (int document = 1; document < 3; document++) {
    			//定位到需要更改的原始文件
    			String filePath = "/Users/kevinwang/eclipse-workspace/test/srcRootDirectory/chapter" + chapter + "/chapter" + chapter + "_" + document + ".java";
    	
    			//要插入的语句
    			String statement = "package chapter" + chapter + ";"; 

    			try {
    				//打开目标文件进行读取。
    				File inputFile = new File(filePath);
    				//创建一个临时文件用于保存修改后的内容。
    				File tempFile = new File("temp.txt");

    				BufferedReader reader = new BufferedReader(new FileReader(inputFile));
    				BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));

    				//在临时文件的第一行写入要添加的语句
    				writer.write(statement);
    				writer.newLine();

    				String line;

    				//将原始文件的内容写入临时文件的后续行
    				while ((line = reader.readLine()) != null) {
    					writer.write(line);
    					writer.newLine();
    				}

    				reader.close();
    				writer.close();

    				//删除原始文件
    				inputFile.delete();

    				//将临时文件重命名为原始文件的名称
    				tempFile.renameTo(inputFile);

    				System.out.println("Statement added successfully.");
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
}

输出之前:

首先按照题意要求,创建好对应根目录、包和java文件,我这里为了简便只创建了3个包

题目中要求,根目录中要有其他目录和其他文件,因此创建(default package)作为其他目录,创建txt文件用作其他文件,他们与本题无关

点开文件,给每一个java文件写入class和main方法,由于没有package信息,所以会报错,之后我们的Test.java代码可以自动加入这个package信息

《Java黑皮书基础篇第10版》 第12章【习题】

输出之后:

我们可以看到报错消失了,点开文件发现,package信息已经被加入

《Java黑皮书基础篇第10版》 第12章【习题】

*12.19(统计单词)

编写一个程序,统计Abraham Lincoln总统的Gettysburg演讲中的单词数,该演讲的网址为http://cs.armstrong.edu/liang/data/Lincoln.txt

package learning;

import java.util.Scanner;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

public class Test {
    public static void main(String[] args) {
    	System.out.print("Input an URL: ");
    	//如果不需要Scanner引用变量,就可以直接创建对象输入
    	String URLString = new Scanner(System.in).next();
    	try {
    		URL url = new URL(URLString);
    		int count = 0;
    		Scanner scanner = new Scanner(url.openStream());
    		while(scanner.hasNext()) {
    			String line = scanner.nextLine();
    			String[] lineArray = line.split("\\D+");
    			count = count + lineArray.length;
    		}
    		System.out.println("The file has " + count + " words.");
    		
    	} catch(MalformedURLException ex) {
    		ex.getStackTrace();
    	} catch(IOException ex) {
    		ex.getStackTrace();
    	}
    }
}

输出结果:

Input an URL: http://www.google.com
The file has 793 words.
**12.20 (删除包语句)

假设在目录 chapter1, chapter2, …, chapter34 下面有 Java源文件。编写一个程序,对在目录 chapteri 下面的 Java 源文件删除其第一行包语句 “ package chapteri;"。假定 chapter1, chapter2, …, chapter34在根目录 srcRootDirectory 下面。根目录和 chapteri 目录可能包含其他目录和文件。

Test.java

这道题我新建了2个项目(project),第1个项目存放题中描述的目录,第2个项目存放Test.java用来测试,详情请看后面的截图

本代码没有涉及命令行参数,直接运行即可

package learning;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
    	//本段代码由6部分组成。大意是,新建一个临时文件,从原有文件的第二行开始(忽略第一行的package信息),复制原有文件的代码,最后用临时文件替代原有文件
    	//1. 打开目标文件进行读取。
    	//2. 创建一个临时文件用于保存修改后的内容。
    	//3. 跳过第一行,不将其写入临时文件。
    	//4. 从第二行开始,将原始文件的内容写入临时文件
    	//5. 删除原始文件。
    	//6. 将临时文件重命名为原始文件的名称,以替换原始文件。
    	
    	//使用循环给所有文件的首行添加package信息,这里为了简化,只做了3个包,每个包2个java文件和1个txt文件
    	for(int chapter = 1; chapter < 4; chapter++) {
    		for (int document = 1; document < 3; document++) {
    			//定位到需要更改的原始文件
    			String filePath = "/Users/kevinwang/eclipse-workspace/delete/srcRootDirectory/chapter" + chapter + "/chapter" + chapter + "_" + document + ".java";

    			try {
    				//打开目标文件进行读取。
    				File inputFile = new File(filePath);
    				//创建一个临时文件用于保存修改后的内容。
    				File tempFile = new File("temp.txt");

    				BufferedReader reader = new BufferedReader(new FileReader(inputFile));
    				BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));

    				//跳过第一行,不将其写入临时文件。
    				reader.readLine();

    				String line;

    				//从第二行开始,将原始文件的内容写入临时文件
    				while ((line = reader.readLine()) != null) {
    					writer.write(line);
    					writer.newLine();
    				}

    				reader.close();
    				writer.close();

    				//删除原始文件
    				inputFile.delete();

    				//将临时文件重命名为原始文件的名称,以替换原始文件。
    				tempFile.renameTo(inputFile);

    				System.out.println("Statement deleted successfully.");
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
}

输出之前:

首先按照题意要求,创建好对应根目录、包和java文件,我这里为了简便只创建了3个包

题目中要求,根目录中要有其他目录和其他文件,因此创建(default package)作为其他目录,创建txt文件用作其他文件,他们与本题无关

点开文件,给每一个java文件写入class和main方法,同时写入package信息,之后我们的Test.java代码可以删除加入这个package信息

《Java黑皮书基础篇第10版》 第12章【习题】

输出之后:

我们可以看到报错出现了,这是因为package信息已经被删除

《Java黑皮书基础篇第10版》 第12章【习题】

*12.21 (数据排好序了吗?)

编写一个程序,从文件 SortedStrings.txt 中读取字符串,并且给出报告, 文件中的字符串是否以升序的方式进行存储。如果文件中的字符串没有排好序,显示没有遵循排序的前面两个字符串。

package learning;

import java.util.Scanner;
import java.util.ArrayList;
import java.io.File;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
    	File file = new File("/Users/kevinwang/eclipse-workspace/test/src/words.txt");
    	ArrayList<String> list = new ArrayList<>();
    	try (Scanner scanner = new Scanner(file)) {
    		//查找两个空格之间的字符序列,并添加到list数组
    		while(scanner.hasNext()) {
    			String lines = scanner.next();
    			list.add(lines);
    		}
    	for(int i = 1; i < list.size(); i++) {
        //如果i小于i-1,那么回传数据小于0;比较方法是首字母,首字母一样就下一个字母,所有字母都一样就=0
    		if(list.get(i).compareTo(list.get(i - 1)) < 0) {
    			System.out.println(list.get(i - 1) + " " + list.get(i));
    		}
    	}
    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
    }
}

输出结果:

//words.txt
apple banana dog cake egg
dog cake
12.22 **(替換文本)

修改编程练习題 12.16, 使用下面的命令用一个新字符串替换某个特定目录下所有文件中的一个字符串:

java Exercise12_22 dir oldString newString
package learning;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
    	if(args.length < 3) {
    		System.out.println("3 args needed, you only have " + args.length);
    		System.exit(1);
    	}
    	//listFiles方法返回目录下所有的文件和子目录
    	File[] files = new File(args[0]).listFiles();
    	if(files == null) {
    		System.out.println("Directory: " + args[0] + "is not a valid directory.");
    		System.exit(2);
    	}
    	for(File f : files) {
    		replaceFile(f, args[1], args[2]);
    	}
    }
    
    public static void replaceFile(File file, String oldString, String newString) {
    	ArrayList<String> list = new ArrayList<>();
    	try(Scanner scanner = new Scanner(file)) {
    		//next() 方法只读取下一个单词,不包括换行符,而且会将多个连续的空格视为一个分隔符。
    		//nextLine() 方法读取整行文本,包括换行符也会被读取,直到遇到换行符为止。
    		while(scanner.hasNextLine()) {
    			String lines = scanner.nextLine();
    			//这里需要注意的是,不可以只写lines.replaceAll(oldString, newString);
    			//因为字符串内容不可变,需要重新加lines,改变字符串的引用地址,代码才会生效
    			lines = lines.replaceAll(oldString, newString);
    			list.add(lines);
    		}
    		//在本题中,读写操作放在一个try-catch块中可以正常运行,但是某些情况下,可能会出错,建议读写应该分成两个try-catch块
    		PrintWriter writer = new PrintWriter(file);
    		for(int i = 0; i < list.size(); i++) {
    			writer.print(list.get(i));
    		}
    		writer.close();
    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
     }
}

输出结果:

在命令行输入命令:

java Test.java /Users/kevinwang/src apple banana

《Java黑皮书基础篇第10版》 第12章【习题】

**12.23 (处理Web上的文本文件中的分数)

假定Web上的一个文本文件http://cs.armstrong.edu/liang/data/Scores.txt中包含了不确定数目的成绩。编写一个程序,从该文件中读取分数,并且显示它们的总数以及平均数。分数使用空格进行分隔。

package learning;

import java.io.IOException;
import java.net.URL;
import java.util.Scanner;
import java.util.ArrayList;

public class Test {
    public static void main(String[] args) {
    	//本题的思路是:
    	//1. 逐行读取URL,将每一行使用正则表达式分离出数字,储存在字符串数组number中。
    	//2. 将数字从字符串数组转移到ArrayList<Integetr>数组list中,然后通过该数组求和、平均数
    	
    	//这里先举个例子,URL中数据的最后3行长这样
    	//Leonardo	Engineering	78
    	//Omar	Engineering	99
    	//Avery	Engineering	51
    	//
    	//(代码末尾会讲)记住上面这个空行,研究了我3个小时。。。
    	
    	//题目中提到的URL已经失效了,被转移到了以下地址,小伙伴们可以自行复制查看。
    	String website = "https://git.savannah.gnu.org/cgit/datamash.git/plain/examples/scores.txt";
    	ArrayList<Double> list = new ArrayList<>();
    	String[] number;
    	
    	try {
    		URL url = new URL(website);
    		Scanner scanner = new Scanner(url.openStream());

    		//(代码末尾会讲)注意事项:使用hasNextLine()判断是否还有下一行,判断的依据是换行符,只要有换行符,就返回true
    		while(scanner.hasNext()) {
    			//如果有下一行,使用nextLine()读取下一个数据
    			String line = scanner.next();
    			//测试是否读取:System.out.println(line);
    			//使用正则表达式分离数字,并储存到String数组中
    			number = line.split("\\D+");
    		
    			//逐一提取String数组元素,并转化成double类型数字储存到ArrayList数组
    			for(int i = 0; i < number.length; i++) {
    				String str = number[i];
    				//(代码末尾会讲)小tips:在循环写入ArrayList数组的时候嵌套了一个try-catch,原因是因为,URL读取数据不同于固定的txt文件,可能会有意想不到的情况发生,如果出现异常,catch捕获之后使用continue可以继续返回try块,不终止try块的执行
    				try {
    					double middleStage = Double.parseDouble(str);
    					list.add(middleStage);
    				} catch(NumberFormatException ex) {
    					System.out.println("Invalid number: " + str + ", skipping...");
    					continue;
    				}
    			}
    		}
    	
    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
    	
    		
    	Double sum = 0.0;
    	for(int i = 0; i < list.size(); i++) {
    		sum = sum + list.get(i); 		
    	}
    	Double average = sum / list.size();
    	System.out.print("The sum is " + sum + ", and the average is " + average);
    }
}
/*
代码中一共出现了3次“(代码末尾会讲)”,现在来讲一下

1. 
首先是URL中的数据,打开URL网站,表面上看代码的最后一行是“Avery Engineering 51”
但是经过我3个小时的怀疑人生,我最终自制了txt文件,发现实际上,在数字51之后还有一个换行符,因为某些原因被隐藏了,请大家注意这一点

2.
辨析一下next()和nextLine()的用法,为了分析简单,假设数据集只有最后2行数据(第一点讲过,这个数字51之后还有一个换行符,只是换行之后没有内容而已)
“
Avery Engineering 51(换行符)
(额外的一行)
”

注意:hasNextLine() 方法不会读取换行符。它仅检查是否存在下一行可供读取,而不会移动指针或读取实际内容。

根据我的代码,共有4种排列组合

while(scanner.hasNext()) {
    String line = scanner.next();
}

while(scanner.hasNext()) {
    String line = scanner.nextLine();
}

while(scanner.hasNextLine()) {
    String line = scanner.next();
}

while(scanner.hasNextLine()) {
    String line = scanner.nextLine();
}

如果是前2种情况,不会报异常,因为就是先检查是否还有数据,如果有,一行一行读取,很正常

但是,第3种情况,第一行是hasNextLine(),第二行是next(),会报错。。。。。。
逐行分析如下:

首先,第一行,scanner.hasNextLine()从"Avery"处检查是否还有更多的行可以读取,在数据集中,第一行以换行符结尾,返回true,但是hasNextLine()并不会移动指针,指针依然在"Avery"前面
其次,第二行,scanner.next()尝试读取下一个单词或字符串,它会从第一行中读取第1个单词"Avery",并将其存储在line变量中,同时将指针移动到空格后面,准备读取Engineering

下一轮循环,第一行,scanner.hasNextLine()检查是否还有更多的行可以读取,在数据集中,第一行以换行符结尾,返回true,但是hasNextLine()并不会移动指针,指针依然在空格后面,准备读取Engineering
下一轮循环,第二行,scanner.next()尝试读取下一个单词或字符串,它会从第一行中读取第2个单词"Engineering",并将其存储在line变量中,同时将指针移动到空格后面,准备读取51

下一轮循环,第一行,scanner.hasNextLine()检查是否还有更多的行可以读取,在数据集中,第一行以换行符结尾,返回true,但是hasNextLine()并不会移动指针,指针依然在空格后面,准备读取51
下一轮循环,第二行,scanner.next()尝试读取下一个单词或字符串,它会从第一行中读取第3个单词"51",并将其存储在line变量中,同时将指针移动到"换行符"前面,并等待下一个输入(next()方法不会读取换行符,因此在读取完 "51" 后,指针会停留在当前行的末尾,而不会自动移动到下一行)

下一轮循环,第一行,scanner.hasNextLine()检查是否还有更多的行可以读取,在数据集中,第一行以换行符结尾,返回true,但是hasNextLine()并不会移动指针,指针依然在"换行符"前面,并等待下一个输入
下一轮循环,第二行,scanner.next()尝试读取下一个单词或字符串,但是最后一个数据已经被读取了,此时没有数据可读,报异常

第4种情况不会报错
nextLine()方法读取整行文本,包括换行符也会被读取,再次进入循环时,条件会变成false
    		
3.
为什么要嵌套一个try-catch块?考虑第2和4种情况

while(scanner.hasNext()) {
    String line = scanner.nextLine();
}

while(scanner.hasNextLine()) {
    String line = scanner.nextLine();
}

在这种情况下,scanner.nextLine();会导致正则表达式无法识别纯数字,原因如下

scanner.nextLine()会读取整行,也就是会读取“Avery Engineering 51”

用正则表达式分开后,String数组number储存的内容是:“[" ", "51"]”
因此,在for循环中没有办法将第一个元素转换成数字,会被catch捕获。
正常情况下,捕获以后try块代码就不再执行,但是由于try-catch是在循环内,只需要continue,就可以重新执行循环,不会导致程序停止
*/

输出结果:

The sum is 5882.0, and the average is 70.86746987951807
*12.24 (创建大的数据集)

创建一个具有1000行的数据文件。文件中的每行包含了一个教职员工的姓、名、级别以及薪水。第i行的教职员工的姓和名为 FirstNamei 和 LastNamei。级别随机产生为 assistant(助理)、associate(副)以及 full(正)。薪水为随机产生的数字,并且小数点后保留两位数字。对于助理教授而言,薪水应该在 50 000 到 80 000 的范围内,对于副教授为 60 000 到110 000, 对于正教授为75 000到130 000。保存文件为 Salary.txt。

package learning;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class Test {
    public static void main(String[] args) {
        try {
            PrintWriter writer = new PrintWriter(new FileWriter("Employees.txt"));

            for (int i = 0; i < 200; i++) {
                String firstName = "FirstName" + i;
                String lastName = "LastName" + i;
                String rank = getRandomRank();
                double salary = getRandomSalary(rank);

                writer.printf("%-13s", firstName);
                writer.printf("%-12s", lastName);
                writer.printf("%-10s", rank);
                writer.printf("%.2f", salary);
                writer.println();
            }

            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static String getRandomRank() {
        int rank = (int) (Math.random() * 3);
        //使用return直接退出整个方法
        switch (rank) {
            case 0:
                return "Assistant";
            case 1:
                return "Associate";
            case 2:
                return "Full";
            default:
                return "";
        }
    }

    private static double getRandomSalary(String rank) {
        switch (rank) {
            case "Assistant":
                return Math.random() * 30000 + 50000;
            case "Associate":
                return Math.random() * 50000 + 60000;
            case "Full":
                return Math.random() * 55000 + 75000;
            default:
                return 0.0;
        }
    }
}

生成文件(部分):

FirstName97  LastName97  Full      121478.87
FirstName98  LastName98  Associate 101478.02
FirstName99  LastName99  Assistant 76299.02
FirstName100 LastName100 Assistant 75776.67
FirstName101 LastName101 Associate 80331.39
FirstName102 LastName102 Associate 99114.27
FirstName103 LastName103 Associate 68459.78
FirstName104 LastName104 Assistant 56111.40
*12.25 (处理大的数据集)

一个大学将其教职员工的薪水发布在http://cs.armstrong.edu/liang/data/Salary.txt中。文件中的每行包含一个教职员工的姓、名、级别以及薪水(见编程练习题12.24)。编写一个程序,分别显示助理教授、副教授、正教授,所有教职员工各个类别的总薪水,以及上述类别的平均薪水。

这道题本质上也是提取数字,和12.23差不多,但是使用的方法更简单

package learning;

import java.util.Scanner;
import java.io.File;
import java.io.IOException;

public class Test {
    public static void main(String[] args) {
    	double assistantTotal = 0;
    	double associateTotal = 0;
    	double fullTotal = 0;
    	double total = 0;
    	int assistantCount = 0;
    	int associateCount = 0;
    	int fullCount = 0;
    	int totalCount = 0;
    	
        File file = new File("Employees.txt");
        
        //代码可以非常简单的提取出文本中的数字,不需要用到正则表达式,ArrayList,甚至String数组也不需要
        //求所有工资的总和,并计算人数
		try (Scanner scanner = new Scanner(file)) {
			 while (scanner.hasNext()) {
			     String line = scanner.next();
			     if (Character.isDigit(line.charAt(0))) {
		             double score = Double.parseDouble(line);
		             total = total + score;
		             totalCount++;
		         }			     
		     }
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		
		try (Scanner scanner = new Scanner(file)) {
			 while (scanner.hasNext()) {
			     String line = scanner.next();
			     //==用于比较引用是否相等,equals用于比较内容是否相等
			     //求三类职员工资的总和,并分别计算人数
			     if (line.equals("Assistant")) {
			    	 double AssistantSalary = scanner.nextDouble();
		             assistantTotal = assistantTotal + AssistantSalary;
		             assistantCount++;
		         }	
			     
			     if (line.equals("Associate")) {
			    	 double AssociateSalary = scanner.nextDouble();
		             associateTotal =  associateTotal + AssociateSalary;
		             associateCount++;
		         }	
			     
			     if (line.equals("Full")) {
			    	 double FullSalary = scanner.nextDouble();
		             fullTotal = fullTotal + FullSalary;
		             fullCount++;
		         }	
			 }
			 //校验:计算总数和平均数。浮点数不精确,比较大小需要让结果小于一个很小的值
			 if (assistantTotal + associateTotal + fullTotal - total < 0.001) {
				 System.out.printf("The total salary for Assistant employees are $%.2f\n", assistantTotal);
				 System.out.printf("The total salary for Associate employees are $%.2f\n", associateTotal);
				 System.out.printf("The total salary for Full employees are $%.2f\n", fullTotal);
				 System.out.printf("The total salary for all employees are $%.2f\n", total);
			 }
			 
			 if (assistantCount + associateCount + fullCount == totalCount) {
				 System.out.printf("The average salary for Assistant employees are $%.2f\n", assistantTotal / assistantCount);
				 System.out.printf("The average salary for Associate employees are $%.2f\n", associateTotal / associateCount);
				 System.out.printf("The average salary for Full employees are $%.2f\n", fullTotal / fullCount);
				 System.out.printf("The average salary for all employees are $%.2f\n", total / totalCount);
			 }
		} catch (IOException ex) {
			ex.printStackTrace();
		}
    }
}

输出结果:

The total salary for Assistant employees are $4338427.29
The total salary for Associate employees are $5995917.23
The total salary for Full employees are $6712662.22
The total salary for all employees are $17047006.74
The average salary for Assistant employees are $65733.75
The average salary for Associate employees are $86897.35
The average salary for Full employees are $103271.73
The average salary for all employees are $85235.03
**12.26 (创建一个目录)

编写一个程序,提示用户输人一个目录名称,然后使用 File 的 mkdirs 方法 创建相应的目录。如果目录创建成功则显示“Directory created successfully”,如果目录已经存在,则显示 “Directory already exists.”

package learning;

import java.io.File;
import java.io.IOException;

public class Test2 {
    public static void main(String[] args) {
    	// 定义文件夹
        File createFolder = new File("/Users/kevinwang/eclipse-workspace/test/package.my");
        if (createFolder.exists()) {
        	System.out.println("The forlder already existed.");
        	System.exit(1);
        }
        
        //定义文件
        File createFile = new File("/Users/kevinwang/eclipse-workspace/test/package.my/newFile.java");
        if (createFile.exists()) {
        	System.out.println("The file already existed.");
        	System.exit(2);
        }
        
        // 创建文件夹。要注意的是,mkdirs创建的是文件夹,不是java的包。java包并不是文件系统的实体,而是java中的逻辑组织结构
        boolean folderSuccess = createFolder.mkdirs();
        
        if (folderSuccess) {
            System.out.println("目录创建成功");
        } else {
            System.out.println("目录创建失败");
        }
        
        try {
        	//创建文件
        	boolean fileSuccess = createFile.createNewFile();
        
        	if (fileSuccess) {
        		System.out.println("文件创建成功");
        	} else {
        		System.out.println("文件创建失败");
        	}
        } catch (IOException ex) {
        	ex.printStackTrace();
        }
    }
}

输出结果:

《Java黑皮书基础篇第10版》 第12章【习题】

目录创建成功
文件创建成功
**12.27 (替換文本)

假定在某个目录下面的多个文件中包含了单词 Exercisei_j, 其中 i 和 j 是数字。编写一个程序,如果 i 是个位数,则在 i 前面插入一个0, 同理如果 y 是个位数,则在 y 前面插入一个0。例如,文件中的单词 Exercise2_1 将被替换为 Exercisc02_01。Java 中,当从命令行传递符号 * 的时候,指代该目录下的所有文件(参见附录III.V)。使用下面的命令来运行程序。

package learning;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
    	File srcDirectory = new File("./testDir");
    	ArrayList<File> files = new ArrayList<>(Arrays.asList(srcDirectory.listFiles()));
    	for (File f: files) {
    		replaceFile(f);
    	}
    }
    
    public static void replaceFile(File file) {
    	ArrayList<String> list = new ArrayList<>();
    	try(Scanner scanner = new Scanner(file)) {
    		while(scanner.hasNext()) {
    			String words = scanner.next();
    			
    			// charAt()方法返回的是字符的ASCII值,因此9必须加单引号
    			if (words.charAt(5) <= '9' || words.charAt(7) <= '9') {
    				// 字符串内容不可变,需要重新加words引用,改变字符串的引用地址,代码才会生效
    				words = words.replaceAll("e", "e0");
    				words = words.replaceAll("_", "_0");
    				list.add(words);
    			} else {
    				list.add(words);
    			}
    		}

    		PrintWriter writer = new PrintWriter(file);
    		for(int i = 0; i < list.size(); i++) {
    			//使用println可以换行,如果使用print就不会换行
    			writer.println(list.get(i));
    		}
    		writer.close();
    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
     }
}

输出结果:

《Java黑皮书基础篇第10版》 第12章【习题】

运行指令:

java Test.java *
**12.28(更改文件名)

假定在某个目录下面有多个文件,命名为Exercisei_j,其中i和j是数字。编写一个程序,如果i是个位数,则在i前面插入一个0。例如,目录中的文件Exercise2_1将被改名为Exercise02_1。Java中,当从命令行传递符号*的时候,指代该目录下的所有文件(参见附录III.V)。

package learning;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
    	//listFiles方法返回目录下所有的文件和子目录
    	File srcDirectory = new File("./testDir");
    	ArrayList<File> files = new ArrayList<>(Arrays.asList(srcDirectory.listFiles()));
    	for (File f: files) {
    		replaceFileName(f);
    	}
    }
    
    public static void replaceFileName(File file) {
    	try(Scanner scanner = new Scanner(file)) {
    		// 获取文件所在的目录和文件名
			String directory = file.getParent();
    		String fileName = file.getName();
    		if (fileName.charAt(8) < '9') {
    			// 将文件名中的某一部分替换为新的值
    			String newFileName = fileName.replace("Exercise", "Exercise0");

    			// 构建新的文件路径和名称
    			String newFilePath = directory + File.separator + newFileName;

    			// 创建新的File对象,表示更改后的文件
    			File newFile = new File(newFilePath);

    			// 重命名文件
    			boolean renamed = file.renameTo(newFile);

    			if (renamed) {
    			    System.out.println("文件名更改成功!");
    			} else {
    			    System.out.println("文件名更改失败!");
    			}
    		}

    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
     }
}

输出结果:

《Java黑皮书基础篇第10版》 第12章【习题】

命令行语句:

java Test.java *
文件名更改成功!
文件名更改成功!
**12.29(更改文件名)

假定在某个目录下面有多个文件,命名为Exercisei_j,其中i和j是数字。编写一个程序,如果j是个位数,则在j前面插入一个0。例如,目录中的文件Exercise2_1将被改名为Exercise2_01。Java中,当从命令行传递符号*的时候,指代该目录下的所有文件(参见附录III.V)。

package learning;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
    	//listFiles方法返回目录下所有的文件和子目录
    	File srcDirectory = new File("./testDir");
    	ArrayList<File> files = new ArrayList<>(Arrays.asList(srcDirectory.listFiles()));
    	for (File f: files) {
    		replaceFileName(f);
    	}
    }
    
    public static void replaceFileName(File file) {
    	try(Scanner scanner = new Scanner(file)) {
    		// 获取文件所在的目录和文件名
			String directory = file.getParent();
    		String fileName = file.getName();
    		if (fileName.charAt(8) < '9') {
    			// 将文件名中的某一部分替换为新的值
    			String newFileName = fileName.replace("_", "_0");

    			// 构建新的文件路径和名称
    			String newFilePath = directory + File.separator + newFileName;

    			// 创建新的File对象,表示更改后的文件
    			File newFile = new File(newFilePath);

    			// 重命名文件
    			boolean renamed = file.renameTo(newFile);

    			if (renamed) {
    			    System.out.println("文件名更改成功!");
    			} else {
    			    System.out.println("文件名更改失败!");
    			}
    		}

    	} catch(IOException ex) {
    		ex.printStackTrace();
    	}
     }
}

输出结果:

《Java黑皮书基础篇第10版》 第12章【习题】

命令行语句:

java Test.java *
文件名更改成功!
文件名更改成功!
**12.30 (每个字母出現的次数)

编写一个程序,提示用户输入一个文件名,然后显示该文件中每个字母出现的次数。字母是大小写敏感的。

package learning;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class Test {
	public static void main(String[] args) {
		// 创建26个长度的数组用于储存每个字母出现的次数
		int[] times = new int[26];
		// 提示用户输入文件名称并载入
		System.out.print("Input a file name: ");
		Scanner scanner = new Scanner(System.in);
		String name = scanner.next();
		File file = new File(name);
		
		try (Scanner input = new Scanner(file)) {
			// 逐个读取单词
			while (input.hasNext()) {
				String word = input.next();
				// 遍历每个单词的每个字符,如果字符是数字,就将int数组中对应的字符数量+1
				for (char ch : word.toCharArray()) {
					if (Character.isLetter(ch)) {
						times[Character.toUpperCase(ch) - 65]++;
					}
				}
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		//遍历times数组输出出现次数
		for (int i = 0; i < times.length; i++) {
			System.out.println("Number of " + (char)(i + 65) + "'s :" + times[i]);
		}
	}
}

输出结果:

文件长这样:

//Employees.txt
abcdefghijklmnopqrstuvwxyzzzz
Input a file name: Employees.txt
Number of A's :1
Number of B's :1
Number of C's :1
······
Number of X's :1
Number of Y's :1
Number of Z's :4
*12.31 (小孩名字流行度排名)

从2001年到2010年的小孩取名的流行度排名可以从 www.ssa.gov/oact/babynames下载并保存在babynameranking2001.txt, babynameranking2002.txt, …, babynameranking2010.txt。每个文件包含了一千行。每行包含一个排名,一个男孩的名字,取该名字的数目,一个女孩子的名字,取该名字的数目。例如,文件babynameranking2010.txt的前面两行如下所示:

《Java黑皮书基础篇第10版》 第12章【习题】

因此,男孩名 Jacob 和女孩名 Isabella 排第一位,男孩名 Ethan 和女孩名 Sophia 排名第二。有 21 875 名男孩取名 Jacob, 22 731 名女孩取名 Isabella。编写一个程序,提示用户输入年份 、性别,接着输入名字,程序可以显示该年份该名字的排名。这里是一个运行示例:

《Java黑皮书基础篇第10版》 第12章【习题】

package learning;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.ArrayList;

public class Test {
	public static void main(String[] args) {
		ArrayList<String> male = new ArrayList<>();
		ArrayList<String> female = new ArrayList<>();
		Scanner scanner = new Scanner(System.in);
		// 提示用户输入
		System.out.print("Enter the year: ");
		int year = scanner.nextInt();
		System.out.print("Enter the gender(M for male, F for female): ");
		String gender = scanner.next();
		System.out.print("Enter the name: ");
		String name = scanner.next();
		
		File file = new File("babynameranking" + year + ".txt");
		
		try (Scanner input = new Scanner(file)) {
			// 逐行读取单词载入ArrayList
			while (input.hasNextLine()) {
				String[] word = input.nextLine().split("\\s+");
				male.add(word[0] + " " + word[1] + " " + word[2]);
				female.add(word[0] + " " + word[3] + " " + word[4]);
				}
			// 匹配男生。字符串比较应该用equals比较内容,不能用==比较指向
			if (gender.equals("M")) {
				boolean hasName = false;
				for (int i = 0; i < male.size(); i++) {
					//使用name + " "的目的是防止子字符串干扰,例如,contains.Jack,会匹配到Jack和Jackson,但是如果加入1个空格,就不会匹配到Jackson
					if (male.get(i).contains(name + " ")) {
						String[] temp = male.get(i).split("\\s+");
						System.out.println(name + " is ranked #" + temp[0] + " in year " + year);
						hasName = true;
					}
				}
				//如果遍历一圈下来,还没有匹配到,那就证明这个名字没有被收录
				if (!hasName) {
					System.out.println("The name " + name + " is not ranked in year " + year);
				}
			}
			// 匹配女生
			if (gender.equals("F")) {
				boolean hasName = false;
				for (int i = 0; i < female.size(); i++) {
					//使用name + " "的目的是防止子字符串干扰,例如,contains.Jack,会匹配到Jack和Jackson,但是如果加入1个空格,就不会匹配到Jackson
					if (female.get(i).contains(name + " ")) {
						String[] temp = female.get(i).split("\\s+");
						System.out.println(name + " is ranked #" + temp[0] + " in year " + year);
						hasName = true;
					} 
				}
				if (!hasName) {
					System.out.println("The name " + name + " is not ranked in year " + year);
				}
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

下面的代码更加简单,编写逻辑更加清晰,值得借鉴

package learning;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.ArrayList;

public class Test {
	//提前定义文件的后缀和前缀为常量,防止被篡改,提高代码可读性和安全性
	private static final String FILENAME_PREFIX = "babynameranking";
	private static final String FILENAME_SUFFIX = ".txt";
	
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		// 提示用户输入
		System.out.print("Enter the year: ");
		int year = scanner.nextInt();
		System.out.print("Enter the gender(M for male, F for female): ");
		String gender = scanner.next().trim();
		System.out.print("Enter the name: ");
		String name = scanner.next();
		
		//将功能封装在方法中,并将返回值赋值给rank
		int rank = getRanking(year, gender.charAt(0), name);
		if (rank == 0) {
            System.out.println("That name is not on the list for that year....");
        } else
        	System.out.print("\n" + name + " is ranked #" + rank + " in year " + year);
	}
	
	public static int getRanking(int year, char gender, String name) {
		//检测用户输入,返回错误值并中断执行方法
		if (year < 2001 || year > 2010) return -1;
        if (gender != 'M' && gender != 'F') return -2;
        
        //因为一个名字的ranking最小是1,这里初始化一个不可能的ranking值,如果没有匹配到合适的名字,就返回这个不可能的值,供调用方参考
		int ranking = 0;
		String fileName = FILENAME_PREFIX + year + FILENAME_SUFFIX;
		File file = new File(fileName);
		
		try (Scanner input = new Scanner(file)) {
			// 逐行将信息载入String数组
			while (input.hasNextLine()) {
				//split之后类似于[1, Jacob, 25,837, Emily, 23,942]
				String[] line = input.nextLine().split("\\s+");
				if (gender == 'M') {
					//忽略用户可能自定义的大小写,如果完全匹配了,就返回对应的ranking值
					if (line[1].equalsIgnoreCase(name)) {
						ranking = Integer.parseInt(line[0].trim());
						break;
					}
				} else {
					if (line[3].equalsIgnoreCase(name)) {
						ranking = Integer.parseInt(line[0].trim());
						break;
					}
				}
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return ranking;
	}
}

输出结果:

Enter the year: 2005
Enter the gender(M for male, F for female): F
Enter the name: Morgan
Morgan is ranked #33 in year 2005

Enter the year: 2006
Enter the gender(M for male, F for female): F
Enter the name: ABC
The name ABC is not ranked in year 2006
*12.32 (排名总结)

编写一个程序,使用编程练习 12.31 中所描述的文件,显示前 5 位的女孩和男孩名字的排名总结表格 :

《Java黑皮书基础篇第10版》 第12章【习题】

package learning;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.ArrayList;

public class Test {
	private static final String FILENAME_PREFIX = "babynameranking";
    private static final String FILENAME_SUFFIX = ".txt";
    
	public static void main(String[] args) {
		int year = 2001; 
		int endYear = 2010;
		System.out.printf("%-10s%-10s%-10s%-10s%-10s%-10s%-10s%-10s%-10s%-10s%-10s%n",
                "Year", "Rank 1", "Rank 2", "Rank 3", "Rank 4", "Rank 5", "Rank 1", "Rank 2", "Rank 3", "Rank 4", "Rank 5");
        System.out.println("__________________________________________________________________________________________________________");
        
		while (endYear >= year) {
			getName(new File(FILENAME_PREFIX + endYear + FILENAME_SUFFIX));
			endYear--;
		}
		
	}
	
	public static void getName(File file) {
		ArrayList<String> maleList = new ArrayList<>();
		ArrayList<String> femaleList = new ArrayList<>();
		try (Scanner input = new Scanner(file)) {
			// 逐行将信息载入String数组
			while (input.hasNextLine()) {
				//split之后类似于[1, Jacob, 25,837, Emily, 23,942]
				String[] line = input.nextLine().split("\\s+");
				if (Integer.parseInt(line[0]) <= 5) {
					maleList.add(line[1]);
					femaleList.add(line[3]);
				}
			}
			for (int i = 2010; i > 2000; i--) {
				//注意substring的第二个参数是不包括的,因此不能写18
				if (Integer.parseInt(file.getName().substring(15, 19)) == i)
					System.out.printf("%-10s", i);
			}
			
			for (int i = 0; i < maleList.size(); i++) {
				
				System.out.printf("%-10s", maleList.get(i) + " ");
			}
			
			for (int i = 0; i < maleList.size(); i++) {
				System.out.printf("%-10s", femaleList.get(i) + " ");
			}
			
			System.out.println();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}

输出结果:

Year      Rank 1    Rank 2    Rank 3    Rank 4    Rank 5    Rank 1    Rank 2    Rank 3    Rank 4    Rank 5    
__________________________________________________________________________________________________________
2010      Jacob     Ethan     Michael   Jayden    William   Isabella  Sophia    Emma      Olivia    Ava       
2009      Jacob     Ethan     Michael   Alexander William   Isabella  Emma      Olivia    Sophia    Ava       
2008      Jacob     Michael   Ethan     Joshua    Daniel    Emma      Isabella  Emily     Olivia    Ava       
2007      Jacob     Michael   Ethan     Joshua    Daniel    Emily     Isabella  Emma      Ava       Madison   
2006      Jacob     Michael   Joshua    Ethan     Matthew   Emily     Emma      Madison   Isabella  Ava       
2005      Jacob     Michael   Joshua    Matthew   Ethan     Emily     Emma      Madison   Abigail   Olivia    
2004      Jacob     Michael   Joshua    Matthew   Ethan     Emily     Emma      Madison   Olivia    Hannah    
2003      Jacob     Michael   Joshua    Matthew   Andrew    Emily     Emma      Madison   Hannah    Olivia    
2002      Jacob     Michael   Joshua    Matthew   Ethan     Emily     Madison   Hannah    Emma      Alexis    
2001      Jacob     Michael   Matthew   Joshua    Christopher Emily     Madison   Hannah    Ashley    Alexis 
**12.33 (搜索 Web)

修改程序清单 12-18, 从网址 http://cs.armstrong.edu/liang 开始搜索单词Computer Programming, 一旦搜索到,程序终止。显示包含了单词的页面的 URL 地址

package learning;

import java.util.Scanner; 
import java.util.ArrayList;

public class Test {
	 private static final String SEARCH_WORD = "SQL";
	 
	public static void main(String[]args) {
		java.util.Scanner input = new java.util.Scanner(System.in);
		System.out.print("Enter a URL: ");
		String url = input.nextLine();
		crawler(url);
	}

	public static void crawler(String startingURL) {
		ArrayList<String> listOfPendingURLs = new ArrayList<>();
		ArrayList<String> listOfTraversedURLs = new ArrayList<>();

		listOfPendingURLs.add(startingURL); 
		while (!listOfPendingURLs.isEmpty() && listOfTraversedURLs.size() <= 20) {
			String urlString = listOfPendingURLs.remove(0); 
			if (!listOfTraversedURLs.contains(urlString)) { 
				listOfTraversedURLs.add(urlString);
				System.out.println("Craw " + urlString);

				for (String s: getSubURLs(urlString)) { 
					if (!listOfTraversedURLs.contains(s)) 
						listOfPendingURLs.add(s);
				}
			}
		}
	}

	public static ArrayList<String> getSubURLs(String urlString) {
		ArrayList<String> list = new ArrayList<>();

		try {
			java.net.URL url = new java.net.URL(urlString);
			Scanner input = new Scanner(url.openStream()); 
			int lineNumber = 0;
			while (input.hasNext()) {
				lineNumber++;
				String line = input.nextLine();
				if (line.contains(SEARCH_WORD) ) {
                    System.out.println("Search phrase found on page: " + urlString + " @ lineNumber: " + lineNumber);
                    //System.out.println(line);
                    System.exit(0);
                }
			}
			
			int current = 0;
			 while (input.hasNextLine()) {
				 String line = input.nextLine();
				current = line.indexOf("http:", current); 
				while (current > 0) {
					int endIndex = line.indexOf("\"", current);
					if (endIndex > 0) {
						list.add(line.substring(current,endIndex)); 
						current = line.indexOf("http:", endIndex);
					} else 
						current = -1;
					}
				}
			}
			catch (Exception ex) {
				ex.printStackTrace();
			}
			
			return list;
		}
}

输出结果:文章来源地址https://www.toymoban.com/news/detail-473821.html

Enter a URL: https://www.khanacademy.org/computing/computer-programming
Craw https://www.khanacademy.org/computing/computer-programming
Search phrase found on page: https://www.khanacademy.org/computing/computer-programming @ lineNumber: 112

到了这里,关于《Java黑皮书基础篇第10版》 第12章【习题】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java 语言程序设计(基础篇)原书第10版 梁勇著 PDF 文字版电子书

    Java 语言程序设计(基础篇)原书第 10 版 是 Java 语言的经典教材,中文版分为基础篇和进阶篇,主要介绍程序设计基础、面向对象程序设计、GUI 程序设计、数据结构和算法、高级 Java 程序设计等内容。本书通过示例讲解问题求解技巧,提供大量的程序清单,每章配有丰富的

    2024年04月16日
    浏览(44)
  • C++编程最基础练习题(1-10) 小白入门必刷

    C++编程练习题 (1-10) 1. 输入3个数,求最大值 2. 编程序,求方程ax2+bx+c=0的根 3. 输入一个成绩,打印相应的等级 4. 输入3个double类型的值,判断这3个值是否可以表示一个三角形的三条边 5. 输入20个数,求其最大、最小和平均值 6. 输入若干个数,设输入的第一个数为后面要输

    2023年04月19日
    浏览(99)
  • C语言基础习题讲解

    1. 设计一个程序, 输入三位数a, 分别输出个,十,百位. (0a1000) 样例输入: 251 样例输出: 2 5 1 2. 设计一个程序, 输入整数l, 求边长为l的正方形面积, 比直径为l的圆形面积大多少. (0l1000, PI取3.14, 输出结果保留两位小数) 样例输入: 3 样例输出: 1.93 1. 设计一个程序, 输入a,b,c三个整数,

    2024年02月07日
    浏览(33)
  • 【R语言编程基础】【课后习题答案】【全】

    (1)多行注释的快捷键是(C)。 A.Ctrl+Shin+N B.Ctrl+N C.Ctrl+Shin+C D.Ctrl+C (2)以下函数不能直接查看plot函数的帮助文档的是(B)。 A. ?plot B.??plot C.help(plot) D.help(plot) (3)以下R包的加载方式正确的是(A)。 A.install.package 函数 B.library 函数 C…libPaths 函数 D.install 函数 (4)以下R包中不

    2023年04月08日
    浏览(55)
  • 编译原理1.6习题 程序设计语言基础

    图源:文心一言 编译原理习题整理~🥝🥝 作为初学者的我,这些习题主要用于自我巩固。由于是自学,答案难免有误,非常欢迎各位小伙伴指正与讨论!👏💡 第1版:自己的解题,与AI老师的判卷~🧩🧩 编辑: 梅头脑🌸  审核: 文心一言 题源: 龙书《编译原理》 Alfre

    2024年01月19日
    浏览(60)
  • 2.如何选择go语言基础类型——Leetcode习题9

    目录 本篇前瞻 Leetcode习题9 题目描述 原题解析 代码编写 有符号整形 基本数据类型 整形 有符号整形 无符号整形 浮点型 布尔型 字符 本篇小结 下一篇预告 欢迎来go语言的基础篇,这里会帮你梳理一下go语言的基本类型,注意本篇有参考go圣经,如果你有完整学习的需求可以看

    2024年02月12日
    浏览(42)
  • Java基础习题大全

    下列哪个是JDK提供的编译器? A.java.exe B.javac.exe C.javap.exe D.javaw.exe 下列哪个是Java应用程序主类中正确的main方法? A.public void main (String args[ ]) B.static void main (String args[ ]) C.public static void Main (String args[]) D.public static void main (String args[ ]) 下列哪个叙述是正确的? A.Java源文件是由若干

    2024年02月12日
    浏览(36)
  • C语言循环结构一些重要的练习题(较为基础的)

    循环结构 1.求累加和问题 2.输出字母A-Z 3.输入正整数n,计算并输出n! 4.将一个正整数倒序输出 5.打印九九乘法表 6.输出三角形、菱形 7.continue语句和break语句 8.输出100-200之间不能被3整除的数 9.求Pi的值,根据 10.求斐波那契数列前k项的值 11.判断一个数是否为素数 12.输出特定

    2023年04月08日
    浏览(42)
  • java基础课后习题答案

    一、 1.对象 2.面向对象、跨平台性 3.javac 4.Java虚拟机(或JVM) 5.JRE 二、 1.错 2.错 3.错 4.对 5.对 三、 1.C 2.ABCD 3.D 4.ABD 5.D 四、 1.简答性、面向对象、安全性、跨平台性、支持多线程、分布性。 2. Java程序运行时,必须经过编译和运行两个步骤。首先将后

    2024年01月21日
    浏览(47)
  • 《python语言程序设计基础》(第二版)第六章课后习题参考答案

    第六章 组合数据类型 6.1 随机密码生成 6.2 重复元素判定 6.3 重复元素判定续 6.4 文本字符分析 6.5 生日悖论分析 6.6 《红楼梦》人物统计 注:上述代码仅供参考,若有问题可在评论区留言! 《红楼梦》及人物名单TXT (百度云链接失效可在评论区留言) 链接:https://pan.baidu.c

    2024年02月05日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包