IO包 之流

作者: acc8226 | 来源:发表于2017-01-28 21:25 被阅读37次
    IO(Input Output)流
    • IO流用来处理设备之间的数据传输
    • Java对数据的操作时通过流的方式
    • Java用于操作流的对象都在IO包中
    • 流按操作数据分为两种: 字节流和字符流
    • 流按类型分为: 输入流, 输出流
    IO流常用基类
    • 字节流的抽象基类对象: InputStream, OutputStream
    • 字符流的抽象基类对象: Reader, Writer
      注:有这四个类派生出来的子类名称都是由其父类名作为子类名的后缀.
      如:InputStream的子类FileInputStream
      如:Reader的子类FileReader
    完整的流家族
    输入流与输出流的层次结构 Reader 和 Writer 的层次结构
    FileReader和FileWriter流

    //创建一个FileWriter对象,该文件会在指定目录下创建.如果同名则覆盖,除非构造方法第二个参数append 为 true;

        /**
         * 
         * @param  str
         *         String to be written
         *         
         * @throws IOException
         */
        public static void createFile(String str) throws IOException{
            //在D盘下创建abc.txt. 如果同名则覆盖,除非构造方法第二个参数append为true;        
            File file = new File("D:" + File.separator + "abc.txt");        
            FileWriter fw = new FileWriter(file, false);             
            fw.write(str);                      
            fw.flush(); //关闭流对象,之前会flash一次缓冲中的数据.      
            fw.close(); //与flush的区别: flush刷新后流可以继续使用,close却将流关闭,不可再写入       
        }   
    

    IO异常的标准处理方式一(以FileWriter为例)

            String fileName = "D:" + File.separatorChar + "abc.txt";
            
            FileWriter fw = null;       
            try {
                fw = new FileWriter(fileName);
                fw.write("balabala...");
                fw.flush();
            } catch (IOException e) {
                //检查异常转为非检查异常
                throw new RuntimeException("产生IO异常");
            } finally {
                if (fw != null)
                    try {
                        fw.close();
                    } catch (IOException e) {
                        throw new RuntimeException("流关闭异常");
                    }
            }
    

    IO异常的标准处理方式二(SE 7 try-with-resources方式)

            String fileName = "D:" + File.separatorChar + "abc.txt";
    
            try (FileWriter fw = new FileWriter(fileName)) {
                fw.write("balabala...");
                fw.flush();
            } catch (IOException e) {
                //检查异常转为非检查异常
                throw new RuntimeException("产生IO异常");
            }
    

    使用FileReader读取文本文件方式一

         int ch = 0;
         while((ch=fr.read())!=-1){
              //relevant operation
         }
    

    使用FileReader读取文本文件方式二(较方法一好,推荐使用)

     int len= 0;
         char[] buf = new char[1024];
         while((len=fr.read(buf))!=-1){
              //relevant operation
         }
    

    拷贝文件(建议使用方式二)
    //relevant operation --> fileWriter.write(buf,0,len);

    字符流的缓冲流BufferedReader与BufferedWriter
    • 提高了对数据的读写效率
    • 对应类:BufferedReader和BufferedWriter
    • 缓冲区要结合流才可以使用
    • 在流的基础上对流的功能进行了增强

    **BufferedWriter **

    • 为提高字符写入流的效率,只要将需要提高效率的流对象作为参数传递到BufferedWriter的构造方法.
    • BufferedWriter有自己特有的readLine()方法, 这是不包含行结束符的
    • 如果需要每次换行则bfr.newLine(), 并且还要flush()一下.最后不要忘记close流.
      //我的理解是BufferedWriter和BufferedReader都是基于原流,且衷于原流.提供了每行的写与读,而不参杂多余的行终止符.所以要每次自己换行.

    BufferedReader
    读取数据

            String line = null;
            while((line=bfr.readLine())!=null){
                //relevant operation
            }
    

    使用BufferedWriter, BufferedReader拷贝文件关键代码

             String line = null;
             while((line=bfr.readLine())!=null){
                  bfr.write(line);
                  bfr.newLine();
                  bfr.flush();
             }
    

    牵扯到装饰设计模式

    • 当先要对已有对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能.
    • 装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能.
    • 装饰模式比继承要灵活,避免了继承体系臃肿.而且降低了类与类之间的关系,装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能.所以装饰类和比装饰类通常是都属于一个体系中.

    LineNumberReader
    BufferedReader()的子类,只是多了标号而已.
    通过setLineNumber设置初始行号, 和输出可以getLineNumber获取每行的行号

    字节流FileOutputStream和FileInputStream

    可以进行二进制形式进行图片, 音乐等文件的读写.

         //拷贝一个图片
         public static void copyFile() throws IOException{
              InputStream fis = new FileInputStream("D:\\source.jpg" );
              OutputStream fos = new FileOutputStream("D:\\dst.jpg" );
               int len = 0;
               byte[] buf = new byte[1024];
               while((len=fis.read(buf))!=-1){
                  fos.write(buf, 0, len);
              }
              fis.close();       
    

    拷贝一首歌(使用字节流的Buffered缓冲区)

    InputStream inputStream = new BufferedInputStream(
              new FileInputStream("D:" + File.separator + "刘涛 - 说不出口.mp3"));        
    OutputStream outputStream = new BufferedOutputStream(
              new FileOutputStream("D:" + File.separator + "刘某 - 就不要说.mp3"));       
    
    键盘录入(InputStreamReader和OutputStreamWriter转换流)

    System.out: 对应的是标准输入设备,控制台
    System.in: 对应的是标准输出设备,键盘

    练习: 通过键盘录入,当输入一行数据后将改行数据进行打印,如果录入的数据是over,那么停止录入.

    //方法一: 传统思考
    //要点记住回车的处理方式:  ASCII码 13 '\r', 10 '\n'
    public static void method1() throws IOException{        
            final InputStream in = System.in;
            int character ;
            final StringBuilder stringBuilder = new StringBuilder();
            while(true){
                character = in.read();
        
                switch (character) {
                case '\r':              
                    break;
                case '\n':
                    final String result = stringBuilder.toString();
                    if("over".equals(result)){
                        return;
                    }   
                    //StringBuilder的清空方式
                    stringBuilder.delete(0, stringBuilder.length());
                    break;
                default:
                    stringBuilder.append((char)character);
                    break;
                }
            }   
        }
    
    //方法二: InputStreamReader转换流,将字节流转成字符流的桥梁,然后经缓冲包装提高效率    
    public static void method2() throws IOException{        
            final InputStream in = System.in;
            final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
            String line = null;
            while ((line = bufferedReader.readLine())  != null) {
                if("over".equals(line)){
                    break;
                }
            }       
        }
    
    //方法三: Scanner  [5.0]
        public static void method3(String[] args) throws IOException {  
            final InputStream in = System.in;
            String line = null;
            final Scanner scanner = new Scanner(in);
            while(scanner.hasNextLine()){
                line = scanner.nextLine();
                if("over".equals(line)){
                    break;
                }           
            }       
        }
    
    OutputStreamWriter转换流

    和InputStreamReader类似,是字符流通向字节流的桥梁.只是包装System.out在缓冲后.

    OutputStream os = System.out;       
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os); 
    outputStreamWriter.write("cba");    
    outputStreamWriter.flush();     
    outputStreamWriter.close();
    
    流操作规律
    1. 明确源和目的
    2. 是否是纯文本(字节流 和 字符流的选取), 具体使用哪个对象
    3. 是否需要提高效率而加入缓冲
      这其中涉及到的OutputStream(OutputStream out, String charsetName)就是字符转字节的桥梁,并可以指定自定义编码,例如"UTF-8",这也是转换流出现的原因.
    改变标准输入输出设备

    System的setIn()方法 重新分配“标准”输入流。否则标准输入流一般都是键盘InputStream.
    System的setOut()方法 重新分配“标准”输出流。否则标准输入流一般都是键盘PrintStream.
    可以利用这两个已关联的流进行相关操作

    打印流 PrintStream和PrintWriter

    该流提供了打印方法,可以将各种类型的数据原样打印.

    PrintStream
    PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream;这意味着可在写入 byte 数组之后自动调用 flush 方法,可调用其中一个 println 方法,或写入一个换行符或字节 ('\n')。

    PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。

    PrintWriter

    • 增加了字符输出流Writer
            // 设置自动刷新
            PrintWriter pw = new PrintWriter(System.out, true);
            String line = null;
            BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
            while ((line = bfr.readLine()) != null) {
                // 这样写比用BufferedWriter更简洁.PrintWriter更适合打印各种数据.
                pw.println(line);
            }
    

    序列流SequenceInputStream(**表示其他输入流的逻辑串联,没有对应的输出流)

    练习: 文件的分割与合并

         //切割只用字节流,而不是字符流
         public static void split() throws IOException{
              FileInputStream fis = new FileInputStream("d:\\123.pdf" );
              FileOutputStream fos = null;
               int len = 0;
               //1M=1024KB=1024*1024字节 存储
               byte[] buf = new byte[1024*1024];
               int i=0;
               while((len=fis.read(buf))!=-1){
                  fos = new FileOutputStream("d:\\" +(++i)+ ".part");
                  fos.write(buf, 0, len);
                  fos.flush();
                  fos.close();;
              }
              fis.close();       
         }
         //文件合并
         public static void meger() throws IOException{
              Vector<FileInputStream> v = new Vector<FileInputStream> ();
               for(int i=1;i<4;i++)
                  v.add( new FileInputStream("d:\\" + i+".part"));
              SequenceInputStream sis = new SequenceInputStream(v.elements());
              FileOutputStream fos = new FileOutputStream("d:\\kk.pdf" );       
               int len = 0;
               byte[] buf = new byte[1024];
               while((len=sis.read(buf))!=-1){
                  fos.write(buf, 0, len);
              }
              sis.close();
              fos.close();
         }
    
    操作对象的ObjectInputStream与ObjectOutputStream
    • 被操作的对象需要实现Serializable(标记接口)
    • 可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID
      ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
      这样生成新的类不会改变UID,而不是使用系统生成的UID.
    • 另外非静态成员变量可以transient修饰不被序列化,同样类(static)变量也不会序列化.
    16. 管道流PipedInputStram和PipedOutputStream
    • 输入输出可以直接连接,结合线程使用

    PipedInputStream,接收InputStream对象
    用于与另一输出管道相连, 读取写入到输出管道中的数据,用于程序中线程的通信

    PipedOutputStream, 可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。

    public class DemoPipedStream {
     
        public static void main(String[] args) throws IOException {
            //建立管道读入流
            PipedInputStream pis = new PipedInputStream();
            //建立管道输出流并与读入流关联,也可以写成connect
            PipedOutputStream pos = new PipedOutputStream(pis);
            new Thread(new Write(pos)).start();
            new Thread(new Read(pis)).start();       
        }
    }
     
    class Read implements Runnable{
        private PipedInputStream pis;
        public Read(PipedInputStream pis){
            this.pis = pis;
        }
        @Override
        public void run(){   
            byte[] buf = new byte[200];
            try{
                System.out.println("---读取流开始获取信息");
                int len = pis.read(buf);
                System.out.println("--"+new String(buf,0,len));
                System.out.println("---读取流读取流信息完毕");
            }catch(IOException e){
                throw new RuntimeException("流读取异常");
            }
            finally{
                if(pis!=null)
                    try{pis.close();}
                catch(IOException e){
                    throw new RuntimeException("流关闭异常");
                }
            }
        }  
    }
     
    class Write implements Runnable{
        private PipedOutputStream pos;
        public Write(PipedOutputStream pos){
            this.pos = pos;
        }
        public void run(){
            try {
                System.out.println("写入流开始写入流信息,持续3S");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                pos.write("Hello PipedStram".getBytes());
                System.out.println("写入流开始写入完毕");
            } catch (IOException e) {          
                throw new RuntimeException("流写入异常");
            }
            finally{
                if(pos!=null)
                    try{pos.close();}
                catch(IOException e){
                    throw new RuntimeException("流关闭异常");
                }
            }
        }
    }
    
    RandomAccessFile

    可以在文件中的任何位置查找或写入数据。磁盘文件都是随机访问的,但是从网络而来的数据流却不是。你可以打开一个随机访问文件,只用于读入或者同
    时用于读写.
    构造器的第二个参数来指定这个选项。

    • “ r”表示只读模式;
    • “ rw”表示读 / 写模式;
    • “ rws”表示每次更新时,都对数据和元数据的写磁盘操作进行同步的读 / 写模式;
    • “ rwd”表示每次更新时,只对数据的写磁盘操作进行同步的读 / 写模式
         //调整对象中指针
         seek(long pos)
         //尝试跳过输入的 n 个字节以丢弃跳过的字节。
         skipBytes()
    
    操作基本数据类型

    以二进制格式读写基本Java类型
    DataInputStream与DataOutputstream
    操作字节数组
    ByteArrayInputStream与ByteArrayOutputStream
    操作字符数组
    CharArrayReader与CharArrayWriter
    操作字符串
    StringReader与StringWriter

    总结:
    以二进制格式写出数据,需要使用 DataOutputStream。
    以文本格式写出数据,需要使用 PrintWriter。

    PrintWriter printWriter = new PrintWriter("setting.dat", "UTF-8");
    

    字符集

    在过去,国际化字符集已经得到了处理,但是处理得很不系统,散布在 Java 类库的各处。在 Java SE 1.4 中引入的 java.nio 包用 Charset 类统一了对字符集的转换(注意 s 是小写的)。

            //获取Charset实例
            Charset.defaultCharset(); //默认字符集       
            Charset charset = Charset.forName("UTF-8");//静态获取指定字符集    
        
            //Returns a set containing this charset's aliases.
            for(String alias: charset.aliases()){
                System.out.println(alias);
            }
            
            //编码Java字符串
            ByteBuffer bytBuffer = charset.encode("自古中秋月最明, 凉风届候夜弥清");
            byte[] bytes = bytBuffer.array();
            
            //要想解码字节序列,需要有字节缓冲区
            CharBuffer cbuf = charset.decode(bytBuffer);
            System.out.println(cbuf.toString());    
    
    字符编码

    //"你好"-->"??"是GBK变utf-8 ; -->"浣犲ソ"是utf-8变GBK
    编一次解一次即可.

    练习: 有五个学生,每个学生有三名课,键盘输入(包括姓名,三门课成绩.)
    输出格式: 如 张三,39,76,54 计算出总成绩,并发学生信息和总分数高到低存放在stud.txt.

    相关文章

      网友评论

        本文标题:IO包 之流

        本文链接:https://www.haomeiwen.com/subject/fhwqittx.html