信息发布软件,b2b软件,广告发布软件

 找回密码
 立即注册
搜索
查看: 2688|回复: 3
打印 上一主题 下一主题

[『 Java 图文教程』] Java对象序列化使用基础和实例教程

  [复制链接]

575

主题

659

帖子

4288

积分

积分
4288
跳转到指定楼层
宣传软件楼主
发表于 2016-10-20 15:27:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

软件教程首图:

软件教程分类:Java 图文教程 

软件图文教程视频教程分类:软件图文教程 

软件教程难易程度:软件高级教程 

软件教程发布日期:2016-10-20

软件教程关键字:

① 本信息收集于网络,如有不对的地方欢迎联系我纠正!
② 本信息免费收录,不存在价格的问题!
③ 如果您的网站也想这样出现在这里,请您加好友情链接,我当天会审核通过!

④友情链接关键字:软件定制网站 网址:http://www.postbbs.com

软件教程详细描述

所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。这个过程也可以通过网络实现,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新"装配"。像RMI、Socket、JMS、EJB它们中的一种,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。  

    Java对象序列化机制一般来讲有两种用途:

    Java的JavaBeans: Bean的状态信息通常是在设计时配置的,Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息,这需要将对象的状态保存到文件中,而后能够通过读入对象状态来重新构造对象,恢复程序状态。

    RMI允许象在本机上一样操作远程机器上的对象;或使用套接字在网络上传送对象的程序来说,这些都是需要实现serializaiton机制的。

    我们通过让类实现Java.io.Serializable 接口可以将类序列化。这个接口是一个制造者(marker)接口。也就是说,对于要实现它的类来说,该接口不需要实现任何方法。它主要用来通知Java虚拟机(JVM),需要将一个对象序列化。

    对于这个,有几点我们需要明确:

    并非所有类都可以序列化,在cmd下,我们输入serialver Java.net.Socket,可以得到socket是否可序列化的信息,实际上socket是不可序列化的。

    Java有很多基础类已经实现了serializable接口,比如string,vector等。但是比如hashtable就没有实现serializable接口。

    将对象读出或者写入流的主要类有两个: ObjectOutputStream与ObjectInputStream .ObjectOutputStream 提供用来将对象写入输出流的writeObject方法, ObjectInputStream提供从输入流中读出对象的readObject方法。使用这些方法的对象必须已经被序列化的。也就是说,必须已经实现 Serializable接口。如果你想writeobject一个hashtable对象,那么,会得到一个异常。

    序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用Java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。

    对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

    Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

    Java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现Java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。


    序列化机制:

    序列化分为两大部分:序列化 和反序列化 。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:

    处理对象流:

    (序列化过程和反序列化过程)

    Java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。

    我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。

    writeObject() 方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个 ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。

    下面,让我们从例子中来了解ObjectOutputStream这个类吧。

// 序列化 today's date 到一个文件中.

FileOutputStream  f = new  FileOutputStream ("tmp" );

ObjectOutputStream  s = new  ObjectOutputStream (f);

s.writeObject("Today" );

s.writeObject(new  Date ());

s.flush();

    现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。 ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。 readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出 ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。

    例子如下:

//从文件中反序列化 string 对象和 date 对象

FileInputStream  in = new  FileInputStream ("tmp" );

ObjectInputStream  s = new  ObjectInputStream (in);

String  today = (String )s.readObject();

Date  date = (Date )s.readObject();

    定制序列化过程:

    序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。

    例子:一个非常简单的序列化类。

public  class  simpleSerializableClass implements  Serializable {

String  sToday="Today:" ;

transient  Date  dtToday=new  Date ();

}

    序列化时,类的所有数据成员应可序列化除了声明为transient 或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与 readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。

    关于如何使用定制序列化的部分代码如下

//重写writeObject()方法以便处理transient的成员。

public  void  writeObject(ObjectOutputStream  outputStream) throws  IOException {

outputStream.defaultWriteObject();//使定制的writeObject()方法可以

利用自动序列化中内置的逻辑。

outputStream.writeObject(oSocket.getInetAddress());

outputStream.writeInt(oSocket.getPort());

}

//重写readObject()方法以便接收transient的成员。

private  void  readObject(ObjectInputStream  inputStream) throws IOException ,

ClassNotFoundException {

inputStream.defaultReadObject();//defaultReadObject()补充自动序列化

InetAddress  oAddress=(InetAddress )inputStream.readObject();

int  iPort =inputStream.readInt();

oSocket = new  Socket (oAddress,iPort);

iID=getID();

dtToday =new  Date ();

}

    完全定制序列化过程:

    如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现 Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现 Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。



unto自媒体真的可以月入几万块钱吗 不管您信不信反正我觉得不是那么容易的事nextJava编程中的IO系统和多实例分解教程
回复

使用道具 举报

575

主题

659

帖子

4288

积分

积分
4288
信息发布软件沙发
 楼主| 发表于 2016-10-20 15:30:43 | 只看该作者
在这篇文章里,我们关注对象序列化。
  首先,我们来讨论一下什么是序列化以及序列化的原理;然后给出一个简单的示例来演示序列化和反序列化;有时有些信息是不应该被序列化的,我们应该如何控制;我们如何去自定义序列化内容;最后我们讨论一下在继承结构的场景中,序列化需要注意哪些内容。
  序列化概述
  序列化,简单来讲,就是以“流”的方式来保存对象,至于保存的目标地址,可以是文件,可以是数据库,也可以是网络,即通过网络将对象从一个节点传递到另一个节点。
  我们知道在Java的I/O结构中,有ObjectOutputStream和ObjectInputStream,它们可以实现将对象输出为二进制流,并从二进制流中获取对象,那为什么还需要序列化呢?这需要从Java变量的存储结构谈起,我们知道对Java来说,基础类型存储在栈上,复杂类型(引用类型)存储在堆中,对于基础类型来说,上述的操作时可行的,但对复杂类型来说,上述操作过程中,可能会产生重复的对象,造成错误。
  而序列化的工作流程如下:
  1)通过输出流保存的对象都有一个唯一的序列号。
  2)当一个对象需要保存时,先对其序列号进行检查。
  3)当保存的对象中已包含该序列号时,不需要再次保存,否则,进入正常保存的流程。
  正是通过序列号的机制,序列化才可以完整准确的保存对象的各个状态。
  序列化保存的是对象中的各个属性的值,而不是方法或者方法签名之类的信息。对于方法或者方法签名,只要JVM能够找到正确的ClassLoader,那么就可以invoke方法。
  序列化不会保存类的静态变量,因为静态变量是作用于类型,而序列化作用于对象。
  简单的序列化示例
  序列化的完整过程包括两部分:
  1)使用ObjectOutputStream将对象保存为二进制流,这一步叫做“序列化”。
  2)使用ObjectInputStream将二进制流转换成对象,这一步叫做“反序列化”。
  下面我们来演示一个简单的示例,首先定义一个Person对象,它包含name和age两个信息。
Java对象序列化使用基础和实例教程 b2b软件 定义Person对象
  然后是两个公共方法,用来完成读、写对象的操作:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void writeObject(Object obj, String filePath) 2 { 3     try 4     { 5         FileOutputStream fos = new FileOutputStream(filePath); 6         ObjectOutputStream os = new ObjectOutputStream(fos); 7         os.writeObject(obj); 8         os.flush(); 9         fos.flush();10         os.close();11         fos.close();12         System.out.println("序列化成功。");13     }14     catch(Exception ex)15     {16         ex.printStackTrace();17     }18 }19 20 private static Object readObject(String filePath)21 {22     try23     {24         FileInputStream fis = new FileInputStream(filePath);25         ObjectInputStream is = new ObjectInputStream(fis);26         27         Object temp = is.readObject();28         29         fis.close();30         is.close();31         32         if (temp != null)33         {34             System.out.println("反序列化成功。");35             return temp;36         }37     }38     catch(Exception ex)39     {40         ex.printStackTrace();41     }42     43     return null;44 } Java对象序列化使用基础和实例教程 b2b软件

  这里,我们将对象保存的二进制流输出到磁盘文件中。
  接下来,我们首先来看“序列化”的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest1()2 {3     Person person = new Person();4     person.setName("Zhang San");5     person.setAge(30);6     System.out.println(person);7     writeObject(person, "d:\\temp\\test\\person.obj");8 } Java对象序列化使用基础和实例教程 b2b软件

  我们定义了一个Person实例,然后将其保存到d:\temp\test\person.obj中。
  最后,是“反序列化”的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void deserializeTest1()2 {    3     Person temp = (Person)readObject("d:\\temp\\test\\person.obj");4     5     if (temp != null)6     {7         System.out.println(temp);8     }9 } Java对象序列化使用基础和实例教程 b2b软件

  它从d:\temp\test\person.obj中读取对象,然后进行输出。
  上述两个方法的执行结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:30
  可以看出,读取的对象和保存的对象是完全一致的。
  隐藏非序列化信息
  有时,我们的业务对象中会包含很多属性,而有些属性是比较隐私的,例如年龄、银行卡号等,这些信息是不太适合进行序列化的,特别是在需要通过网络来传输对象信息时,这些敏感信息很容易被窃取。
  Java使用transient关键字来处理这种情况,针对那些敏感的属性,我们只需使用该关键字进行修饰,那么在序列化时,对应的属性值就不会被保存。
  我们还是看一个实例,这次我们定义一个新的Person2,其中age信息是我们不希望序列化的:
Java对象序列化使用基础和实例教程 b2b软件 定义Person2对象
  注意age的声明语句:
