java IO 的知识总结

装饰者模式

因为java的IO是基于装饰者模式设计的,所以要了解掌握IO 必须要先清楚什么事装饰者模式(装饰者模式也称为包装模式,其使用一种对客户端透明的方式动态的扩展对象功能。装饰者模式也是继承关系的替代方案之一。装饰者模式是结构型设计模式。重点也在装饰二字)

  • 装饰者模式定义: 装饰者模式是指在不必改变原类文件和使用继承的前提下,动态的扩展一个对象的功能,通过创建一个包装对象,也就是装饰者来包裹真实对象。
  • 具体实例:
//1.首先抽象出一个 人 的类 用来表示一个人的穿着
public abstract class Person {
    /**
     * 一个人的衣着(风格,饰品)
     */
    public abstract void wear();
}

//2.在定义一个具体的人 韩梅梅 并实现穿着 函数
public class HanMeiMei extends Person {
    @Override
    public void wear() {
        System.out.println("一身素雅的连衣裙");
    }
}
//3.定义装饰者 ,抽象出装饰者的父类
//装饰器的抽象父类
public class PersonDecorator extends Person {
    private Person person;

    //这是重点,装饰器必须持有一个被装饰者对象。
    public PersonDecorator(Person person) {
        this.person = person;
    }

    @Override
    public void wear() {
        person.wear();
    }
}
//4. 实现一个具体的装饰者
public class ConcretePersonDecorator extends PersonDecorator {
    public ConcretePersonDecorator(Person person) {
        super(person);
    }

    @Override
    public void wear() {
        headWear();
        // 拓展时,前后都可以加方法
        super.wear();
        shoes();
    }
    // 装饰者方法 ,头饰的穿着
    private void headWear() {
        System.out.println("穿戴头饰,带了了簪子 和 耳钉");
    }

    // 装饰者方法 ,鞋子的穿着
    private void shoes() {
        System.out.println("穿了一双 米色的 小高跟!");
    }
}

装饰者无非两点,一:拥有被装饰者对象,二:透明的拓展原本的方法(wear())

  • 使用
   HanMeiMei meiMei = new HanMeiMei();
   ConcretePersonDecorator decorator = new ConcretePersonDecorator(meiMei);
   decorator.wear();
  • 结果

穿戴头饰,带了了簪子 和 耳钉
一身素雅的连衣裙
穿了一双 米色的 小高跟!

  • 装饰者模式和代理模式的区别

装饰者模式和代理模式很像,有的人容易混淆,其实区别的他们很简单。这两个模式他们都会拥有一个对象。但是代理模式更强调的是代理二字。代理它的功能,而不是扩展 ,不对功能进行增强。装饰者模式则重点在装饰二字上。给原有的对象添加(装饰)新的功能。

IO简介

  • 数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
  • ​流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
    -1 字节流: 数据流中最小的数据单元是字节
    -2 字符流: 数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
  • Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。掌握了这些就掌握了Java I/O的精髓了。
  • Java I/O主要包括如下3层次:
  1. 流式部分——最主要的部分。如:OutputStream、InputStream、Writer、Reader等
  2. 非流式部分——如:File类、RandomAccessFile类和FileDescriptor等类
  3. 其他——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。


    IO图谱

IO详细介绍

在Android 平台,从应用的角度出发,我们最需要关注和研究的就是 字节流(Stream)字符流(Reader/Writer)和 File/ RandomAccessFile。当我们需要的时候再深入研究也未尝不是一件好事。关于字符和字节,例如文本文件,XML这些都是用字符流来读取和写入。而如RAR,EXE文件,图片等非文本,则用字节流来读取和写入。面对如此复杂的类关系,有一个点是我们必须要首先掌握的,那就是设计模式中的装饰者模式,学会并理解装饰者模式是搞懂流必备的前提条件,这也是在文章开头就先简绍装饰者模式的原因。

字节流

从流的整个发展历史,出现的各种类之间的关系看,都是沿用了修饰模式,就是一个类的功能可以用来修饰其他类,然后组合成为一个比较复杂的流。比如说:

 DataOutputStream out = new DataOutputStream(new BufferedOutputStream( new FileOutputStream(file)));

