一,单元概述
程序的主要任务是操纵数据。在运行时,这些数据都必须位于内存中,并且属于特定的类型,程序才能操纵它们。本章介绍如何从数据源中读取数据以供程序处理,以及如何把程序处理后的数据写到数据目的地中。Java执行文件读写操作都是通过对象实现的,读取数据的对象称为输入流(input stream),写入数据的对象称为输出流(output stream)。流是Java语言中处理输入输出(I/O)的方式,采用流的方式,可以更加方便快捷地处理与存储不同类型的数据。
Java的输入输出流包括字符流和字节流,它们充分利用了面向对象的继承性,执行读写数据的共用操作在超类中定义,子类提供特殊的操作。使用缓冲流包装输入输出流执行读写操作可以提高读写数据的效率。序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
二,知识要点
(1) 文件管理
- 文件管理概述
- File类
(2) 流的概念及API
- 流的概念
- 字节流中的层次结构图
- 字节流中的主要方法
- 字符流中的层次结构图
- 字符流中的主要方法
(3) 节点流与处理流的使用
- 什么是节点流
- 节点流的方法
- 文件的访问
- 什么是处理流
- 常见的处理流类
(4) 对象的序列化
- 对象序列化概述
- 支持序列化的接口和类
- 对象序列化的条件
三,教学重点与难点
重点:
(1) File类
(2) 输入输出流
(3) 字节流中的主要方法
(4) 字符流中的主要方法
难点:
(1) 对象序列化概述
(2) 支持序列化的接口和类
(3) 对象序列化的条件
3.1文件管理
3.1.1文件管理概述
Java中的对文件的管理,通过java.io包中的File类实现。Java中文件的管理,主要是针对文件或是目录路径名的管理,包括文件的属性信息,文件的检查,文件的删除等,但不包括文件的访问。
3.1.2 File类
Java类库中的File类可以对文件系统中的文件和目录进行操作,它属于java.io这个包。一个File对象可以代表一个文件,也可以代表一个目录。创建了一个File对象后,如果是目录,可以显示目录清单,新建或删除目录;如果是文件,可以查询文件的属性和路径信息,也可以输出和改名,但没有拷贝功能。拷贝属于文件的读写,要用I/O流类来解决。 File类能够解决文件管理的所有问题,下面就来介绍一下这个类。
File类的数据成员主要有下列4个,它们都是类变量:
static String pathSeparator “;” 路径分隔符
static Char pathSeparatorChar ‘;’ 路径分隔符,字符分号而不是字符串
static String separator “\” 路径表达式中的分隔符,如WINDOWS系统中是反斜杠
static Char separatorChar ‘\’ 字符型的路径表达式中的分隔符(反斜杠)
(1)File类的构造方法
public java.io.File(String pathName)
public java.io.File(String parent,String fileName)
public java.io.File(File parent,String fileName)
第一个构造方法通过全路径文件名来创建对象,pathName可以是绝对路径也可以是相对的。
第二个构造方法通过父目录和文件名来创建对象,fileName是不含路径的文件名。
第三个构造方法也是通过父目录和文件名来创建对象,但父目录由一个File对象提供。
【例1】本例程演示用三种构造方法创建File对象。
import java.io.*;
class FileCons {
public static void main(String[] args) {
//第一种方式
File path=new File("c:\\");
File file1=new File("c:\\sss.txt");
//第二种方式
File file2=new File("c:\\","sss.txt");
//第三种方式
File file3=new File(path,"sss.txt");
}
}
程序分析:path,file1、file2、file3分别使用三种构造方法来创建File类对象。
(2)File类中的常用方法
File类常用方法如表3-1所示。
表3-1所示File类常用方法
方法 | 含义 |
---|---|
boolean createNewFile() | 当且仅当不存在具有此抽象路径名指定的名称的文件时,创建由此抽象路径名指定的一个新的空文件。 |
static File createTempFile(String prefix,String suffix) | 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称 |
static File createTempFile(String prefix,Stirng suffix,File directory) | 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称 |
boolean exists() | 测试此抽象路径名表示的文件或目录是否存在 |
boolean delete() | 删除此抽象路径名表示的文件或目录 |
boolean equals(Object obj) | 测试此抽象路径名与给定对象是否相等 |
boolean canRead() | 测试应用程序是否可以读取此抽象路径名表示的文件 |
boolean canWrite() | 测试应用程序是否可以修改此抽象路径名表示的文件 |
String[] list() | 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组 |
String getAbsolutePath() | 返回抽象路径名的绝对路径名字符串 |
String getName() | 返回由此抽象路径名表示的文件或目录的名称,不包括路径名称 |
String getPath() | 将此抽象路径名转换为一个路径名字符串 |
File[] listFiles() | 返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件 |
boolean renameTo(File dest) | 重新命名此抽象路径名表示的文件 |
long length() | 返回由此抽象路径名表示的文件的大小,以byte为单位 |
boolean mkdir() | 创建此抽象路径名指定的目录 |
boolean mkdirs() | 创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。注意,如果此操作失败,可能已成功创建了一些必需的父目录 |
【注意】File类中的一些方法声明抛出异常,因此在调用这些方法时必须使用try…catch语句块。
下面分类详细介绍下其中常用方法的使用。
①创建和删除
利用File对象可以很方便地创建和删除目录,也可以创建一个空文件和删除文件。这些方法都返回boolean值以告知操作是否成功。方法列表如下:
boolean mkdir() 创建一个新目录,不包括子目录
boolean mkdirs() 创建目录及子目录,即一次可以创建多级目录
boolean createNewFile() 创建一个新的空文件
boolean delete() 删除一个空目录或文件
boolean renameTo(File) 目录或文件改名
如果要删除的目录非空,目录无法成功删除,但不抛出异常。如果要删除的文件或目录不存在则不抛出异常。如果要创建的空文件名已经存在,也不抛出异常。下面的示例演示创建工作。
【例2】 创建一个目录。
import java.io.*;
public class DirCreate {
public static void main(String[] args) {
//1、文件实例化 (path,filename)
File path=new File("d:/java/io");//目录
//2、如果目录不存在
if(!path.exists()){
System.out.println("目录不存在,开始创建目录");
//2.1先创建目录 注意:mkdir一次只能创建一个 mkdirs可以一次创建多级目录(目录及父目录)
//path.mkdir();
path.mkdirs();
}
}
}
程序运行结果:在C:\root$dir下创建如下目录:
程序分析:mkdir()是创建一级目录,mkdirs创建目录及子目录
createNewFile()创建文件。
【例3】 删除上例建立的所有子目录和文件,留下“根目录”并改名。
import java.io.*;
class Test03 {
public static void main(String[] args) {
//1、文件实例化
File path=new File("d:/java/io");//目录
//2、如果目录不存在
if(!path.exists()){
System.out.println("目录不存在,开始创建目录");
//2.1先创建目录 注意:mkdir一次只能创建一个 mkdirs可以一次创建多级目录(目录及父目录)
//path.mkdir();
path.mkdirs();
}
String filename="text01.txt";
File f=new File(path,filename);
if(f.exists()){ //如果文件存在 打印文件已存在
System.out.println("文件已存在,可以进行后续操作了");
}else{//如果文件不存在 就需要创建文件
try {
boolean result=f.createNewFile();
if(result){//result==true
System.out.println("文件创建成功");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第一次
目录不存在,开始创建目录
文件创建成功
第二次
目录已存在
文件已存在,可以进行后续操作了
②文件属性测试
File类提供了许多方法给出File对象所对应的文件的各种属性。其中一类是判断性的,大多数无参数都返回boolean值。这些方法有:
canRead()可读否?
canWrite() 可写否?
exists() File对象存在吗?
isDirectory() 是目录吗?
isFile() 是文件吗?
isAbsolute()有File(parent, filename)构建器创建对象时给出的是绝对路径吗?
isHidden()是隐含文件吗?
SetReadOnly()设置为只读文件吗
另一类返回字符串,如文件名和路径等。它们有:
getName() 不含路径的文件名
getPath() 路径文件名
getParent() 父目录名
getAbsolute() 返回绝对路径
toString() 返回File对象的信息
最后还有几个方法是:
long length() 返回文件长度(字节数)
long lastModified() 返回文件的最后修改时间
int compareTo(File f) 比较两个File对象,而不是比较文件内容
boolean equals(Object o) 自Object类继承而来的方法
下面我们有一个程序来演示所有这些方法的用法。
【例4】 测试或获取文件属性信息。
import java.io.File;
import java.io.IOException;
public class File1Properties {
static void printProperty(File f){
System.out.println("可读属性:" + f.canRead());
System.out.println("可写属性:" + f.canWrite());
System.out.println("文件路径:" + f.getPath());
System.out.println("文件名字:" + f.getName());
System.out.println("绝对路径:" + f.getAbsolutePath());
System.out.println("是否隐藏:" + f.isHidden());
System.out.println("文件大小" + f.length());
System.out.println("最后一次修改时间:" + f.lastModified());
System.out.println("-----------------------------------------");
}
public static void main(String[] args){
//1、文件实例化 (path,filename)
File path=new File("d:/java/io");//目录
//2、如果目录不存在
if(!path.exists()){
System.out.println("目录不存在,开始创建目录");
//2.1先创建目录 注意:mkdir一次只能创建一个 mkdirs可以一次创建多级目录(目录及父目录)
//path.mkdir();
path.mkdirs();
}
String filename="text01.txt";
File f=new File(path,filename);
if(f.exists()){ //如果文件存在 打印文件已存在
System.out.println("文件已存在,可以进行后续操作了");
printProperty(f);
}else{//如果文件不存在 就需要创建文件
try {
boolean result=f.createNewFile();
if(result){//result==true
System.out.println("文件创建成功");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
程序运行后会显示如下:
文件已存在,可以进行后续操作了
可读属性:true
可写属性:true
文件路径:d:\java\io\text01.txt
文件名字:text01.txt
绝对路径:d:\java\io\text01.txt
是否隐藏:false
文件大小0
最后一次修改时间:1650416246331
-----------------------------------------
程序分析:本例演示了File类的常用方法和属性。
③显示目录清单
显示目录清单用list()方法,它有两种形式。一种是无参数的,它返回File对象的所有文件和子目录。另一个用过滤器参数,只返回符合条件的文件和子目录列表。
- String[] list()
【例3-2】 简单的目录列表,显示当前目录的父目录的清单。
import java.io.*;
public class DirList {
public static void main(String args[]) {
File path = new File("d:\\java24");
if (path.exists()) {// 目录存在
// 列出目录下的所有文件
File[] files = path.listFiles();
// 循环打印各文件情况 用for循环
for (File f : files) {
// 可以通过get方法获取一些属性
System.out.println(f.getName());
}
}
}
程序运行结果:
第11章文件与流.docx
第12章 多线程编程.docx
第13章 网络编程.docx
第14章 Java7新特性简介.docx
第15章 JDBC.docx
程序分析:..代表当前目录的父目录。
程序分析:
类TestFile创建了File类的两个对象path和f,通过这两个对象去访问当前目录下已经存在的path目录和path目录下的text01.txt文件。程序利用对象中的方法,可以打印对象的一些常用属性,也可以在用完之后执行删除操作
3.2流的概念及API
3.2.1 流的概念
流(Stream)的概念代表的是程序中数据的流通,数据流是一串连续不断的数据的集合。在Java程序中,对于数据的输入/输出操作是以流(Stream)的方式进行的。可以把流分为输入流和输出流两种。程序从输入流读取数据,向输出流写入数据。在java程序中,从输入流读取数据(读到内存中),而从输出流输出数据(从内存存储到文件或显示到屏幕上),参见图3-1
如果数据流中最小的数据单元是字节,那么称这种流为字节流;如果数据流中最小的数据单元是字符,那么称这种流为字符流。在I/O类库中,java.io.InputStream和java.io.OutputStream分别表示字节输入流和字节输出流,java.io.Reader和java.io.Writer分别表示字符输入流和字符输出流。
Java中的流可以按如下方式分类:
- 按流的方向不同分为:输入流、输出流
- 按处理数据的单位不同分为:字节流、字符流
- 按功能不同分为:节点流、处理流
Java语言中,控制数据流的类都放在java.io包中,java.io包中有两大继承体系。以byte处理为主的Stream类,他们的命名方式是XXXStream;以字符处理为主的Reader / Writer类,他们的命名方式XXXReader或XXXWriter。InputStream、OutputStream、Reader、Writer这四个类,是这两大继承体系的父类,如表3-2所示。
表3-2 java.io包中有两大继承体系
3.2.2 InputStream字节输入流的层次结构图与常用方法
(1)层次结构
在java.io包中,java.io.InputStream表示字节输入流,java.io.OutputStream表示字节输出流,它们都是抽象类,不能被实例化。所有字节输入流都是InputStream类的直接或者间接子类。输入流的类层次结构如图3-2所示。
(2)常用方法
InputStream是表示输入字节流的所有类的超类,InputStream类常用的方法如表3-3所示。
表3-3 InputStream类常用的方法
方法 | 含义 |
---|---|
int read() | 一次读取一个byte的数据,并以int类型把数据返回来,如果没有数据可以读了,会返回”-1” |
int read(byte[] buffer) | 把所读取到的数据放在这个byte数组中,返回一个int型的数据,这个int型数据存储了返回的真正读取到的数据byte数 |
int read(byte[] buffer,int offset,int length) | 读取length个字节,并存储到一个字节数组buffer中,并从offset位置开始返回实际读取的字节数 |
void close() | 关闭此输入流并释放与该流关联的所有系统资源 |
3.2.3OutputStream字节输出流的层次结构图与常用方法
(1)层次结构
所有字节输出流都是OutputStream类的直接或者间接子类,输出流的类层次结构如图3-3所示。
(2)常用方法
OutputStream是表示输出字节流的所有类的超类,OutputStream类常用的方法如表3-4所示。
表3-4 OutputStream类常用的方法
方法 | 含义 |
---|---|
void write(byte[] buffer) | 将要输出的数组先放在一个byte数组中,然后用这个方法一次把一组数据输出出去 |
void write(byte[] buffer,int off,int len) | 将指定字节数组中从偏移量off 开始的 len 个字节写入此输出流 |
abstract void write(int b) | 将指定的字节写入此输出流 |
void close() | 关闭此输出流并释放与此流有关的所有系统资源 |
void flush() | 刷新此输出流并强制输出所有缓冲的输出字节 |
(3)Bufffer**常用方法
参考手册
学生练习查看手册
(4)字符流操作
注意:存文件时要用utf-8编码,否则与我们的工作区编码方式 不匹配,出现乱码
4.1读字符文件
@Test
public void test07() throws IOException {
//文本文件的编码方式改为utf-8
// BufferedReader(Reader in) Reader我们选的是FileReader--->FileReader(File
// file)
File file = new File("d:/java24/text01.txt");//3
FileReader fr = new FileReader(file);//2
// 理解为是一个工具类,他专门用来读文本文件内容
BufferedReader br = new BufferedReader(fr);//1
while(true) {//4
String str = br.readLine();// 一次读取一行文本
if (str != null) {
System.out.println(str);
} else {
break;
}
}
}
4.2写字符流文件
@Test
public void test08() throws IOException {
//确认文件是utf-8编码
File file=new File("d:/java24/text02.txt");
FileWriter fileWriter=null;
//第一个参数是为是否append
BufferedWriter bw=null;
try {
fileWriter = new FileWriter(file,true);//第二个参数是表示是否向文件中追加内容
bw=new BufferedWriter(fileWriter);
bw.write("问:没声音吗1111111111");//写文本
bw.newLine();//换行
//bw.flush();//冲下去
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{//记着关闭流
//如果调用了关闭流的方法,就不用手动调bw.flush()
bw.close();
fileWriter.close();
}
}
4.3文本文件拷贝
@Test
public void test09() throws IOException{
//从d:/java24/text01.txt读
//每读到一行,向d:/java24/text02.txt写入
//step1 初始化输入输出流
FileReader fr=null;
BufferedReader br=null;
FileWriter fw=null;
BufferedWriter bw=null;
try {
System.out.println("开始初始化输入输出流");
//初始化输入流
File inputFile=new File("d:/java24/text01.txt");
fr=new FileReader(inputFile);
br=new BufferedReader(fr);
//初始化输出流
fw=new FileWriter("d:/java24/text02.txt",true);
bw=new BufferedWriter(fw);
//step2 死循环从输入流(也就是text01.txt)一行一行读
while(true){
//一次读取一行
String content=br.readLine();
if(content!=null){//如果读到的内容不为null
System.out.println("读到了一行,开始写入");
bw.write(content);
bw.newLine();
}else{
System.out.println("拷贝完成");
break;
}
}
} catch (Exception e) {
// 打印异常
e.printStackTrace();
}finally{
//关闭流
bw.close();
fw.close();
br.close();
fr.close();
}
}
(5)字节流操作
//字节流拷贝
@Test
public void test07(){
//FileInputStream BufferedInputStream
//FileOutputStream BufferedOutputStream
File file=new File("E:" + File.separator + "java6" + File.separator + "filestudy", "01.png");
FileInputStream in=null;
BufferedInputStream bis=null;
File file2=new File("E:" + File.separator + "java6" + File.separator + "filestudy", "10.png");
FileOutputStream out=null;
BufferedOutputStream bos=null;
try {
in=new FileInputStream(file);
bis=new BufferedInputStream(in);
out = new FileOutputStream(file2);
bos=new BufferedOutputStream(out);
//读取到的字节的长度
int b;
//bis.read() 每次读取到的字节数,注意此处给b重新赋值 ,否则死循环
while((b=bis.read())!=-1){
//把b长度的字节写入到bos里
bos.write(b);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
bos.close();
out.close();
bis.close();
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3.1对象序列化和反序列化
step1 新建一个可序列化的对象
Dept.java
1)implements Serializable
2)自动生成一个id:
private static final long serialVersionUID = 5321630967988198440L;
public class Dept implements Serializable{
/**
* 为他指定一个id
*/
private static final long serialVersionUID = -6773472139020270906L;
//部门编号
private int deptNo;
//部门名称
private String dname;
//部门所在地
private String loc;
public int getDeptNo() {
return deptNo;
}
public void setDeptNo(int deptNo) {
this.deptNo = deptNo;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
}
step2 新建一个文本文件
step3 序列化
也是文件操作
序列化涉及的流
低级流:FileOutputStream FileInputStream
高级流:ObjectOutputStream ObjectInputStream
把对象写入到—->txt文件里,用到的是FileOutputStream ObjectOutputStream
ObjectOutputStream.writeObject(Object o)
样例代码
@Test
public void test01() throws IOException{
//ObjectOutputStream writeObject(Object obj)
//为ObjectOutputStream准备一个低级流 FileOutputStream
FileOutputStream fos=null;
//step1 实例化一个工具ObjectOutputStream
ObjectOutputStream oos=null;
try {
System.out.println("开始实例化输出流对象");
File file=new File("d:/java24/dept01.txt");
fos=new FileOutputStream(file);
oos=new ObjectOutputStream(fos);
System.out.println("实例化输出流对象结束");
//--把dept对象存到dept01.txt
//step 2 准备一个dept01对象
System.out.println("准备一个dept对象");
Dept dept10=new Dept();
dept10.setDeptNo(10);
dept10.setDname("部门10");
dept10.setLoc("沈阳");
//step 3把对象持久化到文件里
if(dept10!=null){
System.out.println("开始序列化对象");
oos.writeObject(dept10);
System.out.println("序列化对象结束");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
oos.close();
}
}
step4 对象反序列化
是序列化的反过程
对象反序列化其实是从 txt—>Object (解密)
关键方法
ObjectInputStream.readObject()
@Test
public void test02() throws IOException{
FileInputStream fis=null;
//step1 实例化一个工具ObjectInputStream
ObjectInputStream ois=null;
try {
System.out.println("开始准备工具类");
File file=new File("d:/java24/dept01.txt");
fis=new FileInputStream(file);
ois=new ObjectInputStream(fis);
System.out.println("开始反序列化对象");
Dept dept=(Dept) ois.readObject();
if(dept!=null){
System.out.println(dept.getDname());
}
} catch (Exception e) {
// TODO: handle exception
}finally{
ois.close();
}
}
课后习题:
简答题:
- 拷贝一张 照片 从C 盘根目录到 D 盘根目录 (使用 节点流 实现 )
- 拷贝一张 照片 从C 盘根目录到 D 盘根目录 (使用 处理流 实现 )
创建文件 Demo1.txt 写入文本 hello 创建文件 Demo2.txt 写入文本 Neuedu
将两个文件内容 提取出来输出到 第三个文件 Test.txt 通过 文件与流方式实现
在本机的磁盘系统中,找一个文件夹,利用File类的提供方法,列出该文件夹中的所有文件的文件名和文件的路径,执行效果如下:[必做题]
路径是xxx的文件夹内的文件有:
文件名:abc.txt
路径名:c:\temp\abc.txt
——————————————————————
文件名:def.txt
路径名:c:\temp\def.txt