JAVA学习笔记(8)Java中的I/O流总结

I/O概述

输入输出是根据主存设定的。

按数据流向分类
输入流和输出流

按处理的数据类型分类
字节流和字符流

Java提供了抽象类,字节流以Stream结尾,字符流以Reader和Writer结尾
InputStream
OutputSteam
Reader
Writer

在这里插入图片描述

16种常用输入输出流

在这里插入图片描述

使用需要捕获异常,并且需要close和flush,输入输出流类,相当于和文件系统之间打通了管道,使用之后要关闭,防止内存泄漏

面试题:close()和flush()的区别?

A:close()关闭流对象,但是先刷新一次缓冲区,关闭之后,流对象不可以继续再使用了。
B:flush()仅仅是刷新缓冲区(一般写字符时要用,因为字符是先进入的缓冲区),流对象还可以继续使用

File类

File是对文件系统上的文件和目录的类,具体对象就是某一个文件或者目录,是否存在可以根据exist方法判断,包括创建目录,获取父目录,获取当下所有File对象等等。

构造方法

File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname)
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
File(String parent, String child)
从父路径名字符串和子路径名字符串创建新的 File实例。
File(URI uri)
通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。

对于文件目录操作的常用方法:

boolean	canExecute()
测试应用程序是否可以执行此抽象路径名表示的文件。
boolean	canRead()
测试应用程序是否可以读取由此抽象路径名表示的文件。
boolean	canWrite()
测试应用程序是否可以修改由此抽象路径名表示的文件。

int	compareTo(File pathname)
比较两个抽象的路径名字典。

boolean	createNewFile()
当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。

static File	createTempFile(String prefix, String suffix)
在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。
static File	createTempFile(String prefix, String suffix, File directory)
在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。

boolean	delete()
删除由此抽象路径名表示的文件或目录。

boolean	exists()
测试此抽象路径名表示的文件或目录是否存在。

File	getAbsoluteFile()
返回此抽象路径名的绝对形式。
String	getAbsolutePath()
返回此抽象路径名的绝对路径名字符串。

File	getCanonicalFile()
返回此抽象路径名的规范形式。
String	getCanonicalPath()
返回此抽象路径名的规范路径名字符串。

long	getFreeSpace()
返回分区未分配的字节数 named此抽象路径名。
String	getName()
返回由此抽象路径名表示的文件或目录的名称。

String	getParent()
返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。
File	getParentFile()
返回此抽象路径名的父,或抽象路径名 null如果此路径名没有指定父目录。

String	getPath()
将此抽象路径名转换为路径名字符串。

boolean	isAbsolute()
测试这个抽象路径名是否是绝对的。
boolean	isDirectory()
测试此抽象路径名表示的文件是否为目录。
boolean	isFile()
测试此抽象路径名表示的文件是否为普通文件。
boolean	isHidden()
测试此抽象路径名命名的文件是否为隐藏文件。

long	lastModified()
返回此抽象路径名表示的文件上次修改的时间。

long	length()
返回由此抽象路径名表示的文件的长度。

String[]	list()
返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
String[]	list(FilenameFilter filter)
返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[]	listFiles()
返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
File[]	listFiles(FileFilter filter)
返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。
File[]	listFiles(FilenameFilter filter)
返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。
static File[]	listRoots()
列出可用的文件系统根。
boolean	mkdir()
创建由此抽象路径名命名的目录。
boolean	mkdirs()
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
boolean	renameTo(File dest)
重命名由此抽象路径名表示的文件。

boolean	setExecutable(boolean executable)
为此抽象路径名设置所有者的执行权限的便利方法。
boolean	setExecutable(boolean executable, boolean ownerOnly)
设置该抽象路径名的所有者或每个人的执行权限。
boolean	setLastModified(long time)
设置由此抽象路径名命名的文件或目录的最后修改时间。
boolean	setReadable(boolean readable)
一种方便的方法来设置所有者对此抽象路径名的读取权限。
boolean	setReadable(boolean readable, boolean ownerOnly)
设置此抽象路径名的所有者或每个人的读取权限。
boolean	setReadOnly()
标记由此抽象路径名命名的文件或目录,以便只允许读取操作。
boolean	setWritable(boolean writable)
一种方便的方法来设置所有者对此抽象路径名的写入权限。
boolean	setWritable(boolean writable, boolean ownerOnly)
设置此抽象路径名的所有者或每个人的写入权限。

案例

