输入流与输出流
什么是输入/输出流
输入就是将数据从各种输入设备(包括文件、键盘等)中读取到内存中,输出则正好相反,是将数据写入到各种输出设备(比如文件、显示器、磁盘等)
数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。
- 按照流的方向主要分为输入流和输出流两大类。
- 数据流按照数据单位的不同分为字节流和字符流。
- 按照功能可以划分为节点流和处理流。
输入流
Java 流相关的类都封装在 java.io 包中,而且每个数据流都是一个对象。所有输入流类都是 InputStream 抽象类(字节输入流)和 Reader 抽象类(字符输入流)的子类。其中 InputStream 类是字节输入流的抽象类,是所有字节输入流的父类,其层次结构如图 3 所示。
输出流
在 Java 中所有输出流类都是 OutputStream 抽象类(字节输出流)和 Writer 抽象类(字符输出流)的子类。其中 OutputStream 类是字节输出流的抽象类,是所有字节输出流的父类,其层次结构如图 4 所示。
==============================================================
===========================================================
Java系统流
每个 Java 程序运行时都带有一个系统流,系统流对应的类为 java.lang.System。Sytem 类封装了 Java 程序运行时的 3 个系统流,分别通过 in、out 和 err 变量来引用。这 3 个系统流如下所示:
- System.in:标准输入流,默认设备是键盘。
- System.out:标准输出流,默认设备是控制台。
- System.err:标准错误流,默认设备是控制台。
System.in 是 InputStream 类的一个对象,因此上述代码的 System.in.read() 方法实际是访问 InputStream 类定义的 read() 方法。
System.out 和 System.error 是 PrintStream 类的对象。因为 PrintStream 是一个从 OutputStream 派生的输出流,所以它还执行低级别的 write() 方法。因此,除了 print() 和 println() 方法可以完成控制台输出以外,System.out 还可以调用 write() 方法实现控制台输出。
注意: 在实际操作中,print() 方法和 println() 方法比 write() 方法更常用。
代码示例:
public class SystemIo {
public static void main(String[] args) {
byte[] byteDate = new byte[100]; //声明一个字节数组
System.out.println("请输入英文:");
try{
System.in.read(byteDate);
}catch(IOException e){
e.printStackTrace();
}
System.out.println("你输入的内容如下:");
for(int i=0;i<=byteDate.length;i++){
System.out.write(byteDate[i]);
}
System.err.print("you are right");
}
}
=================================================================
=================================================================
字符编码
计算机中,任何的文字都是以指定的编码方式存在的,在 Java 程序的开发中最常见的是 ISO8859-1、GBK/GB2312、Unicode、 UTF 编码。
Java 中常见编码说明如下:
- ISO8859-1:属于单字节编码,最多只能表示 0~255 的字符范围。
- GBK/GB2312:中文的国标编码,用来表示汉字,属于双字节编码。GBK 可以表示简体中文和繁体中文,而 GB2312 只能表示简体中文。GBK 兼容 GB2312。
- Unicode:是一种编码规范,是为解决全球字符通用编码而设计的。UTF-8 和 UTF-16 是这种规范的一种实现,此编码不兼容 ISO8859-1 编码。Java 内部采用此编码。
- UTF:UTF 编码兼容了 ISO8859-1 编码,同时也可以用来表示所有的语言字符,不过 UTF 编码是不定长编码,每一个字符的长度为 1~6 个字节不等。一般在中文网页中使用此编码,可以节省空间。
public class CharEncoding {
//自己:异常》=3个时就合并成一个父类。
public static void main(String[] args) throws IOException {
//获取系统编码
System.out.println("系统默认编码"+System.getProperty("file.encoding"));
//由于java需要转义,所以\\代表\
File f = new File("C:\\Users\\crystal\\Desktop\\CC.txt");
OutputStream os = new FileOutputStream(f);
// 指定编码
byte b[] = "c你好啊".getBytes("ISO8859-1");
os.write(b);
os.close();
}
}
=================================================================
=================================================================
Java File类(文件操作类)详解
在 Java 中,File 类是 java.io 包中唯一代表磁盘文件本身的对象,也就是说,如果希望在程序中操作文件和目录,则都可以通过 File 类来完成。File 类定义了一些方法来操作文件,如新建、删除、重命名文件和目录等。
File 类提供了如下三种形式构造方法。
- File(String path):如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,则该 File 对象表示的是文件。
- File(String path, String name):path 是路径名,name 是文件名。
- File(File dir, String name):dir 是路径对象,name 是文件名。
File 类中有以下两个常用常量:
- public static final String pathSeparator:指的是分隔连续多个路径字符串的分隔符,Windows 下指"分号"。例如
java -cp test.jar;abc.jar HelloWorld
。 - public static final String separator:用来分隔同一个路径字符串中的目录的,Windows 下指“/”。例如
C:/Program Files/Common Files
。
注意:Windows 的路径分隔符使用反斜线“\”,而 Java 程序中的反斜线表示转义字符,所以如果需要在 Windows 的路径下包括反斜线,则应该使用两条反斜线或直接使用斜线“/”也可以。Java 程序支持将斜线当成平台无关的路径分隔符。
例如:D:\javaspace\hello.java要写成D:/javaspace/hello.java
或者 D:\\javaspace\\hello.java
。
获取文件属性
第一步是先创建一个 File 类对象并指向一个已存在的文件
public class FileIo {
public static void main(String[] args) {
String path ="C:\\Users\\crystal\\Desktop";//指定文件目录
File f = new File(path,"CC.txt");//建立File变量
System.out.println("C:\\Users\\crystal\\Desktop/CC.txt文档信息如下:");
System.out.println("================================================");
System.out.println("文件长度:"+f.length()+"字节");
System.out.println("文件或者非文件:"+(f.isFile() ? "是文件" : "不是文件"));
System.out.println("目录或者非目录:"+(f.isDirectory() ? "是目录" :"不是目录"));
f.canRead();//可读
f.canWrite();//可写
f.isHidden();//是否隐藏
new Date(f.lastModified());//最后修改时间
f.getName();//文件名
f.getPath();//文件路径
f.getAbsolutePath();//绝对路径
}
}
创建和删除文件
- File f = new File("C:\\test.txt"); // 创建指向文件的File对象
- if (f.exists()) // 判断文件是否存在
- {
- f.delete(); // 存在则先删除
- }
- f.createNewFile(); // 再创建
注意:在不同的操作系统中,路径的分隔符是不一样的,例如:
- Windows 中使用反斜杠
\
表示目录的分隔符。 - Linux 中使用正斜杠
/
表示目录的分隔符。
- String path = "C:" + File.separator + "test.txt"; // 拼凑出可以适应操作系统的路径
- File f = new File(path);
- if (f.exists()) // 判断文件是否存在
- {
- f.delete(); // 存在则先删除
- }
- f.createNewFile(); // 再创建
创建和删除目录
- String path = "C:/config/"; // 指定目录位置
- File f = new File(path); // 创建File对象
- if (f.exists()) {
- f.delete();
- }
- f.mkdir(); // 创建目录
遍历目录
File 类的 list() 方法提供了遍历目录功能,该方法有如下两种重载形式。
1. String[] list():如果调用的 File 对象不是目录,则返回 null。
2. String[] list(FilenameFilter filter):如果 filter 为 null,则接受所有名称。
- File f = new File("C:/"); // 建立File变量,并设定由f变量变数引用
- System.out.println("文件名称\t\t文件类型\t\t文件大小");
- System.out.println("===================================================");
- String fileList[] = f.list(); // 调用不带参数的list()方法
- for (int i = 0; i < fileList.length; i++) { // 遍历返回的字符数组
- System.out.print(fileList[i] + "\t\t");
- System.out.print((new File("C:/", fileList[i])).isFile() ? "文件" + "\t\t" : "文件夹" + "\t\t");
- System.out.println((new File("C:/", fileList[i])).length() + "字节");
- }
假设希望只列出目录下的某些文件,这就需要调用带过滤器参数的 list() 方法。首先需要创建文件过滤器,该过滤器必须实现 java.io.FilenameFilter
接口,并在 accept() 方法中指定允许的文件类型。
String fileList[] = f.list(new ImageFilter());
- public class ImageFilter implements FilenameFilter {
- // 实现 FilenameFilter 接口
- @Override
- public boolean accept(File dir, String name) {
- // 指定允许的文件类型
- return name.endsWith(".sys") || name.endsWith(".txt") || name.endsWith(".bak");
- }
- }
Java动态读取文件内容---扩展
Java RandomAccessFile类:动态读取文件内容
例 1
编写一个程序,使用 RandomAccessFileDemo 类创建一个 words.txt 文件,然后写入一个长中文字符串,再从第 6 个字节开始读取并输出。
1)创建一个 RandomAccessFileDemo 类对象。在 main() 方法中创建到 D:\JavaCodes\words.txt
的 File 对象,如果该文件已经存在则先删除再创建,代码如下所示。
- public class RandomAccessFileDemo {
- public static void main(String[] args) {
- try {
- File file = new File("D:\\myJava\\words.txt"); // 指定文件路径
- if (file.exists()) { // 判断文件是否存在
- file.delete();
- file.createNewFile();
- }
- } catch (IOException e) {
- System.out.print(e);
- }
- }
- }
- RandomAccessFile raf = new RandomAccessFile(file,"rw");
- String str1 = "晴天,阴天,多云,小雨,大风,中雨,小雪,雷阵雨"; // 要写入的字符串
- String str2 = new String(str1.getBytes("GBK"),"ISO-8859-1"); // 编码转换
- raf.writeBytes(str2); //写入文件
- System.out.println("当前文件指针的位置:" + raf.getFilePointer());
- raf.seek(6); // 移动文件指针
- System.out.println("从文件头跳过6个字节,现在文件内容如下:");
- byte[] buffer = new byte[2];
- int len = 0;
- while ((len = raf.read(buffer, 0, 2)) != -1) {
- System.out.print(new String(buffer, 0, len)); // 输出文件内容
- }
=============================================================================
================================================================================
Java字节流的使用:字节输入/输出流、文件输入/输出流、字节数组输入/输出流
InputStream 是 Java 所有字节输入流类的父类,OutputStream 是 Java 所有字节输出流类的父类,它们都是一个抽象类,因此继承它们的子类要重新定义父类中的抽象方法。
介绍如何使用它们的子类输入和输出字节流,包括 ByteArrayInputStream 类、ByteArrayOutputStream 类、FileInputStream 类和 FileOutputStream 类。
字节输入流
InputStream 类及其子类的对象表示字节输入流,InputStream 类的常用子类如下。
- ByteArrayInputStream 类:将字节数组转换为字节输入流,从中读取字节。
- FileInputStream 类:从文件中读取数据。
- PipedInputStream 类:连接到一个 PipedOutputStream(管道输出流)。
- SequenceInputStream 类:将多个字节输入流串联成一个字节输入流。
- ObjectInputStream 类:将对象反序列化。
使用 InputStream 类的方法可以从流中读取一个或一批字节。
注意:在使用 mark() 方法和 reset() 方法之前,需要判断该文件系统是否支持这两个方法,以避免对程序造成影响。
字节输出流
OutputStream 类及其子类的对象表示一个字节输出流。OutputStream 类的常用子类如下。
- ByteArrayOutputStream 类:向内存缓冲区的字节数组中写数据。
- FileOutputStream 类:向文件中写数据。
- PipedOutputStream 类:连接到一个 PipedlntputStream(管道输入流)。
- ObjectOutputStream 类:将对象序列化。
字节数组输入流
ByteArrayInputStream 类
- ByteArrayInputStream(byte[] buf):创建一个字节数组输入流,字节数组类型的数据源由参数 buf 指定。
- ByteArrayInputStream(byte[] buf,int offse,int length):创建一个字节数组输入流,其中,参数 buf 指定字节数
例 1
使用 ByteArrayInputStream 类编写一个案例,实现从一个字节数组中读取数据,再转换为 int 型进行输出。代码如下:
- public class test08 {
- public static void main(String[] args) {
- byte[] b = new byte[] { 1, -1, 25, -22, -5, 23 }; // 创建数组
- ByteArrayInputStream bais = new ByteArrayInputStream(b, 0, 6); // 创建字节数组输入流
- int i = bais.read(); // 从输入流中读取下一个字节,并转换成int型数据
- while (i != -1) { // 如果不返回-1,则表示没有到输入流的末尾
- System.out.println("原值=" + (byte) i + "\t\t\t转换为int类型=" + i);
- i = bais.read(); // 读取下一个
- }
- }
- }
原值=1 转换为int类型=1 原值=-1 转换为int类型=255 原值=25 转换为int类型=25 原值=-22 转换为int类型=234 原值=-5 转换为int类型=251 原值=23 转换为int类型=23
从上述的运行结果可以看出,字节类型的数据 -1 和 -22 转换成 int 类型的数据后变成了 255 和 234,对这种结果的解释如下:
- 字节类型的 1,二进制形式为 00000001,转换为 int 类型后的二进制形式为 00000000 00000000 0000000000000001,对应的十进制数为 1。
- 字节类型的 -1,二进制形式为 11111111,转换为 int 类型后的二进制形式为 00000000 00000000 0000000011111111,对应的十进制数为 255。
可见,从字节类型的数转换成 int 类型的数时,如果是正数,则数值不变;如果是负数,则由于转换后,二进制形式前面直接补了 24 个 0,这样就改变了原来表示负数的二进制补码形式,所以数值发生了变化,即变成了正数。
提示:负数的二进制形式以补码形式存在,例如 -1,其二进制形式是这样得来的:首先获取 1 的原码 00000001,然后进行反码操作,1 变成 0,0 变成 1,这样就得到 11111110,最后进行补码操作,就是在反码的末尾位加 1,这样就变成了 11111111。
字节数组输出流
ByteArrayOutputStream 类可以向内存的字节数组中写入数据,该类的构造方法有如下两种重载形式。
- ByteArrayOutputStream():创建一个字节数组输出流,输出流缓冲区的初始容量大小为 32 字节。
- ByteArrayOutputStream(int size):创建一个字节数组输出流,输出流缓冲区的初始容量大小由参数 size 指定。
ByteArrayOutputStream 类中除了有前面介绍的字节输出流中的常用方法以外,还有如下两个方法。
- intsize():返回缓冲区中的当前字节数。
- byte[] toByteArray():以字节数组的形式返回输出流中的当前内容。
例 2
使用 ByteArrayOutputStream 类编写一个案例,实现将字节数组中的数据输出,代码如下所示。
- public class Test09 {
- public static void main(String[] args) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] b = new byte[] { 1, -1, 25, -22, -5, 23 }; // 创建数组
- baos.write(b, 0, 6); // 将字节数组b中的前4个字节元素写到输出流中
- System.out.println("数组中一共包含:" + baos.size() + "字节"); // 输出缓冲区中的字节数
- byte[] newByteArray = baos.toByteArray(); // 将输出流中的当前内容转换成字节数组
- System.out.println(Arrays.toString(newByteArray)); // 输出数组中的内容
- }
- }
数组中一共包含:6字节 [1, -1, 25, -22, -5, 23]
文件输入流
FileInputStream 是 Java 流中比较常用的一种,它表示从文件系统的某个文件中获取输入字节。通过使用 FileInputStream 可以访问文件中的一个字节、一批字节或整个文件。
在创建 FileInputStream 类的对象时,如果找不到指定的文件将拋出 FileNotFoundException 异常,该异常必须捕获或声明拋出。
FileInputStream 常用的构造方法主要有如下两种重载形式。
- FileInputStream(File file):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
- FileInputStream(String name):通过打开一个到实际文件的链接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
- try {
- // 以File对象作为参数创建FileInputStream对象
- FileInputStream fis1 = new FileInputStream(new File("F:/mxl.txt"));
- // 以字符串值作为参数创建FilelnputStream对象
- FileInputStream fis2 = new FileInputStream("F:/mxl.txt");
- } catch(FileNotFoundException e) {
- System.out.println("指定的文件找不到!");
- }
例 3
假设有一个 D:\myJava\HelloJava.java
文件,下面使用 FileInputStream 类读取并输出该文件的内容。具体代码如下:
- public class Test10 {
- public static void main(String[] args) {
- File f = new File("D:/myJava/HelloJava.java");
- FileInputStream fis = null;
- try {
- // 因为File没有读写的能力,所以需要有个InputStream
- fis = new FileInputStream(f);
- // 定义一个字节数组
- byte[] bytes = new byte[1024];
- int n = 0; // 得到实际读取到的字节数
- System.out.println("D:\\myJava\\HelloJava.java文件内容如下:");
- // 循环读取
- while ((n = fis.read(bytes)) != -1) {
- String s = new String(bytes, 0, n); // 将数组中从下标0到n的内容给s
- System.out.println(s);
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- fis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
注意:FileInputStream 类重写了父类 InputStream 中的 read() 方法、skip() 方法、available() 方法和 close() 方法,不支持 mark() 方法和 reset() 方法。
文件输出流
FileOutputStream 类继承自 OutputStream 类,重写和实现了父类中的所有方法。FileOutputStream 类的对象表示一个文件字节输出流,可以向流中写入一个字节或一批字节。在创建 FileOutputStream 类的对象时,如果指定的文件不存在,则创建一个新文件;如果文件已存在,则清除原文件的内容重新写入。
FileOutputStream 类的构造方法主要有如下 4 种重载形式。
- FileOutputStream(File file):创建一个文件输出流,参数 file 指定目标文件。
- FileOutputStream(File file,boolean append):创建一个文件输出流,参数 file 指定目标文件,append 指定是否将数据添加到目标文件的内容末尾,如果为 true,则在末尾添加;如果为 false,则覆盖原有内容;其默认值为 false。
- FileOutputStream(String name):创建一个文件输出流,参数 name 指定目标文件的文件路径信息。
- FileOutputStream(String name,boolean append):创建一个文件输出流,参数 name 和 append 的含义同上
对文件输出流有如下四点说明:
- 在 FileOutputStream 类的构造方法中指定目标文件时,目标文件可以不存在。
2目标文件所在目录必须存在,否则会拋出 java.io.FileNotFoundException 异常。
例 4
同样是读取 D:\myJava\HelloJava.java 文件的内容,在这里使用 FileInputStream 类实现,然后再将内容写入新的文件 D:\myJava\HelloJava.txt 中。具体的代码如下:
- public class Test11 {
- public static void main(String[] args) {
- FileInputStream fis = null; // 声明FileInputStream对象fis
- FileOutputStream fos = null; // 声明FileOutputStream对象fos
- try {
- File srcFile = new File("D:/myJava/HelloJava.java");
- fis = new FileInputStream(srcFile); // 实例化FileInputStream对象
- File targetFile = new File("D:/myJava/HelloJava.txt"); // 创建目标文件对象,该文件不存在
- fos = new FileOutputStream(targetFile); // 实例化FileOutputStream对象
- byte[] bytes = new byte[1024]; // 每次读取1024字节
- int i = fis.read(bytes);
- while (i != -1) {
- fos.write(bytes, 0, i); // 向D:\HelloJava.txt文件中写入内容
- i = fis.read(bytes);
- }
- System.out.println("写入结束!");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- fis.close(); // 关闭FileInputStream对象
- fos.close(); // 关闭FileOutputStream对象
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
Java字符流的使用:字符输入/输出流、字符文件和字符缓冲区的输入/输出流
尽管 Java 中字节流的功能十分强大,几乎可以直接或间接地处理任何类型的输入/输出操作,但利用它却不能直接操作 16 位的 Unicode 字符。这就要用到字符流。
字符输入流
Reader 类是所有字符流输入类的父类,该类定义了许多方法,这些方法对所有子类都是有效的。
Reader 类的常用子类如下。
- CharArrayReader 类:将字符数组转换为字符输入流,从中读取字符。
- StringReader 类:将字符串转换为字符输入流,从中读取字符。
- BufferedReader 类:为其他字符输入流提供读缓冲区。
- PipedReader 类:连接到一个 PipedWriter。
- InputStreamReader 类:将字节输入流转换为字符输入流,可以指定字符编码。
与 InputStream 类相同,在 Reader 类中也包含 close()、mark()、skip() 和 reset() 等方法,这些方法可以参考 InputStream 类的方法。下面主要介绍 Reader 类中的 read() 方法,如表 1 所示。
字符输出流
与 Reader 类相反,Writer 类是所有字符输出流的父类,该类中有许多方法,这些方法对继承该类的所有子类都是有效的。
Writer 类的常用子类如下。
- CharArrayWriter 类:向内存缓冲区的字符数组写数据。
- StringWriter 类:向内存缓冲区的字符串(StringBuffer)写数据。
- BufferedWriter 类:为其他字符输出流提供写缓冲区。
- PipedWriter 类:连接到一个 PipedReader。
- OutputStreamReader 类:将字节输出流转换为字符输出流,可以指定字符编码。
与 OutputStream 类相同,Writer 类也包含 close()、flush() 等方法,这些方法可以参考 OutputStream 类的方法。下面主要介绍 Writer 类中的 write() 方法和 append() 方法,如表 2 所示。
注意:Writer 类所有的方法在出错的情况下都会引发 IOException 异常。关闭一个流后,再对其进行任何操作都会产生错误。
字符文件输入流
为了读取方便,Java 提供了用来读取字符文件的便捷类——FileReader。该类的构造方法有如下两种重载形式。
- FileReader(File file):在给定要读取数据的文件的情况下创建一个新的 FileReader 对象。其中,file 表示要从中读取数据的文件。
- FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新 FileReader 对象。其中,fileName 表示要从中读取数据的文件的名称,表示的是一个文件的完整路径。
在用该类的构造方法创建 FileReader 读取对象时,默认的字符编码及字节缓冲区大小都是由系统设定的。要自己指定这些值,可以在 FilelnputStream 上构造一个 InputStreamReader。
注意:在创建 FileReader 对象时可能会引发一个 FileNotFoundException 异常,因此需要使用 try catch 语句捕获该异常。
字符流和字节流的操作步骤相同,都是首先创建输入流或输出流对象,即建立连接管道,建立完成后进行读或写操作,最后关闭输入/输出流通道。
例 1
要将 D:\myJava\HelloJava.java 文件中的内容读取并输出到控制台,使用 FileReader 类的实现代码如下:
- public class Test12 {
- public static void main(String[] args) {
- FileReader fr = null;
- try {
- fr = new FileReader("D:/myJava/HelloJava.java"); // 创建FileReader对象
- int i = 0;
- System.out.println("D:\\myJava\\HelloJava.java文件内容如下:");
- while ((i = fr.read()) != -1) { // 循环读取
- System.out.print((char) i); // 将读取的内容强制转换为char类型
- }
- } catch (Exception e) {
- System.out.print(e);
- } finally {
- try {
- fr.close(); // 关闭对象
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
字符文件输出流
Java 提供了写入字符文件的便捷类——FileWriter,该类的构造方法有如下 4 种重载形式。
- FileWriter(File file):在指定 File 对象的情况下构造一个 FileWriter 对象。其中,file 表示要写入数据的 File 对象。
- FileWriter(File file,boolean append):在指定 File 对象的情况下构造一个 FileWriter 对象,如果 append 的值为 true,则将字节写入文件末尾,而不是写入文件开始处。
- FileWriter(String fileName):在指定文件名的情况下构造一个 FileWriter 对象。其中,fileName 表示要写入字符的文件名,表示的是完整路径。
- FileWriter(String fileName,boolean append):在指定文件名以及要写入文件的位置的情况下构造 FileWriter 对象。其中,append 是一个 boolean 值,如果为 true,则将数据写入文件末尾,而不是文件开始处。
在创建 FileWriter 对象时,默认字符编码和默认字节缓冲区大小都是由系统设定的。要自己指定这些值,可以在 FileOutputStream 上构造一个 OutputStreamWriter 对象。
FileWriter 类的创建不依赖于文件存在与否,如果关联文件不存在,则会自动生成一个新的文件。在创建文件之前,FileWriter 将在创建对象时打开它作为输出。如果试图打开一个只读文件,将引发一个 IOException 异常。
注意:在创建 FileWriter 对象时可能会引发 IOException 或 SecurityException 异常,因此需要使用 try catch 语句捕获该异常。
例 2
编写一个程序,将用户输入的 4 个字符串保存到 D:\myJava\book.txt 文件中。在这里使用 FileWriter 类中的 write() 方法循环向指定文件中写入数据,实现代码如下:
- public class Test13 {
- public static void main(String[] args) {
- Scanner input = new Scanner(System.in);
- FileWriter fw = null;
- try {
- fw = new FileWriter("D:\\myJava\\book.txt"); // 创建FileWriter对象
- for (int i = 0; i < 4; i++) {
- System.out.println("请输入第" + (i + 1) + "个字符串:");
- String name = input.next(); // 读取输入的名称
- fw.write(name + "\r\n"); // 循环写入文件
- }
- System.out.println("录入完成!");
- } catch (Exception e) {
- System.out.println(e.getMessage());
- } finally {
- try {
- fw.close(); // 关闭对象
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
请输入第1个字符串: 热点要闻 请输入第2个字符串: 个性推荐 请输入第3个字符串: 热搜新闻词 请输入第4个字符串: 本地看点 录入完成!
字符缓冲区输入流
BufferedReader 类主要用于辅助其他字符输入流,它带有缓冲区,可以先将一批数据读到内存缓冲区。接下来的读操作就可以直接从缓冲区中获取数据,而不需要每次都从数据源读取数据并进行字符编码转换,这样就可以提高数据的读取效率。
BufferedReader 类的构造方法有如下两种重载形式。
- BufferedReader(Reader in):创建一个 BufferedReader 来修饰参数 in 指定的字符输入流。
- BufferedReader(Reader in,int size):创建一个 BufferedReader 来修饰参数 in 指定的字符输入流,参数 size 则用于指定缓冲区的大小,单位为字符。
除了可以为字符输入流提供缓冲区以外,BufferedReader 还提供了 readLine()
方法,该方法返回包含该行内容的字符串,但该字符串中不包含任何终止符,如果已到达流末尾,则返回 null。readLine() 方法表示每次读取一行文本内容,当遇到换行(\n)、回车(\r)或回车后直接跟着换行标记符即可认为某行已终止。
例 3
使用 BufferedReader 类中的 readLine() 方法逐行读取 D:\myJava\Book.txt 文件中的内容,并将读取的内容在控制台中打印输出,代码如下:
- public class Test13 {
- public static void main(String[] args) {
- FileReader fr = null;
- BufferedReader br = null;
- try {
- fr = new FileReader("D:\\myJava\\book.txt"); // 创建 FileReader 对象
- br = new BufferedReader(fr); // 创建 BufferedReader 对象
- System.out.println("D:\\myJava\\book.txt 文件中的内容如下:");
- String strLine = "";
- while ((strLine = br.readLine()) != null) { // 循环读取每行数据
- System.out.println(strLine);
- }
- } catch (FileNotFoundException e1) {
- e1.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- fr.close(); // 关闭 FileReader 对象
- br.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
如上述代码,首先分别创建了名称为 fr 的 FileReader 对象和名称为 br 的 BufferedReader 对象,然后调用 BufferedReader 对象的 readLine() 方法逐行读取文件中的内容。如果读取的文件内容为 Null,即表明已经读取到文件尾部,此时退出循环不再进行读取操作。最后将字符文件输入流和带缓冲的字符输入流关闭。
D:\myJava\book.txt 文件中的内容如下: 热点要闻 个性推荐 热搜新闻词 本地看点
字符缓冲区输出流
BufferedWriter 类主要用于辅助其他字符输出流,它同样带有缓冲区,可以先将一批数据写入缓冲区,当缓冲区满了以后,再将缓冲区的数据一次性写到字符输出流,其目的是为了提高数据的写效率。
BufferedWriter 类的构造方法有如下两种重载形式。
- BufferedWriter(Writer out):创建一个 BufferedWriter 来修饰参数 out 指定的字符输出流。
- BufferedWriter(Writer out,int size):创建一个 BufferedWriter 来修饰参数 out 指定的字符输出流,参数 size 则用于指定缓冲区的大小,单位为字符。
该类除了可以给字符输出流提供缓冲区之外,还提供了一个新的方法 newLine(),该方法用于写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行(\n)符。
提示:BufferedWriter 类的使用与 FileWriter 类相同,这里不再重述。
Java字节流和字符流的区别及如何区分输入流和输出流
Java字节流和字符流的区别
首先我们先大概总结一下前面学习的内容,可分为以下几点:
- 以 Stream 结尾都是字节流,Reader 和 Writer 结尾都是字符流。
- InputStream 是所有字节输入流的父类,OutputStream 是所有字节输出流的父类。
- Reader 是字符输入流的父类,Writer 是字符输出流的父类。
字节流:
- 文件流:FileOutputStream 和 FileInputStream
- 缓冲流:BufferedOutputStream 和 BufferedInputStream
- 对象流:ObjectOutputStream 和 ObjectInputStream
字符流:
- 转换流:InputStreamReader 和 OutputStreamWriter
- 缓冲字符流:PrintWriter 和 BufferedReader
区别:
- 读写的时候字节流是按字节读写,字符流按字符读写。
- 字节流适合所有类型文件的数据传输,因为计算机字节(Byte)是电脑中表示信息含义的最小单位。字符流只能够处理纯文本数据,其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便。
- 在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。
- 只是读写文件,和文件内容无关时,一般选择字节流。
区分输入流和输出流
对于初学者,看到输入流与输出流的部分,大部分都不明白到底是输入流写入还是输出流写入文件呢?要将文件读出是用输入流还是输出流呢?程序在内存中运行,文件在磁盘上,把文件从磁盘上读入内存中来,这就需要输入流。反之,把内存中的数据写到磁盘上的文件里就需要输出流。
Windows 里所说的写(将内容写入到文件里,如:存盘)是输入,而读(把内容从文件里读出来,如:显示)是输出,与 Java 的输入输出不一样。Java 里的输入流与输出流是针对内存而言的,它是从内存中读写,而不是所说的显示与存盘。输入流与输出流都可以将内容从屏幕上显示出来。
屏幕和键盘也是区别于内存的设备,System.out.println()
用于将内存中的数据输出到屏幕上,而System.in
用来在终端读取键盘输入内容。
程序操作的数据都应该是在内存里面,内存是操作的主对象,把数据从其他资源中传送到内存,就是输入。反之,把数据从内存传送到其他资源,就是输出。
不管从磁盘、网络还是键盘读,读到内存中就是 InputStream。例如:
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("infilename")));
不管写到磁盘、网络,或者写到屏幕,都是使用 OutputStream。例如
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("outfilename")));
Java转换流:InputStreamReader和OutputStreamWriter
正常情况下,字节流可以对所有的数据进行操作,但是有些时候在处理一些文本时我们要用到字符流,比如,查看文本的中文时就是需要采用字符流更为方便。所以 Java IO 流中提供了两种用于将字节流转换为字符流的转换流。
InputStreamReader 用于将字节输入流转换为字符输入流,其中 OutputStreamWriter 用于将字节输出流转换为字符输出流。使用转换流可以在一定程度上避免乱码,还可以指定输入输出所使用的字符集。
例 1
在 java.txt 中输出“C语言中文网”这 6 个字,将 java.txt 保存为“UTF-8”的格式,然后通过字节流的方式读取,代码如下:
- public static void main(String[] args) {
- try {
- FileInputStream fis = new FileInputStream("D://java.txt");
- int b = 0;
- while ((b = fis.read()) != -1) {
- System.out.print((char) b);
- }
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- try {
- FileInputStream fis = new FileInputStream("D://java.txt");
- byte b[] = new byte[1024];
- int len = 0;
- while ((len = fis.read(b)) != -1) {
- System.out.print(new String(b, 0, len, "UTF-8"));
- }
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- try {
- FileInputStream fis = new FileInputStream("D://java.txt");
- InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
- int b = 0;
- while ((b = isr.read()) != -1) {
- System.out.print((char) b); // 输出结果为“C语言中文网”
- }
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
例 2
下面以获取键盘输入为例来介绍转换流的用法。Java 使用 System.in 代表标准输出,即键盘输入,但这个标准输入流是 InputStream 类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用 InputStreamReader 将其转换成字符输入流,普通的 Reader 读取输入内容时依然不太方便,可以将普通的 Reader 再次包装成 BufferedReader,利用 BufferedReader 的 readLine() 方法可以一次读取一行内容。程序如下所示。
- public static void main(String[] args) {
- try {
- // 将 System.in 对象转换成 Reader 对象
- InputStreamReader reader = new InputStreamReader(System.in);
- // 将普通的Reader 包装成 BufferedReader
- BufferedReader br = new BufferedReader(reader);
- String line = null;
- // 利用循环方式来逐行的读取
- while ((line = br.readLine()) != null) {
- // 如果读取的字符串为“exit”,则程序退出
- if (line.equals("exit")) {
- System.exit(1);
- }
- // 打印读取的内容
- System.out.println("输入内容为:" + line);
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
上面代码第 4 行和第 6 行将 System.in 包装成 BufferedReader,BufferReader 流具有缓冲功能,它可以一次读取一行文本,以换行符为标志,如果它没有读到换行符,则程序堵塞,等到读到换行符为止。运行上面程序可以发现这个特征,在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容。
由于 BufferedReader 具有一个 readLine() 方法,可以非常方便地进行一次读入一行内容,所以经常把读入文本内容地输入流包装成 BufferedReader,用来方便地读取输入流的文本内容。
学到这里,大家可能有一个疑问:既然有字节流转字符流的转换流,那么为什么没有字符流转字节流的转换流呢?
这个问题一语指出了 Java 设计的遗漏之处,想一想字符流和字节流的差别。字节流比字符流的使用范围要更广,但字符流比字节流操作方便。如果有一个流已经是字符流了,也就是说,是一个用起来更方便的流,为什么要转换成字节流呢?反之,如果现在有一个字节流,但可以确定这个字节流的内容都是文本内容,那么把它转换成字符流来处理就会更方便一些,所以 Java 只提供了将字节流转换成字符流的转换流,没有提供将字符流转换成字节流的转换流。
Java利用对象序列化控制输入输出
前面学习了如何控制基本数据的输入输出,本节主要讲解如何输入输出对象数据。对象数据是很复杂的,我们可以利用对象序列化来实现。
对象序列化是什么
对象序列化(Serialize)指将一个 Java 对象写入 IO 流中,与此对应的是,对象的反序列化(Deserialize)则指从 IO 流中恢复该 Java 对象。如果想让某个 Java 对象能够序列化,则必须让它的类实现 java.io.Serializable 接口,接口定义如下:
public interface Serializable { }
Serializable 接口是一个空接口,实现该接口无须实现任何方法,它只是告诉 JVM 该类可以被序列化机制处理。通常建议程序创建的每个 JavaBean 类都实现 Serializable。
ObjectInput 接口与 ObjectOutput 接口分别继承了 DataInput 和 DataOutput 接口,主要提供用于读写基本数据和对象数据的方法。 ObjectInput 接口提供了 readObject() 方法,此方法用于将对象从流中读出。ObjectOutput 提供了 writeObject() 方法,此方法用于将对象写入流中。因为 ObjectInput 与 ObjectOutput 都是接口,所以不能创建对象,只能使用分别实现了这两个接口的 ObjectInputStream 类和 ObjectOutputStream 类来创建对象。
下面讲解如何使用 ObjectInputStream 类和 ObjectOutputStream 类来操作数据
序列化
ObjectOutputStream 类继承了 OutputStream 类,同时实现了 ObjectOutput 接口,提供将对象序列化并写入流中的功能,该类的构造方法如下:
public ObjectOutputStream (OutputStream out)
该构造方法需要传入一个 OutputStream 对象,用来表示将对象二进制流写入到指定的 OutputStream 中。
程序通过以下两个步骤来序列化对象:
1)创建一个 ObjectOutputStream 对象,如下代码所示。
// 创建个 ObjectOutputStream 输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
2)调用 ObjectOutputStream 对象的 writeObject() 方法输出可序列化对象,如下代码所示。
// 将一个 Person 对象输出到输出流中
oos.writerObject(per);
例 1
下面程序定义了一个 Person 类,这个 Person 类就是一个普通的 Java 类,只是实现了 Serializable 接口,该接口表示该类的对象是可序列化的。
- public class Person implements Serializable {
- private String name;
- private int age;
- // 注意此处没有提供无参数的构造器
- public Person(String name, int age) {
- System.out.println("有参数的构造器");
- this.name = name;
- this.age = age;
- }
- // 省略 name 和 age的setter和getter方法
- ...
- }
注意:Person 类的两个成员变量分别是 String 类型和 int 类型的。如果某个类的成员变量的类型不是基本类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。
下面程序使用 ObjectOutputStream 将一个 Person 对象写入到磁盘文件。
- public class WriteObject {
- public static void main(String[] args) throws Exception {
- // 创建一个 ObjectOutputStream 输出流
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
- Person per = new Person("C语言中文网", 7);
- // 将 Per对象写入输出流
- oos.writeObject(per);
- }
- }
上面程序中的第 4 行代码创建了一个 ObjectOutputStream 输出流,这个 ObjectOutputStream 输出流建立在一个文件输出流的基础之上。程序第 7 行代码使用 writeObject() 方法将一个 Person 对象写入输出流。运行上面程序,将会看到生成了一个 object.txt 文件,该文件的内容就是 Person 对象。
反序列化
ObjectInputStream 类继承了 InputStream 类,同时实现了 ObjectInput 接口,提供了将对象序列化并从流中读取出来的功能。该类的构造方法如下:
public ObjectInputStream(InputStream out)
该构造方法需要传入一个 InputStream 对象,用来创建从指定 InputStream 读取的 ObjectInputStream。
// 创建一个ObjectInputStream输入流
ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("object. txt"));
// 从输入流中读取一个Java对象,并将其强制类型转换为Person类
Person P = (Person)ois.readObject();
例 2
下面程序是从例 1 中生成的 object.txt 文件来读取 Person 对象的步骤。
- public class ReadObject {
- public static void main(String[] args) throws Exception {
- // 创建一个ObjectInputStream输入流
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
- // 从输入流中读取一个 Java对象,并将其强制类型转换为Person类
- Person p = (Person) ois.readObject();
- System.out.println("名字为:" + p.getName() + "\n年龄为:" + p.getAge());
- }
- }
当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参数的构造方法,要么也是可序列化的,否则反序列化时将抛出 InvalidClassException 异常。如果父类是不可序列化的,只是带有无参数的构造方法,则该父类中定义的成员变量值不会序列化到 IO 流中。
Java序列化编号
Java 序列化机制是通过类的序列化编号(serialVersionUID)来验证版本一致性的。在反序列化时,JVM 会把传来字节流中的序列化编号和本地相应实体类的序列化编号进行比较,如果相同就认为一致,可以进行反序列化,否则会抛出 InvalidCastException 异常
序列化编号有两种显式生成方式:
- 默认的1L,比如:private static final long serialVersionUID = 1L。
- 根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段。
当实现 Serializable 接口的对象没有显式定义一个序列化编号时,Java 序列化会根据编译的 Class 自动生成一个序列化编号,这种情况下只要 class 文件发生变化,序列化号就会改变,否则一直不变。
Java保存图书信息
每到学校开学季都会新进一批图书教材,需要将这些图书信息保存到文件,再将它们打印出来方便老师查看。下面编写程序,使用文件输入/输出流完成图书信息的存储和读取功能,具体的实现步骤如下。
1)创建 Book 类,在该类中包含 no、name 和 price 3 个属性,分别表示图书编号、图书名称和图书单价。同时还包含两个方法 write() 和 read(),分别用于将图书信息写入到磁盘文件中和从磁盘文件中读取图书信息并打印到控制台。
此外,在 Product 类中包含有该类的 toString() 方法和带有 3 个参数的构造方法,具体的内容如下:
- public class Book {
- private int no; // 编号
- private String name; // 名称
- private double price; // 单价
- public Book(int no, String name, double price) {
- this.no = no;
- this.name = name;
- this.price = price;
- }
- public String toString() {
- return "图书编号:" + this.no + ",图书名称:" + this.name + ",图书单价:" + this.price + "\n";
- }
- public static void write(List books) {
- FileWriter fw = null;
- try {
- fw = new FileWriter("E:\\myJava\\books.txt"); // 创建FileWriter对象
- for (int i = 0; i < books.size(); i++) {
- fw.write(books.get(i).toString()); // 循环写入
- }
- } catch (Exception e) {
- System.out.println(e.getMessage());
- } finally {
- try {
- fw.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- public static void read() {
- FileReader fr = null;
- BufferedReader br = null;
- try {
- fr = new FileReader("E:\\myJava\\books.txt");
- br = new BufferedReader(fr); // 创建BufferedReader对象
- String str = "";
- while ((str = br.readLine()) != null) { // 循环读取每行数据
- System.out.println(str); // 输出读取的内容
- }
- } catch (Exception e) {
- System.out.println(e.getMessage());
- } finally {
- try {
- br.close();
- fr.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class Test14 {
- public static void main(String[] args) {
- Book book1 = new Book(1001, "C语言中文网Java教程", 159);
- Book book2 = new Book(1002, "C语言中文网C++教程", 259);
- List books = new ArrayList();
- books.add(book1);
- books.add(book2);
- Book.write(books);
- System.out.println("********************图书信息******************");
- Book.read();
- }
- }
********************图书信息****************** 图书编号:1001,图书名称:C语言中文网Java教程,图书单价:159.0 图书编号:1002,图书名称:C语言中文网C++教程,图书单价:259.0