1 private transient int age;
  下面是“序列化”和“反序列化”的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest2() 2 { 3     Person2 person = new Person2(); 4     person.setName("Zhang San"); 5     person.setAge(30); 6     System.out.println(person); 7     writeObject(person, "d:\\temp\\test\\person2.obj"); 8 } 9 10 private static void deserializeTest2()11 {    12     Person2 temp = (Person2)readObject("d:\\temp\\test\\person2.obj");13     14     if (temp != null)15     {16         System.out.println(temp);17     }18 } Java对象序列化使用基础和实例教程 b2b软件

  它的输出结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:0
  可以看到经过反序列化的对象,age的信息变成了Integer的默认值0。
  自定义序列化过程
  我们可以对序列化的过程进行定制,进行更细粒度的控制。
  思路是在业务模型中添加readObject和writeObject方法。下面看一个实例,我们新建一个类型,叫Person3:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person3 implements Serializable 2 { 3     private String name; 4     private transient int age; 5     public void setName(String name) { 6         this.name = name; 7     } 8     public String getName() { 9         return name;10     }11     public void setAge(int age) {12         this.age = age;13     }14     public int getAge() {15         return age;16     }17     18     public String toString()19     {20         return "Name:" + name + "; Age:" + age;21     }22     23     private void writeObject(ObjectOutputStream os)24     {25         try26         {27             os.defaultWriteObject();28             os.writeObject(this.age);29             System.out.println(this);30             System.out.println("序列化成功。");31         }32         catch(Exception ex)33         {34             ex.printStackTrace();35         }36     }37     38     private void readObject(ObjectInputStream is)39     {40         try41         {42             is.defaultReadObject();43             this.setAge(((Integer)is.readObject()).intValue() - 1);44             System.out.println("反序列化成功。");45             System.out.println(this);46         }47         catch(Exception ex)48         {49             ex.printStackTrace();50         }51     }52 } Java对象序列化使用基础和实例教程 b2b软件

  请注意观察readObject和writeObject方法,它们都是private的,接受的参数是ObjectStream,然后在方法体内调用了defaultReadObject或者defaultWriteObject方法。
  这里age同样是transient的,但是在保存对象的过程中,我们单独对其进行了保存,在读取时,我们将age信息读取出来,并进行了减1处理。
  下面是测试方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest3() 2 { 3     Person3 person = new Person3(); 4     person.setName("Zhang San"); 5     person.setAge(30); 6     System.out.println(person); 7     try 8     { 9         FileOutputStream fos = new FileOutputStream("d:\\temp\\test\\person3.obj");10         ObjectOutputStream os = new ObjectOutputStream(fos);11         os.writeObject(person);12         fos.close();13         os.close();14     }15     catch(Exception ex)16     {17         ex.printStackTrace();18     }19 }20 21 private static void deserializeTest3()22 {    23     try24     {25         FileInputStream fis = new FileInputStream("d:\\temp\\test\\person3.obj");26         ObjectInputStream is = new ObjectInputStream(fis);27         is.readObject();28         fis.close();29         is.close();30     }31     catch(Exception ex)32     {33         ex.printStackTrace();34     }35 } Java对象序列化使用基础和实例教程 b2b软件

  输出结果如下:
Name:Zhang San; Age:30序列化成功。反序列化成功。Name:Zhang San; Age:29
  可以看到,经过反序列化得到的对象,其age属性已经减1。
  探讨serialVersionUID
  在上文中,我们描述序列化原理时,曾经提及每个对象都会有一个唯一的序列号,这个序列号,就是serialVersionUID。
  当我们的对象实现Serializable接口时,该接口可以为我们生成serialVersionUID。
  有两种方式来生成serialVersionUID,一种是固定值:1L,一种是经过JVM计算,不同的JVM采取的计算算法可能不同。
  下面就是两个serialVersionUID的示例:
1 private static final long serialVersionUID = 1L;2 private static final long serialVersionUID = -2380764581294638541L;
  第一行是采用固定值生成的;第二行是JVM经过计算得出的。
  那么serialVersionUID还有其他用途吗?
  我们可以使用它来控制版本兼容。如果采用JVM生成的方式,我们可以看到,当我们业务对象的代码保持不变时,多次生成的serialVersionUID也是不变的,当我们对属性进行修改时,重新生成的serialVersionUID会发生变化,当我们对方法进行修改时,serialVersionUID不变。这也从另一个侧面说明,序列化是作用于对象属性上的。
  当我们先定义了业务对象,然后对其示例进行了“序列化”,这时根据业务需求,我们修改了业务对象,那么之前“序列化”后的内容还能经过“反序列化”返回到系统中吗?这取决于业务对象是否定义了serialVersionUID,如果定义了,那么是可以返回的,如果没有定义,会抛出异常。
  来看下面的示例,定义新的类型Person4:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person4 implements Serializable 2 { 3     private String name; 4     private int age; 5     public void setName(String name) { 6         this.name = name; 7     } 8     public String getName() { 9         return name;10     }11     public void setAge(int age) {12         this.age = age;13     }14     public int getAge() {15         return age;16     }17     private void xxx(){}18     19     public String toString()20     {21         return "Name:" + name + "; Age:" + age;22     }23 } Java对象序列化使用基础和实例教程 b2b软件

  然后运行下面的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest4()2 {3     Person4 person = new Person4();4     person.setName("Zhang San");5     person.setAge(30);6     7     writeObject(person, "d:\\temp\\test\\person4.obj");8 } Java对象序列化使用基础和实例教程 b2b软件

  接下来修改Person4,追加address属性:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person4 implements Serializable 2 { 3     private String name; 4     private int age; 5     private String address; 6     public void setName(String name) { 7         this.name = name; 8     } 9     public String getName() {10         return name;11     }12     public void setAge(int age) {13         this.age = age;14     }15     public int getAge() {16         return age;17     }18     private void xxx(){}19     20     public String toString()21     {22         return "Name:" + name + "; Age:" + age;23     }24     public void setAddress(String address) {25         this.address = address;26     }27     public String getAddress() {28         return address;29     }30 } Java对象序列化使用基础和实例教程 b2b软件

  然后运行“反序列化”方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void deserializeTest4()2 {    3     Person4 temp = (Person4)readObject("d:\\temp\\test\\person4.obj");4     5     if (temp != null)6     {7         System.out.println(temp);8     }9 } Java对象序列化使用基础和实例教程 b2b软件

  可以看到,运行结果如下:
Java对象序列化使用基础和实例教程 b2b软件
java.io.InvalidClassException: sample.serialization.Person4; local class incompatible: stream classdesc serialVersionUID = -2380764581294638541, local class serialVersionUID = -473458100724786987    at java.io.ObjectStreamClass.initNonProxy(Unknown Source)    at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)    at java.io.ObjectInputStream.readClassDesc(Unknown Source)    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)    at java.io.ObjectInputStream.readObject0(Unknown Source)    at java.io.ObjectInputStream.readObject(Unknown Source)    at sample.serialization.Sample.readObject(Sample.java:158)    at sample.serialization.Sample.deserializeTest4(Sample.java:105)    at sample.serialization.Sample.main(Sample.java:16) Java对象序列化使用基础和实例教程 b2b软件

  但是当我们在Person4中添加serialVersionUID后,再次执行上述各步骤,得出的运行结果如下:
反序列化成功。Name:Zhang San; Age:30
  有继承结构的序列化
  业务对象会产生继承,这在管理系统中是经常看到的,如果我们有下面的业务对象:
Java对象序列化使用基础和实例教程 b2b软件
1 class Person5 2 { 3     private String name; 4     private int age; 5     public void setName(String name) { 6         this.name = name; 7     } 8     public String getName() { 9         return name;10     }11     public void setAge(int age) {12         this.age = age;13     }14     public int getAge() {15         return age;16     }17     18     public String toString()19     {20         return "Name:" + name + "; Age:" + age;21     }22     23     public Person5(String name, int age)24     {25         this.name = name;26         this.age = age;27     }28 }29 30 class Employee extends Person5 implements Serializable31 {32     public Employee(String name, int age) {33         super(name, age);34     }35 36     private String companyName;37 38     public void setCompanyName(String companyName) {39         this.companyName = companyName;40     }41 42     public String getCompanyName() {43         return companyName;44     }45     46     public String toString()47     {48         return "Name:" + super.getName() + "; Age:" + super.getAge() + "; Company:" + this.companyName;49     }50 } Java对象序列化使用基础和实例教程 b2b软件

  Employee继承在Person5,Employee实现了Serializable接口,Person5没有实现,那么运行下面的方法:
Java对象序列化使用基础和实例教程 b2b软件
1 private static void serializeTest5() 2 { 3     Employee emp = new Employee("Zhang San", 30); 4     emp.setCompanyName("XXX"); 5      6     writeObject(emp, "d:\\temp\\test\\employee.obj"); 7 } 8  9 private static void deserializeTest5()10 {    11     Employee temp = (Employee)readObject("d:\\temp\\test\\employee.obj");12     13     if (temp != null)14     {15         System.out.println(temp);16     }17 } Java对象序列化使用基础和实例教程 b2b软件

  会正常运行吗?事实上不会,它会抛出如下异常:
Java对象序列化使用基础和实例教程 b2b软件
java.io.InvalidClassException: sample.serialization.Employee; no valid constructor    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)    at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)    at java.io.ObjectInputStream.readObject0(Unknown Source)    at java.io.ObjectInputStream.readObject(Unknown Source)    at sample.serialization.Sample.readObject(Sample.java:158)    at sample.serialization.Sample.deserializeTest5(Sample.java:123)    at sample.serialization.Sample.main(Sample.java:18)

  原因:在有继承层次的业务对象,进行序列化时,如果父类没有实现Serializable接口,那么父类必须提供默认构造函数。
  我们为Person5添加如下默认构造函数:
1 public Person5()2 {3     this.name = "Test";4     this.age = 1;5 }
  再次运行上述代码,结果如下:
Name:Zhang San; Age:30; Company:XXX序列化成功。反序列化成功。Name:Test; Age:1; Company:XXX
  可以看到,反序列化后的结果,父类中的属性,已经被父类构造函数中的赋值代替了!
  因此,我们推荐在有继承层次的业务对象进行序列化时,父类也应该实现Serializable接口。我们对Person5进行修改,使其实现Serializable接口,执行结果如下:
Name:Zhang San; Age:30; Company:XXX序列化成功。反序列化成功。Name:Zhang San; Age:30; Company:XXX
  这正是我们期望的结果。


回复 支持 反对

使用道具 举报

575

主题

659

帖子

4288

积分

