Java基础--IO流

File类

引入:在java程序中操纵文件/目录?怎么办?

java程序最擅长的就是操作对象,盘符上的文件/目录,将它的各种信息进行了封装,封装为一个对象,

盘符上的文件---》封装为对象---》对象属于File类的对象--》有了这个对象,我们程序就可以直接操纵这个对象,通过这个对象获取文件的各种信息,还可以对文件进行创建 ,删除。

对文件进行操作

 //将文件封装成一个File类对象
        File f = new File( "E:\\Java\\test.txt");
        File f1 = new File( "E:"+File.separator+"Java"+File.separator+"test.txt");//建议使用->File.separator属性帮我们获取当前操作系统的路径拼接符号
        //在windows,dos下,系统默认用“\”作为路径分隔符 ,在unix,url中,使用“/”作为路径分隔符。
//常用方法
        System.out.println("文件是否可读:"+f.canRead());
        System.out.println("文件是否可写:"+f.canWrite());
        System.out.println("文件的名字:"+f.getName());
        System.out.println("上级目录:"+f.getParent());
        System.out.println("是否是一个目录:"+f.isDirectory());
        System.out.println("是否是一个文件:"+f.isFile());
        System.out.println("是否隐藏:"+f.isHidden());
        System.out.println("文件的大小:"+f.length());
        System.out.println("是否存在:"+f.exists());
//        if(f.exists()){
//            f.delete();    //如果存在,删除这个文件
//        }else{
//            f.createNewFile();  //如果不存在,创建这个文件
//        }
        System.out.println(f == f1);//比较两个对象的地址
        System.out.println(f.equals(f1));//比较两个对象对应的文件的路径

//跟路径相关的:
        System.out.println("绝对路径:"+f.getAbsolutePath());
        System.out.println("相对路径:"+f.getPath());
        System.out.println("toString:"+f.toString());
        System.out.println("----------------------");
        File f5 = new File("demo.txt");
        if(!f5.exists()){
            f5.createNewFile();
        }
        //绝对路径指的就是:真实的一个精准的,完整的路径
        System.out.println("绝对路径:"+f5.getAbsolutePath());
        //相对路径:有一个参照物,相对这个参照物的路径。
        //在main方法中,相对位置指的就是:D:\IDEA_workspace\TestJavaSE
        //在junit的测试方法中,相对路径指的就是模块位置
        System.out.println("相对路径:"+f5.getPath());
        //toString的效果永远是  相对路径
        System.out.println("toString:"+f5.toString());

对目录进行操作

对文件操作的都可以对目录进行,除此之外还有特别的方法:

//跟目录相关的方法
        File f2= new File("E:\\a\\b\\c"); //将目录封装为File类的对象:
       // f2.mkdir();   //创建单层目录
        // f2.mkdirs();  //创建多层目录

        //删除:如果是删除目录的话,只会删除一层,并且前提:这层目录是空的,里面没有内容,如果内容就不会被删除
        f2.delete();

        //查看:(遍历目录)
        String[] list = f.list();//文件夹下目录/文件对应的名字的数组
        for(String s:list){
            System.out.println(s);
        }
        System.out.println("--------------");
        File[] files = f.listFiles();  //相比上面那个作用更加广泛
        for (File file:files) {
            System.out.println(file.getName()+","+file.getAbsolutePath());

        }

IO流

引入:

1)File类:封装文件/目录的各种信息,对目录/文件进行操作,但是我们不可以获取到文件/目录中的内容。

2)I/O : Input/Output的缩写,用于处理设备之间的数据的传输

4)IO流的体系结构

案例:通过Java程序完成文件的复制

功能分解1:文件->程序:FileReader

功能分解2:程序->文件:FileWriter

