Java 序列化 JDK序列化总结
Java 序列化 JDK序列化总结
@author ixenos
Java序列化是在JDK 1.1中引入的,是Java内核的重要特性之一。Java序列化API允许我们将一个对象转换为流,并通过网络发送,或将其存入文件或数据库以便未来使用,反序列化则是将对象流转换为实际程序中使用的Java对象的过程。Java同步化过程乍看起来很好用,但它会带来一些琐碎的安全性和完整性问题,在文章的后面部分我们会涉及到,以下是本教程涉及的主题。
- Java序列化接口
- 使用序列化和serialVersionUID进行类重构
- Java外部化接口
- Java序列化方法
- 序列化结合继承
- 序列化代理模式
Java序列化接口 java.io.Serializable
如果你希望一个类对象是可序列化的,你所要做的是实现java.io.Serializable接口。序列化一种标记接口,不需要实现任何字段和方法,这就像是一种选择性加入的处理,通过它可以使类对象成为可序列化的对象。
序列化处理是通过ObjectInputStream和ObjectOutputStream实现的,因此我们所要做的是基于它们进行一层封装,要么将其保存为文件,要么将其通过网络发送。我们来看一个简单的序列化示例。
package com.journaldev.serialization; import java.io.Serializable; public class Employee implements Serializable { // private static final long serialVersionUID = -6470090944414208496L; private String name;
private int id;
transient private int salary;
// private String password; @Override
public String toString(){
return "Employee{name="+name+",id="+id+",salary="+salary+"}";
} //getter and setter methods
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public int getSalary() {
return salary;
} public void setSalary(int salary) {
this.salary = salary;
} // public String getPassword() {
// return password;
// }
//
// public void setPassword(String password) {
// this.password = password;
// } }
注意一下,这是一个简单的java bean,拥有一些属性以及getter-setter方法,如果你想要某个对象属性不被序列化成流,你可以使用transient关键字,正如示例中我在salary变量上的做法那样。
现在我们假设需要把我们的对象写入文件,之后从相同的文件中将其反序列化,因此我们需要一些工具方法,通过使用ObjectInputStream和ObjectOutputStream来达到序列化的目的。
package com.journaldev.serialization; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; /**
* A simple class with generic serialize and deserialize method implementations
*
* @author pankaj
*
*/
public class SerializationUtil { // deserialize to Object from given file
public static Object deserialize(String fileName) throws IOException,
ClassNotFoundException {
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
return obj;
} // serialize the given object and save it to file
public static void serialize(Object obj, String fileName)
throws IOException {
FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj); fos.close();
} }
注意一下,方法的参数是Object,它是任何Java类的基类,这样写法以一种很自然的方式保证了通用性。
现在我们来写一个测试程序,看一下Java序列化的实战。
package com.journaldev.serialization; import java.io.IOException; public class SerializationTest { public static void main(String[] args) {
String fileName="employee.ser";
Employee emp = new Employee();
emp.setId(100);
emp.setName("Pankaj");
emp.setSalary(5000); //serialize to file
try {
SerializationUtil.serialize(emp, fileName);
} catch (IOException e) {
e.printStackTrace();
return;
} Employee empNew = null;
try {
empNew = (Employee) SerializationUtil.deserialize(fileName);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
} System.out.println("emp Object::"+emp);
System.out.println("empNew Object::"+empNew);
} }
运行以上测试程序,可以得到以下输出。
emp Object::Employee{name=Pankaj,id=100,salary=5000}
empNew Object::Employee{name=Pankaj,id=100,salary=0}
由于salary是一个transient变量,它的值不会被存入文件中,因此也不会在新的对象中被恢复。类似的,静态变量的值也不会被序列化,因为他们是属于类而非对象的。
使用序列化和serialVersionUID进行类重构
Java序列化允许java类中的一些变化,如果他们可以被忽略的话。一些不会影响到反序列化处理的变化有:
- 在类中添加一些新的变量。
- 将变量从transient转变为非tansient,对于序列化来说,就像是新加入了一个变量而已。
- 将变量从静态的转变为非静态的,对于序列化来说,就也像是新加入了一个变量而已。
不过这些变化要正常工作,java类需要具有为该类定义的serialVersionUID,我们来写一个测试类,只对之前测试类已经生成的序列化文件进行反序列化。
package com.journaldev.serialization; import java.io.IOException; public class DeserializationTest { public static void main(String[] args) { String fileName="employee.ser";
Employee empNew = null; try {
empNew = (Employee) SerializationUtil.deserialize(fileName);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
} System.out.println("empNew Object::"+empNew); } }
现在,在Employee类中去掉”password”变量的注释和它的getter-setter方法,运行。你会得到以下异常。
java.io.InvalidClassException: com.journaldev.serialization.Employee; local class incompatible: stream classdesc serialVersionUID = -6470090944414208496, local class serialVersionUID = -6234198221249432383
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:604)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1601)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
at com.journaldev.serialization.SerializationUtil.deserialize(SerializationUtil.java:22)
at com.journaldev.serialization.DeserializationTest.main(DeserializationTest.java:13)
empNew Object::null
原因很显然,上一个类和新类的serialVersionUID是不同的,事实上如果一个类没有定义serialVersionUID,它会自动计算出来并分配给该类。Java使用类变量、方法、类名称、包,等等来产生这个特殊的长数。如果你在任何一个IDE上工作,你都会得到警告“可序列化类Employee没有定义一个静态的final的serialVersionUID,类型为long”。
我们可以使用java工具”serialver”来产生一个类的serialVersionUID,对于Employee类,可以执行以下命令。
SerializationExample/bin$serialver -classpath . com.journaldev.serialization.Employee
记住,从程序本身生成序列版本并不是必须的,我们可以根据需要指定值,这个值的作用仅仅是告知反序列化处理机制,新的类是相同的类的新版本,应该进行可能的反序列化处理。
举个例子,在Employee类中仅仅将serialVersionUID字段的注释去掉,运行SerializationTest程序。现在再将Employee类中的password字段的注释去掉,运行DeserializationTest程序,你会看到对象流被成功地反序列化了,因为Employee类中的变动与序列化处理是相容的。
Java外部化接口 java.io.Externalizable
如果你在序列化处理中留个心,你会发现它是自动处理的。有时候我们想要去隐藏对象数据,来保持它的完整性,可以通过实现java.io.Externalizable接口,并提供writeExternal()和readExternal()方法的实现,它们被用于序列化处理。
package com.journaldev.externalization; import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput; public class Person implements Externalizable{ private int id;
private String name;
private String gender; @Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(id);
out.writeObject(name+"xyz");
out.writeObject("abc"+gender);
} @Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
id=in.readInt();
//read in the same order as written
name=(String) in.readObject();
if(!name.endsWith("xyz")) throw new IOException("corrupted data");
name=name.substring(0, name.length()-3);
gender=(String) in.readObject();
if(!gender.startsWith("abc")) throw new IOException("corrupted data");
gender=gender.substring(3);
} @Override
public String toString(){
return "Person{id="+id+",name="+name+",gender="+gender+"}";
}
public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getGender() {
return gender;
} public void setGender(String gender) {
this.gender = gender;
} }
注意,在将其转换为流之前,我已经更改了字段的值,之后读取时会得到这些更改,通过这种方式,可以在某种程度上保证数据的完整性,我们可以在读取流数据之后抛出异常,表明完整性检查失败。
来看一个测试程序。
package com.journaldev.externalization; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class ExternalizationTest { public static void main(String[] args) { String fileName = "person.ser";
Person person = new Person();
person.setId(1);
person.setName("Pankaj");
person.setGender("Male"); try {
FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} FileInputStream fis;
try {
fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Person p = (Person)ois.readObject();
ois.close();
System.out.println("Person Object Read="+p);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} } }
运行以上测试程序,可以得到以下输出。
Person Object Read=Person{id=1,name=Pankaj,gender=Male}
那么哪个方式更适合被用来做序列化处理呢?实际上使用序列化接口更好,当你看到这篇教程的末尾时,你会知道原因的。
Serializable和Externalizable的区别与联系
1、Externalizable继承自Serializable;Externalizable 实例也可以通过 Serializable 接口中记录的 writeReplace 和 readResolve 方法来指派一个替代对象
2、实现Externalizable接口的类必须有默认构造方法。在读入可外部化的(Externalizable)类时,对象流将先用无参构造器创建一个对象,然后调用readExternal方法按writeExternal定义的机制反序列化;
3、若要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替定制的 writeObject 和 readObject 方法实现;write/readExternal要对包括超类数据在内的整个对象的存储和恢复负全责,而Serializable在流中仅仅记录该对象所属的类的属性状态;
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
password = (String) in.readObject();
} public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( name);
out.writeObject( password);
}
但是,如果多个对象a是多次被序列化的,在反序列后对象b会被反序列化多次,因为反序列化时都调用构造器(Externalizable调用无参构造器)在堆中生成新对象,内存地址都是不同的,即多个a对象的属性b是不一样的。
6、Serialization 对象将使用 Serializable 和 Externalizable
接口。对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持 Externalizable 接口。
a) 如果对象支持 Externalizable,则调用
writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream
保存该对象。
b) 在重构 Externalizable 对象时,先使用无参数的公共构造方法创建一个实例,然后调用 readExternal 方法。通过从
ObjectInputStream 中读取 Serializable 对象可以恢复这些对象。
Java序列化方法 java.io.Serializable
我们已经看到了,java的序列化是自动的,我们所要做的仅仅是实现序列化接口,其实现已经存在于ObjectInputStream和ObjectOutputStream类中了。不过如果我们想要更改存储数据的方式,比如说在对象中含有一些敏感信息,在存储/获取它们之前我们要进行加密/解密,这该怎么办呢?这就是为什么在类中我们拥有四种方法,能够改变序列化行为。
如果以下方法在被序列化类中存在,它们就会被用于序列化处理。
- readObject(ObjectInputStream ois):如果这个方法存在,ObjectInputStream readObject()方法会调用该方法从流中读取对象。
- writeObject(ObjectOutputStream oos):如果这个方法存在,ObjectOutputStream writeObject()方法会调用该方法从流中写入对象。一种普遍的用法是隐藏对象的值来保证完整性。
- Object writeReplace():如果这个方法存在,那么在序列化处理之后,该方法会被调用并将返回的对象序列化到流中。
- Object readResolve():如果这个方法存在,那么在序列化处理之后,该方法会被调用并返回一个最终的对象给调用程序(keyijinxing)。一种使用方法是在序列化类中实现单例模式,你可以从序列化和单例中读到更多知识。此方法返回的对象,会被作为readOjbect的返回值(即使readObject方法定义并没有返回任何对象)
通常情况下,当实现以上方法时,应该将其设定为私有类型,这样子类就无法覆盖它们了,因为它们本来就是为了序列化而建立的,设定为私有类型能避免一些安全性问题。
readObejct和writeObject的具体示例:Java Object 对象序列化和反序列化
writeReplace和readResolve的具体示例:Java Object 序列化与单例模式 以及本文的 序列化代理模式
序列化结合继承
有时候我们需要对一个没有实现序列化接口的类进行扩展,如果依赖于自动化的序列化行为,而一些状态是父类拥有的,那么它们将不会被转换为流,因此以后也无法获取。
在此,readObject()和writeObject()就可以派上大用处了,通过提供它们的实现,我们可以将父类的状态存入流中,以便今后获取。我们来看一下实战。
package com.journaldev.serialization.inheritance; public class SuperClass { private int id;
private String value; public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
} }
父类是一个简单的java bean,没有实现序列化接口。
package com.journaldev.serialization.inheritance; import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class SubClass extends SuperClass implements Serializable, ObjectInputValidation{ private static final long serialVersionUID = -1322322139926390329L; private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString(){
return "SubClass{id="+getId()+",value="+getValue()+",name="+getName()+"}";
} //adding helper method for serialization to save/initialize super class state
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException{
ois.defaultReadObject(); //注意读和写的顺序要一致,反序列化出值,再赋给父类属性
setId(ois.readInt());
setValue((String) ois.readObject()); } private void writeObject(ObjectOutputStream oos) throws IOException{
oos.defaultWriteObject();
//调用父类的getId获得值单独写到序列化流中
oos.writeInt(getId());
oos.writeObject(getValue());
} @Override
public void validateObject() throws InvalidObjectException {
//validate the object here
if(name == null || "".equals(name)) throw new InvalidObjectException("name can't be null or empty");
if(getId() <=0) throw new InvalidObjectException("ID can't be negative or zero");
} }
注意,将额外数据写入流和读取流的顺序应该是一致的,我们可以在读与写之中添加一些逻辑,使其更安全。
同时还需要注意,这个类实现了ObjectInputValidation接口,通过实现validateObject()方法,可以添加一些业务验证来确保数据完整性没有遭到破坏。
以下通过编写一个测试类,看一下我们是否能够从序列化的数据中获取父类的状态。
package com.journaldev.serialization.inheritance; import java.io.IOException; import com.journaldev.serialization.SerializationUtil; public class InheritanceSerializationTest { public static void main(String[] args) {
String fileName = "subclass.ser"; SubClass subClass = new SubClass();
subClass.setId(10);
subClass.setValue("Data");
subClass.setName("Pankaj"); try {
SerializationUtil.serialize(subClass, fileName);
} catch (IOException e) {
e.printStackTrace();
return;
} try {
SubClass subNew = (SubClass) SerializationUtil.deserialize(fileName);
System.out.println("SubClass read = "+subNew);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
} }
运行以上测试程序,可以得到以下输出。
SubClass read = SubClass{id=10,value=Data,name=Pankaj}
因此通过这种方式,可以序列化父类的状态,即便它没有实现序列化接口。当父类是一个我们无法改变的第三方的类,这个策略就有用武之地了。
序列化代理模式
Java序列化也带来了一些严重的误区,比如:
- 类的结构无法大量改变,除非中断序列化处理,因此即便我们之后已经不需要某些变量了,我们也需要保留它们,仅仅是为了向后兼容。
- 序列化会导致巨大的安全性危机,一个攻击者可以更改流的顺序,继而对系统造成伤害。举个例子,用户角色被序列化了,攻击者可以更改流的值为admin,再执行恶意代码。
序列化代理模式是一种使序列化能达到极高安全性的方式,在这个模式下,一个内部的私有静态类被用作序列化的代理类,该类的设计目的是用于保留主类的状态。这个模式的实现需要合理实现readResolve()和writeReplace()方法。
让我们先来写一个类,实现了序列化代码模式,之后再对其进行分析,以便更好的理解原理。
package com.journaldev.serialization.proxy; import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable; public class Data implements Serializable{ private static final long serialVersionUID = 2087368867376448459L; private String data; public Data(String d){
this.data=d;
} public String getData() {
return data;
} public void setData(String data) {
this.data = data;
} @Override
public String toString(){
return "Data{data="+data+"}";
} //serialization proxy class
private static class DataProxy implements Serializable{ private static final long serialVersionUID = 8333905273185436744L; private String dataProxy;
private static final String PREFIX = "ABC";
private static final String SUFFIX = "DEFG"; public DataProxy(Data d){
//obscuring data for security
this.dataProxy = PREFIX + d.data + SUFFIX;
} private Object readResolve() throws InvalidObjectException {
if(dataProxy.startsWith(PREFIX) && dataProxy.endsWith(SUFFIX)){
return new Data(dataProxy.substring(3, dataProxy.length() -4));
}else throw new InvalidObjectException("data corrupted");
} } //replacing serialized object to DataProxy object
private Object writeReplace(){
return new DataProxy(this);
} private void readObject(ObjectInputStream ois) throws InvalidObjectException{
throw new InvalidObjectException("Proxy is not used, something fishy");
}
}
- Data和DataProxy类都应该实现序列化接口。
- DataProxy应该能够保留Data对象的状态。
- DataProxy是一个内部的私有静态类,因此其他类无法访问它。
- DataProxy应该有一个单独的构造方法,接收Data作为参数。
- Data类应该提供writeReplace()方法,返回DataProxy实例,这样当Data对象被序列化时,返回的流是属于DataProxy类的,不过DataProxy类在外部是不可见的,所有它不能被直接使用。
- DataProxy应该实现readResolve()方法,返回Data对象,这样当Data类被反序列化时,在内部其实是DataProxy类被反序列化了,之后它的readResolve()方法被调用,我们得到了Data对象。
- 最后,在Data类中实现readObject()方法,抛出InvalidObjectException异常,防止黑客通过伪造Data对象的流并对其进行解析,继而执行攻击。
我们来写一个小测试,检查一下这样的实现是否能工作。
package com.journaldev.serialization.proxy; import java.io.IOException; import com.journaldev.serialization.SerializationUtil; public class SerializationProxyTest { public static void main(String[] args) {
String fileName = "data.ser"; Data data = new Data("Pankaj"); try {
SerializationUtil.serialize(data, fileName);
} catch (IOException e) {
e.printStackTrace();
} try {
Data newData = (Data) SerializationUtil.deserialize(fileName);
System.out.println(newData);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
} }
运行以上测试程序,可以得到以下输出。
Data{data=Pankaj
如果你打开data.ser文件,可以看到DataProxy对象已经被作为流存入了文件中。
这就是Java序列化的所有内容,看上去很简单但我们应当谨慎地使用它,通常来说,最好不要依赖于默认实现。你可以从上面的链接中下载项目,玩一玩,这能让你学到更多。
本文参考以下内容:
原文链接: journaldev 翻译: ImportNew.com- Justin Wu
译文链接: http://www.importnew.com/14465.html
Java 序列化 JDK序列化总结的更多相关文章
- JAVA基础4---序列化和反序列化深入整理(JDK序列化)
一.什么是序列化和反序列化? 序列化:将对象状态信息转化成可以存储或传输的形式的过程(Java中就是将对象转化成字节序列的过程) 反序列化:从存储文件中恢复对象的过程(Java中就是通过字节序列转化成 ...
- 对Java Serializable(序列化)的理解和总结
我对Java Serializable(序列化)的理解和总结 博客分类: Java技术 JavaOSSocketCC++ 1.序列化是干什么的? 简单说就是为了保存在内存中的各种对象的状 ...
- Java对象的序列化与反序列化
序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...
- Java对象的序列化和反序列化[转]
Java基础学习总结--Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用 ...
- Java IO5:序列化与反序列化
一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存 ...
- Java 序列化 对象序列化和反序列化
Java 序列化 对象序列化和反序列化 @author ixenos 对象序列化是什么 1.对象序列化就是把一个对象的状态转化成一个字节流. 我们可以把这样的字节流存储为一个文件,作为对这个对象的复制 ...
- Java基础系列——序列化(一)
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6797659.html 工作中发现,自己对Java的了解还很片面,没有深入的研究,有很多的J ...
- java中的序列化
遇到这个 Java Serializable 序列化这个接口,我们可能会有如下的问题 a,什么叫序列化和反序列化b,作用.为啥要实现这个 Serializable 接口,也就是为啥要序列化c,seri ...
- 【Java】详解java对象的序列化
目录结构: contents structure [+] 序列化的含义和意义 使用对象流实现序列化 对象引用的序列化 自定义序列化 采用实现Serializable接口实现序列化 采用实现Extern ...
随机推荐
- Oracle中忘记sys和System密码的办法
打开cmd:sqlplus/nolog connect/as sysdba; alter user sys identified by ***; alter user system identifie ...
- SQL2008R2 无法读取此系统上以前注册的服务器的列表--网上方法不可行
C:\Users\<username>\AppData\Local\Temp,然后创建两个命名为1和2的文件夹
- springMVC3学习(十一)--文件上传CommonsMultipartFile
使用springMVC提供的CommonsMultipartFile类进行读取文件 需要用到上传文件的两个jar包 commons-logging.jar.commons-io-xxx.jar 1.在 ...
- WINCE 电池状态(C#)
WINCE 电池状态(C#) 分类: 电量 2013-04-18 12:08 397人阅读 评论(1) 收藏 举报 ...
- OpenCascade
Hello World of OpenCascade Hello World of OpenCascade eryar@163.com 摘要Abstract:以一个经典的Hello World程序 ...
- Sublime中开发Ruby
Ruby:Sublime中开发Ruby需要注意的Encoding事项 目录 背景Sublime相关默认的文件存储编码:UTF8默认的输出控制台编码:UTF8修改默认的输出控制台编码Ruby相关默认的代 ...
- windows下配置PHP+Nginx+MySQL完整流程(转)
对于在windows上的php+nginx的配置可能好多同学一次根本都配不正确,于我也是如此,为此我将我成功配置的过程细致的总结如下,希望能帮助搞PHP研究的同学 1.资源准备 MySQL:这个链接不 ...
- Nginx安装配置与HelloWorld
<深入理解Nginx>阅读与实践(一):Nginx安装配置与HelloWorld 最近在读陶辉的<深入理解Nginx:模块开发与架构解析>,一是想跟着大牛练练阅读和编写开源代码 ...
- Matlab下多径衰落信道的仿真
衰落信道参数包括多径扩展和多普勒扩展.时不变的多径扩展相当于一个延时抽头滤波器,而多普勒扩展要注意多普勒功率谱密度,通常使用Jakes功率谱.高斯.均匀功率谱. 多径衰落信道由单径信道叠加而成,而单径 ...
- javascript ajax 脚本跨域调用全解析
javascript ajax 脚本跨域调用全解析 今天终于有点时间研究了一下javsscript ajax 脚本跨域调用的问题,先在网上随便搜了一下找到一些解决的办法,但是都比较复杂.由是转到jqu ...