Java - IO部分的学习总结

文章目录

1.File类

File类是文件和目录路径名的抽象表示。为什么是抽象表示,因为它不是那个文件,它只是一个Java中的对象,它拥有大量的方法。

  • File类的构造方法
构造器描述
File(File parent,String child)在父类抽象路径名中,根据子路径名字符串创建新的File实例
File(String pathname)通过将给的路径名字符串转化为抽象路径名来创建新的File实例
File(String parent,String child)在父路径名字符串中,根据子路径名字符串创建新的File实例
File(URI uri)

比较常用的是前面三种。

  1. 创建文件的时候创建失败:
    1. 把编译器使用管理员身份运行
    2. 换D盘
  2. 按字节1024Byte=1KB,1024k=1MB
  3. 不同系统有不同的符号表示文件级别,Java系统提供了统一的分隔符:路径分隔符\ 名称分隔符;
  4. 练习File类文件操作的时候一定要慎重
  • File类的常用方法:
变量和类型方法描述
booleancreatNewFile()没有文件就创建,存在则创建失败
booleandelete()删除此抽象路径名表示的文件或目录,此文件不一定存在
booleanexists()判断文件是否存在
StringgetAbsolutePath()获取文件的绝对路径名(地址),以字符串形式返回
StringgetName()返回此文件的路径名或目录名称,返回字符串形式
StringgetParent()返回此文件的父项路径名字符串,没有返回null
FilegetParentFile()返回此文件的父项抽象路径名,没有返回null
booleanisDirectiory()测试该路径名表示的文件是否是一个文件夹
booleanisFile()测试该路径表示的文件是否为一个普通文件
longleng()返回文件的大小,以字节形式放回
String[]list()返回字符串,用于命名此抽象路径名表示的目录中的文件或目录
File[]listFiles()获取文件夹里面的所有的文件夹和文件对象,返回的使用一个文件数组来存储
booleanmkdir()没有文件夹就创建,有的话就创建失败
booleanmkdirs()创建此抽象路径名指定的目录,包括任何必需但不存在的父目录
booleanrenameTo(File dest)重命名此抽象路径名表示的文件,移动文件位置

File方法很多,具体的得查看API。

1.1对一些构造方法和方法的使用

1.1.1创建文件和创建文件夹:
public class Demo1 {
    public static void main(String[] args) throws IOException {
        //File(String pathname)构造方法
        File dir = new File("D://haha.txt");
        //文件的创建,文件名称为haha,类型为txt的文件
        dir.creatNewFile();
        //文件夹的创建,文件夹名称为haha.txt
        boolean flag = dir.mkdir();
    }
}

注意其中的createNewFile()创建的是文件,mkdir()创建的是文件夹。

1.1.2在文件夹里面创建一个文件:
public class Demo1 {
    public static void main(String[] args) throws IOException {
        //假设haha文件夹已经存在
        //File(String pathname)构造方法,创建File对象
        File dir = new File("D://haha");
        
        //文件的创建,文件名称为a,类型为txt的文件
        File a = new File(dir,"a.txt");
        a.createNewFile();//创建成功
    }
}

也可以以这样的方式来进行创建:

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //假设haha文件夹已经存在
        //文件的创建,文件名称为b,类型为txt的文件
        File b = new File("D://haha","b.txt");
        b.createNewFile();//创建成功
    }
}
1.1.3对文件进行删除

delete()方法:

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //由上面创建的文件夹及其文件已经存在
        File dir = new File("D://haha");
        File a = new File(dir,"a.txt");
        File b = new File("D://haha","b.txt");
      
        //进行删除,delete()方法
        a.delete();
        b.delete();//a.txt与b.txt已经被删除
    }
}
1.1.4移动文件路径

renameTo()方法,返回boolean,判断是否移动成功.

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //注意级数之间要使用\\符号分隔开
        File file = new File("D:\\book.txt");
        File newFile = new File("D:\\a.txt");
        //通过ranameTo()方法就可以把file这个指向的文件移动到newFile指定的位置,同时文件已经被重命名	
        file.renameTo(newFile);
    }
}
1.1.5File类的字段

当跨平台的时候为了避免系统与系统之间的路径分隔符不一样导致代码出错的问题,Java系统提供了统一的路径分隔符和名称分隔符.下面就来打印一下:

public class Demo1 {
    public static void main(String[] args) throws IOException {	
        //打印路径分隔符.用于跨不同操作系统需要使用的
        System.out.println(File.pathSeparator);
        //名称分隔符
        System.out.println(File.separator);
    }
}

得到的分别是;\

示例:

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //等价于:"D:\\book.txt"
        File file = new File("D:"+File.pathSeparator+"book.txt");
        //等价于:"D:\\文件\\a.txt"
        File newFile = new File("D:"+File.pathSeparator+"文件"+File.pathSeparator+"a.txt");
    }
}

2.文件遍历案例:

以文件遍历进行一些简单的File类操作,加深印象。

2.1创建对象

文件的遍历也就是对listFiles()等方法的使用,listFile()得到的是一个一层的文件和文件夹,不会再深入下去,简单来说就是比如打开D盘页面里面隐藏和非隐藏的所有文件和文件夹就是一层,而文件夹下又有文件以及文件夹,就属于下层.若想要遍历所有文件,就得使用递归的方法。示例如下:

先创建它的File对象、File[]接收listFiles()方法读取的数据。

public class Demo2 {
    public static void main(String[] args) {
        //先创建一个File对象,位置定义为D盘
        File file = new File("d:\\");
        //创建File数组
        //调用listFile()方法获取file对象里面的一层的所有文件夹和文件对象
        File[] files = file.listFiles();
    }
}

2.2遍历方法

再者就是自定义一个方法用来遍历文件夹,该方法为了区分与listFiles()方法名称,自定义为listFile(File[] files)(就算它们同名系统也不会弄混淆,因为它们的使用方法不一样)。对foreach循环不明白的可以查看我集合那一节的总结有关于foreach的解释。

