黑马程序员——Io基础

------Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------



一   I/O内存缓冲区

 

用户空间:常规进程所在区域,JVM就是常规进程,该区域执行的代码不能直接访问硬件设备 

内核空间:操作系统所在区域。内核代码它能与设备控制器通讯,控制着用户区域进程的运行状态,等等。最重要的是,所有的I/O 都直接或间接通过内核空间

 数据交互:当用户(java)进程进行I/O操作的时候,它会执行一个系统调用将控制权移交给内核,内核代码负责找到请求的数据,并将数据传送到用户空间内的指定缓冲区

内核空间缓冲区:内核代码读写数据要通过磁盘的I/O操作,由于磁盘I/O操作的速度比直接访问内存慢了好几个数量级,所以内核代码会对数据进行高速缓存或预读取到内核空间的缓冲区(减少磁盘I/O,提高性能)

用户空间缓冲区:同上,java I/O进程通过系统调用读写数据的速度,要比直接访问虚拟机内存慢好几个数量级,所以可以执行一次系统调用时,预读取大量数据,缓存在虚拟机内存中。


 二  输入流InputStream、输出流OutputStream

       FileInputStream类实现了InputStream的读取单个字节read()、读取字节数组read(byte b[], int off, int len)的方法。FileOutputStream类实现了OutputStream的写入单个字节write(int b)、写入字节数组write(byte b[], int off, int len)的方法(后面对Out介绍略过,参考In)            

 *  文件输出流构造器 

 *  name 路径名 

 *  append 参数为true,数据将被添加到文件末尾, 

 *  而具有相同名字的已有文件不会被删除,否则这个方法删除所有具有相同名字的文件 
 
   public FileOutputStream(String name, boolean append)  
           throws FileNotFoundException  
       {  
           this(name != null ? new File(name) : null, append);  
       }  

         因为InputStream的实现类,只能读取字节,而且每次read都会执行一次系统调用,所以对其实现类进行扩展是必需的,过滤器FilterInputStream继承自InputStream,是一些扩展类(DataInputStream、BufferedInputStream)的基类。           

public class FilterInputStream extends InputStream {  
    protected volatile InputStream in;  
    /** 
     * 构造方法受保护的,只能通过子类调用(初始化) 
     * 参数是InputStream的实现类,例:FileInputStream 
     */  
    protected FilterInputStream(InputStream in) {  
        this.in = in;  
    }  

        read()方法每次都会发起一次系统调用,而系统调用的代价是非常高的,所以为了减少系统调用的次数,就需要通过装饰器BufferedInputStream一次read()大量字节缓存在字节数组中。这里的缓存并不是为了减少磁盘IO操作次数,因为这个操作系统已经帮我们做了

// 默认缓存8192字节=8kb,可以通过参数进行设置  
InputStream in = new BufferedInputStream(new FileInputStream("路径"), 8192);  
// 每次write()的字节,都会缓存到长度8912的字节数组中,直到写满才进行系统调用一次性写入  
OutputStream out = new BufferedOutputStream(new FileOutputStream("路径"), 8192);  
// 同理BufferedReader、BufferedWriter也是一样  

         数据在读取和写入时都是字节状态,适配器DataInputStream 实现了DataInput接口可以将读取的字节,在后台转换成java的基本类型然后进行读取,例:readInt每次读取4个字节

/** 
    * DataInputStream类方法 
 * 转换成int类型,int4字节组成,一个字节8位 
 * 高位在前,进行数值还原 
 */  
   public final int readInt() throws IOException {  
       int ch1 = in.read();  
       int ch2 = in.read();  
       int ch3 = in.read();  
       int ch4 = in.read();  
       if ((ch1 | ch2 | ch3 | ch4) < 0)  
           throw new EOFException();  
       return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));  
   }  

          DataOutputStream、DataInputStream两者一般结合使用,如果DataOutputStream 写数据,java保证我们可以使用DataInputStream准确的读取数据。还有writeUTF()和readUTF()方法,可以对字符串进行写入、读取。 用到这两个类的来读写文件的数据格式一般比较固

DataInputStream buff = new DataInputStream(  
                       new BufferedInputStream(  
                       new FileInputStream("路径")));  

         read和write方法在执行时将阻塞,直到字节被读入或者写出。不过通常是因为网络繁忙。available()方法“在没有阻塞的情况下所能读取的字节数”,即当前可读取的字节数量,那么下面这样就不会 阻塞

