I/O流介绍
介绍
2023-09-27 14:19:50 时间
I/O
- 概念:I(input)O(output)即输入/输出。
- 作用:将外部的数据输入到JVM中或将JVM中的数据输出到外部。
例如:System.out.println()输出,Scanner输入。
1 流
- 概念:传输数据的通道(对象)。
- 内存:存储数据,只存储程序运行过程中需要的数据(变量,对象),这些数据是临时的,读写速度非常快,内存越大可同时运行的程序越多。
- 硬盘:存储数据,存储需要持久保存的数据,关闭软件,关闭计算机这些数据依然存在,读写速度较慢。
2 流的分类
- 按照方向
1. 输入流:将外部的数据读入到程序内部。 2. 输出流:将程序内部的数据输出到外部。
- 按照处理数据的单位
1. 字节流:以字节为单位读写数据,可以处理任何类型的数据(图片,视频)。 2. 字符流:以字符为单位读写数据,只可以处理文本文件,可以保证一个字符的完整性。
- 按照功能
1. 节点流:负责传输数据的流。 2. 过滤流:增强节点流的功能,依赖节点流。
3 字节流
3.1 字节流父类
-
OutputStream(字节输出流)
作用:以字节为单外向外输出数据。
常用方法:方法名 作用 write(int b) 写一个字节的数据 write(byte[] b) 写多个字节的数据 close() 关闭流(释放资源) -
InputStream(字节输入流)
作用:以字节为单位读取数据。
常用方法:方法名 作用 int read() 读一个字节的数据,如果文件已读完返回-1 int read(byte[] bs) 读入多个字节,将数据存储到bs中 close() 关闭流(释放资源)
3.2 字节节点流
- FileOutputStream
作用:以字节为单位向文件中输出数据。
构造方法:构造方法 作用 new FileOutputStream(“路径/文件名.后缀名”); 在指定位置创建一个文件,如果文件已存在则覆盖 new FileOutputStream(“路径/文件名.后缀名”,true) 在指定位置创建一个文件,如果文件已存在则在原有文件中追加内容 - FileInputStream
作用:以字节为单位读取文件中的数据。
构造方法:
演示的代码如下:构造方法 作用 new FileInputStream(“路径/文件名.后缀名”) 从指定文件中读取数据
package com.txw.test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class TestIO {
public static void main(String[] args) throws Exception{
OutputStream os = new FileOutputStream("./a.txt",true);
String str = "HelloWorld!";
// 将字符转换为字节数组
byte[] bs = str.getBytes();
// 写数据
os.write(bs);
// 关闭资源
os.close();
InputStream is = new FileInputStream("a.txt");
// HelloWorld!!
// 读取数据
while(true) {
int b = is.read();
if(b==-1)break; // 如果读到的字节为-1 表示已读完
System.out.print((char) b);
}
// 关闭资源
is.close();
}
}
演示的代码如下:
package com.txw.test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class TestFileCopy {
public static void main(String[] args) throws Exception{
// 文件复制
// 从文件中读取数据
FileInputStream is = new FileInputStream("D:/a.exe");
// 向另外一个文件中输出数据
FileOutputStream os = new FileOutputStream("D:/b.exe");
FileChannel in = is.getChannel(); // NIO获取通道
FileChannel out = os.getChannel();
in.transferTo(0,in.size(),out);
// 读取文件
/*while(true) {
int b = is.read(); // 读取一个字节
if( b==-1 )break;
os.write( b ); // 写出一个字节
}*/
// 关闭资源
in.close();
out.close();
}
}
3.3 字节过滤流
- ObjectOutputStream
作用:可以输出8种基本数据类型与任意对象,自带缓冲区。
常用方法:方法名 作用 writeXx(Xx为基本数据类型) 写8种基本数据类型 writeUTF(String str) 写字符串(编码为UTF-8) writeObject(Object obj) 写对象 flush() 释放缓冲区 - ObjectInputStream
作用:可以读入8种基本数据类型与任意对象。
常用方法:
演示的代码如下:方法名 作用 readXx(Xx为基本数据类型) 读取8种基本数据类型 String readUTF() 读取字符串(UTF-8编码) Object readObject() 读取一个对象
package com.txw.test;
import java.io.*;
public class TestObjectStream {
public static void main(String[] args) throws Exception{
// 创建节点流
OutputStream os = new FileOutputStream("data.dat");
InputStream is = new FileInputStream("data.dat");
// 过滤流
ObjectOutputStream oos = new ObjectOutputStream( os );
ObjectInputStream ois = new ObjectInputStream( is );
Double a = 123D;
Account acc = new Account("8000 2939 2392 123","123456",30000D);
// 写数据
oos.writeObject( acc );
// oos.writeDouble( a );
// oos.writeUTF("ABCDEFG");
//oos.flush(); // 释放缓冲区
// 关闭资源
oos.close(); // 自动释放缓冲区
Object obj= ois.readObject();
System.out.println( obj );
ois.close();
/* String r = ois.readUTF();
System.out.println( r );
ois.close();*/
}
}
class Account implements Serializable{
transient String id;
String password;
Double balance;
public Account(String id, String password, Double balance) {
this.id = id;
this.password = password;
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id='" + id + '\'' +
", password='" + password + '\'' +
", balance=" + balance +
'}';
}
}
- 对象序列化
1. 序列化:程序中的对象到字节的一种转换,Java中的对象必须通过实现Serializable接口的方式证明该对象是可以进行序列化的。 2. 反序列化:将若干字节转换为程序中的对象。 3. 实现Serializable接口是一种编程习惯。
- 注意:
1. 序列化对象时必须保证对象的所有属性已 Serializable。 2. 序列化集合时必须保证集合中的所有元素已实现Serializable。 3. static与对象无关不会被序列化。 4. transient修饰的属性不会被序列化。
4 IO编程步骤
- IO编程四步
演示:积极处理异常// 1.创建节点流 OuputStream os = new FileOutputStream("路径"); // 2.封装过滤流 ObjectOutputStream oos = new ObjectOutputStream( os ); // 3.读写数据 oos.write(数据) // 4.关闭流 oos.close(); // 关闭最外层的流
新的异常处理方案:try-with-resource。ObjectOutputStream oos = null; ObjectInputStream ois = null; try { // 1.创建节点流 OutputStream os = new FileOutputStream("data.dat"); InputStream is = new FileInputStream("data.dat"); // 2.封装过滤流 oos = new ObjectOutputStream(os); ois = new ObjectInputStream(is); // 3.读写数据 Account acc = new Account("8000 2939 2392 123", "123456", 30000D); oos.writeObject(acc); Object obj = ois.readObject(); System.out.println(obj); }catch(Exception e){ e.printStackTrace(); }finally{ // 4.关闭流 try{oos.close();}catch(Exception e){e.printStackTrace();} // 自动释放缓冲区 try{ois.close();}catch(Exception e){e.printStackTrace();} }
语法如下:
作用:自动关闭资源。try(定义需要关闭资源的对象){ }catch(Exception e){ e.printStackTrace(); }
演示的代码如下:
package com.txw.test;
import java.io.*;
public class TestCheckException {
public static void main(String[] args) {
try( // 1.创建节点流
OutputStream os = new FileOutputStream("data.dat");
InputStream is = new FileInputStream("data.dat");
// 2.封装过滤流
ObjectOutputStream oos = new ObjectOutputStream(os);
ObjectInputStream ois = new ObjectInputStream(is);
){
// 3.读写数据
Account acc = new Account("8000 2939 2392 123", "123456", 30000D);
oos.writeObject(acc);
Object obj = ois.readObject();
System.out.println(obj);
}catch(Exception e){
e.printStackTrace();
}
}
}
5 习题
- Java 中根据流的方向,流分为输入流和输出流;对应的操作分别为字节流和字符流。
- Java 中字节流的父类为InputStream和OutputStream;字符流的父类为Reader和Writer。 以上 4 个类都位于java.io包中。
- 利用 FileOutputStream(String path)创建对象时,如果对应的文件在硬盘上不存在,则会如果文件的父级目录存在则创建,不存在则抛异常, 如果对应的文件存在,则覆盖(覆盖 | 不覆盖)原有内容,参数代表文件路径; 利用FileOutputStream(String name, boolean append)构造方法创建 FileOutputStream 对象,将第二个参数设置为true不会覆盖原有内容。
- FileInputStream 有三个重载的 read 方法,完成以下填空:
(1) 无参数的 read()方法返回值为int类型,表示读入的一个字节的 值,到流末尾时返回-1;
(2) 有参数的 read(byte[] bs)方法的返回值表示示读入的字节数,到流末尾时返回-1,参数表示读入的数据存放的位置。
(3) 两个参数的 read(byte[] bs, int offset, int len) 方法返回值表示读入的字节数,到流末尾时返回-1,参数分别表 示读入的数据存放的位置,从哪个下表开始,最大长度多少。 - 对象序列化:
(1) 为了让某对象能够被序列化,要求其实现 Serializable接口;
(2) 为了让该对象某个属性不参与序列化,应当使用transient修饰符。 - 下面关于 FileInputStream 说法正确的是(AB)
A. 创建 FileInputStream 对象是为了读取文件
B. 创建 FileInputStream 对象时,如果硬盘上对应的文件不存在,则抛出一个异常
C. 创建 FileInputStream 对象可以创建对应的文件
D. FileInputStream 对象读取文件时,只能读取文本文件 - 以下描述正确的是(AB)
A. InputStream 和 OutputStream 是基于字节流的
B. Reader 和 Writer 是基于字符流的
C. Reader 和 Writer 是支持对象序列化的
D. PrintWriter 是支持对象序列化的 - 创建一个 BufferedOutputStream 的语句为(C )
A. new BufferedOutputStream(“out.txt”);
B. new BufferedOutputStream(new OutputStream(“out.txt”));
C. new BufferedOutputStream(new FileOutputStream(“out.txt”));
D. new BufferedOutputStream(new FileInputStream(“out.txt”)); - 仔细阅读以下代码,关于程序描述正确的是(A)
A. 该程序编译出错
B. 编译正常,运行时异常
C. 编译正常,运行时也正常
D. 以上描述都不正确
原因:Address类没有实现序列化接口。 - (File 类)以下关于 File 类说法正确的是(ABC)
A.一个 File 对象代表了操作系统中的一个文件或者文件夹
B.可以使用 File 对象创建和删除一个文件
C.可以使用 File 对象创建和删除一个文件夹
D.当一个 File 对象被垃圾回收时,系统上对应的文件或文件夹也被删除
原因:D 错误,File 对象的存在与否与系统中的文件存在与否无关。 - 以下几种文件格式,应当使用字节流还是字符流?
A. java 源文件
B. .class 字节码文件
C. .html 网页文件
D. .jpg 图像文件
E. .mp3 音乐文件
F. .txt 文件
答:
I. java 源文件 字符流
II. .class 字节码文件 字符流
III. .html 网页文件 字符流
IV. .jpg 图像文件 字节流
V. .mp3 音乐文件 字节流
VI…txt文件 字符流 - (对象序列化和字符流)在 PrintWriter 中,有一个方法 print(Object obj) ; 在 ObjectOutputStream 中,有一个方法 writeObject(Object obj) 请简述这两个方法的区别?
答:print 方法表示把对象的 toString 方法返回值写入流 中,writeObject 表示把对象信息写入流中。 - (字符流、桥转换)要想从某个文件中获得一个字符输出流,则至少有以下三种方式:
(1) 利用 FileWriter 类
(2) 利用 PrintWriter 类
(3) 利用 FileOutputStream 类,
并通过 OutputStreamWriter 类获得 Writer 请简述以上三种方式获得 Writer 的区别。
答:
FileWriter 获取 Writer:获得字符流,编码为系统默认。
PrintWriter 获取 Writer:获得字符流,编码为系统默认,同时增 加写八种基本类型、字符串、对象 以及缓冲区的功能 桥转换获取 Writer:获得字符流,能够制定编码方式。 - 仔细阅读以下代码,将程序中不恰当的地方进行改正。
答:修改的代码如下:
package com.txw.test;
import java.io.FileInputStream;
import java.io.IOException;
public class Test{
public static void main(String[] args) {
// 题目修改
FileInputStream fin = null;
try {
fin = new FileInputStream("test.txt");
System.out.println(fin.read());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fin != null) {
// 判断是否为null,不为null调用方法
try {
fin.close(); // 释放资源应该放在finally中,不应该放在try代码块中
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
- 编程:利用 FileInputStream 和 FileOutputStream,完成下面的要求:
(1) 用 FileOutputStream 往当前目录下“test.txt”文件中写入“Hello World”;
(2) 利用 FileInputStream 读入 test.txt 文件,并在控制台上打印出 test.txt 中的内容。
(3) 要求用 try-catch-finally 处理异常,并且关闭流应放在 finally 块中。
演示的代码如下:
package com.txw.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
FileOutputStream fos = null;
//用 FileOutputStream 往当前目录下“test.txt”文件中写入“Hello World”
try {
fos = new FileOutputStream("test.txt");
fos.write("Hello World".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 利用 FileInputStream 读入 test.txt 文件,并在控制台上打印出 test.txt 中的内容
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 每次读取一个字节
int ch;
while ((ch = fis.read()) != -1) {
System.out.print((char) ch);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{ if(fis != null){
try {fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 编程:利用 IO 流,完成以下程序:
(1) 将 26 个大写字母(A~Z)写入到当前项目 a.txt 文件中;
(2) 读取文件中的内容,将读取的内容连接为一个字符串,并将所有的大写字母转变为小写字母打印 输出转换的结果。
演示的代码如下:
package com.txw.test;
import java.io.FileReader;
import java.io.FileWriter;
public class Test {
public static void main(String[] args) throws Exception {
FileWriter fw = new FileWriter("a.txt");
//将 26 个大写字母(A~Z)写入到当前项目 a.txt 文件中
for(char ch = 'A'; ch<='Z'; ch++){
fw.write(ch);
}
fw.close();
// 读取文件中的内容,将读取的内容连接为一个字符串,并将所有的大写字母转变为小写字母打印
// 输出转换的结果。
String s = "";
FileReader fr = new FileReader("a.txt");
// 每次读取一个字符使用空字符串将每一个字符进行拼接
int ch;
while( ( ch = fr.read() ) != -1 ){
s+=(char)ch;
}
System.out.println(s);
System.out.println(s.toLowerCase());
}
}
- 编程:当前项目的根目录 c.txt 文件中的内容为”abddbskshlsjdhhhiw”;编写程序读取文件中的内 容,要求去除重复的字母并按照字母的自然排序后将内容写入到当前项目的根目录 d.txt 文件中。
演示的代码如下:
package com.txw.test;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) throws Exception {
/* 当前项目的根目录 c.txt 文件中的内容为”abddbskshlsjdhhhiw”;
编写程序读取文件中的内容,要求去除重复的字母并按照字母的自然排序后将内容写入到当前项目的根目录d.txt文件中
*/
// 使用 TreeSet集合,集合特点不重复和排序
TreeSet<Character> ts = new TreeSet<Character>();
FileReader fr = new FileReader("c.txt");
// 读取每一个字符存储在集合中,重复的集合不进行存储。
int ch;
while( ( ch = fr.read() ) != -1 ){
ts.add((char)ch);
}
fr.close();
// 所有字符存储到集合中后,该集合中的内容就行一个排序好的
FileWriter fw = new FileWriter("d.txt");
for (Character c : ts) {
// 遍历集合将每一个元素写入文件中
fw.write(c);
}
fw.close();
}
}
- (字节过滤流)利用 Data 流,完成下面操作:
(1) 往当前目录下“test.dat”的文件中写入一个 long类型的数值:10000L
(2) 从该文件中读出数值,并把该数值加 1 之后,再存回文件中。
演示的代码如下:
package com.txw.test;
import java.io.*;
public class Test{
public static void main(String[] args) throws Exception {
// 往当前目录下“test.dat”的文件中写入一个 long类型的数值:10000L
DataOutputStream dos = new DataOutputStream(new FileOutputStream("test.dat"));
dos.writeLong(10000L);
dos.close();
// 从该文件中读出数值,并把该数值加1之后,再存回文件中
DataInputStream dis = new DataInputStream(new FileInputStream("test.dat"));
DataOutputStream dos2 = new DataOutputStream(new FileOutputStream("test.dat"));
long data = dis.readLong();
dos2.writeLong(data);
dis.close();
dos.close();
}
}
- 编程:根据提示完成以下代码。
演示的代码如下:
package com.txw.test;
import java.io.File;
public class Test{
public static void main(String[] args) {
// 创建file对象表示当前目录下hello.txt
File f = new File("hello.txt");
// 判断该文件是否存在 if(f.exists()){
// 如果存在输出结对路径
System.out.println(f.getAbsolutePath());
}
}
20 (字符流、桥转换)完成下面功能: 事先在当前目录下准备好一个 test.txt 的文本文件,要求该文本文件是使用 GBK 编码的多行文本文 件。如 test.txt :
利用字节流+桥转换读入这个文本文件,按照行的顺序,以 UTF-8 编码方式,写到 test2.txt 文件中。 例:test2.txt :
演示的代码如下:
package com.txw.test;
import java.io.*;
import java.util.*;
public class Test {
public static void main(String[] args) {
// 利用字节流+桥转换读入这个文本文件,按照行的顺序,以UTF-8编码方式,写到test2.txt文件中
BufferedReader br = null;
List<String> list = new ArrayList<String>();
// 集合用来存储每一行内容
try {
FileInputStream fin = new FileInputStream("test.txt");
Reader r = new InputStreamReader(fin, "UTF-8");
// 以utf-8读取文件
br = new BufferedReader(r);
// 每次读取一行
String line;
while( (line = br.readLine()) != null ){
// 每行的内容存储在集合中 list.add(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if (br != null)
try{
br.close();
}catch(Exception e){
}
}
PrintWriter pw = null;
try {
FileOutputStream fout = new FileOutputStream("test2.txt");
Writer w = new OutputStreamWriter(fout, "UTF-8");
pw = new PrintWriter(w);
// 将集合中的内容倒叙写入文件中
for(int i = list.size() - 1; i>=0; i--){
pw.println(list.get(i));
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if (pw != null) try{
pw.close();
}catch(Exception e){
}
}
}
}
- 编程:根据描述完成以下程序: 在当前目录下创建一个 worldcup.txt 的文本文件,其格式如下:
该文件采用“年份/世界杯冠军”的方式保存每一年世界杯冠军的信息。 要求:读入该文件的基础上,让用户输入一个年份,输出该年的世界杯冠军。如果该年没有举办世界杯, 则输出“没有举办世界杯” 。
演示的代码如下:
package com.txw.test;
import java.util.*;
import java.io.*;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
BufferedReader br = null;
// 创建Map集合用来存储文件中的国家和年份
Map<String, String> map = new HashMap<String, String>();
try {
FileInputStream fin = new FileInputStream("worldcup.txt");
InputStreamReader r = new InputStreamReader(fin);
br = new BufferedReader(r);
// 每次读取一行
String line;
while ((line = br.readLine()) != null) {
// 文件中的内容格式 2006/意大利 ,按照/分割,年份作为键,国家作为值。
String[] ss = line.split("/"); map.put(ss[0], ss[1]);
}
} catch (Exception e) {
} finally {
if (br != null)
try {br.close();
} catch (Exception e) {
}
}
// 输入年份打印这一年对应的冠军。
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个年份:");
String year = sc.nextLine();
if (map.containsKey(year)) {
System.out.println(map.get(year));
} else {
System.out.println("没有举行世界杯");
}
}
}
- 编程:综合题 从命令行中读入一个文件名,判断该文件是否存在。如果该文件存在,则在原文件相同路径下创建一个 文件名为“copy_原文件名”的新文件,该文件内容为原文件的拷贝。 例如:读入 /home/java/photo.jpg 则创建一个文件 /home/java/copy_photo.jpg 新文件内容和原文件内 容相同。
演示的代码如下:
package com.txw.test;
import java.io.*;
import java.util.Scanner;
public class Test{
/*** 创建新文件,并复制原文件中的内容*/
public static void main(String[] args){
// 键盘录入一个文件的路径
Scanner input = new Scanner(System.in);
System.out.println("请输入一个文件名:");
String fileName = input.next();
boolean result = false;
try {
result = copyFile(fileName);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(result);
}
private static boolean copyFile(String fileName) throws IOException{
File copyFile = createFile(fileName);
// 如果copyfile不是null说明新文件创建了
if(copyFile != null){
// 将源文件中的内容复制到新文件中
InputStream is = new FileInputStream(fileName);
OutputStream os = new FileOutputStream(copyFile);
byte[] bs = new byte[5];
while(true){
int c = is.read(bs);
if(c == -1) break;
os.write(bs,0,c);
}
is.close();
os.close();
return true;
}
return false;
}
// 判断该文件是否存在。如果该文件存在,则在原文件相同路径下创建一个
// 文件名为“copy_原文件名”的新文件,该文件内容为原文件的拷贝。
private static File createFile(String fileName) throws IOException {
File file = new File(fileName);
// 判断文件是否存在
if(file.exists()){
// 存在则获取该文件的父级目录
System.out.println(file.getParent());
// 创建新文件,通过父级目录+copy_+源文件名称
File copyFile = new File(file.getParent() + "\\copy_" + file.getName());
copyFile.createNewFile();
return copyFile;
}else{
System.out.println("原文件不存在");
}
return null;
}
}
6 总结