Java序列化

序列化:一种将对象以一连串的字节描述的过程
反序列化:一种将这些字节重建成一个对象的过程

序列化的应用场景

  • 当你想把的内存中的对象保存到一个文件中或者数据库中时候(数据持久化);
  • 当你想用套接字在网络上传送对象的时候;
  • 当你想通过RMI传输对象的时候; Java RMI 支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用

实现序列化

将需要序列化的类实现Serializable接口就可以了,Serializable接口中没有任何方法,可以理解为一个标记,即表明这个类可以序列化.

序列化例子

FileOutputStream fos = new FileOutputStream("serialize.obj");  
ObjectOutputStream oos = new ObjectOutputStream(fos);  
Serialize serialize = new Serialize();  
oos.writeObject(serialize);  
oos.flush();  
oos.close();
fos.close();  

反序列化例子

FileInputStream fis = new FileInputStream("serialize.obj");  
ObjectInputStream ois = new ObjectInputStream(fis);  
serialize = (Serialize) ois.readObject();  
ois.close();  
fis.close();  

相关注意事项

  • 序列化时,只对对象的状态进行保存,而不管对象的方法;
  • 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
  • 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  • 并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
    1. 安全方面的原因,比如一个对象拥有privatepublicfield,对于一个要传输的对象,比如写到文件,或者进行rmi传输 等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
    2. 资源分配方面的原因,比如socketthread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且也是没有必要这样实现。

序列化前和序列化后的对象的关系

序列化时深复制,反序列化还原后的对象地址与原来的不同。

不同的原因:
通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

破坏单例模式

单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来.

package com.serialize;

import java.io.Serializable;

public class SerSingleton implements Serializable
{
    private static final long serialVersionUID = 1L;
    
    String name;
    
    private SerSingleton()
    {
        System.out.println("Singleton is create");
        name="SerSingleton";
    }
    
    private static SerSingleton instance = new SerSingleton();
    
    public static SerSingleton getInstance()
    {
        return instance;
    }
    
    public static void createString()
    {
        System.out.println("createString in Singleton");
    }
}

@Test
public void test() throws IOException, ClassNotFoundException
{
    SerSingleton s1= null;
    SerSingleton s = SerSingleton.getInstance();
    
    FileOutputStream fos = new FileOutputStream("SerSingleton.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(s);
    oos.flush();
    oos.close();
    
    FileInputStream fis = new FileInputStream("SerSingleton.obj");
    ObjectInputStream ois = new ObjectInputStream(fis);
    s1 = (SerSingleton)ois.readObject();
    System.out.println(s==s1);
}
    
----------
private Object readResolve()  
{  
    return instance;  
}  

输出false

说明测试代码中的ss1指向了不同的实例,在反序列化后,生成多个对象实例。

加上第二部分代码:这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.

序列化ID

序列化IDEclipse下提供了两种生成策略,一个是固定的1L,一个是随机生成一个不重复的long类型数据(实际上是使用JDK工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的1L就可以,这样可以确保代码一致时反序列化成功。这也可能是造成序列化和反序列化失败的原因,因为不同的序列化id之间不能进行序列化和反序列化。

静态变量能否序列化

序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化。即 序列化的是对象的状态不是类的状态。这里的不能序列化的意思,是序列化信息中不包含这个静态成员域。transient后的变量也不能序列化。

transient小结

  1. 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。

  2. transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。

  3. transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

注意:

Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据

对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关

总结

  1. 当父类继承Serializable接口时,所有子类都可以被序列化。
  2. 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,数据不会丢失)但是在子类中的属性仍能正确序列化
  3. 如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错。
  4. 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  5. 在反序列化时,如果serialVersionUID被序列化,则反序列化时会失败
  6. 当一个对象的实例变量引用其他对象,序列化改对象时,也把引用对象进行序列化
  7. statictransient后的变量不能被序列化
2016/3/31 posted in  Java

Java传值还是传引用

2016/3/31 posted in  Java

`Local Storage`缓存

因为WAP触屏版的面向对象都是使用智能手机浏览器访问的用户,而基本上所有智能手机的浏览器都支持Local Storage,这样就可以使用Local Storage缓存一些东西。

之前都是使用Local Storage缓存一些用户信息,当做cookies使用,甚至还有一段时间Local Storage里的内容不能通过浏览器‘清楚访问痕迹’的功能删除,就使用Local Storage作为存储用户唯一标识的地方。

现在为WAP触屏版的优化思路就是将jscss文件存到Local Storage中,用户之后访问需要jscss的时候都从Local Storage中取,而不从网络中拉了。

美团的WAP触屏版是一个做的比较极端的例子

第一阶段

其实对于规模不是很大的网站,上线的流程不会特别严格,特别规范,那么对cssjs经常的修修补补是不可避免的。如果使用Local Storage缓存所有的cssjs,那么就还要制定一套Local Storage缓存失效的规则。所以我们暂时先只对用到的第三方jscssjqueryFramework7等)进行缓存