//为了方便,直接定义再main方法里面
//传入一个包含文件/文件夹的数组,对该数组进行遍历
public static void listFile(File[] files){
    //不为空 且 长度大于0,否则不予操作
    if(files!=null && files.length>0){
        //使用foreach遍历文件
        for(File file:files){
            //判断是不是文件
            if(file.isFile()){
                //使用getName()方法回去文件对象的名称
                //使用endsWith()方法判断结尾是什么文件类型
                //是文件对象就看它是不是.txt结尾的文件(可以改成.png,.avi...)
                if(file.getName().endsWith(".txt")){
                    //还可判断文件大小来决定 是否 获取它的路径:
                    //if(file.length()>100*1024*1024){}//
                    //找到了 文件
                    //getAbsolutePath()获取绝对路径字符串
                    System.out.println("找到了一个txt文件:"+file.getAbsolutePath());
                }
            }else {
                //不是文件就是文件夹,再次遍历到所有文件为止
                //此file已经为第一层里面的文件对象,在遍历它的子类们。递归原理
                File[] files2 = file.listFiles();
                listFiles(files2);
            }
        }
    }
}

需要注意的一点是处理NullPointerException的时候一定的先 判断文件不为null 后 再判断文件长度(大小)大于0,正例:

if(files!=null && files.length>0){}

反例:

if(files.length>0 && files!=null){}
2.2.1解释
  1. length()方法获取的大小是以字节为单位获取的(1KB = 1024Bytes,1MB = 1024KB),在判断大小的时候注意这一点。

  2. endsWith()方法是String类里面的方法,可以判断一段字符串是否以指定的后缀结尾,它的说明是这样的:

    1. 变量和类型方法描述
      booleanendsWith(String suffix)测试此字符串是否已指定的后缀结尾
    2. 我们就可以利用这一点判断我们使用getName()方法获取的文件对象地址的字符串是否为我们需要查找的后缀。

  3. getAbsolutePath()方法获取抽象文件的绝对路径名 字符串,注意与getName不一样,getNmae()获取的是名称,getAbsolutePath()获取的是路径名。

  4. 递归原理的方法获取文件夹里面所有的文件对象。

2.3所有代码

public class Demo2 {
    public static void main(String[] args) {
        File file = new File("d:\\");
        File[] files = file.listFiles();
        listFile(files);
    }
    public static void listFile(File[] files){
        if(files!=null && files.length>0){
            for(File file:files){
                if(file.isFile()){
                    if(file.getName().endsWith(".png")){
                        System.out.println("找到了一个png文件:"+file.getAbsolutePath());
                    }
                }else {
                    File[] files2 = file.listFiles();
                    listFile(files2);
                }
            }
        }
    }
}

学习了File类这一节,就可以写一个自己的清理垃圾的代码,只要给垃圾文件的后缀,找到以后使用delete()方法把它清理掉。

但是的得注意,C盘千万别乱清理,很危险的,一键下去看你下个月再见去了,你得重装系统0.0~

3.文件过滤器(了解)

3.1FileFilter接口

本节使用的是FileFilter接口,用来过滤文件的,使用接口得创建一个class,过滤的规则就是调用accept(File)方法。下面先介绍一下FileFilter接口下面的accept()方法.

3.1.1accept()方法
变量和类型方法描述
booleanaccept(File pathname)测试指定的抽象路径名是否包含在路径名列表中

官方解释很生硬,通俗来说就是测试指定的文件/文件夹路径当中是否含有 你 所指定的文件/文件夹,指定什么就得重写accept()方法。

3.2初步代码演示

3.2.1创建一个过滤文件的接口class
//内部类必须加static
//创建一个接口的class,实现接口的就是在描述一个过滤器,专门过滤txt的
static class TXTFileter implements FileFilter{
    //过滤的规则就是这一个方法accept,自己重写,它是一个抽象路径名过滤器
    @Override
    public boolean accept(File pathname) {
        //如果文件名称是以.txt结尾的 或 它是一个文件夹,就留着它
        if(pathname.getName().endsWith(".txt") || pathname.isDirectory())
            //true-保留文件
            return true;
        //false-不保留文件
        return false;
    }
}
3.2.2创建遍历文件的方法listFile(File)

这里传入的是一个文件,不再是之前的一个文件数组

//传入一个文件
public static void listFile(File file){
    //1.    创建一个过滤器(FileFilter) 并 描述规则(接口)
    FileFilter filter = new TXTFileter();
    //2.    传过滤后的文件的方法 获取子文件夹,存在files[]里面
    //		传了过滤器以后文件 通过listFiles()获取到的文件 都会过滤一遍
    File[] files = file.listFiles(filter);

    //确保文件不为空,先确定文件不为null,再判断是否length>0,不能先判断length
    if(files!=null && files.length>0){
        for (File f:files) {
            if(f.isDirectory()){
                //发现是文件夹,递归调用继续处理文件夹
                listFile(f);
            }else{
                //能进来的都是.txt文件
                System.out.println("发现一个txt:"+f.getAbsolutePath());
            }
        }
    }
}
3.2.3创建一个File对象

我所创建的File对象表示D盘,就能过滤D盘的文件

public static void main(String[] args) {
    File d = new File("d:\\");
}
3.2.4全部代码
public class Demo3 {
    public static void main(String[] args) {
        File d = new File("d:\\");
        listFile(d);
    }
    
    public static void listFile(File file){
        FileFilter filter = new TXTFileter();
        File[] files = file.listFiles(filter);
        if(files!=null && files.length>0){
            for (File f:files) {
                if(f.isDirectory()){
                    listFile(f);
                }else{
                    System.out.println("发现一个txt:"+f.getAbsolutePath());
                }
            }
        }
    }
    
    static class TXTFileter implements FileFilter{
        @Override
        public boolean accept(File pathname) {
            if(pathname.getName().endsWith(".txt") || pathname.isDirectory())
                return true;
            return false;
        }
    }
}