从上面的代码块中大家不难看出这些类的关系:为了向文件中写入数据,首先需要创建一个FileOutputStream,然后为了提升访问的效率,所以将它发送给具备缓存功能的BufferedOutput-Stream,而为了实现与机器类型无关的java基本类型数据的输出,所以,我们将缓存的流传递给了DataOutputStream。从上面的关系, 可以看到 其根本目的都是为outputSteam添加额外的功能。而这种额外功能的添加就是采用了装饰模式来构建的代码. 因此,学习流,必须要学好装饰模式。

  • 下面的图是一个关于字节流的图谱,这张图谱比较全面的概况了我们字节流中间的各个类以及他们之间的关系。


    输入输出流

字节流 类之间的关系

OutputStream -> FileOutputStream/FilterOutputStream ->DataOutputStream->bufferedOutputStream

  • 从学习的角度来,我们应该先掌握FilterOutputStream, 以及FileOutputStream,这两个类是基本的类,从继承关系可以不难发现他们都是对 abstract 类 OutputStream的拓展,是它的子类。然而,伴随着 对 Stream流的功能的拓展,所以就出现了 DataOutputStream,(将java中的基础数据类型写入数据字节输出流中、保存在存储介质中、然后可以用DataOutputStream从存储介质中读取到程序中还原成java基础类型)。这里多提一句、DataOutputStream、FilterOutputStream三个类的关系的这种设计既使用了装饰器模式 避免了类的爆炸式增长。
  • 为了提升Stream的执行效率,所以出现了bufferedOutputStream。bufferedOutputStream就是将本地添加了一个缓存的数组。在使用bufferedOutputStream之前每次从磁盘读入数据的时候都是需要访问多少byte数据就向磁盘中读多少个byte的数据,而出现bufferedOutputSteam之后,策略就改了,会先读取整个缓存空间相应大小的数据,这样就是从磁盘读取了一块比较大的数据,然后缓存起来,从而减少了对磁盘的访问的次数以达到提升性能的目的。
  • 另外一方面,我们知道了outputStream(输出流)的发展历史后,我们便可以知道如何使用outpuSteam了,同样的方法,我们可以运用到inputStream中来,这样对称的解释就出现到了inputStream相关的中来了,于是,我们对整个字节流就有了全方位的理解,所以这样子我们就不会感觉到流的复杂了。这个时候对于其他的一些字节流的使用(byteArrayOutputStream/PipeOutputStream/ObjectOutputStream)的学习就自需要在使用的时候看看API即可。

字符流

下图则是一个关于字符流的图谱,这张图谱比较全面的概况了我们字符流中间的各个类以及他们之间的关系。

字符输入输出流

字符流的学习和字节流的学习是一样的,它和字节流有着同样的发展过程,只是,字节流面向的是我们未知或者即使知道了他们的编码格式也意义不大的文件(png,exe, zip)的时候是采用字节,而面对一些我们知道文件构造我们就能够搞懂它的意义的文件(json,xml,txt)等文件的时候我们还是需要以字符的形式来读取,所以就出现了字符流。reader 和 Stream最大的区别我认为是它包含了一个readline()接口 (在BufferedWriter这个类中), 这个接口标明了,一行数据的意义,这也是可以理解的,因为自有字符才具备行的概念,相反字节流中的行也就是一个字节符号。