功能分解3:利用FileReader,FileWriter文件复制

  public static void main(String[] args) throws IOException {
        //1.有一个源文件
        File f1 = new File("d:\\Test.txt");
        //2.有一个目标文件:
        File f2 = new File("d:\\Demo.txt");
        //3.搞一个输入的管 怼到源文件上:
        FileReader fr = new FileReader(f1);
        //4.搞一个输出的管,怼到目标文件上:
        FileWriter fw = new FileWriter(f2);
        //5.开始动作:
        //方式1:一个字符一个字符的复制:
        /*int n = fr.read();
        while(n!=-1){
            fw.write(n);
            n = fr.read();
        }*/
        //方式2:利用缓冲字符数组:
        /*char[] ch = new char[5];
        int len = fr.read(ch);
        while(len!=-1){
            fw.write(ch,0,len);//将缓冲数组中有效长度写出
            len = fr.read(ch);
        }*/
        //方式3:利用缓冲字符数组,将数组转为String写出。
        char[] ch = new char[5];
        int len = fr.read(ch);
        while(len!=-1){
            String s = new String(ch,0,len);
            fw.write(s);
            len = fr.read(ch);
        }
        //6.关闭流:(关闭流的时候,倒着关闭,后用先关)
        fw.close();
        fr.close();
    }
  • 利用try-catch处理异常

  • FileInputStream与 FileOutputStream读取文件:

    细节1

    文件是utf-8进行存储的,所以英文字符 底层实际占用1个字节但是中文字符,底层实际占用3个字节。

细节2
​ 如果文件是文本文件,那么就不要使用字节流读取了,建议使用字符流
细节3
​ read()读取一个字节,但是你有没有发现返回值是 int类型,而不是byte类型?
​ read方法底层做了处理,让返回的数据都是“正数”
​ 就是为了避免如果字节返回的是-1的话,那到底是读入的字节,还是到文件结尾呢。

//利用字节流读取非文本文件:(以图片为案例:)--》一个字节一个字节的读取:
public static void main(String[] args) throws IOException {
    //1.有一个源文件
    File f = new File("E:\\Java\\笔记\\IO流.png");
    //2.把一个字节流输入管怼到源文件上去
    FileInputStream fis = new FileInputStream(f);

    //3.开始读取动作
    int count = 0; //定义一个计数器,因为字节数很多,用来计已经读入的字节数
    int n = fis.read();
    while(n!=-1) {
        count++;
        System.out.println(n);
        n = fis.read();
    }
    System.out.println(count);

    //4.关闭流
    fis.close();
}
//利用字节类型的缓冲数组:读取非文本文件
public static void main(String[] args) throws IOException {
    //1.有一个源文件
    File f = new File("E:\\Java\\笔记\\IO流.png");
    //2.把一个字节流输入管怼到源文件上去
    FileInputStream fis = new FileInputStream(f);

    //3.开始读取动作
    byte[] b = new byte[1024];   //字节数很多,可以一次读1024或者1024的倍数
    int len = fis.read(b);      //len指的就是读取的数组中的有效长度
    while (len != -1){
        for (int i = 0; i <len ; i++) {
            System.out.println(b[i]);
        }
        len = fis.read(b);
    }

    //4.关闭流
    fis.close();
}
   //功能:完成图片的复制
    public static void main(String[] args) throws IOException {
        //1.有一个源图片
        File f1 = new File("E:\\Java\\笔记\\IO流.png");
        //2.有一个目标图片
        File f2 = new File("E:\\Java\\笔记\\IO流1.png");

        //3.有一个输入的管道怼到源文件上
        FileInputStream fis = new FileInputStream(f1);
        //4.有一个输出的管道怼到目标文件上
        FileOutputStream fos = new FileOutputStream(f2);

        //5.开始复制:(边读边写)
        /*int n = fis.read();
        while(n != -1){
            fos.write(n);
            n = fis.read();
        }*/
        //利用缓冲数组
        byte[] b = new byte[1024*6];
        int len = fis.read(b);
        while(len != -1){
            fos.write(b,0,len);
            len = fis.read(b);
        }
        //6.关闭流(倒着关闭流,先用后关)
        fos.close();
        fis.close();
    }
}

想要完成上面的效果,单纯的靠FileInputStream,FileOutputStream是不可以完成的,这个时候就需要功能的加强,这个加强就需要引入新的流(在FileInputStream,FileOutputStream外面再套一层流):BufferedInputStream ,BufferedOutputStream. ----->处理流

缓冲字节流(处理流)---BufferedInputStream ,BufferedOutputStream

