IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 学会IO流~ 一篇文章就够~ -> 正文阅读

[Java知识库]学会IO流~ 一篇文章就够~

IO流

即使再小的帆也能远航~

一.目录

File类

IO流概念

缓冲流

转换流

对象流

打印流

内存流,数据流

读写流

压缩流

NIO

二.内容

File类

概述

File是java.io包下代表与平台无关的文件和目录,则程序中操作文件和目录,都可以通过File类来完成。File能新建、删除、重命名文件和目录。File不能访问文件内容本身,如果访问文件内容本身,则需要使用输入/输出流

构造方法

File(File parent, String child) 
    //parent:父路径   child:文件名
根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 
File(String pathname) 
    //pathname=Parent+child
通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 
File(String parent, String child) 
根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例 

字段摘要

static String pathSeparator 
与系统有关的路径分隔符,为了方便,它被表示为一个字符串。 
static char pathSeparatorChar 
与系统有关的路径分隔符。 
static String separator 
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。 
static char separatorChar 
与系统有关的默认名称分隔符。 

常用方法

boolean canExecute() 
测试应用程序是否可以执行此对象表示的文件

boolean createNewFile() 
当且仅当不存在具有此对象指定名称的文件时,不可分地创建一个新的空文件。 
 // File的对象存在java内存中,仅仅是硬盘上一个文件的内存表示,不是文件

boolean delete() 
删除此对象表示的文件或目录。//只能删除空文件夹 
    
boolean exists() 
测试此对象表示的文件或目录是否存在
    
String getAbsolutePath() 
返回此对象的绝对路径名字符串 
    
File getAbsoluteFile() 
返回此对象的绝对路径名形式。 
    
String getName() 
返回由此对象表示的文件或目录的名称 

String getPath() 
将此对象转换为一个路径名字符串。 
    
 String getParent() 
返回此对象父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null//父路径:文件资源管理器的搜索框显示的路径

File getParentFile() 
返回此对象父目录的对象;如果此路径名没有指定父目录,则返回 null 

boolean isDirectory() 
测试此对象表示的文件是否是一个目录。

long length() 
返回由此对象表示的文件的长度。//eg:3MB,2GB...

long lastModified() 
返回此对象表示的文件最后一次被修改的时间。 

String[] list() 
返回一个字符串数组,这些字符串指定此对象表示的目录中的文件和目录。   

String [] list(FilenameFilter filter) 
返回一个字符串数组,这些字符串指定此对象表示的目录中满足指定过滤器的文件和目录。 

boolean mkdir() //一级目录
创建此对象指定的目录。 
boolean mkdirs() //多级目录
创建此对象指定的目录,包括所有必需但不存在的父目录。 

boolean renameTo(File dest) 
将对象剪切至dest位置
    
File[] listFiles() 
返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 

File[] listFiles(FileFilter filter) 
返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录 

boolean isFile() 
测试此抽象路径名表示的文件是否是一个标准文件。 

父路径+文件名=绝对路径;即:getParent()+getName()=getAbsolutePath();

获取dir下"第一级别"下的所有文件和目录的名字的数组

public class FileDemo {
    public static void main(String[] args) {
        File dir=new File("D:/Package");
        //获取dir下"第一级别"下的所有文件和目录的名字的数组
        String[] names = dir.list();
        for (String name : names) {
            System.out.println(name);
        }
    }
}

利用String [] list(FilenameFilter filter)方法获取某一目录下第一级别符合要求的的文件名和目录名

  1. FilenameFilter是一个接口,其内有一个方法:
boolean accept(File dir, String name) 
测试指定文件是否应该包含在某一文件列表中。 
  1. 利用匿名内部类 覆盖重写accept()方法;

代码示例:

public class FileDemo {
    public static void main(String[] args) {
        File dir=new File("D:/Package");
        //dir调取list方法时,会拿到第一级别下的所有文件和目录
        //调用accept()方法进行筛选,只选出符合条件的
        String[] names = dir.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".zip")):{
                    return true;
                }
                return false;
            }
        });
        for (String name : names) {
            System.out.println(name);
        }
    }
}

获取某目录下第一级别的文件和目录对象

public class FileDemo {
    public static void main(String[] args) {
        File dir = new File("D:/Package");
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()){
                System.out.println(file.getName());
            }
        }
    }
}