字符流的类间关系

  • Writer- >FilterWriter->BufferedWriter->OutputStreamWriter->FileWriter->其他
    同时类比着学习Reader相关的类。
  • FilterWriter/FilterReader
    ​ 字符过滤输出流、与FilterOutputStream功能一样、只是简单重写了父类的方法、目的是为所有装饰类提供标准和基本的方法、要求子类必须实现核心方法、和拥有自己的特色。这里FilterWriter没有子类、可能其意义只是提供一个接口、留着以后的扩展..... 本身是一个抽象类。
  • BufferedWriter/BufferedReader
    BufferedWriter是 Writer类的一个子类。他的功能是为传入的底层字符输出流提供缓存功能、同样当使用底层字符输出流向目的地中写入字符或者字符数组时、每写入一次就要打开一次到目的地的连接、这样频繁的访问不但效率低下、也有可能会对存储介质造成一定的破坏、比如当我们向磁盘中不断的写入字节时、夸张一点、将一个非常大单位是G的字节数据写入到磁盘的指定文件中的,每写入一个字节就要打开一次到这个磁盘的通道,这个结果无疑是恐怖的。而当我们使用BufferedWriter将底层字符输出流 比如FileReader包装一下之后、我们可以在程序中先将要写入到文件中的字符写入到BufferedWriter的内置缓存空间中、然后当达到一定数量时、一次性写入FileReader流中,此时FileReader就可以打开一次通道,将这个数据块写入到文件中,这样做虽然不可能达到一次访问就将所有数据写入磁盘中的效果、但也大大提高了效率和减少了磁盘的访问量!
  • OutputStreamWriter/InputStreamReader
    ​ 输入字符转换流、是输入字节流转向输入字符流的桥梁、用于将输入字节流转换成输入字符流、通过指定的或者默认的编码将从底层读取的字节转换成字符返回到程序中、与OutputStreamWriter一样、本质也是使用其内部的一个类来完成所有工作:StreamDecoder、使用默认或者指定的编码将字节转换成字符;OutputStreamWriter/ InputStreamReader只是对StreamEncoder/StreamDecoder进行了封装,isr内部所有方法核心都是调用StreamDecoder来完成的,InputStreamReader只是对StreamDecoder进行了封装、使得我们可以直接使用读取方法、而不用关心内部实现。
  • OutputStreamWriter、InputStreamReader分别为InputStream、OutputStream的低级输入输出流提供将字节转换成字符的桥梁、他们只是外边的一个门面、真正的核心是下面两个类:
  • OutputStreamWriter中的StreamEncoder:
    1、使用指定的或者默认的编码集将字符转码为字节
    2、调用StreamEncoder自身实现的写入方法将转码后的字节写入到底层字节输出流中。
  • InputStreamReader中的StreamDecoder:
    1、使用指定的或者默认的编码集将字节解码为字符
    2、调用StreamDecoder自身实现的读取方法将解码后的字符读取到程序中。

在理解这两个流的时候要注意:java——io中只有将字节转换成字符的类、没有将字符转换成字节的类、原因很简单——字符流的存在本来就像对字节流进行了装饰、加工处理以便更方便的去使用、在使用这两个流的时候要注意:由于这两个流要频繁的对读取或者写入的字节或者字符进行转码、解码和与底层流的源和目的地进行交互、所以使用的时候要使用BufferedWriter、BufferedReader进行包装、以达到最高效率、和保护存储介质。

  • FileReader/FileWriter
    FileReader和FileWriter 继承于InputStreamReader/OutputStreamWriter。
    从源码可以发现FileWriter 文件字符输出流、主要用于将字符写入到指定的打开的文件中、其本质是通过传入的文件名、文件、或者文件描述符来创建FileOutputStream、然后使用OutputStreamWriter使用默认编码将FileOutputStream转换成Writer(这个Writer就是FileWriter)。如果使用这个类的话、最好使用BufferedWriter包装一下、高端大气上档次、低调奢华有内涵!
    FileReader 文件字符输入流、用于将文件内容以字符形式读取出来、一般用于读取字符形式的文件内容、也可以读取字节形式、但是因为FileReader内部也是通过传入的参数构造InputStreamReader、并且只能使用默认编码、所以我们无法控制编码问题、这样的话就很容易造成乱码。所以读取字节形式的文件还是使用字节流来操作的好、同样在使用此流的时候用BufferedReader包装一下、就算冲着BufferedReader的readLine()方法去的也要使用这个包装类、不说他还能提高效率、保护存储介质。

字节流与字符流的关系

字节与字符输出流

字节与字符输入流

字节流与字符流的区别

字节流和字符流使用是非常相似的,那么除了操作代码的不同之外,还有哪些不同呢?

  • 字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是要使用到缓冲区的 ,字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容。

开发中究竟用字节流好还是用字符流好呢?

在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。

如果要java程序实现一个拷贝功能,应该选用字节流进行操作(可能拷贝的是图片),并且采用边读边写的方式(节省内存)。

字节流与字符流的转换