public class Main {
    
    
    public static void main(String[] args) {
    
    
            File file = new File("./test");
            if (file.exists() == false){
    
    
                try {
    
    
                    file.createNewFile();
                }catch (IOException e){
    
    
                }finally {
    
    
                    if (file.exists()){
    
    
                        System.out.println("1");
                    }else {
    
    
                        System.out.println("0");
                    }
                }
            }
    }
}

java.io.FileOutputStream和java.io.FileInputStream

这两个流分别实现了InputStream和OutputStream

FileInputStream

构造方法:

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

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

FileInputStream(String name)
通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名
默认的相对路径根目录是工程的路径

**
使用基于IntelliJ的IDE,如phpstorm、android studio都会对project和module的关系比较糊涂。用简单的一句话来概括是:
IntelliJ系中的Project相当于Eclipse系中的workspace。
IntelliJ系中的Module相当于Eclipse系中的Project。
IntelliJ中一个Project可以包括多个Module。
Eclipse中一个Workspace可以包括多个Project。
**

在这里插入图片描述

实例方法:
int	available()
返回从此输入流中可以读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。

void	close()
关闭此文件输入流并释放与流相关联的任何系统资源。

int	read()
从该输入流读取一个字节的数据。
int	read(byte[] b)
从该输入流读取最多 b.length个字节的数据为字节数组。
int	read(byte[] b, int off, int len)
从该输入流读取最多 len字节的数据为字节数组。

long	skip(long n)
跳过并从输入流中丢弃 n字节的数据。

案例(适合读全英文文件)

不同操作系统上的文件中的汉字存储不确定字节大小。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            FileInputStream fis = new FileInputStream("test");		// test在project目录下,相对路径是project
            System.out.println(fis.read());		// 只读一个字节
            fis.close();
        }catch (FileNotFoundException e){
    
    

        }finally {
    
    

        }
    }
}

循环读取:

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            FileInputStream fis = new FileInputStream("test");
            byte[] bytes = new byte[3];
            int count;
            while (( count = fis.read(bytes)) != -1 ){
    
    
                System.out.print(new java.lang.String(bytes , 0 , count));
            }
            fis.close();
        }catch (IOException e){
    
    

        }finally {
    
    
        }
    }
}

FileOutputStream

FileOutputStream继承了OutputStream抽象类
构造函数:

FileOutputStream(File file)
创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(File file, boolean append)
创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(FileDescriptor fdObj)
创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。
FileOutputStream(String name)
创建文件输出流以指定的名称写入文件。
FileOutputStream(String name, boolean append)
创建文件输出流以指定的名称写入文件。

实例函数:

void	close()
关闭此文件输出流并释放与此流相关联的任何系统资源。
protected void	finalize()
清理与文件的连接,并确保当没有更多的引用此流时,将调用此文件输出流的 close方法。
FileChannel	getChannel()
返回与此文件输出流相关联的唯一的FileChannel对象。
FileDescriptor	getFD()
返回与此流相关联的文件描述符。
void	write(byte[] b)
将 b.length个字节从指定的字节数组写入此文件输出流。
void	write(byte[] b, int off, int len)
将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。
void	write(int b)
将指定的字节写入此文件输出流。

案例

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            String ss = "我是abcdef\n";
            // FileOutputStream fos = new FileOutputStream("test"); 运行程序,默认是覆盖原来的文件,如果不存在文件会自动创建
            FileOutputStream fos = new FileOutputStream("test" ,true );
            byte[] bytes = ss.getBytes();
            fos.write(99);
            fos.write(bytes);
            fos.close();
        }catch (IOException e){
    
    

        }finally {
    
    
        }
    }
}

复制任意文件

这里我将一个linux镜像文件复制到了另一个目录中。这是go的原理图,java的也是差不多:
在这里插入图片描述

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            FileInputStream fis = new FileInputStream("/home/hadoop/cn_windows_10_enterprise_ltsc_2019_x64_dvd_9c09ff24.iso");
            FileOutputStream fos = new FileOutputStream("/home/hadoop/shell/ubuntu.iso");
            byte[] bytes = new byte[1024*1024*100];
            int count = 0;
            while ((count = fis.read(bytes)) != -1){
    
    
                fos.write(bytes, 0 , count);
                System.out.println("copy byte:" + count);
            }
            fis.close();
            fos.close();
        }catch (IOException e){
    
    

        }finally {
    
    
        }
    }
}

java.io.FileReader和java.io.FileWriter

这两个都是字符流,都继承了Reader和Writer抽象类,唯一和FileOutputStream和FileInputStream的区别就是,这两个类用来读取和写入普通文本文件,是字符流,不能读取非文本类型文件。

使用方法和FileOutputStream和FileInputStream类似,不过字符流使用char数组构造字符类。

