Java IO流

IO(Input/Output)流

  • IO流用来处理设备之间的数据传输
  • Java对数据的操作是通过流的方式
  • Java用于操作流的对象都在IO包中
  • 流按操作数据分为两种:字节流于字符流
  • 流按流向分为:输入流和输出流

字符流由来就是:早期的字节流 + 编码表,为了更便于操作文字数据

Java IO流_第1张图片
IO操作流程.png

IO流常用基类

所属包:java.io

  • 字节流的抽象基类(二进制)
    • InputStream
    • OutputStream
  • 字符流的抽象基类(文字编码)
    • Reader
    • Writer
  • 注意:有这四个类派生出来的子类名称都是以其父类名字作为子类的后缀,子类的前缀名就是该功能。例如:
    • InputStream派生类:FileInputStream
    • Reader派生类:FileReader
    • OutputStream派生类:FileOutputStream
    • Writer派生类:FileWriter

字节流(char)

FileWriter

需求:将一些文字存储到硬盘一个文件中

/*  FileWriter
 *  要操作文字数据,优先考虑字符流
 *  从内存写道硬盘使用输出流Writer,硬盘的数据体现是文件
 */
package FileWriter;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");  // 换行符

    public static void main(String[] args) throws IOException {
        // 创建一个可以往文件中写入字符数据的字符输出流对象
        /*
         *  既让是往文件中写入一个文字数据,那么在创建对象时,就必须明确该文件(用于存储数据的目的地)
         *  如果文件不存在,则会自动创建
         *  如果文件存在,则会被覆盖
         */
        FileWriter fileWriter = new FileWriter("F:\\demo.txt");

        // 调用Writer对象中的write(String)方法,写入数据,放入临时缓冲区
        fileWriter.write("hello zimo !");
        // 刷新缓冲区,写入文件中
        fileWriter.flush();
        // 文件末尾追加文字数据
        fileWriter.append(LINE_SEPARATOR+"hello world !");
        // 关闭流前,会自动刷新缓冲区后关闭
        fileWriter.close();
        // 关闭后无法操作刷新和写操作
        fileWriter.write("ccc");    // err

        FileWriter fileWriter1 = new FileWriter("F:\\demo1.txt",true);  // 如果构造中加入true,可以对文件进行续写
        fileWriter1.write("aaaaaaa");
        fileWriter1.write("bbbbbbb");
        fileWriter1.close();        // aaaaaaabbbbbbb
    }
}
FileReader

需求:读取文件内容输出到控制台上

/*  FileReader
 *  要操作文字数据,优先考虑字符流
 *  从文件文件读到磁盘使用输出流Reader
 */
package IOTest.FileReader;

import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
        // 创建一个读取字符数据流的对象,用读取流关联一个已存在文件
        FileReader fileReader = new FileReader("f:\\demo.txt");

        // 用Reader中的read方法读取字符,每次读取一个字符
        int ch = 0;
        while ((ch = fileReader.read()) != -1){
            System.out.println((char) ch);
        }
        fileReader.close();

        // 用read(char[])读取文本文件数据
        FileReader fw = new FileReader("f:\\demo.txt");

        char[] buf = new char[1024];    // 1024的整数倍
        int len = 0;
        while((len = fw.read(buf)) != -1){
            System.out.println(new String(buf, 0, len));
        }
    }
}

IOException异常处理
package IOTest.IOExceptionCatch;

import java.io.FileWriter;
import java.io.IOException;

public class IOExceptionDemo {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");  // 换行符

    public static void main(String[] args) {
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter("Z:\\demo.txt");
            fileWriter.write("hello zimo !");
            fileWriter.flush();
            fileWriter.append(LINE_SEPARATOR + "hello world !");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    throw new RuntimeException("关闭异常!");
                }
            }
        }
    }
}

练习:读取C盘的文件,写入到D盘中去

