JAVA之IO流

IO流是什么

I:input

o:output

通过IO可以完成硬盘文件的读和写

 

IO流分类

按流的方向分类:

  1. 往内存中去,叫输入(Input),或者叫读(Read)   输入流
  2. 从内存中出来,叫做(Output),或者叫写(Write)  输出流

按读取数据方式不同分类:

  1. 按照字节的方式读取数据,一次读取一个字节(这种流是万能的,可以读取文本、图片、音视频)  字节流
  2. 按照字符的方式读取数据,一次读取一个字符(这种流只能读取普通txt文件)  字符流

IO流四大家族

四大家族的首领:

以stream结尾的是字节流

java.io.InputStream  字节输入流

java.io.OutputStream  字节输出流

以Reader/Writer结尾的是字符流

java.io.Reader  字符输入流

java.io.Writer  字符输出流

以上四个类均是抽象类

所有的流都实现了java.io.Closeable接口,都是可关闭的,都有close()方法,流是一个内存和硬盘之间的通道,如果一直保持打开而不关闭,会耗费很多资源。

所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush方法,输出流在最终输出之后,一定要flush一下,把通道中剩余的未输出数据输出。

 

java.io包下需要掌握的流

文件专属

java.io.FileInputStream

java.io.FileOutputStream

java.io.FileReader

java.io.FileWriter

转换流:字节流转换位字符流

java.io.InputStreamReader

java.io.OutputStreamWriter

缓冲流

java.io.BufferedReader

java.io.BufferedWriter

java.io.BufferedInputStream

java.io.BufferedOutputStream

数据流

java.io.DataInputStream

java.io.DataOutputStream

标准输出流

java.io.ObjectInputStream

java.io.ObjectOutputStream

对象专属流

java.io.PrintWriter

java.io.PrintStream

 

FileInputStream类

构造方法

 
Constructor and Description
FileInputStream(File file)

通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

FileInputStream(FileDescriptor fdObj)

创建 FileInputStream通过使用文件描述符 fdObj ,其表示在文件系统中的现有连接到一个实际的文件。

FileInputStream(String name)

通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

打开一个文件,并一个字节一个字节地读取数据

