IO学习底层原理分析(必备)

JAVA IO学习(必备)

注:本篇所有有关于底层源码的均使用的是java 8

文件的基本使用

文件流介绍

文件在程序中是以流的形式来操作的

在这里插入图片描述

:数据在数据源(文件)和程序(内存)之间经历的路径

  • 输入流:数据从数据源(文件)到程序(内存)的路径

  • 输出流:数据从程序(内存)到数据源(文件)的路径

常用的文件操作

创建文件对象相关构造器和方法

构造器:File类中有六个有参构造器

public File(String pathname)	//根据路径构建一个File对象(常见)

public File(File parent,String child)	//根据父目录文件+子路径构建(常见)
public File(String child,File parent)

public File(String parent,String child)	//根据父目录+子目录构建(常见)

public File(String pathname, int prefixLength)

//从1.4开始
public File(URI uri)

创建新文件方法

createNewFile()

只有调用了createNewFile()方法才真正在磁盘中创建了文件,在调用方法前file是在java程序(即内存)中的。并不是真正的文件。

创建文件举例

如下:

new File(String pathName)	//根据路径构建一个File对象(常见)	
	@Test
    public void create01(){
        String pathName = "D:\\codeTest/news01.txt";
        File file = new File(pathName);
        try {
            file.createNewFile();
            System.out.println("文件创建成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

new File(File parent,String child)	//根据父目录文件+子路径构建(常见)
    @Test
    public void create02(){
        File parent = new File("D:\\codeTest");
        String child = "news02.txt";
        File file = new File(parent,child);
        try {
            file.createNewFile();
            System.out.println("文件创建成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

new File(String parent,String child)	//根据父目录+子目录构建(常见)
    @Test
    public void create03(){
        String parent = "D:\\codeTest";
        String child = "news03.txt";
        File file = new File(parent,child);
        try {
            file.createNewFile();
            System.out.println("文件创建成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

获取文件相关信息

File类中常见的方法:

getName()、getAbsolutePath()、getParent()、length()、exists()、isFile()、isDirectory()等。

可以通过右击File选择Diagrams选择show Diagrams打开类图进行查看

//getName()-获取文件名、getAbsolutePath()-获取绝对路径、getParent()-获取父目录、length()-获取大小(字节)、exists()-是否存在、isFile()-是否为文件、isDirectory()-是否为目录、···
	@Test
    public void info(){
        //创建文件对象
        File file = new File("D:\\codeTest/news01.txt");
        //调用对应方法,得到对应信息
        System.out.println("文件名-"+file.getName());
    }

目录的操作和文件的删除

mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件

mkdir()、mkdirs()返回值类型Boolean

 //删除文件
	@Test
    public void m1(){

        String pathName = "D:\\codeTest\\news01.txt";
        File file = new File(pathName);
        if (file.exists()){
            if (file.delete()){
                System.out.println(file.getName()+"文件删除成功!");
            }else{
                System.out.println(file.getName()+"文件删除失败!");
            }
        }else {
            System.out.println("文件不存在!");
        }
    }

//删除目录(目录也是一种特殊的文件)
    @Test
    public void m2(){
        String pathName = "D:\\codeTest\\test";
        File file = new File(pathName);
        if (file.exists()){
            if (file.delete()){
                System.out.println("目录删除成功!");
            }else{
                System.out.println("目录删除失败!");
            }
        }else {
            System.out.println("目录不存在!");
        }
    }

//一级目录的创建
    @Test
    public void m3(){
        String pathName = "D:\\codeTest\\test";
        File file = new File(pathName) ;
        if (!file.exists()){
            if (file.mkdir()){
                System.out.println("创建目录成功!");
            }else {
                System.out.println("创建目录失败!");
            }
        }else {
            System.out.println("目录已经存在!");
        }
    }

多级目录的创建
    @Test
    public void m4(){
        String pathName = "D:\\codeTest\\test\\a\\b\\c";
        File file = new File(pathName);
        if (!file.exists()){
            if (file.mkdirs()){
                System.out.println("创建目录成功!");
            }else {
                System.out.println("创建目录失败!");
            }
        }else {
            System.out.println("目录已经存在!");
        }
    }

小结

File底层(可以查看类图来分析)

在这里插入图片描述

File类实现了Serializable(可序列化)接口以及Comparable(比较)接口

serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。

Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。

实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器

int compareTo(T o),比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

public class File
    implements Serializable, Comparable<File>
{
    //***
}

IO流原理及流的分类

Java IO流原理

  1. I/O是Input和Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。如:读写文件,网络通讯等。
  2. Java程序中,对于数据的输入/输出操作以流(Stream)的方式进行。
  3. java.io包下提供了各种”流“类和接口,用以获取不同种类的数据,并通过方法输入或输出数据。
  4. 输入Input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
  5. 输出Output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

在这里插入图片描述

流的分类

  • 按操作数据单位不同分为:字节流(8bit),字符流(按字符)

    字节流继承于InputStream 和OutputStream;字符流继承于InputStreamReader OutputStreamWriter。字符流使用了缓冲区 (buffer),而字节流没有使用缓冲区底层设备永远只接受字节数据字符是字节通过不同的编码的包装,字符向字节转换时,要注意编码的问题。字符的处理,一次处理一个字符,字符的底层仍然是基本的字节序列。InputStreamReader 完成byte流解析为char流,按照编码解析;OutputStreamWriter 提供char流到byte流,按照编码处理。

  • 按数据流的流向不同分为:输入流,输出流

  • 按流的角色不同分为:节点流,处理流/包装流

(抽象基类)字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

1)Java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的。

2)由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

流和文件的区别:可以理解为流是文件的通道。严谨的说流是数据的通道而流向的设备可以是文件,网络,内存等。所以说数据的输入/输出操作都是以“流”的方式进行。

I/O流体系图

注:图示中字符输出流(Writer)中子类为OutputStreamReader错误,应为OutputStreamWriter。

在这里插入图片描述

在这里插入图片描述

InputStream:字节输入流

InputStream抽象类是所有类字节输入流的超类

非抽象子类继承抽象类要覆写抽象类中所有的抽象方法。

常用子类:

  • FileInputStream:文件字节输入流
  • BufferedInputStream:缓冲字节输入流(直接父类为FilterInputStream)
  • ObjectInputStream:对象字节输入流

类图:继承关系

在这里插入图片描述

OutputStream:字节输出流

OutputStream抽象类是所有类字节输出流的超类

常用子类:

  • FileOutputStream:文件字节输出流
  • BufferedOutputStream:缓冲字节输出流(直接父类为FilterOutputStream)
  • ObjectOutputStream:对象字节输出流

类图:继承关系

在这里插入图片描述

FileInputStream和FileOutputStream

FileInputStream:文件字节输入流

三个构造方法:创建FileInputStream对象有三种方式。

1. public FileInputStream(String name)
2. public FileInputStream(File file)
3. public FileInputStream(FileDescriptor fdObj)

代码示例:

注:本示例通过FileInputStream(File file)构造器来创建对象

@Test
public void readFile01(){
    String pathName = "D:\\codeTest\\hello.txt";
    FileInputStream inputStream = null;
    int readData = 0;
    File file = new File(pathName);
    if (file.exists()){
        try {

            inputStream = new FileInputStream(file);

            /*
            * inputStream.read();   单个字节读取,返回值为-1时表示读取完
            * inputStream.read(byte[] b);   最多读取b.lenth()个字节,返回值为实际读取字节数最多为b.lenth()个
            *     输出时可以通过new String(b,0,b.lenth()) 注:b表示创建的字节数组
            * inputStream.read(byte[] b,int off,int len);
            * */

            //从该输入流中读取一个字节的数据。如果没有输入可用,此方法将终止
            //如果返回-1,表示读取完毕
            while((readData = inputStream.read()) != -1){
                System.out.print((char)readData);//转换成char类型
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
          //关闭文件流,释放资源
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }else {
        System.out.println("文件不存在!");
    }

}

FileOutputStream:文件字节输出流

介绍

五种构造器:创建FileOutputStream对象有五种方式

//注:name   the system-dependent(系统相关) filename
public FileOutputStream(String name)	//新写内容会覆盖原来的信息
public FileOutputStream(String name, boolean append)	//append为true则是写在文件名末尾相当于追加
public FileOutputStream(File file)   
public FileOutputStream(File file, boolean append)    
public FileOutputStream(FileDescriptor fdObj) 
写文件

代码示例:

@Test
    public void writeFile01(){
        String pathName = "D:\\codeTest\\hello.txt";
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(pathName);
            /*
            * 注:
            * 1.new FileOutputStream(filePath)创建方式,写入信息会覆盖原来的内容
            * 2.FileOutputStream(String name, boolean append) append为true则是写在文件名末尾相当于追加
            * str.getBytes()会将字符串转换为字节数组
            *
            * */

            //写入一字节
            //outputStream.write('h');

            //写入字符串
            String str = "Hello word!";
//            String str = "你好";
//            outputStream.write(str.getBytes("UTF-8"));
            outputStream.write(str.getBytes("UTF-8"),0,str.length());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
文件拷贝

步骤:

  1. 创建文件输入流,将文件数据读取到程序
  2. 创建文件输出流,将读取到的文件数据写入到指定文件
  3. 创建字节数组,将部分被数据读取到程序
  4. 循环读数据写数据
  5. 关闭流释放资源

示例代码:

public static void main(String[] args) {
        //文件拷贝
        /*
        * 1.创建文件输入流,将文件数据读取到程序
        * 2.创建文件输出流,将读取到的文件数据写入到指定文件
        * */
        String inputFilePath = "D:\\codeTest\\study.png";
        String outputFilePath = "D:\\codeTest\\test\\study.png";
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(inputFilePath);
            outputStream = new FileOutputStream(outputFilePath,true);
            //定义字节数组
            byte b[] = new byte[1024];
            int readLen = 0;	//读取到的数据长度
            while((readLen = inputStream.read(b))!=-1){
                //读取后通过outputStream写入文件
                outputStream.write(b,0,readLen);
            }
            System.out.println("拷贝完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //关闭流,释放资源
                if (inputStream != null){
                    inputStream.close();
                }
                if (outputStream != null){
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
}

FileReader和FileWriter

介绍

FileReader和FileWriter是字符流,即按照字符来操作IO。

FileReader和FileWriter分别继承InputStreamReader和OutPutStreamWriter,两者本身都只含有构造方法。

类图:

在这里插入图片描述

FileReader

构造方法:

public FileReader(String fileName).
public FileReader(File file)    
public FileReader(FileDescriptor fd)    

所以FileReader对象可以调用的方法如下:

public int read()	//每次读取单个字符,返回该字符,如果到文件末尾返回-1.
public int read(char cbuf[])	//批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1.
//public int read(char cbuf[], int offset, int length)
//相关API
    new String(char []):char[]转换为String
    new String(char [],off,len):char[]的指定部分转换为String

代码示例:

单个字符

    @Test
    public void fileReader01(){
        String fileName = "D:\\codeTest\\story.txt";
        FileReader reader = null;
        int data = 0;
        try {
            reader = new FileReader(fileName);
            while ((data = reader.read()) != -1){
                System.out.print((char)data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

多个字符

注:public String(char value[], int offset, int count) -String构造方法,传入字符数组,偏移量以及长度

@Test
public void fileReader02(){
    String fileName = "D:\\codeTest\\story.txt";
    FileReader reader = null;
    char[] c = new char[8]; //定义每次读取的字符长度
    int len = 0;    //实际读取到的长度
    try {
        reader = new FileReader(fileName);
        while ((len = reader.read(c))!=-1){
            System.out.print(new String(c,0,len));
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FileWriter

构造方法:

public FileWriter(String fileName)	//覆盖
public FileWriter(String fileName, boolean append)	//追加
public FileWriter(File file)
public FileWriter(File file, boolean append)    
public FileWriter(FileDescriptor fd)    

常用方法

外话:protected 的属性和方法可以在本包和子类访问

public void write(int c)	//写入单个字符
public void write(char cbuf[])	//写入指定数组
public void write(String str)    //写入整个字符串
public void write(String str, int off, int len)		//写入字符串的指定部分
public void write(char cbuf[], int off, int len)	//写入字符数组的指定部分

相关API:

String类:toCharArray:将String转换为char[]。

:FileWriter使用后必须要关闭(close)或刷新(flush)否则写入不到指定文件。

代码示例:

 @Test
    public void fileWriter01(){
        String fileName = "D:\\codeTest\\node.txt";
        FileWriter fileWriter = null;
        String str = "你好!lpx!";
        char [] buf = {'H','e','l','l','o','!','l','p','x','!'};
        try {
            fileWriter = new FileWriter(fileName);
//            public void write(int c) //写入单个字符
            fileWriter.write('H');
//            public void write(char cbuf[])   //写入指定数组
            fileWriter.write(buf);
//            public void write(String str)    //写入整个字符串
            fileWriter.write(str);
//            public void write(String str, int off, int len)     //写入字符串的指定部分
            fileWriter.write(str,0,3);
//            public void write(char cbuf[], int off, int len) //写入字符数组的指定部分
            fileWriter.write(str.toCharArray(),0,6);
            System.out.println("添加成功!");
        } catch (IOException e) {
            
            e.printStackTrace();
        } finally {
            try {
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

节点流和处理流(包装流)

简介

节点流:节点流从一个特定的数据源读写数据。即节点流是直接操作文件,网络等的流,例如FileInputStream和FileOutputStream,FileReader和FileWriter,他们直接从文件中读取或往文件中写入字节流或字符流(字符的底层仍然是基本的字节序列)。
在这里插入图片描述

处理流:“连接”在已存在的流(节点流或处理流)之上通过对数据的处理为程序提供更为强大的读写功能。过滤流是使用一个已经存在的输入流或输出流连接创建的,过滤流就是对节点流进行一系列的包装。例如BufferedInputStream和BufferedOutputStream,使用已经存在的节点流来构造,提供带缓冲的读写,提高了读写的效率,以及DataInputStream和DataOutputStream,使用已经存在的节点流来构造,提供了读写Java中的基本数据类型的功能。他们都属于过滤流。
在这里插入图片描述

分类

注:

经查阅资料与查看源码(java 8)发现错误如下:

处理流中打印流PrintWriter的抽象基类不为FilterWriter,PrintWriter的直接父类为Writer类。另FilterInputStream和FilterOutputStream不为抽象类。

源码中FilterReader和FilterWriter的直接父类分别为Reader和Writer,且FilterReader和FilterWriter为抽象类,FilterWriter没有子类

PushBackReader为FilterReader的子类。

FilterReader 和 FilterWriter 用于读写过滤后的字符流的抽象类。除了简单覆盖父类方法,没有添加额外的方法。

  • FilterReader:过滤器字符输入流,用于读取过滤后的字符流的抽象类。
  • FilterWriter:过滤器字符输出流,用于写入过滤后的字符流的抽象类。

PushBackReader 是 FilterReader 的子类,一个字符流读取器,允许将字符推回到流中。拥有一个PushBack缓冲区,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端。除了简单覆盖父类方法,没有添加额外的方法。

在这里插入图片描述

节点流和处理流区别

  1. 节点流是底层流/低级流,直接跟数据源连接。
  2. 处理流使用了装饰器设计模式,对节点流进行包装,不直接与数据源相连。
  3. 处理流包装节点流,可以消除不同节点流的实现差异,提供统一的接口完成输入输出。

处理流优势

  1. 提高性能:增加了缓冲的方式提高输入输出的效率。
  2. 操作便捷:提供多个方法来一次输入输出大批量数据,使用灵活。

BufferedReader和BufferedWriter

介绍

BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的。

关闭处理流时,只需要关闭外层流即可。

避免使用到二进制文件中,例如音频,视频等可能会造成文件错误,此时应使用BufferedInputStream和BufferedOutputStream。

BufferedReader

构造方法:

//缓冲流中都含有:private Reader in;
public BufferedReader(Reader in, int sz)	//in   A Reader,sz   Input-buffer size
public BufferedReader(Reader in)	//in   A Reader

代码示例:

 @Test
    public void br01() throws Exception{
        String filePath = "D:\\codeTest\\story.txt";
        FileReader fileReader = new FileReader(filePath);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        //常规读取
/*        char c[] = new char[8];
        int len = 0;
        while ((len = bufferedReader.read(c))!=-1){
            System.out.print(new String(c,0,len));
        }*/

        //按行读取
        String line;
        /*
        * 说明:
        * readLine()是按行读取的
        * 当返回null时文件读取完毕
        *
        * */
        while ((line = bufferedReader.readLine()) != null){
            System.out.println(line);
        }
        //BufferedReader对close()也进行了包装。
        bufferedReader.close();

    }

:关于close()方法,BufferedReader底层

public void close() throws IOException {
    synchronized (lock) {
        if (in == null)
            return;
        try {
            in.close();	//传入的是哪个节点流就调用那个节点流的close()方法
        } finally {
            in = null;
            cb = null;
        }
    }
}

BufferedWriter

构造方法:

public BufferedWriter(Writer out)	//out  A Writer
public BufferedWriter(Writer out, int sz)   //sz   Output-buffer size, a positive integer 

代码示例:文件拷贝

@Test
public void bw() throws Exception{
    String readFilePath = "D:\\codeTest\\story.txt";
    String writerFilePath = "D:\\codeTest\\code.txt";

    FileReader fileReader = new FileReader(readFilePath);
    BufferedReader bufferedReader = new BufferedReader(fileReader);

    FileWriter fileWriter = new FileWriter(writerFilePath);
    BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

    String line;

    while((line = bufferedReader.readLine()) != null){
        bufferedWriter.write(line);
        bufferedWriter.newLine();	//写入换行-系统换行
    }
    
    if (bufferedReader != null){
        bufferedReader.close();
    }
    if (bufferedWriter != null){
        bufferedWriter.close();
    }
}

BufferedInputStream和BufferedOutputStream

介绍

BufferedInputStream和BufferedOutputStream和上面BufferedReader和BufferedWriter类似,区别:BufferedInputStream和BufferedOutputStream时直接对字节的操作而BufferedReader和BufferedWriter是对字符的操作。

BufferedInputStream和BufferedOutputStream一般用于处理二进制文件(视频,音频,图片等)。

BufferedOutputStream 继承自FilterOutputStream,其中BufferedOutputStream 所使用的protected OutputStream out属性也是继承父类的本身并没有定义。

文件拷贝

步骤:

  1. 先创建文件路径。
  2. 创建字节数组b以及实际读取长度len。
  3. 创建字节输入流及字节输出流。
  4. 创建字节缓冲输入流以及字节缓冲输出流。
  5. 循环写文件。

代码示例:

@Test
public void cp() throws Exception{
    String filePath = "D:\\codeTest\\study.png";
    String toFilePath = "D:\\codeTest\\test\\study.png";

    //创建字节数组
    byte[] b = new byte[1024];
    //定义实际读取长度
    int len = 0;
    //创建字节输入流以及输出流
    FileInputStream inputStream = new FileInputStream(filePath);
    FileOutputStream outputStream = new FileOutputStream(toFilePath);

    //创建字符缓冲流
    BufferedInputStream bfInputStream = new BufferedInputStream(inputStream);
    BufferedOutputStream bfOutputStream = new BufferedOutputStream(outputStream);

    while ((len = bfInputStream.read(b)) != -1){
        bfOutputStream.write(b,0,len);
    }

    if (bfInputStream != null){
        bfInputStream.close();
    }
    if (bfOutputStream != null){
        bfOutputStream.close();
    }

}

对象流-ObjectInputStream和ObjectOutputStream

介绍

数据流操作的是我们的基本数据类型和字符串,对象流除了基本数据类型和字符串还包括我们其它的各种对象(也包括我们自定义的对象),所以对象流是数据流的升级版,同时这个流有个特殊的叫法叫做序列化和反序列化。不是所有的对象都可以序列化(必须要有通行证),必须实现Serializable接口。

看需求理解:

  1. 将int num = 100这个int数据保存到文件中,注意不是100数字,而是int 100,并且能够从文件直接恢复int 100。
  2. 将Dog dog = new Dog(“小灰”,3)这个对象保存都文件中,并且能从文件恢复。

上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作。

序列化和反序列化:

  1. 序列化就是在保存数据时,保存数据的值和数据类型。
  2. 反序列化就是在恢复数据时,恢复数据的值和数据类型。
  3. 需要让某个 对象支持序列化机制,则必须让其类时可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
    1. Serializable //标记接口 注:标记接口没有任何方法和属性。
    2. Externalizable //需要实现接口中的方法,一般使用Serializable接口。

ObjectOutputStream

ObjectOutputStream提供反序列化功能。

构造方法:

public ObjectOutputStream(OutputStream out)
protected ObjectOutputStream()    //protected 的属性和方法可以在本包和子类访问,通过protected修饰的构造方法来创建对象是可以使用:ObjectOutputStream objectOutputStream = new ObjectOutputStream(){};

注:

protected:受保护的,本类;同包类;子类。

不同包下的子类虽然能访问父类的protected属性,但不能直接访问。如下:

​ 被protected所修饰的属性和方法可以被该类的子类所访问的意思是说可以在该类的子类内部所访问,所以在和Farther类的不同包中,尽管是new Farther(),但是f引用依然不能访问Farther类中有protected修饰的成员变量和成员方法,方法一:只有是new Son(),而且使用s(而不是使用f)引用,才可以访问不同包的Farther中protected修饰的成员变量和成员方法。方法二:在Son类的成员方法中,使用this、super来调用不同包的Farther中protected修饰的成员变量和成员方法。

类图:

在这里插入图片描述

代码示例:由于并不是纯文本文件(包含值和数据类型)所以显示会乱码,该问题会在转换流中解决。

@Test
public void oInputStream01() throws Exception {

    //序列化后保存的文件格式不是纯文本,而是按照它的格式来保存
    String filePath = "D:\\codeTest\\data.dat";
    FileOutputStream outputStream = new FileOutputStream(filePath);
    ObjectOutputStream oos = new ObjectOutputStream(outputStream);

    //序列化数据到:D:\codeTest\data.dat
    oos.writeInt(100); //int->Integer(会将int基本数据类型自动装箱为Integer包装类型实现了可序列化接口)
    oos.writeBoolean(true); //boolean->Boolean(和上面类似)
    oos.writeChar('H');//char->Character
    oos.writeDouble(9.5); //dubble->Dubble
    oos.writeUTF("Hello,lpx!"); //String不是基本数据类型,本身就实现了可序列化接口
    oos.writeObject(new Dog("小灰",10));
    oos.close();
}

//如果要序列化某个类的对象,则该类需要实现实例化
class Dog implements Serializable {
    private String name;
    private int age;
    public Dog(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

ObjectInputStream

ObjectInputStream提供序列化功能。

构造方法:

public ObjectInputStream(InputStream in)
protected ObjectInputStream()    //protected 的属性和方法可以在本包和子类访问,如上ObjectOutputStream

类图:

在这里插入图片描述

代码示例:

@Test
public void oInputStream01() throws IOException, ClassNotFoundException {
    String filePath = "D:\\codeTest\\data.dat";

    FileInputStream inputStream = new FileInputStream(filePath);

    ObjectInputStream ois = new ObjectInputStream(inputStream);
    System.out.println(ois.readInt());
    System.out.println(ois.readBoolean());
    System.out.println(ois.readChar());
    System.out.println(ois.readDouble());
    System.out.println(ois.readUTF());
    Object dog = ois.readObject();
    System.out.println("运行类型->"+dog.getClass());
    System.out.println("dog信息:"+dog);
    Dog dog1 = (Dog) dog;//当需要调用Dog类中的方法时需要向下转型应为dog为Object类型只有转换为Dog类型才能调用方法
    System.out.println("狗的名字:"+dog1.getName());
    ois.close();
}

注意事项和细节说明

  • 读写顺序要一致
  • 要求实现序列化和反序列化对象,需要实现Serializable
  • 序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
  • 序列化对象时,默认将里面所有属性都进行序列化,但除了static和transient修饰的成员
  • 序列化对象时,要求里面属性的类型也需要实现序列化接口
  • 序列化具备可继承性,也就是说如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化

标准输入输出流

标准输入设备可以作为数据源。例如:

InputStream is = System.in;

介绍

类型默认设备
System.in 标准输入InputStream键盘
System.out 标准输出PrintStream显示器

代码示例及底层分析

public static void main(String[] args) {

    //标准输入
    //in底层为InputStream类型-> public final static InputStream in = null;
    //System.in 编译类型:InputStream
    System.out.println(System.in);
    //System.in 运行类型:BufferedInputStream(缓冲字节输入流)
    System.out.println(System.in.getClass());

    //标准输出
    //System.out 编译类型:PrintStream
    System.out.println(System.out);
    //System.out 运行类型:PrintStream继承FilterOutputStream类,FilterOutputStream继承OutputStream类详细查看类图
    System.out.println(System.out.getClass());
}

注:

传统的方法Scanner是从标准输入接收数据。

转换流-InputStreamReader和OutputStreamWriter

介绍

转换流:用来实现将字节流转化成字符流,字符流与字节流之间的桥梁。

InputStreamReader和OutputStreamWriter分别时Reader和Writer的子类,可以将字节流转换成字符流即InputStream->Reader,OutputStream->Writer。

当处理纯文本数据时,如果使用字符流效率较高,并且可以有效解决中文问题,所以建议将字节流转换成字符流。

可以在使用时指定编码格式(比如UTF-8,GBK,GB2312,ISO8859-1等)。

类图:

在这里插入图片描述

InputStreamReader

构造方法

//in   An InputStream
public InputStreamReader(InputStream in)	//创建一个使用默认字符集的 InputStreamReader。
public InputStreamReader(InputStream in, String charsetName) 	//创建使用指定字符集的 InputStreamReader。
public InputStreamReader(InputStream in, Charset cs)    //创建使用给定字符集的 InputStreamReader
public InputStreamReader(InputStream in, CharsetDecoder dec)    //创建使用给定字符集解码器的 InputStreamReader

代码示例:将编码为GBK的文件读取后控制台输出。

注:

如果不指定字符集为文件编码方式(GBK)则控制台乱码。

    @Test
    public void t01() throws IOException {

        String filePath = "D:\\codeTest\\story.txt";
        String line;

        FileInputStream inputStream = new FileInputStream(filePath);
        InputStreamReader inputStreamReader =
                new InputStreamReader(inputStream,"GBK");	//指定字符集编码
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        while ((line = bufferedReader.readLine()) != null){
            System.out.println(line);
        }

/*        char[] c = new char[8];
        int len = 0;
        while((len = bufferedReader.read(c)) != -1){
            System.out.print(new String(c,0,len));
        }*/

        bufferedReader.close();

    }

OutputStreamWriter

构造方法

//out  An OutputStream
public OutputStreamWriter(OutputStream out, String charsetName)	//创建使用指定字符集的 OutputStreamWriter
public OutputStreamWriter(OutputStream out) //创建使用默认字符编码的 OutputStreamWriter
public OutputStreamWriter(OutputStream out, Charset cs)	//创建使用给定字符集的 OutputStreamWriter。
public OutputStreamWriter(OutputStream out, CharsetEncoder enc) //创建使用给定字符集编码器的OutputStreamWriter

代码示例:向文件中写入字符串并指定文件编码方式。

@Test
public void osr01() throws IOException {
    String filePath = "D:\\codeTest\\node.txt";
    FileOutputStream outputStream = new FileOutputStream(filePath);
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream,"GBK");	//指定编码方式

    BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
    String str = "你好,lpx";
    bufferedWriter.write(str);
    bufferedWriter.close();
}

打印流-PrintStream和PrintWriter

注:

打印流只有输出流没有输入流。

类图:

在这里插入图片描述

字节打印流PrintStream

父类为FilterOutputStream

构造方法(公共)

public PrintStream(OutputStream out)	//创建新的打印流。out - 将向其打印值和对象的输出流
public PrintStream(OutputStream out, boolean autoFlush)    //autoFlush - boolean 变量;如果为 true,则每当写入 byte 数组、调用其中一个 println 方法或写入换行符或字节 ( '\n') 时都会刷新输出缓冲区
public PrintStream(OutputStream out, boolean autoFlush, String encoding)    //同上,同时指定字符集
public PrintStream(String fileName)    //创建具有指定文件名称且不带自动行刷新的新打印流
public PrintStream(String fileName, String csn)    //创建指定名称和字符集且不带自动行刷新的打印流
public PrintStream(File file)    //创建具有指定文件且不带自动行刷新的新打印流
public PrintStream(File file, String csn)  //创建具有指定文件名称和 csn 字符集且不带自动行刷新的新打印流

代码示例

    @Test
    public void psAndPw01() throws IOException {
        PrintStream out = System.out;
//        默认情况下输出为标准输出,即显示器
        //print底层使用的时write,所以可以直接调用write进行打印/输出
/*        public void print(String s) {
            if (s == null) {
                s = "null";
            }
            write(s);
        }*/
        out.write("你好,lpx!".getBytes());
        out.close();

//        修改打印输出流位置
        String filePath = "D:\\codeTest\\hello.txt";
        System.setOut(new PrintStream(filePath));
        System.out.println("hello,www.lpxStudy.top");
    }

字符打印流PrintWriter

直接父类为Writer

构造方法(公共)

public PrintWriter (Writer out)	//根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter
public PrintWriter(Writer out,boolean autoFlush)    //通过现有的 OutputStream 创建新的 PrintWriter带自动行刷新
public PrintWriter(OutputStream out)    //创建不带自动行刷新的新 PrintWriter
public PrintWriter(OutputStream out, boolean autoFlush)	//创建新 PrintWriter带自动行刷新
public PrintWriter(String fileName)    //创建具有指定文件名称且不带自动行刷新的新 PrintWriter
public PrintWriter(String fileName, String csn)    //创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter
public PrintWriter(File file)	//使用指定文件创建不具有自动行刷新的新 PrintWriter
public PrintWriter(File file, String csn)    //创建具有指定文件和字符集且不带自动刷行新的新 PrintWriter
    

代码示例

@Test
public void psAndPw02() throws IOException {
    String filePath = "D:\\codeTest\\hello.txt";

    FileWriter fileWriter = new FileWriter(filePath);
    PrintWriter printWriter = new PrintWriter(fileWriter,true);
    printWriter.println("hi,lpx!");
    //printWriter.close();
}

Properties类

介绍

专门用于读取配置文件的集合类

配置文件的格式:键=值

注:键值对不需要有空格,值不需要引号引起来,默认类型String

类图:

在这里插入图片描述

构造方法

public Properties()
public Properties(Properties defaults)    

常见方法:

load:加载配置文件的键值对到properties对象

list:将数据显示到指定设备/流

getProperty(key):根据键获取值

setProperty(key,value):设置键值对到Properties对象

store:将Properties中的键值对存储到配置文件,在idea中保存信息到配置文件,如果含有中文,会存储为unicode码

在这里插入图片描述

代码及原理分析

代码示例:

    //传统方法读取数据
    @Test
    public void pTest01() throws IOException {
        String filePath = "src\\top\\lpxStudy\\io\\propertiesDemo\\mysql.properties";
        FileReader fileReader = new FileReader(filePath);
        BufferedReader bufferedReader = new BufferedReader(fileReader);
        String line;
        while ((line = bufferedReader.readLine()) != null){
//            System.out.println(line);
            String [] split = line.split("=");  //拆分为字符串数组
            System.out.println(split[0]+"值为:"+split[1]);
        }
        bufferedReader.close();
    }

    //使用properties实现
    @Test
    public void pTest02() throws IOException {
//        使用properties类读取mysql.properties文件

//        创建Properties对象
        Properties properties =new Properties();

//        加载指定配置文件
        String filePath = "src\\top\\lpxStudy\\io\\propertiesDemo\\mysql.properties";
        FileReader fileReader = new FileReader(filePath);
        properties.load(fileReader);

//        将数据显示到控制台
        properties.list(System.out);

//        根据key获取对应值
        String ip = properties.getProperty("ip");
        System.out.println("用户ip:"+ip);
    }

//    创建或修改配置文件
    @Test
    public void pTest03() throws IOException {
        String filePath = "src\\top\\lpxStudy\\io\\propertiesDemo\\image.properties";
//        创建Properties对象
        Properties properties = new Properties();
//        setProperty()如果该文件没有key就创建,反之修改
        properties.setProperty("charset","UTF-8");
        properties.setProperty("User","张三");    //当使用字节流时中文保存的是unicode码,字符流则不是,而描述信息则为unicode码
        properties.setProperty("password","1234");

//        将K-V存储文件中
        String comments = "author is lpx";
        FileWriter fileWriter = new FileWriter(filePath);
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
        properties.store(fileWriter,comments);


    }

添加或修改底层

Properties父类为Hashtable

properties底层

public synchronized Object setProperty(String key, String value) {
    return put(key, value);	//调用Hashtable的put方法
}

Hashtable底层

   public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;//如果key存在就替换
                return old;
            }
        }

        addEntry(hash, key, value, index);	//如果不存在就添加
        return null;
    }
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值