java file 到 byte[]_Java学习笔记-IO

本文结构

  1. 字节流
  2. 字符流
  3. 异常的处理
  4. File类
  5. Properties集合
  6. 缓冲流
  7. 转换流
  8. 对象序列化
  9. 一点小问题

IO

  • 在Java中,I/O流分为字节流和字符流,分类如下:

17432df392f87d870b816143291ace67.png
  • 根据操作对象主要分为:

6e427a67aceecd5641457bdd34dcc5c9.png

1. 字节流

字节流可以传输任意文件数据,在操作流的时候,无论使用什么样的流对象,底层传输的始终都是二进制数据

1.1 字节输出流抽象类 OutputStream

OutputStreamObject的直接子类,此抽象类是表示输出字节流的所有类的父类,输出流接受输出字节并将这些字节发送到某个接收器。

  • 成员方法 : void close() : 关闭此输出流,释放相关的所有系统资源 void flush() : 刷新此输出流,并强制任何缓冲的输出字节被写出 void write(byte[] b) : 将 b.length 字节从指定的字节数组写入此输出流 void write(byte[] b, int off, int len) : 从偏移量 off 开始写入 len 字节到输出流 abstract void write(int b) : 将指定的字节输出流

1.2 字节输入流抽象类 InputStream

  • 成员方法 : int available () : 返回可读字节数量 abstract int read() :读取数据 int read(byte[] b, int off, int len) : 读取指定长度到byte数组 long skip(long n) : 跳过指定个数字节 boolean markSupported() : 是否支持 mark()/reset() synchronized void mark(int readlimit) : mark一个位置 synchronized void reset() : 重置位置为上次mark的位置 void close() : 关闭流,释放资源

1.3 文件字节流

1.3.1 文件字节输出流类 FileOutputStream

  • 构造方法: FileOutputStream(String name) : 创建一个向指定名称的文件中写入数据的文件输出流 FileOutputStream(File file) : 创建一个向指定 File 对象表示的文件中写入数据的文件输出流 FileOutputStream(String name, boolean append) : 可以进行追加写 FileOutputStream(File file, boolean append)
  • 构造方法的作用:
    创建FileOutputStream对象,根据构造方法中的文件路径/文件创建一个空的文件,把FileOutputStream对象指向创建好的文件
  • 写入数据的原理:
    java程序 --> JVM ---> OS ---> OS调用写数据的方法 ---> 将数据写入到文件中
  • 一次写入单个字符
    //创建FileOutputStream对象,传入文件
    FileOutputStream fos = new FileOutputStream("D:a.txt");
    //写入数据
    fos.write('b');
    //释放资源
    fos.close();
  • 一次写入多个字符
    如果第一个字节是正数(0-127),直接查询 ASCII 表,如果第一个字节是负数,那么前两个字节组合,查询 GBK 表
    //使用字符串转字节数组写入
    byte[] in1 = "大家好才是真的好".getBytes();
    fos.write(in1,0,21);   //大家好才是真的,UTF-8编码
    fos.close();
  • 换行符号
    Windows: rn
    Linux: /n
    MacOS: /r

1.3.2 文件字节输入流 FileInputStream

  • 构造方法: FileInputStream(String name) FileInputStream(File file)
  • 按单个字节读入: int read()返回读取到的字节的int
    FileInputStream fis = new FileInputStream("D:a.txt");
    int readByte = 0;
    while((readByte = fis.read())!=-1){     //java中赋值语句会返回该值,读取到结束标记时read()会返回-1
        System.out.println(readByte);
    }
    fis.close();
  • 一次读取多个字节:
    此处的 bytes 起到缓冲作用,存储每次读取到的多个字节,数组的长度定义为 1024 即可一次读取 1KB int read(byte[]) 返回的是每次读取到的有效字节个数
    FileInputStream fis = new FileInputStream("D:a.txt");    //a中存放abcde
    byte[] bytes = new byte[2];
    int len = 0;
    while(len>=0) {
        len = fis.read(bytes);
        System.out.println("len="+len+",bytes="+new String(bytes, 0, len));   
        //使用 String(bytes, 0, len)保证只把有效读取的字符转换为字符串~~~
    }
    fis.close();
    /*
    len=2,bytes=ab
    len=2,bytes=cd
    len=1,bytes=ed       //只读取到一个e,覆盖掉c
    len=-1,bytes=ed      //读取到结束标记,返回-1
    */