虽然Java支持字节流和字符流,但有时需要在字节流和字符流两者之间转换。InputStreamReader和OutputStreamWriter,这两个为类是字节流和字符流之间相互转换的类。

InputSreamReader用于将一个字节流中的字节解码成字符:

有两个构造方法

   InputStreamReader(InputStream in);

功能:用默认字符集创建一个InputStreamReader对象

   InputStreamReader(InputStream in,String CharsetName);

功能:接收已指定字符集名的字符串,并用该字符创建对象

OutputStreamWriter用于将写入的字符编码成字节后写入一个字节流。

同样有两个构造方法

  OutputStreamWriter(OutputStream out);

功能:用默认字符集创建一个OutputStreamWriter对象;

  OutputStreamWriter(OutputStream out,String  CharSetName);

功能:接收已指定字符集名的字符串,并用该字符集创建OutputStreamWrite对象

为了避免频繁的转换字节流和字符流,对以上两个类进行了封装。1) BufferedWriter类封装了OutputStreamWriter类;2) BufferedReader类封装了InputStreamReader类;
封装格式
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in= new BufferedReader(new InputStreamReader(System.in);

利用下面的语句,可以从控制台读取一行字符串:

  BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
  String line=in.readLine();

File操作RandomAccessFile

为什么需要RandomAccessFile ? RandomAccessFile 可以对文件进行分段操作,利用RandomAccessFIle 就可以实现多线程下载,断点续传等功能。

  • 构造方法: RandomAccessFile raf = newRandomAccessFile(File file, String mode);
    其中参数 mode 的值可选 "r":可读,"w" :可写,"rw":可读性;
  • 成员方法: seek(int index);可以将指针移动到某个位置开始读写。 setLength(long len);给写入文件预留空间(用来构建一个设置大小的空文件 来 存储东西)。

RandomAccessFile 特点和优势

  • 既可以读也可以写
    RandomAccessFile不属于InputStream和OutputStream类系的它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是自己从头开始规定的,这里面包含读写两种操作
  • 可以指定位置进行读写
    RandomAccessFile能在文件里面前后移动,在文件里移动用的seek( ),所以它的行为与其它的I/O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。只有RandomAccessFile才有seek搜寻方法,而这个方法也只适用于文件.

IO的一些实例代码

  • 工具类
public class FileUtil {
    public static File getFile(String fleName) {
        File directory = new File("src/text/");//目录
        if(!directory.exists()){
            boolean mkdirs = directory.mkdirs();
            if(!mkdirs)return null;
        }
        File file = new File(directory.getPath()+"/"+fleName);
        if(!file.exists()){
            boolean newFile = false;
            try {
                newFile = file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(!newFile) return null;
        }
        return file;
    }

   public static void clean(File file){
        try {
            FileWriter fileWriter =new FileWriter(file);
            fileWriter.write("");
            fileWriter.flush();
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • DataOutputStream 和 DataInputStream
public class DataStreamTest {
    public static void main(String[] args) {
        File file = FileUtil.getFile("DataStream.txt");
        testDataOutputStream(file);
        testDataInputStream(file);
    }

    private static File getFile(){
        File file = new File("src/text/DataStream.txt");
        boolean success;
        if(!file.exists()){
            try {
                success = file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                success = false;
            }
        }else {
            success = true;
        }
        if(success){
            return file;
        }else {
            return null;
        }
    }

    private static void testDataOutputStream(File file) {
        try {
            DataOutputStream outputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
            outputStream.writeInt(123);
            outputStream.writeBoolean(true);
            outputStream.writeLong(21384329483248L);
            outputStream.writeChar(0x23231);
            outputStream.writeUTF("你是咕咕鸡么");
            outputStream.writeInt(456);
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("操作错误:"+e.getMessage());
        }
    }

    private static void testDataInputStream(File file) {
        try {
            //使用DataInputStream读取文件中数据的时候,需要和DataOutputStream中使用的写入方法对应,否则读出的数据就是错误的
            DataInputStream inputStream = new DataInputStream(new FileInputStream(file));
            System.out.println(inputStream.readInt());
            System.out.println(inputStream.readBoolean());
            System.out.println(inputStream.readLong());
            System.out.println(inputStream.readChar());
            System.out.println(inputStream.readUTF());
            System.out.println(inputStream.readInt());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("操作错误:" + e.getMessage());
        }
    }

}
  • OutputStreamWriter
public class OutputStreamWriterTest {
    private final static String STRING = "I like android very mach ===  这是优美的汉字 ";

    public static void main(String[] args) {
        test();
    }
    private static void test() {
        File file = FileUtil.getFile("OutputStreamWriter.txt");
        if(file == null) return;
        FileUtil.clean(file);//清空文件
        try {
            // true, 设置内容可以追加
            FileOutputStream fos = new FileOutputStream(file, true);
            //todo 是否有一个封装好的writer?  FileWriter 封装了FileOutputStream
            OutputStreamWriter oswDef = new OutputStreamWriter(fos);
            BufferedWriter bwDef = new BufferedWriter(oswDef);
            bwDef.write(STRING);
            bwDef.newLine();//写一个换行符
            bwDef.flush();
//            bwDef.close(); //不能在这里进行流的关闭操作,因为会同时关闭 fos 这样下面的fos就不能操作了 要报错
            System.out.println("默认编码方式:"+oswDef.getEncoding());

            OutputStreamWriter oswGBK = new OutputStreamWriter(fos, "GBK");
            BufferedWriter bwGBK = new BufferedWriter(oswGBK);
            bwGBK.write(STRING + "----->GBK");
            bwGBK.newLine();
            bwGBK.flush();
            System.out.println("指定编码方式GBK: " + oswGBK.getEncoding());

            OutputStreamWriter oswUTF8= new OutputStreamWriter(fos, StandardCharsets.UTF_8);
            BufferedWriter bwUTF8 = new BufferedWriter(oswUTF8);
            bwUTF8.write(STRING + "----->UTF_8");
            bwUTF8.newLine();
            bwUTF8.flush();
            System.out.println("指定编码方式UTF-8: " + oswUTF8.getEncoding());

            bwDef.close();
            bwGBK.close();
            bwUTF8.close();
        }catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

结果:

I like android very mach === 这是优美的汉字
I like android very mach === ����������� ----->GBK
I like android very mach === 这是优美的汉字 ----->UTF_8

  • InputStreamReader

public class InputStreamReaderTest {
    public static void main(String[] args) {
        File file = FileUtil.getFile("OutputStreamWriter.txt");
        if(file == null) return;
        try {
            FileInputStream inputStream1 = new FileInputStream(file);
            FileInputStream inputStream2 = new FileInputStream(file);
            FileInputStream inputStream3 = new FileInputStream(file);
            test(inputStream1,null);//使用默认编码方式
            test(inputStream2,"GBK");//使用 GBK 编码方式
            test(inputStream3, StandardCharsets.UTF_8.toString());//使用UTF-8编码方式

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

    //使用默认的编码方式
    private static void test(InputStream is,String charsetName){
        try{
            InputStreamReader isr;
            if(charsetName == null){
                isr = new InputStreamReader(is);
            }else {
                isr = new InputStreamReader(is,charsetName);
            }
            BufferedReader br = new BufferedReader(isr);
            String string;
            System.out.println("编码方式: " + isr.getEncoding());
            while ((string = br.readLine()) != null) {
                System.out.println(string);
            }
            System.out.println("====================================================== the end !");
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果:

编码方式: UTF8
I like android very mach === 这是优美的汉字
I like android very mach === ����������� ----->GBK
I like android very mach === 这是优美的汉字 ----->UTF_8
====================================================== the end !
编码方式: GBK
I like android very mach === 杩欐槸浼樼編鐨勬眽瀛�
I like android very mach === 这是优美的汉字 ----->GBK
I like android very mach === 杩欐槸浼樼編鐨勬眽瀛� ----->UTF_8
====================================================== the end !
编码方式: UTF8
I like android very mach === 这是优美的汉字
I like android very mach === ����������� ----->GBK
I like android very mach === 这是优美的汉字 ----->UTF_8
====================================================== the end !

  • BufferedOutputStream 和 BufferedInputStream
public class BufferedStreamTest {
    private static final byte[] byteArray = {
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
            0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A,
            0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
            0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
    };

    public static void main(String[] args) {
        File file = FileUtil.getFile("bufferedOutputStream.txt");
        testOutput(file);
        testInput(file);
    }

    private static void testOutput(File file) {
        if (file == null) return;
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            bos.write(byteArray[0]);
            bos.write(byteArray, 1, 25);
            bos.write(10);//换行符的ascii码值
            bos.write(byteArray, 26, 26);
            //加上换行符 总共写入 53个字节 ,一个字母、数字 占一个字节
            bos.flush();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void testInput(File file) {
        if (file == null) return;
        try {
            BufferedInputStream bin = new BufferedInputStream(new FileInputStream(file));
            System.out.println("文件的总字节数:" + bin.available());
            //打印前10个字节 ,bin.available() 刻度字节的位数  bin.read() 读取一个字节
            for (int i = 0; i < 5; i++) {
                if (bin.available() >= 0) {
                    System.out.println(byteToString((byte) bin.read()) + "==可读字节数===>" + bin.available());
                }
            }
            System.out.println("=====================分隔线============================");
            //判断输入流是否支持 mark() 和 reset() 方法
            if (bin.markSupported()) {
                //mark()参数readlimit:表示标记位置无效之前要读取的字节数
                //调用mark(int readlimit)方法后读取多少字节标记才失效,
                // 是取readlimit和BufferedInputStream类的缓冲区大小两者中的最大值,而并非完全由readlimit确定
                bin.mark(10);
                byte[] b = new byte[1024];
                int n1 = bin.read(b, 0, b.length);
                printByteValue(b);
                System.out.println("剩余的有效字节数 : " + n1);//为什么是48 因为进行了mark标记
                // reset() 将流重新定位在此输入流上最后一次调用mark方法的位置 (这里是 下标为5的地方,因为mark前读取了5次)
                bin.reset();
            }
            System.out.println(byteToString((byte) bin.read()));
            System.out.println("=====================分隔线============================");
            //skip 参数表示要跳过的字节数,返回值表示实际跳过的字节数,(可能剩余字节数小于需要跳过的字节数)
            long skip = bin.skip(1);
            System.out.println("实际跳过字节数:" + skip);
            System.out.println(byteToString((byte) bin.read()));
            System.out.println("=====================分隔线============================");

            byte[] b = new byte[1024];
            int n2 = bin.read(b,0, b.length);
            //只有使用 bin.read() 才会减少剩余有效字节数
            System.out.println("剩余的有效字节数 : " + n2);
            printByteValue(b);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static String byteToString(byte b) {
        byte[] bArray = {b};
        return new String(bArray);
    }

    private static void printByteValue(byte[] buf) {
        for (byte b : buf) {
            if (b != 0) {
                System.out.print(byteToString(b) + " ");
            }
        }
    }
}

结果:

文件的总字节数:53
A==可读字节数===>52
B==可读字节数===>51
C==可读字节数===>50
D==可读字节数===>49
E==可读字节数===>48
=====================分隔线============================
F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z 剩余的有效字节数 : 48
F
=====================分隔线============================
实际跳过字节数:1
H
=====================分隔线============================
剩余的有效字节数 : 45
I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z

  • BufferedWriter 和 BufferedReader
public class BufferedWriteReaderTest {
    public static void main(String[] args) throws IOException {
        File srcFile = FileUtil.getFile("BufferedReader.txt");
        File dstFile = FileUtil.getFile("BufferedWrite.txt");
        if(srcFile == null || dstFile == null)return;
        BufferedWriter bw = new BufferedWriter(new FileWriter(dstFile));
        BufferedReader br = new BufferedReader(new FileReader(srcFile));

        char[] string = new char[1024]; // 请注意此处不是byte,而是char
        while ((br.read(string)) != -1) {
            bw.write(string);
        }
        br.close();
        bw.flush();
        bw.close();
    }
}

适用于将一个文件的字符串内容拷贝到另一个文件中。

  • RandomAccessFile
public class RandomAccessFileTest {
    private static final String FILE_NAME = "rafFile";

    public static void main(String[] args) {
        testWriter();
        testReader();
    }

    /**
     * 向文件中写入内容
     */
    private static void testWriter() {
        File file = FileUtil.getFile(FILE_NAME);
        if (file == null) return;
        FileUtil.clean(file);//清空文件避免干扰
        try {
            //第二个参数 mode: "r"表示可读,"w"表示可写 ,"rw"表示文件可读可写
            RandomAccessFile rsfWriter = new RandomAccessFile(file, "rw");
            //seek() 不会改变文件大小、但是他会将下一个字符的写入位置标识为10000、
            //也就是说此后只要写入内容、就是从10001开始存、
            rsfWriter.seek(10000);
            printFileLength(rsfWriter);        //文件长度为: 0 ,因为文件还没有写入东西
            printSplitLine();
            //每个汉子占3个字节、写入字符串的时候会有一个记录写入字符串长度的2个字节
            rsfWriter.writeUTF("我是一只安卓小菜鸟");
            printFileLength(rsfWriter);// length:10029  point:10029
            printSplitLine();
            //会改变文件大小、只是把文件的size改变、
            //并没有改变下一个要写入的内容的位置、
            rsfWriter.setLength(10100);
            //每个字符占两个字节
            rsfWriter.writeChars("abcde");
            printFileLength(rsfWriter);        //length: 10100 point:10039
            printSplitLine();

            //再从“文件指针”为5000的地方插一个长度为100、内容全是'a'的字符数组
            //这里file长依然是10100、因为他是从“文件指针”为5000的地方覆盖后面
            //的200个字节、下标并没有超过文件长度
            rsfWriter.seek(5000);
            char[] cbuf = new char[100];
            for (int i = 0; i < cbuf.length; i++) {
                cbuf[i] = 'a';
                rsfWriter.writeChar(cbuf[i]);
            }
            printFileLength(rsfWriter);    //length:  10100  point:5200
            printSplitLine();
            //这里file长依然是10100 , 因为他是从“文件指针”为1000的地方覆盖后面
            //的100个字节、下标并没有超过文件长度
            byte[] bbuf = new byte[100];
            Arrays.fill(bbuf, (byte) 0x41);
            rsfWriter.seek(1000);
            rsfWriter.writeBytes(new String(bbuf));
            printFileLength(rsfWriter);
            printSplitLine();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取文件中内容
     * 这里我们要清楚现在文件中有什么内容、而且还要清楚这些内容起始字节下标、长度
     */
    private static void testReader() {
        /*
          简要说明rafFile文件中的内容
          0-1000:空白
          1000-1100:为大写字母A,(0X41)
          5000-5200:为100个字符 'a'
          10000-10029:我是一只安卓小菜鸟 (包含2个文件长度)
          10030-10039: abcde
         */
        System.out.println();
        System.out.println("===================这是读取分割线====================");
        System.out.println();
        File file = FileUtil.getFile(FILE_NAME);
        if (file == null) return;
        try {
            RandomAccessFile rsfReader = new RandomAccessFile(file, "r");
            //可按照自己想读取的东西所在的位置、长度来读取
            //读取"我是一只安卓小菜鸟"
            rsfReader.seek(10000);
            System.out.println(rsfReader.readUTF());
            printSplitLine();
            //读取字符'abcde'
            byte[] bbuf1 = new byte[10];
            rsfReader.seek(10030);
            rsfReader.read(bbuf1);
            System.out.println(new String(bbuf1));
            printSplitLine();
            //读取100个字符a
            rsfReader.seek(5000);
            byte[] bbuf2 = new byte[200];
            rsfReader.read(bbuf2);
            System.out.println(new String(bbuf2));
            printSplitLine();
            //读取100个A
            rsfReader.seek(1000);
            byte[] bbuf3 = new byte[100];
            rsfReader.read(bbuf3);
            System.out.println(new String(bbuf3));
            printSplitLine();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 打印文件长度
     *
     * @param rsfWriter 指向文件的随机文件流
     */
    private static void printFileLength(RandomAccessFile rsfWriter) {
        try {
            System.out.println("文件长度: " + rsfWriter.length() + "  <============>  文件指针位置(当前偏移量): " + rsfWriter.getFilePointer());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void printSplitLine() {
        System.out.println();
        System.out.println("====================我是一条帅气的分割线=========================");
        System.out.println();
    }
}

结果:


你可能感兴趣的:(java IO 的知识总结)