Stream File IO流

Stream File IO流

1.0 Stream 简介

Stream流是jdk8新增的一套api,用于操作集合或者数组的数据,Stream流大量集合了Lambda的语法风格,增强

了代码的可读性和简易性

来看一下简单应用

package MyStream;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;


public class StreamTest {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("1");
        list.add("11");
        list.add("2");
        List<String> list1=list.stream().filter(s->s.startsWith("1")&&s.length()==1).collect(Collectors.toList());
    }
}

该代码利用Stream流将List中特定特征的元素收集起来,用collect传递给Collectors中toList()方法创建的ArrayList

Stream开发思想分几步

第一步为获取Stream流

第二步为调用Stream中的方法,对数据进行处理计算,比如过滤,排序,去重

最后一步为获取处理到的结果,放到新的集合中

1.1获取Stream流

看下面的程序

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("1");
        list.add("11");
        list.add("2");
        Stream<String> stream =list.stream();
        System.out.println(stream.count());
        Map<String,Integer> map=new HashMap<>();
        stream=map.keySet().stream();
        //获取键流
        Stream<Integer> stream1=map.values().stream();
        //获得值流
        Stream<Map.Entry<String,Integer>> stream2=map.entrySet().stream();
        //获得entry流
        String[] strings={"1","2"};
        Stream<String> stream3=Arrays.stream(strings);
        Stream<String> stream4=Stream.of(strings);
        //两种方法都可以获取数组的Stream流
    }
}

1.2 Stream流常见方法

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("1");
        list.add("11");
        list.add("111");
        Stream<String> stream =list.stream();
        List list1 =list.stream().filter(s ->s.startsWith("1")).toList();
        System.out.println(list1);
        list.stream().filter(s ->s.startsWith("1")).forEach(System.out::println);
        //遍历过滤后的集合并输出
        //list.stream().sorted().forEach(System.out::println);
        //排序后输出
        List<Student> list2=new ArrayList<>();
        list2.add(new Student(22));
        list2.add(new Student(21));
        list2.add(new Student(20));
        list2.stream().sorted((o1, o2) -> o1.getAge()- o2.getAge()).forEach(System.out::println);
        //排序重写CompareTo方法或者指定排序规则
    }
}

1.3 Stream流的终结方法

终结方法使用完流就结束了,因为终结方法并没有返回值。

max方法用来返回最大值

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<Student> students=new ArrayList<>();
        students.add(new Student(11));
        students.add(new Student(12));
        students.add(new Student(13));

        //Optional<Student> opt=students.stream().max((o1, o2) -> o1.getAge()-o2.getAge());
        Student maxStudent=null;
        //Optional 为了避免产生空指针对象
        //不过一般这样写
        maxStudent=students.stream().max((o1, o2) -> o1.getAge()-o2.getAge()).get();
        System.out.println(maxStudent);
    }
}

其中需要对max方法指定具体的比较方式

需要注意的是stream流的数据只能使用一次,不能二次调用,所以stream只用来处理数据,最终的结果还是需要

数组或者集合来保存的

下面看下转换方式

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<Student> students=new ArrayList<>();
        students.add(new Student(11));
        students.add(new Student(12));
        students.add(new Student(13));
        List<Student> list1=students.stream().filter(o->o.getAge()==11).toList();
        System.out.println(list1);
        //list1.add(new Student(44));
        //上面的写法也行,不过在java16后才允许使用,而且返回的是不可变list,不能修改其中的值
        list1=students.stream().filter(o->o.getAge()==12).collect(Collectors.toList());
        list1.add(new Student(22));
        //该方法可选返回是可变List还是不可变List
        
    }
}

我们使用两种方式将处理后的数据转换为List,使用第二种方法,collect的底层对stream流的数据进行了遍历,然

后加到Collections的toList方法创建的新的List中