2. 字符流

字节流读取中文字符的时候,由于中文一个字符可能占用多个字节存储,可能不会显示完整字符,所以java提供了字符流,以字符为单位读写数据,专门处理文本文件,底层也是字节流

2.2 字符输入流抽象类 Reader

  • 成员方法:boolean ready() int read() :读取单个字符,可读返回读取的整型数,遇到文件末尾返回-1 int read(char[] cbuf) : 读取到char数组 abstract int read(char[] cbuf, int off, int len) void reset() long skip(long n) abstract void close()

2.1 字符输出流抽象类 Writer

  • 成员方法: abstract void flush() void write(int c) void write(char[] cbuf) abstract void write(char[] cbuf, int off, int len) void write(String str) void write(String str, int off, int len)

2.3 文件字符流

java.io.FileReader extends InputStreamReader extends Reader java.io.FileWriter extends OutputStreamWriter extends writer

2.3.1 文件字符输入流 FileReader

  • 构造方法: FileReader(File file) FileReader(String fileName)
  • read 读取到的是字符的编码,需要转换为 char

2.3.2 文件字符输出流 FileWriter

  • 构造方法: FileWriter(String fileName) FileWriter(String fileName, boolean append) FileWriter(File file) FileWriter(File file, boolean append)
    字符流输出的时候,write方法先把数据写入到内存缓冲区中,字符转换为字节,再利用flush刷新内存缓冲区中的数据,把数据写入到文件中,释放资源也会自动把内存缓冲区中的数据刷新到文件中
  • 字节输出流即使不调用close()方法,也会把数据写入到文件中,但是字符传输流如果不调用close()或者flush(),数据不会写入到文件,只在内存缓冲区中,程序运行结束时便消失了
  • 与 try catch 结合使用
    FileWriter fw = null;      //提高变量作用域,使finally中可以使用
                     // 必须给初值,否则new出现异常无法执行finally
    try {
        fw = new FileWriter("D:a.txt");
        fw.write(97);   //写入int的ASCII码

        char[] chs = {'a', 'b', 'c', 'r', 'n'};
        fw.write(chs);    //写入字符数组

        String s = "写入字符串了rn";
        fw.write(s);    //写入字符串
        fw.write(s, 0, 5);

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fw != null) {   //不为null时才close,否则会出现空指针异常
            try {           //close()的异常再次进行捕获
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3. 异常的处理

3.1 jdk7新特性

  • 在jdk7之后,try之后可以加()在括号中定义流对象,此流对象作用域只在try中有效,try中代码执行完毕自动释放流对象不需要再写finally
    public static void main(String[] args) {
        try (   //在try中创建流对象
                FileInputStream fis = new FileInputStream("D:lena.bmp");
                FileOutputStream fos = new FileOutputStream("D:lena_copy.bmp");
        ) {   
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }                                       //try中代码执行完之后自动释放流对象
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3.2 jdk9新特性

  • 可以在 try之前定义流对象,在try的括号中引入流对象的名称,try的代码执行完毕之后,流对象也可以自动释放,不用写finally释放流对象
    public static void main(String[] args) throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("D:lena.bmp");
        FileOutputStream fos = new FileOutputStream("D:lena_copy.bmp");
        try (fis;fos) {
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }
        } catch (IOException e) {
            System.out.println("出错了" + e);
            e.printStackTrace();
        }
    }

4. File类

  • 构造函数: File(String directoryPath) File(String directoryPath, String filename) : 操作同目录多个文件时比较方便 File(File dirObj, String filename)
  • 常用方法: String getName() String getParent() : 返回父路径名 String getPath() : 路径名转字符串 boolean isFile() : 此路径名是表示的文件是否是标准文件 boolean equals() String[] list() : 返回目录中所有文件和文件夹名的字符串数组 boolean mkdir() : 创建路径对应的目录 String getAbsolutePath() : 返回绝对路径名 boolean exist()
    File file = new File("D:b.txt");
    file.createNewFile(); //创建对应的文件
    System.out.println(File.pathSeparator);  //路径分隔符,Windows是;  linux是:
    System.out.println(File.separator);     //路径名称分隔符,Windows是 linux是/

    if(file.exists()){      //删除文件
        file.delete();
    }


    //也可以对文件夹操作
    String fileName = "D" + File.separator + "aabb";
    File file1 = new File(fileName);
    file.mkdir();       //创建文件夹
    file.listFiles();   //列出所有文件,包括隐藏文件
    file.isDirectory();  //判断指定路径是否是目录

5. Properties集合

  • Properties extends Hashtable<k, v> implements Map<k, v>
  • Properties 类表示了一个持久的属性集,是唯一和IO流相结合的集合,可以保存在流中或者从流中加载
  • Properties 集合的 key 和 value 默认都是字符串 Object setProperities(String key, String value) : 调用 Hashtable 的 put 方法 String getPreperities(String key) : 通过 key 找到对应 value Set<String> ProperityNames() : 返回所有的key的集合,相当于 Map.keySet()
  • void store(Writer writer, String comments) : 使用字符输出流输出(中文用字符流), comments是注解(unicode),用中文会输出unicode void store(OutputStream out, String comments) : 使用字节输出流输出,输出中文会得到unicode码
  • synchronized void load(Reader reader) synchronized void load(InputStream inStream)


读取的时候,key和value的连接符可以是等号=,空格,以及其他符号
存储的文件中可以使用 # 对键值对进行注释,被注释的键值对不再读取
键值都是字符串,不用再加 ""

    Properties pp = new Properties();
    pp.setProperty("11201209120","小明");
    pp.setProperty("11201209121","小红");
    pp.setProperty("11201209122","小蓝");
    pp.setProperty("11201209123","小绿");
    Set<String> keys= pp.stringPropertyNames();   //得到keys(names)
    System.out.println(keys);

    try(FileWriter fw = new FileWriter("D:a.txt");){   //字符流写入
        pp.store(fw,"中文some coments...");
    } catch (IOException e) {
        e.printStackTrace();
    }

    try(FileOutputStream fos = new FileOutputStream("D:b.txt");){  //字节流写入
        pp.store(fos, "中文some coments...");
    } catch (IOException e) {
        e.printStackTrace();
    }

    /*结果 

    a.txt
    #u4E2Du6587some coments...
    #Sun Aug 02 20:25:39 CST 2020
    11201209122=小蓝
    11201209121=小红
    11201209123=小绿
    11201209120=小明


    b.txt
    #u4E2Du6587some coments...
    #Sun Aug 02 20:25:39 CST 2020
    11201209122=u5C0Fu84DD
    11201209121=u5C0Fu7EA2
    11201209123=u5C0Fu7EFF
    11201209120=u5C0Fu660E            

    */

接下来读一下文件到集合:

    pp.clear();
    try(FileReader fr = new FileReader("D:b.txt")){
        pp.load(fr);
    } catch (IOException e) {
        e.printStackTrace();
    }
    for(Map.Entry<Object, Object> entry : pp.entrySet()){
        System.out.println(entry.getKey()+"--->"+entry.getValue());
    }

6. 缓冲流(高效流)

  • 上面的字节流和字符流在每次读写数据时,都要经过jvm,os,读写文件,效率低下
  • 缓冲流给基本的输入输出流增加一个缓冲区(数组),提高基本的字符输入流的读取效率

6.1 字节缓冲流

  • 字节缓冲流:
    BufferedInputStream extends InputStream
    BufferedOutputStream extends OutputStream
  • 构造方法: BufferedInputStream(InputStream in) BufferedInputStream(InputStream in, int size) : 指定缓冲区的大小 BufferedOutputStream(OutputStream out) BufferedOutputStream(OutputStream out, int size)
    long start = System.currentTimeMillis();
    try (
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:lena.bmp"));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:lena_copy.bmp"));
    ) {
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = bis.read(bytes))!=-1){
            bos.write(bytes, 0, len);
            bos.flush();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.currentTimeMillis();
    System.out.println("用时"+(end-start)+"ms");   // 3ms

6.2 字符缓冲流

  • 字符缓冲流
    BufferedWriter extends Writer
    BufferedReader extends Reader
  • 构造方法:BufferedReader(Reader in) BufferedReader(Reader in, int size) : 指定缓冲区的大小 BufferedWriter(Writer out) BufferedWriter(Writer out, int size)
  • 其他方法: String readLine() : 读取一行,不会读取换行符,到达文件末尾时返回 null void newLine() : 根据不同系统,写入一个行分割符(println也是调用的newLine)
    String line = null;
    while((line = br.readLine())!=null){
        System.out.println(line);
    }
  • 注意:第一行直接回车,也能读取到换行符,并不会返回null结束读取

7. 转换流

  • 之前的FileReaderFileWriter只能按UTF-8编码 InputStreamReader : 字节流向字符的桥梁,是FileReader的父类,可以指定编码方式 OutputStreamWriter : 是FileWriter的父类,可以指定编码方式写入
  • 构造方法: OutputStreamWriter(OutputStream out, String charsetName) OutputStreamWriter(OutputStream out, Charset cs) InputStreamReader(InputStream in, String charsetName) InputStreamReader(InputStream in, Charset cs)
    //以GBK编码格式写入和读取
    try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                       new FileOutputStream("D:a.txt"), Charset.forName("GBK")))) {
        bw.write("缓冲的带格式的写入方法".toCharArray());
        bw.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }

    try (BufferedReader br = new BufferedReader(new InputStreamReader(
                        new FileInputStream("D:a.txt"), Charset.forName("GBK")))) {
        String line = br.readLine();
        System.out.println(line);
    } catch (IOException e) {
        e.printStackTrace();
    }