// 对于文件,这意味着整个文件都可读取  
int bytesAvailable = in.available();  
if (bytesAvailable > 0) {  
    byte[] bytes = new byte[bytesAvailable];  
    in.read(bytes);  
}  

         输入流InputStream、输出流OutputStram的几个方法    

public static void main(String[] args) throws IOException {  
  
    // ByteArrayInputStream 可以将一个字节数组转成输入流  
    InputStream in = new ByteArrayInputStream("abcdefghi".getBytes());  
    // 判断是否支持标记功能  
    in.markSupported(); // true  
    // 跳过2个字节  
    in.skip(2);  
    /** 
     * 在当前位置打标记,非所有流都支持(这个流支持)  
     * 如果从输入流中已经读的字节数大于5个,则这个流允许忽略这个标记(这个流不支持) 
     */  
    in.mark(5);  
    // 跳过2个字节  
    in.skip(2);  
    System.out.println((char) in.read());// 输出:e  
    // 返回到最后一个标记,随后read将重新读入这些字节。如果没有标记不被重置  
    in.reset();  
    System.out.println((char) in.read());// 输出:c  
    // 超过5个字节被读取  
    in.skip(4);  
    // mark不失效  
    in.reset();  
    System.out.println((char) in.read());// 输出:c  
  
    byte[] b = new byte[10];  
    // 向b中塞4个字节,从b[5]开始  
    in.read(b, 54);  
    // 关闭流  
    in.close();  
  
    // OutputStram  
    OutputStream out = new ByteArrayOutputStream();  
    // 冲刷输出流,也就是将所有缓冲的数据送到目的地  
    out.flush();  
    // 冲刷并关闭流  
    out.close();  
  
}  

   

三  字符输入流Reader、字符输出流Writer   

        不管磁盘还是网络传输,最小的存储单位都是字节,所以抽象类InputStream和OutputStream构成了输入/输出(I/O)类层次的基础。

       基础的输出输入流仅支持8位的字节流,并且不能很好的处理16为的Unicode字符,由于Unicode用于字符国际化(java本身的char也是16的Unicode,即包含两个字节), 所以适配器InputStreamReader、OutputStreamWriter出现了,用于字节和字符之间的 转换。

// ByteArrayInputStream 可以将一个字节数组转成输入流  
InputStream in = new ByteArrayInputStream("你好".getBytes());  
// 先转换成字符,然后用BufferedReader进行缓存  
BufferedReader br = new BufferedReader(new InputStreamReader(in));  

      字符就是大家都能看懂的文字(包括其它国家文字)、符号之类的,比如txt存储的都是字符。无法转换成字符的如图片MP3

      以字符格式写出数据,可以使用PrintWriter,还可以设置是否每次写入都冲刷输出流。print/println方法可以写入字符和java的基本类型

/** 
 * 其中的一个私有构造器方法 
 * @param charset 以选定的字符编码,将Unicode字符转换成字节进行存储 
 * @param file  文件路径 
 * @throws FileNotFoundException 
 */  
   private PrintWriter(Charset charset, File file)  
           throws FileNotFoundException  
       {  
           this(new BufferedWriter(//对字符缓存  
             new OutputStreamWriter(   
             new FileOutputStream(file), charset)),false);  
       }  

 

       以字符格式读取数据,可以使用BufferedReader,有一个readLine()方法,每次读取一行。没有按java基本类型读取数据功能。下面是将字节按照GBK字符集格式转换成字符。

BufferedReader buff = new BufferedReader(  
                      new InputStreamReader(  
                      new FileInputStream(path), "GBK"));   

 

       java.util.Scanner类可以按行读取数据,也可以按java基本类型读取数据。

       RandomAccessFile类可以在文件中任何位置查找和写入数据,适合大小已知的记录组成的文件。类中有一个表示下一个读入或写出的字节所处的位置,用seek()方法,可以设置指针在文件中的任意位置。其实现了DataOutput, DataInput接口,可以对  java的基本类型进行读写。在使用该类时,一般要知道文件的排版。还有其大多功能都可以用nio取代

//只能进行读入访问  
RandomAccessFile in = new RandomAccessFile(path, "r");  
//可以进行读入、写出访问  
RandomAccessFile inOut = new RandomAccessFile(path, "rw");  


四  ZipInputStream和ZipOutputStream 