//利用BufferedInputStream ,BufferedOutputStream处理流
public static void main(String[] args) throws IOException {
    //1.有一个原图片
    File f1 = new File("E:\\Java\\笔记\\IO流.png");
    //2.有一个目标图片
    File f2 = new File("E:\\Java\\笔记\\IO流1.png");
    //3.有一个输入的管 怼到 源文件
    FileInputStream fis = new FileInputStream(f1);
    //4.有一根输出的管 怼到目标文件
    FileOutputStream fos = new FileOutputStream(f2);

    //5.功能加强,在FileInputStream外面套一个管:BufferedInputStream:
    BufferedInputStream bis = new BufferedInputStream(fis);
    //6.功能加强,在FileOutputStream外面套一个管:BufferedOutputStream:
    BufferedOutputStream bos = new BufferedOutputStream(fos);

    //7.开始动作
    byte[] b = new byte[1024*6];
    int len = bis.read(b);
    while(len != -1){
       bos.write(b,0,len);
       /* bos.flush(); 底层已经帮我们做了刷新缓冲区的操作,不用我们手动完成:底层调flushBuffer()*/
       len = bis.read(b);
   }
    //8.关闭流(后用先关)
    //如果处理流包裹着节点流的话,那么其实只要关闭高级流(处理流),那么里面的字节流也会随之被关闭。
    bos.close();
    bis.close();
    /*fos.close();
    fis.close();*/
}
  • 对比非文本文件复制的三种方法的效率:

    测一下三种方法的时间差

缓冲字符流(处理流)---BufferedReader与BufferedWriter完成文本文件的复制

//利用缓冲字符流---BufferedReader与BufferedWriter完成文本文件的复制
public static void main(String[] args) throws IOException {
    //1.有一个源文件
    File f = new File("E:\\Java\\笔记\\test.txt");
    //2.有一个目标文件
    File f1 = new File("E:\\Java\\笔记\\test1.txt")
    //3.需要一个管 怼到 源文件
    FileReader fr = new FileReader(f);
    //4.需要一根管  怼到目标文件
    FileWriter fw = new FileWriter(f1);

    //5.套一根管在输入字符流外面
    BufferedReader br = new BufferedReader(fr);   //处理流一定要套在一个流上使用
    //6.套一根管在输出字符流外面
    BufferedWriter bw = new BufferedWriter(fw);

    //7.开始动作:
    //方式一:读取一个字符,输出一个字符
    /*int n = br.read();
    while (n !=-1){
        bw.write(n);
        n = br.read();
    }*/
    //方式二:利用缓冲数组
    /*char[] ch = new char[20];
    int len = br.read(ch);
    while(len != -1){
        bw.write(ch,0,len);
        len = br.read(ch);
        }*/
    //方式三:读取String
    String str = br.readLine();  //每次从文本文件中读取一行,返回字符串
    while(str != null){
        bw.write(str);
        //在文本文件中应该再写出一个换行:
        bw.newLine(); //新起一行
        str = br.readLine();
    }
    
    //8.关闭流
    bw.close();
    br.close();

}

转换流--将字节流和字符流进行转换

属于 字节流还是字符流?属于字符流(看后缀)

InputStreamReader :字节输入流 ---》字符的输入流
OutputStreamWriter : 字符输出流 --》字节的输出流

