14 Java IO流

简介: IO(Input Output)流• IO流用来处理设备之间的数据传输• Java对数据的操作时通过流的方式• Java用于操作流的对象都在IO包中• 流按操作数据分为两种: 字节流和字符流• 流按类型分为: 输入流, 输出流

IO(Input Output)流



  • IO流用来处理设备之间的数据传输


  • Java对数据的操作时通过流的方式


  • Java用于操作流的对象都在IO包中


  • 流按操作数据分为两种: 字节流和字符流


  • 流按类型分为: 输入流, 输出流


IO流常用基类



  • 字节流的抽象基类对象: InputStream, OutputStream


  • 字符流的抽象基类对象: Reader, Writer
    注:有这四个类派生出来的子类名称都是由其父类名作为子类名的后缀.
    如: InputStream 的子类 FileInputStream
    如: Reade r的子类 FileReader


完整的流家族


image.png


输入流与输出流的层次结构


image.png


Reader 和 Writer 的层次结构


FileReader和FileWriter流


创建一个 FileWriter 对象,该文件会在指定目录下创建.


如果同名则覆盖, 除非构造方法第二个参数append 为 true; 由此可得默认为false.


/**
     * 
     * @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. 明确源和目的


  1. 是否是纯文本(字节流 和 字符流的选取), 具体使用哪个对象


  1. 是否需要提高效率而加入缓冲



这其中涉及到的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)变量也不会序列化.


管道流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

编一次解一次即可.


关闭流



总体可以认为是“先开后关”原则,原因是 IO 流的打开顺序是固定且层层依托的。

然后是closeable关闭的优化


如果new FileOutputStream, 然后上级目录不存在会抛出FileNotFoundException异常, 所以需要先行创建上层文件夹。




目录
相关文章
|
2月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
3月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
19天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
13天前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
29天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
2月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
28 2
|
2月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
2月前
|
缓存 Java
【IO面试题 一】、介绍一下Java中的IO流
Java中的IO流是对数据输入输出操作的抽象,分为输入流和输出流,字节流和字符流,节点流和处理流,提供了多种类支持不同数据源和操作,如文件流、数组流、管道流、字符串流、缓冲流、转换流、对象流、打印流、推回输入流和数据流等。
【IO面试题 一】、介绍一下Java中的IO流
|
2月前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
39 2
|
2月前
|
Java Android开发
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
解决Android编译报错:Unable to make field private final java.lang.String java.io.File.path accessible
152 1