public class test{
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("C:\\Users\\dell\\Desktop\\demo.txt");
            //也可以这样
            //FileInputStream fis = new FileInputStream("C:/Users/dell/Desktop/demo.txt");
            while(true){
                int readData = fis.read();
                //若读到文件末尾,fis.read方法会返回-1
                if(readData == -1){
                    break;
                }
                System.out.println(readData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

以上方法存在一个明显的缺点:一个字节一个字节地读取,硬盘和内存交互太频繁,大部分时间浪费在了交互上了。

read方法的重载方法中有一个read(byte [])方法,可以一次读多个字节。这个方法的返回值是每次读取到的字节数,若没有读取到字节,则返回-1。

修改后的程序的读取部分如下:

byte []bytes = new byte[4];
while(true){
int readCount = fis.read(bytes);
if(readCount == -1){
    break;
      }
System.out.println(new String(bytes));

这样做还有一个问题是:假设有下面这种情况,有6个字节abcdef,第一次读取后bytes中是[a,b,c,d],文件中还剩ef没有被读取,之后再次读取文件时,会读取ef,由于bytes已经满了,读取到的ef会覆盖掉ab,最后输出的将是efcd,这是我们不想看到的。

也就是说我们不应该将byte数组全部转换为字符串输出,而是读到几个字节,就将几个字节转换为字符串,需要调用构造方法

String(byte[] bytes, int begin,int length );

修改后的程序如下(最终版

byte []bytes = new byte[4];
            while(true){
                int readCount = fis.read(bytes);
                if(readCount == -1){
                    break;
                }
                System.out.println(new String(bytes,0,readCount));
            }

FileInputStream中的其他方法

int available()获取当前文件中还未读的字节数,便于创建合适大小的byte数组(不适合大文件)

long skip(int num)跳过几个字节不读取

 

FIleOutputStream类

构造方法
FileOutputStream(File file)

创建文件输出流以写入由指定的 File对象表示的文件。

FileOutputStream(File file, boolean append)

创建文件输出流以写入由指定的 File对象表示的文件。

FileOutputStream(FileDescriptor fdObj)

创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。

FileOutputStream(String name)

创建文件输出流以指定的名称写入文件。

FileOutputStream(String name, boolean append)

创建文件输出流以指定的名称写入文件。

模板

public class test{
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            //demo.txt不存在会先创建一个!
            //若存在,则先将原文件清空
            fos = new FileOutputStream("C:\\Users\\dell\\Desktop\\demo.txt");
            byte []bytes = {97,98,99,100,101,102};
            //将byte数组全部写入
            fos.write(bytes);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(fos !=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

如果是以追加方式,可以使用构造函数

fos = new FileOutputStream("C:\\Users\\dell\\Desktop\\demo.txt",true);


FileReader类

文件文本输入流,只能读取普通文本。

读取文本内容时,比较方便,快捷。

构造方法
FileReader(File file)

创建一个新的 FileReader ,给出 File读取。

FileReader(FileDescriptor fd)

创建一个新的 FileReader ,给定 FileDescriptor读取。

FileReader(String fileName)

创建一个新的 FileReader ,给定要读取的文件的名称。

读取文件:和FileInputStream类似,只不过byte数组变成char数组了

public class test{
    public static void main(String[] args){
        FileReader fr = null;
        try {
            fr = new FileReader("Demo/src/demo.txt");
            int readCount = 0;
            char []chars = new char[4];//一次读四个字符
            while((readCount = fr.read(chars)) != -1){
                System.out.println(new String(chars,0,readCount));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(fr != null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

输出结果为

 中文是两个字节,也被读进内存了,如果是用FileInputStream则可能会出现乱码,因为FileInputStream每次只读一个字节。

 

 

FileWriter类

文件字符输出流。

只能输出普通文本。

除了简单的输出到文件,FileWrite还提供了很多更方便的方法

例如

write(char []args)

将字符数组输出到文件

write(String s)

将字符串输出到文件

 构造方法

FileWriter(File file)

给一个File对象构造一个FileWriter对象。

FileWriter(File file, boolean append)

给一个File对象构造一个FileWriter对象。

FileWriter(FileDescriptor fd)

构造与文件描述符关联的FileWriter对象。

FileWriter(String fileName)

构造一个给定文件名的FileWriter对象。

FileWriter(String fileName, boolean append)

构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。

BufferedReader类

带有缓冲区的字符输入流。

使用这个流的时候不需要自定义char数组,或者byte数组。

 

构造函数

BufferedReader(Reader in)    创建一个使用默认大小输入缓冲区的缓冲字符留对象

BufferedReader(Reader in, int sz)    创建一个使用指定大小输入缓冲区的缓冲字符留对象

新概念:当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做节点流,外部负责包装的这个流叫包装流 

关闭包装流,其里面的节点流自动关闭!

常用方法

1.readLine() 读取一行,到达文件末尾返回null

2.使用字节流来初始化BufferedReader的方法(需要先将字节流对象转换为字符流对象才可以)

首先创建一个字节流对象,用这个对象来初始化一个InputStreamReader对象,再用这个InputStreamReader对象来初始化BufferedReader对象即可

BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(文件路径)))

BufferedWrite类

与BufferedReader类类似 

 

DataOutputStream类

这个流可以将数据连同数据的类型一并写入文件。

注意:这个文件不是普通的文本文档(这个文件使用记事本打开会是乱码)

构造方法

DataOutputStream(OutputStream out);

 常用方法

public class test{
    public static void main(String[] args) {
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("data"));
            int i =1;
            long l = 2L;
            short s = 3;
            byte b = 4;
            float f= 3.0F;
            double d = 3.14;
            boolean gender = false;
            char c= 'a';
            dos.writeInt(i);
            dos.writeLong(l);
            dos.writeShort(s);
            dos.writeByte(b);
            dos.writeFloat(f);
            dos.writeDouble(d);
            dos.writeBoolean(gender);
            dos.writeChar(c);

            //清空缓冲区
            dos.flush();

            //关闭流
            dos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

打开输出的data文件后发现是乱码

 

DataInputStream类

DataOutputStream 写的文件,只能是用DataInputStream去读取文件。并且读的顺序需要和写的顺序一致才可以正常取出数据。

 

 读取文件

public class test{
    public static void main(String[] args) {
        DataInputStream dos = null;
        try {
            dos = new DataInputStream(new FileInputStream("data"));
            int i =dos.readInt();
            long l = dos.readLong();
            short s = dos.readShort();
            byte b = dos.readByte();
            float f= dos.readFloat();
            double d = dos.readDouble();
            boolean gender = dos.readBoolean();
            char c= dos.readChar();
            System.out.println(i);
            System.out.println(l);
            System.out.println(s);
            System.out.println(b);
            System.out.println(f);
            System.out.println(d);
            System.out.println(gender);
            System.out.println(c);
            
            //关闭流
            dos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

 

PrintStream类

标准字节输出流,默认输出到控制台

构造函数

PrintStream(OutputStream out);

最常用的一个类。。。

public class test{
    public static void main(String[] args) {
    //out本来就是一个PrintStream类型的属性
    PrintStream ps = System.out;
    ps.println("hello world");

    }
}

重定向输出

public class test{
    public static void main(String[] args) {
        try {
            //创建一个通向log文件的流
            PrintStream printStream = new PrintStream(new FileOutputStream("log"));
            //将输出方向修改到"log"文件
            System.setOut(printStream);
            System.out.println("hello world");
        
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
}

实现一个日志工具

public class test{
    public static void log(String msg) {
        try {
            //创建一个指向log.txt的对象
            PrintStream ps = new PrintStream(new FileOutputStream("log.txt"));
            //修改输出方向为log.txt
            System.setOut(ps);
            //获取当前日期,并格式化
            Date date = new Date();
            SimpleDateFormat sdfs = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String nowTime = sdfs.format(date);
            //输出当前时刻发生的事情
            System.out.println(nowTime+":"+msg);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
}

 

File类

File类和四大家族没有关系,所以File类不能完成文件的读和写

File是文件和目录路径名的抽象表示形式,例如:

C:\Drivers 这是一个File对象

C:\Drivers\demo.txt 这也是一个File对象

 

File类常用的方法

boolean exists()

判断文件是否存在

boolean createNewFile()

创建一个文件,文件名为File对象初始化时的参数

File f = new File("Demo/src/file");
f.createNewFile();

boolean mkdirs()

以多层目录的形式创建

File f = new File("Demo/src/a/b/c/d");
f.mkdirs();

String getParent()

获取文件的父路径

String getAbsolutePath()

获取绝对路径

boolean delete()

删除文件

String getName()

获取文件名

boolean isDirectory()

判断是否是一个目录

boolean isFile()

判断是否是一个文件

long lastModified()

返回文件最后修改时间,返回值为毫秒,可以作为参数创建一个Date对象来返回日期。

long length()

获取文件大小

File[] listFiles()

获取当前目录下的子文件

序列化与反序列化

序列化(Serialize):java对象存储到文件中,将java对象的状态保存下来的过程

反序列化(DeSerialize):将硬盘上的数据重新恢复到内存中,恢复成java对象

  1. 使用ObjectOutputStream和ObjectInputStream分别进行序列化和反序列化
  2. 通过序列化和反序列化的对象,必须实现Serializable接口

Serializable接口如下:

这个接口啥都没有,只是起到标识的作用,这个标识是给jvm参考的,jvm会给实现Serializable的类自动生成一个序列化版本号,就算两个类名字一模一样,他们的序列化版本号也是不同的

自动生成序列化版本号有什么缺陷呢?

一旦代码确定后,不能进行后续修改。因为只要进行修改,必然重新编译,此时会生成全新的序列化版本号

 

序列化:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class test{
    public static void main(String []args) throws IOException {
        Student s = new Student(1111,"张三");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
        //写入文件
        oos.writeObject(s);
        //刷新缓冲区
        oos.flush();
        //关闭流
        oos.close();
    }
}
//要进行序列化的类必须
class Student implements Serializable {
    private int no;
    private String name;

    public Student(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Student() {
    }

}

反序列化:

public class test{
    public static void main(String []args) throws IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
        try {
            Object obj = ois.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        ois.close();
    }
}

一次性序列化多个对象

writeObject方法可以接受一个集合作为参数

public class test{
    public static void main(String []args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
        ArrayList<Student> sList = new ArrayList<>();
        sList.add(new Student(1,"张三"));
        sList.add(new Student(2,"李四"));
        sList.add(new Student(3,"王五"));
        oos.writeObject(sList);
        oos.flush();
        oos.close();

    }
}

一次性反序列化多个对象

public class test{
    public static void main(String []args) throws IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
        try {
            Object obj = ois.readObject();
            if(obj instanceof List){
                List<Student> list = (List<Student>) obj;
                for(Object it: list){
                    System.out.println(it);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

transient关键字

加在属性前面,默认该属性不参与序列化

例如

private transient String name;

在序列化的过程中,name的值不会保存到文件里。

自动生成序列化版本号的缺陷

一旦代码确定后,不能进行后续修改。因为只要进行修改,必然重新编译,此时会生成全新的序列化版本号,jvm会认为这是一个全新的类。

所以我们需要给实现Serializable接口的类提供一个固定不变的序列化版本号。

声明序列化版本号的语法:

private static final long serialVersionUID = 8683452581122892189L;
//jvm识别一个类的时候,先通过类名,如果类名一致再通过序列化版本号进行识别

使用IDEA自动生成序列化版本号

找到设置

打上对勾后,点击apply,就可以了。

在我们想生成的类的类名处按alt+Enter,即可生成序列化版本号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值