How2J学习笔记——I/O

I/O

教程来源:https://how2j.cn/k/io/io-file/345.html

流关系图

  1. 流分为字节流和字符流
  2. 字节流下面常用的又有数据流和对象流
  3. 字符流下面常用的又有缓存流
截屏2021-02-21 10.59.56

FileInputStream, FileOutputStream(文件输入输出流)

对于文件输入输出流,流的构造函数的参数都是文件。

FileInputStream fis = new FileInputStream(f);

FileOutputStream fos = new FileOutputStream(f);

try {
    File f = new File("d:/lol.txt");
    // 创建基于文件的输入流
    FileInputStream fis = new FileInputStream(f);
    // 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中

} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
try {
    File f = new File("d:/lol.txt");
    FileOutputStream fos = new FileOutputStream(f);

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

InputStream, OutputStream(字节输入输出流)

用于以字节的形式读取和写入数据。

InputStream和OutputStream是抽象类。

FileInputStream 是InputStream子类。

流的实例(如fis, fos)在向字节数组输入、从字节数组输出时,参数都是字节数组

fis.read(all); // 以字节流的形式读取文件所有内容

fos.write(data); // 把数据写入到输出流

try {
    //准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
    File f =new File("d:/lol.txt");
    //创建基于文件的输入流
    FileInputStream fis =new FileInputStream(f);
    //创建字节数组,其长度就是文件的长度
    byte[] all =new byte[(int) f.length()];
    
    //以字节流的形式读取文件所有内容
    fis.read(all);
    //每次使用完流,都应该进行关闭
	fis.close();

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

FileOutputStream 是OutputStream子类

try {
    File f = new File("d:/lol2.txt");
    // 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
    byte data[] = { 88, 89 };

    // 创建基于文件的输出流
    FileOutputStream fos = new FileOutputStream(f);
    
    // 把数据写入到输出流
    fos.write(data);
    // 关闭输出流
    fos.close();
    
} catch (IOException e) {
    e.printStackTrace();
}

关闭流的方式

标准关闭流的方式

  1. 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
  2. 在finally关闭之前,要先判断该引用是否为空
  3. 关闭的时候,需要再一次进行try catch处理
File f = new File("d:/lol.txt");
FileInputStream fis = null;
try {
    fis = new FileInputStream(f);
    byte[] all = new byte[(int) f.length()];
    fis.read(all);
    for (byte b : all) {
    	System.out.println(b);
	}
} catch (IOException e) {
	e.printStackTrace();
} finally {
    // 在finally 里关闭流
    if (null != fis){  // 如果引用不为空,关闭
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
}

try-with-resources

在try, catch, finally结束的时候自动关闭,回收相关资源。

在try中多个资源用分号隔开即可。

File f = new File("d:/lol.txt");
try (FileInputStream fis = new FileInputStream(f)) {
    byte[] all = new byte[(int) f.length()];
    fis.read(all);
} catch (IOException e) {
	e.printStackTrace();
}

字符流

Reader字符输入流,Writer字符输出流,专门用于字符的形式读取和写入数据。

FileReader 是Reader子类。

FileReader与FileInputStream相似,差异表现为接收数组为字符数组类型。

File f = new File("d:/lol.txt");
// 创建基于文件的Reader
try (FileReader fr = new FileReader(f)) {
    // 创建字符数组,其长度就是文件的长度
    char[] all = new char[(int) f.length()];
    // 以字符流的形式读取文件所有内容
    fr.read(all);
} catch (IOException e) {
	e.printStackTrace();
}

同样的,FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件

File f = new File("d:/lol2.txt");
// 创建基于文件的Writer
try (FileWriter fr = new FileWriter(f)) {
    // 以字符流的形式把数据写入到文件中
    String data="abcdefg1234567890";
    char[] cs = data.toCharArray();  // 注意这里要把String转换成字符数组
    fr.write(cs);
} catch (IOException e) {
	e.printStackTrace();
}

它们也是使用read和write函数。

编码问题

工作后经常接触的编码方式有如下几种:
ISO-8859-1 ASCII 数字和西欧字母
GBK GB2312 BIG5 中文
UNICODE (统一码,万国码)

其中
ISO-8859-1 包含 ASCII
GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。
UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中

用FileInputStream 字节流正确读取中文

假设已经使用 FileInputStream fis 将文本读出到了字节数组byte [] all中

将字节数组转换成GBK编码的字符串:

String str = new String(all,"GBK");

注: 在GBK的棋盘上找到的 某个字 后,JVM会自动找到 这个字 在UNICODE这个棋盘上对应的数字,并且以UNICODE上的数字保存在内存中。

用FileReader 字符流正确读取中文

FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK

FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替

new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 

InputStreamReader也是字符流

举例,使用FileReader和InputStreamReader分别读取一个txt文件(使用UTF-8编码)

18~25行:FileReader

28~36行:InputStreamReader

package stream;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
 
public class TestStream {
    public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
        File f = new File("E:\\project\\j2se\\src\\test.txt");
        System.out.println("默认编码方式:"+Charset.defaultCharset());
        //FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
        //而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
        try (FileReader fr = new FileReader(f)) {
            char[] cs = new char[(int) f.length()];
            fr.read(cs);
            System.out.printf("FileReader会使用默认的编码方式%s,识别出来的字符是:%n",Charset.defaultCharset());
            System.out.println(new String(cs));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
        //并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) 		{
            char[] cs = new char[(int) f.length()];
            isr.read(cs);
            System.out.printf("InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n");
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         
    }
}

输出如下:

截屏2021-02-22 01.12.05

FileInputStream字节流 与 InputStreamReader (或FileReader ) 字符流读取的区别

FileInputStream是先把文本读取到字节数组在转换到字符串时手动设置编码。

InputStreamReader是在构造流的时候设置编码,直接把文本读取到字符数组

缓存流

以介质是硬盘为例,字节流和字符流的弊端

在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。

缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。

缓存流必须建立在一个存在的流的基础上

使用缓存流读取数据

缓存字符输入流 BufferedReader 可以一次读取一行数据

File f = new File("d:/lol.txt");

try (FileReader fr = new FileReader(f);
    BufferedReader br = new BufferedReader(fr);){
    while (true) {
        // 一次读一行
        String line = br.readLine();
        if (null == line)
        	break;
        System.out.println(line);
    }
} catch (IOException e) {
	e.printStackTrace();
}

使用缓存流写出数据

PrintWriter 缓存字符输出流, 可以一次写出一行数据

注意PrintWriter实例实用的方法。

File f = new File("d:/lol2.txt");
try (
    FileWriter fw = new FileWriter(f);            
    PrintWriter pw = new PrintWriter(fw);){
    pw.println("garen kill teemo");
    pw.println("teemo revive after 1 minutes");
    pw.println("teemo try to garen, but killed again");
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

flush

有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush

例如在上面pw.println("…")下一行加上pw.flush();

数据流

使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写

如本例,通过DataOutputStream 向文件顺序写出 布尔值,整数和字符串。 然后再通过DataInputStream 顺序读入这些数据。

数据流也必须建立在一个存在的流的基础上

注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。

package stream;
      
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
      
public class TestStream {
      
    public static void main(String[] args) {
        write();
        read();
    }
 
    private static void read() {
        File f =new File("d:/lol.txt");
        try (
                FileInputStream fis  = new FileInputStream(f);
                DataInputStream dis =new DataInputStream(fis);
        ){
            boolean b= dis.readBoolean();
            int i = dis.readInt();
            String str = dis.readUTF();
             
            System.out.println("读取到布尔值:"+b);
            System.out.println("读取到整数:"+i);
            System.out.println("读取到字符串:"+str);
 
        } catch (IOException e) {
            e.printStackTrace();
        }
         
    }
 
    private static void write() {
        File f =new File("d:/lol.txt");
        try (
                FileOutputStream fos  = new FileOutputStream(f);
                DataOutputStream dos =new DataOutputStream(fos);
        ){
            dos.writeBoolean(true);
            dos.writeInt(300);
            dos.writeUTF("123 this is gareen");
        } catch (IOException e) {
            e.printStackTrace();
        }
         
    }
}

对象流

对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘

一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口

对象输入输出流也是建立在一个存在的流上

写入和读取对象使用oos.writeObject(原对象)和ois.readObject()方法

//创建一个Hero garen
Hero h = new Hero();
h.name = "garen";
h.hp = 616;

//准备一个文件用于保存该对象
File f =new File("d:/garen.lol");

try(
    //创建对象输出流
    FileOutputStream fos = new FileOutputStream(f);
    ObjectOutputStream oos =new ObjectOutputStream(fos);
    //创建对象输入流
    FileInputStream fis = new FileInputStream(f);
    ObjectInputStream ois =new ObjectInputStream(fis);
) {
    oos.writeObject(h);
    Hero h2 = (Hero) ois.readObject();
} catch (IOException e) {
	e.printStackTrace();
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}

System.in

System.in返回的是InputStream指向命令行输入的字节流,它的read方法以字节流的方式来读取命令行的输入的数据

Scanner(System.in)创建一个Scanner,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。如果要获取输入的内容,则只需要调用Scanner的nextLine()方法即可。

package stream;
 
import java.util.Scanner;
 
public class TestStream {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        int a = s.nextInt();
        System.out.println("第一个整数:"+a);
        int b = s.nextInt();
        System.out.println("第二个整数:"+b);
    }
}

一些Scanner常见的API:

截屏2021-02-22 10.48.53
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值