输入与输出-2.2文本输入与输出

2.2 文本输入与输出

在保存数据时,可以选择二进制格式或文本格式。例如,整数1234存储成二进制数时,它被写为由字节00 00 04 D2构成的序列(十六进制表示法),而存储成文本格式时,它被存成了字符串“1234”。尽管二进制格式的I/O高速且高效,但不宜人阅读。
在存储文本字符串时,需要考虑字符编码方式。OutputStreamWriter类将使用选定的字符编码方式,把Unicode码元的输出流转换为字节流。而InputStreamReader类将包含字节(用某种字符编码方式表示的字符)的输入流转换为可以产生Unicode码元的读入器。
例如,下面代码就展示了如何让一个输入读入器可以从控制台读入键盘敲击信息,并将其转换为Unicode:

Reader in = new InputStreamReader(System.in);

这个输入流读入器会假定使用主机系统所使用的默认字符编码方式。所以应该总是在InputStreamReader的构造器中选择一种具体的编码方式。例如:

Reader in = new InputStreamReader( new FileInpurStream("data.txt"),StandardCharsets.UTF_8);

2.2.1 如何写出文本输出

对于文本输出,可以使用PrintWriter,这个类拥有以文本格式打印字符串和数字的方法,他还有一个将PrintWriter链接到FileWriter的便捷方法,下面的语句:

PrintWriter out = new PrintWriter("employee.txt","UTF-8");

等同于:
写到文本文件中,但不会打印到控制台中输出

PrintWriter out = new PrintWriter(
	new FileOutputStream("employee.txt"), "UTF-8");

为了输出到打印写出器,需要使用与使用System.out时相同的print,println和printf方法。可以打印数字、字符、boolean值、字符串和对象。

String name = "Harry";
double salary = 7500;
out.print(name);
out.print(' ');
out.println(salary);

println方法在行中添加了对目标系统来说恰当的结束符。如果写出器设置为自动冲刷模式,那么只要println被调用,缓冲区中的所有字符都会被发送到他们的目的地(打印写出器总是带缓冲区的)。在默认情况下,自动冲刷机制是禁用的,你可以通过使用PrintWriter(Writer out, Boolean autoFlush)来启用或禁用自动冲刷机制:

PrintWriter out = new PrintWriter(
	new OutputStreamWriter(
		new FileOutputStream("employee.txt"),"UTF-8"),
	true);
	//这里以及上面PrintWrite在编译器中只能有一个参数,请大佬指教!

priint方法不抛出异常,你可以调用checkError方法来查看输出流是否出现了某些错误。

2.2.2如何读入文本输入

最简单的处理任意文本的方式就是广泛使用的Scanner类。我们可以从任何输入流中构建Scanner类。
或者,我们可以将短小的文本文件像下面这样读入到一个字符串中:

String content = new String(Files.readAllBytes(path),charset);

但是,如果想要将这个文件一行行的读入,那么可以调用:

List<String> lines = Files.lones(path,charset))

如果文件太大,那么可以将行惰性处理为一个Stream< String >对象:

try(Stream<String> lines = Files.lines(path,charset))
{
	...
}

在早期的java版本中。处理文本输入的唯一方法就是通过BufferedReader类。它的readLine方法会产生一行文本,或者在无法获得更多输入时返回null。典型的输入循环看起来像下面这样:

InputStream inputStream =  ...;
try (BufferedReader in = new BufferedReader(new InputStreamReader(
					inputStream,StandardCharset.UTF_8)));
{
	String line;
	while((line = in.readLine())!= null)
	{
		do something
		}
}

如今,BufferedReader类又有了一个lines方法,可以产生一个Stream< String >对象。但是,与Scanner不同,BufferedReader没有用于任何读入数字的方法。

2.2.3 以文本格式存储对象

在本节中,将会用一个Employee记录数组存储成了 一个文本文件,其中每条记录都保存成单独的一行,而实例字段彼此之间使用分隔符分离开,这里我们是用“|”作为分隔符(假设不会发生要储存的字符串中存在|的情况)。
写出记录相当简单,因为我们是要写到一个文本文件中,所以我们使用PrintWriter类。我们直接写出所有的字段,每个字段后面跟着一个|,而最后一个字段的后面跟着一个\n。这项工作是在下面这个添加到Employee类中的WriterEmployee方法里完成的:

public static void writeEmployee(PrintWriter out, Employee e)
{
	out.println(e.getName()+"|"+e.getSalary+"|"+e.getHireDay());
	}

为了读入记录,我们每次读入一行,然后分离所有的字段。我们使用一个扫描器来读入每一行,然后用String.spilt方法将这一行断开成一组标记。

	public static Employee readEmployee(Scanner in)
	{
		String line = in.nextLine();
		String[]tokens = line.split("\\|");
		String name = tokens[0];
		double salary = Double.parseDouble(tokens[1]);
		LocalDate hireDate = LocalDate.parse(tokens[2]);
		int year = hireDate.getYear();
		int month = hireDate.getMonthValue();
		int day = hireDate.getDayOfMonth();
		return new Employee(name,salary,year,month,day);
	}

