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

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

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

  [复制链接]

565

主题

649

帖子

4228

积分

积分
4228
跳转到指定楼层
宣传软件楼主
发表于 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系统和多实例分解教程
回复

使用道具 举报

565

主题

649

帖子

4228

积分

积分
4228
信息发布软件沙发
 楼主| 发表于 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
  这正是我们期望的结果。


回复 支持 反对

使用道具 举报

565

主题

649

帖子

4228

积分

积分
4228
推广工具板凳
 楼主| 发表于 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。该存储规则极大的节省了存储空间。


回复 支持 反对

使用道具 举报

565

主题

649

帖子

4228

积分

积分
4228
软件定制开发地板
 楼主| 发表于 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。该存储规则极大的节省了存储空间。


回复 支持 反对

使用道具 举报

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

本版积分规则

相关导读
群发软件53货源网全自动群发工具
53货源网全自动群发工具,会员登陆后就可以发帖,这个网站是发布货源的网站,相当于一些工厂货源发布的网站,厂家是比较多人来这里发的,网站也比较新鲜,不像是一些 B2B 网站照样直接就拿来用的网站,真的,有的网站本来是很有机会的,可是他们非不愿意自我放开,导致扣扣爽爽的,结果就让很多货源的人不【知】道去哪里发,换句话说,有的人有资源找不到发的地方,有的人有资源,却不【知】道哪里找到客户发,这就有点悲了,本来
群发软件华中贸易网全自动群发软件
华中贸易网全自动群发软件,这个网站是 B2B 网站,现在这类网站对于上传图片 POST 这块会有比较大的区别,有的是要自定义后缀,有的是要对应后缀,比如是 PNG 或是 jpg 的都可以,这个网站五大分类都可以发,像供应,资讯这些会收录比较好,软件对于这个 POST 会比较友好,但调试是很麻烦的,有时候多一个空格也不行的,网站对于数据流处理是很严格,或是说他们容易很低,不管收录有好有差的一边,这类网站有时候收录极好,有时候
群发软件168信息网自动发帖软件
168信息网自动发帖软件,这个网站是 B2B 网站,和以前做的都不同,它是一个新人开发的平台,收录比其它的都好,可见作者是不是打的批量版,而是打精品战略的人,有的人喜欢搞大军团作战,有的人喜欢搞尖刀式作战,各有各的优点,看自己能力圈在哪里就往哪里发展才是最优选择吧,如果有能力那肯定是尖刀式的省钱,但没有,那就只能做批量创造收录的概率呢,就像成功的事常做一样,只需要把量打上去,一样可以达到那种效果的,所有的
群发软件云推送网站网站自动发布帖子软件
云推送网站自动发帖软件,这种网站也是 B2B 改版过来的,不过他看上去会比较新鲜一些,毕境很多功能像是以前的那些搜了网的功能,这种平台发帖多,收录主要是靠提交给百度蜘蛛池的,如果没有自己的池子,是很难被百度这老人家强行收录的,网站比较简单,但可以刷新信息,和这个发资讯的脚本一起上传,听说刷新收录快,不【知】道是不是真的哈,软件自动登陆账号,自动发帖,导入标题内容,要和网站开 VIP 会员,不然是发不了帖子的
群发软件站站网全自动群发软件
站站网全自动群发软件,这个软件是 B2B 网站,现在这类网站发的人越来越多,反正有这样的市场,那些单个的网站反而没有什么好戏码,这类网站批量生成的,有好的会留下,收录不好的,自然就会被换一个域名,换一个网站空间重新去做,然后留下就会越来越好了,软件只要导入标题内容,导入账号就可以全自动发帖,如果用软件自动发帖,就可以省了很多手工操作,比如他能一年挂机不用操作心,只要自己准备好账号密码和标题内容就可以全
群发软件投聂网全自动B2B发布帖子工具
投聂网全自动B2B发布帖子工具,这个网站是 B2B 网站来的,自己可以自主充值,这些 B2B 网站可能没有秒收录,但月收录还是有的,他们打的都是一个批量生产,相当于边发边收录,还是边发,您说终竟的最终收录,他们是肯定没有咯,而这些理论上存在的例子也不是那么好的呈现在上面,这个网站也分为 PC 端一个,POST 后台版一个,都做了一个脚本吧,免得有的人喜欢爱好都不同,也能闭出一个不会的理由出来,有了这两个版本就不同,他们
信息发布软件旅法师营地创作平台自动发布帖子软件
旅法师营地创作平台自动发布帖子软件,这个网站发帖的步骤会很多,做完后要三十多步,他要选图片,又要上传封面图片,确定选择版块这些,整的头都痛的节奏,现在还有哪一个平台这么复杂的哈,网站是手机验证码登陆的,自己要注册一堆账号,他每一个号只能发一帖,这种这么偏避的平台,还是没有什么人抢着注册的,随便找一个 API 就可以注册一堆的号,网站游戏论坛,收录不会差到哪去的,就是步骤比较繁琐,只要做好一次,倒计时足
群发软件五洲商务网全自动发布帖子软件
五洲商务网全自动发布帖子软件,这个网站是 B2B 网站,继续弄两个版本吧,一个是 POST 的,一个是普通的版本,对于搜狗来说,这些网站效果还是很好的,很多人傻白甜分不出什么是什么吧,所以一直有人在用,不过这些现在都不重要了吧,百度自己也没有什么大的更新,感觉有点修修补补,几乎要放弃了,可是规则上加码,完全不管收录了,这个网站,只要有号就可以发,所有什么供应,采购,行情,这些信息都会自动切换着发,从头部切换
群发软件CSDN网站自动发注册账号软件
CSDN网站自动发注册账号软件,这个软件是利用短信验证码,大批量的注册 CSDN 网站的账号的,注册的时候发现居然没有验证码,以前这个网站是丢外链的圣地,现在倒是管的严格,收录也没有以前那么好了,发现他注册账号完全不用验证码了,这倒是一件值得看的好事,只要收一个短信码,就可以顺利注册成功,不过初始账号还是比较多的,需要输入新的用户名,点确定就可以注册成功,它网站注册成功会跳到主页去,然后软件判断主页代码,进
群发软件gitee网站注册账号软件
gitee网站注册账号软件,这个网站有一个拖动验证码,有一个字母验证码,以前是做过的,现在换一个短信平台,就可以直接注册成功,并且把缓存给采集到列表备用,这个网站收录是几乎无敌的状态,所以账号是肯定值钱的,您如果有什么外链,可以丢到这个网站进行跑蜘蛛,这个网站有点像以前的 CSDN 网站,CSDN 以前是超多漏洞的,不【知】道以前发过的人有没有记过,发外链几乎秒收录,可是他自己太作做了,非要改变,导致收录不同往日
群发软件秀购网全自动发布帖子软件
秀购网全自动发布帖子软件,这是一个 B2B 网站,只是做了一个 POST 脚本,一个普通网页的脚本,他们的限制是比较少的,网站个人中心也可以联系 QQ 充值,这些可不是我们软件的平台,他们找的网站都有这些,软件中您想节省电脑资源的,可以用 POST 版本,想看过程的就用 PC 普通的版本,两个有好也有差的一方面,但收录都是一样的,图片放到软件的列表中去,他能自动的传上网站去的,但图片后缀只能用 JPG,其它格式不能上传上去,
群发软件武法出租车论坛全自动发布帖子软件
武法出租车论坛全自动发布帖子软件,这个论坛版块是比较多的,而且是一种动态验证码,它是一种典型的养网站例子了,不过您要是没有买到内部账号,发帖是比较费劲的,因为一个号只能发两个帖子,软件先是自动的写入网页缓存账号,写入缓存是只能有两个论坛发帖参数的,如果不然,您一会发帖的验证码,他永远提示您是错误的,针对这些有登陆验和发帖验证码的,都必须按这个脚本这样写入缓存才可以。这个可以作为动态验证码论坛例子,
群发软件导列网全自动群发软件
导列网全自动群发软件,这是一个 B2B 网站,做一个普通网页版的群发软件,做一个后台 POST 提交的软件,软件没有太多折腾的地方,全部都是自动操作的,导入标题内容就可以全自动发帖子,图片一定要有 JPG 后缀的,不然会在 POST 脚本上传不了图片,内容的图片可以加在内容 2 或是内容 3 里,这样发出去带 IMG 的标签,就会显示到内容图片里,您发帖的时候,在内容框里上传一次,内容会自动的带在图片里面穿插进去,弄二个脚本,一
群发软件快手图文自动发布软件
快手图文自动发布软件,图文发布实际就是传一张图片,然后加上标题和内容,和上传视频一样,他都是一样的操作,只是内容不能超过一千字,不然会发不出去的,手机 APP 上也会有这样的限制,视频对于这个网站来说发的比较多,图文虽然流量很大,但却是没有多少人有耐心看完所有的文章的,都几乎往视频流里里折腾,图文这个可不是靠什么百度收录的,他们就像 BIBI 网站这些一样,全靠自身的超大 IP 流量进行粘帖用户,相当于一个大的
群发软件百家号头条全自动群发软件
百家号头条发送软件,这个头条是自备文章导入发送的,之所以新瓶装旧酒是因为这个是有技巧的发送,他是利用一个 HTML 编辑平台的转换代码,让自己可以在百家号发送头条号不被删除除,文章估计都是用通过小炎号类似的人工智能创作平台进行攒写的,所以都比较容易不被删除的,而且很容易被百度收录,现在几乎没有人会通过自己手打出来,软件先是 HTML 平台把代码给转换一下,再用软件的复制全赞功能,把内容给写进了粘帖板,写入缓存
群发软件知乎个人主页自动设置软件
知乎个人主页自动设置软件,用缓存去写入【知】乎个人主页名称,他是 180 天才能改一次的名称,还有他里面的个性签名,他的职业介绍,都成了百度蜘蛛超级爱好的地方,只是注册一个号只能改一次,这着实有点浪费 RMB 了,不过效果是好的很的,第一来,也极少会被封号,就算号被封了,他的个人主页还在那,名称也不会变,致少还一直在那,二来还是要找到手机好的平台,才可以这样玩,价格不贵,脚本这个是廷续昨天的那个,这个是设置
群发软件知乎自动注册软件脚本下载地址
知乎自动注册软件,这个网站管的是比较严格的,而且验证码也是比较恶心的那种,但对接飞码这个平台还是可以注册不少的账号,如果这个平台没有号码了,也可以更换到其它平台的 API 里,软件并不限制您对接哪一个 API 接口的,只要有随便可以改的,像什么俄罗斯或是其它国家的号码都是可以收到的,只要短信一接,就可以登陆,有的平台限制多,有的平台限制极少的,这个脚本主要是提供注册,〖逻辑〗判断是比较多的,各种判断有没有登
群发软件搜客商务网全自动发帖软件
搜客商务网全自动发布帖子软件,这个网站也是 B2B 网站,做两个版本供后续使用,网站传图片一定要注意,这个脚本默认是 JPG 的,如果需要改成其它格式,他网站是不能自动识别的,需要改脚本上传动作,只要把上传动作改成对应的后缀格式就可以,但没有经验的最好不要去动吧,JPG 图片用的比较多,就用这个就可以了,也没有什么很卷的操作吧,各种各样的格式,如果不是为了美化什么的,没有多大的必要,网站每发一帖就要付一角钱的节
群发软件商务导航网全自动发布软件
商务导航网全自动发布软件,这个网站也是 B2B 网站,只做分类信息发布,现在的目标是把所有这些 B2B 网站进行大切换,做一个 POST 版本,一个 PC 普通的版本,像昨天的那个网站,居然玩的是手机收录的接口,而这个倒是中规中矩的网站,只要有 VIP 都发帖就可以了,没有太多的限制,不是 VIP 也不给发帖,全部是走会员路线的,网站只要有点收录,就有一大群人往里面堆量的,现在 B2B 网站都成了这个常态,然后收录又会慢慢下滑,然
群发软件发财草网站自动发帖软件
发财草网站全自动发布软件,这个网站倒是蛮新的,他有点像分类 58 同城网的模块,现在倒是落了一个清静的网站,专攻什么手机 APP 里去了,越来越麻烦的地方,这个网站还是B2B后台类似的,能不能收录,完全看发的数量,发的多了,收录自然就好了,如果发的少就很难说了,免费发的时候还会有一个验证码比较麻烦的,网站只能发一个地方,就是资讯的版块,也不用做太多〖逻辑〗判断了,软件还是做了两个脚本,一个是 POST,一个是普通
群发软件爱搜群网站自动发布软件
爱搜群网站自动发布软件,这个也是 B2B 网站,网站现在免费发帖都有验证码,每天有十帖的免费,都是集人气的一种措施,不过免费的发的这么少自然效果不会好到哪去,这个要了解哈,毕竟水桶的容易是按他的短板计算的,可不是什么随随便便可以改变的事,这个脚本分二个脚本,一个是后台运行,但您必须开好会员,不然肯定发不成功的,这可不是 POST 就可以自动的跨越他的限制,就直接可以发了,这是不可能的,除非人家允许这样免费哈
群发软件银盾皇全自动发帖软件
银盾皇全自动发帖软件,这里两个版本,一个是 POST 版本,一个是普通版本的,很多人非要弄两个版本去发一个网站,有时候需要快,有时候需要看到过程,所以很多 B2B 网站改成了两个版本,网站发帖是比较简单的,有账号就可以发,他们 B2B 网站本来就是用来发广告的,他们充好会员就可以一直发帖,网站是一种计件方式的,每发一帖就扣多少钱,网站越好,收费就越贵一些,便宜的网站也有人发,那些家伙也有的是图心里安慰的,也不【知
群发软件跑列网全自动发布软件
跑列网全自动发布软件,这个是 B2B 网站来的,只要导入好标题和内容,放好会员号就可以全自动发帖,这个是后台发送的版本,和普通的版本,只要有号就可以全自动发,两个版本放在上面了,看电脑吃饭,第一个就用的是普通版本,他能看到过程,一个是 POST 过程,他全程后台运行,可以多线程,发的会比较快,几乎不占电脑资源,后台静默运行,针对差劲的电脑设计的,不过您看不到过程有时候会心慌慌,人就是对没有掌控感的东西会产生
群发软件小木虫论坛自动软件
小木虫论坛自动发布帖子软件,这个脚本是手机版的,所以还是有必要发布一下的,网站每年这几个月,会超多人用,所以到处找这个软件的人也比较多,而 PC 不管是用的 POST 还是啥,都容易被封号,但是这个 WAP 端应该是老掉牙的地方,所以防火墙会有一个漏洞的,这个网站主要是内部号发的比较多,也就是他们自己的号,小号发还是有机率会被封的,最好有能力的还是拿到内部号,这个端的会发的比较快,可以自己发一些版块,导入比较多
群发软件鲁德软件平台提问软件
鲁德软件平台提问软件,这个网站是一种软件平台来的,软件在这个网站的提问版块进行提问,因为是提问的,一般一个账号就可以发很多,不过他是有一个极验证码的,软件这个脚本用的是图鉴平台进行打码,识别还是很便宜的,直接可以拖动对,没有用 POST 的那个极验证码平台,感觉他们现在已经差不多了,时代在进步,人工智能找这些图片座标还是像爸爸打三岁小孩似的,随便下手。不过这个网站和的是缓存登陆哦,第一次发,您可要自己登
群发软件爱搜网全自动群发工具
爱搜网全自动群发软件 POST 版,这个网站只有两个分类,一个是产品供应分类,一个是资讯分类,产品供应分类的系数是 25 这个可能网站自己也没有关注到问题所在,一般是 21 系数收录才会好,好在资讯这个默认了,对于收录,这些网站都已经被百度蜘蛛给训化了,人家都会按惯性的方式去爬行,如果您想另起一门旗帷,那就只能说抱歉了,您还没有那个实力与之抗争,还不要说,人家现在技术性的业务是比较少的,您想让他改变那种爬行方式
信息发布软件POST 中巴网全自动群发软件
POST 中巴网全自动群发软件,这个是 B2B 网站,不过以前都是做的普通版本,这个是 POST 版本,可以节省电脑很多资源,可以不占用后台,可以后台静默运行,可以飞快的跳过一些错误,可以秒完成发帖过程,自动后台传图片,然后采集图片地址,进行采集保留,再判断里面有哪一些分类,再把分类随机切换,这个标题可以加长的模式,内容也是不被限制的发帖模式,因为是后台运行,他这些限制的都是页面 JS 限制的,不过您要不是网站会员,
群发软件乐扫网 B2B 网站全自动发送软件
乐扫网 B2B 网站全自动发送软件,这个网站也是 B2B 网站来的,有的人总是在迷茫中生活着,总是说找不到网站发,那您就可以弄几个这样的 B2B 网站先把池水动起来呀,为什么一直在纠结那些秒收不秒收的事呢,很多网站前期是沉默的,某天东风将至,收录直接就飞速上升了,这些网站现在都有这种特点,不过他们也不【知】道,现在有很多论坛更是牛,收录比这些快的多,可不是什么 DZ 论坛,而是一些主题论坛,也是这样,某几天收录超级
群发软件记得网 B2B 网站全自动群发软件
记得网 B2B 网站全自动群发软件,这个网站也是 B2B 网站分类来的,他对搜狗收录会比较好,不过谷歌居然也有很高的收录,不【知】道他是走的啥运气了,百度收录差一些了,现在几乎很少人玩这些,不过有收录就有流量肯定的,国内国外都有,网站现在已经做了这种 B2B 网站模板了,全部过程只需要改一个地方,那就是网址列表的网址,您换了网站,只要把域名放进去,全部就会自动的,不管您想发供应,还是产品,还是行情这些分类,软件
群发软件尚格论坛自动发布帖子软件
尚格论坛自动发帖软件,这个只是普通的论坛,但因为先前是地方论坛来的,人气还可以的,收录也不会差到哪去,论坛是用户名登陆的,他一个页面有手机登陆,这个可不是通用的登陆接口,普通登陆是登陆不上的,只能用户名登陆的接口才可以,注册账号的时候要注意,不是手机号码登陆的,注册是手机号码,但是用户名才有效果的,发帖的时候没有验证码,一个账号可以无限的发送。只是发帖时间要十秒以上,不然他可是会出验证码给您按排上
群发软件驴在陌自动发提问文章软件
驴在陌自动发提问软件,这个网站是发旅行日记和路线的,不过日记这里肯定收录的比较好一些的。只是他登陆的地方只能用微信搜码登陆,短信验证码登陆,收录后还是要跑百度蜘蛛池的,不然基本不会有收录量的,发帖还有一个字母的验证码,您还必须写上,不然是不给发帖的,网站间隔时间是三十秒一帖,如果要快就只能加多几个号同时开脚本去发的。内容这里需要用座标的输入方法去整,不然是没有用的,因为他不让源码输入,也不用逐字输
群发软件Apipost网站自动发博客软件
Apipost 网站自动博客发帖软件,这个网站登陆是有一个点触验证码的,不过对软件来说这种验证码已经是最简章的存在了,所以加一个座标点击,就完事了,网站也没有太高的要求,不过发那个博客是五分钟只能发一帖,不然会一直显示发帖太过于频繁,会发不出去的,五分钟一帖,是很折磨的,但软件不会嫌烦,他能自动的切换账号发,可以自动用的缓存登陆发,可以自动动的等分钟发的,完全看您想要方还是圆的,一切都可以调试,没有什么限
群发软件灭提问网自动发提问软件
灭提问网自动发提问软件,这个网站后台会不会有眯怪,不管这么多了,软件可是用来发广告用的,这个网站登录有验证码,发帖也有一个字母的验证码在上面的,所以是要耗钱的,不过这些网站这两年如果找到好的,都是秒收录的存在,比百度自己的产品收录都要好,这是非常神奇的地方,特别是这些提问类的网站,更是牛的不行,边发边秒收,只是每天要发,有一天不发,那收录就全部回到了解放前了,原因可没有人【知】道的事,规律就是这样
群发软件齐鲁信息网站自动发布工具
齐鲁信息网站自动发布工具,这个网站只是一个站群论坛来的,他网站会自动大量地用软件挂 N 多信息,等收录好了就卖账号给别人发布帖子的方式,论坛是比较难做收录的,但一旦能做起来,收录会更好一些,排名也会高,论坛本来就是冲着交流的属性去的,所以百度蛛蜘收录的方式排名也是不一样的,虽然经过了这么多久,但基础框架摆在那里是不可能太多的转变,网站就一个简单的问题验证码,回答对了就可以登陆和发帖,他网站也会有大量
群发软件微软Sway自动生成软件
微软Sway自动生成软件下载, 对于国内来说可能不是很神奇,但在谷歌里可能早已经是大佬级别的存在吧,外国人的东西有很多是很新鲜的玩意儿,但用的人却像是比较高级的,而国内的东西,有一点新鲜的东西就会扎堆在那里玩弄,也不【知】道是价值观的问题,还是因为人的大脑都喜欢没有见过的新鲜玩意儿,这个东东可以生成很多漂亮的新闻稿,像 PPT 类似的。不过外国人这些看上去很多都已经被人工智能给完美替代的主了吧。软件这里是写
群发软件百度文档自动上传 WORD 文件软件
百度文档自动上传 WORD 文件软件,这个网站应该是比较多人弄的,不过弄的都是正规的方式,以前可以弄成审核通过了,放一些外链网址去,可是现在审核通过修改,又要审核了,基本让他堵住了漏洞,不过还是有一些人在上传 WORD 用的,毕竟正规工作的人太多太多需求了,这些文档收录好,还可以赚钱,很多人会贪那点方便下载别人集合好的文档是行调用,特别是一些总结,一些目录,都是别人幸苦劳作的结果,甚至有的是作为小说的宣传地把
群发软件企业录网站分类信息软件
企业录网站分类信息发布软件,这类网站有十年多的历史了吧,还是老网站好,随便发一发就收录,问题是养起这些大胖小子也是不容易的,先前的坚持与无奈,以前可能会觉得是一只吞金兽,现在可是一棵摇钱树了吧,还理啥财,种一棵树最好的时机是十年前,其次是现在,我们努力的规划好了吗,现在人工智能发帖也是这样,找一个 AI 的形象,把自己训练出来,也许可以永生哦,有很多人都是这样想的,把自己所有的思想存到 AI 里,算出来一
群发软件师徒网自动发布提问软件
师徒网自动发布提问软件,这个网站有点像博客,不过他又分出了一个提问版区,所以这里收录会比较好一些,他这个提问区其实是 WP 博客系统弄的,也不【知】道为什么整出这么一个版块出来,他登陆方式,全部都已经美化了,但 WP 博客系统那种选择分类的模型至今没有什么变化,还是老三样,这里发布帖子的时候,他分类是用了固定的方式的,登陆有一个点触验证码,估计是比较简单的鼠标检验,用座标功能点击一下就可以实现自动登陆了。
群发软件大河网B2B软件
大河网全自动群发软件,这个网站是 B2B 网站来的,本来就给您发广告用的,不过他只有发资讯类的,要充多少钱他是可以自动开通的,也算是很方便的一个自助网站了,这类权重比较小的网站,网站主人也是很聪明的,他们会把所有信息发到一个分类,这样可以提高一个分类目标的权重,收录自然会比散弹枪好很多的,只是网站也没有啥快的,很卡屏的感觉吧。不过他这个鬼下拉分类倒是很奇葩,天天会更换,不【知】道要整点啥,所以我这里是
群发软件天天基金头条发布软件
天天基金头条发布软件,天天基金这个可是史上最多有钱人的集散地,网站以前其实是他博客的一个升级版,不过现在头条看上去大气上档次一些吧,也没有太多的限制,只是一个账号一天只能发五帖,首次登陆会有一个点触验证码,不过第二天就会没有,网站是可以缓存登陆的,要是封号了就很完蛋,内容要关于财富之类的内容,不然是会被秒删除的,软件现在是用账号密码登陆,第一次登陆您得手工去输入验证码才让登陆成功,账号密码可以自动
群发软件建设工程网软件群发
建设工程网软件群发,这个网站看上去也是同一批人产品,这类 B2B 网站,如果能收录的,都会出大牛股,如果不能的,那就只能做一个低调的的老百姓了,百度蜘蛛也是一样的得性,看到这类网站开始的时候疯狂追逐,等发现网站内容太过于相似的时候,他又心生讨厌了,只好时不时的远离了,某一天还来给您玩一个空然消失的游戏,让您猜个饱,您是这里生病了,还是那里得病了呢,不过找原因都是徒劳,所有问题都其实出在一个新玩意儿的身
群发软件中国结艺网自动 B2B 网站群发软件
中国结艺网自动 B2B 网站群发软件,这个网站是发布广告的网站,只要有号就可以发,没有验证码,没有审核,有内容和标题都可以导入进去发,其实现在 GPT 这类生成文章,发起来效果会好一些,自己的内容,再加上生成的内容进行发上去,会比插入新闻和小说内容会好一些,只是很多后【知】后觉的人不【知】道怎么生成去用罢了。 结艺网自动 B2B 网站软件下载地址: 随着互联网的普及和电子商务的兴起,B2B(Busin
群发软件当当AdSmart 自动上架货物软件
当当AdSmart 自动上架货物软件, AdSmart依托全球最大中文书店&网上商城当当网,帮助客户进行最有效的市场推广。面向白领,公务员及您可自由选择广告发布类型和目标页面,并可随时修改广告内容和预算。这个软件可以自动批量的上架一些自己的物货,自己只需要输入书本的系列号到软件中去,然后软件会自动按书名,关键字排序,把货物自动把图片,和系列号自动上架上去的一种程序化操作,账号这个分类也是需要自己添加一下的,就是想
群发软件36商务网发布帖子软件
36商务网B2B网站软件,这个网站自然也是 B2B 分类商务网来的,网站是做搜狗搜索的,所以对于这个比较友好一些,只是他们都是 PC 端的,虽然搜狗能秒收,可百度收录也没有多好的效果的,这些网站现在主要是打一个堆量的效果,发的多,收录自然也会上去,发的少的话,自然没有什么好的收录量,不管您发的这种类型网站是大网站或是小网站,没有量一定就是扯皮,没有效果的。只要把量堆下去了,百度蜘蛛多么正人君子,最好也会看在多的
群发软件哔哩哔哩网站专栏群发软件
哔哩哔哩网站自动上传内容图片发布专栏软件,这个网站就是账号比较难求得,一直以来他网站都这种开放模式,收录是现在全网巨好的一份子,现在几乎没有这么开放的网站,有这么高收录量的老大哥,这个脚本可以上传四张图片,其实就是重复的把那几步上传步骤修改一下就可以的,然后分别采集存放到了软件的四个列表,方便插入到内容里,他调用图片必须有一个代码,不然是无法显示图片的,这层里是最麻烦的地方吧,不过专栏不用上传视频
群发软件电梯阁论坛群发工具帖子软件
电梯阁网站全自动发帖子软件下载,电梯阁论坛全自动发帖软件,这个网站看上去会很普通,不过只是一个论坛,但是他是一种技术性论坛,所以秒收录是极高的,特别是他放广告的版块,收录还是比较喜人的,都很多秒收录,看来很多人还是很在乎这些技术性的分享的,虽然现在不正常的人还玩着电脑,正常的人可能早就飞上了哪一个高枝了吧,网站还是这么奇葩的样子,版块多,广告多,收录也居然还被百度蜘蛛给爱上了哈。 电梯阁论坛群发工
群发软件搜狸网站自动发帖软件
搜狸网站自动发帖软件,这个网站是一种注册账号模式发帖的,他和B2B网站类似,但网站没有这么容易即时放开审核。显示中的都是自动审核过的,网站收录的也是搜狗的作用,不过网站是付费形式的,他有会员后台,登陆也会有一个验证码,发帖就一张图片,一个分类,一个标题内容就可以自动发帖成功,没有什么难度的,不过内容这个输入框是嵌套的方式,也就是只能针对他一个网站会有作用,换一个网站这步就得重新做了,好在网站发帖也没
群发软件微页B2B网站发帖软件
微页B2B网站发帖软件,软件市场营销和信息发布时,微页B2B网站发帖软件无疑是一款备受推崇的辅助工具。该软件专为在微页B2B平台进行信息发布与推广而设计,其独特之处在于发帖权限主要面向付费用户开放。这一特性使得微页B2B网站得以有效筛选并确保平台上企业的资质及信息的真实性,从而维护了一个相对纯净且高效率的商业信息发布环境。 微页B2B网站发帖软件下载地址: 发帖过程录像: 微页B2B网站发帖软件作为一项
群发软件图图网文章自动投稿软件
图图网文章自动投稿软件,这个看上去像是一个相册网站,但他其实是一种投稿的网站来的,账号也是付费才会通过审核,并不是免费让您我限发的,网站肯定有点水份才会让您付费,一般这类付费发帖的网站,都可以实现免自己提交蜘蛛池的效果,至于收录结果,完全是看您自己的标题内容的质量说话的,再加上网站的权重高,那收录可能超好,要是没有达到蜘蛛池的要求,那收录就像打水漂似的差劲的,发这类网站,最好的方式是自己复制当天秒
群发软件喜马拉雅自动评论软件
喜马拉雅自动评论软件,这个网站可以发听歌单的,不【知】道为什么大家都不动合,不过今天刚好有人要弄一个评论,他说收录更秒吓人,他是先采集最新的听单,然后采集多少页,再进行听单逐个评论,由于是最新的听单,所以评论都几乎在第一页呈现,如果提交给百度蜘蛛池,很快就秒收录了,这些网站PV非常高,几乎是人气最大的地方,他们拥有的宝藏是值得开发的,如果你的号是VIP号,评论几乎是秒出来,他是利用听单的评论收录,所以
群发软件亿图脑图账号注册个人主页中心页宣传方案
亿图脑图账号注册个人主页中心页宣传方案,这个网站是注册账号进行主页宣传的,这个网站是AI脑图生成的,非常聚人气,所以收录一直很好,他个性签名可以输入标题和内容,内容是二百个字,超过了就输入不了,提交不成功,最好是自己算好字数导入,特别是有一些特殊符号的,这种网站是要跑蜘蛛池的,不跑基本很难收录,不过一旦收录,会一直存在上面,很划算的,排名也很高,不过这个网站也有一个毛病,他要在个人主页里点注册,如果
群发软件17173社区发帖软件
17173社区发帖软件,游戏社区一直是秒收录的地方,像这些大IP超级最多人玩的PV地,肯定都是秒收录的高级地区,这个脚本是自动在剑侠社区发帖的,社区可以自己更换,也可以自己选择分类,不过现在收录最好的就是这么几个社区地了,虽然他是三级域名,但17173的名头还在的,社区里最多人的地方,就是在这里了,社区很多人流的地方,不过这个一个账号发帖会有限制,软件是利用缓存CK进行写入的,没有做登陆步骤,因为他登陆有验证码,
信息发布软件知识辰星网站动态群发软件
知识辰星网站动态群发软件,这个网站和以前的动态微博类似的网站,他是没有太大的区别的,不过他网站一会放开注册发帖,一会又限制,让人摸不着头脑,网站放开的时候就像是洪水一样带来N多的收录,发帖多的人自然知道怎么弄内容可以收录,以不弄弄某些内容不会收录的,如果不明白这点,是极度百万剧的存在,因为你根本就没有吸取前面的经验,只是有盲目从众的干活,是得不出好的结果的,做很多事情既要低头拉车,也要抬头看路,知
群发软件720云网站自动创建场景软件
720云网站自动创建场景软件,这个网站是做全景VR影像类似的,发现他们居然可以把这个链接做为宣传的地方,不过只能五十个字完工,也就是标题这里输入五十个字左右,脚本里做了限制五十个字,太长会被K掉,用的是他的素材库,网站没有什么太大的限制,封号也是看运气的,只是网站不能用缓存登陆,他那个验证码是会动来动云的,传到网站打码会有一点差别的,基本是手工打码一个号就可以一直发,没有什么限制,素材用啥都可以,网站权
群发软件QQ 音乐歌单全自动群发软件
QQ 音乐歌单是一个比较低成本的宣传方式,软件先是通过 QQ 登陆歌单页面,然后往了老面的发标题和简介进行宣传,音乐视频小说站可以说是最容易收录的地方了,比其它的限制还少,现在主力位往往很少关注这些,这也是给广告提供了位置的空缺地带,这个脚本可以采集缓存,然后写入缓存进行账号切换着发,这样是很简单的操作了,有的收录了,连评论都成了权重地,因为这些跑跑蜘蛛池极容易变成一个收录地带圈,这也是因为 腾讯这些默认
群发软件晋江小说网站书单软件
晋江小说网站书单软件,最近看书的书单很火暴收录,因为他们书单可以任意的发,可以让他网站小说的PV直接升级收录,一般来说他们PV高的网站都是秒收录的存在,只是他们没有太多的外在链接,全部是靠自身的小说网站进行的,只不过这些网站比较难找到,特别是这个网站,他的书单是在很多很多公告最底下的,不认真看居然不【知】道他的存在,居然他存在就会有一个好的收录量了,只要找到书单的网址,然后提交书单,都可以有一个好的收
群发软件洋溪信息港自动发布软件
洋溪信息港自动提问软件,这个网站有点像论坛,但他其实手机网址里发提问才有收录,其它收录比较差劲,而且这个已经很久了,以前一直不让发的,最近又恢复了可以发帖,故此做一个脚本进行发提问,发成功后可以直接采集网址,自然收录也可以,丢蜘蛛池也是可以的,网站效果还是很别具风格的,就是账号都是手机验证码注册,好在他网站提问账号可以疯狂刷,没有验证码发帖,只要放进去账号密码,软件就可以自动登陆,自动的发帖,没有
群发软件百度贴吧视频上传软件
百度贴吧视频上传软件,百度贴吧消费了这么多年,仍然是最大的社区,也算是难得了吧,他的规则时严时松,给做广告的人都有了一定的空间,这个网站虽然严格,全部都是机器人审核的,但也因为这样才有操作的空间,有的时候某些BUG是层出不穷的,有时候是同音字的天下,有时候是视频上传的天下,有时候是图片动态的天下,各有千秋,好不热闹的样子,这个脚本是自动上传视频的,只要号是正常都可以上传成功,主要是没有验证码,省一笔R
信息发布软件Moloney网站自动发帖软件
moloney网站软件,这个网站只能发提问的一个后台,因为网站是品牌比较在的网站,可能架构是有问题的,不过网站发帖一个号可以发很多帖子,帖子发完要在个人中心才能把帖采集回来,发的很快也无所谓,网站不理这些事,提问这个发成功他是按里面分类进行帖子排列的,你发哪一个分类就要专进哪一个版块去采集个人帖子网址,这样丢蜘蛛池才会有效果,百度自然收录这些网站基本是不能的,因为网站权重比较低的,只能靠外力去抓取,上星
群发软件三一办公网站自动注册账号宣传主页软件
三一办公网站自动注册账号宣传主页软件,这个网站都是下载PPT的一个网站,这些网站收录量都极大,随随便便一个就有几百万的收录量,也不【知】道他们好在哪,可能现在做老师的还会在这些网站下载资料吧,他们也不得不弄这些PPT上课玩,生活所迫,都有不容易的,下载这些PPT也是乘着以前的流量做大做强了许多,现在可能都在用手机在做PPT了吧,不过聪明点老师们可能还在用传统的电脑在做这些PPT,不然他们以前买的电脑不也是成了废
群发软件面圈网站自动发帖工具
面圈网站发提问软件,这个网站是一个综合性网站,但似乎可以发提问作为广告,以前不可以注册账号,最近又恢复了可以注册了,注册需要手机验证码才可以发,他发帖也没有多少限制,基本就是登陆一个号,就可以一直发提问,不过内容输入框有点麻烦,软件这里是用点击一下,复制所有列表要发的内容,然后粘帖进去的,完美的解决,不过这样也有点问题就是会占用打字的粘帖板哈,这样就只能独立让他运行了,不然会窜行的,发帖的时候还要
群发软件腾讯兔小巢软件
腾讯兔小巢自动创建产品软件,这个创建产品有点像微信里的小程序,他是一个小组讨论类似的,这个网站账号也是比较多人买的,都是微信搜码登陆,然后自动登陆上的意思,不管是发帖,还是创建产品都会有一个腾讯的验证码,现在随着人工智能的来到,这些验证码几乎就是小菜一碟了,没有啥难度,三下五除二就可以破解好,并且可以给您建一个强大的识别库了,现在打码平台也很多看到了这种机遇,只要跑一跑几天,就可以把一个识别库给整
群发软件17推论坛专用发帖软件
17推论坛是一个站长论的网站,以前很火的,现在也还是让别人发一发软文,发一发外链作为网站的赚钱点了,很多不愿意去尝试新的东西的网站都还守着这一亩三分地,当然在不断变化的时候,这也没有什么所谓对与错,只是他们选择是这样,没有了那种多大心去办多大的事,只有剩下这点能量,再去发火挥自己应该有的光和能罢了,有时候坚持自己能车范围的事,比说什么大道理还要有用,出击着各种各样的新市场,守着自己的成功之作,发光也
群发软件西瓜视频软件
西瓜创作平台视频自动上传软件,这个网站其实是抖音的子公司,也是借助他的力量做大的,但西瓜视频他主要着重点是网页的东东,不过也因为太多太杂的原因,他网站收录的就比较快,视频网站别的不了解,但收录是最好的,没有之一,现在他改版成只能上传视频,输入标题,上传封面图片,我们可以弄关键字,然后把视频封面上下功夫,其它是不让填写的,没有简介的了,以前是有的,现在已经全部改版了,软件是可以发一帖切换一个账号,切
群发软件汕头大学论坛自动群发工具
这些个论坛是精简单的,不过人家做的久一些,所以有一定的流量,以前这些论坛是满大街的到处都是,还整了各种各样的验证码,现在简单了,只要您有号就给发帖,可以说是白送的,因为这些论坛想活下去,就必须卖点账号,必须拼一拼到最后一口气,直到没有了,才会消失,很多以前很多人的社区都是想赚最后一个铜板,都想来赚赚钱,然后再关闭,服务器什么时候到期都不会再去续费玩了,不过最后收录还是很好的,以前您可能都没有发现自

QQ|( 京ICP备09078825号 )

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

GMT+8, 2024-5-1 06:33 , Processed in 0.283017 second(s), 50 queries .

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

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