Java简述 Java Stream(流)的分类, 四大基本流的介绍

一, java Stream的分类

Java 的Stream有很多个子类,  可以从各个层面可以划分为一下若干个类

1.1 输入流和输出流

上篇文章介绍过, Java里的Stream只有1种方向.

所以我们把从外部设备流向程序的流成为输入流

反之, 把从程序流向外部设备的流称为输出流.

1.2 字符流和字节流

根据数据在Stream里的最小传输单位, 我们也可以把流分为两类

字符流:

      最小传输单位为1个字符(java里的字符不再用ASCII码表示,而是用万国码, 所以1个字符(char) = 2个字节(byte) = 16bit(位)).

字节流:

      最小传输单位为1个字节(byte).

它们有1个最大的区别:

就是字符流只能读写文本格式的外部设备.

而字节流可以读写所有格式的外部设备(例如2进制文件, 多媒体文件等).

因为字节本身是不需编码和解码的, 将字节转换为字符才涉及编码和解码的问题.

1.3 节点流和处理流(原始流和包裹流)

Java里的stream还可以嵌套. 按照流的功能还可以分为节点流和处理流

节点流:

      也叫原始流, 用于传输基本数据的流

处理流:

      也叫包裹流, 包裹在节点流之上, 对节点流的数据作进一步处理的流, 处理流的前提是 具有节点流. 

      处理流可以多重嵌套

如下图:



二, java 四大基本Stream

Java里四个基本流分别是

InputStream : 输入字节流, 也就是说它既属于输入流, 也属于字节流

OutputStream: 输出字节流, 既属于输出流, 也属于字节流


Reader: 输入字符流, 既属于输入流, 又属于字符流

Writer: 输出字符流, 既属于输出流, 又属于字符流

  输入流 输出流
字符流    Reader   Writer
字节流   InputStream   OutputStream


这个4个流都是虚拟类, 也就是说它们不能直接被实例化, 

下面的代码都是用它们的子类(文件流)作例子.

三, Reader流及其常用方法.

本文第1个例子用到的FileReader就是继承自Reader这个流.

Reader流属于输入流和字符流, 也就是说Reader流的作用是从外部设备传输数据到程序, 而且最小单位是1个字符.

3.1 new Reader() 

首先讲下构造方法, 上面这个方法是没有throws Exception的

但是 其子类的某些重载的构造方法有些会thorws Excepiont

例如 FileReader 的

public FileReader(String fileName)
           throws FileNotFoundException