就可以过滤文件以后遍历查找目标文件。

3.3优化代码逻辑

3.3.1方式一

通过匿名内部类,就使用一次就可以,值得注意的就是匿名内部类只能使用一次,不能重复使用。直接在listFile()方法里面使用匿名内部类:

public static void listFiles(File file){
        //1.    创建一个过滤器(FileFilter) 并 使用匿名内部类
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".txt") || pathname.isDirectory()){
                    return true;
                }
                return false;
            }
        }
        File[] files = file.listFiles(filter);
        if(files!=null && files.length>0){
            for (File f:files) {
                if(f.isDirectory()){
                    listFile(f);
                }else
                    System.out.println("发现一个txt:"+f.getAbsolutePath());
                }
            }
        }
    }

省掉了后面的过滤class,全部代码是:

public class Demo3 {
    public static void main(String[] args) {
        File d = new File("d:\\");
        listFile(d);
    }
    
    public static void listFiles(File file){
        //1.    创建一个过滤器(FileFilter) 并 使用匿名内部类
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".txt") || pathname.isDirectory()){
                    return true;
                }
                return false;
            }
        }
        File[] files = file.listFiles(filter);
        if(files!=null && files.length>0){
            for (File f:files) {
                if(f.isDirectory()){
                    listFile(f);
                }else{
                    System.out.println("发现一个txt:"+f.getAbsolutePath());
                }
            }
        }
    }
}
3.3.2方式二

观察上面代码发现

 FileFilter filter = new FileFilter(){}

这一行filter代替了后面的匿名内部类,那么在使用filter的地方可以直接替换成匿名内部类的重写,更加方便,缺点就是读代码的时候不方便。

public static void listFiles(File file){
        File[] files = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".txt") || pathname.isDirectory()){
                    return true;
                }
                return false;
            }
        });
        if(files!=null && files.length>0){
            for (File f:files) {
                if(f.isDirectory()){
                    listFile(f);
                }else{
                    System.out.println("发现一个txt:"+f.getAbsolutePath());
                }
            }
        }
    }
}

全部代码是:

public class Demo3 {
    public static void main(String[] args) {
        File d = new File("d:\\");
        listFile(d);
    }
    
    public static void listFile(File file){
        File[] files =  FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".txt") || pathname.isDirectory()){
                    return true;
                }
                return false;
            }
        }
        if(files!=null && files.length>0){
            for (File f:files) {
                if(f.isDirectory()){
                    listFile(f);
                }else{
                    System.out.println("发现一个txt:"+f.getAbsolutePath());
                }
            }
        }
    }
}

4.相对路径和绝对路径

路径就是描述文件的地址。

绝对路径:该路径指一个固定的位置:从盘符开始,是一个完整的路径,如:c:\a.txt

相对路径:不包含盘符,在Java代码中是相对于项目 的 目录路径,是一个不完整的便捷路径,在Java开发中很常用,例如:a.txt。它的位置也很清晰,它在项目文件夹中。

代码演示

public class  Dmo4 {
    public static void main(String[] args) {
        //在d盘的a.txt文件位置
        File file1 = new File("d://a.txt");
        //相对路径,位置在Java的项目文件当中
        File file2 = new File("a.txt");
        System.out.println(file1.getAbsoluteFile());
        System.out.println(file2.getAbsoluteFile());
    }
}

输出的结果是:

d:\a.txt
D:\IDEA\a.txt

5.IO流概述

5.1IO流概念

IO流重点在于io,文件移动都是数据在传输,可以将数据传输看作一种数据的流动,按照数据的流动方向可将数据分为输入(Input)和输出(Output)。比如喝水过程中水杯就是输出,嘴就是在输入。

Java中的IO操作主要指的是 java.io 包下的一些常用类的使用,通过这些常用类对数据进行读取(输入Input) 和 写出(输出Output)

5.2IO流的分类

按流的方向按流的流动数据类型
输入流和输出流字节流和字符流

还有一些打印流、对象流。下面是输入流与输出流的顶级父类

字节流顶级父类
输入流InputStream
输出流OutputStream
字符流顶级父类
输入流Reader
输出流Writer

6.OutputStream(抽象类)

6.1一切皆字节

  1. 计算机的任何数据(文本、图片、视频、音频)都是以二进制的形式进行存储的
  2. 在数据传输时 也都是以二进制的形式存储的。
  3. 后续学习的任何流,在传输时 底层 都是二进制

6.2OutputStream方法

变量和类型方法描述
voidclose()关闭此输出流并释放与此流关联的所有系统资源
voidflush()刷新此输出流并强制写出任何缓冲的输出字节
static OutputStreamnullOutputStream()返回一个新的OutputStream,它丢弃所有字节
voidwirte(byte[] b)将b.length从指定的字节数组写如此输出流
voidwirte(byte[] b,int off,int len)写入一个byte字节,从off位置开始len个写入此输出流
abstract voidwirte(int b)将指定的字节写入此输出流,此int范围0-255

其中 流不close()操作不了;有些流带有缓冲,可以在传输起来更方便,一批一批的传输,提高效率,而在常用flush()方法刷新输出字节。wirte(int b),传入的虽然是int类型,但是它能存储的仅为 8 bit(后8位),范围0-255有效,超出就会数据丢失.

6.3FileOutputStream(实体类)

FileOutputStream类文件输出流是OutputStream类最常用子类。OutputStream是一个抽象类,现在的FileOutputStream是一个具体的实现类。而FileOutputStream只是能操作文件的其中一个子类,其他的OutputStream的子类用法大致相同。学习如何构建其对象是关键。

创建的FileOutputStream的对象表示向某个文件输出的一个流管道,就可以通过这个对象向指定的文件输出内容。

构造方法:

