Java学习(九)

Java学习(九)

Java I/O流

字节流:8bit,InputStreamOutputStream为抽象基类

字符流:16bit,ReaderWriter做抽象基类

Java同样有着C/C++文件位置指针的类似概念

通常的,打开一个文件之类的、从/向特定IO设备(磁盘、网络等)读写的流,称为节点流。

处理流:对已存在的流做封装,使用起来更简便

  1. 抽象基类输入流

    InputStream

    int read():从输入流读取单个字节,返回所读取的字节数据

    int read(byte[] b):读取b.length个字节,并存储在数组b中,返回实际读取的字节数

    int read(byte[] b, int off, int len):从输入流读取len个字节,并从b[off]位置开始存储在数组b中,返回实际读取的字节个数

    当返回为-1时,表示输入流读取完毕

    Reader

    int read():从输入流读取单个字符,返回所读取的字符数据

    int read(char[] cbuf):类似上文

    int read(char[] cbuf, int off, int len):类似上文

  2. 抽象基类输出流

    OutputStream和Writer公共的方法

    void write(int c):将指定的字节/字符输出到输出流中

    void write(byte[]/char[] buf):将字节数组/字符数组中的数据输出到指定输出流

    void write(byte[]/char[] buf, int off, int len):将字节数组/字符数组buf中从off位置开始,长度为len的字节/字符输出到输出流中

    Writer因为操作单位是字符,所以有额外的两个操作String类对象的方法

    void write(String str):将str输出到指定输出流中

    void write(String str, int off, int len):将str从off位置开始,长度为len的字符输出到指定输出流中

  3. 节点流

    举例:

    字节输入流:FileInputStreamByteArrayInputStreamPipedInputStream

    字节输出流:上述改成Output

    字符输入流:FileReaderCharArrayReaderPipedReaderStringReader

    字符输出流:上述改成Writer

  4. 处理流

    使用方法:直接采用物理IO节点作为构造器参数。

    示例:

    public class PrintStreamTest
    {
        public static void main(String[] args)
        {
            try{
                FileOutputStream fos = new FileOutputStream("test.txt");
                PrintStream ps = new PrintStream(fos);
                {
                    ps.println("str");
                    ps.println(new PrintStreamTest());
                }
            }
            catch (IOException ioe)
            {
                ioe.printStackTrace();
            }
        }
    }
    
  5. 缓冲流

    增加缓冲功能,当调用flush()时,才将缓冲区内容写入实际物理节点

  6. 转换流

    只有字节转字符,没有字符转字节。

    比如,键盘输入时,由于都是字符数据,所以可以将System.inInputStream类的实例)转换成InputStreamReader字符输入流

  7. 推回输入流

    PushbackInputStreamPushbackReader

    void unread(byte[]/char[] buf):将一个字节/字符数组的内容推回到推回缓冲区内,使得刚才读取的内容可重复读取

    void unread(byte[]/char[] b, int off, int lne):将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区内

    void unread(int b):将一个字节/字符推回到推回缓冲区内

    推回流调用read()方法时,总是先从推回缓冲区内读取。

    可以利用这个特性每次只读取目标字符串之前的内容,即将一个字符串作为读取的界限

  8. 重定向

    System类提供了三种重定向的方法

    static void setErr(PrintStream err)

    static void setIn(InputStream in)

    static void setOut(PrintStream out)

  9. JVM读取其他进程数据

    Process类提供了三种方法

    InputStream getErrorStream()

    InputStream getInputStream()

    OutputStream getOutputStream()

    输入输出流容易混淆,应该站在主程序的角度看问题,如果主程序想要往子进程写,则应该获取子进程的输出流,否则获取输入流

    Extra:IDEA下,启动java子进程,需要用-classpath选项指定具体.class文件的路径,否则找不到程序,而且PrintStream是有编码问题的,只有输入输出为英文时才没问题,否则需要做一定的修改。

  10. 随机读写文件的RandomAccessFile

    RandomAccessFile类能够自由访问文件的任意位置,可以自由定义文件记录指针。

    但仅能访问(读写)文件,不能读写其他IO节点。

  11. 对象序列化

    目的:将对象保存到磁盘中,使对象脱离程序独立存在,常用于Web端。

    把Java对象转化成平台无关的二进制流(序列化),可通过这个二进制流恢复成原来的Java对象(反序列化)。

    实现:对象的序列化必须实现SerializableExternalizable接口

    1. 实现Serializable

      让目标类实现该标记接口即可,无需实现任何方法,如:

      class Person implements java.io.Serializable /*只需要在此处实现标记接口即可*/
      {
          private String name;
          private int age;
          public Person(String name, int age)
          {
              this.name = name;
              this.age = age;
          }
          ......
      }
      
      public class WriteObject
      {
          public static void main(String[] args)
          {
              try(
                  ObjectOutputStream oos = new ObjectOutputStream(
                  new FileOutputStream("object.txt")))
                  {
                      Person per = new Person("XXX", 500);
                  	oos.writeObject(per);
                  }/*这样就将序列化的Person对象写入IO流中,完成序列化*/
              	catch (IOException ex)
                  {
                      ex.printStackTrace();
                  }
              
              try(
                  ObjectInputStream ois = new ObjectInputStream(
                  new FileInputStream("object.txt")))
                  {
                      Person p = (Person)ois.readObject();/*默认是Objcet类对象,
                      强制类型转换可以得到实际的对象*/
                  }/*这样就将序列化的Person对象读出IO流,完成反序列化*/
              	catch (IOException ex)
                  {
                      ex.printStackTrace();
                  }
              
          }
      }
      

      和普通文件一样,如果写入了多个对象,那么反序列化也必须按照顺序还原。

      几条原则:

      1. 一个可序列化类有多个父类时,这些父类必须也是可序列化的或者有无参数的构造器,否则反序列化会抛出InvalidClassException异常。
      2. 如果父类仅仅是带有无参数构造器,那么父类定义的成员变量值不会序列化到二进制流中
      3. 如果可序列化的目标类的成员变量含有其他类型(非String类、非基本类型)的引用类型,那么这个引用类也必须是可序列化的,因为有着递归序列化的机制

      当目标类实例的成员变量引用了相同的对象时,序列化会改为输出序列化编号而不是序列化对象。

      但这引入了问题:如果反序列化先还原了目标对象,并且修改了目标对象的值,后续反序列化还原出来的含有序列化编号的对象的参照物仍然是原来文件内的序列化对象。

      自定义序列化:

      1. transient关键字,只能修饰实例变量,使得对应变量不序列化

      2. 通过重写如下方法可以自定义序列化:

        private void writeObject(java.io.ObjectOutputStream out)throws IOException:自主决定序列化的内容,默认调用out.defaultWriteObject

        private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException:自主决定反序列化内容,默认调用in.defaultReadObject

        private void readObjectNodata()throws ObjectStreamException:反序列化对象版本不同时调用(比如新类比对象流中的实例变量少或多,可以不更新serialVersionUID),得到的值要么是null要么是0(少)或者忽略掉(多),java为序列化类提供一个private static final serialVersionUID值,用于标识序列化版本,这个值可以自己改动(默认由JVM提供),以保证正确地反序列化

      单例类、枚举类的反序列化

      应该提供readResolve()方法,保证反序列化对象正常,因为反序列化恢复Java对象时,并不需要构造器,而且该方法应该尽量写在最后的子类,否则会被还原为父类对象。

    2. Externalizable接口

      该方式完全由程序员决定存储和恢复对象数据,且在声明了实现该接口后,还必须实现 void readExternal(ObjectInput in)void writeExternal(ObjectOutput out)这两个方法

    3. 不会被序列化的内容

      方法、类变量、transient实例变量都不会被序列化

    4. 反序列化对象时必须有序列化对象的.class文件

  12. NIO

    NIO即新IO,采用内存映射文件来处理IO(模拟OS的方式),其核心类是ChannelBuffer类。

    Channel是对传统IO的模拟,所有的数据都要通过Channel传输,Channel与传统InputStreamOutputStream的最大区别是提供了一个map()方法,来将内存映射文件,或者说用Buffer映射文件。

    发送到Channel和从Channel中读取的数据都必须首先放到Buffer中。

    1. Buffer类

      该类是个抽象类。

      可通过static XxxBuffer allocate(int capacity)来得到一个容量为CapacityXxxBuffer对象。

      Buffer的三大重要概念:

      1. capacity:表示缓冲区的容量最大值,一经创建不能改变
      2. limit:第一个不能读出或写入缓冲区的位置索引。从limit索引开始的数据不可访问。
      3. position:下一个可以被访问的缓冲区索引,初始值为0
      4. mark:在position之前的一个索引,可以直接将position定位到mark处

      重要的方法:

      void flip();:将limit设为position,并让position置为0,为输出数据做好准备。

      void clear();:将position置为0,并将limit设为capacity,为读取数据做准备。

      所有子类提供的读写方法:

      void put();:向Buffer写入数据

      void get();向Buffer读入数据

      这两个方法都有绝对和相对读写,相对:从position处开始读写,然后更新position;绝对:根据索引向Buffer读写数据,不会改变position的值,此时需要给出索引值作为形参

      Extra:只有ByteBuffer提供了allocateDirect()方法创建直接Buffer,这种Buffer创建成本高,但执行效率高,适合长生存期。

    2. Channel

      两个特性:

      1. Channel可以直接将指定文件的部分或全部直接映射成Buffer
      2. 只能通过Buffer来访问Channel

      只能通过传统的流节点InputStreamOutputStreamgetChannel()方法来获取对应的ChannelRandomAccessFile也可以通过此方法得到Channel,但是读写取决于其打开方式。

      常用的方法:

      1. map():将Channel对应的部分或全部数据映射为MappedByteBuffer
      2. read():从Channel读,向Buffer写
      3. write():向Channel写,从Buffer读
    3. NIO.2

      提供了FilesPaths两个工具类

  13. 字符集

    Java默认使用Unicode字符集,可以使用Charset来处理字节序列和字符序列(字符串)之间的转换关系

  14. 文件锁

    FileChannel提供的lock()tryLock()可以得到FileLock对象。

    区别:

    1. lock():当试图锁定某个文件时,如果得不到文件锁,则程序阻塞。

    2. tryLock():尝试锁定文件,返回文件锁或null

      可以使用如下内容锁定文件的部分内容:

    3. lock(long position, long size, boolean shared)

    4. tryLock(long position, long size, boolean shared)

      shared为true时,表示为共享锁,否则为排他锁,如果没有形参则为默认情况,是排他锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值