//压缩:写出到ZIP文件  
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream("压缩文件路径.zip"));  
//压缩文件中的每一项都是一个ZipEntry对象  
ZipEntry entry = new ZipEntry("文件名字");  
//将ZipEntry添加到压缩文件中  
zipOut.putNextEntry(entry);  
//可以通过缓存流写入数据  
BufferedOutputStream outbuf = new BufferedOutputStream(zipOut);  
//还可以继续嵌套  
DataOutputStream dou = new DataOutputStream(outbuf);  
  
  
//解压:将文件从压缩文件中读出  
ZipInputStream zipIn = new ZipInputStream(new FileInputStream("压缩文件路径.zip"));  
//返回下一个ZipEntry对象,没有更多项返回null  
zipIn.getNextEntry();  
//可以通过缓存流读取数据  
BufferedInputStream inbuf = new BufferedInputStream(zipIn);  
//还可以继续嵌套  
DataInputStream din = new DataInputStream(inbuf);  

      ZipInputStream不能一次解压整个压缩文件,只能一次一个ZipEntry 的解压。

      ZipOutputStream不能一次压缩一个文件夹,只能一次一个ZipEntry 的压缩。

 

     ZipInputStream的read方法再碰到当前ZipEntry结尾时返回-1,然后必须调用closeEntry来读入下一个ZipEntry 。写入时同理。 


五 对象序列化

        java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并可以将转换的字节序列恢复为原来的对象,这种机制可以弥补不同操作系统之间的差异,可以在windows上序列化传到unix恢复

        java的远程方法调用RMI(Remote Mthod Invocation)就是通过序列化实现的,当远程对象发送信息时,需要通过对象序列化来传输参数和返回值 

        对象序列化不紧可以保存对象的全景图,还能追踪对象内所包含的全部引用,并保存那些对象 

        序列化时每个对象的引用都会关联一个序列号,相同对象的重复序列化,将被存储为对这个对象序列号的引用。反序列化时过程相反。 

        在反序列化一个对象时,会拿其序列号与它所属类的当前序列号进行对比,不匹配,说明这个类在序列化后发生过变化,这涉及“版本管理”就不概述了

public static void main(String[] args) throws IOException, ClassNotFoundException {  
  
    // 序列化  
    ByteArrayOutputStream out = new ByteArrayOutputStream();  
    // 创一个ObjectOutputStream 写出到指定的OutputStream  
    ObjectOutputStream oos = new ObjectOutputStream(out);  
    // 这个方法将存储指定对象的类、类的签名,以及这个类及其超类中所有非静态和非transient(瞬时)修饰的域的值  
    oos.writeObject("实现Serializable接口的对象");  
    oos.writeObject("可以序列化多个对象");  
  
    // 反序列化  
    byte[] bs = out.toByteArray();  
    InputStream is = new ByteArrayInputStream(bs);  
    // 创建一个ObjectInputStream 用于从指定的InputStream读回对象信息  
    ObjectInputStream ois = new ObjectInputStream(is);  
    // 返回对象的类、签名、以及这个类及其超类中所有非静态和非瞬时域的值  
    String str1 = (String) ois.readObject();  
    String str2 = (String) ois.readObject();  
    System.out.println(str1 + str2);  
}  

 

       实现Externalizable接口,它继承自Serializable。在其序列化时会调用writeExternal方法,反序列化时通过调用无参构造器,然后调用readExternal进行对象的初始化。

public class Test implements Externalizable {  
  
    private String str;  
    private int i;  
  
    // 反序列化时调用  
    public Test() {  
        System.out.println("无参构造器");  
    }  
  
    public Test(String str, int i) {  
        System.out.println("构造器");  
        this.str = str;  
        this.i = i;  
    }  
  
    @Override  
    public String toString() {  
        return str + i;  
    }  
  
    // 序列化时调用  
    @Override  
    public void writeExternal(ObjectOutput out) throws IOException {  
        out.writeObject("writeExternal-");  
        out.writeInt(1);  
    }  
  
    // 反序列化时调用  
    @Override  
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
        this.str = (String) in.readObject();  
        this.i = in.readInt();  
    }  
  
    public static void main(String[] args) throws IOException, ClassNotFoundException {  
        // 初始化  
        Test test = new Test("test"0);  
        // 序列化  
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(outputStream);  
        oos.writeObject(test);  
        oos.close();  
        // 反序列化  
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(inputStream);  
        Test te = (Test) ois.readObject();  
        System.out.println(te);  
        inputStream.close();  
        ois.close();  
  
    }  
}  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值