8. 对象序列化

8.1 序列化操作

ObjectOutputStream extends OutputStream ObjectInputStream extends InputStream

  • 写对象,可以把对象转换为字节序列,写入到文件(序列化),或者把文件中的对象读出来(反序列化)
  • 类必须实现 Serializable 接口,才能被序列化
  • 构造方法:ObjectOutputStream(OutputStream out) ObjectInputStream(InputStream in)
    //序列化对象
    try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream(new File("D:" + File.separator + "a.txt")));
    ) {
        Student s1 = new Student("小明", 12, "男");     //Student必须实现Serializable接口
        oos.writeObject(s1);
    } catch (IOException e) {
        e.printStackTrace();
    }

    //反序列化  
    try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:" + 
                                          File.separator + "a.txt")))){
        Object s1 = ois.readObject();
        System.out.println(((Student)s1).getName());
        System.out.println(s1);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }   

8.2 瞬态关键字 transient

  • 只要实现了Serilizable接口,对象就可以被序列化,但是有时候为了安全起见,类的一些敏感属性不希望被序列化后传输,这事就需要瞬态关键字
  • 在对象序列化的时候,transient1修饰的成员变量不能被序列化,生命周期仅存在于调用者的内存中,不会写到磁盘里持久化
  • transient 只能修饰变量,不能修饰方法和类
  • stastic变量属于类,不管是否被`transient`修饰,都不能被序列化
  • 需要用两个进程分别测试序列化和反序列化,否则jvm已经把类加载进来了,可以读取到类的static变量信息


