Java 常用IO流(字节流和字符流)详解

一,简介及分类

IO流用来处理设备之间的数据传输;JAVA对数据的操作是通过流的方式;JAVA用于操作流的类都在IO包中

流按流向分为两种:输入流,输出流。

流按操作类型分为两种:

          字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的

          字符流 : 字符流只能操作纯字符数据,比较方便。

区别:字节流每次读写一个字节,而字符流每次读写一个字符;

区分字符还是字节流:看类名结尾,例如:FileInputStream以Stream结尾的就是字节流,FileReader以Reader结尾的就是字符流

二,IO流常用父类

字节流的抽象父类:

       InputStream 

       OutputStream

字符流的抽象父类:

       Reader 

       Writer

三,字节流的使用

3.1,FileInputStream 字节输入流

private static void demo1() throws FileNotFoundException, IOException {
    FileInputStream fis = new FileInputStream("abc.txt");
    int b;
    while ((b=fis.read())!=-1) { //从该输入流读取一个字节的数据
        System.out.println(b);
    }

    fis.close(); //注意:流使用完之后,要关闭
}

问题:read()方法返回为啥是int类型而不是byte类型

因为一个字节的底层表示8bit,即二进制表示最大为11111111,再读取数据时有机会为1111111;而-1的byte表示也是11111111,所以如果用byte接受,在正常读取数据时候有可能当作结束标记结束了;在读取的时候用int类型接收,如果11111111会在其前面补上24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型;

 

3.2,FileOutputStream 字节输出流

FileOutputStream fos = new FileOutputStream("mm.txt"); //mm.txt如果没有这个文件,就创建一个;如果这个文件已存在,在创建FileOutputStream的时候,会清空文件内容;

fos.write(100);
fos.write("你好".getBytes());
fos.close();

注意:如果mm.txt已经存在,那么在执行new FileOutputStream("mm.txt")的时候就会清空mm.txt文件内容;如果想以追加的方式写入数据,就需要在创建FileOutputStream对象时这样写fos = new FileOutputStream("mm.txt",true);

 

3.3,FileInputStream和FileOutputStream综合使用,其实以下六行代码就是IO流的核心代码,其他都是根据核心代码进行升级;

private static void demo2() throws FileNotFoundException, IOException {

    FileInputStream fis = new FileInputStream("abc.txt"); //1
    
    FileOutputStream fos = new FileOutputStream("ss.txt"); //2

    int b;

    while ((b = fis.read())!=-1) { //从该输入流读取一个字节的数据 //3

        fos.write(b); //4

    }

    fis.close(); //5

    fos.close(); //6

}

这样写效率不高,字节流一次读写一个字节;读一次写一次;

 

通过available() 方法改进,此方法时获取读的文件所有的字节个数,如下:

FileInputStream fis = new FileInputStream("aaa.mp3");
FileOutputStream fos = new FileOutputStream("bbb.mp3");

byte[] arr = new byte[fis.available()]; //根据文件大小(fis.available() 字节个数)创建一个字节数组

fis.read(arr); //将文件上的所有字节读取到数组中
fos.write(arr); //将数组中的所有字节一次写到了文件上

fis.close();
fos.close();

弊端:有可能会内存溢出

 

最终改进方案:定义小数组,每次读写一个自定义数组大小的数据;

private static void demo3() throws IOException {
    FileInputStream fis = new FileInputStream("abc.txt");
    FileOutputStream fos = new FileOutputStream("ss.txt");

    byte [] arr = new byte[1024];
    int len; //有效字节数的个数
    
    while ((len = fis.read(arr))!=-1) {
        fos.write(arr,0,len); //写出有效个字节个数
    }

    fis.close();
    fos.close();
}

3.4,BufferedInputStream和BufferOutputStream

BufferedInputStream内置了一个缓冲区(数组)

BufferedOutputStream也内置了一个缓冲区(数组)

程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中

直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里

使用:

FileInputStream fis = new FileInputStream("aaa.mp3");
BufferedInputStream bis = new BufferedInputStream(fis);

FileOutputStream fos = new FileOutputStream("bbb.mp3");
BufferedOutputStream bos = new BufferedOutputStream(fos);

int b;
while((b = bis.read()) != -1) {
    bos.write(b);
}

bis.close(); //只关装饰后的对象即可
bos.close();

注意:BufferedOutputStream中的flush()和close()方法的区别

flush()方法

