Java I/O编程

2019-07-28  本文已影响0人  Roct

字节输出类 outputStream

字节的数据是以byte类型为主实现的操作, 在进行字节内容输出的时候可以使用outputStream类完成, 这个类的基本定义如下:

public abstract class OutputStream extends Object implements Closeable, Flushable
outputStream的继承

outputStream类定义的事一个公共的输出操作标准, 而在这个操作标准里一共定义了3个内容输出的方法:

public abstract void write(int b) throws IOException;
public void write(byte[] b) throws IOException;
public void write(byte[] b, int off, int len) throws IOException;
FileOutputStream类的构造方法
public FileOutputStream(File file) throws FileNotFoundException;
public FileOutputStream(File file, boolean append) throws FileNotFoundException;

往D盘写入一个txt文件, 并往文件里写入'hello world'

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt"); // 指定文件要操作的目录
        if (!file.getParentFile().exists()) { // 如果文件不存在
            file.getParentFile().mkdirs(); // 那么创建父目录
        }
        OutputStream outputStream = new FileOutputStream(file); // 通过子类实例化对象
        String str = "hello world";
        outputStream.write(str.getBytes()); // 将字符串转为字节并输出
        outputStream.close(); // 关闭资源
    }
}

将上面代码改为自动关闭

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt"); // 指定文件要操作的目录
        if (!file.getParentFile().exists()) { // 如果文件不存在
            file.getParentFile().mkdirs(); // 那么创建父目录
        }
        try (OutputStream outputStream = new FileOutputStream(file)) { // 通过子类实例化对象
            String str = "hello world";
            outputStream.write(str.getBytes()); // 将字符串转为字节并输出
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

将写入文件改为运行多次, 内容追加

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt"); // 指定文件要操作的目录
        if (!file.getParentFile().exists()) { // 如果文件不存在
            file.getParentFile().mkdirs(); // 那么创建父目录
        }
        try (OutputStream outputStream = new FileOutputStream(file, true)) { // 通过子类实例化对象
            String str = "hello world";
            outputStream.write(str.getBytes()); // 将字符串转为字节并输出
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

字节输入类 inputStream

InputStream类主要实现的就是字节数据读取, 该类定义如下:

public abstract class InputStream extends Object implements Closeale

读取单个字节数据, 如果现在已经读取到底了返回-1

public abstract int read() throws IOException

\color{red}{读取一组字节数据, 返回的是读取的个数, 如果没有数据已经读取到底则返回`-1`(重要)}

public int read(byte[] b) throws IOException

读取一组字节数据(只占数组的部分)

public int read(byte[] b, int off, int len) throws IOException;

InputStream类是一个抽象类, 应该依靠它的子类来实例化对象, 如果要从文件读取一定使用FileInputStream子类.
读取数据

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt");
        InputStream inputStream = new FileInputStream(file);
        byte data [] = new byte[1024]; // 开辟一个缓冲区读取数据
        int len = inputStream.read(data); // 读取数据, 数据全部保存在字节数组中, 返回读取的个数
        System.out.println("[" + new String(data, 0, len) + "]");
        inputStream.close();
    }
}

对于直接输入流最麻烦的就是: 使用read()方法读取的时候只能够以字节数组为主进行接收,JDK1.9推出了一个新的方法, 可以一次读取出所有的数据:

public byte[] readAllBytes() throws IOException;

应用

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt");
        InputStream inputStream = new FileInputStream(file);
        byte data [] = inputStream.readAllBytes(); // 读取全部数据
        System.out.println("[" + new String(data) + "]");
        inputStream.close();
    }
}

需要注意:

Writer字符输出流

定义

public abstract class Writer extends Object implements Appendable, Closeable, Flushable;
Writer的继承关系

输出字符数据

public void write(char[] cbuf) throws IOException;

输出字符串

public void write(String str) throws IOException;

创建一个文本写入hello world

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt");
        if (!file.getParentFile().exists()) { // 如果文件不存在
            file.getParentFile().mkdirs(); // 创建一个文件
        }
        Writer w = new FileWriter(file);
        String str = "hello world2222 \n";
        w.write(str);
        w.close();
    }
}

追加

Writer w = new FileWriter(file, true);
w.append("dshsjh");

Reader字符输入流

定义

public abstract class Reader extends Object implements Readable, Closeable
Reader的继承关系

接收数据

public int read(char[] cbuf) throws IOException;

实现数据读取

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt");
        if (file.getParentFile().exists()) { // 如果文件存在
            Reader reader = new FileReader(file);
            char data[] = new char[1024];
            int len = reader.read(data);
            System.out.println("输出数据[" + new String(data, 0, len) + "]");
            reader.close();
        }
    }
}

转换流

转换流指的是字节流和字符流相互转换. java.io包提供了两个类

OutputStreamWriter InputStreamReader
定义 public class OutputStreamWriter extends Writer public class InputStreamReader extends Reader
构造方法 public OutputStreamWriter (OutputSteam out) public InputStreamReader(InputSteam in)
类的继承结构

