Java基础整理(十八)

IO流(下)

传送门:IO流(上)

10. BufferedReader

带有缓冲区的字符输入流,使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲

public class Demo {
        public static void main(String[] args) throws Exception{

            FileReader reader = new FileReader("test.txt");
            //当一个流的构造方法需要一个流的时候,这个被传进去的流叫做:节点流
            //外部负责包装的这个流叫做:包装流/处理流
            //当前这个程序:FileReader就是节点流,BufferedReader就是包装流/处理流
            BufferedReader br = new BufferedReader(reader);

            //当readLine读到文件最后时,会返回null,且readLine方法读取不带换行符
            String s =  null;
            while((s =br.readLine()) != null){
                System.out.println(s);
            }

            //对于包装类来说,只需要关闭最外层流就行,里面的节点流会自动关闭(在调用外层流close()方法时,会在方法内调用节点流的close()方法)
            br.close();

        }
}

BufferedReader构造方法只能传入字符流,如果想要传进去字节流怎么办?

采用转换流InputStreamReader/OutputStreamReader进行转换

public class Demo {
        public static void main(String[] args) throws Exception{

            //字节流
            FileInputStream in = new FileInputStream("test.txt");
            //通过转换流转换
            //in是节点流,reader是包装流
            InputStreamReader reader = new InputStreamReader(in);
            //只能传入字符流,不能传入字节流
            //reader是节点流,br是包装流
            BufferedReader br = new BufferedReader(reader);
            
            //合并 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt")));

            //关闭最外层即可
            br.close();

        }
}

11. BufferedWriter

带有缓冲的字符输出流

public class Demo {
        public static void main(String[] args) throws Exception{
            
            BufferedWriter out = new BufferedWriter(new FileWriter("copy"));
            //BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy")));
            //开始写
            out.write("hello world\n");
            out.write("hello zhangsan");

            out.flush();
            out.close();
        }
}

12. DataOutputStream

数据专属的流,这个流可以将数据连通数据的类型一并写入文件。

这个文件不是普通文本文档(记事本打不开)

public class Demo {
        public static void main(String[] args) throws Exception{
            
            DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
            byte b =100;
            short s = 200;
            float f =3.0F;
            boolean sex = false;
            char c ='a';
            
            //写数据
            dos.writeByte(b);
            dos.writeShort(s);
            dos.writeFloat(f);
            dos.writeBoolean(sex);
            dos.writeChar(c);
            
            dos.flush();
            dos.close();
            
        }
}

13. DataInputStream

数据字节输入流,DataOutputStream写的文件,只能使用DataInputStream去读,并且读的时候需要提前知道写入的顺序。读的顺序需要和写的顺序一致,才可以正常取出数据。

public class Demo {
        public static void main(String[] args) throws Exception{

            DataInputStream dis = new DataInputStream(new FileInputStream("data"));

            //读数据
            byte b =dis.readByte();
            short s =dis.readShort();
            float f = dis.readFloat();
            boolean sex = dis.readBoolean();
            char c = dis.readChar();
            
            dis.close();
        }
}

14. PrintStream

标准的字节输出流,默认输出到控制台,标准输出流不需要手动close()关闭