构造器描述
FileOutputStream(File file)创建文件输出流以写入由指定的File对象表示文件
FileOutputStream(File file,boolean append)创建文件输出流以写入由指定的File对象表示文件,并决定是否重新写入
FileOutputStream(String name)创建文件输出流以写入具有指定名称的文件
FileOutputStream(String name,boolean append)创建文件输出流以写入具有指定名称的文件,并决定是否重新写入数据
FileOutputStream(FileDescriptor fdObj)创建要写入指定文件描述的文件输出流,该文件输出符表示与文件系统中实际文件的现有连接

boolean append:流建立以后,是接着文件往后写还是重新开始写由它决定,true接着写,false或不传表示重新写。

只有一个流重新创建时才要是否指定追加,一个流关闭前写的都是一个文件。

流如果关闭以后就不要再写了,会出错.

变量和类型方法描述
voidclose()关闭文件输出流并释放与此流关联的所有系统资源
protected voidfinalize()已弃用
FileChannelgetChannel()返回与此文件输出流关联的唯一FileChannel对象
FileDescriptorgetFD()返回与此流关联的文件描述符
voidwrite(byte[] b)将指定字节数组中的b.length字节写入此文件输出流
voidwrite(byte[] b,int off,int len)将偏移量off开始的指定字节数组中的len个字节写入此文件输出流
voidwrite(int b)将指定的字节输入此文件输出流

“”.getBytes()方法可以将""里面的内容转化为字节数组,这是一个来自String类里面的方法,具体查看API。

6.3.1代码演示写入

演示一:

public class Demo5 {
    /**
     * 写一个字节/字节数组的案例
     */
    public static void main(String[] args) throws IOException {
        //创建一个文件对象的位置,进行操作
        FileOutputStream fos = new FileOutputStream("d:\\a.txt",false);
        byte[] bytes = {65,66,67,68,69,70,13};
        byte[] bytes2 = {65,66,67,68,69,13};
        //只有一个流重新创建时才要是否指定追加(true/false),一个流关闭前写的都是一个文件
        //下面这种会清空之前写的数据
        fos.write(bytes2);
        fos.write(bytes);
        //写完记得关闭文件
        fos.close();
    }
}

**注意:直到流关闭之前写的都是一个流,数据不会丢失的;但是关闭之后没开启追加模式就会丢失数据。**而在流关闭以后是不能进行数据操作的。

写入的数据就是:

ABCDEF
ABCDE

演示二:

前面的代码的存入方法,输入的数据却存储成为了字符,我们存储的时候还得根据需求查看ascii码,不是那么方便,下面提供一种较为方便的方法进行字符的存储:

public class Demo5 {
    /**
     * 写一个字节/字节数组的案例
     */
    public static void main(String[] args) throws IOException {
        //创建一个文件对象的位置,进行操作
        FileOutputStream fos = new FileOutputStream("d:\\a.txt",false);
        //直接双引号写入数据,调用getBytes()变成byte类型的数组
        byte[] bytes = "abcdef\n床前明月光".getBytes();
        //表示写入bytes数组 下标为2开始,写入长度为3
        fos.write(bytes);
        System.out.println("已经写出");
        fos.close();
    }
}

写入的数据是:

abcdef
床前明月光

很显然需要写入字符的时候下面的方法会方便很多。如果只需要写入数据的一部分也是可以操作的,直接在writer()方法里面操作即可。

7.InputStream(抽象类)

InputStream是把硬盘文件读取到我们的内存中。read()读取一个字节数据,常见子类FileInputStream。下面就看一看FileInputStream类的使用方式。

7.1FileInputStream

7.1.1构造方法
构造器描述
FileInputStream(File file)通过打开与实际文件的连接来创建流,该文件由文件系统中的File对象file命名
FileInputStream(FileDescriptor fdObj)使用文件描述符fdObj来创建FileInoutStream,该文件描述符表示与系统中实际的文件的现有连接
FileInputStream(String name)通过打开与实际文件的连接来创建流,该文件由文件系统中的路径名 name 命名
  1. FileInputStream(File file):传入一个文件对象,建立一个文件的读取的输入流
  2. FileInputStream(String name):传入一个name,name是一个字符串描述的文件地址,可以是一个相对路径/绝对路径。
7.1.2常用方法
变量和类型方法描述
intavailable()返回可以从此输入流中可读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞
intread()从输入流当中读取一个字节的数据,返回的int数据的ascii码
intread(byte[] b)从输入流中读取b.length个字节存储在byte[]数组中
intread(byte[] b,int off,int len)从输入流中 在下标为off处开始读取最多len个字节存储在byte,返回的int表示读取的数据个数
longskip(long n)跳过并从输入流中丢弃 n 字节的数据
  1. read()返回的int表示读取的数据,read()连续读取它会自动跳到下一个,读到末尾没有数据返回-1;read()读取的范围是0-255,读取内容为空返回-1,每次读取都会跳到下一个.

    代码演示:

    public class Demo6 {
        public static void main(String[] args) throws IOException {
            //创建FileInputStream对象,指向d://a.txt
            FileInputStream fis = new FileInputStream("d://a.txt");
            int b;
            //使用循环读取
            while(true){
                //读取的范围是0-255
                b = fis.read();
                if(b != -1){
                    System.out.println((char)b);
                }else {
                    System.out.println("读取结束");
                    //关闭输入流
                    fis.close();
                    break;
                }
            }
        }
    }
    
  2. read(byte[] b)/read(byte[] b,int off,int len)返回的int数表示读取到的字节个数,读到末尾没有数据返回-1,在使用循环读取的时候把它作为一种读完的标志;

    1. 常用的是读取一组字节 read(byte[] b)/read(byte[] b,int off,int len) 方法,可以减少java代码进行io的频率;
  3. 下面讲一讲String类的一种构造方法String(byte[] byte,offset,int len),下面用到

    构造器描述
    String(byte[] byte,int offset,int len)通过平台的默认字符集解码指定的字节数组构造新的String类型

    byte表示字符集,offset表示字符集在新的String类型当中的起始位置,len表示构造的字符集长度.

