Day20 --IO流对象 字节流

来源:互联网 发布:陕西广电网络是国企嘛 编辑:程序博客网 时间:2024/05/16 14:57
 a.
    IO流
        概述
            * IO流用于处理设备之间的数据传输  --局域网、互联网之间的数据传输底层都是用的IO流
            * Java中对于数据的操作,就是通过流的方式 
            * Java操作的流对象都是在IO包中
            * 流按流向分为:输入流 输出流
            * 流按操作分为:
                * 字节流:
                    * 字节流可以操作任何数据,因为计算机中的数据都是以字节的形式存储的。
                        * 因为不同国家语言不同,就存在不同的码表,码表不同读取字节数据就必须转换成字符来读写,这样方便阅读。
                        * 比如不同国家照片的,在不同国家的显示是不同的,虽然照片相同,但照片的字是不同,这个时候就需要有字节来操作,将字节转字符。     
                * 字符流:
                    * 字符流只能操作纯字符数据,比较方便
        
        IO流中常用父类
            字节流抽象父类
                * InputStream --字节输入流的顶层抽象父类
                    * 读(从硬盘中读取数据:内存 - 硬盘)创建FileInputStream,如果没有就创建该文件后,再做其他操作
                * OutputStream --字节输出流的顶层抽象父类
                    * 写(往硬盘中写入数据:硬盘 - 内存) 创建FileOutputStream。如果没有该文件,会先帮我们创建,前提可以没有该文件
            
            字符流抽象父类
                * Reader 字符输入流 读
                * Writer 字符输出流 写    
        
        IO流的书写过程
            * 使用前,导入IO包中的类  
            * 使用时,对IO流进行异常处理
            * 使用后,释放IO流资源
        
        IO流就相当于一个管道,操作的是 硬盘 和 内存 之间的数据。
            从硬盘中读数据 到 内存中。 InputStream
            从内存中写数据 到 硬盘中。OutputStream
        可以这样理解写的数据: 在写数据的时候,没有保存之前在内存中,当一旦保存,该数据就到硬盘中了。     
        


