05-2IO-字节流

05-2IO-字节流,第1张

05-2IO-字节流 1IO流概述

介绍:

I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。流:是一种抽象概念,是对数据传输的总称.也就是说数据在设备间的传输称为流,流的本质是数据传输java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

IO流的使用场景

如果 *** 作的是纯文本文件,优先使用字符流如果 *** 作的是图片、视频、音频等二进制文件,优先使用字节流如果不确定文件类型,优先使用字节流.字节流是万能的流

流的分类

按 *** 作数据单位不同分为:字节流(8 bit),字符流(16 bit)按数据流的流向不同分为:输入流,输出流按流的角色的不同分为:节点流,处理流 (抽象基类)字节流字符流输入流InputStreamReader输出流OutputStreamWriter

1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

2字节流

字节流抽象基类

InputStream:这个抽象类是表示字节输入流的所有类的超类OutputStream:这个抽象类是表示字节输出流的所有类的超类子类名特点:子类名称都是以其父类名作为子类名的后缀

字节流输入常用方法

方法名说明int read()返回读入的一个字节。如果达到文件末尾,返回-1int read(byte[] b)返回读入最多b.length个字节的数据为字节数组int read(byte[] b, int off, int len)将 len字节从指定的字节数组开始,从偏移量off开始读入此文件输入流 一次写一个字节数组的部分数据

字节流输出常用方法

方法名说明void write(int b)将指定的字节写入此文件输出流 一次写一个字节数据void write(byte[] b)将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据void write(byte[] b, int off, int len)将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据 2.1字节输入流

FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名

    // 读取某文件内容读入程序中,并输出到控制台
    // 说明点:
    //1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
    //2. 异常的处理:为了保证流资源一定可以执行关闭 *** 作。需要使用try-catch-finally处理
    // 3. 读入的文件一定要存在,否则就会报FileNotFoundException
    @Test
    public void test04() throws Exception{
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("E:\a.txt");
            int len;
            while ((len = fis.read()) != -1) {
                System.out.print((char) len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            fis.close();
        }
    }
    //read有参构造
    @Test
    public void test05(){
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("E:\a.txt");
            byte[] bytes = new byte[10];//和字符的区别只需把byte换成char,数组长度一般使用1024
            int len;
            while ((len=fis.read(bytes))!=-1) {
                //方式1
           
                // for (int i = 0; i < len; i++) {
                //     System.out.print(chars[i]);
                // }
                //方式2
                // String str = new String(chars);//和for循环chars[]的错误是一样的
                String str = new String(bytes,0,len);
                System.out.print(str);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
2.2字节输出流

FileOutputStream(String name):创建文件输出流以指定的名称写入文件

    @Test
    public void test01() throws IOException {
        //创建字节输出流对象
      	
        //FileOutputStream(String name):创建文件输出流以指定的名称写入文件
        FileOutputStream fos = new FileOutputStream("E:\a.txt");

        //void write(int b):将指定的字节写入此文件输出流
        fos.write(97);
//        fos.write(57);
//        fos.write(55);

        //最后都要释放资源
        //void close():关闭此文件输出流并释放与此流相关联的任何系统资源。
        fos.close();
    }

字节输出流换行和追加

追加

默认输出流的write方法是覆盖以前的数据

public FileOutputStream(String name,boolean append)创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头

换行 *** 作

windows:rn

linux:n

mac:r

    @Test
    public void test02() throws IOException {//通过异常 finally块关闭流资源
        //创建字节输出流对象
        FileOutputStream fos = null;
        try {
            new FileOutputStream("E:\a.txt",true);
            //写数据
            for (int i = 0; i < 10; i++) {
                fos.write("hello".getBytes());
                fos.write("rn".getBytes());
            }
        }catch (Exception e){
            e.fillInStackTrace();
        }finally {
            if (fos!=null){
                //释放资源
                fos.close();
            }
        }
    }

复制一个文件到指定目录

    @Test
    public void test4() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader("E:\a.txt");
            fw = new FileWriter("E:\new.txt");
            char[] chars = new char[10];
            int len;
            while ((len=fr.read(chars))!=-1){//读入内存中
                //写到文件中
                // String str = new String(chars,0,len);
                // System.out.print(str);
                // fw.write(str);
                fw.write(chars,0,len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fr.close();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
3字符流

由于字节流 *** 作中文不是特别的方便,所以Java就提供字符流

字符流的基类是Reader和Writer

字符流 = 字节流 + 编码表

常用方法
刷新和关闭的方法

方法名说明flush()刷新流,之后还可以继续写数据close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

其他方法参考字节流

3.1编码表

字符集

是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等

常见的字符集

ASCII字符集:

lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)

基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等

GBXXX字符集:

GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等

Unicode字符集:

UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码

编码规则:

128个US-ASCII字符,只需一个字节编码

拉丁文等字符,需要二个字节编码

大部分常用字(含中文),使用三个字节编码

其他极少使用的Unicode辅助字符,使用四字节编码

字符串中的编码解码

相关方法

方法名说明byte[] getBytes()使用平台的默认字符集将该 String编码为一系列字节byte[] getBytes(String charsetName)使用指定的字符集将该 String编码为一系列字节String(byte[] bytes)使用平台的默认字符集解码指定的字节数组来创建字符串String(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来创建字符串
    @Test
    public void test01() throws UnsupportedEncodingException {
        String s = "中国";

        //byte[] bys = s.getBytes(); //[-28, -72, -83, -27, -101, -67]
        //byte[] bys = s.getBytes("UTF-8"); //[-28, -72, -83, -27, -101, -67]
        byte[] bys = s.getBytes("GBK"); //[-42, -48, -71, -6]
        System.out.println(Arrays.toString(bys));

        //String ss = new String(bys);
        //String ss = new String(bys,"UTF-8");
        String ss = new String(bys,"GBK");
        System.out.println(ss);
    } 
4缓冲流

开发中字节流和字符流使用的较少,一般使用缓冲流,提高读取效率

在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。

复制一个文件到指定目录

    //实现文件复制的方法
    public void copyFileWithBuffered(String srcPath,String destPath){
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[1024];
            int len;
            while((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(bis != null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }
    }

    @Test
    public void testCopyFileWithBuffered(){
        long start = System.currentTimeMillis();

        String srcPath = "C:\Users\Administrator\Desktop-视频.avi";
        String destPath = "C:\Users\Administrator\Desktop-视频.avi";


        copyFileWithBuffered(srcPath,destPath);


        long end = System.currentTimeMillis();

        System.out.println("复制 *** 作花费的时间为:" + (end - start));//618 - 176
    }

在使用缓存流之后明显节省了很多的时间

5其他流 5.1转换流

InputStreamReader:是从字节流到字符流的桥梁,父类是Reader

它读取字节,并使用指定的编码将其解码为字符

它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

OutputStreamWriter:是从字符流到字节流的桥梁,父类是Writer

是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节

它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

    @Test
    //转换流
    public void test02() throws Exception{
        FileReader fr = new FileReader("E:\a.txt");
        int len;
        char[] buff = new char[10];
        while ((len=fr.read(buff))!=-1){
            System.out.print(new String(buff,0,len));//出现乱码问题
        }
        fr.close();//生产应该用finally关闭流
    }
    @Test
    //InputStreamReader
    public void test08() throws Exception{
        InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\a.txt"),"utf-8");
        int len;
        char[] buff = new char[10];
        while ((len=isr.read(buff))!=-1){
            System.out.print(new String(buff,0,len));//出现乱码问题
        }
        isr.close();//生产应该用finally关闭流
    }
    //OutputStreamWriter没有演示参考其它输出流
5.2对象流

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

序列化:用ObjectOutputStream类保存基本类型数据或对象的机制反序列化:用ObjectInputStream类读取基本类型数据或对象的机制

ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

对象的序列化(****)

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象

序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原

序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是JavaEE 平台的基础

如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常

SerializableExternalizable

public class Student implements Serializable {
    private String name;
    // private int age;//修改该类信息后会报InvalidClassException
    public int age;
//get set方法
}

    @Test
    //序列化
    public void test1() throws Exception{//没有try catch异常
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\a.txt"));
        Student student = new Student("张飞",13);
        oos.writeObject(student);
        oos.close();
    }
    @Test
    //反序列化
    public void test2() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\a.txt"));
        Object o = ois.readObject();//修改该类信息后会报InvalidClassException
        System.out.println(o);
    }

用对象序列化流序列化了一个对象后,修改了对象所属的类文件,读取数据会出问题,抛出InvalidClassException异常

解决方法

重新序列化

给对象所属的类加一个serialVersionUID(private static final long serialVersionUID = 42L;)

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;

serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。

如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

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

如果一个对象中的某个成员变量的值不想被序列化,使用transient关键字修饰

public class Student implements Serializable {
    private static final long serialVersionUID = -7619734160029632002L;
    private String name;
    private int age;//修改该类信息后会报InvalidClassException
    // public int age;
    private transient String passWord;//密码不想被序列化
}
    @Test
    //序列化
    public void test1() throws Exception{//没有try catch异常
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\a.txt"));
        Student student = new Student("张飞",13,"sodifd");
        oos.writeObject(student);
        oos.close();
    }
    @Test
    //反序列化
    public void test2() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\a.txt"));
        Object o = ois.readObject();//
        System.out.println(o);
    }

测试结果:passWord的值是为空的

谈谈你对 java.io.Serializable 接口的理解,我们知道它用于序列化, 是空方法接口,还有其它认识吗? 实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿 *** 作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。 由于大部分作为参数的类如String、Integer等都实现了java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5708053.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存