Java IO流

IO流

1.什么是IO流

IO流是Java对文件进行操作,同时还可以对文件的内容读取和写入,在Java中,这些操作文件的类称之为流

1.1 IO流的分类 — 面试题
  • 根据流向:
    • 输入流:对文件的内容进行读取
    • 输出流:对文件的内容进行写入
  • 根据单位:
    • 字节流:每次读取或者写入一个字节
    • 字符流:每次读取或者写入一个字符
  • 根据功能:
    • 节点流:可以从数据的某个节点向某个节点写入数据,就是普通的输入输出流
    • 处理流:对已经存在的流做二次封装的,可以提供更加丰富的流处理的功能,还可以提高读写效率,就是缓冲流
1.2 流的父类
  • 字节输入流:通过字节对文件进行读取 InputStream(父类)
  • 字节输出流:通过字节对文件进行写入 OutputStream(父类)
  • 字符输入流:通过字符对文件进行读取 Reader(父类)
  • 字符输出流:通过字符对文件进行写入 Writer(父类)

笔试题:下列哪些流是字节流(ABC)?

A:FileInputStream
B:ObjectOutputStream
C:DataOutputStream
D:BufferedReader
E:FileWriter
F:InputStreamReader

总结:流的命名规则,看后缀和哪个父类一样就是什么类

1.3 字节流和字符流的区别 — 面试题
  • 字节流:通过字节对文件进行读写,可以对任何文件(比如:图片、视频、音频、文件…)进行读写,但是对于读写文本文件不太适合,原因在于中文可能会出现乱码
  • 字符流:通过字符对文件进行读写是,非常适合读写文本文件,可以防止中文乱码,因为读写任何文本数据,就算是中文也当成一个字符

2.字节流

2.1 FileInputStream

属于InputStream的子类,属于文件输入流,负责对文件按照字节为单位对数据进行读取

//1.创建输入流对象,提供了多种有参构造
FileInputStream fis = new FileInputStream(参数);
//参数:可以写字符串,表示读取的文件地址
//参数:可以写File类型的参数,可以判断文件是否存在,防止文件丢失异常(FileNotFoundException)

//2.通过流对象.方法(),对文件进行读取
read():运行一次,表示读取一个字节
read(字节数组):表示每次读取的内容会保存到字节数组里面,并且会返回读取的真实长度,如果没有数据会返回-1
close():关闭流,一般流使用完后都需要关闭,为了节省资源

字节流读取中文可能会出现中文乱码,原因在于每次读取的中文未必会全部读完,如果没有读取完只读取了一个汉字的一部分就会出现乱码,不同的编码方式占字节数不同,如果是UTF-8每个汉字占三个字节,如果是GBK每个汉字占两个字节

2.2 FileOutputStream

属于OutputStream子类,属于文件字节输出流,负责将数据进行写入

//1.创建输出流对象
FileOutpStream fos = new FileOutputStream(参数);
//一个参数的构造:String 或者 File
//两个参数的构造:String,boolean 或者 File,boolean
//boolean表示是否是追加模式,默认值是false,表示不追加,如果是true,会在原来的数据基础上写入新内容