参考了《Web移动端使用localStorage缓存Js和css文件》1,这套代码基本实现了使用Local Storage缓存jscss文件的基本需求,对于简单的网站页面效果也是明显的。

但是有两个问题:

1、不能跨域。程序是用ajax请求cssjs的,由于ajax不能跨域,所以必须保证静态资源文件和网站要在同一个域名下,这个跟动静分离的思想有所违背。比较糙的解决方法就是将要缓存的文件映射一份在网站同域名下。

2、文件不能顺序加载。程序在拿到cssjs文件后,将这些文件的内容head.appendChild(js/css)html中,这样会让这些文件同步加载到html中,而缓存的文件加载的顺序不能保证一定在非缓存文件之前,如果之后的js的文件依赖于缓存的js先加载,那么就会报错。

举个例子:

<head>
    <script>
        whir.res.loadJs("jquery", __BASE_SERVER__ + "/js/jquery-1.8.3.min.js");
    </script>
    <script type="text/javascript" src="${__static_server__}/js/needsJQUERY.js"></script>
</head>

第二个文件需要jquery先加载,然后使用jquery的函数,但是jquery被缓存,且不能保证会在第二个文件处理前加载好。这样就会报错。

程序对顺序加载的解决方案是使用回调函数,保证数序:

whir.res.loadJs("jquery", __BASE_SERVER__ + "/js/jquery-1.8.3.min.js"), function(){
    whir.res.loadJs("needsJQUERY", "/needsJQUERY.js");  
});

这种解决的方法对于结构简单的页面是有效的,但是我们的页面都是依赖一个公用的宏,在公用的宏中申明了所有地方放的jscss,每个页面自己又有一套本页面有效的jscss,对于这种callback随页面变化的情况,前面说的解决方法不太适用。

比较糙的解决方法就是在加载最后一个缓存js文件的时候,callback方法中写location.reload();,页面强行刷新下

第二阶段

第二个问题相对来说更棘手,所以我们先要解决这个问题。

我们的思路是这样的:

  • 对于第一次来网站的用户,Local Storage为空,我们先使用正常的jscss引用的方法,然后再将jscss放入Local Storage中,但这一次不用Local Storage中缓存的文件
  • 对于之前来过网站的用户,Local Storage是有缓存的jscss,所以直接使用这些缓存的文件

判别用户之前有没有来过网站,我们就拿一个cookie来记录,如果用户有这个cookie值,则判断用户来过;如果没有这个值,则判断用户没有来过。

再者,如果用户的这个cookie值和我们pageVersion不一样,说明我们要缓存的js或者css版本号改变了,这时候,我们会先让cookie为空,将用户理解为一个全新的、第一次来的用户,然后继续整个流程。

这样好像就解决了用户第一次来,还要强制刷新页面的问题。直到我们用safari或者uc浏览器无痕浏览的功能访问网站的时候

safari的无痕浏览模式不支持Local Storage,并且会在localstorage.setItem的时候报错,自动停止程序,导致咱们缓存jscss时,用户访问的cookie写成功了,但是缓存没写进去。

uc浏览器的无痕浏览模式也不支持Local Storage,并且在将cookie值更改以后,新旧两个cookie都会同时存在,这样我们的程序就变成了死循环,检测到用户访问过,以为有缓存,实际没有。

所以我们对程序又做了修正:

  • 将添加cookie操作加入到loadJs的回调函数中
  • 在保证localstorage.setItem成功运行之后,我们才会把cookie值记录进去
<script type="text/javascript" src="${__static_server__}/js/localstorage.js"></script>
<#if local_storage_version != ''>
    <script>
        var lsv = "";
        var name = "lsv=";
        var ca = document.cookie.split(";");
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == " ") {
                c = c.substring(1);
            }
            if (c.indexOf(name) == 0) {
                lsv = c.substring(name.length, c.length);
            }
        }
        if (lsv != whir.res.pageVersion || (window.localStorage && lsv != localStorage.getItem("version"))) {
            var d = new Date();
            d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
            var expires = "expires=" + d.toUTCString();
            document.cookie = "lsv=;" + expires;
            location.reload();
        }
    </script>
<#else>
<script type="text/javascript" src="${__static_server__}/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="${__static_server__}/js/framework7/js/framework7.min.js"></script>
</#if>
<script>
    whir.res.loadJs("jquery", __BASE_SERVER__ + "/js/jquery-1.8.3.min.js");           whir.res.loadJs("framework7js", __BASE_SERVER__ + "/js/framework7/js/framework7.min.js", function () {
            myApp = new Framework7();
            $$ = Dom7;
            $.cookie('lsv', whir.res.pageVersion, {path: '/', expires: 365});
        });
</script>

localstorage.js