积分
4288
推广工具板凳
 楼主| 发表于 2016-10-20 15:40:37 | 只看该作者
Java中的对象的内部状态只保存在内存中,其生命周期最长与JVM的生命周期一样,即JVM停止之后,所有对象都会被销毁。但有时候,Java对象是需要持久化的,因此Java提供了一种对象持久化方式——对象序列化机制(Object serialization),可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。
对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
注意:序列化的是对象(对象的状态,成员变量等),而不是类
简单示例?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]package [size=1em]java_interview;
[size=1em]import [size=1em]java.io.File;
[size=1em]import [size=1em]java.io.FileInputStream;
[size=1em]import [size=1em]java.io.FileOutputStream;
[size=1em]import [size=1em]java.io.ObjectInputStream;
[size=1em]import [size=1em]java.io.ObjectOutputStream;
[size=1em]import [size=1em]java.io.Serializable;
[size=1em]import [size=1em]java.lang.reflect.Field;
[size=1em]import [size=1em]java.util.ArrayList;
[size=1em]import [size=1em]java.util.Arrays;
[size=1em]import [size=1em]java.util.Collections;
[size=1em]import [size=1em]java.util.Comparator;
[size=1em]import [size=1em]java.util.HashMap;
[size=1em]import [size=1em]java.util.HashSet;
[size=1em]import [size=1em]java.util.Iterator;
[size=1em]import [size=1em]java.util.List;
[size=1em]import [size=1em]java.util.Map;
[size=1em]import [size=1em]java.util.NavigableMap;
[size=1em]import [size=1em]java.util.Random;
[size=1em]import [size=1em]java.util.Scanner;
[size=1em]import [size=1em]java.util.TreeMap;
[size=1em]import [size=1em]java.util.TreeSet;
[size=1em]public [size=1em]class [size=1em]Test {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {

[size=1em]    [size=1em]}
[size=1em]}
[size=1em] [size=1em]enum [size=1em]Gender {
[size=1em]    [size=1em]MALE, FEMALE
[size=1em]}
[size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]String getName() {
[size=1em]        [size=1em]return [size=1em]name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setName(String name) {
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Integer getAge() {
[size=1em]        [size=1em]return [size=1em]age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setAge(Integer age) {
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Gender getGender() {
[size=1em]        [size=1em]return [size=1em]gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setGender(Gender gender) {
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]String toString() {
[size=1em]        [size=1em]return [size=1em]"[" [size=1em]+ name + [size=1em]", " [size=1em]+ age + [size=1em]", " [size=1em]+ gender + [size=1em]"]"[size=1em];
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);

[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]Person person = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]101[size=1em], Gender.MALE);
[size=1em]        [size=1em]oout.writeObject(person);
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject(); [size=1em]// 没有强制转换到Person类型
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]/*输出:
[size=1em]arg constructor
[size=1em][John, 101, MALE]

[size=1em]*/
[size=1em]</code>



此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
Serializable的作用
什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]private [size=1em]void [size=1em]writeObject0(Object obj, [size=1em]boolean [size=1em]unshared) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]String) {
[size=1em]        [size=1em]writeString((String) obj, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](cl.isArray()) {
[size=1em]        [size=1em]writeArray(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Enum) {
[size=1em]        [size=1em]writeEnum((Enum) obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Serializable) {
[size=1em]        [size=1em]writeOrdinaryObject(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]{
[size=1em]        [size=1em]if [size=1em](extendedDebugInfo) {
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName() + [size=1em]"\n"
[size=1em]                    [size=1em]+ debugInfoStack.toString());
[size=1em]        [size=1em]} [size=1em]else [size=1em]{
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName());
[size=1em]        [size=1em]}
[size=1em]    [size=1em]}

[size=1em]}</code>



从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
默认序列化机制
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。
默认的序列化机制,是对对象的所有成员变量(静态变量不会被序列化,因为它不属于某个对象,是所有对象共享的)进行序列化
使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
影响序列化
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
transient关键字
有些情况下,是不希望对象的所有成员变量都进行序列化,比如User中的passwd字段,这个是敏感数据,不希望它被序列化,那么就可以使用transient关键字。
transient,顾名思义,非持久化的。使用transient关键字修饰成员变量,能够使它在序列化的过程中被忽略。
此处将Person类中的age字段声明为transient,如下所示,
?
1

2

3

4

5

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]}</code>



再执行SimpleSerial应用程序,会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]null[size=1em], MALE]</code>



* writeObject()方法与readObject()方法*
对于上述已被声明为transitive的字段age,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];


[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}
[size=1em]}</code>



在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。
再次执行SimpleSerial应用程序,则又会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]</code>



必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。
通过这两个方法,可以对特殊要求的字段,增加额外的加密,解密的代码
Externalizable接口
以上例子都是基于实现Serializable接口来实现序列化的,Externalizable接口继承于Serializable,通过实现Externalizable接口也能实现序列化,不同的是,序列化操作的细节需要自己实现,而且,必须提供public的无参构造函数,否则会出现以下错误:

Java对象序列化使用基础和实例教程 b2b软件

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类修改成如下,
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {

[size=1em]    [size=1em]}

[size=1em]}</code>



此时再执行SimpleSerial程序之后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][[size=1em]null[size=1em], [size=1em]null[size=1em], [size=1em]null[size=1em]]</code>



从该结果,一方面可以看出Person对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了Person类的无参构造器。
另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述Person类作进一步的修改,使其能够对name与age字段进行序列化,但要忽略掉gender字段,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.writeObject(name);
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]name = (String) in.readObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]}</code>



执行SimpleSerial之后会有如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][John, [size=1em]31[size=1em], [size=1em]null[size=1em]]</code>



可知:采用这种方法实现序列化,transient是不起作用的,如果你不想序列化某个成员变量,只要在readExternal和writeExternal中不对该变量进行相应操作就可以了。
readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]}</code>



同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs cs"[size=1em]>[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);
[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]oout.writeObject(Person.getInstance()); [size=1em]// 保存单例对象
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject();
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);

[size=1em]        [size=1em]System.out.println(Person.getInstance() == newPerson); [size=1em]// 将获取的对象与Person类中的单例对象进行相等性比较
[size=1em]    [size=1em]}
[size=1em]}</code>



执行上述应用程序后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]false[size=1em]</code>



值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Object readResolve() [size=1em]throws [size=1em]ObjectStreamException {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]}</code>



再次执行本节的SimpleSerial应用后将有如下输出:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]true[size=1em]</code>



无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
总结实现Serializable与Externalizable接口都可以实现序列化 前者实现方式如果不想序列化某个成员变量,使用transient关键字修饰该成员变量即可;如果想在此基础上添加一些自定义操作,在该类中实现writeObject与readObject方法(注意是private方法),在这两个方法里就可以做一些自定义操作,如改变某个成员变量的值。 后者实现方式,需要自己实现序列的细节(writeExternal与readExternal方法),并且必须提供一个public的无参构造函数。这种方式为自定义序列化提供了更多的灵活性。高级认识序列化 ID 问题
情境:
两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。
问题:
C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
解决:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
清单 1. 相同功能代码不同序列化 ID 的类对比
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]> [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 1L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]     [size=1em]}
[size=1em] [size=1em]}

[size=1em] [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 2L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]</code>



如此问题中情景,客户端A将对象C序列化后,传给客户端B;客户端B对C进行反序列化时,B中必须有C对象对应的类,而且A、B客户端中对于对象C对应的类的序列化 ID 必须一致。
序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
序列化存储规则?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs avrasm"[size=1em]>ObjectOutputStream out = [size=1em]new [size=1em]ObjectOutputStream(
[size=1em]                    [size=1em]new [size=1em]FileOutputStream([size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]Test test = [size=1em]new [size=1em]Test();
[size=1em]    [size=1em]//试图将对象两次写入文件
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.flush();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.close();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());

[size=1em]    [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(
[size=1em]            [size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]//从文件依次读出两个文件
[size=1em]    [size=1em]Test t1 = (Test) oin.readObject();
[size=1em]    [size=1em]Test t2 = (Test) oin.readObject();
[size=1em]    [size=1em]oin.close();

[size=1em]    [size=1em]//判断两个引用是否指向同一个对象
[size=1em]    [size=1em]System.out.println(t1 == t2);
[size=1em]/*
[size=1em]31
[size=1em]36
[size=1em]true
[size=1em]*/

[size=1em]</code>



解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。


回复 支持 反对

使用道具 举报

575

主题

659

帖子

4288

积分

积分
4288
软件定制开发地板
 楼主| 发表于 2016-10-20 15:40:39 | 只看该作者
Java中的对象的内部状态只保存在内存中,其生命周期最长与JVM的生命周期一样,即JVM停止之后,所有对象都会被销毁。但有时候,Java对象是需要持久化的,因此Java提供了一种对象持久化方式——对象序列化机制(Object serialization),可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。
对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
注意:序列化的是对象(对象的状态,成员变量等),而不是类
简单示例?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]package [size=1em]java_interview;
[size=1em]import [size=1em]java.io.File;
[size=1em]import [size=1em]java.io.FileInputStream;
[size=1em]import [size=1em]java.io.FileOutputStream;
[size=1em]import [size=1em]java.io.ObjectInputStream;
[size=1em]import [size=1em]java.io.ObjectOutputStream;
[size=1em]import [size=1em]java.io.Serializable;
[size=1em]import [size=1em]java.lang.reflect.Field;
[size=1em]import [size=1em]java.util.ArrayList;
[size=1em]import [size=1em]java.util.Arrays;
[size=1em]import [size=1em]java.util.Collections;
[size=1em]import [size=1em]java.util.Comparator;
[size=1em]import [size=1em]java.util.HashMap;
[size=1em]import [size=1em]java.util.HashSet;
[size=1em]import [size=1em]java.util.Iterator;
[size=1em]import [size=1em]java.util.List;
[size=1em]import [size=1em]java.util.Map;
[size=1em]import [size=1em]java.util.NavigableMap;
[size=1em]import [size=1em]java.util.Random;
[size=1em]import [size=1em]java.util.Scanner;
[size=1em]import [size=1em]java.util.TreeMap;
[size=1em]import [size=1em]java.util.TreeSet;
[size=1em]public [size=1em]class [size=1em]Test {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {

[size=1em]    [size=1em]}
[size=1em]}
[size=1em] [size=1em]enum [size=1em]Gender {
[size=1em]    [size=1em]MALE, FEMALE
[size=1em]}
[size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]String getName() {
[size=1em]        [size=1em]return [size=1em]name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setName(String name) {
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Integer getAge() {
[size=1em]        [size=1em]return [size=1em]age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setAge(Integer age) {
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Gender getGender() {
[size=1em]        [size=1em]return [size=1em]gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]void [size=1em]setGender(Gender gender) {
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]String toString() {
[size=1em]        [size=1em]return [size=1em]"[" [size=1em]+ name + [size=1em]", " [size=1em]+ age + [size=1em]", " [size=1em]+ gender + [size=1em]"]"[size=1em];
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);

[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]Person person = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]101[size=1em], Gender.MALE);
[size=1em]        [size=1em]oout.writeObject(person);
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject(); [size=1em]// 没有强制转换到Person类型
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);
[size=1em]    [size=1em]}
[size=1em]}
[size=1em]/*输出:
[size=1em]arg constructor
[size=1em][John, 101, MALE]

[size=1em]*/
[size=1em]</code>



