IO

File类只能操作属性,无法操作文件里面的数据

IO

InputStream——OutputStream

从流向上划分:输入流/输出流

从操作内容上来划分:字节流 字符流

操作文件里面的数据:

  1. 读取文件里面的数据 --->read 输入流
  2. 将数据写入文件 –>write 输出流

1.字节流

计算机里面所有文件内存存储都是以字节的形式进行存储,也就是二进制文件。因此字节流就是万能流,可以操作计算机里面任意类型的文件

1.1 字节输入流InputStream

InputStream是一个抽象类,常用子类:

FileInputStream();//文件字节输入流
BufferedInputStream();//高效字节输入流
DataInputStream();//数据输入流
ObjectInputStream();//对象输入流

InputStream里面的方法:

abstract int read() 
int read(byte[] bytes);  读取字节数组长度的字节内容

FileInputStream 效率低

FileInputStream(File file)
FileInputStream(String name)

read(); 读取一个字节内容

private static void demo1() {
    //读取指定文件数据:文件必须存在
    String filePath="1129\\1129.iml";
    //创建字节输入流对象:
    InputStream inputStream=null;
    try {
         inputStream=new FileInputStream(filePath);
         //读取文件数据
        int len;
        while ((len=inputStream.read())!=-1){
            System.out.print((char) len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(inputStream!=null){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
//但是上面大部分代码都是用于处理异常,我们换下面的方式:
private static void demo2() {
        //jdk1.7之后提供了一种更加优雅的方式释放资源(自动释放资源)  try...with...resource
        try (
                //创建的对象(必须实现Closeable接口)
                InputStream stream = new FileInputStream("1129\\1129.iml");
        ) {
            //读取数据
            int read;
            while ((read = stream.read()) != -1) {
                System.out.print((char) read);
            }

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

read(byte[] bytes); 读取字节数组长度的字节内容

//让他来读取给定长度(一般是1024的整数倍)的字节内容,读取的内容会放到该数组里面,想看数据的话得看读取的字节数组
private static void demo3() {
        try(
                InputStream stream=new FileInputStream("1129\\src\\exercise.java");
                ){
              byte[] bytes = new byte[10];
            //这里我给了10,如果文件里面的字节小于10,读取字节数也会小于10
            // 文件长度超过10,也会只读10个
            System.out.println(stream.read(bytes));
            System.out.println(new String(bytes));
            //将字节数组转字符串输出,如果是中文,有可能会出现乱码:import jav
            System.out.println(stream.read(bytes));
           //该字节数组已经装满了,如果再去读byte.length长度个,读取内容会覆盖原先的字节数组
        }catch (IOException e){
            e.printStackTrace();
        }
    }

read(byte[] bytes,int startIndex,int lenth) 从起始位置读取数组长度的内容,读取指定字节长度

private static void demo4() {
    try(
            InputStream stream=new FileInputStream("1129\\src\\exercise.java");
            ){
        byte[] bytes=new byte[1024];
        stream.read(bytes,0,1024);
        System.out.println(new String(bytes));
    }catch (IOException e){
        e.printStackTrace();
    }
}

1.2.字节输出流

OutputStream:将数据内容写入文件中 也就是write,常用子类

FileOutputStream();//文件字节输出流
BufferedOutputStream();//高效字节输出流
DataOutputStream();//数据输出流
ObjectOutputStream();//对象输出流

outputstream的构造:

FileOutputStream(File file)
FileOutputStream(File file, boolean append)//给定是拼接原文件还是覆盖源文件,默认覆盖
FileOutputStream(String name)
FileOutputStream(String name, boolean append) 

FileOutputStream 效率低

private static void demo5() {
    try (
            //文件可以不存在,构造方法里面可以加一个布尔来判断是覆盖原文件还是拼接原文件
            OutputStream stream = new FileOutputStream("1129\\src\\a.txt");
    ) {
        stream.write(65);
        stream.write(49);
        stream.write(52);
        stream.write("abdcfe".getBytes());
        stream.write('\n');
        stream.write("学习使我快乐!".getBytes());//给汉字的时候注意编码格式

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

模拟用户上传头像

private static void demo6() {
    File filePath = new File("E:/肖战.jpg");
    File filePath1 = new File("D:/肖战.jpg");
    String targetPath = "1129\\src\\image\\" + LocalDate.now().toString();
    try (
            InputStream inputStream = new FileInputStream(filePath1);
            OutputStream outputStream =
        new FileOutputStream(targetPath + UUID.randomUUID());
        //由于图片名称可能会相同,下载路径一致的时候可能会被覆盖,这里我们用UUID的随机数字符来拼接
    ) {
        int len;
        while ((len = inputStream.read()) != -1) {
            outputStream.write(len);
        }
        System.out.println(targetPath);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
//读取大文件的时候,这种读取速度是很慢的:
private static void demo7() {
        String filePath="E:\\第一阶段课堂笔记(JavaSE).nyf";
        String targetPath = "1129\\src" + LocalDate.now().toString();
        try(
                InputStream inputStream = new FileInputStream(filePath);
                OutputStream outputStream = new FileOutputStream(targetPath + UUID.randomUUID());
        ) {
            int len;

            while ((len=inputStream.read())!=-1){
                outputStream.write(len);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
//读取速度很慢,我等了一分钟也没读完

但是上面的读写速度是比较慢的,如果需要传大文件的时候,一个读一个写,这样的速度是很慢的,因此我们可以用带参的write(byte[] bytes)来加快写入文件的速度:

private static void upLoadFile(String sourceFilePath) {
        Objects.requireNonNull(sourceFilePath);
        File target = new File(TARGET_PATH, LocalDate.now().toString());
        if (!target.exists()) {
            target.mkdirs();//判断文件目录是否存在,不存在去创建
        }
        String filename=UUID.randomUUID().toString()+
            sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator)+1);
       File targetPath=new File(target,filename);//创建目标文件路径和目标文件名
        try (
                InputStream inputStream = new FileInputStream(sourceFilePath);
                OutputStream outputStream = new FileOutputStream(targetPath);
        ) {
            int len;
            byte[] bytes = new byte[1024 * 100];//一次写入这样多的字节
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);//如果不加这一行最后会多读取1024个字节
            }
            System.out.println("文件上传成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

上面,file来操作文件夹,而IO来操作文件数据

高效字节流

BufferedInputStream:高效字节输入流 read

BufferedOutputStream:高效字节输出流 write

底层自动提供了字节数组来缓冲,默认长度为8192

private static void demo1() {
    try{
        BufferedOutputStream stream=new BufferedOutputStream(new FileOutputStream("day1202\\src\\a.txt"));
        stream.write(78);
        stream.write("hello".getBytes());
        //写入的数据都放在缓存区,因为此时的文件不满8192的长度,需要释放资源,或者刷新
        //stream.flush();
        stream.close();//底层自动调用flush()
    }catch (IOException e){
        e.printStackTrace();
    }
}

若用该方法进行io,并且在代码里面又加了一个临时缓冲区,效率会更快,两个缓存区一起使 ,因此推荐使用自己写一个缓冲区,然后再与BufferedOutputStream来一起使用 ,效率会很快。

2.字符流

reader:字符输入流

writer:字符输入流

字符流不能操作其他的二进制文件,主要用于操作纯文本文件(txt、java、html.etc) ,对于有样式的文本文件,后期会用POI来进行操作。底层:字节流+字符集,它可以写入一串

public void write(String str, int off, int len) throws IOException {
    se.write(str, off, len);
}

reader常用子类:

BufferedReader
FileReader
InputStreamReader

writer常用子类:

BufferedWriter
FileWriter
OutputStreamWriter

用法跟字节流差不多,直接拿来用:

上传文件:

private static void demo2() {
    String targetPath="day1202\\src";
    File filePath=new File(targetPath,LocalDate.now().toString());
    if (!filePath.exists()) {
        System.out.println("目标路径目录不存在,已为您创建目录");
        filePath.mkdirs();
    }
    String path= "day1202\\src\\a.txt";
    File sourcePath=new File(path);
    String name=path.substring(path.lastIndexOf('\\')+1);
    File targetFile=new File(filePath,name);//得到文件(文件名+文件类型)
    try (
            Reader reader = new FileReader(sourcePath);
            Writer writer=new FileWriter(targetFile)//参数只能是文件,不能是文件夹
    ) {
        int read;
        while ((read = reader.read())!=-1){
            writer.write(read);
            System.out.println("上传成功");
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

但是它用来操作其他文件的话得到的是一个破损文件,是打不开的,只能操作文本文件

高效字符流

装饰了基本字符流的读写功能,底层自带缓冲(带一个长度为8192的字符数组) ,他里面有一个方法:String readLine() //一次读取一行返回值类型为string

private static void demo3() {
        List<User> users=new ArrayList<>();
        String sourPath = "day03\\src\\bean\\a.txt";
        try (
                BufferedReader reader=new BufferedReader(new FileReader(sourPath))
        ) {
            byte[] bytes=new byte[1024*10];
            String userInfor;
            while ((userInfor=reader.readLine())!=null){
                String[] split = userInfor.split("-");
                users.add(new User(Integer.parseInt(split[0]),split[1],split[2],Integer.parseInt(split[3])));
            }
            users.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

序列化流:

也称为对象流,读写对象数据。被读写的对象类必须实现serializable接口,因此以后的大部分实体类都需要实现Serializable接口,表示可以在网络上进行数据传输

public class User implements Serializable {
    private static final long serialVersionUID = -8154682815263956903L;
    private Integer id;
    private String name;
    private transient String pwd;//防止序列化
    private int age;
}

ObjectOutputStream对象输出流 (序列化操作)

private static void demo5() {
    User user=new User(23,"liku","password",25);
    try (
            ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream("day03\\src\\bean\\a.txt"))
    ) {
        stream.writeObject(user);//将对象写入
        //如果该类没实现序列化接口,会报下面异常
        //java.io.NotSerializableException: bean.User
        System.out.println("序列化成功"+user.hashCode());
        //我们将序列化成功之后的hashcode输出来看一下:序列化成功856419764
    } catch (IOException e) {
         e.printStackTrace();
    }
}

ObjectInputStream对象输入流(反序列化操作)

对于java文件只要修改一次,都需要重新编译,也就是说源文件修改之后如果不重新编译,反序列化之后的版本号是不一致的,类似深克隆,class的版本号serialVersionUID就会发生改变
解决该方法的操作:
1、重新序列化,然后反序列化
2、固定版本ID:idea-->setting-->editor-->inspections-->搜索serial-->勾选已经实现序列化类但是没有生成serialVersionUID
​ //3、对于一些需要加密的数据(密码,身份证号等)变量可以用transient来修饰,它可以保证变量不被序列化成功

private static void demo6() {

    try(
            ObjectInputStream stream=new ObjectInputStream(new FileInputStream("day03\\src\\bean\\a.abc"))
            ){
       User user=(User) stream.readObject();
        System.out.println("反序列化成功! "+user.hashCode());
        //反序列化成功! 1989780873
        System.out.println(user.toString());
        //当我把对象以字符串来输出的时候,报了异常:java.io.InvalidClassException
        //对于java文件只要修改一次,都需要重新编译,也就是说源文件修改之后如果不重新编译,反序列化之后的版本号是不一致的,类似深克隆,class的版本号serialVersionUID就会发生改变
        //解决该方法的操作:
        // 1、重新序列化,然后反序列化
        //2、固定版本ID:
        // idea-->setting-->editor-->inspections-->搜索serial-->勾选已经实现序列化类但是没有生成serialVersionUID
        //3、对于一些需要加密的数据(密码,身份证号等)变量可以用transient来修饰,它可以保证变量不被序列化成功
    }catch (IOException e){
        e.printStackTrace();
    } catch (ClassNotFoundException e) {//jvm找不到当前类的class文件
        e.printStackTrace();
    }
}

转换流

字节流与字符流之间的相互转换,常用:字节流转换成字符流:用于操作网络数据

InputStreamReader(InputStream in)    字节转字符
OutputStreamWriter(OutputStream out)   字符转字节

1、字节流转字符流:

抓取网站上的内容到本地文件

public static void main(String[] args) {
        String path = "https://read.qidian.com/chapter/jE-Qgi-8d_j-JlVC31J8Aw2/BZmMwrZww9BMs5iq0oQwLQ2/";
        demo7(path);
    }

    private static void demo7(String path) {
        try (
                BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(path).openStream()));
                BufferedWriter writer = new BufferedWriter(new FileWriter("day03\\src\\novel\\a.txt"))
        ) {
            String s;
            while ((s = reader.readLine()) != null) {
                if(s.contains(" <p>  “光之治愈……”<p>")){
                    writer.write(s.replaceAll("<p>","\n"));
                }
            }
            System.out.println("下载成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

将读取的内容在控制台打印输出

private static void demo8() {
    try (
            BufferedReader reader = new BufferedReader(new FileReader("day03\\src\\novel\\a.txt"));
            //读取文件内容,在控制台上打印输出
            //System.out.println: PrintStream extends FilterOutputStream属于字节流,字节输出流,将内容写到控制台
            //因此出现流的转换:字节流-->字符流
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out))
    ) {
        String line;
        while ((line = reader.readLine()) != null) {
            writer.write(line);
            writer.newLine();
        }

    } catch (IOException e) {

    }
}

其他流:

数据流:

一般用于网络数据的传输

DataInputStream数据输入流 DataOutputStream 数据输出流

只有由数据流自己写入的数据,才可以读出数据,否则会出现乱码

private static void demo4() {
    //1082
    //liku
    //李四
    //adminwangwu
    //true
    try (
            DataInputStream stream = new DataInputStream(new FileInputStream("day03\\src\\bean\\a.txt"));

    ) {
        System.out.println(stream.readInt());
        System.out.println(stream.readUTF());
        System.out.println(stream.readUTF());
        System.out.println(stream.readUTF());
        System.out.println(stream.readChar());
        //java.io.EOFException:end of file exception
        //因为文件里面的内容不是由DataOutputStream写入的,因此会出现问题
    } catch (IOException e) {
        e.printStackTrace();
    }
}

重要方法:readUTF()writeUTF() 读写汉字

Properties

Map接口的一个是实现类 继承了HashTable(里面也有map的方法:put,get,size,values,isEmpty,entryset,getOrDefault,foreach,remove,replace,clear等方法) 该类表示一组持久的属性

Properties()  //创建一个没有默认值的空属性列表
Properties(Properties defaults)
    
//一般不利用map的get,put来操作元素,用它自带的
Object setProperty(String key, String value) //赋值    
String getProperty(String key)     //取值
String getProperty(String key, String defaultValue) //可以防止空指针

void load(InputStream inStream)//读取数据
void load(Reader reader)     

读取核心配置文件(文件名.properties)里面的数据

private static void demo9() {
    //我创建了配置文件路径为:day03\src\bean\b.txt 里面给了一组数据:
    // username=张三
    //password=password

    Properties properties=new Properties();
    try {
        //读取直接用load
        properties.load(new FileInputStream("day03\\src\\bean\\b.txt"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    //取值
    System.out.println(properties.getProperty("username"));//出现乱码:å¼ ä¸‰
    System.out.println(properties.getProperty("password"));//password

}

注意配置文件里面的数据必须是key=value的格式,否则会读不到值,在配置文件里面一般都是用英文来写的,注释用#号 ,上面用户名出现乱码,因为properties的编码不一定是utf-8,因此需要在idea里面去修改一下配置:(改完之后重启)

setting---》search File Encoding---》将Default Properties的编码格式:utf-8 并且将后面的transparent native to ascII conversion ✔

获取配置文件的方式:【b.properties在bean下面,当前位置为Test2.class】,因此需要通过指定路径才能找到文件

 //jvm加载的是class文件 也就是out文件里面的class文件
 //加载核心配置文件,需要加载编译路径下的核心配置文件 也就是:某类.class(该类必须是跟配置文件在同一个项目下)
Properties p = new Properties();
try {
    //指定编译文件的路径  ../表示回退到上一级目录
    p.load(Test2.class.getResourceAsStream("../bean/b.properties"));
    System.out.println(p);
} catch (IOException e) {
    e.printStackTrace();
}

但是上面的方式需要回退,至于回退几级还得看文件路径,过于麻烦,当我们知道配置文件相对当前文件(自己写的,不是jdk的文件)的路径,我们换下面的方式:

当前文件.class:获取当前文件的编译文件

getClassLoader():得到类加载器

getResourceAsStream("文件相对路径"):文件相对当前文件的路径

当前文件.class.getClassLoader().getResourceAsStream("bean/b.properties"));

private static void demo3() {
    Properties p = new Properties();
    try {   p.load(Test2.class.getClassLoader().getResourceAsStream("bean/b.properties"));
        System.out.println(p);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

一般加载文件都是放在工具类里面,并且只加载一次

posted @ 2022-12-02 23:00  Liku007  阅读(291)  评论(0编辑  收藏  举报