7.1.3提到的一个bug
public class Demo6 {
    public static void main(String[] args) throws IOException {
        //创建FileInputStream对象,指向d://a.txt
        FileInputStream fis = new FileInputStream("d://a.txt");
        byte[] bytes = new byte[10];
        fis.read(bytes);
        System.out.println(new String(bytes));
        fis.read(bytes);
        System.out.println(new String(bytes));
        fis.read(bytes);
        System.out.println(new String(bytes));
        fis.close();

输出的数据为:

abcdefghij
klmnopkrst
uvwxyzkrst

当文件数据为abcdefghijklmnopqrstuvwxyz总长度为24,读取的第三次的时候只能读取6位,那么后面的最后输出的数据位uvwxyz qrst,原因是前面的数组内容未被全部覆盖,直接输出。因为read(bytes)读取以后会返回一个int类型的数,该数表示本次读取字节数,而我们在打印的时候就可以根据read(bytes)返回的数进行打印:

public class Demo6 {
    public static void main(String[] args) throws IOException {
        //创建FileInputStream对象,指向d://a.txt
        FileInputStream fis = new FileInputStream("d://a.txt");
        byte[] bytes = new byte[10];
        int len = fis.read(bytes);
        System.out.println(new String(bytes));
        len = fis.read(bytes);
        System.out.println(new String(bytes));
        len = fis.read(bytes);
        System.out.println(new String(bytes));
        fis.close();

输出数据为正常:

abcdefghij
klmnopkrst
uvwxyz

还可以使用读取到末尾就返回-1的特性进行优化

public class Demo6 {
    public static void main(String[] args) throws IOException {
        //创建FileInputStream对象,指向d://a.txt
        FileInputStream fis = new FileInputStream("d://a.txt");
        byte[] bytes = new byte[10];
        int len;
        while (true){
            len = fis.read(bytes);
            if(len == -1)
                break;
            System.out.println(new String(bytes,0,len));
        }
    }
}

这里特别值得你关注的就是System.out.println(new String(bytes,0,len));语句,他能匿名打印我们的数组

8.文件加密和解密工具

8.1步骤

创建原文件、新文件的文件对象并设置文件位置;创建文件的输入、输出流,用于文件数据的读取和写入。使用while(true)死循环操作数据。

8.2原理

任何数据 异或(^) 相同的数字两次,得到的就是其本身。原理就是一切皆字节.

8.3代码

public class Demo7 {
    public static void main(String[] args) throws IOException {
        System.out.println("请输入文件存储的全路径");
        Scanner input = new Scanner(System.in);
        //获取用户输入的文件绝对路径
        String fileNmae = input.nextLine();
        //创建原文件对象
        File oldFile = new File(fileNmae);
        //加密存储新文件
        //getParentFile()返回它的父类文件,在父类文件夹里面创建新文件
        File newFile = new File(oldFile.getParentFile(),"mi+"+oldFile.getName());
		
        //创建输入输出流
        FileInputStream fis = new FileInputStream(oldFile);
        FileOutputStream fos = new FileOutputStream(newFile);
        
        //接受读取的数据
        int b;
        while(true){
            b = fis.read();
            if(b == -1){
                //第一时间关闭流
                fis.close();
                fos.close();
                break;
            }
            //任何数据异或(^)两次,得到的就是其本身
            //写入数据
            fos.write(b^10);
        }
        System.out.println("加密/解密完成");
    }
}

9.字符编码

  1. 计算机里面存储的数据都是二进制(1/0)

  2. 硬盘就是磁盘,机械硬盘的好处:长时间永久保存数据,耐高温;固态硬盘存储得快取得快,不能耐高温,长时间存储会导致数据丢失

  3. 内存的读写的度特别快。30-60G/s之间,通电之后可以运行,断电之后运行不了,存不了东西。程序运行的时候使用的就是内存

  4. 乱码:数据的接收方和数据的输出方使用的编码表不一样导致的。为了避免乱码错误,都是以UTF-8编码

  5. 文字能显示因为有编码表,能显示在显示器上面因为有显卡

  6. 编码:把二进制与文字做一个对应

10.字符流操作

在我们使用字节流读取 文字(字符流)的时候,因为编码表不同的问题,读取文字会导致乱码。

FileInputStream fis = new FileInputStream("b.txt");
byte[] bytes = new byte[10];
        int len = fis.read(bytes);
        System.out.println(new String(bytes,0,len));
		len = fis.read(bytes);
        System.out.println(new String(bytes,0,len));
        len = fis.read(bytes);
        System.out.println(new String(bytes,0,len));
        //一定要记得关闭输入流
        fis.close();

其中的b.txt文件建立在UTF-8编码下,UTF-8是动态长度的编码。由于文字一个字会使用多个字节进行存储,所以在读取汉字的时候不能确定使用多少的数组来存储文字,很容易读一半文字就数组满的情况。那么就由字节流就能够解决读取半个汉字的问题。

10.1Writer类-字符输出流

字符流输出数据的时候使用的也是字节流,只不过把写出的单位变成一个字符一个字符进行输出的,不会出现读取半个字的情况。字符流以一个字符作为单位,只能操作文字。

10.1.1Writer类的方法
变量和类型方法描述
Writerappend(char c)将指定的字符追加到此writer
Writerappend(CharSequence csp)将指定的字符序列追加到此writer
Writerappend(CharSequence csp,int off,int len)将指定的字符序列的子序列追加到此writer
abstract voidclose()关闭流
abstract voidflush()刷新流
static WriternullWtriter()返回一个新的Writer,他丢弃所有字符
voidwirte(char[] cbuf)写一个字符数组
abstract voidwirte(char[] cbuf,int off,int len)写一个字符数组的一部分
voidwirte(int c)写一个字符,表示的是一个byte
voidwirte(String str)写一个字符串
voidwirte(String str,int off,int len)写一个字符串的一部分

我们使用的是它的一个子类:FileWriter

10.1.2FileWriter构造方法

与FileOutputStream类常见的是完全一致的,只不过它会多一个Charset类型的参数cahrset:

构造器描述
FileWriter(File file)给File写一个FileWriter,使用平台的default charset
FileWriter(FileDescriptor fd)构造一个FileWriter给出的文件描述,使用该平台的default charset
FileWriter(File file,boolean append)再给出要写入的FileWriter下构造File,并使用该平台的default charset,指定是否附加写入数据
FileWriter(File file,Charset charset)构造一个FileWriter给予File和Charset编写
FileWriter(File file,Charset charset,boolean append)
FileWriter(String fileName)构造一个FileWriter给出文件名,使用平台的default charset
FileWriter(String fileName,boolean append)
FileWriter(String fileName,Charset charset)构造一个FileWriter给出文件名和charset
FileWriter(String fileName,Charset charset,boolean append)

而它的方法就是父类的方法,常用的有writer(),flush()。下面就看一看apend()方法,append()方法顾名思义,是附加的功能,它是继承其父类的方法,我们查看源码:

public Writer append(CharSequence csq) throws IOException {
        if (csq instanceof CharBuffer) {
            se.write((CharBuffer) csq);
        } else {
            se.write(String.valueOf(csq));
        }
        return this;
    }

append()方法return的是调用它的对象,属于Writer类,这样我们就可以一直调用append()方法进行写出:

fileWriter.append("致橡树\n").append("平凡的世界\n").append("林海雪原\n")...

下面展示了对象 fw2 == fw 对象,所有可以使用 fw 的地方就可以使用 fw2,这两个对象指的是同一个文件地址:

FileWriter fw2 = (FileWriter) fileWriter.append("月亮与六便士");
	//打印的是true
	System.out.println(fileWriter == fw2);

这个输入的是true证明了两个对象是对等的,我们就可以不断的在后面一直调用append()方法输出字符:

public class Demo8 {
    public static void main(String[] args) throws IOException {
        //文件若不存在则会创建,存在不再创建
        FileWriter fileWriter = new FileWriter("D://c.txt");
        //使用append方法写入,append()--追加
        //append()方法return一个调用该方法的对象,可以看上面的源码
        fileWriter.append("致橡树\n").append("平凡的世界\n").append("林海雪原\n");
        
        //强转是因为apend()方法返回的是Writer类的对象,需要转为	FileWriter类
        FileWriter fw2 = (FileWriter) fileWriter.append("月亮与六便士\n");
        //记得关闭流
        fileWriter.close();
    }
}

注意:append()方法只是一次输出(在close()流之前)可以一直追加,与构造器当中的 true 功能不一样。如果从开始写入到关闭流,创建的是一个新的文件就不使用true;而需要保留原文件数据就加true。

10.2Reader-字符输入流

10.2.1Reader类的常用方法
变量和类型方法描述
abstractclose()关闭流并释放与其关联的所有系统资源
void
intread()读取一个字符
intread(char[] cbuf)将字符读入数组
abstract intread(char[] cbuf,int off,int len)
intread(CharBuffer target)
booleanready()
voidreset()重置流
longskip(long n)跳过字符
longtransferTo(Writer out)

read()读取到文件尾部返回-1,读取会自动读取下一位。我们使用的是它的子类FileReader

10.2.2FileReader类
10.2.2.1read()方法逐个字符读取
public class Demo9 {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("D://c.txt");
        //读取单个数组
        while (true){
            int c = fileReader.read();
            if(c == -1)
                break;
            System.out.print((char)c);
        }
        fileReader.close();
    }
}

这种方法还是那句话,会大大增加系统进行io的频率,影响性能,推荐使用读取多个数据的方法。使用数组接受read()方法读取的数据时,read()方法return的一个int数指的是读取到的字符的个数。在读取的过程中输出的时候可以根据该int值进行输出,减少空间的占用,避免出现问题。

并且对读取方法进行一些优化,如:

public class Demo9 {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("D://c.txt");
        //以char类型数组进行读取
        char[] chars = new char[100];
        int len;
        //此处调用read()方法放回的是所读取到的字符个数
        //这样就能够读取目标文档里面的所有内容,且避免了bug
        while(true) {
            len = fileReader.read(chars);
            //打印0开始的len个数组下标,即所读取的字符个数
            System.out.print(new String(chars, 0, len));
            if(len < 100){
                fileReader.close();
                System.out.println("读取结束!");
                break;
            }
        }
    }
}

输出的是读取得到的长度len,避免出现问题。

10.3字符流、字节流输出不同的点以及flush()方法

  1. 字符输出时会以字符为单位进行输出,字符流里面也是字节流。如果一个文字有三个字节,系统在读取这个文字的时候会读满着三个字节在把文字输出。当然根据编码不同文字的字节数不一样。

  2. 当读取了半个字的字符字节后,是不会输出的,而是把它 缓存 起来,也就是说在输出方给输入方发送的这个过程中它是有一个缓存存在的。flush()就是一个刷新缓存空间的方法,强制把缓存空间的内容写出到文件中去,它缓存的不止一个文字,而在close()方法调用的时候会自动把缓存的东西写出到文件中去,才会写入成功。

  3. 不刷新表示文字都还在内存中缓存,还没有到文件里面,文件里面是不会有内容的。输入输出都需要刷新流,都调用flush()方法。

  4. 下面举一个失败的例子:

    public class Demo8 {
        public static void main(String[] args) throws IOException {
            FileWriter fileWriter = new FileWriter("D://c.txt");
            fileWriter.append("致橡树\n").append("平凡的世界")//不使用fuulsh(),close()方法的话数据就不会被写入,还仍然存储在内存当中,未存入硬盘当中
            //fileWriter.flush();
            //fileWriter.close();
        }
    }
    

    使用close(),flush()任何一个方法就会使得它写出成功。

11.字节流转换成字符流

转换流主要的作用就是将字节流转换为字符流,它能转换输入和输出。

字节流 “装饰” 为字符流:使用了装饰者设计模式,这里演示的字节流都是创建的,以后在工作当中我们可能会获取到字节流,这就得与字符流进行转换是一个方法。必须得转,不转输出不了文字!

11.1InputStreamReader

转换流的输入的名称是InputStreamReader,字节流和字符流读取最大的父类拼接在一起,我们来看一下它的构造方法

构造器描述
InputStreamReader(InputStream in)创建一个使用默认字符集的InputStreamReader
InputStreamReader(InputStream in,String charsetName)创建一个使用charset的InputStreamReader
InputStreamReader(InputStream in,Charset cs)创建一个给定charset的InputStreamReader
InputStreamReader(InputStream in,CharsetDecoder dec)创建一个给定charset解码器的InputStreamReader

输入流演示:

public class Demo10 {
    public static void main(String[] args) throws IOException {
        //假设该行是一个获取到的字节流,而不是创建的字节流,老壳没有包
        FileInputStream fis = new FileInputStream("D://c.txt");

        /**
         * 将字节输入流,转化为字符输入流,对于InputStreamReader
         * 参数1. 要转换的字节流
         * 参数2. 指定编码名称
         */
        //传入一个字符集,详细查看构造方法API
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
        while (true){
            int c = isr.read();
            if(c == -1)
                //读取结束
                break;
            System.out.print((char)c);
        }
    }
}

11.2OutputStreamWriter

转换流的输入的名称是OutputStreamWriter,构造方法于InputStreamReader类似。

输出流演示:

public class Demo10 {
    public static void main(String[] args) throws IOException {
        //假设为获取到的字节流
        FileOutputStream fos = new FileOutputStream("D://c.txt");
        //将字节输出流,转化为字符输出流
        //把字节流装饰为字符流
        OutputStreamWriter osw = new OutputStreamWriter(fos);
        osw.append("你要跳舞吗?\n");
        //进行刷新,存入文件里面
        osw.flush();
        osw.close();
    }
}

注意直接使字节输出流FileOutputStream写入文字是写不了的,会导致错误,具体请查看API里面的对于FileOutputStream的writer()方法的规定。

在做服务器端开发需要给服务期上传文件文字,就可以通过一些输出的字节的流,在再通过上面的转换将其转换成字符流,然后通过字符流在输出。更好的转换方式请看下面。

12.Print与BufferedReader

Print与BufferedReader就是指打印流和缓存读取流。

12.1Print打印流

在常用的System.out.println();当中的out就来源与PrintStream流,查看源码更为详细.

PrintStream与PrintWriter的操作没什么区别,一个是字节流(PritStream),一个是字符流(PrintWriter),注意一点就是PrintWriter得使用flush/close方法把缓存在内存的字节/字符写入文件,否则文件里面是不会有数据的;而OrintStream不需要刷新

12.1.1PrintStream流

字节流,构造方法于常用方法查看API。自己创建一个打印流,指定位置去打印,字符输出(打印流)代码演示:

public class Demo11 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("d://x.txt");
        ps.println("锄禾日当午1");
        ps.println("锄禾日当午2");
        //不使用clos(),flush()方法也可以输出
        ps.close();
    }
}
12.1.2PrintWriter流