下面这个例子显示了流如果用两次会怎样

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<Student> students=new ArrayList<>();
        students.add(new Student(11));
        students.add(new Student(12));
        students.add(new Student(13));
        Stream<Student> stream=students.stream();
        List<Student> list1=stream.filter(o->o.getAge()==11).toList();
        System.out.println(list1);
        Set<Student> set=stream.collect(Collectors.toSet());
        //该方法可选返回是可变List还是不可变List

    }
}

报错为;Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:260)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:702)
at MyStream.StreamTest.main(StreamTest.java:14)

此时如果要重新操作需要再拿一个Stream流

将结果转化为Map

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<Student> students=new ArrayList<>();
        students.add(new Student(11));
        students.add(new Student(12));
        students.add(new Student(13));
        Stream<Student> stream=students.stream();
        Map<Integer,Integer> map=stream.limit(3).collect(Collectors.toMap(o1->o1.getAge(),o2->o2.getAge()));
        System.out.println(map);

    }
}

转化中需要指定键和值的映射

但我们需要注意,MAP集合中键是不能重复的,所以我们在设置映射关系导致键出现了重复则会报错,如果在出现重复键时我们想固定选取某一个,可以再传递一个匿名内部类去表示选择决策

package MyStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.*;
public class StreamTest {
    public static void main(String[] args) {
        List<Student> students=new ArrayList<>();
        students.add(new Student(11));
        students.add(new Student(11));
        students.add(new Student(13));
        Stream<Student> stream=students.stream();
        Map<Integer,Integer> map=stream.limit(3).collect(Collectors.toMap(o1->o1.getAge(),o2->o2.getAge(),(v1,v2)->v1));
        System.out.println(map);

    }
}

这样写就是当出现两个键重复时选第一个

1.4 File 和IO

File是io下的类,File类的对象用于代表当前操作系统的文件

提供了很多对文件进行操作的接口,不过File类只能对文件本身进行操作,不能操作里面的数据

如果我们需要读写文件中的数据,就需要IO流进行操作

1.5 创建File对象

package FileIo;
import java.io.*;
public class Mytest {

    public static void main(String[] args) {
        //如果使用相对路径,需要注意的是是从文件的根目录而不是代码目录开始
        File f1=new File("src/FileIo/text.txt");
        System.out.println(f1.length());
        //获得大小,如果获得的是文件夹的大小,显示的只是文件夹的大小,不是所有文件总和的大小

    }

}

1.6 File的判断方法

package FileIo;
import lombok.Data;

import java.io.*;
import java.time.format.DateTimeFormatter;

public class Mytest {

    public static void main(String[] args) {
        File f1=new File("src/FileIo/text.txt");
        System.out.println(f1.exists());//判断文件是否存在
        System.out.println(f1.isFile());//判断打开的是否是文件
        System.out.println(f1.isDirectory());//判断打开的是否是文件夹
        System.out.println(f1.getName());//获得文件名
        System.out.println(f1.length());//获得文件长度
        long time = f1.lastModified();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDate = java.time.Instant.ofEpochMilli(time)
                .atZone(java.time.ZoneId.systemDefault())
                .format(dtf);
        System.out.println("Last modified: " + formattedDate);
        //获得文件上一次的修改时间
        System.out.println(f1.getPath());
        //获得创建文件对象时的路径
        System.out.println(f1.getAbsolutePath());
        //获得绝对路径


    }

}

1.7 删除和创建文件

package FileIo;
import lombok.Data;

import java.io.*;
import java.time.format.DateTimeFormatter;

public class Mytest {

    public static void main(String[] args) throws IOException {
        File f1=new File("src/FileIo/a.txt");
        System.out.println(f1.createNewFile());
        //创建一个文件
        File f2=new File("src/FileIo/myfile/newfile");
        //System.out.println(f2.createNewFile());
        //由于创建文件夹只能一次创建一级,当前我的路径下FileIo后的文件夹都不存在,需要创建两级所以失败
        System.out.println(f2.mkdirs());
        //使用mkdirs则可以一次创建多级,不过如果使用mkdir则不行
        f2.delete();
        f1.delete();//删除两个文件夹和文件
        //需要注意的是,只能删除空文件夹或者文件,想要实现一个存在文件的文件夹删除需要使用递归删除
    }

}