通过类的继承结构和构造方法可以发现, 所谓的转换处理就是将接收到的字节流向上转型转换成字符流.

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" +
                File.separator + "world.txt");
        if (!file.getParentFile().exists()) { // 如果文件存在
            file.getParentFile().exists();
        }
        OutputStream output = new FileOutputStream(file);
        Writer out = new OutputStreamWriter(output); // 字节流变成字符流
        out.write("hello world"); // 直接输出字符串, 字符流适合处理中文
        out.close();
    }
}
FileWriter的继承结构 FileReader的继承结构 文件传输流程

字符编码

计算机只认识0, 1的数据, 如果想描述一些文字的编码, 就需要对二进制的数据进行一些组合, 如果想正确的显示出一些内容, 那么就需要有对应的解码, 所以编码和解码需要进行同一套的标准

常用的编码

内存操作流

正常的IO操作都是以文件为终端, 所以一定会有文件的产生,


正常的IO文件操作流

但是如果不想产生文件进行IO操作(临时文件), 那么内存就是IO操作的终端.


内存操作流

Java中提供两种内存操作流

public ByteArrayInputStream(byte[] buf);
public ByteArrayOutputStream()

利用内存流实现一个小写字母转大写字母的操作

public class Main {
    public static void main(String[] args) throws Exception {
        String str = "hello world";
        InputStream input = new ByteArrayInputStream(str.getBytes()); // 将数据保存到内存中
        OutputStream out = new ByteArrayOutputStream();
        int data = 0;
        while ((data = input.read()) != -1) { // 每次读一个字节
            out.write(Character.toUpperCase(data)); // 保存数据
        }
        System.out.println(out.toString());
        input.close();
        out.close();

    }
}

管道流

管道流主要的功能是实现两个线程之间的IO处理操作.


管道流

字节管道流

字符管道流

实现管道操作

class SendThread implements Runnable {
    private PipedOutputStream output;

    public SendThread() {
        this.output = new PipedOutputStream();
    }

    public PipedOutputStream getOutput() {
        return this.output;
    }