用来刷新缓冲区的,刷新后可以再次写出

close()方法

用来关闭流释放资源的的,如果是带缓冲区的流对象的close()方法,不但会关闭流,还会再关闭流之前刷新缓冲区,关闭后不能再写出

 

3.5,对图片加密,原理:一个数异或两次同一个值之后等于自己,例如 b^222^22b2=b;对于图片加密每读取一个字节异或上一个数,解密的时候再异或同一个数就好;

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c.jpg"));

int b;
while((b = bis.read()) != -1) {
    bos.write(b ^ 222);
}

bis.close();
bos.close();

3.6,案例:在控制台录入文件的路径,将文件拷贝到当前项目下

public static void main(String[] args) throws IOException {
    File file = getFile();
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));    
    BufferedOutputStream bos = new BufferedOutputStream(new     FileOutputStream(file.getName()));

    int b;
    while ((b = bis.read())!=-1) {    
        bos.write(b);
    }

    bis.close();
    bos.close();
}
public static File getFile(){
    Scanner sr = new Scanner(System.in); //创建键盘录入对象
    while(true){
        String line = sr.nextLine();
        File file = new File(line);
        if(!file.exists()){
            System.out.println("录入的路径不存在");
        }else if (file.isDirectory()) {
            System.out.println("录入的是文件夹路径");
        }else {
            return file;
        }
    }
}

3.7,案例:将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到exit时就退出

public static void main(String[] args) throws IOException {
    Scanner sc = new Scanner(System.in);
    FileOutputStream fos = new FileOutputStream("text.txt");
    System.out.println("请输入:");

    while(true){
        String line = sc.nextLine();        
        if("exit".equals(line)){
            break;
        }

        fos.write(line.getBytes());
        fos.write("\r\n".getBytes());
    }
    fos.close();
}

最后,注意字节流读取中文会出现乱码;写出的时候不会;解决读取乱码的问题有两个方法:1,使用字符输入流;2,使用内存输出流ByteArrayOutputStream

 

3.8,序列流 SequenceInputStream

序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类推;

例如:把两个或者更多的mp3整合成一个;

private static void demo1() throws FileNotFoundException, IOException {

    Vector<InputStream> streams = new Vector();
    streams.add(new FileInputStream("E:\\CloudMusic\\齐秦 - 狼.mp3"));  //注意反斜杠\\第一个时转义字符
    streams.add(new FileInputStream("E:\\CloudMusic\\庄心妍 - 梦诛缘·忆暖冬.mp3"));
    SequenceInputStream sis = new SequenceInputStream(streams.elements());
    FileOutputStream fos = new FileOutputStream("E:\\CloudMusic\\齐秦和庄心妍.mp3");

    int len;
    byte[] b = new byte[1024*8];
    while((len =sis.read(b))!=-1){
        fos.write(b, 0, len);
    }

    sis.close();
    fos.close();

}

3.9,内存输出流 ByteArrayOutputStream

该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据

面试题:定义一个文件输入流,调用read(byte[] b)方法,将a.txt文件中的内容打印出来(byte数组大小限制为5)

private static void demo1() throws FileNotFoundException, IOException {
    FileInputStream fis = new FileInputStream("a.txt");
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); //没有指向任何文件,也就是没有和硬盘上建立流通道,所以不需要关流;
    byte[] b = new byte[5];
    int len;
    while((len = fis.read(b))!=-1){
        baos.write(b, 0, len);
    }

    fis.close();
    // System.out.println(baos);

    byte[] data = baos.toByteArray();
    System.out.println(new String(data));
}

调用ByteArrayOutputStream的close()方法无效,就是调用之后也在是使用对象也不会抛异常;

 

3.10,对象输出输入流

  • ObjectOutputStream
  • ObjectInputStream

案例:

private static void demo2() throws IOException, FileNotFoundException {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
    List<Film> films = new ArrayList(); //使用List是防止读取时,不知道存了多少个对象,导致超出存入的数量会出现EOFExceptions

    films.add(new Film("战狼2"));//Film别忘了实现Serializable,可序列化的;
    films.add(new Film("绿皮书"));
    films.add(new Film("速度与激情"));
    oos.writeObject(films);
    oos.close();
}
private static void demo3() throws IOException, FileNotFoundException,

ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
    List<Film> films = (List<Film>) ois.readObject();
    for (Film film : films) {
        System.out.println(film);
    }
    ois.close();
}
public class Film implements Serializable{
    private static final long serialVersionUID = 111; //不加这个参数,会有个默认参数,反序列化多次时会出现InvalidClassException;或者没写入就读取也会出现此异常前提是么有加这个ID号;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Film(String name) {
        super();
        this.name = name;
    }

    @Override
    public String toString() {
        return "Film [name=" + name + "]";
    }
}

 