b.
    FileInputStream --字节输入流 过程:硬盘到内存 显示给我们看(控制台),虚拟显示。
        概述
            * 从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。 
        方法
            * read(); 读数据(从 硬盘 中读一个字节数据到 内存 上) --从硬盘文件中读取一个字节数据
                 * 每read()一次,指针就往后走一位,当读到末尾数据(不存在的数据)就返回-1,所以可以将-1 作为文件内容的结束标记,同时可以作为循环判断的一个子条件。
                 * 当读到-1,就表示文本内容数据已经结束。 
            * 读的时候,该文件必须存在,如果不存在就报异常。
                 
        FileNotFoundException 表示文件找不到异常
        FileNotFoundException 是 IOException 的一个子类,所以自己抛IOException即可    

        代码
        String url = "数据.txt";
        FileInputStream fis = null;
        try {
            File file = new File(url); // 创建File对象,用于获取文件路径
            fis = new FileInputStream(file); //创建输入流对象,如果该文件不存在,就报异常
            int read = fis.read();  // 从硬盘文件中读取字节数据
            System.out.println(read); //97  此时只能读出一个字节数据
            /**
             * 为什么文本中是a,而输出的是97呢?
             * 首先,我们用的是FileInputStream 字节输入流来操作
             * 其二,因为计算机中存储的数据都是以0101这种二进制的形式进行存储的
             * 97在ascii码表中代表a, 97就是a的二进制表现形式。更确切的是是GBK码表(win系统用的就是GBK码表)。因为ascii码表攘扩到每一种码表中(因为每个码表都需要数字和英文字母)
             * 而我们在文本中的写的a 就是计算机通过码表翻译成自己能看得懂的97的二进制表现形式。(a在计算机中代表的就是97的二进制表现形式)
             * */
             
//        如果想将文件中的数据全都读出来,就用while循环来做
            int r ; //定义临时变量r,让其记录每次读到的文本数据
            while((r = fis.read()) !=-1){ //当读到-1,就表示文本内容已经结束
//                r = fis.read() 将每次读到的文本数据赋值给r
                System.out.println(r); //97 98 99 
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            fis.close(); //关闭IO流资源
        }    
    
分析:为什么read()的返回值类型是int,而不是byte?
我的说法
    注意:int(占4个字节) byte(占1个字节)  int转byte会损失精度。
    * 字节输入流可以读取任意文件(如:音频,图片,文本等),这些文件底层都是以二进制的形式进行存储的。
    * 当使用字节流的read()方法时,读到文件末尾都会有一个结束标记-1,遇到-1系统就以为该文件已经读取完毕。 如:一个图片文件占4个字节,byte的表现形式就是: 00000111 00110000 11111111 00100011,而11111111刚好是byte-1的表现形式。如果采用byte类型来接收,当读到11111111的时候,系统就以为读取到该文件的末尾处,那么该数据就会读取不完整。为了避免情况,系统采用int来作为接收类型。如果读到该图片文件的其中一个字节正好是11111111,就会在前面补上24个0, 24个0等等于3个8个比特位。(24+8[读取到的当前一个字节]=32)/4=8。而11111111刚好是正的255,而255!=-1,就会继续往后读取该文件的其他字节数。而结束标记-1就是一个int类型。
    问?当补上24个0的时候,该文件数据就改变了,那该怎么办?
    答:字符流还有一个write()写的方法,在写出该数据时,会将其24个0给砍掉,来保证原有数据的原样性。
        所以设计用int来接收,而结束标记-1就是一个int类型,无需补上24个0了。

    注意:int类型占4个字节,每个字节都会在前面补上24个0, 24个0等等于3个8个比特位。(24+8[读取到的当前一个字节]=32)/4=8,即int占4个字节。 
    
老师说法
        因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111
        那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上
        24个0[每8个比特位是一个字节]凑足4个字节,那么byte类型的-1就变成int类型的255。这样可以保证整个数据读完,而结束标记的-1就是int类型
        
        

c.
    FileOutputStream --字节输出流 过程:内存到硬盘 存储到硬盘中,实际存在了。
        概述
            * 文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
            * 将数据写入到硬盘中(内存 - 硬盘)。
        方法
            * write(int b); 写数据(将数据从 内存 写到 硬盘中)。
                * 写的时候,可以没有该文件,会在写的过程中给自动创建。
                * 前提:写到磁盘中的路径必须存在。
                
        代码
            FileOutputStream fos = null;
        try {
            String pathName = "write.txt";
            File file = new File(pathName);
            fos = new FileOutputStream(file); //创建字节输出流对象,如果没有该文件,系统会自动创建
            fos.write(97); // 往硬盘中写入数据 a   虽然写出的是一个int数,但实际写到文件上的是一个字节,会自动去除前3个8位,也就是一个byte。
            fos.write(98); // 往硬盘中写入数据 b [写入数据后,刷新该项目(其实刷新的是该文本write.txt)]
        } catch (Exception e) {
            e.printStackTrace();
        }
        fos.close();  //关闭资源
//        因为操作的该系统的GBK码表,所以写的时候会翻译成对应GBK中的ascii码表中对应的数据。
    

    FileOutputStream的追加?
        概述
            * 就是在原有数据的文本上再添加数据。
        实现
            * 操作的是 构造方法
            * FileOutputStream fos = new FileOutputStream(路径,true);
            * 如果想续写文件数据,在构造方法中添加第二个参数,true。
            * 如果没有参数true,就是将原有文件数据清空后,再重写写入数据。 
            
        代码
        String name ="write.txt";
        File file = new File(name);    
//        FileOutputStream fos = new FileOutputStream(file);  如果没有参数true,就是将原有文件数据清空,再重写写入数据。
        FileOutputStream fos = new FileOutputStream(file, true); //构造参数添加第二个参数true后,才能在文本中追加数据。
        fos.write("11".getBytes()); //如果想添加 非数字的数据,就要用getBaytes()将其转成字节。
        fos.close();
    

d.
    拷贝图片
        概述
            * 分析:
                * 拷贝图片,是将原有图片重新生成一份。

        代码
        拷贝图片
            String pathName = "美女.jpeg";
            File file = new File(pathName);
            FileInputStream fis = new FileInputStream(file); //创建字节输出流对象
            FileOutputStream fos = new FileOutputStream("copy美女.jpeg"); //创建字节输出流对象
            
            int r;
            while((r = fis.read()) != -1){ //首先读取原图片文件数据
                //然后写出原图片数据到copy美女.jpeg中
                fos.write(r);
            }
            
    //        关流操作
            fis.close();
            fos.close();
    --但是不推荐这样做,因为如果操作音频等大一些的文件的时候,效率太慢,所以使用数组的方式读取并写入。
        拷贝音频
            Date d = new Date();
            long start = d.getTime();
            String pathname ="music.mp3";
            File file = new File(pathname);
            FileInputStream fis = new FileInputStream(file);
            FileOutputStream fos = new FileOutputStream("copyMusic.mp3");
            int r ;
            while((r = fis.read()) != -1){
                fos.write(r);
            }
            long end = System.currentTimeMillis();
            long time = end - start;
            long resultTime  = time / 60 / 60;
            System.out.println("花费时间:"+resultTime+" 秒"); //19秒
        

    字节数组拷贝之available()方法 --available:adj. 可获得的;可购得的;可找到的;有空的
        概述
            * available 表示一次性读取该文件的所有大小    
        方法
            * available(); 获取读的文件的所有字节个数
            * int read(byte[] b); 一次读取一个字节数组
            * wirte(byte[] b); 一次写入一个字符数组
        存在弊端:有可能内存溢出,如果读取的文件太大,那么可能导致内存异常。

        代码
                    
        long start = new Date().getTime();
        
        String pathname = "张静波 - 这支烟灭了以后.mp3";
        File file = new File(pathname);
        
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream("copy张静波 - 这支烟灭了以后.mp3");
//        定义数组来设置每次读取的大小
        byte[] arr = new byte[fis.available()]; //该方法是获取该文件的数据大小
        System.out.println("该文件大小:"+fis.available());
        fis.read(arr);  //一次性全部读取。 --将文件上的字节读取到内存中
        fos.write(arr); //一次性全部写入到 copy张静波 - 这支烟灭了以后.mp3 中。 --将字符数组中的数据全部写入到copy文件中
    
        
        long end = System.currentTimeMillis();
        long time = (end - start) / 60 / 60;
        fis.close();
        fos.close();
        
        System.out.println("使用available方法读取后,花费的时间是:"+time+" 秒");  //0秒
        
//        原来一个一个字节的读取,花费7秒
//        使用available后读取,花费0秒,速度超快,但是这样做有弊端,如果遇到很大的文件,可能导致内存溢出异常
//        所以采用自定义的数组来记录每次读取的大小。

        
c.
        定义小数组
            概述
                * 因为 一个个读取字节个数 和 一次性读取字节个数都存在弊端,所以建议自己 使用定义小数组的方式 来自定义的每次读取和写入的字节个数。
            方法
                * 系统考虑到这一点,就为我们提供了这样的方法供我们使用。
                * write(byte[] b);
                * wirte(byte[] b, int off, int len); 写出有效的字节个数
                    * 参数说明
                        * byte[] b; 自定义的字节数组大小
                        * int off; 表示数组中当前数据的索引,一般定义为0 
                            * 0 表示当前字节数组每个数据的索引(即根据索引拿数据就不会出错)
                        * int len; 每一次读取到的有效字节个数,当没有数据就返回-1

            定义小数组的标准格式
                * 为什么字节数组定义1024的整数倍? 
                    * 即: byte[] b = new byte[1024*8];
                * 因为计算机中的机制就是1024。即2的10次方。
                * 注意:如果read()中没有传入参数,那么返回的不是读取的字节个数,而是字节的码表值
                
            代码
                long start = new Date().getTime(); //记录当前时间
                String pathname ="Dima Bilan.mp3"; 
                File file = new File(pathname); 
                FileInputStream fis = new FileInputStream(file); // 创建字节输入流
                FileOutputStream fos = new FileOutputStream("copyDima Bilan.mp3"); // 创建字节输出流
                byte[] b = new byte[1024 * 8];  // 定义小数组的大小 建议是1024的整数倍
                int len; //记录每次读取字节数组的数据大小
                while(( len = fis.read(b)) !=-1){
                    fos.write(b, 0, len); //0表示当前字节数组每个数据的索引(即根据索引拿数据就不会出错)
                }
                long end = System.currentTimeMillis(); //记录拷贝完后的时间
                long resultTime = (end - start) / 60 /60; //将两个时间相减,拿到拷贝音乐一共用了多久
                System.out.println("拷贝一首歌曲花费:"+ resultTime +"秒"); //:0秒
                
                fis.close(); //关闭IO流资源
                fos.close(); //关闭IO流资源


d.
    BufferedInputStream 和 BufferedOutputStream 拷贝文件(缓冲区的方式copy)
        概述
            * 字节流一次读写一个数组明显要比一次读写一个字节的速度要快,
            * 这是加入数组这样的缓冲区效果,Java本身在设计的时候,也考虑到这样的设计思想(装饰设计模式),所以提供了字节缓冲区流。
            * BufferedInputStream 字节输入缓冲区流  --对FileInputStream进行包装
                * BufferedInputStream内置了一个缓冲区(数组)
                * 从BufferedInputStrem中读取一个字节时,BufferedInputStream会一次性从文件中读取8192个,存入到缓冲区中,返回给程序一个字节
                * 程序再次读取时,就不用从文件中拿去,而是直接从缓冲区中获取
                * 直到缓冲区中的8192个被全部使用过以后,才会重新从文件中读取8192个
                
            * BufferedOutputStream 字节输出缓冲区流 ----对FileOutputStream进行包装
                * BufferedOutputStream 同样内置了一个缓冲区(数组)
                * 程序向流中写出字节时,不会直接写到文件中,而是先写到缓冲区中,
                * 直到缓冲区写满8192个,BufferedOuputStream才会把缓冲区中的数据一次性写到文件里    
        
        * 过程
                *     读的时候一次读取8192个字节给BufferedInputStream,但int r 是一个一个从bufferedInputStream拿每一个字节, r再一个一个的写入到BuffereOutputStream中,直到写出了8192个后,再给copy
                *  源文件  FileInputStream  - BufferedInputStream - int r  - BuffereOutputStream - FileOutputStream  -  copy文件
                    
         注意:
             缓冲区就是操作内存, 内存之间传输数据要比硬盘的速度要高,所以降低到硬盘的读写次数就会提高效率。
             
         * 使用缓冲区    
             源文件  FileInputStream  - BufferedInputStream - int r  - BuffereOutputStream - FileOutputStream  -  copy文件   --其实操作的就是 内存与内存之间的读写数据(减少对硬盘的访问读写次数,从而提高效率。但是这是操作的是两个数组)
         * 定义小数组    
             源文件   - int r(一次性记录自定义的数组大小)  - FileOutputStream - copy文件  --其实操作的就是 硬盘到内存与内存硬盘之间的读写数据 (并没有减少对硬盘的读写次数,但是也是使用到了缓冲区,但是使用的缓冲区的次数并没有那么多。这是操作的是一个数组)

        代码
            String pathname ="是阿涵阿 - 不想.mp3";
        File file = new File(pathname); 
        FileInputStream fis = new FileInputStream(file); // 创建字节输入流
        BufferedInputStream bif = new BufferedInputStream(fis); // 创建缓冲流对象,对字节输入流进行保证,让其变得更加强大。
        
        FileOutputStream fos = new FileOutputStream("copy是阿涵阿 - 不想.mp3"); // 创建字节输出流
        BufferedOutputStream bos = new BufferedOutputStream(fos); // 创建缓冲流对象,对字节输出流进行保证,让其变得更加强大。
        
        int r ;
        while((r = bif.read()) != -1){
            bos.write(r);
        }
        
//        关流 只关Buffere类的流对象,因为以后的书写形式直接Buffere传入匿名内部类的方式,所以只关Buffere的
        bif.close();
        bos.close();

自定义小数组和带Buffere的读写速度哪个更快。
        * 如果定义小数组8192个字节大小和Buffere自带的8192个字节相比较的话
        * 定义小数组的能比Buffere的快那么一点点,因为读写操作的都是同一个数组
            * (FileInputStream 和 FileOutputStream)
        * 而Buffered操作的是两个数组
            * (FileInputStream 和 BufferedInputStream, FileOutputStream 和 BufferedOuputStream)


e.
    flush 和 close 方法区别?
        * flush(); 是用来刷新缓冲区的,刷新完后还可以继续使用流对象。
            * 好处:做QQ聊天记录,使用缓冲区的输出流)关闭聊天窗口后,下次打开还能看到原先聊天记录,而不是将QQ下线后,再能看原先的聊天记录。(即使用流对象)
        * close(); 是关闭释放资源的,但是在关闭资源之前会刷新一次缓冲区,将缓冲区中的数据全部输出到文件中,再关闭。刷新完后就不能再使用流对象了。
            * 使用close()确保copy数据的完整性。
            