此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
Serializable的作用
什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]private [size=1em]void [size=1em]writeObject0(Object obj, [size=1em]boolean [size=1em]unshared) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]String) {
[size=1em]        [size=1em]writeString((String) obj, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](cl.isArray()) {
[size=1em]        [size=1em]writeArray(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Enum) {
[size=1em]        [size=1em]writeEnum((Enum) obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]if [size=1em](obj [size=1em]instanceof [size=1em]Serializable) {
[size=1em]        [size=1em]writeOrdinaryObject(obj, desc, unshared);
[size=1em]    [size=1em]} [size=1em]else [size=1em]{
[size=1em]        [size=1em]if [size=1em](extendedDebugInfo) {
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName() + [size=1em]"\n"
[size=1em]                    [size=1em]+ debugInfoStack.toString());
[size=1em]        [size=1em]} [size=1em]else [size=1em]{
[size=1em]            [size=1em]throw [size=1em]new [size=1em]NotSerializableException(cl.getName());
[size=1em]        [size=1em]}
[size=1em]    [size=1em]}

[size=1em]}</code>



从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
默认序列化机制
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。
默认的序列化机制,是对对象的所有成员变量(静态变量不会被序列化,因为它不属于某个对象,是所有对象共享的)进行序列化
使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
影响序列化
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
transient关键字
有些情况下,是不希望对象的所有成员变量都进行序列化,比如User中的passwd字段,这个是敏感数据,不希望它被序列化,那么就可以使用transient关键字。
transient,顾名思义,非持久化的。使用transient关键字修饰成员变量,能够使它在序列化的过程中被忽略。
此处将Person类中的age字段声明为transient,如下所示,
?
1

2

3

4

5

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]}</code>



再执行SimpleSerial应用程序,会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]null[size=1em], MALE]</code>



* writeObject()方法与readObject()方法*
对于上述已被声明为transitive的字段age,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];


[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}
[size=1em]}</code>



在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。
再次执行SimpleSerial应用程序,则又会有如下输出:
?
1

2

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]</code>



必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。
通过这两个方法,可以对特殊要求的字段,增加额外的加密,解密的代码
Externalizable接口
以上例子都是基于实现Serializable接口来实现序列化的,Externalizable接口继承于Serializable,通过实现Externalizable接口也能实现序列化,不同的是,序列化操作的细节需要自己实现,而且,必须提供public的无参构造函数,否则会出现以下错误:

Java对象序列化使用基础和实例教程 b2b软件

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口–Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类修改成如下,
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {

[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {

[size=1em]    [size=1em]}

[size=1em]}</code>



此时再执行SimpleSerial程序之后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][[size=1em]null[size=1em], [size=1em]null[size=1em], [size=1em]null[size=1em]]</code>



从该结果,一方面可以看出Person对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了Person类的无参构造器。
另外,若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述Person类作进一步的修改,使其能够对name与age字段进行序列化,但要忽略掉gender字段,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Externalizable {

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]transient [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]public [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]writeObject(ObjectOutputStream out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.defaultWriteObject();
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]void [size=1em]readObject(ObjectInputStream in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]in.defaultReadObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]writeExternal(ObjectOutput out) [size=1em]throws [size=1em]IOException {
[size=1em]        [size=1em]out.writeObject(name);
[size=1em]        [size=1em]out.writeInt(age);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]@Override
[size=1em]    [size=1em]public [size=1em]void [size=1em]readExternal(ObjectInput in) [size=1em]throws [size=1em]IOException, ClassNotFoundException {
[size=1em]        [size=1em]name = (String) in.readObject();
[size=1em]        [size=1em]age = in.readInt();
[size=1em]    [size=1em]}

[size=1em]}</code>



执行SimpleSerial之后会有如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em]none-arg constructor
[size=1em][John, [size=1em]31[size=1em], [size=1em]null[size=1em]]</code>



可知:采用这种方法实现序列化,transient是不起作用的,如果你不想序列化某个成员变量,只要在readExternal和writeExternal中不对该变量进行相应操作就可以了。
readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]}</code>



同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs cs"[size=1em]>[size=1em]public [size=1em]class [size=1em]SimpleSerial {

[size=1em]    [size=1em]public [size=1em]static [size=1em]void [size=1em]main(String[] args) [size=1em]throws [size=1em]Exception {
[size=1em]        [size=1em]File file = [size=1em]new [size=1em]File([size=1em]"person.out"[size=1em]);
[size=1em]        [size=1em]ObjectOutputStream oout = [size=1em]new [size=1em]ObjectOutputStream([size=1em]new [size=1em]FileOutputStream(file));
[size=1em]        [size=1em]oout.writeObject(Person.getInstance()); [size=1em]// 保存单例对象
[size=1em]        [size=1em]oout.close();

[size=1em]        [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(file));
[size=1em]        [size=1em]Object newPerson = oin.readObject();
[size=1em]        [size=1em]oin.close();
[size=1em]        [size=1em]System.out.println(newPerson);

[size=1em]        [size=1em]System.out.println(Person.getInstance() == newPerson); [size=1em]// 将获取的对象与Person类中的单例对象进行相等性比较
[size=1em]    [size=1em]}
[size=1em]}</code>



执行上述应用程序后会得到如下结果:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]false[size=1em]</code>



值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]>[size=1em]public [size=1em]class [size=1em]Person [size=1em]implements [size=1em]Serializable {

[size=1em]    [size=1em]private [size=1em]static [size=1em]class [size=1em]InstanceHolder {
[size=1em]        [size=1em]private [size=1em]static [size=1em]final [size=1em]Person instatnce = [size=1em]new [size=1em]Person([size=1em]"John"[size=1em], [size=1em]31[size=1em], Gender.MALE);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]public [size=1em]static [size=1em]Person getInstance() {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]String name = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Integer age = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Gender gender = [size=1em]null[size=1em];

[size=1em]    [size=1em]private [size=1em]Person() {
[size=1em]        [size=1em]System.out.println([size=1em]"none-arg constructor"[size=1em]);
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Person(String name, Integer age, Gender gender) {
[size=1em]        [size=1em]System.out.println([size=1em]"arg constructor"[size=1em]);
[size=1em]        [size=1em]this[size=1em].name = name;
[size=1em]        [size=1em]this[size=1em].age = age;
[size=1em]        [size=1em]this[size=1em].gender = gender;
[size=1em]    [size=1em]}

[size=1em]    [size=1em]private [size=1em]Object readResolve() [size=1em]throws [size=1em]ObjectStreamException {
[size=1em]        [size=1em]return [size=1em]InstanceHolder.instatnce;
[size=1em]    [size=1em]}

[size=1em]}</code>



再次执行本节的SimpleSerial应用后将有如下输出:
?
1

2

3

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs delphi"[size=1em]>arg constructor
[size=1em][John, [size=1em]31[size=1em], MALE]
[size=1em]true[size=1em]</code>



无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
总结实现Serializable与Externalizable接口都可以实现序列化 前者实现方式如果不想序列化某个成员变量,使用transient关键字修饰该成员变量即可;如果想在此基础上添加一些自定义操作,在该类中实现writeObject与readObject方法(注意是private方法),在这两个方法里就可以做一些自定义操作,如改变某个成员变量的值。 后者实现方式,需要自己实现序列的细节(writeExternal与readExternal方法),并且必须提供一个public的无参构造函数。这种方式为自定义序列化提供了更多的灵活性。高级认识序列化 ID 问题
情境:
两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。
问题:
C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
解决:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
清单 1. 相同功能代码不同序列化 ID 的类对比
?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs java"[size=1em]> [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 1L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]     [size=1em]}
[size=1em] [size=1em]}

[size=1em] [size=1em]package [size=1em]com.inout;

[size=1em] [size=1em]import [size=1em]java.io.Serializable;