字符流,方法与PrintStream类似,详细查看API:

public class Demo11 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintWriter pw = new PrintStream("d://x.txt");
        pw.println("锄禾日当午1");
        pw.println("锄禾日当午2");
        //不使用clos(),flush()方法就无法输出
        pw.flush();
        pw.close();
    }
}

两者的效果是一样的。另外PrintWriter也能作为转换器,把字节流转换为字符流:

public class Demo11 {
    public static void main(String[] args) throws FileNotFoundException {
        FileOutputStream fos = new FileOutputStream("d://x.txt");
        PrintWriter pw = new PrintWriter(fos);
        pw.println("锄禾日当午哈哈哈哈哈哈哈哈哈哈!");
        //不使用clos(),flush()方法就无法输出
        pw.flush();
        pw.close();
    }
}

在平时需要把字节流转换为字符流的时候,更建议使用PrintStream转换成打印流来完成类似需求

12.2BufferedReader缓存流

字符输入流 转换为带有缓存 可以一次读取一行的缓存字符读取流。把读取方式转换成缓存流的方式,之前使用FileReader只能一个一个的读取(一个字符/一个字符数组),现在可以一行一行的读取,增加了效率,也有了一种读取的新形式.

BufferedReader构造方法:

构造器描述
BufferedReader(Reader in)创建使用默认大小的输入缓冲区的缓冲字符输入
BufferedReader(Reader in,int sz)创建使用指定大小的输入缓冲区的缓冲字符输入