// 不关流,可能导致copy后数据的不完整性
//        源文件数据大小:   10,881,245  bytes
//        copy后的数据大小: 10,878,976  bytes 
不使用close(),有可能导致copy后的数据时不完整性,因为自定义的小数组中还剩余一部分字节,而刷新的只是自定义数组缓冲区的整数倍数据,可能源文件数据大小不是缓冲区的整数倍大小,所以导致copy后的数据是不完整的。


f.    
    字节流读取 和 写出中文
        字节流读取中文的问题 --读取中文会乱码,只能用字符流来解决。
            * 字节流在读写中文的时候,可能有时候会读到半个中文,而导致文件乱码。所以读中文只能用字符流来读取。
        字节流写出中文的问题 --写中文可以用getBytes()将其转成字节数组来解决。
            * 因为字节流字节操作的是字节,所以在写出中文的时候要将其转换为字节数组,使用getBytes()方法。
            * 如果想写出回车换行,就用write("/r/n".getBytes());


g.
    流的标准处理异常代码 --分1.6及1.6之前 和 1.7版本 【1.6面试必问】
         1.6及1.6之前处理异常代码  --try{}...finally 采用finally嵌套的方式

        代码
        public static void demo01_jdk6即6之前_流异常代码的处理() throws IOException{
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try{
            fis = new FileInputStream("aaa.txt");
            fos = new FileOutputStream("copyaaa.txt");
            
            int r;
            while( (r = fis.read()) != -1){
                fos.write(r);
            }
        }finally{
            try {
                if(fis != null)  //如果fis关闭不了,就关闭fos
                    fis.close();
            } finally {   // try..catch 嵌套的目的是能关一个就尽量关一个
                if(fos != null)
                    fos.close();
            }
        }
            
    
         1.7处理异常代码 --try(){}...close(此处的close并没有写,会自动关闭资源。因为实现了InputStream 间接的实现了 AutoCloseable接口)

        代码
        public static void demo02_jdk7_流异常代码的处理() throws IOException{
        try(
                FileInputStream fis = new FileInputStream("aaa.txt");
                FileOutputStream fos = new FileOutputStream("copyaaa2.txt");
                MyClose c = new MyClose();
            ){ 
            int r;
            while( ( r = fis.read()) != -1){
                fos.write(r);
        }
        
        }
    }
    注意:
        原理
        * 在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用,流对象的close方法将流关掉 
        *【如果一个自定义类想实现自动关闭功能,必须实现 AutoCloseable 接口。】



f.
    图片加密 (简单方法) --改变的是每个字节的大小,从而完成对该文件的加密 和 解密 过程。
        概述
            * 图片加密不是对源文件进行操作,而是根据源文件生成一个加密后的文件,解密也是拿加密后的文件进行解密,从而生成一个新的解密的文件。
            
        加密
            * 将写出的每个字节都 异或 一个数,就是对源文件进行加密。
            * 将加密后的文件也 异或 加密前的那个数,就是对copy后的文件进行解密(会重新生成一个解密后的文件) 
            
        注意:一个数被 异或 两次,还是该原数本身(原来的数),根据这个思想对图片文件进行加密和解密操作。

        加密和解密过程:
            * 加密的时候 对源文件数据进行了 一次异或  这就是加密。
                *     操作的是 源文件数据  和 copy_原图片文件_(加密)
            * 解密的时候 对加密的文件数据进行又 一次异或   相当于 对源文件数据 异或两次 还是源文件数据。
                * 操作的是 copy_原图片文件_(加密) 和     copy_原图片文件_(加密)_[解密过程].jpg

g.
    拷贝文件
        案例:在控制台录入文件的路径,将文件拷贝到当前项目路径下
        代码:Day20_IO流对象 字节流\src\com\day20\b\test\io\Test02_控制台上录入路径_将文件copy到当前项目路径下.java
        

        案例:将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到quit时就退出
        代码
         FileOutputStream fos = new FileOutputStream("键盘录入数据copy到该文件中.txt");
         Scanner sc = new Scanner(System.in);
         System.out.println("请录入:");
         while(true){
             String str = sc.nextLine();
             if(str.equals("quit")){
                 System.out.println("遇到quit,输出结束");
                 break;  // break结束该循环
                }
                fos.write(str.getBytes()); // 字符串写出 必须转换成字节数组
                fos.write("\r\n".getBytes()); //换行输出
                }
             fos.close();
原创粉丝点击