四,字符流

4.1,字符流介绍

字符流是可以直接读写字符的IO流

字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.

4.2,FileReader简单使用

private static void demo1() throws FileNotFoundException, IOException {
    FileReader fr = new FileReader("aaa.txt");
    int ch;

    while ((ch = fr.read()) != -1) { //一次读一个字符(char);    
        System.out.print((char) ch);    
    }
    fr.close();
}

4.3,FileWriter简单使用

private static void demo2() throws IOException {
    FileWriter fw = new FileWriter("cc.txt");
    fw.write("大家好,才是真的好!!");
    fw.close();
}

4.4,FileReader和FileWriter复制

private static void demo3() throws FileNotFoundException, IOException {
    FileReader fr = new FileReader("aaa.txt");    
    FileWriter fw = new FileWriter("ddd.txt");
    int ch;
    while((ch= fr.read())!=-1){
        fw.write(ch);
    }

    fr.close();
    fw.close();
}

4.5,自定义字符数组,完成复制;

private static void demo4() throws FileNotFoundException, IOException {
    FileReader fr = new FileReader("aaa.txt");
    FileWriter fw = new FileWriter("mm.txt");

    int len;
    char c[] = new char[1024];
    while ((len=fr.read(c))!=-1) {
        fw.write(c,0,len);
    }
    fr.close();
    fr.close();
}

注意:FileWriter的close()方法,有自带缓冲区;不关流有可能会写出不完整,也就是有一部分数据在缓冲区中没写出;

4.6,带缓冲的字符流:BufferedReader和BufferedWriter

    BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率

    BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率


private static void demo1() throws FileNotFoundException, IOException {

    BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
    BufferedWriter bw = new BufferedWriter(new FileWriter("nn.txt"));

    int ch;
    while((ch = br.read())!=-1){
        bw.write(ch);
    }
    br.close();
    bw.close();
}

BufferedReader中的readLine()和BufferedWriter中的newLine()

readLine()方法可以读取一行字符(不包含换行符号)

newLine()可以输出一个跨平台的换行符号"\r\n"

private static void demo2() throws FileNotFoundException, IOException {
    BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
    BufferedWriter bw = new BufferedWriter(new FileWriter("nn.txt"));

    String s;
    while((s = br.readLine())!=null){
        bw.write(s);
        bw.newLine(); //写出一个跨平台的换行符
    }
    br.close();
    bw.close();
}

4.7,什么情况下使用字符流

字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.

程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流

读取的时候是按照字符的大小读取的,不会出现半个中文

写出的时候可以直接将字符串写出,不用转换为字节数组

总结,只读或者只写一段文本的时候,使用字符流;

 

4.8,字符流是否可以拷贝非纯文本的文件

不可以拷贝非纯文本的文件

因为在读的时候会将字节(文件内容都是以字节的形式存储的)转换为字符(两个字节转换成一个字符),在转换过程中,可能找不到对应的字符,就会用?代替,写出的时候会将字符转换成字节写出去

如果是?,直接写出,这样写出之后的文件就乱了,看不了了

 

4.9,LineNumberReader

是BufferedReader的子类,比父类更强大的地方就是可以获取和设置行号:getLineNumber()和setLineNumber();

private static void demo3() throws FileNotFoundException, IOException {
    LineNumberReader lnr = new LineNumberReader(new FileReader("aaa.txt"));
    BufferedWriter bw = new BufferedWriter(new FileWriter("ss.txt"));

    String line;
    lnr.setLineNumber(100); //设置行号从100起

    while((line= lnr.readLine())!=null){
        bw.write(lnr.getLineNumber()+": "+ line);
        bw.newLine();
    }
    lnr.close();
    bw.close();
}

4.10,转换流

InputStreamReader和OutputStreamWriter都是字符流;使用方式类似于FileReader和FileWriter;

InputStreamReader:把字节流转换字符流读入,并使用指定的编码表将其解码为字符;

OutputStreamWriter:把字符流转换字节流输出,向其写入的字符编码成使用指定的字节编码表;

问题和替换:

FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表)

FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)

private static void demo4() throws UnsupportedEncodingException,

FileNotFoundException, IOException {
    InputStreamReader isr = new InputStreamReader(new FileInputStream("utf-8.txt"),"UTF-8"); //utf-8.txt是UTF_8编码格式
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK"); //gbk.txt文件使用的是GBK编码表

    int ch;
    while((ch = isr.read())!=-1){
        osw.write(ch);
    }
    
    isr.close();
    osw.close();
}


4.11,获取文本中字符出现的次数

private static void demo2() throws FileNotFoundException, IOException {

    FileReader fr = new FileReader("aaa.txt");
    FileWriter fw = new FileWriter("jj.txt");
    int ch;

    TreeMap<Character, Integer> map = new TreeMap<Character, Integer>();
    while((ch = fr.read())!=-1){
        char c = (char) ch;
        map.put( c, !(map.containsKey(c)) ? 1: map.get(c)+1);
    }

    for(Character c: map.keySet()){ //map.keySet()相当于遍历set集合;
        switch (c) {
        case '\r':
            fw.write("\\t" +": "+ map.get(c));
        break;

        case '\n':
            fw.write("\\n" +": "+ map.get(c));
        break;
        case '\t':
            fw.write("\\t" +": "+ map.get(c));
        break;

        default:
            fw.write(c +": "+ map.get(c));
        break;
        }
        fw.write("\r\n"); //FileWriter可以换成BufferedWriter,就可以使用跨平台的换行:newLine();
    }

    fr.close();
    fw.close();
}

4.12,从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.png文件名

通过递归完成;

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

File file = getFile();

printPNG(file);

}

private static void printPNG(File dir){

File[] subFile = dir.listFiles();

for (File file : subFile) { //递归结束条件

if(file.isFile()&&file.getName().endsWith(".png")){

System.out.println(file);

} else if(file.isDirectory()){

printPNG(file);

}

}

}

private static File getFile(){

System.out.println("请输入一个文件夹路径:");

Scanner sn = new Scanner(System.in);

while(true){

String path = sn.nextLine();

File file = new File(path);

if (file.isDirectory()) {

return file;

}else if(file.isFile()){

System.out.println("输入的是文件,请重新输入:");

}else {

System.out.println("请输入正确的文件夹路径");

}

}

}

五,其他流

5.1,打印流 PrintStream 和PrintWriter

打印流只操作数据目的,可以从打印流的构造方法(有OutputStream参数)可以看出;

private static void demo2() throws FileNotFoundException {

PrintWriter pw =new PrintWriter("mm.txt");

pw.print(22); //不能自动刷出

pw.println("aabbcc"); //自动刷出

pw.close();

}

 

private static void demo1() throws FileNotFoundException {

// PrintStream ps = new PrintStream("mm.txt");

PrintStream ps = System.out; //其默认向控制台输出信息

ps.print(111);

ps.println("mmmmmmm");

ps.close();

}

 

5.2,标准输入输出流System.in 和System.out;

System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据

System.out是PrintStream, 标准输出流, 默认可以向Console中输出字符和字节数据

修改标准输入输出流

修改输入流: System.setIn(InputStream)

修改输出流: System.setOut(PrintStream)

System.setIn(new FileInputStream("a.txt")); //修改标准输入流
System.setOut(new PrintStream("b.txt")); //修改标准输出流

InputStream in = System.in; //获取标准输入流
PrintStream ps = System.out; //获取标准输出流

int b;
while((b = in.read()) != -1) { //从a.txt上读取数据
    ps.write(b); //将数据写到b.txt上
}
in.close();
ps.close();

5.3,数据输入输出流

DataInputStream和DataOutputStream:可以按照基本数据类型大小读写数据;例如按int大小写出一个数字, 写出时该数据占8字节. 读取的时候也可以按照Long类型读取, 一次读取8个字节。

private static void demo4() throws FileNotFoundException, IOException {
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("cc.txt"));
    dos.writeInt(1111);
    dos.writeLong(2222);
    dos.close();
}
private static void demo5() throws FileNotFoundException, IOException {
    DataInputStream dis = new DataInputStream(new FileInputStream("cc.txt"));    
    System.out.println(dis.readInt());
    System.out.println(dis.readLong());
    dis.close();
}

5.4,随机访问流

RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。

支持对随机访问文件的读取和写入。

主要方法:read(),write(); seek() :设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ang_qq_252390816

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值