BufferedReade方法查看API。缓存读取流示例:

public class Demo11 {
        //缓存读取流,将字符输入流 转换为带有缓存 可以一次读取一行的缓存字符读取流
        FileReader fr = new FileReader("d://x.txt");
    	//转换成缓存流的方式
        BufferedReader br = new BufferedReader(fr);
        //读取一行数据,返回的是读取的一行数据,而read读取返回的是个数
        String text;
        while(true){
            text = br.readLine();
            if(text == null){
                //readLine()读取到结束返回null
                br.close();
                System.out.println("over!");
                break;
            }
            System.out.println(text);
        }
    }
}

13.收集异常日志

将异常输出到文件,而不是控制台。printStackTrace()是Throwable类的方法,先看一下printStackTrace()方法

变量和类型方法描述
voidprintStackTrace()将此throwable及其回溯打印到标准错误流
voidprintStackTrace(PrintStream s)将此throwable及其backtrace打印到指定的打印流
voidprintStackTrace(PrintWriter s)将此throwable及其backtrace打印到指定的打印编写器

代码演示:

public class Demo12 {
    public static void main(String[] args) throws FileNotFoundException {
        try{
            String s = null;
            s.toString();
        }catch (Exception e){
            //创建打印流
            PrintStream ps = new PrintStream("d://bug.txt");
            //创建日期格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            
            //输出一个日期
            ps.println(sdf.format(new Date()));
            //把输出到pw对象创建的文件当中
            e.printStackTrace(ps);
            //关闭流
            ps.close();
        }
    }
}