一般来说,可以用编辑器打开的文本文件就是普通文本,包括java文件等。

java.io.OutputStreamWriter和java.io.InputStreamReader

转换流:字节流转换成字符流来操作

常用构造方法

输入字节流转换为的输入字符流方法相反,但都很类似。

以下的构造函数一般都是传入一个字节输出流,被称为节点流,OutputStreamWriter被称为包装流,包装流关闭,其中节点流都要关闭。

OutputStreamWriter(OutputStream out)
创建一个使用默认字符编码的OutputStreamWriter。

OutputStreamWriter(OutputStream out, Charset cs)
创建一个使用给定字符集的OutputStreamWriter。

OutputStreamWriter(OutputStream out, CharsetEncoder enc)
创建一个使用给定字符集编码器的OutputStreamWriter。

OutputStreamWriter(OutputStream out, String charsetName)
创建一个使用命名字符集的OutputStreamWriter。

缓冲流BufferedReader和 BufferedWriter

两个缓冲流也是字符流,并且都是包装流

BufferedReader常用方法:

BufferedReader(Reader in)
创建使用默认大小的输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz)
创建使用指定大小的输入缓冲区的缓冲字符输入流。
void	close()
关闭流并释放与之相关联的任何系统资源。
int	read()
读一个字符
int	read(char[] cbuf, int off, int len)
将字符读入数组的一部分。
String	readLine()
读一行文字。

BufferedWriter常用方法

BufferedWriter(Writer out)
创建使用默认大小的输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz)
创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。
void	close()
关闭流,先刷新。
void	flush()
刷新流。
void	newLine()
写一行行分隔符。
void	write(char[] cbuf, int off, int len)
写入字符数组的一部分。
void	write(int c)
写一个字符
void	write(String s, int off, int len)
写一个字符串的一部分。

案例

