一、使用I/O操作文件
1.操作文件或目录属性
File对象既可表示文件,也可表示目录
(1)File类的构造方法
File(String path) 用指定的文件路径构造文件
File(String dir, String subpath)
在指定的目录下创建指定文件名的文件,dir参数指定目录路径,subpath参数指定文件名
File(File parent, String subpath)
根据一个文件对象和一个字文件构造文件对象,parent参数指定目录文件,subpath参数指定文件名
import java.io.File;
import java.io.IOException;
/**
* 操作文件
*/
public class TestFile {
public static void main(String[] args) throws IOException {
File f1 = new File("G:\\testFile");
//mkdir() 创建目录(如果路径不存在,则不执行) mkdirs() 创建目录(如果路径不存在,则创建路径上的目录)
createDir(f1);
File f2 = new File("G:\\testFile","a.txt");
createFile(f2);
File f3 = new File(f1,"b.txt");
createFile(f3);
//判断该文件对象是文件还是目录 文件对象.isDirectory();
System.out.println(f1.isDirectory() + "," + f2.isDirectory() + "," + f3.isDirectory()); //true,false,false
//判断该文件对象是文件还是目录 文件对象.isDirectory();
System.out.println(f1.isFile() + "," + f2.isFile() + "," + f3.isFile()); //false,true,true
//获取文件或目录的名称 文件对象.getName()
System.out.println(f1.getName() + "," + f2.getName() + "," + f3.getName()); //testFile,a.txt,b.txt
//获取文件或目录的绝对路径 文件对象.getAbsolutePath()
System.out.println(f1.getAbsolutePath() + "," + f2.getAbsolutePath() + "," + f3.getAbsolutePath()); //G:\testFile,G:\testFile\a.txt,G:\testFile\b.txt
//获取文件或目录的最后修改日期 文件对象.lastModified()
System.out.println(f1.lastModified() + "," + f2.lastModified() + "," + f3.lastModified()); //1605079154073,1605079154073,1605079154073
//获取文件或目录的大小,单位为字节 文件对象.length()
System.out.println(f1.length() + "," + f2.length() + "," + f3.length()); // 0,0,0
deleteFile(f2); //删除a.txt文件
deleteFile(f3); //删除b.txt文件
deleteFile(f1); //删除testFile目录(PS:如果目录中有文件,则不会执行删除操作)
}
static void createDir(File file){
if(!file.exists()){
file.mkdirs();
}
}
static void createFile(File file) throws IOException {
if(!file.exists()){
file.createNewFile();
}
}
static void deleteFile(File file){
if(file.exists()){
file.delete();
}
}
}
2.认识Java的流
读文件是指把文件中的数据读取到内存中,写文件是把内存中的数据写到文件中。
流是指一连串流动的字符,是以先进先出的方式发送和接收数据的通道。
如果数据输入到内存中,则称为输入流。如果从内存中输出,则称为输出流。
输入流的read()方法,取出流中的数据用输入流。
输出流的write()方法,向流中写入数据用输出流
字节流是8位通用字节流,其基本单位是字节。
字符流是16位Unicode字符流,其基本单位是Unicode字符,字符流最适合用来梳理字符串和文本。
3.字节流读写文本文件
(1)使用字节流读文本文件
FileInputStream将文本中的数据输入到内存中,可以利用它来读取文本文件中的数据。
- read() 从输入流中(要读取的内容)读取数据的下一个字节到java内部程序。
返回值为0到255的int类型的值,返回值为字符的ASCII码(如a就返回97,n就返回110)。
如果没有可用的字节,因为已经到达流的末尾,返回-1。运行一次只读一个字节。- read(byte[] b) 从输入流中(要读取的内容)读取的一定数量的字节数,并将它们存储到缓存数组b中
返回值为实际读取的字节数,运行一次读取一定数量的字节数。读取的字节数是最多等于b的长度。
读取到的第一个字节存入b[0], 下一个字节存入b[1]…
如果没有可用的字节,因为已经到达流的末尾,返回-1。如果b.length为0,返回0。- read(byte[] b, int off, int len) 读取len字节的数据从输入流到缓存字节数组。
返回值为实际读取的字节数,试图读取多达len字节,但可能读取到少于len字节。
读取到的第一个字节存入b[off], 下一个字节存入b[off+1]…
import java.io.FileInputStream;
import java.io.IOException;
/**
* 使用字节流读文本文件
*/
public class TestFileInputStream {
public static void main(String[] args) throws IOException {
//创建流对象
FileInputStream fis = null;
try{
fis = new FileInputStream("G:/testFile/a.txt");
int data;
System.out.println("可读取的字节数:" + fis.available());
while((data=fis.read()) != -1){
System.out.print((char)data); //data为int类型,a.txt中的内容是abc,如果不进行强制类型转换将会输出979898.
}
}finally {
//关闭输入流
if(fis != null){
fis.close();
}
}
}
}
(2)使用字节流写文本文件
把内存中数据输出到文件中,可以利用它把内存中的数据写入到文本文件中。
使用FileInputStream类读取文件时应注意以下几个方面:
- 在创建FileInputStream实例时,如果相应的文件并不存在,就会自动创建一个空的文件。
- 如果参数file或name表示的文件路径存在,但是代表一个文件目录,则此时会抛出FileNotFoundException类型的异常。
- 默认情况下,向文件中写数据时将覆盖文件中原有的内容。
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 使用字节流写文本文件
*/
public class TestFileOutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = null;
try{
//append设置为true,表示在文件末位添加数据。如果不填或者设置为false,表示每次向文件中写数据时将覆盖文件中原有的内容。
fos = new FileOutputStream("G:/testFile/b.txt",true);
String str = "好好学习Java";
byte[] words = str.getBytes(); //将字符串转换成字节数组
//将byte数组从偏移量0ff开始的长度为len的字节数据输出到输出流中
fos.write(words,0, words.length);
System.out.println("文件更新成功!");
}catch(IOException e){
System.out.println("创建文件失败!");
}finally {
//关闭输出流
if(fos != null){
fos.close();
}
}
}
}
4.字节流读写二进制文件
package demoIO;
import java.io.*;
/**
* 使用字节流读写二进制文件
*/
public class TestDataIOStream {
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
DataInputStream dis = null;
FileOutputStream fos = null;
DataOutputStream dos = null;
try{
fis = new FileInputStream("G:/testFile/DemoMath.class");
dis = new DataInputStream(fis);
fos = new FileOutputStream("G:/testFile/A.class");
dos = new DataOutputStream(fos);
int temp;
while((temp=dis.read()) != -1){
dos.write(temp);
}
}finally {
//关闭流
if(dos != null){
dos.close();
}
if(fos != null){
fos.close();
}
if(dis != null){
dis.close();
}
if(fis != null){
fis.close();
}
}
}
}
使用DataInputStream和DataOutputStream,按照与平台数据无关的方式在流中读写基本数据类型以及采用UTF-8编码的字符串
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* DataInputStream类 可以按照与平台无关的方式从流中读取基本数据类型的数据,readUTF()能读取采用UTF-8字符集编码的字符串
* DataOutputStream类可以按照与平台无关的方式向流中写入基本数据类型的数据,writeUTF()能写入采用UTF-8字符集编码的字符串
*/
public class TestBufferedIOStream {
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
DataInputStream dis = null;
FileOutputStream fos = null;
DataOutputStream dos = null;
try{
fis = new FileInputStream("G:/testFile/c.txt");
dis = new DataInputStream(fis);
fos = new FileOutputStream("G:/testFile/c.txt");
dos = new DataOutputStream(fos);
dos.writeByte(1);
dos.writeLong(2);
dos.writeChar('c');
dos.writeUTF("Hello");
System.out.println(dis.readByte());
System.out.println(dis.readLong());
System.out.println(dis.readChar());
System.out.println(dis.readUTF());
} catch (IOException e){
e.printStackTrace();
} finally {
if(dos != null){
dos.close();
}
if(fos != null){
fos.close();
}
if(dis != null){
dis.close();
}
if(fos != null){
fos.close();
}
}
}
}
5.字符流读写文本文件
通过字符流的方式读取文件,会使用缓冲区,提高了读文本文件的效率
(1)使用字符流读文本文件
package demoIO;
import java.io.BufferedReader;
import java.io.FileReader;
/**
* 使用字符流读文本文件
*/
public class TestReader {
public static void main(String[] args) throws IOException{
FileReader fr = null;
BufferedReader br = null;
try{
//创建一个FileReader对象
fr = new FileReader("G:/testFile/c.txt");
//创建一个BufferedReader对象
br = new BufferedReader(fr);
//读取一行数据
String line = br.readLine();
while(line != null){
System.out.println(line);
line = br.readLine();
}
}catch (IOException e){
System.out.println("文件不存在");
}finally {
//关闭流
if(br != null){
br.close();
}
if(fr != null){
fr.close();
}
}
}
}
(2)使用字符流写文本文件
package demoIO;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/**
* 使用字符流写文本文件
*/
public class TestWriter {
public static void main(String[] args) throws IOException {
FileWriter fw = null;
BufferedWriter bw = null;
try{
//创建一个FileWriter对象,写文件直接在文件后面添加
fw = new FileWriter("G:/testFile/d.txt",true);
//创建一个BufferedWriter对象
bw = new BufferedWriter(fw);
//写入信息
bw.write("你也在学Java吗?");
bw.newLine();
bw.write("是啊");
bw.newLine();
bw.write("请多多指教!");
bw.newLine();
System.out.println("使用BufferedWriter写文件完成!");
bw.flush(); //刷新缓冲区
}catch (IOException e){
System.out.println("该文件不存在!");
}finally {
//关闭流
if(bw != null){
bw.close();
}
if(fw != null){
fw.close();
}
}
}
}
6.重定向标准I/O
System.in和System.out,主要是用于从键盘接受数据以及向屏幕输出数据。
输入本来默认是键盘输入,改成其他的输入方式,就是输入重定向。
输出本来输出的位置是显示器,改成其他输出方式,就是输出重定向。
System.in常见方法
int read(),此方法从键盘结束一个字节的数据,返回值是该字符的ASCII码
int read(byte[] buf),此方法从键盘接收多个字节的数据,保存至buf中,返回值是接收字节数据的个数,非ASCII码
System.out常见方法
int print(),向屏幕输出数据,不换行,参数可以是Java的任意数据类型
int println(),向屏幕输出数据,换行,参数可以是Java的任意数据类型
(1)重定向输入
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
/**
* 重定向输入
* 1.有一个已经初始化的FileInputStream输入流
* 2.调用System.setIn()方法,将标准输入流重定向到目的输入流
* 3.读取System.in中的内容
*/
public class ReIn {
public static void main(String[] args) throws Exception{
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try{
//1.声明一个输入流
fis = new FileInputStream("G:/testFile/e.txt");
//2.重定向
System.setIn(fis);
//3.读取System.in标准输入流中的内容
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
//4.输出System.in(也就是e.txt文件)中的内容
String line;
while((line=br.readLine()) != null){
System.out.print(line);
}
} finally {
if(br != null){
br.close();
}
if(isr != null){
isr.close();
}
if(fis != null){
fis.close();
}
}
}
}
(2)重定向输出
import java.io.FileOutputStream;
import java.io.PrintStream;
/**
* 重定向输出流
* 1.初始化PrintStream对象
* 2.调用System.setOut()方法,将标准输出流重定向至PrintStream对象
* 3.操作System.out流
*/
public class ReOut {
public static void main(String[] args) throws Exception{
FileOutputStream fos = null;
PrintStream ps = null;
try{
//1.声明一个输出流PrintStream对象
fos = new FileOutputStream("G:/testFile/f.txt",true);
ps = new PrintStream(fos);
//2.重定向标准输出流
System.setOut(ps);
//3.使用PrintStream对象向流中写信息
System.out.println("测试重定向输出是否成功");
System.out.println(new ReOut());
}finally {
if(ps != null){
ps.close();
}
if(fos != null){
fos.close();
}
}
}
}
二、使用对象流读写对象信息
1.认识序列化
使用序列化将对象保存在文件中,使用反序列化从文件中读取对象信息。
Java中只有实现了java.io.Serializable接口的类的对象才能被序列化。
Serializable表示可串行的,可序列化的。
2.序列化保存对象信息
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 使用序列化将学生对象保存在文件中
*/
public class SerializableObj {
public static void main(String[] args) {
ObjectOutputStream oos = null;
FileOutputStream fos = null;
try{
//创建ObjectOutputStream输出流
fos = new FileOutputStream("G:/testFile/stu.txt");
oos = new ObjectOutputStream(fos);
List<Student> list = new ArrayList<>();
Student stu1 = new Student("lsw",22,"男");
Student stu2 = new Student("monster",21,"男");
list.add(stu1);
list.add(stu2);
//对象序列化,写入输出流
oos.writeObject(list);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(oos != null){
try{
oos.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
class Student implements Serializable {
private static final long serialVersionUID = 2883734592647323105L;
private String name;
private Integer age;
private String sex;
public Student() {
}
public Student(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
3.反序列化获取对象信息
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
/**
* 使用反序列化读取文件中的学生对象
*/
public class ReverseSerializable {
public static void main(String[] args) {
ObjectInputStream ois = null;
FileInputStream fis = null;
try{
//创建ObjectInputStream输入流
fis = new FileInputStream("G:/testFile/stu.txt");
ois = new ObjectInputStream(fis);
//反序列化,进行强制类型转换
ArrayList<Student> list = (ArrayList<Student>)ois.readObject();
//输出生成后的学生信息
for(Student stu:list){
System.out.print("姓名:" + stu.getName());
System.out.print(" 年龄:" + stu.getAge());
System.out.println(" 性别:" + stu.getSex());
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(ois != null){
try{
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try{
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:
- 如果向文件中使用序列化机制写入多个对象,那么反序列化恢复对象时,必须按照写入的顺序读取。
- 如果一个可序列化的类,有多个父类(包括直接或间接父类),则这些父类要么是可序列化的,要么有无参数的构造器,否则会抛出异常。
4.对象引用的序列化
如果一个类的成员包含其他类的对象(如班级类中包含学生类型的对象),那么当要序列化班级对象时,则必须保证班级类和学生类都是可序列化的。
序列化的算法规则如下:
- 所有保存到磁盘中的对象都有一个序列号
- 当程序试图序列化一个对象时,将会检查是否已经被序列化,只有序列化后的对象才能被转换成字节序列输出
- 如果一个对象已经被序列化,则程序直接输出一个序列号编号,而不再重新序列化