[size=1em] [size=1em]public [size=1em]class [size=1em]A [size=1em]implements [size=1em]Serializable {

[size=1em]     [size=1em]private [size=1em]static [size=1em]final [size=1em]long [size=1em]serialVersionUID = 2L;

[size=1em]     [size=1em]private [size=1em]String name;

[size=1em]     [size=1em]public [size=1em]String getName()
[size=1em]     [size=1em]{
[size=1em]         [size=1em]return [size=1em]name;
[size=1em]     [size=1em]}

[size=1em]     [size=1em]public [size=1em]void [size=1em]setName(String name)
[size=1em]     [size=1em]{
[size=1em]         [size=1em]this[size=1em].name = name;
[size=1em]</code>



如此问题中情景,客户端A将对象C序列化后,传给客户端B;客户端B对C进行反序列化时,B中必须有C对象对应的类,而且A、B客户端中对于对象C对应的类的序列化 ID 必须一致。
序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。
序列化存储规则?
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

[size=1em]<code [size=1em]class[size=1em]=[size=1em]"hljs avrasm"[size=1em]>ObjectOutputStream out = [size=1em]new [size=1em]ObjectOutputStream(
[size=1em]                    [size=1em]new [size=1em]FileOutputStream([size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]Test test = [size=1em]new [size=1em]Test();
[size=1em]    [size=1em]//试图将对象两次写入文件
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.flush();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());
[size=1em]    [size=1em]out.writeObject(test);
[size=1em]    [size=1em]out.close();
[size=1em]    [size=1em]System.out.println([size=1em]new [size=1em]File([size=1em]"result.obj"[size=1em]).length());

[size=1em]    [size=1em]ObjectInputStream oin = [size=1em]new [size=1em]ObjectInputStream([size=1em]new [size=1em]FileInputStream(
[size=1em]            [size=1em]"result.obj"[size=1em]));
[size=1em]    [size=1em]//从文件依次读出两个文件
[size=1em]    [size=1em]Test t1 = (Test) oin.readObject();
[size=1em]    [size=1em]Test t2 = (Test) oin.readObject();
[size=1em]    [size=1em]oin.close();

[size=1em]    [size=1em]//判断两个引用是否指向同一个对象
[size=1em]    [size=1em]System.out.println(t1 == t2);
[size=1em]/*
[size=1em]31
[size=1em]36
[size=1em]true
[size=1em]*/

[size=1em]</code>



解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。


回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关导读
信息发布软件明珠网POST发帖工具下载
明珠网全自动发布帖子软件,这个网站做两个发帖过程,一个是 POST 的,一个是普通版本,现在 POST 版本是电脑差的标准配置,不过效率还是非常高的,要是自己能弄几个这样的网站,加上收录好的,是一笔不错的收录量,只可惜很多笨人根本看不透这些事,还拼了命的去找网站发,自己养几个网站效果是很多找网站的几十倍,不过很多人也不珍惜这样的机遇,因为网站前期都有很高的收录量保护期的,一旦不把握好这样的新手村效果,就变的无
信息发布软件天龙新闻网全自动发帖软件
天龙新闻网全自动发帖软件,这个是 B2B 网站,有的人找不到网站的自然能看上这类网站自动发帖的,因为都简单,导入标题内容,充好账号的积分,就可以全自自动的发帖,不用太多的干扰就行了,网站只有资讯和文章二个分类可以发,账号充的会员过期会发帖不了,虽然只能发两个分类,不过都已经把分类给做到脚本里的,他们对应分类会进去选择分类,或是上传图片了,输入标题内容都是通用的,添加产品这些网站不让发,打开也是没有用的
信息发布软件脉脉账号挂机软件下载
脉脉自动挂机注册账号软件下载,这个脚本是注册脉脉账号的,用了豪猪的接口打码,还有椰子接口打码,二个差不多,哪一个有账号就拿哪一个用,要注意的是猪这个接口账号一另一个网站不一样,他是加密后的账号密码,一般人记不住,只能用普通账号进去网站后,点 API 账号进行获取,得到手机号码后,才可以进行注册用,注册账号后,会采集账号的缓存,这样就可以后面发帖使用啥的,比较简单,也是相当麻烦的脚本,看上去是普通的与注
信息发布软件乐搜网全自动发帖工具
乐搜网全自动群发软件是一款非常好用的工具,它能够帮助用户轻松实现信息的群发和帖子的发布,提高工作效率和传播效果。用户对该软件的评价也非常高,认为它是一款值得信赖和使用的工具。 乐搜网全自动发帖工具下载: 乐搜网全自动群发软件作为一款功能强大且用户友好的工具,不仅极大地简化了信息传播的过程,还通过其多样化的功能满足了不同用户的特定需求。该软件支持多种类型的信息群发,包括但不限于文本、图片以
信息发布软件揭阳优聘自动群发工具下载
揭阳优聘自动群发工具下载,这个网上右上角有联系电话号码,你要发帖就只能找他免费给你发送,发帖有验证码,会员应该都不需要会员就可以发帖,比较简单,做起来步骤是比较多的,但效果还是很好的,采集地多发送,只能发到他这个网站所在地,固定的,虽然标记是地区的,但其实全世界也可以访问进去的,百度收录的也有 N 多,全部是有效信息,网站对这些发帖删的极少的存在,网站没有什么大的限制,只是发帖容易一些收录,他网站主
信息发布软件GPT4文章生成器软件
一直有人问我用文章怎么自动生成器操作,这个软件脚本就可以自动群发文章生成器作用,他是对接 GPT4.0 生成的,而且是最新的接口,他这个网站接口还是基本稳定的,不管怎么整,他都还在,也经过历史的检验了,这个脚本已经是去年做的,拿起来直接现在还可以用,说明还算稳定了,现在增加一个接口选择,他接口是一个字母代码的,也加上了清空会话,不然网站会发现在弄了太多会话,会有可能把你的号给封了,是一个变态的操作,如果一
信息发布软件816商务网全自动发帖软件
816商务网全自动发帖软件,这个网站也做了两个版本,一个是低配版,POST 提交,可以自动传图,自动切换标题和内容,导入账号就可以用,另一个是能看到界面的,也是导入多标题多内容组合发出去,一个是占用资源只有几 M,一个要渲染网页 CSS 和 JS 自然要用的资源多一些,现在也是很大的证明,如果网站没有对接百度蜘蛛池的,可能收录不如普通的版本,因为他不会执行百度的那几个 JS 文件,普通版当然就是模拟发帖的,自然会执行所
信息发布软件第一枪网站发布帖子B2B网站群发工具
第一枪网站发布帖子B2B网站群发工具,这个网站和普通的 B2B 网站没有什么大的区别,注册需要上传营业执照和手机短信验证码的,比较正规的那种,这个脚本可以发展会管理,可以发资讯管理,还可以布产品信息,软件登陆有一个拖动的验证码,会比较麻烦,所以这个软件是采用了缓存登陆的方式进行,发布帖子前一定要先采集一次缓存,这样就省去了每天跑去手工登陆账号的麻烦事。导入标题好内容,这样就可以省去一些麻烦事,软件能自动组
信息发布软件搜巴巴全自动群发软件
搜巴巴全自动群发软件,这个是同城分类信息网站,这类网站是 N 年没有更新的了,虽然漏洞比较多,但现在随着病毒终结,杀软也没有再去折腾这些事,现在毒与杀是存在这种微妙的关系之中,这个网站发的是商务同类信息,其实和 B2B 网站也没有多大的区别,网站给钱就可以发帖,自己可以充值套餐,反正这些网站是收录好,就发出去让别人充值一把作为建网站的收获期吧,这类网站有的收录好,有的是收录差的,不过新网站短期爆发收录好,
信息发布软件本地生活网全自动群发软件
本地生活网全自动群发软件,这个网站是本地生活的网站,他主要是发家电维修类的内容比较多一些,还有一些是商务服务类的信息,这类网站现在收录好的,也就那么几个,后来者往往很难居上了,他们都大多数是收费模式,给多少钱发多少帖一天。网站登陆是普通汉字验证码,发帖有分类,电话号码和发帖标题已经调用在标题上面了,发多少帖子这个按钮调用也是有用的,简介和地址分别有列表调用,你只需要导入进去就可以了,这个上传图片也
群发软件首码项目网发布宣传软件
首码项目网全自动发帖软件,这个网站是一种后台头条模式的,他有点像 ZBLOG 形式,很多人是没有啥动力放弃一些收录不好的网站,而总是舍又不舍得,放弃又还是在发帖,变成了一个一个背包,越积越多,效果越差,这里建议的是,三个月收录不好的网站就扔了,你可以很多模板,有时候你做对了很多事情,但因为百度蜘蛛就是和你斗气,看不上你的域名,看不上你的服务器,就会不收录,是完全不收录的那种状态。那就果断换域名,换服务器
群发软件天龙新闻网站B2B网站群发工具
天龙新闻网全自动群发工具,这个网站 POST 版本会比较容易提交,他有二个分类,一个是资讯管理,一个是文章管理,现在这些网站基本是发几百条后,有几条是收录好的,原因不言自明,发的多了自然就有可能被收录,没有什么大的技巧,对于百度来说,这类广告信息太多了,他肯定要随机选几个收录到自己数据库中的,几乎没有什么悬念的事,而对于发的人来说,只能发大点量,提高自己被成为天选之子的好处吧,如果选不中,那就继续发,或
群发软件一路供应网站发布宣传软件
一路供应网站全自动发帖软件,这个网站是发布供应信息软件来的,网站只有两个版块可以发,一个是文章,一个是资讯信息,其它发了也不收录,网站已经下架过去了。像这类网站信息,最好当然是用一些 GPT 写文章,生成一些原创文章收录会好一些的,可是很多人总是拿着老一套,内容就改前面一段就行,这样效果当然很差劲了,形成了一些恶性的循环环不收录的机制,除非网站有很大机率被收录,不然就很难有好运气砸中自己的,网站这里只
群发软件海威数控软件群发脚本下载地址
海威数控软件群发脚本下载地址,这个海威是一个 B2B 网站,他发帖的时候是比较慢的,所以软件准备了二个脚本,一个是 POST 提交,一个是普通网页版本,后台提交当然会快一些,而普通版收录当然好一些的,这个也是因为网站有这种梗导致需要准备两个版本,上传文件和其它网站不同,他这个记录的刻印号和别 B2B 网站不同,只是少了几个提交的字母,所以就需要改版,POST 提交是严谨的,而普通版本就没有太多讲究,啥都可以提交,就是
群发软件多宝网POST版本和普通版本一起群发软件
多宝网POST版本和普通版本一起群发软件,这个软件版本分了两个版本,一个版本是底层提交的,这样就不会占用电脑网络和电源 CPU 的资源,他用的量是极少的,对于差的电脑会有更兼容的运行效果,另一个是普通网页版本,这个版本对收录会有一定的优势,但会资源消耗的会比较多一些,同是一个网站整了两个版本也不过是客户要求的罢了,他有一个电脑比较差,又要挂上几十个脚本,那就只好用这种版本的提交方式了,这个版本上传图片这些
群发软件桂林生活网全自动论坛群发软件
桂林生活网全自动论坛群发软件,这个网站是发生活信息的,有点像是二手车房产之类的网站,不过他是论坛改版的,功能并不会有多少,他分类信息这些也有很多的改变,登陆是拖动验证码,发成功帖子后,就可以自动的提示审核的节奏效果,网站登陆验证码需要用录像的效果进行拖动,不然下次他有可能会拖动不成功,下拉选择这些电话号码这些可以自由更改成自己的,分类地区选择的是七星区,下拉这些是固定的,没有做自动变换, 些都没有
群发软件快手图文群发软件
快手图文群发软件,快手又有重大升级,不得不把这个脚本软件给改一下,这个是发图文的软件,用缓存登陆一下账号,姓名这里已经改版,采集缓存有所变化,所以重做了一个步骤,上传图文这里已经不可以出直接用访问网址了,只能打开网页,然后点击一下上传图片才可以传成功图片,其实上传图文这里,最大的不同就是图片上传几张,现在可以上传十多张网站会自动帮你转换成视频,然后软件就是帮你上传上去,标题也帮你输入进去,他这个加
群发软件免费蜘蛛池效果软件下载
免费蜘蛛池效果软件下载,这些蜘蛛池都是几亿收录效果的超级网站,他们也是得益于网站主是一个钱爸爸,因为他们的硬盘能装这么多的东西,也是超级无敌的冤大头罢了,随着发的人越来越多,他们发展效果自然也不在话下,能有一个超级大的留存力度,虽然看上去是没有什么人去,可是收录也会因为水涨船自然高的样子存在,网站有的有验证码,有的没有,这里收录十一个最好效果的网站放在那里,有的网站可能随后会标为不稳定,但也没有办
群发软件百家号个主页宣传软件
百家号个主页宣传软件,他这里主要是导入账号,然后修改个性签名,修改自己账号名称,生成一个主要进行操作的,这个一直都有人在弄这些数据的改变,不过他这个前详细说明,似乎要通过实名认证才可以填入,名称是随便一天可以改三次,这个操作虽然很费账号,但效果也是很显然的,毕竟是百度自己家的东西,只要你有蜘蛛池,丢进去,百分之五十的机率都会被收录,而且这种操作排名也是相当高的,只要不被提示,几乎很久都会存在于快照
群发软件咪咕创作者平台自动上传软件下载地址
咪咕创作者平台自动上传软件下载地址,这个网站是自动上传内容的网站,有点像是头条网站,有点像是发视频音乐的网站,他这个网站缓存会比较难弄,需要用 JS 的模式写入,不然就会无法写入缓存的,而且需要清空,不清空会直接给断开链接,不能直接写入缓存,上传好文件,就可以输入标题和描述内容了,看上去很容易,实也示做起来一点也不轻松,他妥妥的要点时间去写好动作才可以自动发帖的,分类这里会比较麻烦,需要用键盘按下键才
群发软件YX工具站全自动群发软件
YX工具站全自动群发软件,这个网站是一种导航网站,和一些装系统的工具盘类似的,网站用一种流量拉高自己网站排名的形式进行发展,先是打开首页,你不知道后台得和他买号会发给你,不过脚本软件都是通用的,打开登陆接口,登陆好账号,后面就可以访问发帖投稿的网址,打开输入标题和内容就可以发帖,其实是和先前的那几个什么首码网站是一样的,他网站也有做好这里的友情链接在里面进行扩展,网站开始收录好,后面就不知道了,毕竟
群发软件AR首码网全自动发帖软件
AR首码网全自动发帖软件,AR首码网全自动发帖软件是一款专为网络营销人员设计的强大工具,它不仅能够极大地提高工作效率,还能在一定程度上帮助企业或个人实现信息的快速传播。随着互联网技术的发展,特别是社交媒体平台的兴起,如何高效地管理和利用这些平台成为了众多企业和营销人士关注的重点之一。正是基于这样的背景,AR首码网全自动发帖软件应运而生,旨在通过自动化技术解决传统手动发布内容时所面临的时间成本高、效率低下
群发软件百度AI高光视频剪切群发工具
百度AI高光视频剪切自动发百家号网站软件,这个百度 AI 视频已经运作了一段时间了,虽然总是卡来卡去的,但起码人家还是能顶一点事的,不管怎么样,还是很多挂羊头卖狗肉的产品可以发布,比如这个你丢了下视频给他,他会自动把你视频段割出来,然后自动发到百家号,居然几乎不用审核就出来了,你以为他们用的是很好,其实他们是为了审核通过罢了,这样发出去省时省力,这个软件就起到这种作用,但他剪切的是很慢的,有的要三分钟之
群发软件优秀网全自动群发软件
优秀网全自动群发软件,这个网站是充值后台的发帖形式,如果不会找网站发帖,这类网站其实已经集中了 N 多个网站放在了后台供选择发帖了,人家也是真正钞票,把网站给做起来的,你也不要怪别人商人目的性强,毕竟都是一分钱一份货的买卖,他们收录好,你认真的发帖,也算是一件不可多得的好事放在那里了。你还有什么可以失落的事呢,网站分类这个点击全部放到了列表了,有不想发的分类可以自己去删掉就行,没有什么难度的,不过网
群发软件搜狐博客群发软件
搜狐博客群发软件,这些偶断丝链的博客也是极度让人怀念的,这不,搜狐博客又来了第二春,现在的收录确实是比以前几十年的收录更加好,他们全部都是一种怀旧的形式的活着,但也不是很乐观的样子,因为他们配置的服务器可能只有一台老掉牙的托管源,你也不用想着他能有多快提交,他们也总是会一卡一卡的样子呈现在那里,发帖如果出验证码就歇菜,不是软件不能识别,而是网站他转几下验证码都不知道在哪里,然后图片就碎在那里了,这
群发软件咔嚓娱乐网全自动发布帖子软件
咔嚓娱乐网全自动发布帖子软件,这个网站是一种后台登陆方式进行发帖的,他登陆有一个验证码,后台是头条形式,你充值给网站就可以发帖,注册进去后可以自主注册账号,但免费账号是发不了帖子的,这类网站可以说有几个之多,他们都有的是养网站的,把好的挑出来卖账号,把差的扔进沉默成本里了,这类网站发帖是比较简单的,他点击下拉这里,已经全部放到了变量增加区里进行更换了,这样就不会把分类全部雷同,全部可以自由更换的,
群发软件小红书视频批量上传软件下载地址
小红书自动发布视软件下载,这个网站图文发的人是比较多的,不过视频现在也可以,只是他网站现在在逐渐改版中,现在他网页进去后会有一层的透明的 DIV 层,你如果不点击一下,会以为软件失效了,这个网脚本是用缓存登陆好,然后进去后用座标点击一下视频二字,再去判断账号登陆成功了没有,有没有提示要绑定手机验证码,有没有登陆成功,三个判断,再上传视频,下拉标签这些功能,他网站这里需要用复制和粘帖功能,才可以实现内容
群发软件温江直聘网站全自动发帖软件
温江直聘网站全自动发帖软件,这个网站也是发普工类信息的网站,网站不用登陆账号和密码,也不用自己注册,只需要打开发帖页面,输入标题和内容就可以全自动的完成发帖,发帖的时候注意右上角采集的标记,他是帮助你采集到发帖链接的作用,右上角也有一个手机号码,要改成自己的哦,看很多傻家伙,一直放着默认的联系方式拼了老命的发帖,这不是有点笨吗,那个是默认的设置,需要导入自己的广告标题和广告内容,联系方式,进行发帖
群发软件拓英人才网全自动发布帖子软件
拓英人才网全自动发布帖子软件,这个网站是人才网来的,一般也叫普工发布网站,网站就一个输入密码就行,根本不用手机号和验证码,非常多人发,发的标记要填好,不然是采集不回来自己帖子的。这类网站权重其实很低的,但也架不住无限的刷帖子,然后把链接采集回来,提交给百度蜘蛛池那边的,只要发的链接够,收录其实很轻轻松松的,只是很多家伙太不懂得宣传的底层逻辑了,他们往往就几个内容,拼了老命的发,也不知道让人工智能多
群发软件单县招聘网全自动群发软件
单县招聘网全自动群发软件,这个网站就是发招聘信息的,最好的方式是他不用整手机号码了,现在的一大趋势,因为方便才是真材实料的利好,网站一个普通的验证码,就可以直接发帖,填上姓名,作为标题,简介作为内容就可以发布成功,发成功后,需要自己填一个标题的标记,比如标题或是联系方式,填上自己手机号码,这样就可以只采到自己的网址,不会采到别人的网址了,采好的链接可以放到蜘蛛池里自己作为收录点,疯狂的发送起来,先
群发软件天涯生活网全自动群发软件
天涯生活网全自动群发软件,这个是 B2B 网站来的,后台都是一样,但网站可能自己也不知道,他把分类这些项目给弄错了,导致很多都错位了,他这个版块发的是 25 后面的去了,网站只有两个分类收录比较好,一个是资讯管理,一个是文章管理,其它什么问答管理和生活管理基本没有收录,这个脚本在逻辑判断进入分类里进行了调整,因为不调的话他就会发不了,因为他这些分类的汉字给改了,所以和别的网站不,不改是铁定发不了帖子的,这
群发软件印象首码网全自动发帖软件
印象首码网全自动发帖软件,这个网站是一种后台付费发送模式,先前这类网站发啥都能秒收录,他是一种头条形式的网站,也因为收录好吧,所以很多人弄了这类网站批量生产内容,然后把收录好的网站给都给留下来了,这类网站后台还是比较强大的,充值后就可以自动开通 VIP 直接可以发,进入后台就是输入标题内容就可以发送成功,不过他每一个网站的分类会有所区别,发分类的时候,需要自己加一个点击一下才可以分类选择成功,网站这个
群发软件易次元工作室全自动创建工具
易次元工作室自动创建工具下载,这个网站是网易的动漫网站,不变的趋势吧,这么多人待在上面玩游戏,看动漫,估计一有点时间也会在里面浪费掉了,网站可以发帖,但发帖容易封号,我看他不如去创建一个工作项目比较靠谱,这里给标题给简介,而封号少还给单独的链接,给我们有宣传发帖的空间,项目创建一个就可以采集回来链接,还是很容易发成功的,没有验证码,缓存登陆网站的。 易次元工作室全自动创建工具下载地址:
群发软件精英论坛俱乐部论坛自动发布帖子软件
精英论坛俱乐部论坛发帖软件,这个网站注册账号后要 48 小时才可以发帖成功,是台湾那边的网站,网站论坛是很老的效果了,不过他里面有 N 多款游戏,还是一个很多人聚餐的地方,发帖没有验证码,注册账号有手机验证码和邮箱注册,注册成功的验证码,和登陆的验证码位数是不同的,这个要注意,网站发帖的时候比较简单,就输入标题和内容就可以成功完成,没有什么大的限制,但发广告还是会被抓到封号的,这个网站属于快餐文化,可不
群发软件优秀网全自动群发软件
优秀网全自动群发软件,这类网站有的收录极好,全部超出了你的想象力,不过这类网站收录信息都是收费的,他们用的是站群形式,总有那么几个网站表现的会极其优秀的呈现,网站登陆后台就可以发帖,前台是一种投稿形式主义,发帖只要输入标题和内容,还有一个分类也放在了我们软件列表上了,这样就可以随时更改,出问题也容易纠正,这类网站都是自研平台,BUG 肯定是有很多的,好在发帖验证码他没有上 BUG,不然就要额外的费用了,发
群发软件搜巴巴分类信息网全自动发帖软件
搜巴巴分类信息网全自动发帖软件,这个分类信息网站可以免费发帖,但是网站对免费账号是有限制的。付费账号发的帖子就比较多,发的多收费自然也会比较高的,首页发帖要先绑定好手机号码,他注册如果是邮箱,就要自己去绑定一下手机号码,如果不是就不用绑定,可以手机验证码直接注册账号,分类可以在第二个访问网址动作自行更改的,没有什么大的限制,就是他这个上传图片总是会卡在那里,没在特别需要可以不传,标题内容导入就可以
群发软件精英乒乓网自动论坛发帖软件
精英乒乓网全自动论坛发帖工具,这个是一个普通的论坛,看很多人都几乎把网址给堆满了天,收录还是蛮好的,只是他注册账号是容易的,但注册成功后要手机验证码绑定一下账号才可以用,不然是发不了帖子,登陆的时候会有一个动态的验证码,只能用截图然后识别发帖,发帖还有一个动态图片验证码,需要识别成功才可以发帖成功的,基本用火眼就可以发成功就是步骤会有点多而已做好已经是三十多步的格局在里面了。这个截图发验证码的,不
群发软件杜桥东部人才普工群发
杜桥东部人才普工群发,这个网站每发一条帖子需要短信验证码,软件已经对接好网页验证码的获取平台,自己打上平台账号就可以,现在他那个 API 接口账号和密码已经更改了,要注意自己获取到来,更改后全部会变,软件这里的账号密码不是登陆账号,是他平台的 API 特有的账号密码才可以用的。这个平台发帖的,现在没有其它验证码的,还是比较方便发帖的,短信验证码其实只能用一次,因为是共享的平台,你也不知道别人用它手机号码发了
群发软件天天洁日志自动发布软件
天天洁日志自动发布软件,这个网站是自动转发采集那个绿猫网日志的,以前有这个脚本,很多人让我找这个天天洁的发布日志网址在哪里,我看了一下我用以前天天猫的网站也是可以登陆的,发布日志后,他几个小时才能同步过去,怪不得绿猫网会有这么好的收录量,原因就是因为他还有很多未知的网站转发了他发的日志,以前就很多网站为了拉自己主网站,偷偷自己弄了一些小网站转发里面的日志,而以前蜘蛛又比较天真可爱,所以就可以拉高了
群发软件京东私信群发软件
京东私信群发软件,京东对账号管得还是很严格的,可是也是一个发私信的好地方,这个脚本是通过对店铺信息采集,然后自动跳到商家地址去自动采集发信息的,所有功能都分开了,采集是按关键字进行采集的,然后逐个的自动发信息,软件能自动的切换账号,自动跳到他网站又会重新开始发帖,网站开始是没有什么大的限制,但疲举报后会被禁言,有购买记录的号会比较耐用,原理自然就是有纠纷的更加耐用了,网站账号可以导入网页的账号缓存
群发软件小红书全自动图文软件新版推荐
小红书全自动图文软件,由于小红书有所改动,特别更新一下小红书软件,小红书这个输入内容现在只能用复制文本,再贴进去,不然他会说你没有输入内容的,网站没有什么大的限制,就是发非法的东东肯定封号的事,图文一般都是比较好的流量,视频这些流量会相对质量没有那么高,都是卖货的多吧,图文这个一般是先传图片几张后,他会自动合成一种视频格式的播放模式,再加上一些文字作为段落的,网站对于发帖还是比较友好的,可以做百度
群发软件搜购网全自动发帖软件
搜购网全自动发帖软件,这个网站是 B2B 网站来的,发的人也是很多,要注意一个问题是你登陆的时候千万不要输入错误的密码,不然多次后,发帖都会有验证码,这就特别的烧钱,要是遇到这个事情也不用太担心,你只需要换一个 IP 就可以避免验证码的出现,不然就得等 48 小时再消失这个验证码的,这个得注意操作哈,网站是要付费账号才让发帖,免费号基本不让发帖,不然也会安上验证码,打码的钱算起来也够买他网 VIP 了,网站后台会有
群发软件爱卡车评论软件
爱卡车评论软件,这个网站还是爱车一派的最爱网站,发头条是可以秒收录的,不过评论还是靠刷的多,当然正规的评论也是可以上去的,虽然网站说要审核,评论最大的问题就是看上去上去了,一会又被删除的可能,网站过滤一遍,判断 IP 判断内容,判断账号频繁度,就给你安排存还是减的命运,不过量多了,总是会有很多漏网之鱼,有的人偏爱瞎看的,一条好内容往往都会带有 N 多评论,这样有利于自己主帖的排名,曝光度的呈现,都是很有
群发软件中国混凝土网站注册账号个人主页宣传页面
中国混凝土网站注册账号个人主页宣传方式,这个网站是很麻烦的 B2B 网站注册网页,不过只要完善好主页所有信息后,就可以无碍的开启主页作为宣传点,企业名作为关键字,简介作为发帖内容,是一个很不错的宣传点,注册的时候只有一个普通的字母验证码,其它几乎没有什么难度,就是步骤有点多,全部要填上才可以的,网站现在开放状态,不用换 IP,一个就可以无限的注册成功,注册成功填完所有信息后,软件可以自动的保留链接到记事本
群发软件16DS网站自动群发软件
16DS网站自动群发软件,这个网站是一种化工网站来的,可以发布商品,也可以发布帖子,一般号只能发帖子,收录还是可以的,不过一个号只能发三帖子,你要大量发帖,需要在账号列表放多一些账号去,不然是发不了太多帖子的,不知道网站 VIP 服务是多少帖子了,估计也是便宜货,毕竟全部是自己源码开发的网站,还是有价格的,软件现在设置的是发三个帖子就自动换号,要是没有号就只能发三帖子,收录概率不会很高,也会有收录,只是效
群发软件化工综合服务商网站全自动发布帖子
化工综合服务商全自动发帖软件,这个网站秒收录是比较高的,这个网站注册账号有几十项要填写的,非常试,不过也没有办法,人家的规则就是这样子,不知道是不是付费网站来的,反正登陆账号也是可以发招聘信息的,可是我看他们秒发秒收录,也不知道怎么整出来的,普通账号是每发一帖就会提示审核中,每一个帖子都要审核,可明明别人秒发就可以不用什么审核,全部都多数是秒收录上去的,收录可谓不是说不好,而是太牛了,虽然是化工网
群发软件电子工程世界论坛自动发布帖子软件
电子工程世界论坛自动发布帖子软件,这个网站注册账号会比较麻烦一些,他是先关注网站的公众号,然后公众号自动发一条短信给你手机验证码,才可以注册成功账号,注册成功的账号还要填账号密码信息,才可以注册成功,成功后还要等上五分钟,新账号才可以发帖的,不过这个论坛收录是一般的好,几乎都是秒收录的盛况。导入标题关键字到标题列表,软件会按列表从上往下自动切换着发,内容也是一样,想通过组合就放多个内容列表中去,如
群发软件领动商务网发布宣传软件
领动商务网全自动 B2B 网站群发软件,这个网站是 B2B 网站,他后台也有很多网站可以发,自己看哪一个对自己关键字收录好的才去买号发,这个网站脚本只要导入标题和内容就可以全自动发帖,没有什么难度的,什么都可以发,不过搜狗收录比较多,其它收录很差劲的,图片需要导入到图片列表,关键字可以导入标题 1 也可以导入标题 2 二个列表进行组合,内容也是一样的,多导入不同的内容有助于收录,现在都人工智能时代了,不要总是弄几
群发软件天涯生活网全自动发帖软件
天涯生活网全自动发帖软件,这个网站后台有几百个网站供选择发帖的,有的收录好, 有的收录差,看菜可以下饭的网站,给钱买一个套餐发帖卡,就可以无限的发帖了,有的网站是扣点的,就是每发一个帖子,就可以发多少帖,有的是套餐的,什么样的账号一个月可以发多少个帖子的形式,这个网站后台的文字改了,弄的后面不能通用的发帖了,他把生活必需用的关键字调用在那里,看上去当然很麻烦了,不过只做好一次就可以自动发帖了,也没
群发软件78动漫网发布宣传软件下载
78 动漫网全自动发帖软件,这个网站注册的时候需要短信验证码,注册好后就可以用账号密码登陆网站,软件是自动登陆的,导入标题内容到软件里,就可以自动发帖,发帖步骤会有点烦,要选择话题,选择分类,还要选择版块,他发帖地址全部是统一的,只有版块和话题这些是动态的,选择哪一个就可以发到哪一个版块去,软件这里是集中发到他高达的这个版块里面去的,发哪都一样,收录是比较好的,大动漫现在还有很多人喜欢追,毕竟抓人眼
群发软件混凝土网站自动B2B网站注册软件
混凝土网站自动B2B网站注册软件,这个网站是 B2B 网站后台,看上去是大站,实际全部是站群类似的,网站就是注册一个账号,然后让你输入标题,再输入公司简介,作为宣传的关键字和联系方式作为基准点的,注册的时候有一个算术验证码,软件可以自动计算出来,还有一个字母验证码,自己用火眼就可以过完,挂在那里也可以每天注册几万个账号的,然后通过收录最大取胜,后台填写也没有验证码,比较省钱的那种,不限制 IP,注册长久存活
群发软件宁美母婴论坛全自动发帖软件
宁美母婴论坛全自动发帖软件,这个网站有的人打开会很慢,有时候会很快,全看自己运气了,有的人可以发日志的,可以跑去发日志,不过论坛收录会高一些的,一般都发论坛,这个网站是不能用什么常规思维去搞的,他网站看上去是注册账号的,但注册账号并不能发帖成功,发邮件这些也是收不到的,直接用 QQ 号登陆,点一下 QQ 号登陆,然后把缓存放到软件里才是最优的方法,也可以用微信搜码登陆,微信搜码登陆只能保留缓存一天,QQ 号
群发软件揭阳招聘网站自动发普工信息软件
揭阳招聘网站自动发普工信息软件,这个网站是免费发广告的网站,他都是需要短信验证码解码后才可以发送,这种网站不用注册账号,只需要付出几个短信验证码就可以完美的发帖成功,收录还很牛的样子哦,只是现在短信平台总是改来改去的,就有点麻烦,但做好一次就可以疯狂发送也是很值当的,网站是网页发帖的形式,自己导入好关键字作为标题,这个是输入姓名和工作名称里去的,内容就是直接输入简介里的,为了方便收录,这个每一个号
群发软件第一枪B2B网站自动发布宣传软件
第一枪B2B网站全自动发送帖子软件,这个是 B2B 网站后台,比较复杂,不过你要是用缓存写入他的登陆方式就简单太多了,这个是通用脚本,按客户的思路定制的,先是用缓存登陆网站,然后打开产品和资讯的发送网页,输入标题,清空内容,为什么他网站要有一部清空呢,原因是网站他自动会给你上一段内部的内容例子,你还必须去清空,不然他就会在你输入内容的后头里呆着,手工发的时候你感觉不到,因为一点,会自动消失,软件点不行,只
群发软件脉脉专栏网站自动群发工具下载地址
脉脉专栏自动发帖软件,脉脉这些大网站都基本有头条专栏的,而且都只能发正规的内容,他一般都需要审核的,账号也比较难搞到,发的人不多,可收录会比别的都好,他们都限制发多少,所以这个脚本都做了发帖切换账号的功能,发多少就切换一次账号,这样正规的内容一般都可以无限的循环发帖,加上现在智能人工写文章,其实收录会很吓人的,内容这里是图文并茂在一起的,图片上传了两张,是用 POST 上传的,看不到过程,调用是图片变量
群发软件小站教育全自动注册发提问软件
小站教育全自动注册发提问软件,这个网站一个手机号码一个验证码,只能注册一个号,成本比较大,但效果也是蛮好的,注册有一个字母验证码,一个短信验证码,软件全自动完成,注册的时候用户名不能超过 10 个字,软件用户名是调用标题 1 提问的时候是调用总标题总内容的,注册成功后,软件会自动去发一条提问,因为你到时收录的是个人主页,有一条提问,就有了联系方式,有了联系方式就在了关键字,有了关键字就有了内容,这些都是
群发软件弈城围棋论坛全自动发帖工具
弈城围棋论坛全自动发帖工具,这个网站是学棋的,现在素质教育成了很多人消费的场所,有人气的地方就有江湖,所以收录都不会差到哪去,他们往往就是百度蜘蛛看热闹的地方,哪怕是论坛有几个人对骂吵架,都成了它爱去的场所,带来了吃瓜群众的心情。这个论坛发帖没有验证码,注册一个简单的验证码,注册成功就可以发帖,没有啥限制,因为是论坛来的,所以发帖数量可能会被有限制,软件可以设置多少帖就换一个账号的。其它不用设置什
群发软件奇幻世界社区发帖软件
奇幻世界社区发帖软件,这个是繁体网站只是一个游戏论坛,估计是台湾省那边的主题网站,游戏不知道有没有人玩,看收录还是可以的,注册账号有一个验证码,看的不是很清楚,不过随便输入就可以注册成功,网站写的比较差劲,但还是可以有收录的,自创论坛就是这个模样的多,这些字体也比较难看得出来,好在猜也可以猜出点意思,发帖自然也能发出去的。这个软件写了登陆步骤,可以自己换号,发帖成功,也有采集成功网址到记事本里,自
群发软件OAWXW复印机维修论坛全自动发帖软件
OAWXW复印机维修论坛全自动发帖软件,这个网站是类坛来的,需要用微信关注他的公众号,才可以进行发帖,注册成功网站账号后,还要手机验证码一次,才可以发帖,这个脚本是用缓存 CK 进行登陆的,采集的时候只能用两个参数,不然网站采集回来的缓存是最后对不上发帖的那二个验证码的,一个是算术的,一个是汉字的验证码,这种汉字验证码只能用火眼去打码,不然失败率高,自动的识别率不高,当然现在人工智能还是可以的,就是还蛮贵
群发软件易次元网站自动刷帖子软件
易次元网站自动发布帖子软件,这个网站现在还是很多人跑去玩的,所以收录肯定都是秒收录的,像网易的网站,大多数都是各种各样的验证码,各种各样的邮箱验证的,看上去安全策略够高了,可在人工智能打击下,他的验证码都已成摆放了,这个网站发帖可以秒收录的,但新号可能要审核一次验证码,登陆是用缓存的,没有买到 CK 号就只能自己先把号全部登陆一次,软件可以点击采集缓存按钮,就可以全部缓存到碗里去的。发帖内容只能用键盘
群发软件汽车之家头条号自动发帖软件
汽车之家头条号自动发帖软件,这个网站登陆有验证码,所以脚本写成了用缓存登陆,这样切换账号,就可以方便一些,每天也不用自己手工登陆,蛮方便的操作了,网站标题需要模拟键盘输入的方法,而内容需要用粘帖的方法进行操作,不然标题会没有输入成功,内容也会没有格式,他的这个内容图片会比较特别,需要用他网站的一种 IMG 标签,不然你粘进去的图片也会只有源码,但是用他网站格式的图片,就会很顺利的发成功,封面图片也能自
群发软件vk网站自动创建社区自动发帖软件
vk网站自动创建社区自动发帖软件,以前 VK 网站一直不给登陆账号了,现在又有人弄了一种超级缓存账号来登陆创建社区,然后自动发帖,这个脚本也是这样,最后还可以采集出来帖子网址扔蜘蛛池,完美的操作,这种号发多了也没有验证码,不知道他们在哪弄的,不过只要有缓存就可以用这个脚本,自动的登陆自动的发帖,你用这个脚本只需要导入标题内容和账号就可以,注意,账号是缓存形式,你要是几个号的,没有缓存,就自己登陆一下,点
群发软件临时工网站自动发帖手机版软件
临时工全自动群发软件,这个网站是一种手机网页来的,倒计时会比较多,你不点击他一些比如姓名,工作地区的选择,不点它是不加载的,加载要一点点时间,对于这个脚本来说,很多动作都需要加点倒计时,不然运行太快会导致发不成功的,不是说没有选择这个,就是那个,他这个介绍,更是弹窗的方式才可以输入的,其它还好,不过网站要手机验证码的,软件全部流程做好了,只要自己弄好短信平台账号到软件里面就可以全自动的运行下去,网
群发软件学习使我快乐自动注册软件
学习使我快乐自动注册软件,这个网站听起来会不会比较鸡血,不过人家就好这口,你也无能为力,网站注册很简单,用软件自带的邮箱账号就可以无限的注册,收到激活码一填就完成任务了,网站可以标题填写成昵称,介绍可以做成内容,这些都可以无限的注册,也不封 IP,没有什么限制,有蜘蛛池的大家们,可以工作优化对待这个网站的,网站收录还可以的,个人中心页收录了也很多,有先见之明,全部都不错的存在了,注册成功保留了网址,
群发软件分析百科网站网自动注册个人主页群发软件
分析百科网站网站自动注册个人主页宣传方式,这个网站是注册账号的,本来先前还可以发日志的,网站数据库的姓名没有改,所以发不了帖子了,密码对应不上,存不了数据的 BUG ,所有内容都不可以输入,但是标题可以,所有标题不超过二十个字,都能让网站存档的,这个是网站的 BUG,没有办法解决,解决这个网站也不存在了吧,注册账号有字母验证码,要短信才可以注册成功,宣传主页,只能填个人中心的标题,带上联系方式和关键字,收

QQ|( 京ICP备09078825号 )

本网站信息发布软件,是可以发布论坛,发送信息到各大博客,各大b2b软件自动发布,好不夸张的说:只要手工能发在电脑打开IE能发的网站,用这个宣传软件就可以仿制动作,进行推送发到您想发送的B2B网站或是信息发布平台上,不管是后台,还是前台,都可以进行最方便的广告发布,这个广告发布软件,可以按月购买,还可以试用软件,对网站的验证码也可以完全自动对信息发布,让客户自动找上门,使企业轻松实现b2b发布,这个信息发布软件,均是本站原创正版开发,拥有正版的血统,想要新功能,欢迎提意见给我,一好的分类信息群发软件在手,舍我其谁。QQ896757558

GMT+8, 2024-11-23 22:28 , Processed in 0.235941 second(s), 50 queries .

宣传软件--信息发布软件--b2b软件广告发布软件

快速回复 返回顶部 返回列表