测试:

    public class Student implements Serializable {
        private String name;
        private static int age;
        private transient String sex;
        constructor...getter and setter....
    }

    //序列化
    public class DemoObjectOutputStream {
        public static void main(String[] args) {
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
                new File("D:" + File.separator + "a.txt")));
            ) {
                Student s1 = new Student("小明", 12, "男");
                oos.writeObject(s1);

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

    //反序列化
    public class DemoObjectInputStream {
        public static void main(String[] args) {
            try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                new File("D:" + File.separator + "a.txt")))){
                Object s1 = ois.readObject();
                System.out.println(s1);
            } catch (ClassNotFoundException | IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 结果:
    // Student{name='小明', age=0, sex='null'}

8.3 InvalidClassException

  • 当我们反序列化时,如果对应的 class 和序列化时相比已经发生了变化,就会出现InvalidClassException,class文件和序列化文件的SerialVersionUID不匹配
  • 如果我们想修改class之后不让SerialVersionUID不发生变化,可以在类中通过显示声明Static final long SerialVersionUID = ... 定义自己的SerialVersionUID
  • IDEA也可以自动生成serialVersionUID,settings -> Editor -> Inspections -> Serialization issues -> Serialization class without 'serialVersionUID' 打上勾即可

8.4 writeObject()与readObject()

  • 对于被transient修饰的字段,除了将transitive关键字去掉之外,还可以在类中添加两个方法:writeObject()与readObject(),如下所示:
//学生类,添加writeObject()和readObject()方法
public class Student implements Serializable {
    private static final long serialVersionUID = 1686151893029100782L;
    private String name;
    private static int age;
    private transient String sex;

    Constructor...

    private void writeObject(ObjectOutputStream oos) throws IOException {   //添加writeObject方法
        oos.defaultWriteObject();
        oos.writeChars(sex);
    }
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { //添加readObject方法
        ois.defaultReadObject();
        sex = String.valueOf(ois.readChar());
    }

    getter and setter...
}

public class DemoObjectOutputStream {
    public static void main(String[] args) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:" + File.separator + "a.txt")));
        ) {
            Student s1 = new Student("小明", 12, "男");
            oos.writeObject(s1);

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

public class DemoObjectInputStream {
    public static void main(String[] args) {
        try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:" + File.separator + "a.txt")))){
            Object s1 = ois.readObject();
            System.out.println(s1);
        } catch (ClassNotFoundException | IOException e) {
            e.printStackTrace();
        }
    }
}

//结果
//Student{name='小明', age=12, sex='男'}
//成功的写入了。。。。

9. 一点小问题

可以看到,java字节流和字符流的输入,都是在遇到文件末尾的时候返回-1,那如果我们直接往文件中写入-1的话,会发生什么情况呢?会不会读取不到之后的信息呢?

FileOutputStream fos = new FileOutputStream("D:a.txt");
    fos.write(-1);
    fos.write(97);
    fos.write((97-256));
    fos.close();

    FileInputStream fis = new FileInputStream("D:a.txt");
    int len = 0;
    byte[] chs = new byte[10];
    len = fis.read(chs);
    System.out.println(Arrays.toString(chs));
    System.out.println(new String(chs, 0, len));
    fis.close();

    FileInputStream fis2 = new FileInputStream("D:a.txt");
    int value = 0;
    while((value=fis2.read())!=-1){
        System.out.print(value+"  ");
    }
    //最后得到的结果是
    //[-1, 97, 97, 0, 0, 0, 0, 0, 0, 0]
    //�aa
    //255  97  97

可以看到,当我们以byte数组接收的时候,接收到的仍然是-1,以int接受的时候返回的是255,这是怎么回事呢?

原来,在java中,byte以一个字节表示的是有符号整数,也就是[-128,127]
当我们传入-1的时候(1000 0001)b,存储的是-1的补码(1111 1111)b
int形式输出的时候,int是32位,自然就是255了,而read()方法返回值就是int啊,所以写入-1,实际上读取到的是int的255,所以不会停止读入
而在用byte[]接收的时候又进行了intbytebyte接收到(11111111)b,自然就输出-1了

我们如果按下面的方式读取,就不会读到任何东西了

FileInputStream fis2 = new FileInputStream("D:a.txt");
    byte value = 0;
    while((value=(byte)fis2.read())!=-1){
        System.out.print(value+"  ");
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值