public class Demo {
        public static void main(String[] args) throws Exception{

            //联合写
            System.out.println("hello world");
            
            //分开写
            //System类中out属性返回值为PrintSteam类型
            PrintStream ps = System.out;
            //PrintStream类中的静态方法println()
            ps.println("hello world");
            
        }
}
  • System类常用的方法和属性

    System.gc();
    System.currentTimeMillis();
    System.out;
    System.exit(0);
    System.arraycopy(...);
    
  • 修改输入流的方向

    public class Demo {
            public static void main(String[] args) throws Exception{
    
                //标准输出流不再指向控制台,指向log文件
                PrintStream printStream = new PrintStream(new FileOutputStream("log"));
                //修改输出方向,将输出方向修改为log文件
                System.setOut(printStream);
                //再输出 直接输出在文件里了
                System.out.println("hello world");
            }
    }
    
  • 实际应用(记录日志文件)

    public class Demo {
            public static void main(String[] args) throws Exception{
    
                //测试
                log("调用了....方法");
                log("账号被登录");
    
            }
    
            public static void log(String msg){
                //标准输出流不再指向控制台,指向log文件
                PrintStream printStream = null;
                try {
                    printStream = new PrintStream(new FileOutputStream("log.txt",true));
                    //修改输出方向,将输出方向修改为log文件
                    System.setOut(printStream);
                    Date date = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                    String strTime = sdf.format(date);
                    System.out.println(strTime +": "+msg);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
    }
    

    结果图:在这里插入图片描述


15. File类

  • File类和四大家族没有关系,所以File类不能完成文件的读和写
  • File对象是文件和目录路径名的抽象表示形式,一个File对象有可能对应的是目录,也可能是文件
(1)File类中常用的方法
public class Demo {
    public static void main(String[] args) throws IOException {

        //创建一个File对象
        File f1 = new File("E:\\test");

        //1.如果E:\test不存在,则以文件的形式创建
        if(!f1.exists()){
            f1.createNewFile();
        }

        //2.如果E:\test不存在,则以目录的形式创建
        if(!f1.exists()){
            f1.mkdir();
        }

        File f2 = new File("E:/a/b/c/d/e");
        if(!f2.exists()){
            //3.多重目录的形式创建
            f2.mkdirs();
        }

        File f3 = new File("E:\\Java-Project\\test.txt");
        //4.获取文件的父路径,返回的是String类型
        String parentPath = f3.getParent();
        System.out.println(parentPath); //E:\Java-Project
        //5.获取文件父路径,返回的File类型
        File parentFile = f3.getParentFile();
        System.out.println("获取绝对路径:"+parentFile.getAbsolutePath());//6.获取绝对路径:E:\Java-Project

        File f4 = new File("E:\\Java-Project\\test.txt");

        //7.获取文件名
        System.out.println(f4.getName()); //test.txt

        //8.判断是否是一个目录
        System.out.println(f4.isDirectory()); //false

        //9.判断是否是一个文件
        System.out.println(f4.isFile()); //true

        //10.获取文件最后一次修改时间
        long updateTime = f4.lastModified(); //返回的是从1970年到现在的毫秒数
        //将毫秒转换为日期
        Date time = new Date(updateTime);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(updateTime);
        System.out.println(strTime); //2021-12-09 21:08:08 835

        //11.获取文件大小
        System.out.println(f4.length()); //3字节

        //12.获取当前目录下所有的子文件
        File f5 = new File("E:\\Java-Project\\Java-Code");
        File[] files = f5.listFiles(); //返回的是一个File数组
        for(File file : files){
            System.out.print(file.getName()+" ");//.idea Java-Code.iml out src
        }
    }
}

(2)目录复制
public class Copy {
    public static void main(String[] args) throws Exception {

        //源目录
        File sourceDir = new File("E:\\test");
        //目标目录
        File desDir =  new File("C:\\copy");
        copy(sourceDir,desDir);
    }

    public static void copy(File sourceDir, File desDir) throws Exception{
        //如果源是一个文件,结束递归
        if(sourceDir.isFile()){
            String sourcePath = sourceDir.getAbsolutePath();
            String desPath = desDir.getAbsolutePath();
            //新的目的路径
            String desNew = (desPath.endsWith("\\") ? desPath : desPath + "\\") + sourcePath.substring(3);
            FileInputStream fis = new FileInputStream(sourceDir);
            FileOutputStream fos = new FileOutputStream(desNew);
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while((readCount = fis.read()) != -1){
                fos.write(bytes,0,readCount);
            }
            fos.flush();
            fos.close();
            fis.close();
            return;
        }
        //否则就是目录 获取子目录
        File[] files= sourceDir.listFiles();
        for(File file : files){
            String sourcePath = file.getAbsolutePath();
            String desPath = desDir.getAbsolutePath();
            //新的目的路径
            String desNew = (desPath.endsWith("\\") ? desPath : desPath + "\\") + sourcePath.substring(3);
            if(file.isDirectory()){
                File newDir= new File(desNew);
                if(!newDir.exists()) {
                    newDir.mkdirs();
                }
            }
            copy(file,desDir);
        }
    }
}

16. 序列化与反序列化

  • 参与序列化和反序列化的对象,必须要实现Serializable接口,否则会出现java.io.NotSerializableException
  • Serializable接口是一个标志接口,在接口当中什么代码都没有,它只是起到标识作用,java虚拟机看到这个类实现了这个接口,会进行特殊待遇,自动生成一个序列化版本号,没有手动写出,java虚拟机会自动默认提供这个序列化版本号
(1)图解

在这里插入图片描述


对于下面的操作,全部基于该Student类

public class Student implements Serializable {
    
    private int id;
    private String name;

    public Student(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

(2)序列化
public class Demo {
    public static void main(String[] args) throws IOException {

        //创建java对象
        Student s = new Student(123,"zhangsan");
        //序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students")); //存入students文件
        //序列化对象
        oos.writeObject(s);
        //刷新
        oos.flush();
        //关闭
        oos.close();

    }
}

执行结果:在这里插入图片描述

生成一个文件存储的Student对象,该文件无法正常打开,需进行反序列化操作


(3)反序列化
public class Demo {
    public static void main(String[] args) throws Exception {

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));//反序列化的文件
        //读出来的是Object类型,底层是Student
        Object obj = ois.readObject();
        //自动调用Student类的toString()方法
        System.out.println(obj);
        ois.close();
    }
}

执行结果:在这里插入图片描述


(4)一次多个对象序列化和反序列化

将对象放到集合当中,再序列化集合

  • 序列化

    public class Demo {
        public static void main(String[] args) throws Exception {
    
            List<Student> studentList = new ArrayList<>();
            studentList.add(new Student(123,"lisi"));
            studentList.add(new Student(124,"zhangsan"));
            studentList.add(new Student(125,"wangwu"));
    
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("studentlist"));
    
            oos.writeObject(studentList);
    
            oos.flush();
            oos.close();
        }
    }
    
  • 反序列化

    public class Demo {
        public static void main(String[] args) throws Exception {
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("studentlist"));
            List<Student> studentList = (List<Student>) ois.readObject();
            for(Student student : studentList){
                System.out.println(student);
            }
            ois.close();
        }
    }
    

    执行结果:在这里插入图片描述

    注:参与序列化的ArrayList集合以及集合中的元素Student都需要实现java.io.Serializable接口