涉及到的知识点都忘记了,回顾啊!

14.Properties对象

属于IO又属于集合,查看源码它是HashTable集合的子类,属于Map集合。用于把程序的数据存储到文件当中,或读取文件当中的数据加载到程序当中。

Properties存储的文件当中每一行都是以 键值对 的格式存储的(键=值),在存储的文件当中,#表示注释内容不起作用,并且不建议存储中文,而Properties存储的文件当中的comments也不允许出现中文,非要写成中文的会以unicode编码存储,当然新版本支持中文.

14.1存入数据

public class Demo13 {
    public static void main(String[] args) throws IOException {
        //查看源码:Properties类为HashTable集合的子类
        Properties ppt = new Properties();
        //与集合的操作方式类似,存入两个 键-值对
        ppt.put("name","The moon and sixpence");
        ppt.put("info","It tells a stroy");
        //创建一个文件输出流FileWriter对象,在D盘命名为book.properties, .properties 这是一种命名的规范
        FileWriter fw = new FileWriter("d://book.properties");
        //调用Properties类的store对象存储数据到文件内
        ppt.store(fw,"Stroyed books");
        fw.close();
    }
}

14.2读取数据

Properti的详细方法查看API,下面单独看一看它的load()方法

变量和类型方法描述
voidload(InputStream inStream)从输入字节流中读取属性列表(键值对)
voidload(Reader reader)以简单的面向行的格式从输入字符流中读取属性列表(键值对)

Properties提供了 getProperties获取文件里面的信息.想了解可查看其源码

public class Demo13 {
    public static void main(String[] args) throws IOException {
        Properties ppt = new Properties();
        Reader r = new FileReader("d://book.properties");
        //传入一个能读取的流
        ppt.load(r);
        //关闭流
        r.close();
        System.out.println(ppt.get("name"));
        System.out.println(ppt.get("info"));
        
        //Properties提供了 getProperties获取文件里面的信息.想了解可查看其源码
        System.out.println(ppt.getProperty("info"));
        System.out.println(ppt.getProperty("name"));
    }
}

注意的是:

  1. 上面存入的文件使用 文件名.properties 方式存储.
  2. load()方法需要传入一个Reader对象,所以在创建对象的时候就必须得创建Reader对象
  3. 集合的操作方法回顾并且加强一下

15.序列化技术

列化技术分为序列化 与 反序列化:

  1. 所谓的序列化:将程序中的对象直接以文字形式存储起来,按照对象在内存中的内存序列进行存储的,不是眼睛可以看出来的;

  2. 反序列化:把存储起来的对象读取回来。

注意:

  1. 一个类型想要被序列化,需要实现标记接口implements Serializable,该类型的所有属性都得实现,String类型默认拥有该接口;
  2. 当存储的是一个集合的时候取出来还是应该转化成集合.集合就像是一个容器,拿出来的是容器不是容器里面的内容,而内容就是存储到集合里面的,直接读取就可以。

15.1序列化

代码(注意其中的一些格式)

public class Demo14 {
    public static void main(String[] args) throws IOException {
        //序列化--使用ObjectOutputStream类输出
        Book b = new Book("The moon and sixpence","It tells a stroy");
        //ObjectOutputStream顾名思义输出 OutputStream 对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d://book.txt"));
        oos.writeObject(b);
        oos.close();
    }
    //创建一个book类
    static class Book implements Serializable{
        private String name;
        private String info;
        public Book(String name, String info) {
            this.name = name;
            this.info = info;
        }
    }
}

把数据对象数据进去,不是像之前的对文件进行的操作。

15.2反序列化

public class Demo14 {
    public static void main(String[] args) throws IOException {
        //反序列化--使用ObjectInputStream类输入
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://book.txt"));
        Book o = (Book) ois.readObject();
        System.out.println(o.getName());
    }
    static class Book implements Serializable{
        private String name;
        private String info;
        public Book(String name, String info) {
            this.name = name;
            this.info = info;
        }
    }
}

把之前存储的对象读取回来,尽管在重启之后也能进行读取

16.try-with-resource

try-with-resource是IO的知识点也是异常处理的知识。在操作需要关闭(close()方法)的资源时(比如流),以前使用会有很多步骤才能达到close()流的目标,显得繁琐。在JDK1.7版本的时候提出了try-with-resource解决这个问题,在JDK1.9进行优化.

16.1JDK1.7时

public class Demo15 {
    public static void main(String[] args) throws FileNotFoundException {
        //try()括号内拥有close方法会自动执行,但是拥有的缺点是:后面不能再使用该对象
        try(FileReader fr = new FileReader("d://x.txt")){
            int c = fr.read();
            System.out.println((char) c);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

它的缺点就是后面想再次使用fr的时候不能使用了,已经close()了。

16.2JDK1.9时

JDK1.9就优化了上面出现的问题:

public class Demo15 {
    public static void main(String[] args) throws FileNotFoundException {
        //JDK1.9提出了优化,后面能使用该对象
        FileReader fr = new FileReader("d://x.txt");
        PrintWriter pw = new PrintWriter("d:\\x.txt");
        try(fr;pw){
            //演示可以同时操作多个对象,中间使用分号隔开即可.pw对象就不进行操作了
            int c = fr.read();
            System.out.println((char) c);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try()方法的括号内传入对象它会自动的调用Closeable/AutoCloseable当中的close()方法,需要调用操作多个对象时,就可以使用分号隔开。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值