java的I/O详解

  IO负责数据的传递,也是最容易形成瓶颈的一环,使用IO的情况有很多,大致分为以下几部分:
         ·字节操作(InputStream、OutputStream)
         ·字符操作(Writer、Reader)
         `磁盘操作
         ·控制台操作
           ·网络操作 socket (此类不在java.io包下面)
影响IO操作的要么是传输的数据格式(字符、字节),要么就是传输介质(磁盘、控制台、网络),java.io有很多jar包来解决这个问题。


1、输入流

java io类库可以分为输入流和输出流,输入流来读数据,输出流来写数据;输入流中分字节流的InputStream和字符流的Reader;输出流分字节流的OutputStream和字符流的Writer;

InputStream是字节输入流的超类,她的衍生子类都有read()方法,可读一个字节或者字节数组, 但是read()方法只负责读数据

OutputStream的衍生子类都有write()方法,用来写一个字节或者字节数组;

但是我们一般不来调用这个方法,因为一般我们需要流的时候,并不是简单的用一个类创建一个对象,而是组合多个对象形成一个流,这就是为什么java.io中流比较复杂的原因。



那么输入流读数据一般从哪里读呢?数据来源是什么,一般是如下几个方面:
直接已知子类: AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream
1、字节数组(我们用一个InputStream做例子,一般都需要将要读入的数据转换成字节数组)
2、字符串
3、文件系统
4、管道系统
5、filter 他可以通过组装其他流来给流提供额外的特性,比如让流必须使用缓存等
5、其他(如网络等)


下面分别介绍下几个输入流:

流名称作用构造器参数作用
ByteArrayInputStream1、她在内存中开辟一个空间作为她的一个缓冲区,读了字节数据放在这个缓冲区里面;
2、你关闭不了她,即使关闭她后,仍然能够使用她的read()方法,而不报错
ByteArrayInputStream(byte[] buf) 
创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组
StringBufferInputStream1、可以读字符串
2、但是读字符有问题,建议使用StringReader
StringBufferInputStream(String s) 
 创建一个字符串输入流,以从指定字符串读取数据。
FileInputStream1、读文件 字节文件(img等)、字符文件
2、但是读字符有问题,建议FileReader
FileInputStream(File file) 
要读取的文件
PipedInputStream1、读数据到输入管道流,并提供给输出管道流
2、不建议两者放在同一个线程,会堵塞
PipedInputStream(PipedOutputStream src) 创建 PipedInputStream,
以使其连接到传送输出流 src。
SequenceInputStream1、她将多个流按顺序串联起来,一个流一个流的读SequenceInputStream(InputStream s1, InputStream s2) 
          通过记住这两个参数初始化新创建的 SequenceInputStream
(将按顺序读取这两个参数,先读取 s1 然后读取 s2),以提供从此 SequenceInputStream 读取的字节
FilterInputStream1、其他流继承他,然后给这些流提供额外的功能,见下表 


继承自filterInputStream的类,他们扩展了如下的功能。
java.io.FilterInputStream
  java.io.BufferedInputStream
 java.io.DataInputStream (implements java.io.DataInput) 
java.io.LineNumberInputStream
 java.io.PushbackInputStream

FilterInputStream及其派生类有两项重要任务:

DataInputStream可以读取各种primitive及String。(所有的方法都以"read"打头,比如readByte( ), readFloat( ))。它,以及它的搭档DataOutputStream,能让你通过流将primitive数据从一个地方导到另一个地方。

  其它的类都是用来修改InputStream的内部行为的:是不是做缓冲,是不是知道它所读取的行信息(允许你读取行号或设定行号),是不是会弹出单个字符。后两个看上去更像是给编译器用的(也就是说,它们大概是为Java编译器设计的),所以通常情况下,你是不大会用到它们的。 

不论你用哪种I/O设备,输入的时候,最好都做缓冲。所以对I/O类库来说,比较明智的做法还是把不缓冲当特例(或者去直接调用方法),而不是像现在这样把缓冲当作特例

流名称
作用
构造器参数作用
DataInputStream1、可以从基本流中自由的读取java的基本类型和string,通过readXXX方法
2、因为前面基本的流都只是读字节数组或者字符串的,读不了int等,必须转换
DataInputStream(InputStream in) 
          使用指定的基础 InputStream 创建一个 DataInputStream。
BufferedInputStream1、强制使用缓冲区BufferedInputStream(InputStream in) 
          创建 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用
LineNumberInputStream1、读当前流数据行号
2、给编译器用的
 
PushbackInputStream1、她可以讲读出来的数据再放回去
2、编译器用的
 

2、输出流
输出流于输入流对应,输出流主要输出到下面几个部分:
直接已知子类 ByteArrayOutputStream(字节数组), FileOutputStream(文件), FilterOutputStream(扩展破坏器), ObjectOutputStream, OutputStream, PipedOutputStream(管道)、控制器
流名称
作用
构造器参数作用
ByteArrayOutputStream1、她在内存中开辟一个空间作为她的一个缓冲区,要写的字节数据放在这个缓冲区里面;
2、你关闭不了她,即使关闭她后,仍然能够使用她的read()方法,而不报错
ByteArrayOutputStream(byte[] buf) 
创建一个 ByteArrayOutputStream,使用 buf 作为其缓冲区数组
StringBufferOutputStream这个没有,写字符串不用缓存 
FileOutputStream1、写文件 字节文件(img等)、字符文件
2、但是写字符有问题,建议FileWriter

FileOutputStream(File file) 
要写的文件

FileOutputStream(File file,boolean append) 
要写的文件 采用追加的方式

PipedInputStream1、读数据到输入管道流,并提供给输出管道流
2、不建议两者放在同一个线程,会堵塞
 
SequenceInputStream
这个没有,排序交给input了 
FilterOutputStream1、其他流继承他,然后给这些流提供额外的功能,见下表 


继承自filterOutputStream的类,他们扩展了如下的功能。
java.io.FilterOutputStream
  java.io.BufferedOutputStream
  java.io.DataOutputStream (implements java.io.DataOutput) 
java.io.PrintStream (implements java.lang.Appendable, java.io.Closeable) 



流名称
作用
构造器参数作用
DataOutputStream1、可以向基本流中写入java基本类型和字符串
2、因为前面基本的流都只是写字节数组或者字符串的,写不了int、boolean等,必须转换
创建一个新的数据输出流,将数据写入指定基础输出流。
BufferedOutputStream
1、以前写数据每次都要调用文件系统,使用缓冲可以先缓冲一部分数据再交给基本流一次性写入。 
LineNumberInputStream1、读当前流数据行号
2、给编译器用的
 
PushbackInputStream1、她可以讲读出来的数据再放回去
2、编译器用的
 


下面是一个关于DataInputStream的例子

  1. package test.io.filter;  
  2.   
  3. import java.io.ByteArrayInputStream;  
  4. import java.io.ByteArrayOutputStream;  
  5. import java.io.DataInputStream;  
  6. import java.io.DataOutputStream;  
  7. import java.io.FileInputStream;  
  8. import java.io.FileNotFoundException;  
  9. import java.io.FileOutputStream;  
  10. import java.io.PrintStream;  
  11.   
  12. /** 
  13.  * @2012年12月19日10:59:58 
  14.  * @author sunyz 
  15.  * DataOutputStream 和DataInputStream 存在的意义: 
  16.  * 比如你想存储一个float值到文件当中,需要将float值转换成String对象,然后再分解成字节数组才能够存放,使用FileWriter; 
  17.  * 然而使用DataOutputStream 不用考虑这么多的数据类型,它支持java各种基本类型和String,只需要使用它的writeXXX()方法 
  18.  * 就可以了,所以很是方便 
  19.  *  
  20.  * 同样DataInputStream 只需要使用readXXX()读出来相应的方法,DataInputStream需要的构造参数就是DataOutputStream 
  21.  */  
  22. public class FilterInputStreamTest {  
  23.   
  24.     public static void main(String[] args) {  
  25.         testOutput();  
  26.     }  
  27.       
  28.     /** 
  29.      * 现在使用DataOutputStream 测试如何存储float到number.txt文件当中 
  30.      */  
  31.     public static void testOutput(){  
  32.         String source = "请将我读出来";  
  33.         try {  
  34.             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
  35.             DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);  
  36.             //dataOutputStream.writeChars(source);  
  37.             dataOutputStream.writeFloat(12.30f);  
  38.             dataOutputStream.writeBoolean(false);  
  39.               
  40.             ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());  
  41.             DataInputStream dataInputStream = new DataInputStream(byteArrayInputStream);  
  42.               
  43.             System.out.println(dataInputStream.available());  
  44.             System.out.println(dataInputStream.readFloat());  
  45.             System.out.println(dataInputStream.readBoolean());  
  46.             byteArrayOutputStream.close();  
  47.             dataOutputStream.close();  
  48.               
  49.               
  50.         } catch (Exception e) {  
  51.             e.printStackTrace();  
  52.         }  
  53.           
  54.     }  
  55.   
  56. }  

 

3、本身的缺陷:RandomAccessFile

       从类名上看他不是输入io类的,但是他实现了InputStream 和 OutputStream当中的接口,他跟其他IO类相比,有一个很大的功能就是:能够在使用seek()方法在一个文件的任何位置移动,(前提是这个文件长度是已知的),能在任意位置读取和修改这个文件,看起来好像是InputStream和OutputStream两者的结合,他的构造函数中RandomAccessFile(File file,String mode)  mode参数还能设定文件的访问类型,是只读?读写?还是什么的

     文件的读写权限,可以参考SecurityManager.checkRead();权限。认识SecurityManager请参考http://blog.csdn.net/zhoche2008/article/details/7101830

     跟RandomAccessFile类一比较就看出来IO的缺陷出来,比方说我们想象RandomAccessFile一样,随便的访问流中的任意一个位置,这是做不到的,RandomAccessFile虽然可以访问任意位置,但是只限于找文件。

备注:

 1、涉及到字节到字符的转换,一般用InputStreamReader(读字节)

 2、涉及到字符到字节的转换,一般用OutputStreamWriter(写字节)

 
******************************************************

一.I/O 流(java 如何实现与外界数据的交流)
 流定义:
    任何有能力产出数据的数据源对象或者有能力接收数据的数据源对象。他屏蔽了实际的I/O设备处理数据的细节。
1.Input/Output:指跨越出了JVM 的边界,与外界数据的源头或者目标数据源进行数据交换,这正因为跨出了JVM的边界,所以要我们手动来关闭close()

2.流的分类:
   按流向分为:输入流和输出流;
   按传输单位分为:字节流(Stream)结尾的和字符流(Reader和Writer);
   按功能分为:节点流和过滤流。
                           节点流:负责数据源和程序之间建立连接;(相当于裸枪)
                           过滤流:用于给节点增加功能。(相当于功能零部件)FilterInputStream and FilterOutputStream,他以其他流作为构造函数的参数(这样的设计模式称为装饰模式)。
 
   注:I/O流是很消耗性能,所以使用完后必须调用close()方法关闭流并释放资源。在关闭流时只用关闭最外层的流。   


 3.File 类(java.io.*)可表示一个文件,也有可能是一个目录
    (在JAVA 中文件和目录都属于这个类中,而且区分不是非常的明显 
 4.Java.io 下的方法是对磁盘上的文件进行磁盘操作,但是无法读取文件的内容。
注意:创建一个文件对象和创建一个文件在JAVA 中是两个不同的概念。前者是在虚拟机中创建了一个文件,
      但却并没有将它真正地创建到OS 的文件系统中,随着虚拟机的关闭,这个创建的对象也就消失了。
      而创建一个文件才是在系统中真正地建立一个文件。          

二File类:

         我们猛看起来像是文件,但是他可能并不存在,是一个虚拟对象,原因是因为大多数情况下我们并不关心他到底是不是真的,只是在用到的时候才去用他,就好像电话号码本一样,里面有电话号码,但是我们并不是说一个个的去使用他;有一个类FileDescriptor就会产生真实的文件对象。

         其实File类也可以指代一个文件集,当作为文件集时我们可以对此调用List方法。
         这个类的常用构造有:File("路径"),File(“前边路径”,“后边路径”)File(File,“路径”)
         这个类的常用方法:exists(),delete(),getName(),getPath(),isDirectory(),isFile(),length(),
                        listFile(FileFilter),主要用来过滤文件这里有(可以用来嵌套内部类) 
                        mkdir(),mkdirs(),toString(),
         FileFilter接口只有accept方法,其返回值为Boolean型,可以在其里边写一些正则表达式来对文件进行筛选。
         这里需要注意的是传入accept的参数必须是final类型(匿名内部类的要求),这样他才能使用该类范围之外的队像。
         顺便讲讲:内部类优点 高聚拢性,缺点在于不易阅读,谨慎使用。
三 字节流:
  InputStream/OutputStream 所有输入输入流的父类,是抽象类。
            子类有:

           FileInputStream/FileOutputStream(节点流)(注意FileInputStream(path名,boolean)Boolean为true 表示拼接)
           DataInputStream/DataOutputStream  数据输入流允许应用程序以与"机器无关方式"从底层输入流中读取基本 Java 数据类型 (8种基本类型,加一种String)读入写出时类型必须一致
           BufferedInputStream/BufferedOutputStream 字节缓冲流 用于给节点流增加一个缓冲的功能。(典型的牺牲空间换时间)
      

      1.字节输入流:io包中的InputStream为所有字节输入流的父类。
         Int read();读入一个字节(每次一个);
         可先使用new byte[]=数组,调用read(byte[] b)
         read (byte[])返回值可以表示有效数;read (byte[])返回值为-1 表示结束。 
      2.在流中close()方法由程序员控制。因为输入输出流已经超越了JVM的边界,所以有时可能无法回收资源。
         原则:凡是跨出虚拟机边界的资源都要求程序员自己关闭,不要指望垃圾回收。


四、字节流的字符编码:
     字符编码把字符转换成数字存储到计算机中,按ASCii 将字母映射为整数。
     把数字从计算机转换成相应的字符的过程称为解码
     乱码的根源在于编解码方式不统一。在世界上任何一种编码方式中都会向上兼容ASCII码。所以英文没有乱码。
     编码方式的分类:
     ASCII(数字、英文):1 个字符占一个字节(所有的编码集都兼容ASCII)
     ISO8859-1(欧洲):1 个字符占一个字节
     GB-2312/GBK:1 个字符占两个字节。GB代表国家标准。
     GBK是在GB-2312上增加的一类新的编码方式,也是现在最常用的汉字编码方式。
     Unicode: 1 个字符占两个字节(网络传输速度慢)
     UTF-8:变长字节,对于英文一个字节,汉字三个字节。
     原则:保证编解码方式的统一,才能不至于出现错误。   
    I/O学习种常范的两个错误 1。忘了加flush2.没有加换行。
五。字符流
  Reader/Writer所有字符流的父类
  其子类有
  InputStreamReader/OutputStreamWriter 称为从字节流到字符流的桥转换类。这个类可以设定字符转换方式 
  FileReader/FileWriter (FileInputStream/FileOutputStream和InputStreamReader/OutputStreamWriter)的组合,只能是本地默认编码
  Bufferedreader/BufferedWriter  字符缓冲流,其特有方法readLine()读取一个文本行,newLine()表示Writer到下一个文本行
六 .对象序列化
   1. 定义:把一个对象通过I/O流写到文件(持久性介质)上的过程叫做对象的序列化。
   2. 序列化接口:Serializable
       此接口没有任何的方法,这样的接口称为标记接口。
   3. 不是所有对象都能序列化的,只有实现了Serializable的类,他的实例对象才是可序列化的。
   4. 在Java种定义了一套序列化规范,对象的编码和解码方式都是已经定义好的。
   5. class ObjectOutputStream 和ObjectInputStream也是过滤流,使节点流直接获得输出对象。
       ganbin@tarena.com.cn
      最有用的方法:
    (1)writeObject(Object b)
    (2)readObject();该方法返回的是读到的一个对象,但是需要我们注意的是,该方法不会以返回null表示读到文件末尾。
     而是当读到文件末尾时会抛出一个IOException;
    6. 序列化一个对象并不一定会序列化该对象的父类对象
    7. 瞬间属性(临时属性)不参与序列化过程。
    8. 所有属性必须都是可序列化的,特别是当有些属性本身也是对象的时候,要尤其注意这一点。序列化的集合就要求集合中的每一个元素都是可序列化的。
    9. 用两次序列化把两个对象写到文件中去(以追加的方式),
    和用一次序列化把两个对象写进文件的大小是不一样的。
    因为每次追加时都会要在文件中加入一个开始标记和结束标记。所以对于对象的序列化不能以追加的方式写到文件中。

*******************************************************
还有要序列化的:

  1. <span style="WHITE-SPACE: pre"> </span>我们可以通过序列化来保存一个对象的状态(实例变量)到文件中,也可以从这个格式化的文件中很容易地读取对象的状态从而可以恢复我们保存的对象。  

对象序列化就是把对象写入到输出流中,用来存储或者传输。

对象的反序列化就是从输入流中读取对象。

      用来实现序列化的类都在java.io包中,我们常用的类或接口有:

        ObjectOutputStream:提供序列化对象并把其写入流的方法

ObjectInputStream:读取流并反序列化对象

Serializable:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。

Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;

        但是如果你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient就可以实现了。

  1. import java.io.*;   
  2.   
  3. /**  
  4. * Java对象的序列化测试  
  5. * File: ObjectStreamTest.java  
  6. * User: leizhimin  
  7. * Date: 2008-3-12 20:41:43  
  8. */   
  9. public class ObjectStreamTest {   
  10.     public static void main(String args[]) {   
  11.         testObjectSeri();   
  12.         testObjectInSeri();   
  13.   
  14.     }   
  15.   
  16.     /**  
  17.      * 对象序列化测试  
  18.      */   
  19.     public static void testObjectSeri() {   
  20.         Person person = new Person("熔岩", "341022225562156", "lavasoft");   
  21.         FileOutputStream fos = null;   
  22.         ObjectOutputStream oos = null;   
  23.         try {   
  24.             fos = new FileOutputStream("Q:\\study\\java5study\\src\\io\\person.dat");   
  25.             oos = new ObjectOutputStream(fos);   
  26.             oos.writeObject(person);   
  27.         } catch (FileNotFoundException e) {   
  28.             System.out.println("找不到指定的文件!");   
  29.             e.printStackTrace();   
  30.         } catch (IOException e) {   
  31.             e.printStackTrace();   
  32.         } finally {   
  33.             try {   
  34.                 oos.flush();   
  35.                 oos.close();   
  36.             } catch (IOException e) {   
  37.                 e.printStackTrace();   
  38.             }   
  39.         }   
  40.     }   
  41.   
  42.     /**  
  43.      * 对象反序列化测试  
  44.      */   
  45.     public static void testObjectInSeri() {   
  46.         FileInputStream fis = null;   
  47.         ObjectInputStream ois = null;   
  48.         Person person = null;   
  49.         try {   
  50.             fis = new FileInputStream("Q:\\study\\java5study\\src\\io\\person.dat");   
  51.             ois = new ObjectInputStream(fis);   
  52.             person = (Person) ois.readObject();   
  53.         } catch (FileNotFoundException e) {   
  54.             e.printStackTrace();   
  55.         } catch (IOException e) {   
  56.             e.printStackTrace();   
  57.         } catch (ClassNotFoundException e) {   
  58.             e.printStackTrace();   
  59.         } finally {   
  60.             try {   
  61.                 ois.close();   
  62.             } catch (IOException e) {   
  63.                 e.printStackTrace();   
  64.             }   
  65.         }   
  66.   
  67.         System.out.println(person.toString());   
  68.     }   
  69. }   
  70.   
  71. /**  
  72. * 测试序列化所用的类  
  73. */   
  74. class Person implements Serializable {   
  75.     private String username;   
  76.     private String cardNumber;   
  77.     private transient String password;   
  78.   
  79.     public Person(String username, String cardNumber, String password) {   
  80.         this.username = username;   
  81.         this.cardNumber = cardNumber;   
  82.         this.password = password;   
  83.     }   
  84.   
  85.     public String getUsername() {   
  86.         return username;   
  87.     }   
  88.   
  89.     public void setUsername(String username) {   
  90.         this.username = username;   
  91.     }   
  92.   
  93.     public String getCardNumber() {   
  94.         return cardNumber;   
  95.     }   
  96.   
  97.     public void setCardNumber(String cardNumber) {   
  98.         this.cardNumber = cardNumber;   
  99.     }   
  100.   
  101.     public String getPassword() {   
  102.         return password;   
  103.     }   
  104.   
  105.     public void setPassword(String password) {   
  106.         this.password = password;   
  107.     }   
  108.   
  109.     public String toString() {   
  110.         StringBuffer sb = new StringBuffer(this.getClass().getName());   
  111.         sb.append("[");   
  112.         sb.append("\n\t");   
  113.         sb.append("username=" + this.username);   
  114.         sb.append("\n\t");   
  115.         sb.append("cardNumber=" + this.cardNumber);   
  116.         sb.append("\n\t");   
  117.         sb.append("password=" + this.password);   
  118.         sb.append("]");   
  119.         return sb.toString();   
  120.     }   
  121. }  

运行结果如下:
  1. io.Person[   
  2.     username=熔岩   
  3.     cardNumber=341022225562156   
  4.     password=null]   
  5.   
  6. Process finished with exit code 0   

Serializable接口和Externalizable接口的区别不仅限于此:

Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器的!!!


其他说明:

1、        基本类型 的数据可以直接序列化

2、        对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。比如上面 的例子中,Student类中有一个Book类型 的实例就是,要想让Student的对象成功序列化,那么Book也必须要实现Serializable接口;

如果不想让Book实现Serializable接口,并且让Student类成功序列化也可以,使用transient关键字

 

serialVersionUID作用:序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 有两种生成方式: 一个是默认的1L,比如:private static final long se...

serialVersionUID作用: 
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。
 
有两种生成方式:
 
一个是默认的1L,比如:
private static final long serialVersionUID = 1L;
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
 
private static final long serialVersionUID = xxxxL; 
当你一个类实现了Serializable接口,如果没有定义serialVersionUIDEclipse会提供这个
 
提示功能告诉你去定义 。在Eclipse中点击类中warning的图标一下,Eclipse就会
 
自动给定两种生成的方式。如果不想定义它,在Eclipse的设置中也
 
可以把它关掉的,设置如下:
 
Window ==> Preferences ==> Java ==> Compiler ==> Error/Warnings ==> 
Potential programming problems 
Serializable class without serialVersionUIDwarning改成ignore即可。
 

 


最后,还有两个问题:

1、        如果一个类没有实现Serializable接口,但是它的基类实现 了,这个类可不可以序列化?

2、        和上面相反,如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?

 

第1个问题:一个类实现 了某接口,那么它的所有子类都间接实现了此接口,所以它可以被 序列化。

第2个问题:Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?这个我将在下一篇文章中用一个例子来说明,请见http://blog.csdn.net/moreevan/article/details/6698529

第3个问题:如果将一个对象写入某文件(比如是a),那么之后对这个对象进行一些修改,然后把修改的对象再写入文件a,那么文件a中会包含该对象的两个 版本吗?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值