public static void main(String[] args) throws IOException {
    //转换流-InputStreamReader:将输入的字节流转换为输入的字符流,然后完成文件--》程序
    //1.有一个文件
    File f = new File("E:\\Java\\笔记\\test.txt");
    //2.需要一个输入的字节流接触文件:
    FileInputStream fis = new FileInputStream(f);

    //3.加入一个转换流,将字节流转换为字符流:(转换流属于一个处理流)
    //将字节转换为字符的时候,需要指定一个编码,这个编码跟文件本身的编码格式统一,否则乱码
    //InputStreamReader isr = new InputStreamReader(fis,"utf-8");
    //不写对应的编码即默认为当前编译器的编码
    InputStreamReader isr = new InputStreamReader(fis);
    //4.开始动作,将文件中的内容显示在控制台
    char[] ch = new char[20];
    int len = isr.read(ch);
    while(len !=-1){
        System.out.print(new String(ch,0,len));  //将缓冲数组转为字符串在控制台上打印出来
        len = isr.read(ch);
    }

    //5.关闭流
    isr.close();

//InputStreamReader,OutputStreamWriter实现文本文件的复制
public static void main(String[] args) throws IOException {
    //1.有一个源文件
    File f1 = new File("E:\\Java\\笔记\\test.txt");
    //2.有一个目标文件
    File f2 = new File("E:\\Java\\笔记\\demo.txt");

    //3.输入方向
    FileInputStream fis = new FileInputStream(f1);
    InputStreamReader isr = new InputStreamReader(fis,"utf-8");
    //4.输出方向
    FileOutputStream fos = new FileOutputStream(f2);
    OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

    //5.开始动作
    char[] ch = new char[20];
    int len = isr.read(ch);
    while(len != -1){
        osw.write(ch,0,len);
        len = isr.read(ch);
    }
    //6.关闭流
    osw.close();
    isr.close();
}
  • System类对IO流的支持

    System.in--->是个标准输入流

    System.out-->是个标准输出流

    Scanner的作用:扫描器->起扫描作用的,扫键盘的从这根管出来的数据

    public static void main(String[] args) throws IOException {
        //得到的是标准的输入流--->从键盘输入
        InputStream in = System.in;
        //调用方法:
        //int n = in.read();     //read方法等待键盘的录入
        //System.out.println(n);
    
        //以前案例:从键盘录入一个int类型的数据:
        //形象的理解:System.in管,这个管怼到键盘上去了,所以你从键盘录入的话,就从这个管到程序中了
        //Scanner的作用:扫描器:起扫描作用的,扫键盘的从这根管出来的数据
        /*Scanner sc = new Scanner(System.in);
        int i = sc.nextInt();
        System.out.println(i);*/
        //既然Scanner是扫描的作用,不一定非得扫 System.in进来的东西,还可以扫描其他管的内容:
        Scanner sc = new Scanner(new FileInputStream(new File("E:\\Java\\笔记\\test.txt")));
        while(sc.hasNext()){
            System.out.println(sc.next());
        }}
    
public static void main(String[] args) {
        //写到控制台
        PrintStream out = System.out;
        //调用方法
        out.print("你好1");//直接在控制台写出,但是不换行
        out.print("你好2");
        out.print("你好3");
        out.print("你好4");
        out.println("我是中国人1");//直接在控制台写出,并且换行操作
        out.println("我是中国人2");
        out.println("我是中国人3");
        out.println("我是中国人4");
        System.out.println("你是");
        System.out.print("中国人");
    }

练习:键盘录入内容输出到文件中

public static void main(String[] args) throws IOException {
    //1.准备输入方向
    InputStream in = System.in;   //属于字节流
    InputStreamReader isr = new InputStreamReader(in);   //字节流---》字符流
    BufferedReader br = new BufferedReader(isr);
    //2.准备输出方向
    //准备目标文件
    File f = new File("E:\\Java\\笔记\\demo.txt");
    FileWriter fw = new FileWriter(f);
    BufferedWriter bw = new BufferedWriter(fw);

    //3.开始动作
    String s = br.readLine();
    while(!s.equals("exit")){    //当字符串为exit时,退出
        bw.write(s);
        bw.newLine();   //文件中换行
        s = br.readLine();
    }
    //4.关闭流
    bw.close();
    br.close();
}

数据流:用来操作基本数据类型和字符串

DataInputStream:将文件中存储的基本数据类型和字符串 写入 内存的变量中
DataOutputStream: 将内存中的基本数据类型和字符串的变量 写到 文件中

//利用DataOutputStream向外写出变量:
public static void main(String[] args) throws IOException {
    //DataOutputStream:  将内存中的基本数据类型和字符串的变量 写出  文件中
    File f = new File("E:\\Java\\笔记\\demo.txt");
    FileOutputStream fos = new FileOutputStream(f);
    DataOutputStream dos = new DataOutputStream(fos);
    //把三条语句合成一条
    // DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("E:\\Java\\笔记\\demo.txt")));
    //向外将变量写到文件中
    dos.writeUTF("你好");
    dos.writeBoolean(false);
    dos.writeDouble(4.14);
    dos.writeInt(42);
    //关闭流
    dos.close();
}

发现:这个内容我们看不懂,是给程序看的,通过读取程序展示出来

所以下面我们开始读取的程序:

public static void main(String[] args) throws IOException {
    //DataInputStream:将文件中存储的基本数据类型和字符串  写入  内存的变量中
    File f = new File("E:\\Java\\笔记\\demo.txt");
    FileInputStream fis = new FileInputStream(f);
    DataInputStream dis = new DataInputStream(fis);
    //将文件中内容读取到程序中来
    System.out.println(dis.readUTF());
    System.out.println(dis.readBoolean());
    System.out.println(dis.readDouble());
    System.out.println(dis.readInt());

    //关闭流
    dis.close();

验证:那个文件,我们看不懂,程序看得懂
要求:写出的类型跟读入的类型 必须 要匹配!

对象流:ObjectInputStream,ObjectInputStream

---ObjectOutputStream 类 : 把内存中的Java对象转换成平台无关的二进制数据(我们看不懂的,程序能看懂),从而允许把这种二进制数据持久地保存在磁盘上,或通过网络将这种二进制数据传输到另一个网络节点。----》序列化
---用ObjectInputStream类 : 当其它程序获取了这种二进制数据,就可以恢复成原来的Java对象。----》反序列化

---用于存储和读取基本数据类型数据或对象的处理流。
它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

1)---操作字符串对象:首先将一个字符串对象写到文件中去:----》序列化

public static void main(String[] args) throws IOException {
    //1.有一个文件,有一个文件输出流、对象输出流
    File f = new File("E:\\Java\\笔记\\demo1.txt");
    FileOutputStream fos = new FileOutputStream(f);
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    //写成一条语句
    //ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\Java\\笔记\\demo1.txt)));
    //2.开始行动
    oos.writeObject("你好");
    //3.关闭流
    oos.close();

写一个程序读文件中内容:----》反序列化

//写一个程序读文件中内容:----》反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
    //1.有一个文件,文件输入流,对象输入流
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\Java\\笔记\\demo1.txt")));
    //开始行动
    String s = (String) (ois.readObject());  //我们上一个输入的是字符串,所以可以用字符串来接收
    System.out.print(s);
}

2)---操作自定义类的对象