1.8 遍历File

package FileIo;
import lombok.Data;

import java.io.*;
import java.time.format.DateTimeFormatter;

public class Mytest {

    public static void main(String[] args) throws IOException {
        File f2=new File("src/FileIo/myfile/newfile");
        f2.mkdirs();
        File f1=new File("src/FileIo/myfile/newfile/a.txt");
        f1.createNewFile();
        String[] str=f2.list();//将当前文件对象目录下一级的文件名放在字符串数组中
        for(String s:str){
            System.out.println(s);
        }
        File [] files=f2.listFiles();//获得当前文件对象目录下一级文件对象将其放在文件对象数组中
        for(File file:files){
            System.out.println(file.getAbsolutePath());
        }

    }

}

注意

1 id是,当主调是文件,或者路径不存在时,我们调用其listFiles()或者list()时会返回NULL

2 当主调为空文件夹时返回一个长度为0的数组,不是null

3 当主调有内容时,正常返回一级路径下的内容

4 即使一级目录下有隐藏文件,我们仍然可以正常读取

5 当主调是一个文件夹,我们没有权限访问,此时会返回null

1.9 递归搜索文件

有了上面的这些知识,我们就可以实现递归搜索文件了

package FileIo;
import java.io.File;
import java.sql.SQLOutput;
import java.util.*;
public class Search {
    public static void main(String[] args) {
        File dir=new File("F:/TEST");
        searchFile(dir,"a.txt");
    }
    public static void searchFile(File dir,String fileName){
        if(dir==null||!dir.exists()|| dir.isFile()){
            return ;
        }
        File[] files=dir.listFiles();
        if(files==null||files.length==0){
            return ;
        }

        for(File file:files){
            if(file==null){
                continue;
            }
            if(file.getName().equals(fileName)&&file.isFile()){
                System.out.println(file.getPath());
                continue;
            }
            if(file.isDirectory()){
                searchFile(file,fileName);
            }
        }
    }

}

2.0 字符集的编码和解码

package FileIo;

import java.io.UnsupportedEncodingException;
import java.sql.SQLOutput;
import java.util.Arrays;

public class CharSet {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str="你好世界";
        byte[] bytes=str.getBytes();
        //UTF-8编码下一个汉字三个字节
        System.out.println(Arrays.toString(bytes));//默认编码方式为utf-8
        System.out.println(new String(bytes));
        //默认解码方式,使用utf-8

        bytes=str.getBytes("GBK");
        System.out.println(Arrays.toString(bytes));
        System.out.println(new String(bytes,"GBK"));//指定解码方式
    }
}

2.1 IO流简介

I指Input 称为输入流,负责把数据读到内存中去

O值Output ,称为输出流,负责写数据出去

按照流中最小数据单位,IO流可以分为字节流和字符流,其中字节流适合操作所有类型的文件,字符流只适合操

作文本类型的文件

按照上面的分类,IO流总共可以分为下面这四大类,

字节输入流 字节输出流 字符输入流 字符输出流

不同的流进行操作的对象不同,操作的方式也不同,这就是他们的区别

2.2 文件字节输入流

字节输入流的抽象类是 InputStream 对应的实现类为FileInputStream

即文件字节输入流

先看一个简单运用,我们试着一个字节一个字节的读取文件中的内容

package FileIo;

import java.io.*;

public class Finput {
    public static void main(String[] args) throws IOException {
        // InputStream is =new FileInputStream(new File("src/FileIo/myfile/newfile/a.txt"));
        //上面为完整写法
        InputStream is = new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        /*
        * int b1=is.read();//read方法用于读取文件的一个字节,如果读取不到则返回值-1
        * System.out.println((char) b1);
        * */
        int b ;
        while ((b=is.read())!=-1) {//注意,赋值语句的返回值是所赋给的值本身
            System.out.print((char) b);
        }
    }
}