package IOTest.CopyFile;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyFileDemo {
    public static void main(String[] args) throws IOException {

        // 1.读取已有的文本文件,使用字符读取流和文件相关联
        FileReader fileReader = new FileReader("C:\\TextDemo.txt");
        // 2.创建一个目录,用于存储读取到的数据
        FileWriter fileWriter = new FileWriter("D:\\CopyFileDemo.txt");

        // 3.频繁的读写操作
        EveryCopyByByte(fileReader,fileWriter);
        EveryCopyByBuffer(fileReader,fileWriter);

        // 4.关闭流资源
        if(fileReader != null)
            fileReader.close();
        if(fileWriter != null)
            fileWriter.close();
    }

    // 每次读取一个缓冲区
    private static void EveryCopyByBuffer(FileReader fileReader, FileWriter fileWriter) {
        char[] buff = new char[1024];
        int len = 0;
        try {
            while ((len = fileReader.read(buff)) != -1) {
                fileWriter.write(buff, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 每次读取一个字符
    private static void EveryCopyByByte(FileReader fileReader, FileWriter fileWriter) throws IOException {
        int ch = 0;
        while ((ch = fileReader.read()) != -1) {
            fileWriter.write(ch);
        }
    }
}

Java IO流_第2张图片
复制文本文件图解.png

字符流的缓冲区

  • 缓冲区的出现提高了对数据的读写效率
  • 对应类
    • BufferedWriter
    • BufferedReader
  • 缓冲区要结合流才可以使用
  • 在流的基础上对流的功能进行了增强
BufferedWriter
  • newLine():换行符

缓冲区在缓冲对象时必须具备缓冲对象(流)

package IOTest.BufferWriter;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fileWriter = new FileWriter("buf.txt");

        // 为了提高写入效率,使用字符流的缓冲区
        // 创建了一个字符写入流的缓冲区对象,并和指定要被缓冲的对象相关联
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        // bufferedWriter.write("aaaaaaaaaa\r\nbbbbbbbb");
        bufferedWriter.write("aaaaaaaaaa");
        bufferedWriter.newLine();   // 缓冲区的换行符
        bufferedWriter.write("bbbbbbbb");

        // 使用缓冲区的写入方法将数据写入到缓冲区中
        bufferedWriter.flush();
        // 关闭缓冲区,其实关闭了缓冲区的流对象fileWriter,close();
        bufferedWriter.close();
    }
}
BufferedReader
  • readLine():读取一行
package IOTest.BufferReader;

import java.io.BufferedReader;
import java.io.FileReader;

public class BurreredWriterDemo {
    public static void main(String[] args) {
        FileReader fileReader = new FileReader("buf.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);

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

Java IO流_第3张图片
readLine方法原理.png

练习:使用缓冲区拷贝文本文件

package IOTest.BufferRW;

import java.io.*;

public class BufferCopyFileDemo {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("test.txt");
        BufferedReader bufferedReader = new BufferedReader(fileReader);

        FileWriter fileWriter = new FileWriter("copy_test.txt");
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        /*int ch = 0;
        while ((ch = bufferedReader.read()) != -1){
            bufferedWriter.write(ch);
        }*/

        String line = null;
        while ((line = bufferedReader.readLine())!= null){
            bufferedWriter.write(line);
            bufferedWriter.newLine();
            bufferedWriter.flush();
        }

        bufferedReader.close();
        bufferedWriter.close();
    }
}

练习:自定义读取缓冲区

// 模拟BufferedReader
package IOTest.BufferRW;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/*
分析:
 *  缓冲区无非就是封装了一个数组
 *  并对外提供了更多的方法对数组进行访问
 *  其实最终操作的就是数组的角标
 
原理:
 *  从源中获取一批数据装进缓冲区
 *  在缓冲区中不断取出一个个数据
 *  在此次取完后,在从源中继续取一批数据进缓冲区
 *  当源中的数据取完,用-1作为结束标记
 */
public class MyBufferedReader /*extends Reader*/ {

    private FileReader fileReader;
    /*private Reader reader;*/
    
    // 定一个数组作为缓冲区
    private char[] buf = new char[1024];

    // 定义一个指针用于操作这个数组中的元素。操作到最后一个元素,指针归零
    private int ptr = 0;

    // 定义一个计数器用于记录缓冲区中的数据个数。当该数据减到0,就从源中继续获取数据到缓冲区中
    private int count = 0;

    MyBufferedReader(FileReader fileReader){
        this.fileReader =fileReader;
    }
    /*
    MyBufferedReader(Reader reader){
        this.reader =reader;
    }
    */
    public int myRead() throws IOException {
        // 1.从源中获取一批数据到缓冲区中。需要先判断计数器为0时,才需要从源中获取数据
        if (count == 0){
            count = fileReader.read(buf);
            ptr = 0;
        }
        if (count < 0)
            return -1;
        char ch = buf[ptr++];
        count--;
        return ch;
    }

    public String myReadLine() throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        int ch = 0;
        while ((ch = myRead()) != -1){
            if (ch == '\r')
                continue;
            if (ch == '\n')
                return stringBuilder.toString();
            // 将从缓冲区读取到的字符,存储到缓存行数据的缓冲区中
            stringBuilder.append((char)ch);
        }
        if (stringBuilder.length()!= 0){
            return stringBuilder.toString();
        }
        return null;
    }
    public void myClose() throws IOException {
        fileReader.close();
    }
    /*
    @Override
    public int read(char[] cbuf, int off, int len) throws IOException{
        return 0;
    }
    @Override
    public void close() throws IOException{
        
    }
    */
}
LineNumberReader:

装饰类,行号读取追踪

public static void main(String[] args) throws IOException{
    FileReader fr = new FileReader("demo.txt");
    LineNumberReader lnr = new LineNumberReader(fr);
    
    String line = null;
    // lnr.setLineNumber(100);  // 默认从0开始,开始读取一行就变成1,可以手动设置100
    while ((line = lnr.readLine()) != null){
        System.out.println(lnr.getLineNumber() + ":" +line);
    }
}

装饰设计模式

对一组对象的功能进行增强时,就可以使用该模式进行问题的解决

class Person{
    void eat(){
        System.out.println("吃饭");
    }
}
// 这个类是为了增强Person而出现的
class NewPerson{
    private Person p;       // 本质还是Person
    NewPerson(Person p){
        this.p = p;
    }
    public void eat(){      // 外观进行了升级 
        System.out.println("开胃酒");
        p.eat();
        System.out.println("饭后甜点");
    }
}
public static void main(String[] args){
    Person p = new Person();
    NewPerson p1 = new NewPerson(p);
    p1.eat();
}
// 继承同样可以实现效果
class NewPersonExtends extends Person{
        public void eat(){
        System.out.println("开胃酒");
        super.eat();
        System.out.println("饭后甜点");
    }
}
继承和装饰的区别:
  • 假设的基础体系:
  • Writer:
    • | -- TextWriter:用于操作文本的流对象
    • | -- MediaWriter:用于操作媒体的流对象

想要对操作的动作进行效率的提高,按照面向对象思想,可以通过继承对功能进行扩展。

  • 效率提高需要加入缓冲技术
  • Writer:(继承实现)
    • | -- TextWriter:用于操作文本
      • | -- BufferTextWriter:加入了缓冲技术的操作文本流对象
    • | -- MediaWriter:用于操作媒体
      • | -- BufferMediaWriter:加入了缓冲技术的操作媒体流对象

如果体系再次进行功能扩展,又多了一个流对象,是否要产生子类?

  • 是,这时就会发现只为了提高功能,进行的继承,导致继承体系越来越臃肿,继承实现不够灵活

加入的都是同一种技术 --- 缓冲技术

前一种是让缓冲和具体的对象相结合,可以将缓冲进行单独的封装,哪个对象需要缓冲就将哪个对象和缓冲进行关联

  • Writer:
    • | -- TextWriter:用于操作文本的流对象
    • | -- MediaWriter:用于操作媒体的流对象
    • | -- BufferWriter:用于提高效率

装饰比继承灵活

class Buffer{
    // 一次增强一个流对象的功能
    Buffer(TextWriter w){}
    Buffer(MediaWriter w){}
}
class BufferWriter extends Writer{
    // 一次增强一个体系的功能
    Buffer(Writer w){}
}

字符流(byte)

  • 基本操作与字符流类相同
  • 但它不仅仅可以操作字符,还可以操作其他媒体文件
  • 例子:Copy一个jpg文件
package ByteStream;

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

public class ByteStreamDemo {
    public static void main(String[] args) throws IOException {
        writeDemo();
        readDemo();
    }

    private static void readDemo() throws IOException {
        // 1.创建一个读取流对象,和指定的文件进行关联
        FileInputStream fileInputStream = new FileInputStream("byteTest.txt");

        // 2.读取字符,一次读取一个字符
        /*
        int ch = 0;
        while ((ch = fileInputStream.read()) != -1){
            System.out.println((char) ch);
        }

        // 2.读取字符,一次读取一个指定大小的缓冲区,建议使用这个
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = fileInputStream.read(buf)) != -1){
            System.out.println(new String(buf, 0, len));
        }
        */

        // 2.读取字符,一次读取一个刚好的缓冲区
        // fileInputStream.available()      返回一个文件的字节大小,慎用,如果文件太大,内存会崩溃
        byte[] buf = new byte[fileInputStream.available()];
        System.out.println(new String(buf));

        // 3.关闭字节流对象
        fileInputStream.close();
    }

    private static void writeDemo() throws IOException {
        // 1.创建字节输出流对象,用于操作文件
        FileOutputStream fileOutputStream = new FileOutputStream("byteTest.txt");

        // 2.写数据,直接写入到目标文件,flush没有意义
        fileOutputStream.write("hello zimo!".getBytes());

        // 缓冲区需要刷新,字节流不需要刷新
        // fileOutputStream.flush();

        // 3.关闭字节流对象
        fileOutputStream.close();
    }
}

练习:复制Mp3文件

package CopyByte;

import java.io.*;

public class CopyMp3Demo {
    public static void main(String[] args) throws IOException {
        copyMp3();
        copyMp3Buffer();
    }
    // 缓冲区缓冲
    private static void copyMp3Buffer() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("C:\\大悲咒.mp3");
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\大悲咒-副本.mp3");
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

        int ch = 0;
        while ((ch = bufferedInputStream.read()) != -1){
            bufferedOutputStream.write(ch);
            // bufferedOutputStream.flush();
        }
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }
    // 自定义缓冲区
    private static void copyMp3() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("C:\\大悲咒.mp3");
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\大悲咒-副本.mp3");

        byte[] buf = new byte[1024];

        int len = 0;
        while ((len = fileInputStream.read(buf)) != -1){
            fileOutputStream.write(buf,0, len);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

不要使用字符流拷贝图片文件,可能存在字符解析异常

// 读取一个键盘录入的数据,并打印在控制台上。
    public static void main(String[] args) throws IOException {
        InputStream inputStream = System.in;

        int ch = inputStream.read();    // 阻塞式方法
        System.out.println(ch);

        inputStream.close();    // 标准流对象只有这一个,建议不用关,随着系统消失自动关闭

        InputStream inputStream2 = System.in;
        ch = inputStream.read();    // err:标准输入流已经被关闭了
    }
  • 标准输入/输出设备流,通常情况下不需要关闭
public static void main(String[] args) throws IOException {
    StringBuilder sb = new StringBuilder()
    InputStream inputStream = System.in;
    int ch = 0;
    while((ch  = inputStream.read()) != -1){
        if(ch == '\r')continue;
        if(ch == '\n'){
            String tmp = sb.toString();
            if("over".equals(tmp)){
                break;
            }
            System.out.println(tmp.toUpperCase());
            sb.delete(0, sb.length());
        }else
            sb.append((char)ch);
    }
}

转换流

1) 将字节流转成字符流:InputStreamReader(解码)

public static void main(String[] args){
    // 字节流
    InputStream in = System.in;
    // 将字节转成字符的桥梁,转换流
    InputStreamReader isr = new InputStreamReader(in);
    // 字符流
    BufferedReader buffer = new BufferedReader(isr);
    String line = null;
    while((line - buffer.readLine())!= null){
        if("over".equals(line))break;
        System.out.println(line.toUpperCase());
    }
}

2) 将字符转成字节流:OutputStreamWriter(编码)

public static void main(String[] args){
    OutputStream out = System.out;
    OutputStreamWriter osw = new OutputStreamWriter(out);
    String line = null;
    osw.write(line.toUpperCase() + "\r\n");
    osw.flush();        // 编码完刷新一下缓冲区
    
    BufferedWriter buffer = new BufferedWriter(osw);
    buffer.write(line.toUpperCase());
    buffer.newLine();
    buffer.flush();
}
  • 简化代码
public static void main(String[] args){
    BufferedReader buffReader = new BufferedReader(new InputStreamReader(System.in));   // 键盘读入
    BufferedWriter buffWriter = new BufferedWriter(new OutputStreamWriter(System.out)); // 输出到控制台
    //BufferedWriter buffWriter = new BufferedWriter(new FileOutputStream("a.txt"));    // 输出到a.txt
    
    String line = null;
    while((line = buffReader.readLine()) != null){
        if("over".equals(line))break;
        buffWriter.write(line.toUpperCase());
        buffWriter.newLine();
        buffWriter.flush();
    }
}
Java IO流_第4张图片
转换流.png

流的操作基本规律

  • 四个明确:

    1. 明确源和目的(汇)

      • 源:InputStream、Reader
      • 目的:OutputStream、Writer
    2. 明确数据是否是纯文本数据

      • 源:

        1. 是纯文本:Reader
        2. 不是纯文本:InputStream
      • 目的:

        1. 是纯文本:Writer
        2. 不是纯文本:OutputStream
    3. 明确具体的设备

      • 源:
        1. 硬盘:File
        2. 键盘:System.in
        3. 内存:数组
        4. 网络:Socket流
      • 目标:
        1. 硬盘:File
        2. 控制台:System.out
        3. 内存:数组
        4. 网络:Socket流
    4. 是否需要额外功能

      1. 是否需要高效(缓冲区): + buffer
      2. 是否需要转换: + InputStreamReader、OutputStreamWriter
    5. 如果需求中明确指定码表编码的动作

      • 不能使用FileWriter,因为内部使用的默认本地编码表
      • 只能使用其父类OutputStreamWriter
      • OutputStreamWriter接收一个字符输出流对象,既然操作文件,那么应该使用FileOutputStream
public static void main(String[] args){
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"),"UTF-8");
    // FileWriter fw = new FileWriter("a.txt"); // 等同于 OutputStreamWriter("a.txt")默认构造函数
    /**
     * FileWriter:实际上就是默认本机编码表,默认编码表操作文件的便捷类
     * 需要明确指定的编码,不能使用FileWriter类
     */
    osw.write("你好");
    osw.close();
}

什么时候使用转换流?

  1. 源或目的对应设备是字节流,但操作的是文本数据,可以作为转换桥梁,提高对文本操作的便捷
  2. 一旦操作文本涉及到具体指定的编码表时,必须使用转换流

File类

  • 用来将文件或者文件夹封装成对象
  • 方便对文件与文件夹的属性信息进行操作
  • File对象可以作为参数传递给流的构造函数
  • File类中的常用方法
public static void main(String[] args){
    // 将一个已存在或者不存在的文件或者目录封装成file对象 
    File file = new File("c:\\a.txt");
    File file1 = new File("c:\\", "a.txt");
    File file2 = new File("c:\\");
    File file3 = new File(file2, "a.txt");
    File file4 = new File("c:" + System.getProperty + "abc" + File.separator + "a.txt");
}
  • 常用方法分类:
    1. 获取:
      • 获取文件名称:String getName()
      • 获取文件路径:String getPath() | String getAbsolutePath()
      • 获取文件大小:Long length()
      • 获取文件修改时间:Long lastModified()
      • 获取文件父目录:String getParent()如果是相对路径,则获取不到父目录
    2. 创建与删除:
      • 创建文件:boolean createNewFile() 如果文本不存在则创建,如果存在不创建
      • 创建文件夹:boolean mkdir() | boolean mkdirs()
      • 删除文件:boolean delete() 直接删除 boolean deleteOnExit() 结束后删除
    3. 判断:
      • 是否存在:boolean exists()
      • 是否文件:boolean isFile()
      • 是否目录:boolean isDirectory()
    4. 其他:
      • 重命名/剪切:boolean renameTo(File f1)
      • 列出根目录下文件:static File listRoots()
      • 可用磁盘空间:long getFreeSpace()
      • 总磁盘容量:long getTotalSpace()
      • 已用磁盘空间:long getUsableSpace()
      • 获取目录名称:String[] list() 包含隐藏文件(如果对象是文件或者是系统级,会抛出异常)
      • 获取目录对象:File[] listFiles()
// 文件名过滤器:获取路径下所有.java文件
public class FilterByJava implements FilenameFilter{
    @Override
    public boolean accept(File dir, String name){
        return name.endsWith(".java");
    }
}
// 文件过滤器:过滤隐藏文件
public class FilterByHidden implements FilenameFilter{
    @Override
    public boolean accept(File pathname){
        return !pathname.isHidden();
    }
}
//测试
public static void main(String[] args){
    File dir = new File("D:\\");
    String[] names = dir.list(new FilterByJava());
    for(String name : names){
        System.out.println(name);
    }
    File[] names = dir.list(new FilterByHidden());
    for(String name : names){
        System.out.println(name);
    }
}
Java IO流_第5张图片
文件过滤器解析.png

深度遍历文件夹(递归方式)

import java.io.File;

public class test {
    public static void main(String[] args){
        File file = new File("F:\\JavaTest");
        int level = 0;
        listAll(file ,level);
    }

    public static void listAll(File file, int level) {
        System.out.println(getSpace(level) + file.getName());
        level++;
        File[] files = file.listFiles();
        for (File file1 : files){
            if (file1.isDirectory()){
                listAll(file1, level);
            }else {
                System.out.println(getSpace(level) + file1.getName());
            }
        }
    }

    private static String getSpace(int level) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("|--");
        for (int i = 0; i < level; i++){
//            stringBuilder.append("\t");
            stringBuilder.insert(0, "|\t");
        }
        return stringBuilder.toString();
    }
}

递归:自身直接或间接调用自身。

一个功能在被重复使用,并且每次使用时,参与运算的结果和上一次有关,可以用递归来解决问题

注意:递归一定要明确条件,否则容易栈溢出,注意递归的次数

// 删除一个有内容的目录
public static void main(String[] args){
    File file = new File("E:\\demo");
    removeDir(file);
}

public void removeDir(File file){
    File[] files = file.listFiles();
    for(File f : files){
        if(f.isDirectory()){
            removeDir(f);
        }else{
            f.delete();
        }
    }
    file.delete();
}
  • Map

    | ---- Hashtable

          | ---- Properties:
    

    Properties集合:

    • 特点:
      1. 该集合中的键和值都是字符串类型
      2. 集合中的数据可以保存到流中,或者从流中获取
      3. 通常该集合用于操作以键值对形式存在的配置文件
public static void main(String[] args) throws IOException {
    Properties prop = new Properties();
    // 存储元素
    prop.setProperty("zhangsan","30");
    prop.setProperty("lisi","31");
    prop.setProperty("wangwu","29");
    prop.setProperty("zhaoliu","33");

    // 持久化存储--->> 使用流将文件存储到文件中
    FileOutputStream fos = new FileOutputStream("info.txt");
    
    // 将集合数据存储到文件中去
    prop.store(fos, "name : age");
    fos.close();
    
    // 加载文件中的数据(键值对)到集合中
    // 使用读取流
    FileInputStream fis = new FileInputStream("info.txt");
    prop.load(fis);
    prop.list(System.out);
}

模拟Properties.load方法

public static void myLoad() throws IOException{
    Properties prop = new Properties();
    BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
    
    String line = null;
    while((line = bufr.readLine()) != null){
        if(line.startsWith("#")) continue;
        String[] arr = line.split("=");
        prop.setProperty(arr[0], arr[1]);
    }
    prop.list(System.out);
    bufr.close();
}

修改Properties中的某一个键对应的值

// 对已有的配置文件中的信息进行修改
/**
 *  读取这个文件
 *  将这个文件中的键值数据存储到集合中
 *  通过集合对数据进行修改
 *  再通过流将修改后的数据存储到文件中
 */
public static void test() throws IOException{
    // 读取这个文件
    File file = new File("info.txt");
    if(!file.exists()){
        // 文件不存在
        file.createNewFile();
    }
    FileReader fr = new FileReader("info.txt");
    
    // 创建集合存储信息
    Properties prop = new Properties();
    // 将流中的信息存储到集合中
    prop.load();
    
    // 修改
    prop.setProperty("wangwu", "16");
    FileWriter fw = new FileWriter(file);
    prop.store(fw, "");
    
    prop.list(System.out);
    fw.close();
    fr.close();
}

练习:免费试用程序5次,之后请注册再使用

public static void main(String[] args) throws IOException{
    getAppCount();
}

public static void getAppCount() throws IOException{
    // 将配置文件封装成File对象
    File countFile = new File("count.properties");
    if(!countFile.exists()){
        countFile.createNewFile();
    }
    FileInputStream fis = new FileInputStream(countFile);
    Properties prop = new Properties();
    prop.load(fis);
    
    // 从集合中获取键的次数
    String value = prop.getProperty("time");
    int count = 0;
    if(value != null){
        count = Integer.parseInt(value);
        if(count >= 5){
            throw new RuntimeException("使用次数已到,请注册,给钱!");
        }
    }
    count++;
    prop.setProperty("time", count + "");
    FileOutputStream fos = new FileOutputStream(countFile);
    prop.store(fos, "");
    fos.close();
    fis.close();
}

打印流

  • PrintWriter与PrintStream
    • 可以直接操作输入流和文件

PrintStream构造函数,接收三种类型的值:

  1. 字符串路径
  2. File对象
  3. 字节输出流

PrintWriter构造函数,接收三种类型的值:

  1. 字符串路径
  2. File对象
  3. 字节输出流
  4. 字符输出流
public static void main(String[] args){
    PrintStream out = new PrintStream("print.txt");
    out.write(97);          // a 
    out.write(609);         // a 只写最低8位 0000-0000 0000-0000 0000-0010 0110-0001
    out.print(97);          // 97 将原有字符保持原样,然后将数据打印到目的地
    out.close();
    ----------------------------------------------------------------------------------------------------
    // 字符打印流
    BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter out = new PrintWriter(System.out);
    // PrintWriter out = new PrintWriter(System.out, true); 自动刷新缓冲区,无需调用flush
    String line = null;
    while((line = bufr.readLine()) != null){
        if("exit".equals(line)) break;
        out.println(line);
        out.flush();
    }
    out.close();
    bufr.close();
}

序列流

  • SequenceInputStream
    • 多个流进行合并
// 将三个文件中内容放到一个文件中去
public static void main(String[] args) throws IOException{
    /*
    Vector v = new Vector();
    v.add(new FileInputStream("1.txt"));
    v.add(new FileInputStream("2.txt"));
    v.add(new FileInputStream("3.txt"));
    Enumeration en = v.elements();
    */
    
    ArrayList al = new ArrayList();
    for(int i = 1; i <= 3; i++){
        al.add(new FileInputStream(i + ".txt"));
    }
    final Iterator it = al.iterator();
    /*
    Enumeration en = new Enumeration(){
        @Override
        public boolean hasMoreElements(){
            return it.hasNext();
        }
        @Override
        public FileInputStream nextElement(){
            return it.next();
        }
    };
    */
    Enumeration en = Collections.enumeration(al);
    SequenceInputStream sis = new SequenceInputStream(en);
    
    FileOutputStream fos = new FileOutputStream("4.txt");
    
    byte[] buf = new byte[1024];
    
    int len = 0;
    while((len = sis.read(buf)) != -1){
        fos.write(buf, 0, len);
    }
    fos.close();
    sis.close();
}

文件切割

public static void main(String[] args){
    private static final int ONE_M = 1024 * 1024;
    File file = new File("C:\\0.bmp");
    // 用读取流关联源文件
    FileInputStream fis = new FileInputStream(file);
    // 定义一个1M缓冲区
    byte[] buf = new byte[ONE_M];
    // 创建目的
    FileOutputStream fos = null;
    
    int len = 0;
    int i = 1;
    /**
     *  记录切割文件的数据:名称、碎片个数,方便合并
     */
    Properties prop = new Properties();
    
    File dir = new File("D:\\partfile");
    if(!dir.exists())
        dir.mkdirs();
    while((len = fis.read(buf))!= -1){
        fos = new FileOutputStream(new File(dir, (i++) + ".part"));
        fos.write(buf, 0, len);
        fos.close();
    }
    // 将切割文件的信息保存到prop集合中
    prop.setProperty("filename", file.getName());
    prop.setProperty("partnum", count + "");
    fos = new FileOutputStream(new File(dir, count + ".properties"));
    // 将prop集合中数据存储到文件中
    prop.store(fos,"save file info");
    fos.close();
    fis.close();
}

文件合并

public static void main(String[] args){
    File dir = new File("D:\\partfile");
    // 获取指定目录下文件配置信息
    File[] files = dir.listFile(new SuffixFilter(".properties"));
    if(files.length != 1){
        throw new RuntimeException(dir + ",该目录下没有properties扩展名文件或者不唯一");
    }
    // 记录配置文件对象
    File configFile = files[0];
    
    // 获取该文件中信息
    Properties prop = new Properties();
    FileInputStream fis = new FileInputStream(configFile);
    prop.load(fis);
    
    String filename = prop.getProperty("filename");
    int count = Integer.parseInt(prop.getProperty("partnum"));
    
    // 获取该目录下所有的碎片文件
    File[] partFiles = dir.listFiles(new SuffixFilter(".part"));
    if(partFiles.length != (count - 1)){
        throw new RuntimeException("碎片文件不符合,无法合并,应该是"+count);
    }
    
    // 将碎片文件和流对象关联,并存储到集合中
    ArrayList al = new ArrayList();
    for(int i = 1; i <= partFiles.length ; i++){
        al.add(new FileInputStream(new File(dir, x + ".part")));
    }
    
    // 将多个流合并成一个序列流
    Enumeration en = Collections.enumeration(al);
    SequenceInputStream sis = new SequenceInputStream(en);
    FileOutputStream fos = new FileOutputStream(new File(dir, filename));
    
    // 读写过程
    byte[] buf = new byte[1024];
    int len = 0;
    while((len = sis.read(buf)) != -1){
        fos.write(buf, 0, len);
    }
    fos.close();
    sis.close()
}
// 文件过滤器
public class SuffixFilter implements FilenameFilter{
    private String suffix;
    public SuffixFilter(String suffix){
        super();
        this.suffix = suffix;
    }
    @Override
    public boolean accept(File dir, String name){
        return name.endsWith(suffix);
    }
}

操作对象

  • ObjectInputStream与ObjectOutputStream
    • 被操作的对象需要实现Serializable(标记接口)
public static void main(String[] args) throws IOException{
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
    // 对象序列化,被序列化对象必须实现Serializable接口
    oos.writeObject(new Person("zimo", 18));    // 不实现Serializable标记接口,无法写入
    oos.close();
    
    -------------------------------------------------------------------------------------------------
    // 对ObjectOutputStream写入的基本数据和对象进行反序列化
    ObjectInputStream ois = new ObjectInputStream(new FileOutputStream("obj.object"));
    Person p = (Person)ois.readObject();    // 以来与Person的class文件 throw ClassNotFountException
    System.out.println(p.getName() + ":" + p.getAge());
}
public class Person implements Serializable{
    private static final long serialVersionUID = 9527L;
    private String name;
    private int age;
    public Person(String name, int age){
        super();
        this.name = name;
        this.age = age;
    }
    public String getName(){
        return name;
    }
    public int getAge(){
        return age;
    }
}

Serializable接口

序列化运行时使用一个serialVersionUID,用于反序列化时验证发送者和接收者是否为该对象加载的序列化兼容的类

Person{
    private transient String name;
    private static int age;
}
transient:非静态数据不想被序列化可以使用这个关键字修饰

其他类

  • RandomAccessFile
    • 随机访问文件,自身具备读写的方法
    • 通过skipBytes(int x), seek(int x)来达到随机访问
  • 管道流
    • PipedInputStream和PipedOutputStream
      • 输入输出可以直接进行连接,通过结合线程使用
// 使用RandomAccessFile对象写入一些人员信息,比如姓名和年龄
public static void writeFile() throws IOException{
    RandomAccessFile raf = new RandomAccessFile("ranacc.txt","rw");
    raf.write("zhangsan".getBytes());
    raf.write(97);
    raf.writeInt(609);
    //raf.write(609);       // 丢弃高位 剩下97
    raf.close();
    -------------------------------------------------------------------------------------------------------
    RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "r");
    // 通过seek设置指针的位置,可以进行随机读取
    raf.seek(0*8);
    byte[] buf = new byte[4];
    raf.read(buf);
    String name = new String(buf);
    int age = raf.readInt();
    System.out.println(name + age);
    raf.close();
}
public class PipedStream{
    public static void main(String[] args) throws IOException{
        PipedInputStream input = new PipedInputStream();
        PipedOutputStream output = new PipedOutputStream();    
        // 连接管道
        input.connect(output);
        
        new Thread(new Input(input)).start();
        new Thread(new Output(output)).start();
    }
}
class Input implements Runnable{
    private PipedInputStream in;
    Input(PipedInputStream in){
        this.in = in;
    }
    public void run(){
        try{
            byte[] buf = new byte[1024];
            int len = in.read(buf);
            String s = new String(buf, 0, len);
            System.out.println("s="+s);
            in.close();
        }catch(Exception e){
            
        }
    }
}
class Output implements Runnable{
    private PipedOutputStream out;
    Output(PipedOutputStream out){
        this.out = out;
    }
    public void run(){
        try{
            out.write("管道来了".getBytes());
        }catch(Exception e){
            
        }
    }
}
  • 操作基本数据类型
    • DataInputStream与DataOutputStream
  • 操作字节数组
    • ByteArrayInputStream与ByteArrayOutputStream
    • 关闭ByteArray(I/O)Stream无效,关闭后仍可以使用,不抛出IO异常,只读取内存数据,不占用资源
  • 操作字符数组
    • CharArrayReader与CharArrayWrite
  • 操作字符串
    • StringReader与StringWrite
