第七章 数据输入与输出
7.1 了解Java的输入与输出
一、什么是输入流与输出流
流是按一定顺序排列的数据集合。例如,从键盘或文件输入的数据,向显示器或文件输出的数据等都可以看作是一个个的数据流。
输入数据时,一个程序打开数据源上的一个流(文件或内存等),然后按照顺序输入这个流中的数据,这样的流称为输入流。
输出数据时,一个程序可以打开一个目的地的流(如文件或内存等),然后按顺序向这个目的地输出数据,这样的流称为输出流。
二、什么是字节流与字符流
输入/输出流根据处理数据的类型不同可分为两类:一类是字节流,另一类是字符流。字节流表示按照字节的形式读/写数据,字符流表示按照字符的形式读/写数据。
在Java中,抽象类InputStream和OutputStream及其派生子类用来处理字节流的输入与输出,抽象类Reader和Writer及其派生子类用来处理字符流的输入与输出。
三、标准输入/输出类System
System类属于java.lang包,它提供的一些方法可用来终止当前正在运行的Java虚拟机,运行垃圾回收器,以及获取指定的环境变量值等。
System类有两个非常有用的静态成员常量in和out,分别表示标准输入设备(一般为键盘)和标准输出设备(一般为显示器)。
in:声明形式为public static final InputStream in。使用时,我们可以利用System.in.read()方法从键盘读入字节数据。不过,System.in更经常被作为其他对象的参数,表示将键盘输入的数据作为其数据源。
out:声明形式为public static final PrintStream out。使用时,我们可以利用System.out.print (“字符串”);语句和System.out.println(“字符串”);语句在显示器上显示各种类型的数据。
// SystemIOExample1.java
package Chapter7;
import java.io.IOException;
public class SystemIOExample1 {
public static void main(String[] args) throws IOException {
int b;
System.out.println("请输入数据:");
// 循环读取数据,遇到输入字符'N'终止循环
while ((b = System.in.read()) != 'N') {
System.out.print((char) b);
}
}
}
四、字节与字符输入/输出流类
1.字节输入流类InputStream
字节输入流类InputStream用于以字节形式从数据源中读取数据,它是所有字节输入流类的父类。此外,由于该类是抽象类,因而不能被实例化,即无法基于该类创建对象。
InputStream
ByteArrayInputStream
FileInputStream
FilterInputStream
BufferedInputStream
DataInputStream
LineNumberInputStream
PushbackInputStream
ObjectInputStream
PipedInputStream
SequenceInputStream
StringBufferInputStream
提示:首先打开Windows的“记事本”程序,在其中输入“These data are from d:\test.txt
// SystemIOExample2.java
package Chapter7;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class SystemIOExample2 {
public static void main(String args[]) throws IOException {
// 定义一个文件输入流对象,其内容源自d:\test.txt文件
FileInputStream in = new FileInputStream("d:\\test.txt");
// 定义一个缓冲输入流对象,其内容源自文件输入流
BufferedInputStream bufin = new BufferedInputStream(in);
// 定义一个缓冲输入流对象,其内容源自键盘
BufferedInputStream keyin = new BufferedInputStream(System.in);
// 定义两个字节数组
byte[] b1 = new byte[1024], b2 = new byte[1024];
// 将从文件中读取的数据放入字节数组b1内,num1为读入的字节个数
int num1 = bufin.read(b1);
// 将字节数组转换成字符串
String str1 = new String(b1, 0, num1);
System.out.println(str1);
// 关闭缓冲输入流,同时关闭了文件
bufin.close();
// 将从键盘读取的数据放入字节数组b2内,num2为读入的字节个数
int num2 = keyin.read(b2);
// 将字节数组转换成字符串
String str2 = new String(b2, 0, num2);
System.out.println(str2);
}
}
2.字节输出流类OutputStream
字节输出流类OutputStream用于以字节形式将数据写入目的地,其主要派生子类包括:FileOutputStream(将数据写入文件)、PrintStream(用于输出各种类型的数据,如整数、浮点数、字符、字符串、布尔值)等。
OutputStream
ByteArrayOutputStream
FileOutputStream
FilterOutputStream
BufferedOutputStream
DataOutputStream
PrintStream
ObjectOutputStream
PipedOutputStream
// SystemIOExample3.java
package Chapter7;
import java.io.*;
public class SystemIOExample3 {
public static void main(String[] args) throws IOException {
// 创建文件输出字节流对象,其目标为d:\test.txt文件。如果该文件
// 已存在,则删除并新建该文件;否则,将新建该文件
FileOutputStream out = new FileOutputStream("d:\\test.txt");
// 创建缓冲输出字节流对象,其目标为文件输出字节流对象
BufferedOutputStream buffer_out = new BufferedOutputStream(out);
// 要写入文件的内容
String s = "These will be writed to d:\\test.txt file!\r\n";
// 将字符串s的内容以字节形式写入缓冲区buffer_out。实际上,也就间接
// 写入到了d:\test.txt文件中。getBytes()为String类的方法,其作用是将
// 字符串转换为字节数组
buffer_out.write(s.getBytes());
buffer_out.write(s.getBytes());
buffer_out.write(s.getBytes());
buffer_out.write(s.getBytes());
buffer_out.flush(); // 清空缓冲区
buffer_out.close(); // 关闭缓冲输入流,同时关闭了文件
// 创建文件输入流,其内容源自文件d:\test.txt
FileInputStream in = new FileInputStream("d:\\test.txt");
// 将文件输入流包装成缓冲流,即把文件内容首先读入缓冲区
BufferedInputStream buffer_in = new BufferedInputStream(in);
byte[] b = new byte[1024];
// 将数据读入字节数组b内,num为读入的字节个数
int num = buffer_in.read(b);
// 将字节数组转换成字符串
String str = new String(b, 0, num);
System.out.println(str);// 输出读入的结果
buffer_in.close();// 关闭缓冲输入流,同时关闭了文件
}
}
3.字符输入流类Reader
字符输入流类Reader用于以字符形式从数据源中读取数据,其主要派生子类包括InputStreamReader(读取字节数据并将其解码为字符)、FileReader(用来读取字符文件的内容)、BufferedReader(从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取)等。
OutputStream
ByteArrayOutputStream
FileOutputStream
FilterOutputStream
BufferedOutputStream
DataOutputStream
PrintStream
ObjectOutputStream
PipedOutputStream
4.字符输出流类Writer
字符输出流类Writer用于以字符的形式将数据写入目的地。Writer类是所有字符输出流类的父类,其主要派生子类包括OutputStreamWriter(将字符以字节形式写入输出流)、FileWriter(将字符数据写入文件)、BufferedWriter(将字符数据写入缓冲区)、PrintWriter(格式化输出字符数据)等子类。
Writer
BufferedWriter
CharArrayWriter
FilterWriter
OutputStreamWriter
FileWriter
PipedWriter
PrintWriter
StringWriter
实例7-1 利用InputStreamReader类和BufferedReader类输入数据
【实例描述】
利用InputStreamReader类和BufferedReader类从键盘输入数据,如果数据格式无效的话,可通过捕捉异常提示用户数据输入有误,必须重新输入。
【技术要点】
利用InputStreamReader类创建对象iin,其内容来自键盘(即System.in),利用其read()方法可读取单个字符。
利用BufferedReader类创建对象stdin,其内容来自iin,即把从键盘输入的数据保存到缓冲区,从而提高数据的高效读取。利用其read()方法可读取单个字符,利用其readLine()方法可读取一行。
// SystemIOExample4.java
// 从命令行读入字符串,并处理和显示
package Chapter7;
import java.io.*;
public class SystemIOExample4 {
public static void main(String[] args) throws IOException {
// 使用System.in构造InputStreamReader对象iin
// 该对象用来从键盘读入一个单字节字符
InputStreamReader iin = new InputStreamReader(System.in);
// 利用iin对象构造BufferedReader对象stdin
// 该对象用来从字符输入流中读取文本到缓冲区
BufferedReader stdin = new BufferedReader(iin);
// 读取并输出字符串。
System.out.print("请输入一个字符串: ");
System.out.println(stdin.readLine());
boolean dataright = false;
do {
try {
// 读取字符串并转换成double类型数据输出
System.out.print("请输入一个浮点数: ");
// 将字符串解析为带符号的double类型数据。如果数据无效,则
// 产生一个NumberFormatException异常
double numberx = Double.parseDouble(stdin.readLine());
System.out.println(numberx);
dataright = false;
} catch (NumberFormatException e) {
System.out.println("数据无效,请重新输入!");
dataright = true;
}
} while (dataright);
}
}
五、使用Scanner类输入各种类型的数据
Scanner类是一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。
nextByte()、nextShort()、nextInt()、nextLong()、nextFloat()、nextDouble()、nextBoolean()等方法分别用来读取字节、短整型、整型、长整型、浮点数、双精度浮点数和布尔值等。
hasNextByte()、hasNextShort()、hasNextInt()、hasNextLong()、hasNextFloat()、hasNextDouble()、hasNextBoolean()等方法分别用来判断要读入的数据是否是字节、短整型、整型、长整型、浮点数、双精度浮点数或布尔值等。
nextLine()方法用于读取一行数据,如果已用nextByte()、nextShort()等方法读取数据,此方法用于读取当前行中后续数据;hasNextLine()方法用于确认是否还有下一行数据,此方法主要针对文件,用于判断是否到达文件结尾处。
实例7-2 利用Scanner类输入一组浮点数
【实例描述】
利用Scanner类的nextFloat()方法读取浮点数,利用hasNextFloat()方法判断是否结束数据读取。如果当前读取的数据不是浮点数,则hasNextFloat()方法返回false,否则返回true。
【技术要点】
基于Scanner类创建对象时,其数据源为System.in,表示来自键盘。
系统在遇到Scanner类的hasNextFloat()方法时,会等待用户输入数据(可以在一行中输入一个或多个数据。如输入多个数据,各数据之间以空格分隔)并按回车键确认,然后会通过正则表达式匹配来确认第一个数是否是浮点数。
// SystemIOExample5.java
package Chapter7;
import java.io.*;
import java.util.*;
public class SystemIOExample5 {
public static void main(String[] args) throws IOException {
float numberx = 0; // 输入的浮点数
// 创建Scanner对象in,其内容来自InputStream类对象System.in,即键盘输入
Scanner in = new Scanner(System.in);
System.out.println("请输入一组浮点数,最后以任意非数" +
"字字符串或按Ctrl+Z结束输入! ");
// 如果读入的数是浮点数,则循环读
while (in.hasNextFloat()) {
// 读入浮点数并输出
numberx = in.nextFloat();
System.out.print(numberx + " ");
}
}
}
7.2 掌握文件的读写与管理方法
一、文件字节输入流/输出流
文件字节输入/输出流是指FileInputStream和FileOutputStream类,它们实现了对文件的顺序访问,并以字节为单位进行读/写操作。
在Java中,对文件的读/写操作主要步骤是:① 创建文件输入/输出流对象,此时文件自动打开或创建;② 用文件读写方法读写数据;③ 关闭数据流,同时关闭了文件。
1.FileInputStream类
FileInputStream(String name)
FileInputStream(File file)
其中,name表示要打开的文件名,file表示文件类File的对象。
例如:
FileInputStream in=new FileInputStream("d:\\test.txt");
2.FileOutputStream类
FileOutputStream(String name)
FileOutputStream(String name,boolean append)
FileOutputStream(File file)
其中,name表示要新建并打开的文件名;参数append的值为true时,表示在原文件的尾部添加数据,否则将覆盖原文件的内容;file表示文件类File对象。
例如:
File myfile=new File("d:\\test.txt ");
// 基于File类对象myfile创建文件输出流类对象fis,新建//d:\test.txt文件
FileOutputStream fis=new FileOutputStream (myfile);
实例7-3 利用字节输入/输出流读写文件
【实例描述】
使用文件字节输入/输出流类将数值0~10写入文件test.txt,然后再把它们从文件中读取出来。
【技术要点】
通过执行FileOutputStream out = new FileOutputStream(“d:\test.txt”);语句创建FileOutputStream对象时,系统会自动在D盘根目录下创建一个名为text.txt的空文件。如果该文件已存在,系统会先删除原文件,再创建新文件。
如果用户希望打开一个文件,然后向其中追加内容,应执行FileOutputStream out = new FileOutputStream(“d:\test.txt”, true);语句。如果此时目标文件已存在,则打开它;否则,将新建文件。
// FileIOExample1.java
package Chapter7;
import java.io.*;
public class FileIOExample1 {
public static void main(String[] args) throws IOException {
// 创建文件输出流对象,新建并打开文件d:\test.txt
FileOutputStream out = new FileOutputStream("d:\\test.txt");
for (int i = 0; i <= 10; i++) {
out.write(i); // 向文件中写数据
}
out.close(); // 关闭输出流,即关闭打开的文件
// 创建文件输入流对象,打开文件d:\test.txt
FileInputStream in = new FileInputStream("d:\\test.txt");
int value;
while ((value = in.read()) != -1) { // 循环读取文件中的数据
System.out.print(value + " "); // 输出文件中的数据
}
in.close(); // 关闭输入流,即关闭打开的文件
}
}
二、文件字符输入/输出流
文件字符输入/输出流是指FileReader类和FileWriter类,它们分别用于从文件中读取字符数据,或将数据以字符形式写入文件。
FileReader(File file)
FileReader(String filename)
FileWriter(String filename)
FileWriter(String filename, boolean append)
FileWriter(File file)
FileWriter(File file, boolean append)
其中,file是文件类File的对象;filename是要打开的文件名;当参数append的值为true时,表示在原文件的末尾添加数据,否则将覆盖原文件的内容。
实例7-4 利用字符输入/输出流读写文件
【实例描述】
使用文件字符输入/输出流类将键盘输入的字符写入文件test.txt,以空行或按【Ctrl+Z】组合键结束数据输入,然后再把它们从文件中读取出来。
【技术要点】
使用InputStreamReader iin = new InputStreamReader(System.in);语句和BufferedReader stdin = new BufferedReader(iin);语句创建stdin对象后,可利用stdin对象的readLine()方法读取一行输入的内容。其中:
如果当前行未输入内容,直接按回车键,此时输入字符串的
length()为0。但是,此时输入的字符串既不是空串“”,也不是
null。
如果按【Ctrl+Z】组合键,此时系统会忽略该行输入的内容,读
取的字符串为null。
// FileIOExample2.java
package Chapter7;
import java.io.*;
public class FileIOExample2 {
public static void readFile() throws IOException {
// 创建文件字符输入流对象,即打开一个文件
FileReader fr = new FileReader("d:\\test.txt");
// 定义一个字符数组
char data[] = new char[1024];
// 将数据读入字符数组data内,num为字符个数
int num = fr.read(data);
// 将字符数组转换成字符串
String str = new String(data, 0, num);
System.out.println(str);
fr.close(); // 关闭文件
}
public static void writeFile(String s) throws IOException {
// 创建文件字符输出流对象,即打开或新建一个文件
FileWriter fw = new FileWriter("d:\\test.txt", true);
fw.write(s); // 将字符串s写入文件
fw.close(); // 关闭文件
}
public static void main(String[] args) throws IOException {
// 创建InputStreamReader对象,其内容来自键盘
InputStreamReader iin = new InputStreamReader(System.in);
// 基于InputStreamReader对象创建BufferedReader对象
BufferedReader stdin = new BufferedReader(iin);
// s1用于临时保存读取的当前行内容,S2用来存放最终读取的字符串
String s1 = "", s2 = "";
// 循环读取数据,
do {
s1 = stdin.readLine(); // 读取一行
System.out.println(s1);
// 当用户按Ctrl+Z组合键时,readLine()方法返回的是一个null
// 同时,本行输入的内容作废。因此,此时应退出数据输入循环
if (s1 == null) {
break;
}
// 如果在某行直接按回车,不输入任何内容,此时用readLine()方法
// 读取的字符串的length()为0,结束数据输入。反之,如果length()
// 不为0,表示该行已输入字符串
if (s1.length() != 0) { // 如果已输入字符串
s1 = s1 + '\r' + '\n'; // 为字符串末尾增加回车符和换行符
s2 = s2 + s1; // 将新字符串增加到目标字符串中
}
} while (s1.length() != 0);
writeFile(s2); // 将读取的内容写文件
readFile(); // 读取文件内容
}
}
Are you ready? 你准备好了吗?
Are you ready? 你准备好了吗?
yes 是的
yes 是的
今天几号?
今天几号?
25
25
程序的运行结果
Are you ready? 你准备好了吗?
yes 是的
今天几号?
25
三、利用File类管理文件
1.构造方法
File类既可以表示文件又可以表示目录,它提供了操作文件或目录的一组方法 。
File(String pathname):通过将给定的路径名字符串转换为抽
象路径名来创建一个新 File 实例。
File(String parent, String child):根据 parent 路径名字符串和
child 路径名字符串创建一个新 File 实例。
File(File parent, String child) :根据 parent 抽象路径名和 child
路径名字符串创建一个新 File 实例。例如:
// 用文件名test.txt创建文件对象f,该文件所在路径是当前工作路径
File f=new File("test.txt");
// 用路径c:\aa和文件名test1.txt创建文件对象f1 (c:\aa\test1.txt)
File f1=new File("c:\\aa","test1.txt");
// 用文件对象f1的内容(c:\aa\test1.txt)作为父目录,然后和文件名//test2.txt组合,创建文件对象f3(c:\aa\test1.txt\test2.txt)
File f2=new File(f1,"test2.txt");
2.方法的修饰符
public String getName():返回此抽象路径名表示的文件或目录
的名称。
public String getPath():将此抽象路径名转换为一个路径名字符
串。
public String getAbsolutePath():返回此抽象路径名的绝对路径
名字符串。
public String getParent():返回此抽象路径名父目录的路径名字
符串。如果此路径名没有指定父目录,则返回 null。
3.抽象路径查询
public boolean exists():测试此抽象路径名表示的文
件或目录是否存在。如果存在,返回true;否则,
返回false。
public boolean isDirectory():测试此抽象路径名表示
的是否是一个目录。如果是,返回true;否则,返回
false。
public boolean isFile():测试此抽象路径名表示的是
否是一个标准文件。如果是,返回true;否则,返
回false。
public boolean isHidden():测试此抽象路径名表示的
是否是一个隐藏文件。如果是,返回true;否则,返
回false。
4.文件与目录操作
public boolean cteateNewFile():当且仅当不存在此抽象路径名
指定的文件时,创建一个新的空文件。如果指定的文件不存在
且创建成功,返回true;否则,返回false。
public boolean mkdir():创建此抽象路径名指定的目录。如果
创建成功,返回true;否则,返回false。如果路径名有多层,
必须确保前面路径有效。
public boolean delete():删除此抽象路径名表示的文件或目
录。如果此路径名表示一个目录,则该目录必须为空才能删
除。当且仅当成功删除文件或目录时,返回 true;否则返回
false。
public String[] list():列出此抽象路径路径名表示的目录中的文
件和目录。如果此抽象路径名表示的不是一个目录,那么,此
方法将返回null;否则将返回一个字符串数组,每个数组元素
对应目录中的一个文件或目录。
实例7-5 利用File类创建、删除目录和文件
【实例描述】
创建一个File类对象,然后分别调用其各种方法创建目录和文件,然后再删除它们。
【技术要点】
(1)和创建FileOutputStream、FileWriter对象时可以自动创建文件不同,创建File对象时不会创建文件。
(2)要利用createNewFile()方法创建文件,必须确保目录存在。
(3)如果目录有多层,要利用mkdir()方法创建目录,必需确保父目录存在,系统才能在父目录下创建目录。也就是说,系统不能一次创建多级目录。
// FileIOExample3.java
package Chapter7;
import java.io.*;
public class FileIOExample3 {
public static void main(String[] args) throws IOException {
File dir = new File("c:\\xyz"); // 创建file对象
if (!dir.exists()) { // 判断c:\xyz目录是否存在
dir.mkdir(); // 如果不存在,则创建目录
}
File file = new File("c:\\xyz", "test.txt"); // 创建file对象
if (!file.exists()) { // 判断c:\abc目录下,文件test.txt是否存在
file.createNewFile(); // 如果不存在,则创建一个新文件
}
System.out.println("文件路径:" + file.getAbsolutePath()); // 输出文件路径
if (file.exists()) { // 判断c:\abc目录下,文件test.txt是否存在
file.delete(); // 如果存在,则删除之
}
if (dir.exists()) { // 判断c:\xyz目录是否存在
dir.delete(); // 如果存在,则删除之
}
if (dir.exists()) { // 判断c:\xyz目录是否存在
System.out.println("c:\\xyz目录存在!");
} else {
System.out.println("c:\\xyz目录不存在!");
}
}
}
四、使用RandomAccessFile类随机读写文件
字节输入/输出流和字符输入/输出流都是顺序地读/写文件的,而RandomAccessFile类称作随机存取文件类,它提供了随机访问文件的方法。
RandomAccessFile类与输入/输出流类相比,有两点不同:
RandomAccessFile类直接继承了对象类Object,同时实现了DataInput接口和DataOutput接口,所以RandomAccessFile类既可以作为输入流,又可以作为输出流。
RandomAccessFile类之所以允许随机访问文件,是由于它定义了 一个文件当前位置指针,文件的存取都是从文件当前位置指针指示的位置开始的。通过移动这个指针,就可以从文件的任何位置开始进行读/写操作。与此同时,系统在从文件中读取数据或向文件中写入数据时,位置指针会自动移动。
1.构造方法
RandomAccessFile(File file, String mode)
RandomAccessFile(String name, String mode)
其中,file是一个文件对象;mode是访问方式,有三个值:r(读),w(写),rw(读写)。
2.常用方法
public long getFilePointer():返回文件指针的位置。
public long length():返回文件的长度。
public void seek(long pos):将文件指针移到pos位置处。
public int skipBytes(int n):使文件指针跳过n个字节。
public void close():关闭此随机访问文件流并释放与该流关联的
所有系统资源。
public int read():从此文件中读取一个数据字节。以整数形式返
回此字节,范围在 0 到 255(0x00-0x0ff)之间。
public void write(int b):从当前文件指针位置开始,向此文件写入
指定的字节。
实例7-6 利用RandomAccessFile类随机读写文件
【实例描述】
创建一个RandomAccessFile类对象,然后向其中写入一组字符和整数,然后再将它们读出来。
【技术要点】
(1)了解每种数据类型数据所占字节数,以及使用哪些方法将其写入和读出文件。
(2)了解文件指针的意义和定位方法。
// FileIOExample4.java
package Chapter7;
import java.io.*;
public class FileIOExample4 {
public static void main(String[] args) throws IOException {
File file = new File("d:\\test.txt");
// 如果文件已存在,则删除后重新创建一个空文件
if (file.exists()) { // 判断文件是否存在
file.delete(); // 如果存在,则删除文件
}
file.createNewFile(); // 创建新文件
// 以读写方式创建RandomAccessFile对象
RandomAccessFile rafile = new RandomAccessFile(file, "rw");
// 向文件中写入字符串,每个字符占两个字节
rafile.writeChars("Are you ready? 你准备好了吗?");
// 记录当前指针的位置
long p1=rafile.getFilePointer();
// 输出文件指针的位置
System.out.println("当前文件指针位置:" + p1);
// 向文件中写入一个长整型数和一个浮点数
rafile.writeLong(2345);
rafile.writeFloat(890.7F);
// 输出文件中的字符串
for (int i = 0; i < p1; i = i + 2) {
rafile.seek(i); // 定位指针
// 输出文件指针所在位置的字符
System.out.print(rafile.readChar());
}
// 输出写入的长整数和浮点数
System.out.println();
System.out.println(rafile.readLong());
System.out.println(rafile.readFloat());
}
}
本章小结
本章首先介绍了输入流、输出流、字节流和字符流的概念,然后依次介绍了使用System类完成数据基本输入/输出的方法,使用字节输入/输出流类(InputStream和OutputStream)和字符输入/输出流类(Reader和Writer)以字节或字符形式输入/输出数据的方法,使用Scanner类输入各种类型数据的方法。
在7.2中,我们主要介绍了使用FileInputStream类和FileOutputStream类、FileReader类和FileWriter类,分别以字节形式或字符形式读写文件的方法。以及使用File类管理文件的方法,使用RandomAccessFile类随机读写文件的方法。