获取某目录下第一级别的符合要求文件和目录对象

1.FileFilter是一个接口,其内有一个方法:

an accept(File pathname) 
测试指定对象是否应该包含在某个路径名列表中。 
  1. 利用匿名内部类 覆盖重写accept()方法;

代码示例:

 public class FileDemo {
    public static void main(String[] args) {
        File dir = new File("D:/Package");
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if (pathname.isDirectory()) {
                    return true;
                }
                return false;
            }
        });
        for (File file : files) {
            System.out.println(file.getName());
        }
    }
}

文件移动(rename()方法)

背景:在D盘ABC文件夹下,有六个.txt文件,文件名前半部分是固定的,叫"世界上最美丽的地方之";
任务:把六个.txt文件从D:/ABC移动的D:/ABC&把文件名前半部分去掉

public class FileDemo {
    public static void main(String[] args) {
        File dir = new File("D:/ABC");
        String goout="世界上最美丽的地方之";
        int len=goout.length();
        //File就表示文件,把文件从D:/ABC拿走,放到File类型的数组里面
        File[] files = dir.listFiles();
        for (File file : files) {
            //得到文件的名字
            String filename = file.getName();
            //截取goout之后的文件名字
            String newName = filename.substring(len);
            //创建新的文件,父路径+文件名=绝对路径;
            File newFile = new File(dir, newName);
            //File类型的数组里面的元素(文件)剪切到新的文件位置上
            file.renameTo(newFile);
        }
    }
}

递归删除

递归:方法调方法

public class FileDemo {
    public static void main(String[] args) {
        File dir = new File("D:/ABC");
        FileDemo.deleteRecursion(dir) ';
    }
    //递归删除指定文件夹
    private static void deleteRecursion(File dir) {
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                System.out.println(file.getName() + "正在被删除--->");
                file.delete();
            }else{
                //递归调用此方法
                deleteRecursion(file);
            }
        }
        System.out.println(dir.getName()+"==>目录正在被删除");
        dir.delete();
    }
}

java中IO流

概述

Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入、输出两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。

流的分类

  1. 输入流和输出流
    按照流向来分,可以分为输入流和输出流
    输入流:只能从中读取数据,而不能向其写入数据;基本上是从磁盘文件到系统内存。
    输出流:只能向其写入数据,而不能从中读取数据;基本上是从系统内存到磁盘

    Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。他们都是一些抽象基类,不能直接创建实例对象。
  2. 字节流与字符流
    字节流和字符流的用法几乎一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的数据单元是一个byte字节,而字符流操作的数据单元是一个char字符
    字节流主要由InputStream和OutputStream作为基类,字符流主要由Reader和Writer作为基类。

InputStream字节输入流

InputStream是所有字节输入流的抽象基类,本身并不能创建实例来执行输入,它是所有字节输入流的模板,它的方法是所有字节输入流都可以使用的方法。

stract  int read()//返回:下一个数据字节;若到达流的末尾,则返回 -1
从输入流中读取数据的下一个字节。 

int read(byte[] b) //返回:读入缓冲区的总字节数;如果因为已经到达流末尾而不再有数据可用,则返回 -1。 
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 

int read(byte[] b, int off, int len)//返回:读入缓冲区的总字节数;如果因为已到达流末尾而不再有数据可用,则返回 -1。 
将输入流中最多 len 个数据字节读入 byte 数组。 

Reader字符输入流

Reader是所有字符输入流的抽象基类,任何一个字符输入流的子类都可以使用Reader上定义的方法。使用字符输入流的可以更加便利的对文本文件进行处理,而且也不会出现中文乱码等情况。因为使用Reader字符流时是按照字符来进行读取,一次读取到一个完整字符数据。

int read()//返回:读取的字符数,如果已到达流的末尾,则返回 -1 
读取单个字符。 

int read(char[] cbuf)//返回:读取的字符数,如果已到达流的末尾,则返回 -1 
将字符读入数组。 

abstract  int read(char[] cbuf, int off, int len) //返回:读取的字符数,如果已到达流的末尾,则返回 -1 
将字符读入数组的某一部分。 

OutputStream字节输出流

OutputStream字节输出流提供了对外输出功能的操作,可以将程序运行产生的数据保存到指定的文件中去,是实现数据的持久化操作。