但这样写是有问题的,第一是效率很差,第二是无法正确读取汉字

所以我们需要一次读多个字节,

package FileIo;

import java.io.*;

public class Finput {
    public static void main(String[] args) throws IOException {
        // InputStream is =new FileInputStream(new File("src/FileIo/myfile/newfile/a.txt"));
        //上面为完整写法
        InputStream is = new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        byte[] bytes=new byte[3];//创建一个字节数组
        int len=is.read(bytes);//这样会从文件中依次读取字节,直到这个字节数组被填满,或者读取文件的指针指向文件终止符
        System.out.println(new String(bytes));
        /*
            需要注意的是,如果我们使用同一个字节数组进行多次读取操作,每次
            读取后进行新一次读取时上一次读取的值是不会自动删除的,只会产生覆盖,所以
            对于没有覆盖到的地方,还是上一次读取的值。
         */
        //System.out.println(new String(bytes,0,len));//通过指定解码范围来避免出现无效字符
    }
}

下面使用循环进行优化

package FileIo;

import java.io.*;

public class Finput {
    public static void main(String[] args) throws IOException {
        InputStream is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        byte[] bytes=new byte[3];
        int len;
        while((len=is.read(bytes))!=-1){
            System.out.print(new String(bytes,0,len));
        }
    }
}

这样就实现了一次读取多个字符,但读取汉字还是存在问题,因为该字节数组定义长度为三,所以任然可能会对汉

字进行分割读取,从而无法识别

如果我们想要正确读取汉字不产生乱码,即解决这种不正确的分割问题,我们可以一次读取全部字节

package FileIo;

import java.io.*;

public class Finput {
    public static void main(String[] args) throws IOException {
        InputStream is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        File f=new File("src/FileIo/myfile/newfile/a.txt");
        // byte[] bytes=new byte[f.length()]; //这样写不行,因为文件的长度返回的是一个Long
        byte[] bytes=new byte[(int) f.length()];//所以如果文件很大的时候,是不能直接这样的
        is.read(bytes);
        System.out.print(new String(bytes));
    }
}

java官方提供一个更简单的写法

package FileIo;

import java.io.*;

public class Finput {
    public static void main(String[] args) throws IOException {
        InputStream is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        byte[] bytes=is.readAllBytes();
        System.out.print(new String(bytes));
    }
}

上面的写法从Jkd9开始支持

从上面我们可以知道,读文本文件使用字节流显得有点麻烦,后面我们使用字符流可以简化这个过程

2.3 文件字节输出流

作用是以内存为基准,把内存中的数据以字节形式写到文件中去

package FileIo;

import java.io.*;
import java.util.Objects;

public class Finput {
    public static void main(String[] args) throws IOException {
        OutputStream os=new FileOutputStream("src/FileIo/myfile/newfile/a.txt");
        os.write('b');
        os.write(65);
        os.write(66);
        /*使用方法1 write(int a) 写入一个字节
            该方法只会写入一个字节,超出的部分不写入,
            当我们想上面这样写入字节时,写入的内容会被当作ASCII码进行处理
            如果只想要写入数字而不是ASCII码,可以像下面这样写。
         */
        os.write(String.valueOf(66666666).getBytes(),0,1);//此时写入的就是数字了
        byte[] bytes="sss".getBytes();
        os.write(bytes);//使用write(byte[] x);直接将字节数组写入
        os.write("\r\n".getBytes());
        os.write("abc".getBytes(),0,2);
        /*
        上面的方法指定写入字节数组的哪部分,第二个参数是起点,第三个参数是写入的字节数
         */

    }
}

但当前使用的是覆盖管道,写入的内容直接从开头对原来的内容进行覆盖,接下来学习怎么追加