    @Override
    public void run() {
        try {
            this.output.write((Thread.currentThread().getName() + "发送消息\n").getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ReceiveThread implements Runnable {
    private PipedInputStream input;

    @Override
    public void run() {
        byte data[] = new byte[1024];
        try {
            int len = this.input.read(data);
            System.out.println(Thread.currentThread().getName() + ": 接受消息" + new String(data, 0, len) + "\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public ReceiveThread() {
        this.input = new PipedInputStream();
    }

    public PipedInputStream getInput() {
        return this.input;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        SendThread send = new SendThread();
        ReceiveThread receive = new ReceiveThread();
        send.getOutput().connect(receive.getInput()); // 进行管道连接
        new Thread(receive, "接受").start();
        new Thread(send, "发送").start();

    }
}

RandomAccessFile(随机存储)

对于文件内容的处理操作主要是通过InputStream(Reader)OutputStream(Writer)进行操作, 但是只能将数据部分部分的读取出来, 如果现在有一个20G大小的文件需要读取, 传统的读取方式无法满足, 这个时候就需要使用RandomAccessFile, 能够实现文件跳跃式的读取(RandomAccessFile使用的前提需要有一个完善的保存形式, 数据保存位数都要提前确定好)

RandomAccessFile定义了如下的操作方法:

public RandomAccessFile(File file, String  mode) throws FileNotFoundException;

打印流

所有的文件输出操作都要依靠OutputStream类完成, 但是OutputStream类有一个很大的缺点, 所有的数据一定要转为字节数组以后才可以输出, 如果现在要输出long, doule, Date, 那么需要将数据转为字节数组,
设计一个打印流

class PrintUntil implements AutoCloseable {
    private OutputStream output;
    public PrintUntil(OutputStream output) {
        this.output = output;
    }
    public void print(String str) {
        try {
            this.output.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void print(long num) {
        this.print(String.valueOf(num));
    }
    public void println(String str) {
        this.print(str + "\n");
    }
    public void println(long num) {
        this.println(String.valueOf(num));
    }
    @Override
    public void close() throws Exception {
        this.output.close();
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("d:" + File.separator + "hello.txt");
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        PrintUntil print = new PrintUntil(new FileOutputStream(file));
        print.println("你会唱小星星吗");
        print.println(20);
        print.close();
    }
}

输出结果

输出结果

java自带的打印流

PrintStream PrintWriter
继承关系 public class PrintStream extends FilterOutputStream implements Appendable, Closeable public class printWriter extends Writer
构造方法 public PrintStream(OutputStream out) public PrintWriter(OutputStream out)
public PrintWriter(Writer out)

使用PrintWriter实现数据的输出操作

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("d:" + File.separator + "hello.txt");
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        PrintWriter print = new PrintWriter(new FileOutputStream(file));
        print.println("你会2唱小星星吗");
        print.println(20);
        print.close();
    }
}

JDK1.5开始, printWriter有追加的格式化支持.

public PrintWriter printf(String format, Object ... args);

格式化输出

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("d:" + File.separator + "hello.txt");
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        PrintWriter print = new PrintWriter(new FileOutputStream(file));
        String name = "小强";
        int age = 29;
        double salary = 28298239.2222;
        print.printf("姓名:%s; 年龄:%d; 收入:%7.2f", name, age, salary);
        print.close();
    }
}

比起直接使用OutputStream类, 那么使用PrintWriter, PrintStream类更加简单, 使用输出的时候最好使用打印流

System对于IO的支持

标准输出(显示器)

public static final PrintStream out;

错误输出

public static final PrintStream err;

标准输入(键盘)

public static final InputStream in;

err和out的区别

public class Main {
    public static void main(String[] args) throws Exception {
        String eg = "a";
        try {
            System.out.println(Integer.parseInt(eg));
        } catch (Exception e) {
            System.out.println(e);
            System.err.println(e);
        }
    }
}

在IDEA中, out是显示的白色, err显示的是红色.
在设计的最初, out是输出想让用户看到的信息, err是不想让用户看到的信息

修改输出的位置

public static void setOut(PrintStream out);
public static void setErr(PrintStream err);

修改System.err的位置

public class Main {
    public static void main(String[] args) throws Exception {
        System.setErr(new PrintStream(new FileOutputStream(new File("d:" + File.separator + "err.txt"))));
        String eg = "a";
        try {
            System.out.println(Integer.parseInt(eg));
        } catch (Exception e) {
            System.out.println(e);
            System.err.println(e);
        }
    }
}

这样错误日志就输出到了D盘的err.txt文本内, 所以修改System.err的位置一般错误日志的收集

BufferedReader类

BufferedReader类是一个缓冲输入流, 利用这类可以实现键盘输入数据的标准化定义
读取一行数据

public String readLine() throws IOException;
BufferedReader的继承结构

使用BufferedReader实现键盘输入年龄并校验

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入您的年龄:");
        String msg = input.readLine();
        if (msg.matches("\\d{1,3}")) {
            System.out.println("您的年龄是:" + msg);
        } else {
            System.out.println("请确认您听明白了我的意思!");
        }
    }
}

Scanner扫描流

Scanner是在JDK1.5以后推出的, 是BufferedReader的替代类.
构造

public Scanner(InputStream source)

判断是否有数据

public boolean hasNext();

取出数据

public String next()

设置分隔符

public Scanner userDelimiter(String pattern)

Scanner可以和正则直接做自定义验证
获取输入的是否为生日

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入您的生日:");
        if (scanner.hasNext("\\d{4}-\\d{1,2}-\\d{1,2}")) {
            String str = scanner.next();
            System.out.println("输入的生日为:" + new SimpleDateFormat("yyyy-MM-dd").parse(str));
        }
        scanner.close();
    }
}

读取某个文件里的文本内容

public class Main {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(new File("d:" + File.separator + "hello.txt"));
        scanner.useDelimiter("\n"); // 设置什么是换行符
        if (scanner.hasNext()) {
            System.out.println(scanner.next());
        }
        scanner.close();
    }
}

对象序列化

将内存中保存的对象以二进制数据流的形式进行处理, 可以实现对象的保存或者是网络的传输.

对象序列化
并不是所有的对象都可以被序列化, 在Java当中有一个强制性的要求, 如果要序列化, 对象必须实现java.io.Serializable父接口
类名称 序列化:ObjectOuputStream 反序列化ObjectInputStream
类定义 public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
构造方法 public ObjectOutputStream(OutputStream out) throws IOException public ObjectInputStream(InputStream in) throws IOException
操作方法 public final void writeObject(Object obj) throws IOException public final Object readObject() throws IOException, ClassNotFoundException

实现序列化和反序列化

class Person implements Serializable {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "姓名:" + this.name + ", 年纪:" + this.age;
    }
}
public class Main {
    private  static final File SAVE_FILE = new File("D:" + File.separator + "hello.person");
    // 对象反序列化
    public static Object loadObject() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(SAVE_FILE));
        Object obj = ois.readObject();
        ois.close();
        return obj;
    }
    // 对象序列化
    public static void saveObject(Object obj) throws Exception {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(SAVE_FILE));
        oss.writeObject(obj);
        oss.close();
    }
    public static void main(String[] args) throws Exception {
        // 序列化
        saveObject(new Person("tinger", 39));
        // 反序列化
        System.out.println(loadObject());
    }
}

transient关键字

默认情况下当执行了对象序列化以后会将类中的全部属性的内容进行全部的序列化. 但是很多时候可能有一些属性不需要序列化, 所以可以在对应属性的定义上使用transient关键字.
如果将nametransient修饰

private transient String name;

那么name属性的内容是不会被保留下来的, 读取出的name的值就是null.

上一篇下一篇

猜你喜欢

热点阅读