public class Main {
    
    
    public static void main(String[] args) {
    
    
        BufferedReader bufferedReader = null;
        try {
    
    
            String ss;
            bufferedReader = new BufferedReader(new FileReader("test") , 8);
            while ((ss = bufferedReader.readLine()) != null){
    
    
                System.out.println(ss);
            }
        } catch (IOException e){
    
    
            e.printStackTrace();
        }finally {
    
    
            if (bufferedReader != null){
    
    
                try {
    
    
                    bufferedReader.close();		// 包装流关闭,节点流也关闭
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
        BufferedWriter bufferedWriter = null;

        try {
    
    
            bufferedWriter = new BufferedWriter(new FileWriter("test" , true) );
            for (int i = 0 ; i <10 ; i++){
    
    
                bufferedWriter.write("yingying" , 0 , "yingying\n".length() -1 );
                bufferedWriter.write(10);
            }
            bufferedWriter.flush();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if (bufferedWriter != null){
    
    
                try {
    
    
                    bufferedWriter.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

数据流DataOutputStream和DataInputStream

这两个流比较特殊,使用DataOutputStream写入的数据到文件中是不能够通过文件直接查看内容,类似于加密,必须要用DataInputStream读取。这两个流也是包装流。读出的顺序也是按照写入数据流的顺序类似。

DataOutputStream构造方法:

DataOutputStream(OutputStream out)
创建一个新的数据输出流,以将数据写入指定的底层输出流。

DataInputStream构造方法:

DataInputStream(InputStream in)
创建使用指定的底层InputStream的DataInputStream。

案例

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            FileOutputStream fileOutputStream = new FileOutputStream("test");
            FileInputStream fileInputStream = new FileInputStream("test");
            DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
            DataInputStream dataInputStream = new DataInputStream(fileInputStream);
            try {
    
    
                dataOutputStream.writeInt(11);
                dataOutputStream.writeFloat(11.12f);
                dataOutputStream.writeDouble(12.56);
                dataOutputStream.writeBoolean(true);
                dataOutputStream.writeChar(10);
                System.out.println(dataInputStream.readInt());
                System.out.println(dataInputStream.readFloat());
                System.out.println(dataInputStream.readDouble());
                System.out.println(dataInputStream.readBoolean());

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

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

    }
}

标准输出流PrintStream和PrintWriter

IDE上面的控制面板是标准的输出输入地方,可以通过标准输出流输出在什么地方,比较容易理解

序列化和反序列化

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程:

序列化(output):对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。

反序列化(输入):客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

序列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东西,就可以进行通用的格式传输或保存,传输结束以后,要再次使用,就进行反序列化还原,这样对象还是对象,文件还是文件。

在这里插入图片描述

序列化实现ObjectOutputStream和反序列化实现ObjectInputStream

这也是一个包装类,构造函数如下:

ObjectOutputStream(OutputStream out)
创建一个写入指定的OutputStream的ObjectOutputStream。
 ObjectInputStream(InputStream in)
创建从指定的InputStream读取的ObjectInputStream。

实例方法和之前的流很相似,也有写入常用方法,只不过这里对于对象的序列化和反序列化用的writeObject和readObject方法。

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test"));
            objectOutputStream.writeObject(new Student("llll" , 18));
            objectOutputStream.writeObject(new Student("yyy" , 18));
            objectOutputStream.close();
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test"));
            Object object = objectInputStream.readObject();
            Student object1 = (Student) objectInputStream.readObject();
            System.out.println(object);
            System.out.println(object1.getName());
            objectInputStream.close();
        } catch (ClassNotFoundException e){
    
    

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

    }
}

// 参与序列化和反序列化要实现Serializable接口,Serializable接口是一个标志接口,没有任何抽象方法,起到一个标识作用
class Student implements Serializable{
    
    
    private int no;
    private String name;
    public Student() {
    
    
    }

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

    public int getNo() {
    
    
        return no;
    }

    public void setNo(int no) {
    
    
        this.no = no;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return "Student [no=" + no + ", name=" + name + "]";
    }
}

transient标识的实例变量不参与序列化

transient标识的实例变量不参与序列化操作,反序列化之后new出的对象是null

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test"));
            objectOutputStream.writeObject(new Student("llll" , 18));
            objectOutputStream.writeObject(new Student("yyy" , 18));
            objectOutputStream.close();
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test"));
            Object object = objectInputStream.readObject();
            Student object1 = (Student) objectInputStream.readObject();
            System.out.println(object);
            System.out.println(object1.getName());
            objectInputStream.close();
        } catch (ClassNotFoundException e){
    
    

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

    }
}

// 参与序列化和反序列化要实现Serializable接口,Serializable接口是一个标志接口,没有任何抽象方法,起到一个标识作用
class Student implements Serializable{
    
    
    private int no;

    // transient标识的实例变量不参与序列化操作,反序列化之后new出的对象是null
    private transient String name;

    public Student() {
    
    

    }

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

    public int getNo() {
    
    
        return no;
    }

    public void setNo(int no) {
    
    
        this.no = no;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return "Student [no=" + no + ", name=" + name + "]";
    }
}

序列化版本号的作用

jvm判断是否是同一个类的条件:

  1. 完整类名
  2. 序列化版本号

A类序列化之后,如果要在一个程序中反序列化,必须要满足完整类名相同,如果类名相同,属性值和方法没有发生改变的话,是可以成功序列化的。

但是类名一样的,内容不一样,这个类被重写过了,就需要判断序列化版本号,但是重写之后,序列化版本号肯定改变,重新编译之后生成的序列化版本号不一致,所以不能成功序列化。

序列化用到的Student类

package org.example;
import java.io.Serializable;
// 参与序列化和反序列化要实现Serializable接口,Serializable接口是一个标志接口,没有任何抽象方法,起到一个标识作用
public class Student implements Serializable {
    
    
    // jvm自动增加序列化的版本号,一旦修改代码就无法反序列化
    // 自己增添一个序列化版本号,jvm就不会自动增加序列化版本号,这个序列号就会成为这个类的唯一标识
    // sss是改变的内容
    // private static final long serialVersionUID = 5883771096095870239L;
    // private String sss;
    private int no;
    private String name;


    public Student() {
    
    
    }

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

    public int getNo() {
    
    
        return no;
    }

    public void setNo(int no) {
    
    
        this.no = no;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return "Student [no=" + no + ", name=" + name + "]";
    }
}

序列化测试:

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test"));
            objectOutputStream.writeObject(new Student("llll" , 18));
            objectOutputStream.writeObject(new Student("yyy" , 18));
            objectOutputStream.close();
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test"));
            Object object = objectInputStream.readObject();
            Student object1 = (Student) objectInputStream.readObject();
            System.out.println(object);
            System.out.println(object1.getName());
            objectInputStream.close();
        } catch (ClassNotFoundException e){
    
    
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
}

重新编译Student类之后再次反序列化,内容改动并且没有加序列号,反序列化失败。内容没有改动,反序列化成功。

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test"));
            Object object = objectInputStream.readObject();
            Student object1 = (Student) objectInputStream.readObject();
            System.out.println(object);
            System.out.println(object1.getName());
            objectInputStream.close();
        } catch (ClassNotFoundException e){
    
    

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

猜你喜欢

转载自blog.csdn.net/liutao43/article/details/112431942