//2.流对象.调用方法(),进行写入数据
write():写入一个字节,参数int,编写ASCII
write(字节数组):写入一个字节数组
flush():清空缓存,Java读写都是基于内存的,每次读取和写入存储到缓存中,清空了缓存后,才会到本地磁盘,如果没有清空缓存,但是关闭了资源(先清空缓存,再写入本地磁盘)
close():关闭流
2.3 复制文件的原理
//测试文件复制(先对文件进行读取,再对文件进行写入)
public class TestCopy {
    public static void main(String[] args) {
        File in = new File("D://IO//后台管理登陆 -联想浏览器 2025-01-05 15-31-02.mp4");
        File out = new File("d://IO//movie//myPC.mp4");
        if (in.exists()){
            File parentOut = out.getParentFile();
            if (!parentOut.exists()){
                parentOut.mkdirs();
            }
            //统计当前系统时间毫秒
            long start = System.currentTimeMillis();
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                fis = new FileInputStream(in);
                fos = new FileOutputStream(out);
                byte[] bs = new byte[1024];
                int len;
                while ((len = fis.read(bs)) != -1) {
                    fos.write(bs,0,len);
                }
                //再统计一次系统时间毫秒数
                long end = System.currentTimeMillis();
                long duration = end - start;
                System.out.println("复制成功:"+duration+"ms");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }finally {
                try {
                    if (fis != null) fis.close();
                    if (fos != null) fos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else {
            System.out.println("要复制的文件不存在");
        }
    }
}

//新版try-catch 实现文件复制
public class TestCopy2 {
    public static void main(String[] args) {
        //try(){}catch{}
        //可以将需要关闭资源的内容放入()编写 它会帮你自动关闭
        try (
                FileInputStream fis = new FileInputStream("D://IO//后台管理登陆 -联想浏览器 2025-01-05 15-31-02.mp4");
                FileOutputStream fos = new FileOutputStream("D://IO//movie//myPC2.mp4")
        ){
            long start = System.currentTimeMillis();
            byte[] bs = new byte[1024];
            int len;
            while ((len = fis.read(bs)) != -1) {
                fos.write(bs, 0, len);
            }
            long end = System.currentTimeMillis();
            System.out.println("复制成功:"+(end - start)+"ms");
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.缓冲流

缓冲流:是属于处理流,可以提高文件的读写效率,原理是每次进行文件读取的时候不会立即写入,会先放入缓冲区(内存)等待缓冲区存储满了再一口气写入到本地磁盘,由于缓冲区是属于内存的,而访问内存的速度会高于本地磁盘,所以才可以提高读写效率,同时还减少了访问本地磁盘的次数,提高了本地磁盘的使用寿命

//字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(字节输入流);
bis.read();  bis.read(字节数组);  close();
//字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(字节输出流);
bos.write();  bos.write(字节数组);  close();

4.字符流

字符流:非常适合读写文本内容,因为中文在字符流中也算是一个字符,不会像字节流一样对中文拆分字节进行读取,不会出现中文乱码的问题

  • 节点流:
    • FileReader:

      read():返回的是一个整型,表示数据的ASCII;
      read(字符数组):将读取内容存储到字符数组,返回读取长度,否则为-1close():关闭流;
      
    • FileWriter:

      write(int c):写入单个字符。 
      write(String str): 将字符串内容写入到本地文件; 
      write(字符数组):写入字符数组。 
      flush():刷新该流的缓冲
      close():关闭流;
      
  • 处理流:
    • BufferedReader
      • readLine():读取一整行数据,返回String类型,如果没有数据了,会返回null;
    • BufferedWriter
      • newLine():表示换行----可选(\n);
//测试字符流和缓冲流
public class TestChar {
    public static void main(String[] args) {
        try(
                FileReader fr = new FileReader("D://IO//testIO.txt");
                FileWriter fw = new FileWriter("D://IO//result3.txt");
                BufferedReader br = new BufferedReader(fr);
                BufferedWriter bw = new BufferedWriter(fw);
        ){
            String msg;
            while((msg = br.readLine()) != null){
                bw.write(msg);
                bw.newLine();
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
4.1 字节流转换字符流
  • InputStreamReader:本质上就是字符输入流,提供了一个有参构造,将字节流转换成字符输入流

    InputStreamReader isr = new InputStreamReader(字节输入流);
    //Scanner-->System.in(底层就是InputStream);
    //socket.getInputStream();
    
  • OutputStreamWriter:本质上就是字符输出流,提供了一个有参构造,将字节流转换成字符输出流

    OutputStreamReader osw = new OutputStreamReader(字节输出流);
    
//模拟Scanner对象获取控制台数据
public class TestScanner {
    private InputStream is;
    private InputStreamReader isr;
    private BufferedReader br;
    //new对象的时候执行
    public TestScanner(InputStream is) {
        this.is = is;
        isr = new InputStreamReader(is);
        br = new BufferedReader(isr);
    }
    //private InputStreamReader isr =  new InputStreamReader(is);
    //private BufferedReader br = new BufferedReader(isr);
    public String next(){
        String msg = null;
        try {
            msg = br.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return msg;
    }
    public int nextInt(){
        String msg = null;
        try {
            msg = br.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        int result = Integer.parseInt(msg);
        return result;
    }
    public static void main(String[] args) throws IOException {
        //System.in表示系统输入流,它可以获取控制台数据,本质上是一个字节输入流
        //Scanner sc = new Scanner(System.in);
        TestScanner ts = new TestScanner(System.in) ;
        InputStream is = System.in;
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        while (true) {
            System.out.println("Enter a word: ");
            String msg = br.readLine();
            System.out.println("读取控制台数据:"+msg);
        }
    }
}
class Test{
    public static void main(String[] args) {
        //加载类(加载成员变量)
        TestScanner ts = new TestScanner(System.in) ;
        System.out.println("请输入姓名:");
        String name = ts.next();
        System.out.println("请输入年龄:");
        int age = ts.nextInt();
        System.out.println("姓名:"+name+"年龄:"+age);
    }
}

5.序列化和反序列化 — 重点和难点和面试题

  • 序列化:将Java中的对象转换成字节序列(如果字节序列写入到本地文件就会形成一个文件)的过程

  • 反序列化:将字节序列转换回Java中的对象

    面试题:Java创建对象的方式有哪些?

    • new对象():通过构造方法创建对象
    • 克隆:不走构造方法,是堆内存直接将已经存在的对象拷贝一个新的
    • 反序列化:不走构造方法将对象的字节流恢复为对象,需实现 Serializable 接口。
    • 反射:通过类的Class对象调用 newInstance() 方法创建对象,要求类必须有无参构造器。通过构造器对象创建对象,支持有参构造器和私有构造器(需设置 setAccessible(true) )。走构造方法
5.1 实现序列化和反序列化注意事项
  • 序列化必须要实现一个接口Serializable,表示支持序列化,否则会出现NotSerializableException异常
  • 序列化会自带一个默认生成序列化唯一标识serialVersionUID,防止序列化和反序列化对象数据不统一的问题,如果两次UID不同,则序列化和反序列化失败
  • 序列化还可以设置指定属性是否忽略序列化
    • 通过static修饰的属性会忽略,因为static修饰的属性是属于类的不属于对象
    • 通过transient修饰的属性也会忽略
5.2 序列化和反序列化实现
ObjectOutputStream: 用于实现序列化,将Java中的对象转换成本地文件
writeObject(对象): 

ObjectInputStream: 用于实现反序列化的,将本地文件转换成Java中的对象
对象 = (对象)readObject(); //默认返回Object

6.Object类 — 面试题

Object类是属于java.lang包下的,无需导入包(import),是Java类中所有类的父类,本身Object类提供了很多公开的方法,所以Java中的所有类都可以使用这些方法

面试题:Object类有哪些常用方法?

  • equals(): 等价于==,用于比较地址是否一致,如果子类重写了equals(),会根据子类重写后的规则来,比如:String、Integer····就重写了equals()

  • toString(): 将对象转换成字符串输出,默认是引用地址(在堆内存存储的位置),如果重写了,就会返回重写后的toString()的返回值

  • hashcode(): 获取哈希码的

  • 什么是哈希码:用于表示一个对象的基本特征,一般是由32位的二进制数构成,形成int类型保存结果,主要包含三大类

    • 对象类:根据对象的引用地址计算得到
    • 基本类型封装类:保存的就是对应的数值
    • String类:根据字符串的内容计算得到
      • 哈希冲突:当值不同但是hashcode相同时,就是哈希冲突,比如:“重地”和“通话”,如果值相同,最终转换的hashcode一定相同,如果hashcode相同,存储的值不一定相同
  • getClass(): 获取类对象(User.class属于类对象,new User()属于对象),是做反射的前提

  • notify(): 唤醒随机一个处于等待的线程

  • notifyAll(): 唤醒全部属于等待的线程

  • wait(): 让线程处于等待,需要其他线程进行唤醒

  • clone(): 用于克隆对象的

  • 浅克隆:只能针对于对象中的基本类型和String类型进行克隆,但是如果是引用类型的属性,是不能克隆的,只是把地址复制一份,指向的还是同一个对象。实现:实现Cloneable接口,重写clone()

  • 深克隆:不仅可以针对于String类型和基本类型的属性进行克隆,引用类型的属性也可以支持克隆

    实现:实现Cloneable接口,重写clone(),同时引用类型的属性也要实现Cloneable接口,重写clone()

  • finalize(): GC垃圾回收机制使用的方法,自动将堆内存的无用对象进行回收,Java会自动调用,目的是防止内存溢出

面试题:深克隆和浅克隆的区别?

//克隆对象:
//浅克隆:需要实现Cloneable接口表示支持克隆,重写父类的clone()方法
//深克隆:需要实现Cloneable接口表示支持克隆,重写父类的clone()方法,对象属性也要这么做
public class Emp implements Cloneable{
    Integer id;
    String name;
    EmpInfo info;
    public Emp(){
        System.out.println("无参构造");
    }
    @Override
    protected Emp clone() throws CloneNotSupportedException {
        Emp emp = (Emp) super.clone();
        emp.info = info.clone();
        return emp;
    }
}
class EmpInfo implements Cloneable{
    Integer id;
    String sex;
    Integer age;
    @Override
    protected EmpInfo clone() throws CloneNotSupportedException {
        return (EmpInfo) super.clone();
    }
}
class TestClone{
    public static void main(String[] args) throws CloneNotSupportedException {
        Emp emp = new Emp();//走构造方法
        emp.id = 10;
        emp.name = "张三";
        emp.info = new EmpInfo();
        emp.info.id = 10;
        emp.info.age = 18;
        emp.info.sex = "男";
        Emp emp1 = emp.clone();//在堆内存直接复制一个对象出来
        System.out.println(emp+" "+emp.info);
        System.out.println(emp1+" "+emp1.info);
        emp.name = "李四";
        emp.info.age = 19;
        System.out.println(emp1.name+" "+emp1.info.age);
    }
}

你可能感兴趣的:(Java学习,java,开发语言,intellij-idea,后端)