public static void main(String[] args) throws IOException{
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
    dos.writeUTF("你好");     // 代表UTF-8修改版  带标头 8个字节
    dos.close();
    
    DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
    String str = dis.readUTF();
    System.out.println(str);
    dis.close();
    ----------------------------------------------------------------------------------------------
    ByteArrayInputStream bis = new ByteArrayInputStream("abcdefg".getBytes());
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
    int ch = 0;
    while((ch = bis.read()) != -1){
        bos.write(ch);
    }
    // 无需关流
}

编码表

编码表的由来

  • 计算机只能识别二进制数据,早期由来是电信号
  • 为了方便应用计算机,让它可以识别各个国家的文字
  • 就将各个国家的文字用数字来表示,并一一对应,形成一张表
  • 这就是编码表

常见的编码表

  • ASCII:美国标准信息交换码
    • 用一个字节的7位可以表示
  • ISO8859-1:拉丁码表。欧洲码表
    • 用一个字节的8位表示
  • GB2312:中国的中文编码表
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号
  • Unicode:国际标准码,融合了多种文字
    • 所有文字都用两个字节来表示,Java语言使用的就是Unicode
  • UTF-8:最多用三个字节来表示一个字符
  • ...
// 简单的编码解码
/**
 *  字符串 --->> 字节数组:编码   看的懂 --> 看不懂
 *  字节数组 --->> 字符串:解码   看不懂 --> 看得懂
 */

public static void main(String[] args){
    String str = "你好";
    // 编码
    byte[] buf = str.getBytes();
    // byte[] buf = str.getBytes("utf-8");      // -28 -67 -96 -27 -91 -67
    
    for(byte b : buf){
        System.out.println(b);  // GBK: -60  -29  -70  -61
    }
    
    // 解码
    String s1 = new String(buf);
    // String s1 = new String(buf, "utf-8");
    System.out.println("s1 = " + s1);
}
Java IO流_第6张图片
编码错误补救.png

你可能感兴趣的:(Java IO流)