split方法的参数是一个描述分隔符的正则表达式,我们在本章末尾将详细讨论正则表达式。|在正则表达式中具有特殊的含义,因此需要用\字符来表示转义,而这个\又需要另一个\来转义,所以就有了“\|”表达式。

int n = in.nextInt();
		in.nextLine();
		
		Employee[] employees = new Employee[n];
		for(int i =0;i<n;i++)
		{
			employees[i] = readEmployee(in);
		}

对nextInt的调用读入的是数组长度,但不包括行位的换行字符,我们必须处理掉这个换行符,这样,在调用nextLine方法后,readData方法就可以获得下一行输入了。(代码在文末)

2.2.4字符的编码方式

输入和输出流都是用于字节序列的,但是在许多情况下,希望操作的是文本,即字符序列。
Java针对字符使用的是Unicode标准。每个字符或“编码点”都具有一个21位的整数。
最常见的编码方式是UTF-8,他会将每个Unicode编码点编码为1到4个字节的序列。UTF-8的好处是传统的包含了英语中用到的所有字符的ASCII字符集中的每个字符都只会占用一个字节。
另一种常见的编码方式是UTF-16他会将每个Unicode编码点编码为1个或2个16位值。有两种形式的UTF-16,被称为“高位优先”和“低位优先”。为了表示使用的是哪一种格式,文件可以以“字节顺序标记”开头,读入器可以使用这个值来确定字节顺序,然后丢弃它。

不存在任何可靠的方式可以自动地探测出字节流中所使用的字符编码方式。某些API方法使用 “默认字符集”,即计算机的操作系统首选的字符编码方式。这种字符编码方式与我们的字节源中所使用的编码方式相同吗?字节源中的字节可能来自世界上的其他国家或地区,因此,应该明确指定编码方式。例如,在编写网页时,应该检查Content-Type头信息。

StandardCharsets类具有类型为Charset的静态变量,用于表示每种java虚拟机都必须支持的字符编码方式:

StandardCharsets.UTF_8
StandardCharsets.UTF_16
StandardCharsets.UTF_16BE
StandardCharsets.UTF_16FE
StandardCharsets.ISO_8859_1
StandardCharsets.US_ACSII

为了获得另一种编码方式的Charset对象,可以使用静态的forName方法:

Charset shiftJIS = Charset.forName("Shift-JIS");

再读入或写出文本时,应该使用Charset对象。例如,我们可以像下面这样讲一个字节数组转换成字符串:

String str = new String(bytes, StandardCharasets.UTF_8);

2.2.3 代码

package JavaSE8的流库;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDate;
import java.util.Scanner;

public class TextFileTest {

	public static void main(String[] args) throws IOException {
		// TODO 自动生成的方法存根

		Employee[]staff =new Employee[3];
		staff[0] = new Employee("Clar Cracker", 75000.0, 1987, 12, 15);
		staff[1] = new Employee("Harry Hacker", 5000.0, 1989, 10, 1);
		staff[2] = new Employee("Tony Tester", 4000.0, 1990, 3, 15);
		
		try(PrintWriter out = new PrintWriter("employee.dat","UTF-8"))
		{
			writeData(staff,out);
		}
		
		try(Scanner in = new Scanner(
				new FileInputStream("employee.dat"),"UTF-8"))
		{
			Employee[] newStaff = readData(in);
			
			for(Employee e:newStaff)
				System.out.println(e);
		}
	}
	
	private static void writeData(Employee[] employee, PrintWriter out)throws IOException
	{
		out.println(employee.length);
		
		for(Employee e: employee)
			writeEmployee(out,e);
	}
	
	private static Employee[] readData(Scanner in)
	{
		int n = in.nextInt();
		in.nextLine();
		
		Employee[] employees = new Employee[n];
		for(int i =0;i<n;i++)
		{
			employees[i] = readEmployee(in);
		}
		return employees;
	}
	
	public static void writeEmployee(PrintWriter out, Employee e)
	{
		out.println(e.getName()+"|"+e.getSalary()+"|"+e.getHireDay());
	}
	
	public static Employee readEmployee(Scanner in)
	{
		String line = in.nextLine();
		String[]tokens = line.split("\\|");
		String name = tokens[0];
		double salary = Double.parseDouble(tokens[1]);
		LocalDate hireDate = LocalDate.parse(tokens[2]);
		int year = hireDate.getYear();
		int month = hireDate.getMonthValue();
		int day = hireDate.getDayOfMonth();
		return new Employee(name,salary,year,month,day);
	}

}

class Employee{
	private String name;
	private Double salary;
	private int year;
	private int month;
	private int day;
	
	public Employee(String name, Double salary, int year, int month, int day)
	{
		this.name = name;
		this.salary = salary;
		this.year = year;
		this.month = month;
		this.day = day;
	}
	
	public String getName() {
		return name;
	}

	public Double getSalary() {
		return salary;
	}

	public LocalDate getHireDay() {
		return LocalDate.of(year, month, day);
	}
}

运行结果:
在这里插入图片描述
在这里插入图片描述
如有问题,请评论指正Thanks♪(・ω・)ノ

猜你喜欢

转载自blog.csdn.net/z036548/article/details/84346123