注意!!!你想要序列化的那个对象对应的类,必须要实现一个接口:

public class Person implements Serializable{

}

接口内部,什么都没有,这种接口叫 标识接口。(通行证)
起到标识作用,标识什么呢?只要实现这个接口的类的对象才能序列,否则不可以。

public class Test72Person {
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public Test72Person() {
        }
        public Test72Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
public static void main(String[] args) throws IOException {
    //序列化:将内存中对象 ---》 文件:
    //有一个对象:
    Test72Person p = new Test72Person("lili",19);
    //有对象流:
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\Java\\笔记\\demo2.txt")));
    //向外写:
    oos.writeObject(p);
    //关闭流:
    oos.close();
}

如果不实现接口Serializable,会报错

实现接口之后,即可

用程序实现 反序列化操作:将这个对象 恢复到内存中来:

public static void main(String[] args) throws IOException, ClassNotFoundException {
    //准备文件、流
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\Java\\笔记\\demo2.txt")));
    //开始行动
    Test72Person o= (Test72Person)(ois.readObject());
    System.out.println(o);
    //关闭流
    ois.close();
}

因为自定义类中没有重写toString方法,所以控制台打印的是对象的地址

3)serialVersionUID:
凡是实现Serializable接口(标识接口)的类都有一个表示序列化版本标识符的静态常量:
➢private static final long serialVersionUID;
➢serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序加化时是否兼容。
➢如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

➢简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

-那么,我们需要在Person类中加入toString方法,可以让控制台打印的东西好看点

完了,芭比Q了,又出错了。。。就因为我在代码里带了个帽子(加了个toString)Why???

解决办法:给这个类 加入一个 序列号:serialVersionUID(属性值)

加入之后,重新编译序列化那个文件,再编译反序列化那个文件即可。

总之:也就是说,你想要序列化的那个对象对应的类,必须要实现一个接口Serializable,凡是实现Serializable接口(标识接口)的类都有一个表示序列化版本标识符的静态常量---》也就是给类中加一个属性值:private static final long serialVersionUID;

但是,每次都配置序列号很麻烦,我们可以idea系统自动生成


4)序列化细节:

--被序列化的类的内部的所有属性,必须是可序列化的 (基本数据类型都是可序列化的)

--static,transient修饰的属性 不可以被序列化

posted @   二白--  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
点击右上角即可分享
微信分享提示