void write(byte[] b) 
将 b.length 个字节从指定的 byte 数组写入此输出流。
 
void write(byte[] b, int off, int len) 
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
 
abstract  void write(int b) 
将指定的字节写入此输出流 

void close() 
关闭此输出流并释放与此流有关的所有系统资源。 
void flush() 
刷新此输出流并强制写出所有缓冲的输出字节。 

Writer字符输出流

riter是Java提供的字符输出流的抽象基类,所有的字符输出流实现类都是以Writer为基础进行扩展。

Writer append(char c) 
将指定字符添加到此 writer。 

abstract  void close() 
关闭此流,但要先刷新它。
 
abstract  void flush() 
刷新该流的缓冲。 

void write(char[] cbuf) 
写入字符数组。 
    
abstract  void write(char[] cbuf, int off, int len) 
写入字符数组的某一部分。
    
void write(int c) 
写入单个字符。 

void write(String str) 
写入字符串。 

void write(String str, int off, int len) 
写入字符串的某一部分。 

FileInputStream

构造方法

FileInputStream(File file) 
          通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
public class IOTest01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("F:\\a.txt");
            //1)我们定义一个变量readData,来存放读取到的字节
            int readData = 0;
            //2)以下代码的意思是,只要读取到的字节不为-1,也就是文本没有读完,就一直循环地读,并把
            //读到的值赋值给readData,同时打印readData。
            while((readData=fis.read())!=-1){
                System.out.println(readData);
            }
        //!!!注意从现在开始,这行以下代码基本都是相同的,我们不需要再关注它们
        //重要事情再说一遍,接下来的重复代码,我们看try{}中的代码即可
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class IOTest01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("a.txt");
            int readData = 0;
            //1)定义一个字节数组,长度为4
            byte[] bytes = new byte[4];
            
            //2)将字节4个4个地读取到bytes中
            while((readData=fis.read(bytes))!=-1){
            //3)这一步稍作修改,使用String类的构造方法传入一个字节数组,将字节数组中的数据转换成
            //字符串,从下标0读到下标为readData的位置
				System.out.println(new String(bytes,0,readData));
			}
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

对 if(fis!=null) 的解释:

这个判断条件很有必要加,加完之后。
假设fis为空,那么就不需要关闭了,就不会执行 if{ } 中关闭流这些代码;如果fis不为空,就关闭流。
这样做的目的是防止空指针异常。
你想想,如果不加的话,fis为空,即fis这个引用没有指向任何对象,那它怎么调用close()方法,这样就会发空指
针异常。

对read()方法的解释:

read()方法就是“读取”,每次读取一个字节大小的数据,返回一个int类型的字面量。
每读取一个,光标就会往下移动一个字节,以便读取下一个字节。
当读取的返回值是-1时,表示文本已经读完了。

read(byte[] b)的解释:

这个方法,是将读取到的多个字节存放到字节数组中,然后一次性搬到内存。这就好比外卖小
哥学聪明了,他用大袋子装了很多饭盒,这就不用往复跑很多次了。这样,效率就变高了。

注意:read()的返回值是一个小于256的int类型的数字;read(byte[] b)的返回值表示读到了几个字节数量,比如我们
定义一个byte[1024],也就是能存1024个字节的数组,然后我们的文件大小是1025个字节,那么使用循环读取,第
一次返回的int是1024,第二次是1,第三次是-1。

FileOutputStream

构造方法

FileOutputStream(File file) 
          创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 
FileOutputStream(File file, boolean append) 
          创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 