想要实现追加,只需要在一开始文件输出流的时候加一个参数即可

outputStream os=new FileOutputStream("src/FileIo/myfile/newfile/a.txt",true);

但io流管道会消耗内存资源和系统资源,所以我们需要进行关闭操作,而且如果数据还在缓冲区中我们不关闭管道

直接结束程序,就会出现数据并未写入的情况,所以关闭管道很重要

2.4 文件复制

让我们写一个简单使用,来实现文件复制功能

package FileIo;

import java.io.*;
import java.util.Objects;

public class Finput {
    public static void main(String[] args) throws IOException {
        InputStream is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        OutputStream os=new FileOutputStream("src/FileIo/myfile/newfile/b.txt");
        byte[] bytes=is.readAllBytes();
        os.write(bytes);
        is.close();
        os.close();
    }
}

这样写是通过一个字节数组将整个文件装进来,但可能会出现文件太大的情况,这时候就要优化一下写法,分多次

装入

package FileIo;

import java.io.*;
import java.util.Objects;

public class Finput {
    public static void main(String[] args) throws IOException {
        InputStream is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
        OutputStream os=new FileOutputStream("src/FileIo/myfile/newfile/b.txt",true);
        byte[] bytes=new byte[1024];
        int len;
        while((len=is.read(bytes))!=-1){
            os.write(bytes,0,len);
        }
        is.close();
        os.close();
    }
}

这种方法也是先了该功能,但需要注意的是,当我们创建字节输出文件流时,当地址中的文件不存在时,会自动

创建文件,不过要是当输入地址中的文件夹也不存在,则会抛出一个错误

2.5 安全的管道关闭方式

package FileIo;

import java.io.*;
import java.util.Objects;