var whir = window.whir || {};
var refreshYN = false;
whir.res = {
    pageVersion: "121", //页面版本,由页面输出,用于刷新localStorage缓存
    //动态加载js文件并缓存
    loadJs: function (name, url, callback) {
        if (window.localStorage) {
            var xhr;
            var js = localStorage.getItem(name);
            if (js == null || js.length == 0 || this.pageVersion != localStorage.getItem("version")) {
                refreshYN = true;
                if (window.ActiveXObject) {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } else if (window.XMLHttpRequest) {
                    xhr = new XMLHttpRequest();
                }
                if (xhr != null) {
                    xhr.open("GET", url);
                    xhr.send(null);
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            js = xhr.responseText;
                            localStorage.setItem(name, js);
                            localStorage.setItem("version", whir.res.pageVersion);
                            // 确保浏览器支持localStorage.setItem
                            if (localStorage.getItem("version") == whir.res.pageVersion) {
                                if (callback != null) {
                                    callback(); //回调,执行下一个引用
                                }
                            }
                        }
                    };
                }
            } else {
                whir.res.writeJs(js);
                if (callback != null) {
                    callback(); //回调,执行下一个引用
                }
            }
        } else {
            whir.res.linkJs(url);
        }
    },
    loadCss: function (name, url) {
        if (window.localStorage) {
            var xhr;
            var css = localStorage.getItem(name);
            if (css == null || css.length == 0 || this.pageVersion != localStorage.getItem("version")) {
                if (window.ActiveXObject) {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } else if (window.XMLHttpRequest) {
                    xhr = new XMLHttpRequest();
                }
                if (xhr != null) {
                    xhr.open("GET", url);
                    xhr.withCredentials = true;
                    xhr.send(null);
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            css = xhr.responseText;
                            localStorage.setItem(name, css);
                            localStorage.setItem("version", whir.res.pageVersion);
                        }
                    };
                }
            } else {
                css = css.replace(/images\//g, "style/images/"); //css里的图片路径需单独处理
                whir.res.writeCss(css);
            }
        } else {
            whir.res.linkCss(url);
        }
    },
    //往页面写入js脚本
    writeJs: function (text) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("script");
        link.type = "text/javascript";
        link.innerHTML = text;
        head.appendChild(link);
    },
    //往页面写入css样式
    writeCss: function (text) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("style");
        link.type = "text/css";
        link.innerHTML = text;
        head.appendChild(link);
    },
    //往页面引入js脚本
    linkJs: function (url) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("script");
        link.type = "text/javascript";
        link.src = url;
        head.appendChild(link);
    },
    //往页面引入css样式
    linkCss: function (url) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("link");
        link.type = "text/css";
        link.rel = "stylesheet";
        link.rev = "stylesheet";
        link.media = "screen";
        link.href = url;
        head.appendChild(link);
    }
};

第三阶段

对于跨域的处理,我们采用了CORS的方式,主要就是在Nginx服务器上配置一下,比较简便。

2016/3/29 posted in  others

应用多级缓存模式支撑海量读服务

2016/3/27 posted in  others

浅谈Web缓存

浏览器缓存

Cache-Control

1、max-age(单位为s)当浏览器向服务器发送请求后,在max-age这段时间里浏览器就不会再向服务器发送请求了。max-age会覆盖掉Expires

max-age=2592000

也就是说缓存有效期为2592000秒(也就是30天)。于是在30天内都会使用这个版本的资源,即使服务器上的资源发生了变化,浏览器也不会得到通知。

2、s-maxage(单位为s),只用于共享缓存(比如CDN

s-maxage=60

在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。如果存在s-maxage,则会覆盖掉max-ageExpires header

3、public指定响应会被缓存,并且在多用户间共享。如果没有指定public还是private,则默认为public

4、private响应只作为私有的缓存,不能在用户间共享。

5、no-cache指定不缓存响应,表明资源不进行缓存。设置了no-cache之后并不代表浏览器不缓存,而是在缓存前要向服务器确认资源是否被更改。因此有的时候只设置no-cache防止缓存还是不够保险,还可以加上private指令,将过期时间设为过去的时间。

6、no-store绝对禁止缓存

Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires = max-age + 请求时间,需要和Last-modified结合使用。

Last-modified

服务器端文件的最后修改时间,需要和cache-control共同使用,是检查服务器端资源是否更新的一种方式。当浏览器再次进行请求时,会向服务器传送If-Modified-Since报头,询问Last-Modified时间点之后资源是否被修改过。如果没有修改,则返回码为304,使用缓存;如果修改过,则再次去服务器请求资源,返回码和首次请求相同为200,资源为服务器最新资源。

ETag

根据实体内容生成一段hash字符串,标识资源的状态,由服务端产生。浏览器会将这串字符串传回服务器,验证资源是否已经修改

使用ETag可以解决Last-modified存在的一些问题:

  • 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新
  • 如果资源修改非常频繁,在秒以下的时间内进行修改,而Last-modified只能精确到秒
  • 一些资源的最后修改时间改变了,但是内容没改变,使用ETag就认为资源还是没有修改的。

使用缓存流程

其他

LocalStorage是一种本地存储的公共资源,域名下很多应用共享这份资源会有风险;LocalStorage是以页面域名划分的,如果有多个等价域名之间的LocalStorage不互通,则会造成缓存多份浪费。

2016/3/26 posted in  others