则代表我们初始话1个Reader时有必要将其扑捉(放入try{} 字句or throws给上一层函数.


所以在实际编程中,我们建议初始化1个FileReader时利用如下方法:

   FileReader fr = null;
   try{
            fr = new FileReader("/home/gateman/tmp/build.xml");}
   catch(FileNotFoundException e){
         ...
   }

关键是第一句的 = null; 不要省去, 否则在try{}字句外就不能使用 fr, 会编译失败, 提示fr可能未初始化.

加上 = null; 就编译通过, 在try{} 字句外使用时判断一下 fr != null 就ok了.

后面介绍 close()方法时会再次提到.

3.2 int read() throws IOException

read()方法可以讲是Reader最基础的一个方法, 它的作用是从流中读取1个字符, 并把这个字符存储到1个整形(int)变量中.

如果读到了输入流的末尾(最后1个字符) 则返回 -1.


我们知道字符类型是char, 为何返回的是1个int类型?

原因就是为了接口统一,  实际上read()方法会将接收到的char类型的数据存储在Int类型的较低位2个字节中.

如下图, 因为java中的用的是万国码表示字符, 所以1个char字符占2个字节, 而int类型是有4字节的.



注意, 因为网络, 设备等外部原因可能会导致读取失败, 这个read()方法throws IOException, 使用时要捕捉.


3.3 int read(char[] charbuffer) throws IOException

是不是觉得上面的方法一次读1个字符是不是很蛋疼,  觉得有点浪费内存的嫌疑啊.

所以Reader 流提供了另1个方法, 可以1次个读取若干个字符.

这个read方法的名称和返回值都跟上面的方法相同, 可以讲是上面方法的重载.  但是意义大大不同.

上面的int read()方法 返回值就是接受的字符数据, 只不过用int变量来存储.

而 这个 int read(char[] charbuffer) 一定要有1个字符数组作为参数, 然后把接受到若干个字符放进这个字符数组(从数组头部开始放)中. 然后返回实际接受到字符的个数.  假如读取到外部设备的结尾, 则返回-1.

例如执行一次下面的语句.

len = ReaderA.read(cbuffer);

那么程序就会从ReaderA这个流中读取一次若干(Len)个字符放入到字符数组cbuffer中, len就是具体读取的字符数据个数.


3.3.1 究竟int read(char[])方法读取的字符个数是多少个,由什么决定?

通常这个是第一次接触流的程序猿最想知道的问题.

答案也很简单.

通常来讲,  这个len就是参数字符串的长度. 也就是说一般来讲int read(char[])方法会填满这个字符串.

但是也有例外:

1. 内存紧张

2. 读取到最后的部分, 例如外部设备中有89个字符,  参数字符串的长度是20, 那么前面4次, 都是读20个字符, 最后那次就只读9个字符.

3. 读到外部设备的结尾, 返回-1.


3.3.2 返回值len的意义

有人也会问, 我为什么要关心具体返回值的个数, 这个返回值很重要吗.

答案是很重要.


原因是: int read(char[]) 执行一次新的读取时, 并不会擦除参数字符数组的原有数据, 而读取的字符数不是确定的(上面解析了3个原因), 所以我们需要返回值 len 来确定,数组内那些字符是有效的, 那些字符是之前读取的.


所以, 我们需要返回值len来在参数字符串中提取有效数据!

下面就是例子:


3.3.3 FileReader的一个例子.
这个例子跟上一篇介绍流的例子都是读取1个文件并输出到屏幕上.


代码:

import java.io.*;
import java.util.ArrayList;
import java.lang.Integer;

public class Reader2{
    public static void f() throws IOException{
       FileReader fr = new FileReader("/home/gateman/tmp/build.xml");

       char[] cbuffer = new char[20]; //not char
       int len; //throws IOEXCEPTION
       ArrayList<Integer> lenarr = new ArrayList<Integer>();
       
       do{
            len = fr.read(cbuffer); //if ch = -1, means got the end of the file
            lenarr.add(len);
            charArrPrint(cbuffer,len);
       }while(len > -1); //if ch = -1, means got the end of the file
       
       System.out.println("==============================");
       System.out.println(lenarr);

    }

    public static void charArrPrint(char[] cbuffer, int len){
        int i = 0;
        for (i=0; i < len; i++){
            System.out.printf("%c",cbuffer[i]);
        }
    }
}

上面定义了1个长度为20的字符数组cbuffer,然后利用它不断接受流的数据并输出到屏幕上, 并记录每一次的获取个数数据.


而且打印数组的方法 charArrPrint, 打印的长度是参数len, 而不是数组本身的长度.length.


输出:

     [java] <?xml version="1.0" encoding="GB2312" ?>
     [java] 
     [java] <!-- a project, maybe includes many groups of tasks(targets) -->
     [java] <project default="main" basedir=".">
     [java] 
     [java] 	<!-- one of the tasks(target) -->
     [java] 	<target name="main">
     [java] 
     [java] 		<!-- compile -->
     [java] 		<javac srcdir="src\main" destdir="build\classes" debug="on" debuglevel="lines,vars,source"/> 
     [java] 		
     [java] 
     [java] 		<!-- run -->
     [java] 		<java classname="Enter_1">
     [java] 			<classpath>
     [java] 				<pathelement path="build\classes"/>
     [java] 				<pathelement path="/home/gateman/Studies/Java/java_start/Java_1/jar/generated/Test_jar.jar"/>
     [java] 			</classpath>
     [java] 		</java>
     [java] 
     [java] 	</target>
     [java] 
     [java] </project>
     [java] ==============================
     [java] [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 9, -1]

可以见到最终输出, 实际上基本上所有的读取次数都是20, 知道最后的9个, 然后再尝试读取就返回-1了!


总之,int read(char[]) 方法不能确定每次读取的字符串的个数, 但是能限制每次读取的最大个数, 就是传入参数字符数组的长度.


3.4 int read(char[] charbuffer, int offset, int length) throws IOException

这个方法也是3.1 方法的另1个重载, 参数更加多了.  其实也不难理解.


1. 参数charbuffer, 也是用于存放接收到的字符数据.

2. 关于第2个参数,但是从第offset个位置开始存放接收到的数据. 也就是说在那一次读取方法中, 该数组中前offset的数据很可能是以前的数据.

3. 每次接受的个数不能大于第三个参数length, 也就是执行1次read, 最多读取length,  当然, 也不能大于数组参数 charbuffer的长度,  所以 length设置大于charbuffer的长度是无意义的.

4. 也是返回实际接受到的字符个数.


例子: 将上面的例子稍稍修改, 使用当前介绍的函数.

import java.io.*;
import java.util.ArrayList;
import java.lang.Integer;

public class Reader3{
    public static void f() throws IOException{
       int startIdx = 4;
       int rLength = 13;
       FileReader fr = new FileReader("/home/gateman/tmp/build.xml");

       char[] cbuffer = new char[20]; //not char
       int len; //throws IOEXCEPTION
       ArrayList<Integer> lenarr = new ArrayList<Integer>();
       
       do{
            len = fr.read(cbuffer, startIdx, rLength); //if ch = -1, means got the end of the file
            lenarr.add(len);
            charArrPrint(cbuffer,startIdx,len);
       }while(len > -1); //if ch = -1, means got the end of the file
       
       System.out.println("==============================");
       System.out.println(lenarr);

    }

    public static void charArrPrint(char[] cbuffer, int startIdx, int len){
        int i ;
        for (i=0; i < len; i++){
            System.out.printf("%c",cbuffer[i + startIdx]);
        }
    }
}

上面例子中我定义了1个长度为20的字符数组, 但是每次最多读取13个. 而且从字符数组的第5个位置开始存储.

注意charArrprint()这个函数必须有3个参数了.


输出:

     [java] <?xml version="1.0" encoding="GB2312" ?>
     [java] 
     [java] <!-- a project, maybe includes many groups of tasks(targets) -->
     [java] <project default="main" basedir=".">
     [java] 
     [java] 	<!-- one of the tasks(target) -->
     [java] 	<target name="main">
     [java] 
     [java] 		<!-- compile -->
     [java] 		<javac srcdir="src\main" destdir="build\classes" debug="on" debuglevel="lines,vars,source"/> 
     [java] 		
     [java] 
     [java] 		<!-- run -->
     [java] 		<java classname="Enter_1">
     [java] 			<classpath>
     [java] 				<pathelement path="build\classes"/>
     [java] 				<pathelement path="/home/gateman/Studies/Java/java_start/Java_1/jar/generated/Test_jar.jar"/>
     [java] 			</classpath>
     [java] 		</java>
     [java] 
     [java] 	</target>
     [java] 
     [java] </project>
     [java] ==============================
     [java] [13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, -1]

可以见到基本上每次读取13个, 值到最后一次读取10个..


3.5 long skip(long n) throws IOException

尝试跳过n个字符不读, 返回实际跳过的字符数, 比较少用啦.


3.6 void close() throws IOException

由上面例子中, 我们会觉得流的使用挺简单的, 只需简单一两行就建立1个流.

这样的确很方便了程序猿, 但是可以Reader这个类在后台做了很多东西, 包括分配内存, 建立连接等, 占用相当的系统资源.


所以我们应该在每次使用完1个流后, 把它关闭掉, 释放出系统资源!


将上面的例子修改如下, 才是标准的流使用方法:

import java.io.*;
import java.util.ArrayList;
import java.lang.Integer;

public class Reader4{
    public static void f(){
       int startIdx = 4;
       int rLength = 14;
       FileReader fr = null; // = null is very important, otherwise it will not pass the compilation, popup the error "fr may not be initailed"
       char[] cbuffer = new char[20]; //not char
       int len; //throws IOEXCEPTION
       ArrayList<Integer> lenarr = new ArrayList<Integer>();
       
       try{
            fr = new FileReader("/home/gateman/tmp/build.xml");
            do{
                len = fr.read(cbuffer, startIdx, rLength); //if ch = -1, means got the end of the file
                lenarr.add(len);
                charArrPrint(cbuffer,startIdx,len);
            }while(len > -1); //if ch = -1, means got the end of the file
        }
       catch(FileNotFoundException e){
            System.out.println("File not found!");
       }
       catch(IOException e){
            System.out.println("IO Problem!");
            e.printStackTrace();
       }
       catch(Exception e){
            e.printStackTrace();
       }
       finally{
            if (null != fr){
                try{
                    fr.close();
                }
                catch(IOException e){
                     System.out.println("failed to close the reader!");
                }
            }     
       }
       
       System.out.println("==============================");
       System.out.println(lenarr);

    }

    public static void charArrPrint(char[] cbuffer, int startIdx, int len){
        int i ;
        for (i=0; i < len; i++){
            System.out.printf("%c",cbuffer[i + startIdx]);
        }
    }
}


值得注意的时, fr.close()方法是放在finally字句内的, 也就是构造函数new FileReader(String filestr) 所在的try{}字句内.

所以第9行的 = null 不能省.

否则编译失败, 提示fr有可能未初始化!



四, Writer流及其常用方法.

相对于上面的Reader流, Writer流也是字符流, 但是方向是从程序到外部文本文件.

4.1 new Writer() 

构造方法与上面的Reader类似, 有可能throws Exception, 不多说了.


4.2 void write(int c) throws IOException

将1个字符通过输出流写到输出流( 未到达为外部设备 )中. 值得注意的是, 传入的参数字符(char)数据是存放到1个整形(int)变量中. 而且是放在整形变量的低位2个字节中(实际上在jdk1.7中提供了以char类型作为参数的重载方法).


上面红色加粗的字体意思是: 当执行了这个方法, 字符数据并没有立即写入到外部设备, 而是保存在输出流的缓冲区中(还在内存中).


例子:

import java.io.*;

public class Writer1{
    public static void f(){
        int i;
        String s = new String("Just a testing for writer stream!\n");
        File fl = new File("/home/gateman/tmp/testwriter1.txt");
        if (fl.exists()){
            fl.delete();
        }
        
        try{
            fl.createNewFile();
        }catch(IOException e){
            System.out.println("File created failed!");
        }
        System.out.println("File created!");

       
        FileWriter fw = null;
        try{
           fw = new FileWriter(fl); 
        }catch(IOException e){
            System.out.println("Create filewriter failed!");
            if (null != fw){
                try{
                    fw.close();
                }catch(IOException e1){
                    System.out.println("Close the filewriter failed!");
                    return;
                }
                System.out.println("Close the filewriter successfully!");
            }
            return;
        } 

        for (i=0; i<s.length(); i++){
            try{
                fw.write((int)s.charAt(i));
            }catch(IOException e){
                System.out.println("error occurs in fw.write()!");
            }
        }
        
        System.out.println("done!");
    }    
}


上面代码建立了1个文件/home/gateman/tmp/testwriter1.txt.

然后尝试使用wrtier 方法把String "Just a testing for writer stream!\n" 的内容写入到那个文件.


程序输出:

     [java] File created!
     [java] done!

BUILD SUCCESSFUL
Total time: 1 second
gateman@TPEOS Java_1 $ cat /home/gateman/tmp/testwriter1.txt
gateman@TPEOS Java_1 $ 

可以见到实际场程序能正常执行完最后一行代码, 文件也创建成功了, 但是文件是空的.  因为输出流在缓冲区的数据并未写入到文件.

那么如何把输出流缓冲区的数据写入到外部设备.


答案很简单, 执行 流的关闭close()方法即可. 这也是使用流的良好习惯之一.


加上close()的代码如下:

import java.io.*;

public class Writer1{
    public static void f(){
        int i;
        String s = new String("Just a testing for writer stream!\n");
        File fl = new File("/home/gateman/tmp/testwriter1.txt");
        if (fl.exists()){
            fl.delete();
        }
        
        try{
            fl.createNewFile();
        }catch(IOException e){
            System.out.println("File created failed!");
        }
        System.out.println("File created!");

       
        FileWriter fw = null;
        try{
           fw = new FileWriter(fl); 
        }catch(IOException e){
            System.out.println("Create filewriter failed!");
            if (null != fw){
                try{
                    fw.close();
                }catch(IOException e1){
                    System.out.println("Close the filewriter failed!");
                    return;
                }
                System.out.println("Close the filewriter successfully!");
            }
            return;
        } 

        for (i=0; i<s.length(); i++){
            try{
                fw.write((int)s.charAt(i));
            }catch(IOException e){
                System.out.println("error occurs in fw.write()!");
            }
        }
        
        try{
            fw.close();
        }catch(IOException e){
            System.out.println("Close the filewriter failed!");
            return;
        }
        System.out.println("Close the filewriter successfully!");

        System.out.println("done!");
    }    
}

这次的输出:

     [java] File created!
     [java] Close the filewriter successfully!
     [java] done!

BUILD SUCCESSFUL
Total time: 1 second
gateman@TPEOS Java_1 $ cat /home/gateman/tmp/testwriter1.txt
Just a testing for writer stream!
gateman@TPEOS Java_1 $ 

提示了关闭文件成功, 而且对应的硬盘上的文件也见到我们写入的字符数据!


4.3 void flush() throws IOException

根据上面的例子会知道, write()方法写入的数据未必回立即写入到外部设备, 而是有1个缓冲区存储着它们.

当close()方法成功时, 才会保证所有缓冲区的内容都写入到了外部设备.


但是close()方法一般放在1个事务的最后部分执行, 那么中间出现了异常或错误导致程序跳出时, 就很可能回丢失缓冲区的数据了.

那么有无1个类似与保存的功能, 让程序猿在这个期间强制把缓冲区的数据写入到外部设备中?


答案就是有的,  这个flush()方法的作用就是把当前缓冲区所有的数据写入到外部设备.

所以flush() 有时也被称为"刷新缓冲区", "清空缓冲区"等等, 实际上的意思都是一样的.


下面这个例子, 即使最后忘记了写close()方法(尽量避免), 但是执行了flush(), 也保证了数据写入到了磁盘上的文件:

import java.io.*;

public class Writer2{
    public static void f(){
        int i;
        String s = new String("Just a testing for writer stream!\n");
        File fl = new File("/home/gateman/tmp/testwriter1.txt");
        if (fl.exists()){
            fl.delete();
        }
        
        try{
            fl.createNewFile();
        }catch(IOException e){
            System.out.println("File created failed!");
        }
        System.out.println("File created!");

       
        FileWriter fw = null;
        try{
           fw = new FileWriter(fl); 
        }catch(IOException e){
            System.out.println("Create filewriter fail!");
            return;
        } 

        for (i=0; i<s.length(); i++){
            try{
                fw.write(s.charAt(i));
            }catch(IOException e){
                System.out.println("error occurs in fw.write()!");
            }
        }

        try{
            fw.flush();
        }catch(IOException e){
            System.out.println("error occurs in fw.flush()");
        }
        
       // try{
       //     fw.close();
       // }catch(IOException e){
       //     System.out.println("Close the filewriter failed!");
       //     return;
       // }
       // System.out.println("Close the filewriter successfully!");
        System.out.println("done!");

    }    
}

输出:

     [java] File created!
     [java] done!

BUILD SUCCESSFUL
Total time: 1 second
gateman@TPEOS Java_1 $ cat /home/gateman/tmp/testwriter1.txt
Just a testing for writer stream!


4.4 void write(char[] cbuffer) throws IOException

此方法是4.2方法的重载.

可以把1个字符数组的所有数据写入到输出流缓冲区.. 


4.5 void write(String s) throws IOException

这个方法也是上面4.2 的重载方法,  很容易理解啊, 就是把整个字符串的内容写入到输出流缓冲区. 这里不写例子代码了.

对比Reader流.

为什么输出流可以把一个字符串or字符数组写入到输出流

而Reader流用于接受一段字符数据必须用字符数组?


这时因为在java中,字符串实际上是常量, 在静态区不支持修改. 这需要深入理解java的字符串.


4.6 void write(String s, int offset, int length) throws IOException

也很简单啦, 就是把字符串的一部分写入输出流缓冲区..



五, InputStream及其常用方法.

所谓InputStream就是字节输入流, 与Reader最大的区别就是它不但支持文本外部设备,还支持2进制外部设备.

这里用的例子是FileInputStream, 顾名思义, 就是搭向磁盘文件的字节输入流.


5.1 new 方法 

构造方法与Reader类似, 不再详讲了, 参数可以是1个File对象, 也可以是1个String(文件路径), Throws 异常!


5.2 int read() throws IOException

读取1个字节(2进制),并以整数形式(10进制)返回, 如果读到流的末尾,则返回-1.

1个简单例子, 与FileReader的使用方法很类似.

import java.io.*;

public class InputStream1{
    public static void f(){
       FileInputStream fis = null;
       try{
           fis = new FileInputStream("/home/gateman/tmp/build.xml");
       }catch(FileNotFoundException e){
           System.out.println("file not found!");
           return;
       }


       int bt; //byte
       try{
            bt = fis.read();
            while(bt > -1){
                System.out.printf("%c",(char)bt);
                bt = fis.read();
            }
       }catch(IOException e){
           System.out.println("IOException!");
           e.printStackTrace();
       }finally{
           if (null != fis){
               System.out.println("============");
               try{
                   fis.close();
               }catch(IOException e){
                   System.out.println("Stream close failed!");
                   return;
               }
               System.out.println("Stream close successfully!");
           }
       }
    }
}

输出:

 [java] <?xml version="1.0" encoding="GB2312" ?>
     [java] 
     [java] <!-- a project, maybe includes many groups of tasks(targets) -->
     [java] <project default="main" basedir=".">
     [java] 
     [java] 	<!-- one of the tasks(target) -->
     [java] 	<target name="main">
     [java] 
     [java] 		<!-- compile -->
     [java] 		<javac srcdir="src\main" destdir="build\classes" debug="on" debuglevel="lines,vars,source"/> 
     [java] 		
     [java] 
     [java] 		<!-- run -->
     [java] 		<java classname="Enter_1">
     [java] 			<classpath>
     [java] 				<pathelement path="build\classes"/>
     [java] 				<pathelement path="/home/gateman/Studies/Java/java_start/Java_1/jar/generated/Test_jar.jar"/>
     [java] 			</classpath>
     [java] 		</java>
     [java] 
     [java] 	</target>
     [java] 
     [java] </project>
     [java] ============
     [java] Stream close successfully!

BUILD SUCCESSFUL
Total time: 1 second

5.3 int available() throws IOException

先看看这个方法在jdk api的字面解释


返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。下一次调用可能是同一个线程,也可能是另一个线程。一次读取或跳过此数量个字节不会发生阻塞,但读取或跳过的字节可能小于该数。

在某些情况下,非阻塞的读取(或跳过)操作在执行很慢时看起来受阻塞,例如,在网速缓慢的网络上读取大文件时。


简答地将, 这个方法返回输入流中还有多少剩余字节数.


如果是在连接磁盘文件的输入流中, 一般就是返回磁盘文件的(剩余)大小.

但是这个方法在网络流中更有意义


例如网络上1个流连接两个程序传输, 1个程序往往要等待另1个程序向流发出数据, 那么这个方法就可以用于判断流中是否存在数据了!



5.4 int read(byte[] b) throws IOException

5.2方法的重载, 读取一定数量的字字皆, 并存储在字节数组b中, 返回实际读取的字节数. 如果读到输出流的末尾, 则返回-1.

这个方法的用法很想 Reader流的 int read(char[] cbuffer) . 


5.5 int read(byte[] b, int offset, int length) throws IOException

上面的方法的重载,读取一定量的字节, 存放在数组b的特定位置...



六, OutputStream及其常用方法.

这个本文介绍的最后1个基本流, 输出字节流..  其用法与Writer流很类似.. 不再详细写例子介绍了..

6.1 void write(int b) throws IOException

向输出流缓冲区中写入1个byte的数据, 该字节数据为参数整型b的低8位.

6.2 void write(byte[] b) throws IOException

将1个字节数组b的内容写入输入流缓冲区.


6.3 void write(byte[] b, int offset, int length) throws IOException

将字节数组b的部分内容写入输入流缓冲区


6.4 void flush() throws IOException

立即将缓冲区的数据写入到外部设备



转载自:http://lib.csdn.net/article/javase/2899

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值