//第二个参数 boolean append,当为true时,表示在文本内容末尾加上新的内容;当为false时,表示将文本内容覆盖掉,写入新的内容。
public class IOTest01 {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
	        //同样的,我们关注try{}中的代码即可
	        //1)假设我们的new.txt文件是不存在。那它会像FileInputStream那样出现找不到文件的异常吗?
	        //答案是不会,如果new.txt文件不存在,由于这是个相对路径,因此会在当前项目创建一个new.txt
	        //文件
            fos = new FileOutputStream("new.txt");
           	//2)我们要想 new.txt 中写入数据,先定义一个有数据的字节数组。97、98、99、100分别对应字
           	//符'a'、'b'、'c'、'd'
            byte[] bytes = {97,98,99,100};
            //3)调用write()方法将数组bytes中的数据写到文件 new.txt 中
            fos.write(bytes);
            //4)前面已经有提到,输出流在关闭之前,需要调用flush()方法清空数据流。防止数据缺漏
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

执行以上代码之后,我们可以看到在 javase 文件夹下,多了一个新的 new.txt 文件,且文本内容是 abcd 。此时,我们想在 new.txt 中,再加上 efg,使文本内容变成 abcdefg,这里就不再演示了,大家改改代码验证一下就行。

FileOutputStream fos = new FileOutputStream("new.txt",true);

汉字转换成字节:使用getBytes()方法。

try{
	fos = new FileOutputStream("new.txt",true);
	byte[] bytes = "我爱中国".getBytes();
	fos.write(bytes);
	fos.flush();
}

//当然,还能简化一点
try{
	fos = new FileOutputStream("new.txt",true);
	fos.write("我爱中国".getBytes());
	fos.flush();
}

文件复制

已经学习了FileInputStream和FileOutputStream,输入流是将数据从硬盘读取到内存,输出流是将数据从内存写入到硬盘,那这样的话,我们就能实现文件的复制了。这次不操作文本文件,我们来试一下图片的复制。

public class IOTest01 {
    public static void main(String[] args) {
    	//1)声明两个对象,要在try{}以外声明,否则不能在finally{}中关闭
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("01.jpg");
            fos = new FileOutputStream("IO流\\01.jpg");
            byte[] bytes = new byte[1024];
            int len=-1;
            while(len=fis..read(bytes))!=-1){
                fis.read(bytes);
                fos.write(bytes);
                fos.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        //2)注意这里两个流都要关闭
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

有了前面的学习,这里的代码就不难了。执行程序,图片就复制成功了。
注意这里前面说过,字节流能操作任何类型的文件,所以这里除了复制图片,复制其他类型的文件也是可以的,只不过如果是比较大的视频,效率可能会比较低。

FileReader、FileWriter

字节流的read()和write(),是一个字节一个字节地读取和写入,那么字符流的read()和write(),自然就是一个字符一个字符地读取和写入。比如我们读取一个内容为 我爱abc 的文本文件,会先读 ‘我’ ,然后光标移动一位,下次调用read()方法的时候,就是读 ‘爱’。

FileReader

public class IOTest01 {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("a.txt");
            int len=-1;
            char []gyu=new char[1023];
            while ((len=reader.read(gyu))!=-1){
               // System.out.println(new String(gyu,0,len));
                System.out.println(gyu);
                //这两种读取方法都可以,第一种是把char型数据变成字符串;第二种是直接读字符char(一个个往外蹦)
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        	//3)这里的变量名要修改成fr喽,别忘了
            if(fr!=null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


FileWriter

public class IOTest01 {
    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("a.txt",true);
            char[] chars = {'我','爱','中','国'};
           // 直接把要放入硬盘的数据放到一个数组里,直接把这个数组里的字符char输出即可,不需要while循环
            fw.write(chars);
            fw.flush();//刷新
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fw!=null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

缓冲流

有四个缓冲流,分别是 BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream ,和文件流中的字节流和字符流类似,缓冲流中的字节流和字符流之间基本的方法也都是类似的,因此这里只介绍 BufferedReader和BufferedWriter。

所谓缓冲流,就是自带字节数组或者字符数组,我们不需要像之前那样去定义字节数组或者字符数组了,因为缓冲流中是自带的。

BufferedReader和BufferedWriter特有方法(创建对象不能再用多态了)

String readLine() //BufferedReader
          读取一个文本行 
    
void newLine() //BufferedWriter
          写入一个行分隔符。 

BufferedReader

public class IOTest01 {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
        br = new BufferedReader(new FileReader("a.txt"));
            String str = null;
            //只要读到的字符串不为null,也就是文本没有结束,就一直一行一行地往下读
            while((str=br.readLine())!=null){
				System.out.println(str);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(br!=null){
                try {
                //3)注意这里,只有最外层的包装流BufferedReader需要关闭
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这里还有两个概念,就是节点流和包装流,很好理解。当我们将一个流对象A作为参数传给另一个流B的构造方法时,
A就是节点流,B就是包装流。
节点流和包装流是相对而言的,比如下面的代码中,InputStreamReader对于FileInputStream来说是包装流,对于
BufferedReader来说是节点流。

BufferedWriter

BufferedWriter就不多介绍了,你想想,BufferedReader可以读取一行,且读取到的一行是字符串,那么相对的,BufferedWriter的write()方法,也可以写入一行字符串

public class IOTest01 {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
       br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a.txt")));
            br.write("张三\n");
            br.write("李四");
            br.flush();//刷新
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(br!=null){
                try {
                //3)注意这里,只有最外层的包装流BufferedReader需要关闭
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

flush()何时使用?

IO流的四个基类:InputStream,OutputStream,Reader,Writer;

只有OutputStream和Writer有flush()方法=>flush()在OutputStream和Writer的子类中使用

转换流(把字节流转换成字符流)

输入/输出体系中提供了两个转换流InputStreamReader和OutputStreamWriter,这两个转换流用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流。

InputStreamReader

在处理文本文件的数据时,字符流往往要比字节流方便了很多,但是在某种情况下,处理数据时,并不会直接对数据源(文件)操作,接收到的有可能是一个字节流。那么在这种情况下就希望对字节流进行一次包装,能把字节流包装成一个字符流。

InputStreamReader就是一个包装流,创建这个流对象时需要传入一个字节流对象作为参数,并将这个字节流包装成一个字符流对象返回,包装后的字符流跟普通的字符流对象操作方式完全一样。流对象操作完毕后,进行关闭时,只需要关闭包装流即可,因为关闭包装流时会同时将内部的字节流关闭

OutputStreamWriter

OutputStreamWriter是一个字符输出的转换流,可以将一个字节输出流转换成一个字符输出流。创建转换流OutputStreamWriter对象时,需要将一个字节输出流对象作为参数传递进去,并包装成一个字符输出流返回。输出操作结束后,关闭字符流时,同时也会将内部的字节输出流同时关闭。使用包装流进行输出时,希望原有数据文件中的数据不被覆盖掉,那就需要传入给OutputStreamWriter的字节流就是以追加的方式进行输出的。因为OutputStreamWriter仅仅是个包装流,将字节流进行了一次封装,底层还是字节流的机制运行的。

步骤:

  1. 将字节流转换成字符流(也就是InputStreamReader或者 OutputStreamWriter)
    InputStreamReader是InputStream的子类,FileReader的父类,InputStreamReader是一个具体类(非抽象类),那几个read方法是它从InputStream那继承来的,又传给了FileReader==>我们用FileReader来完成的常规操作,用InputStreamReader也可以完成(两者功能上等效(此时是节点流),且InputStreamReader同时还可以起转换作用(此时是包装流))==>FileReader可以被缓冲流修饰,InputStreamReader也可以


    ?OutputStreamWriter是OutputStream的子类,FileWriter的父类, OutputStreamWriter是一个具体类(非抽象类),那几个write方法是它从OutputStream那继承来的,又传给了FileWriter==>我们用FileWriter来完成的常规操作,用 OutputStreamWriter也可以完成(两者功能上等效(此时是节点流),且 OutputStreamWriter同时还可以起转换作用(此时是包装流))==>FileWriter可以被缓冲流修饰, OutputStreamWriter也可以
  2. 利用字符流的方法进行相关操作
  3. 关闭InputStreamReader或者 OutputStreamWriter(两者是包装流),因为关闭包装流时会同时将内部的字节流关闭
 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("syt"));

==>进一步   BufferedWriter bw = new BufferedWriter(osw);
==>: BufferedWriter bw= new BufferedWriter(new OutputStreamWriter(new FileOutputStream("syt")));

对象流: ObjectInputStream、ObjectOutputStream

学习怎么向文件中写入java对象。

为什么我们要向文件中写入java对象呢?

java对象中可以保存数据,假设我们定义了一个User类(用户)来存放用户的一些信息,比如账号、密码,以及个人
信息等,这些在用户注册完账号后,都要先把数据作为参数保存在User对象中,再将User对象写入到文件中,这样就
能将用户信息永久地保存在硬盘中了。
当下一次用户登陆的时候,我们就去文件中找到对应的账号,只有用户输入的密码是正确的,才能成功地登陆。

什么是“序列化”和“反序列化”?

这两个流因为能写入和读取文件中的java对象,因此称为“对象流”。其中,ObjectOutputStream又称为“序列化流”,
ObjectInputStream又称为“反序列化流”。
序列化是指将对象从内存写入到硬盘的过程,反序列化是指将对象从硬盘读取到内存的过程。

构造方法

构造方法仍然是需要传入FileOutputStream和FileInputStream(多态,再次强调!)

ObjectInputStream(InputStream in) 
          创建从指定 InputStream 读取的 ObjectInputStreamObjectOutputStream(OutputStream out) 
          创建写入指定 OutputStreamObjectOutputStream

先创建一个User类,等会就来存这个类的对象

public class User {
    private long account; //账号
    private String password; //密码

    public User(long account, String password) {
        this.account = account;
        this.password = password;
    }

    public long getAccount() {
        return account;
    }

    public void setAccount(long account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "account=" + account +
                ", password='" + password + '\'' +
                '}';
    }
}
  1. 序列化流将对象写入文件
    现在,我们来实际操作一下,首先是序列化流ObjectOutputStream

    public class IOTest01 {
        public static void main(String[] args) throws IOException {
        	//1)创建一个序列化对象
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
            //2)创建user对象
            User user = new User(123456, "abc");
            //3)调用方法将user对象写入
            oos.writeObject(user);
            oos.flush();
            oos.close();
        }
    }
    

    执行以上程序,结果出异常了
    解决方法很简单,我们修改一下User类如下:也就是去实现Serializable这个接口

    public class User implements Serializable{
    }
    

    我们查看这个Serializable接口,发现这个接口里面竟然什么都没有写!
    这里扩展一下,向Serializable这种什么内容都没有的接口,称为’标识接口’。
    实现Serializable这个接口,并不是想要实现它的某些方法,或者遵从它的某些规范,它仅仅只是一个标识的作用,
    它仅仅只是要告诉程序员,实现了它的类,就是可以序列化的类。

    也就是说,实现了Serializable之后,现在我们的User对象可以序列化了。
    继续执行将其序列化的代码,发现成功了,而且生成了一个user文件,且打开之后都是乱码。想要知道里面存的是什么,方法就是使用反序列化流来读取。

  2. 反序列化流读取文件中的对象
    其实,我们发现,加上序列化流中上述代码,和之前相比只多了两个新的方法,writeObject()和readObject(),所以要掌握的新内容也不多,下面直接给代码,很容易看懂
    接下来,开始反序列化流ObjectInputStream的代码:

    public class IOTest02 {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
        	//1)定义一个反序列化对象,指向user文件
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
            //2)调用readObject()方法,读取文件中的对象
            Object obj = ois.readObject();
            System.out.println(obj); //打印一个对象,其实是调用该对象的toString()方法,我们已经重写了该方法
            ois.close();
        }
    }
    

    成功读取出了对象。

    1. 存入和读取多个对象向文件中写入多个对象,不能向其他流一样,通过 new FileOutputStream(" ",true) 的方式,也就是在文件末继续写入对象来实现(原因比较复杂,我会重新开坑,写一篇专门介绍这个原因的博客)。
      而是采用集合的方式,向集合对象中添加入多个User对象,再将集合对象写入到文件中,来实现一次性存入多个User对象。如果你还没学过集合,就先理解成集合是一个容器,可以存放对象。当然,这不是唯一的实现方式,且这是较差的一种方式,我都会在新坑里做介绍的。

      我们先看看代码,注意要先把原来的 user 文件删除掉。
      写入集合对象的代码如下:

      public class IOTest01 {
          public static void main(String[] args) throws IOException {
              ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
              List<User> list = new ArrayList<>(); //创建一个集合
              list.add(new User(111,"abc")); //add()方法是,向集合中添加对象,我们这里直接传入一个匿名对象
              list.add(new User(222,"abc")); 
              list.add(new User(333,"abc"));
              oos.writeObject(list); //将集合写入文件
              oos.flush();
              oos.close();
          }
      }
      

      读取集合对象:

      public class IOTest02 {
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
              //将集合读取出来,由于readObject()方法是返回一个Object对象,因此我们这里要将Object强转成List
              List list = (ArrayList) ois.readObject(); 
              User user1 = (User) list.get(0); //集合的get()方法,可以通过传入索引来获取到对象
              User user2 = (User) list.get(1);
              User user3 = (User) list.get(2);
              System.out.println(user1); //打印取到的User对象
              System.out.println(user2);
              System.out.println(user3);
              ois.close();
          }
      }
      

      这样,我们就取出了User对象

    这一小节的开头我有说到,使用 new FileOutputStream(" ",true) 作为参数传给 ObjectOutputStream 的构造方法,来实现添加对象的方式是行不通的。因此,如果我们还要写入更多的User对象,只能将集合读取出来,往集合中存入新的User对象,再将集合继续写入文件来实现。我们来看下面的代码,稍微修改一下IOTest02即可。

    public class IOTest02 {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
            //ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
            List list = (ArrayList) ois.readObject();
            System.out.println(list); //这里的打印,是为了确认一下user文件集合中只有3个User对象
            list.add(new User(444,"abc")); //然后我们往读取出来的集合中,再添加2个User对象
            list.add(new User(555,"abc"));
            ois.close(); 
            //注意以下细节,不能在上面那个位置中定义,会报错
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
            oos.writeObject(list);
            oos.flush();
            oos.close();
        }
    }
    

    再执行一下以下程序,验证我们的文件中的集合中已经有5个User对象了。

    public class IOTest03 {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
            List list = (ArrayList) ois.readObject();
            System.out.println(list);
        }
    }
    

    这种方式,显然过于繁琐,所以我才说这是一种比较差劲的方式。当然,这里你也可以不用深究,也没有必要深究。如果想深究的话,可以去看看其他博客,或者等我把填坑的博客写完,我会在这里加上链接

    1. transient关键字很简单,比如你给 User 类中的 password 变量加上 transient,(private transient String password;)那么序列化 User 类对象的时候,变量 password 将不会被写入到文件中。
      以下代码可以直接复制粘贴(记得删除 user 文件),没有学习的必要,主要是要知道 transient 关键字的作用即可。

      public class IOTest01 {
          public static void main(String[] args) throws IOException {
              ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
              oos.writeObject(new User(111,"abc"));
              oos.flush();
              oos.close();
          }
      }
      
      
      public class IOTest02 {
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
              User user = (User) ois.readObject();
              System.out.println(user); //输出结果 User{account=111, password='null'}
          }
      }
      

      我们取出来的 User 对象,其变量 password 的值是null,说明我们在给 User 类的 password 加上关键字 transient 之后,其就没有被写入到文件了。

    2. 序列化版本号了解序列化版本号之前,我们还是先来试试代码。
      我们把 User 类中的 transient 去掉,然后运行第4节中的两段代码,此时我们的 user 文件中就有一个对象,User{account=111, password=‘abc’}。现在,我们修改一下 User 类,给它新增一个成员变量 email,如下,请大家直接复制代码试验即可

      public class User implements Serializable {
          private long account; //账号
          private String password; //密码
          private String email;
      
          public User(long account, String password, String email) {
              this.account = account;
              this.password = password;
              this.email = email;
          }
      
          public long getAccount() {
              return account;
          }
      
          public void setAccount(long account) {
              this.account = account;
          }
      
          public String getPassword() {
              return password;
          }
      
          public void setPassword(String password) {
              this.password = password;
          }
      
          public String getEmail() {
              return email;
          }
      
          public void setEmail(String email) {
              this.email = email;
          }
      
          @Override
          public String toString() {
              return "User{" +
                      "account=" + account +
                      ", password='" + password + '\'' +
                      ", email='" + email + '\'' +
                      '}';
          }
      }
      

      改完之后,我们再去执行第4节中的第二段代码,来读取我们的 User 类对象,发现报错了。错误如下:
      java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 3094711356857122676, local class serialVersionUID = 4110144175829258817

      原来报错的根本原因是类的序列化版本号不一致。

      那么这个序列化版本号究竟是什么呢?
      Java虚拟机在看到 User 类实现了 Serializable 这个标识接口之后,就会给这个类自动生成一个序列化版本号。这个版本号的作用,是确保流中的类和文件中的类是同一个类,因为只有同一个类,序列化之后,才能被反序列化。

      由于我们修改了 User 类,导致 Java虚拟机 重新为 User 类生成了一个新的序列化版本号,所以导致不一致。
      但是,我们自己清楚,这两个类确实都是 User 类。所以,我们不需要 Java虚拟机 为我们自动生成序列化版本号,我们可以自己定义,且当我们自己定义的时候,便不会再自动生成。
      自定义序列化版本号非常简单,只需要在类中加上 serialVersionUID 这个常量即可,如下:

      public static final long serialVersionUID = 16516516543215L; //可以随便写
      

    【小总结】:我们想要序列化和反序列化的对象,都要实现 Serializable 这个接口,且最好手动给它加上一个自定义的序列化版本号。

    另外,我们在 IDEA 中也可以设置自动生成序列化对象的代码,设置方法如下:将√取消掉,然后apply,OK。

    设置完之后,只要实现了 Serializable 接口的类,可以将光标停在该类上,然后按 “Alt + Enter”,就能手动添加序列化版本号了。

打印流:PrintStream【重要】、PrintWriter

你还记得我们学习java的第一个程序“Hello World”吗?打印字符串的语句:

System.out.println("Hello world");

这我们都会写。但是你有没有去想过,为什么System.out.println(),就能将字符串打印在控制台上吗?我来解释给你听。
1)首先了解System,它也是一个类,在java.lang包下面,java规定,lang包下的类都是不用导入就能直接使用的。因此我们不导包,也能直接使用System类。
2)其次了解System.out。我们来看System类的部分源代码。System类中有一个引用out,其类型是就是PrintStream,我们还发现,这是一个用static修饰的引用,因此我们能直接用 System.out 进行访问。
这里的 out 是 null ,但往下的代码中会对其进行赋值,这里涉及到的有些方法是用C++或者C代码写的,我们没有必要再深究了。只要知道,其最后会被赋值一个PrintStream对象即可。
3)接着了解整句,System.out.println()。我们看PrintStream的源码。我们发现,原来println()是PrintStream类的一个方法。关于这个方法为什么能将字符串打印到控制台上,我们依旧没有必要再深究了。

来,也就是说我们得到以下的代码,两者是完全等价的。

public class IOTest02 {
    public static void main(String[] args) throws IOException {
        System.out.println("张三");

        PrintStream ps = System.out;
        ps.println("张三");
    }
}

理解了System.out.println()之后,先转个脑回路,我们思考一个问题:凭什么只能输出在控制台上,我输出到其他地方不可以吗?我们来看看System类的源码。我们发现一个setOut()方法,根据字面意思,就是设置输出,这应该是就是我们要用到的方法了。

setOut()方法调用了两个方法,我们来看setOut0()方法。发现它是个本地方法(用关键字 native 修饰的方法称为本地方法,底层是用C++写的),那就不深究了,我们会用setOut()方法就行。

setOut()方法需要传入一个PrintStream对象,因此我们需要先创建一个PrintStream对象,PrintStream的构造方法又需要传入一个OutputStream,OutputStream是抽象类,因此我们要使用其子类FileOutputStream来创建(利用多态特性)。
万事俱备了,我们来看代码

public class IOTest01 {
    public static void main(String[] args) throws IOException {
        PrintStream ps = new PrintStream(new FileOutputStream("a.txt"));
        System.setOut(ps);
        //以下语句会打印到当前项目下的a.txt文件中
        System.out.println("张三");
        System.out.println("李四");
        System.out.println("王五");
    }
}

那输出到文件有什么作用呢?作用大着呢,我们可以利用上述代码来写一个日志类,通过日志类来生成日志。(日志文件,就是专门用来记录程序执行情况的文件)
日志类的代码如下:(这个类的代码是垃圾代码,不规范,只是想让大家知道日志是个什么东西)

public class Logger {
    public static void log(String str){
        try {
        	//1)创建一个打印流,打印在log.txt文件上
            PrintStream ps = new PrintStream(new FileOutputStream("log.txt",true));
            //2)通过System类修改输出方向
            System.setOut(ps);
            //3)创建日期对象
            Date date = new Date();
            //4)格式化日期:年-月-日 时-分-秒 毫秒
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String time = sdf.format(date);
            //5)将日期和参数str打印到log.txt文件中
            System.out.println(time + ":" + str);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

创建好Logger类之后,我们执行以下程序。日志文件就生成了,打开看文件中的内容,你就了解日志文件是个什么玩意儿了。

public class IOTest01 {
    public static void main(String[] args) throws IOException {
        Logger.log("调用了login()方法,用户开始登陆");
        Logger.log("用户登陆成功");
    }
}

内存流,数据流

读写流

压缩流

NIO

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-25 23:42:19  更:2021-08-25 23:42:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:08:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码