public class Finput {
    public static void main(String[] args)  {
        InputStream is=null;
        OutputStream os=null;
        try {
            is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
            os=new FileOutputStream("src/FileIo/myfile/newfile/b.txt");
            byte[] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1) {
                os.write(bytes, 0, len);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
        finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

我们发现我们这样写是安全了,不过太麻烦了,所以从jdk7中,提供了更简单的资源释放方法

try-with-resource

其形式为:

try(定义资源1;定义自愿2;.....){
	可能出现异常的代码;
}
catch(异常类名 变量名){
    异常处理代码;
}

其中()中只能方资源,要不然报错,资源的定义是继承了AutoCloseable接口

当资源使用结束后,会自动运行重写的close法

对于上面的方法,我们简化后为:

package FileIo;

import java.io.*;
import java.util.Objects;

public class Finput {
    public static void main(String[] args)  {
        try (InputStream    is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
             OutputStream  os=new FileOutputStream("src/FileIo/myfile/newfile/b.txt");){
            byte[] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1) {
                os.write(bytes, 0, len);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }

    }
}

该技术自jdk7开始可以使用

2.6 文件字符输入流(FileReader)

就是以字符的形式将硬盘中的数据读到主存中,

package FileIo.char_stream;
import java.nio.file.Paths;
import java.io.*;
public class MyFileReader {
    public static void main(String[] args) throws Exception {
        Reader fr = new FileReader("src/FileIo/myfile/newfile/a.txt");
        int c1= fr.read();
        System.out.println(c1);
        int c2= fr.read();
        System.out.println(c2);
        
   }
}

其中read方法返回的是读取的字符对应的十进制数,如果是英文字符或者数字等可以被ascii码编码的返回的就是

ascii码,其余返回其存储的二进制数对应的十进制。

package FileIo.char_stream;
import java.nio.file.Paths;
import java.io.*;
public class MyFileReader {
    public static void main(String[] args) throws Exception {
        Reader fr = new FileReader("src/FileIo/myfile/newfile/a.txt");
        int c;
        while((c=fr.read())!=-1){
            char ch=(char)c;
            System.out.print(ch);
        }
   }
}

这样可以读取文本中的所有内容并输出,但性能较差

我们对其进行更改;

package FileIo.char_stream;
import java.nio.file.Paths;
import java.io.*;
public class MyFileReader {
    public static void main(String[] args) throws Exception {
        try(Reader fr = new FileReader("src/FileIo/myfile/newfile/a.txt")){
            char[] buffer=new char[1024];
            int len;
            while((len=fr.read(buffer))!=-1){
                String rs=new String(buffer,0,len);
                System.out.print(rs);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
        }
}

如果我们用字符数组去存储读到的结果,read返回的结果是读到的字符长度

此时的读取方式可以避免乱码,读取后会自动关闭管道,性能优秀

2.7 文件字符输出流FileWriter

package FileIo.char_stream;
import java.io.*;
public class MyFileWriter {
    public static void main(String[] args) {
        try (Writer writer=new FileWriter("src/FileIo/myfile/newfile/a.txt",true)){
            writer.write(49);
            writer.write("\r\n");
            writer.write("aaaa");
            writer.write("abcd",0,2);//限定写入的范围
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

2.8字节缓冲流

我们先前学习的FileInputStream FileReader因为其性能不够强大,被称为原始流或者低级流

而对其进行优化的缓冲流被称为包装流,或者叫处理流

其优化原理就是在主存开设输入缓冲区和输出缓冲区,减少主存和外部存储的数据交换次数,从而达到性能优化的

作用

package FileIo.char_stream;
import java.io.*;
import java.util.Objects;

public class Mybufferin{
    public static void main(String[] args)  {
        try (InputStream  is=new FileInputStream("src/FileIo/myfile/newfile/a.txt");
             InputStream bi =new BufferedInputStream(is);
             OutputStream os=new FileOutputStream("src/FileIo/myfile/newfile/b.txt");
             OutputStream bo =new BufferedOutputStream(os);){
            byte[] bytes = new byte[1024];
            int len;
            while ((len = bi.read(bytes)) != -1) {
                os.write(bytes,0,len);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }

    }
}

字节流的缓冲池大小为8KB ,字符流的缓冲流大小为8K字符

对于字符流也是一样,将原始流包起来就可以使用,功能上没有变化,只是性能上的提升

但对于缓冲字符输入流,新加了按行读取的方法readLine

对于缓冲字符输出流,新加了换行的方法newLine

注意,由于使用了子类的新方法,所以我们在这里不应该使用多态。

package FileIo.char_stream;
import java.nio.file.Paths;
import java.io.*;
public class Mybufferin {
    public static void main(String[] args) throws Exception {
        try(Reader fr = new FileReader("src/FileIo/myfile/newfile/a.txt");
            BufferedReader bfr=new BufferedReader(fr);
            Writer ou=new FileWriter("src/FileIo/myfile/newfile/b.txt");
            BufferedWriter bou =new BufferedWriter(ou);){
            String line=null;
            while ((line=bfr.readLine())!=null){
                bou.write(line);
                bou.newLine();
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

2.9 字符输入转换流

当我们读入的字符编码方式和我们java选择的编码方式不同时,读取的字符就会乱码,这时候就需要使用字符输入

转换流, 即InputStreamReader

该字符输入转换流的解决思路为:先获取文件的原始字节流,再将其按真是的字符集编码转换成字符输入流

package FileIo.char_stream;

import java.io.*;

public class MyInputStreamReader {
    public static void main(String[] args) {
        try(
                InputStream is= new FileInputStream("src/FileIo/myfile/newfile/a.txt");
                Reader isr= new InputStreamReader(is,"GBK");
                BufferedReader bis =new BufferedReader(isr);
        ){
            String line ="";
            while((line=bis.readLine())!=null){
                System.out.println(line);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

其中InputStreamReader是一个字符输入流,但并不是我们先前的FileReader

3.0 字符输出转换流

同样,字符输出转换流可以指定输出编码方式

package FileIo.char_stream;

import java.io.*;

public class MyInputStreamReader {
    public static void main(String[] args) {
        try(
                InputStream is= new FileInputStream("src/FileIo/myfile/newfile/a.txt");
                Reader isr= new InputStreamReader(is,"GBK");
                BufferedReader bis =new BufferedReader(isr);
                OutputStream ou=new FileOutputStream("src/FileIo/myfile/newfile/b.txt");
                Writer our=new OutputStreamWriter(ou,"GBK");
                BufferedWriter bou=new BufferedWriter(our);
        ){
            String line ="";
            while((line=bis.readLine())!=null){
                bou.write(line);
                bou.newLine();
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

3.1 打印流

为了简化输出,java中还给出了打印流,打印流分为字节打印流PrintStream和字符打印流PrintWriter两种

使用打印流非常方便和高效,所以用打印流更方便一点,也是官方更推荐的io方法,相比我们之前的缓冲流简便不

package FileIo.char_stream;

import java.io.PrintStream;

public class MyPrint {
    public static void main(String[] args) {
        try(
                PrintStream printStream=new PrintStream("src/FileIo/myfile/newfile/b.txt");
        ){
            printStream.println("你好锕");
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

需要注意,字节打印流只会选择系统默认的编码方式,而字符打印流更便捷一点,可以选择任意的编码方式

但默认使用打印流是直接对原文件进行覆盖,而不是进行追加,想要进行追加需要将其包装到低级管道中。

package FileIo.char_stream;

import java.io.FileOutputStream;
import java.io.PrintStream;

public class MyPrint {
    public static void main(String[] args) {
        try(
                PrintStream printStream=new PrintStream(new FileOutputStream("src/FileIo/myfile/newfile/b.txt",true));
        ){
            printStream.println(true);
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

但注意,想要将低级管道转换到字节打印流只能选择字节流进行转换,而不能选择字符流

使用打印流还可以实现输出重定向功能

正常我们输出是输出给控制台,我们现在想要控制输出的对象,可以这样操作

package FileIo.char_stream;

import java.io.FileOutputStream;
import java.io.PrintStream;

public class MyPrint {
    public static void main(String[] args) {
        try(
                PrintStream printStream=new PrintStream(new FileOutputStream("src/FileIo/myfile/newfile/b.txt",true));
        ){
            System.setOut(printStream);
            System.out.println("你好你好");
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

这样我们所有的System.out.println();都写到了我们指定的文件中

3.2 特殊数据流

这两个流允许将数据和类型一并写出去,分为特殊数据输入流和特殊数据输出流,这两个流都是字符流

通过特殊数据输出流写入的数据不是采用utf-8也不是采取gbk编码,其目的并不是给用户看所以无法正确解码,使

用特殊数据输入流可以读入我们先前写入的数据,注意,我们写入的顺序要和我们读入的顺序相同,不能跳着读

下面是一个例子:

package FileIo.char_stream;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class DataStream {
    public static void main(String[] args) {
        try(
                DataOutputStream ou=new DataOutputStream(new FileOutputStream("src/FileIo/myfile/newfile/b.txt"));
                DataInputStream in =new DataInputStream(new FileInputStream("src/FileIo/myfile/newfile/b.txt"));
                ){
            ou.write(1);
            ou.writeBoolean(true);
            ou.writeDouble(1.2);
            ou.writeByte(69);
            ou.writeUTF("你好");
            int i=in.read();
            System.out.println(i);
            Boolean b=in.readBoolean();
            System.out.println(b);
            Double c=in.readDouble();
            System.out.println(c);
            Byte d=in.readByte();
            System.out.println(d);
            String s=in.readUTF();
            System.out.println(s);
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

3.3 序列化

java中肯定也有序列化和反序列化操作,序列化就是将java对象变成数据形式存储下来,反序列化就是读取对象序

列化后的数据形成对象

反序列化流即,ObjectInputStream继承于字节输入流,序列化流ObjectOutputStream 继承于字节输出流

我们这里给出一个学生类,将对此类的对象进行序列化和反序列化操作

package FileIo.char_stream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class NewStudent implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String email;
}

注意,想要实现序列化和反序列化,被操作的类必须继承序列化接口,也就是上面的Serializable

虽然这个接口里什么都没有,是一个空接口,但作为标志告诉jvm虚拟机需要用序列化的形式存储

下面是反序列化和序列化操作:

package FileIo.char_stream;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class ObjectStream {
    public static void main(String[] args) {
        try(ObjectOutputStream ou=new ObjectOutputStream(new FileOutputStream("src/FileIo/myfile/newfile/b.txt"));
            ObjectInputStream in=new ObjectInputStream(new FileInputStream("src/FileIo/myfile/newfile/b.txt"));
        ){
            NewStudent student =new NewStudent("BOB",12,"28878989@QQ.COM");
            ou.writeObject(student);
            NewStudent student1=(NewStudent) in.readObject();
            System.out.println(student1.getName());
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

对于我们的一些敏感属性,比如密码,如果序列化后文件泄露则很容易暴露出去,我们控制其不进行序列化,其实

也很简单,用transient修饰一下我们想要不序列化的属性即可,这样在序列化时不会将此属性进行序列化,反序

列化后该属性为null

例如:

private transient String email;

如果我们有多个对象需要序列化,我们直接将对象放在ArrayList里面,对ArrayList对象直接进行序列化即可。

3.4 使用io框架进行文件操作

我们io操作如果还需要这样慢慢写未免也太麻烦了,所以可以使用已经写好的io框架进行操作,这里使用

conmmons-io

package FileIo.char_stream;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class ioframe {
    public static void main(String[] args) {
        try {
            FileUtils.copyFile(new File("src/FileIo/myfile/newfile/a.txt"),new File("src/FileIo/myfile/newfile/c.txt"));
            // FileUtils.copyDirectory(new File(""),new File("")); 拷贝文件夹
            // FileUtils.deleteDirectory(); 删除文件夹
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.5 复制文件夹和删除文件夹

虽然FileUtils中提供了直接操作文件夹的方法,但我们这里不使用,自己写的练下手

复制文件夹:

package FileIo.char_stream;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class CopyDir {
    public static void main(String[] args) throws IOException {
        copyDir(new File("D:\\java\\code\\test1"),new File("D:\\java\\code\\test2"));
    }
    public static void copyDir(File sfile,File dfile) throws IOException {
        if(sfile==null||dfile==null||!sfile.exists()||sfile.isFile()||dfile.isFile()||!dfile.exists()){
            return ;
        }
        File deNameDir=new File(dfile,sfile.getName());
        deNameDir.mkdirs();

        File [] files=sfile.listFiles();
        if(files==null||files.length==0){
               return ;
        }
        for (File file : files) {
            if(file.isFile()){
                FileUtils.copyFile(file,new File(deNameDir,file.getName()));
            }
            else {
                copyDir(file,deNameDir);
            }
        }
    }
}

删除文件夹

package FileIo.char_stream;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class DeleteDir {
    public static void main(String[] args) throws IOException {
        deleteDir(new File("D:\\java\\code\\test2"));
    }
    public static void deleteDir(File sfile) throws IOException {
        if(!sfile.exists())
            return ;
        File[] filelist=sfile.listFiles();
        if(filelist.length==0||filelist==null){
            FileUtils.delete(sfile);
            return;
        }

        for(File file : filelist){
            if(file.isFile()){
                FileUtils.delete(file);
            }
            else{
                deleteDir(file);
                FileUtils.delete(file);
            }
        }
        FileUtils.delete(sfile);

    }
}

到这里java的stream 和io知识点就结束了

完结撒花

posted @ 2025-04-03 17:32  折翼的小鸟先生  阅读(22)  评论(0)    收藏  举报