(5)transient

transient关键字表示游离,不参与序列化

//将上面Student类的属性改为
public class Student implements Serializable {
    
    private int id;
    private transient String name; //name不参与序列化操作
    
}

再进行同样的序列化和反序列化操作,得到的结果:

在这里插入图片描述


(6)序列化版本号
  • Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号

  • 当一个类修改了源代码后,需要重新编译,编译之后生成全新的字节码文件,并且java虚拟机生成的序列化版本号也发生改变

    比如:修改源代码后,直接进行反序列化,则会出现:

    Exception in thread "main" java.io.InvalidClassException: com.study.www.Student; 
    local class incompatible: 
    stream classdesc serialVersionUID = 8628483378815559334,(修改代码之前的序列号)
    local class serialVersionUID = -1019878040008205449(修改代码之后的序列号)
    

    这代表着java虚拟机认为这是不同的两个类

    不同的人编写了同一个类,但这两个类确实不是同一个类,这时候序列化版本号就起作用了。对于java虚拟机来说,是可以区分这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号。

  • java语言采用什么机制来区分类呢?

    • 首先通过类名进行对比,如果类名不一样,肯定不是一个类
    • 如果类名一样,则靠序列化版本号进行区分
  • 自动生成序列化版本号有什么缺陷?

    一旦代码确定之后,不能进行后续的修改,因为只要修改,必须会重新编译,此时会生成全新的序列化版本号,这时候java虚拟机就会认为这是一个全新的类

结论:凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使修改了代码,但是版本号不变,java虚拟机会认为是同一个类

public class Student implements Serializble{
    
    private static final long serialVersionUID = 1L;
    
    //...
}

17. IO和Properties联合使用

Properties是一个Map集合,key和value都是String类型

此处用IO流和Properties联合使用将工程下的userinfo属性文件中的数据加载到Properties对象当中

在这里插入图片描述

public class Demo {
    public static void main(String[] args) throws Exception {

        //新建一个输入流
        FileReader reader = new FileReader("userinfo");

        //新建一个Map集合
        Properties pro = new Properties();

        //调用Properties对象的load方法将文件中的数据加载到Map集合中
        pro.load(reader);//文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value

        //通过key来获取value
        String username = pro.getProperty("username");
        System.out.println(username); //root

    }
}

以后经常改变的数据,可以单独写到一个文件中,使用程序动态获取。将来只要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启,就可以动态拿到信息

  • 类似于userinfor的内容格式key1=value的时候,我峨嵋你把这种配置文件叫做属性配置文件
  • Java规范中要求:属性配置文件建议以**.properties**结尾,但不是必须的
  • 在属性配置文件中井号(#)是注释,属性配置文件的key重复的话,value会自动覆盖
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值