[转载]京东SDK模板卡盘效果实现代码 - cnblogs/chen - 博客园

mikel阅读(1045)

[转载]京东SDK模板卡盘效果实现代码 – cnblogs/chen – 博客园.

最近在做京东模板,因为是最新平台,好多功能都需要摸索,本人技术一般,摸索出一个建议的卡盘功能 各位童鞋,使用的是分类推荐模块哦!

本着共享的精神,俺将代码放到这儿了,各人请自便。(代码还不够完善,希望完善的童鞋可以发给我,直接放到评论里吧!在这里提前谢过哦!呵呵)

 

本人京东设计师:王靖鋆          设计模板: http://zx.jd.com/designerDetail.html?id=148

HTML代码 如下:

复制代码
#if($!moduleTitle.exists)
    <!--<div class="jTm">
        #if($!moduleTitle.titleExists)
            #if($!moduleTitle.title!='')
                <a href="$!moduleTitle.titleLink">$!moduleTitle.title</a>
            #end 
        #end

        #if($!moduleTitle.moreExists)
            #if($!moduleTitle.more!='')
                <div class="jMore"><a href="$!moduleTitle.moreLink">$!moduleTitle.more</a></div>
            #end
        #end

        #if($!moduleTitle.titleExists)
            #if($!moduleTitle.titleImg!='')
              <a href="$!moduleTitle.titleLink"><img src="$!moduleTitle.titleImg" /></a>
            #end
        #end
        #if($!moduleTitle.moreExists)
            #if($!moduleTitle.moreImg!='')
              <div class="jMore"><a href="$!moduleTitle.moreLink"><img src="$!moduleTitle.moreImg" /></a></div>
            #end
        #end
    </div>-->
#end    
#set($intNum=0)
<div class="j-module" module-function="tabAutoLayout" module-param="{}">
        <div class="jSortContent">
            #foreach($!categoryRec in $!goodsCategoryRecList)
                #foreach($!goods in $!categoryRec.goodsList)
                <ul><li>
                    <div class="jItem">
                        <div class="jPicbig">
                        <div class="jaoHot"></div>
                            <a title="$!goods.wname" href="$!jshopProduct.getBuyUrl($!goods.goodsId)" target="_blank">
                                <img width="350" height="320" src="$!jshopCommon.getImage($!goods.imageurl,1)" alt="$!goods.wname">
                            </a>
                        #if( $!goods.promTag!="")
                        <div class="jPromotionLabel">

                                $!goods.promTag

                        </div>
                        #end
                        </div>
                        <div class="jGoodsInfo">
                            <div class="jTitle"><h1><span style="font-size:17px;">HOT</span> 《热卖商品》</h1></div>
                            <div class="jDesc" title="$!goods.wname $!goods.advertWord">
                                <div class="jWname">
                                <a href="$!jshopProduct.getBuyUrl($!goods.goodsId)" target="_blank">$!goods.wname</a></br>
                                </div>
                                <div style="line-height:30px;">
                                    <span class="jSlogan">$!goods.advertWord</span>
                                </div>
                            </div>                          
                            <div class="jPrice">
                                <div class="jSalePrice">
                                    <span class="jText">参考价:</span>
                                    <span class="jRmb"></span>
                                    $!jshopPrice.getSalePrice($!goods.goodsId)
                                </div>
                                <div class="jdPrice">
                                    <span class="jText">促销价:</span>
                                    <span class="jRmb"></span>
                                    $!jshopPrice.getJdPrice($!goods.goodsId)
                                </div>
                            </div>
                            <div class="jBtnArea">
                                <a  href="$!jshopCommon.addCart($!goods.goodsId)"  target="_blank" >加入购物车</a>
                            </div>
                        </div>
                    </div>
                </li></ul>
                #end
            #end
        </div>

        <div class="jSortTab">
            #foreach($!categoryRec in $!goodsCategoryRecList)
                #foreach($!goods in $!categoryRec.goodsList)
                    <span>
                        <div class="jPic">
                            <a title="$!goods.wname" href="$!jshopProduct.getBuyUrl($!goods.goodsId)" target="_blank">
                                <img width="50" height="50" src="$!jshopCommon.getImage($!goods.imageurl,5)" alt="$!goods.wname">
                            </a>
                        </div>
                    </span>
                #end
            #end
        </div>
        <div class="jSortTabArrow"><b></b></div>
</div>
复制代码

 

CSS代码 如下:

复制代码
.user_PShow{font-family:'microsoft yahei';}
.user_PShow *{font-family:'microsoft yahei';}
.user_PShow .mt{}
.user_PShow .mc{background:#fff; overflow:hidden; position:relative;border-radius:4px;border:5px solid #EDEDED;border-bottom:0px;}
.user_PShow .jSortTab{
    border-top:2px solid #AAAAAA; overflow:hidden; position:relative;
    background-color:#ededed;height:74px;
}
.user_PShow .jSortTab span{float:left; width:158px; padding:7px 0;text-align:center; font-size:14px; font-weight:bold; color:#666;}
.user_PShow .jSortTab span.current{
    color:#FFFFFF;background-color:#FFFFFF;
}
.user_PShow .jSortTabArrow{width:158px; position:absolute; z-index:1; top:25px; left:0; height:7px; overflow:hidden; text-align:center;}
.user_PShow .jSortTabArrow b{display:inline-block; margin-top:-8px; width:0; height:0; border-width:10px; border-color:transparent transparent #E4393C transparent; overflow:hidden; zoom:1; font-size:0;}
.user_PShow .jSortContent{
    overflow:hidden;border-bottom:1px solid #F1F1F1;height:100%;
}
.user_PShow .jSortContent ul{overflow:hidden; display:none;}
.user_PShow .jSortContent ul.current{
    display:block;padding:10px;
}
.user_PShow li{width:157px; overflow:hidden; float:left;}

.user_PShow .jPic{
    padding:5px; text-align:center;background-color:#c3c3c3;width:50px;margin:0px auto;
}
.user_PShow .jDesc{
    height:55%; line-height:1.3; overflow:hidden;margin:10px 0px;
}
.user_PShow .jDesc a{color:#666;font-size:13px;}
.user_PShow .jDesc a:hover{color:#E4393C; text-decoration:underline;}
.user_PShow .jdPrice .jRmb{font-size:12px;}
.user_PShow .jdPrice .jdNum{font-size:15px;}
.user_PShow .jdPrice .jdNumNo{font-size:12px;}
.user_PShow .jMore{
     position:absolute;right:0px;top:0px;text-align:center;
}
.user_PShow .jTm{
    height:auto;
    position:relative;
}

.user_PShow .jPicbig{
    text-align:center;float:left;border:5px solid #ededed;
}
.user_PShow .jGoodsInfo{
    float:left;margin-left:30px;height:330px;width:550px;overflow:hidden;
}
.user_PShow .jSortContent ul li{
    width:100% !important;
}
.user_PShow .jTitle{
    background-color:#E7492E;
    font-size:15px;color:#FFFFFF;padding:5px 15px;
}
.user_PShow .jPrice,.user_PShow .jBtnArea,.user_PShow .jDesc{
    padding:0px 15px;
}
.user_PShow .jPrice{
    color:#c3c3c3;font-weight:bold;line-height:25px;min-height:45px;text-align:right;
}
.user_PShow .jBtnArea{
    color:#FFFFFF;font-weight:bold;line-height:43px;padding:0px;
}
.user_PShow .jBtnArea a,.user_PShow .jBtnArea .btn-coll{
    display:block; background: url("file/loadImage.html?templateId=5&imageName=Bjsale.png") no-repeat scroll 0 0 transparent;
    width:200px;height:40px;margin:0px auto;line-height:40px;text-align:center;color:#FFFFFF;font-size:17px;float:right;
}
.user_PShow .jBtnArea .btn-coll{
    background: url("file/loadImage.html?templateId=5&imageName=Bjsalegz.png") no-repeat scroll 0 0 transparent;float:left;
    cursor: pointer;
    display: block;
    vertical-align: top;font-weight:bold;;width:200px;height:40px;margin:0px;padding:0px;
}
.user_PShow .jAdvertWord{
    font-size:12px;height:30%;margin:10px 0px;
}
.user_PShow .jSalePrice{ text-decoration:line-through; overflow:hidden; clear:both;}
.user_PShow .jdPrice{color:#ff0000; overflow:hidden;font-weight:bold;}
.user_PShow h1{font-weight:bold;}
.user_PShow .jWname{margin:10px 0px;width:100%;text-align:center;border-bottom:2px solid #C3C3C3;}
.user_PShow .jSlogan{color:#565656;font-size:17px;font-weight:bold;}
.user_PShow .jaoHot{
    background: url("file/loadImage.html?templateId=5&imageName=hotjao.png") no-repeat scroll 0 0 transparent;width:84px;height:84px;
    position:absolute;left:6px;top:7px;
}

.user_PShow .jPromotionLabel{padding:15px 0px;color:#FFFFFF;font-size:12px;width:56px; height:55px;text-align:center; background:url("file/loadImage.html?templateId=5&imageName=promTag.png") no-repeat;  _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://img10.360buyimg.com/cms/g8/M02/0F/11/rBEHZ1C0Ma8IAAAAAAAIpkn1m8MAADCTQP_7yMAAAi-795.png'); _background-image:none;  position:absolute; top:4px; left:310px;}
.user_PShow .jPromotionTextArea{width:56px;font-size:12px;overflow:hidden; text-align:center; padding-top:35px;color:#fff;}
.user_PShow .jPromotionText1{}
.user_PShow .jPromotionNum{}
.user_PShow .jPromotionText2{}
.user_PShow .jSortTab span.current .jPic{
    background-color:#FF0000;
}
复制代码

 

[转载]用Python写一个简单的Web框架 - RussellLuo - 博客园

mikel阅读(762)

[转载]用Python写一个简单的Web框架 – RussellLuo – 博客园.

一、概述

在Python中,WSGI(Web Server Gateway Interface)定义了Web服务器与Web应用(或Web框架)之间的标准接口。在WSGI的规范下,各种各样的Web服务器和Web框架都可以很好的交互。

由于WSGI的存在,用Python写一个简单的Web框架也变得非常容易。然而,同很多其他的强大软件一样,要实现一个功能丰富、健壮高效的Web框架并非易事;如果您打算这么做,可能使用一个现成的Web框架(如 DjangoTornadoweb.py 等)会是更合适的选择。

本文尝试写一个类似web.py的Web框架。好吧,我承认我夸大其辞了:首先,web.py并不简单;其次,本文只重点实现了 URL调度(URL dispatch)部分。

二、从demo_app开始

首先,作为一个初步体验,我们可以借助 wsgiref.simple_server 来搭建一个简单无比(trival)的Web应用:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from wsgiref.simple_server import make_server, demo_app

httpd = make_server('', 8086, demo_app)
sa = httpd.socket.getsockname()
print 'http://{0}:{1}/'.format(*sa)

# Respond to requests until process is killed
httpd.serve_forever()

运行脚本:

$ python code.py
http://0.0.0.0:8086/

打开浏览器,输入http://0.0.0.0:8086/后可以看到:一行”Hello world!” 和 众多环境变量值。

三、WSGI中的application

WSGI中规定:application是一个 可调用对象(callable object),它接受 environstart_response 两个参数,并返回一个 字符串迭代对象

其中,可调用对象 包括 函数方法 或者 具有__call__方法的 实例environ 是一个字典对象,包括CGI风格的环境变量(CGI-style environment variables)和 WSGI必需的变量(WSGI-required variables);start_response 是一个可调用对象,它接受两个 常规参数(status,response_headers)和 一个 默认参数(exc_info);字符串迭代对象 可以是 字符串列表生成器函数 或者 具有__iter__方法的可迭代实例。更多细节参考 Specification Details

The Application/Framework Side 中给出了一个典型的application实现:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

现在用simple_app来替换demo_app:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""code.py"""

from wsgiref.simple_server import make_server
from application import simple_app as app

if __name__ == '__main__':
    httpd = make_server('', 8086, app)
    sa = httpd.socket.getsockname()
    print 'http://{0}:{1}/'.format(*sa)

    # Respond to requests until process is killed
    httpd.serve_forever()

运行脚本code.py后,访问http://0.0.0.0:8086/就可以看到那行熟悉的句子:Hello world!

四、区分URL

倒腾了一阵子后,您会发现不管如何改变URL中的path部分,得到的响应都是一样的。因为simple_app只识别host+port部分。

为了对URL中的path部分进行区分处理,需要修改application.py的实现。

首先,改用 来实现application:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

class my_app:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

然后,增加对URL中path部分的区分处理:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

class my_app:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        path = self.environ['PATH_INFO']
        if path == "/":
            return self.GET_index()
        elif path == "/hello":
            return self.GET_hello()
        else:
            return self.notfound()

    def GET_index(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Welcome!\n"

    def GET_hello(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

    def notfound(self):
        status = '404 Not Found'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Not Found\n"

修改code.py中的from application import simple_app as app,用my_app来替换simple_app后即可体验效果。

五、重构

上面的代码虽然奏效,但是在编码风格和灵活性方面有很多问题,下面逐步对其进行重构。

1、正则匹配URL

消除URL硬编码,增加URL调度的灵活性:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re ##########修改点

class my_app:

    urls = (
        ("/", "index"),
        ("/hello/(.*)", "hello"),
    ) ##########修改点

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self): ##########修改点
        path = self.environ['PATH_INFO']
        method = self.environ['REQUEST_METHOD']

        for pattern, name in self.urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() + '_' + name
                if hasattr(self, funcname):
                    func = getattr(self, funcname)
                    return func(*args)

        return self.notfound()

    def GET_index(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Welcome!\n"

    def GET_hello(self, name): ##########修改点
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello %s!\n" % name

    def notfound(self):
        status = '404 Not Found'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Not Found\n"

2、DRY

消除GET_*方法中的重复代码,并且允许它们返回字符串:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re

class my_app:

    urls = (
        ("/", "index"),
        ("/hello/(.*)", "hello"),
    )

    def __init__(self, environ, start_response): ##########修改点
        self.environ = environ
        self.start = start_response
        self.status = '200 OK'
        self._headers = []

    def __iter__(self): ##########修改点
        result = self.delegate()
        self.start(self.status, self._headers)

        # 将返回值result(字符串 或者 字符串列表)转换为迭代对象
        if isinstance(result, basestring):
            return iter([result])
        else:
            return iter(result)

    def delegate(self): ##########修改点
        path = self.environ['PATH_INFO']
        method = self.environ['REQUEST_METHOD']

        for pattern, name in self.urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() + '_' + name
                if hasattr(self, funcname):
                    func = getattr(self, funcname)
                    return func(*args)

        return self.notfound()

    def header(self, name, value): ##########修改点
        self._headers.append((name, value))

    def GET_index(self): ##########修改点
        self.header('Content-type', 'text/plain')
        return "Welcome!\n"

    def GET_hello(self, name): ##########修改点
        self.header('Content-type', 'text/plain')
        return "Hello %s!\n" % name

    def notfound(self): ##########修改点
        self.status = '404 Not Found'
        self.header('Content-type', 'text/plain')
        return "Not Found\n"

3、抽象出框架

为了将类my_app抽象成一个独立的框架,需要作出以下修改:

  • 剥离出其中的具体处理细节:urls配置 和 GET_*方法(改成在多个类中实现相应的GET方法)
  • 把方法header实现为类方法(classmethod),以方便外部作为功能函数调用
  • 改用 具有__call__方法的 实例 来实现application

修改后的application.py(最终版本):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""application.py"""

import re

class my_app:
    """my simple web framework"""

    headers = []

    def __init__(self, urls=(), fvars={}):
        self._urls = urls
        self._fvars = fvars
        self._status = '200 OK'

    def __call__(self, environ, start_response):
        del self.headers[:] # 在每次作出响应前,清空上一次的headers
        result = self._delegate(environ)
        start_response(self._status, self.headers)

        # 将返回值result(字符串 或者 字符串列表)转换为迭代对象
        if isinstance(result, basestring):
            return iter([result])
        else:
            return iter(result)

    def _delegate(self, environ):
        path = environ['PATH_INFO']
        method = environ['REQUEST_METHOD']

        for pattern, name in self._urls:
            m = re.match('^' + pattern + '$', path)
            if m:
                # pass the matched groups as arguments to the function
                args = m.groups()
                funcname = method.upper() # 方法名大写(如GET、POST)
                klass = self._fvars.get(name) # 根据字符串名称查找类对象
                if hasattr(klass, funcname):
                    func = getattr(klass, funcname)
                    return func(klass(), *args)

        return self._notfound()

    def _notfound(self):
        self.status = '404 Not Found'
        self.header('Content-type', 'text/plain')
        return "Not Found\n"

    @classmethod
    def header(cls, name, value):
        cls.headers.append((name, value))

对应修改后的code.py(最终版本):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""code.py"""

from wsgiref.simple_server import make_server
from application import my_app

urls = (
    ("/", "index"),
    ("/hello/(.*)", "hello"),
)

class index:
    def GET(self):
        my_app.header('Content-type', 'text/plain')
        return "Welcome!\n"

class hello:
    def GET(self, name):
        my_app.header('Content-type', 'text/plain')
        return "Hello %s!\n" % name

wsgiapp = my_app(urls, globals())

if __name__ == '__main__':
    httpd = make_server('', 8086, wsgiapp)
    sa = httpd.socket.getsockname()
    print 'http://{0}:{1}/'.format(*sa)

    # Respond to requests until process is killed
    httpd.serve_forever()

当然,您还可以在code.py中配置更多的URL映射,并实现相应的类来对请求作出响应。

六、参考

本文主要参考了 How to write a web framework in Python(作者 anandology 是web.py代码的两位维护者之一,另一位则是大名鼎鼎却英年早逝的 Aaron Swartz),在此基础上作了一些调整和修改,并掺杂了自己的一些想法。

如果您还觉得意犹未尽,Why so many Python web frameworks? 也是一篇很好的文章,也许它会让您对Python中Web框架的敬畏之心荡然无存:-)

[转载]SQL Server跨网段(跨机房)FTP复制 - 听风吹雨 - 博客园

mikel阅读(1404)

[转载]SQL Server跨网段(跨机房)FTP复制 – 听风吹雨 – 博客园.

一、 背景

搭建SQL Server复制的时候,如果网络环境是局域网内,通过主机名就可以实现了,但是如果是跨网段、跨机房异地搭建复制的时候就需要注意了,因为SQL Server复制不支持通过IP连接分发服务器,那有什么办法解决跨网段、跨机房的问题呢?

我在SQL Server跨网段(跨机房)复制已经讲到了两种解决方法,如果想用请求订阅模式,共享快照文件权限的配置比较麻烦,更好更安全的方式是通过FTP形式读取快照文件进行初始化;

 

二、 搭建过程

(一) 环境信息

系统环境:Windows Server 2008 + SQL Server 2008

发布服务器:192.168.1.101,1924,服务器名称:USER-H2B2A89PEK

分发服务器:与发布服务器同一台机器

订阅服务器:192.168.1.102,1433,服务器名称:QuZhoushiwei105

发布数据库:Task

订阅数据库:TaskSubscribe

数据库帐号:ReplicationUser/ ReplicationPassword

FTP地址:ftp://192.168.1.101:9721

FTP帐号密码:ftpuser/ftppassword

 

(二) 搭建步骤

开始下面的步骤之前你需要确认你的FTP地址是可用的,确保使用FTP客户端程序可以读取并下载生成的快照文件;

A. 发布服务器配置

下面是设置发布服务器的具体步骤:

wps_clip_image-2226

(Figure1:选择发布数据库)

wps_clip_image-23413

(Figure2:事务发布)

wps_clip_image-6041

(Figure3:选择对象)

wps_clip_image-16851

(Figure4:初始化订阅)

wps_clip_image-29542

(Figure5:设置帐号密码)

wps_clip_image-10587

(Figure6:发布名称)

wps_clip_image-21790

(Figure7:快照)

下图是设置FTP快照的主界面:

wps_clip_image-8162

(Figure8:设置FTP快照)

wps_clip_image-28832

(Figure9:重新发布)

wps_clip_image-22971

(Figure10:发布的快照文件)

wps_clip_image-281

(Figure11:内网FTP列表)

wps_clip_image-17630

(Figure12:外网FTP列表)

 

B. 订阅服务器配置

创建完发布服务器(分发服务器也一起创建了),接下来就可以创建订阅服务器了,在跨网段的情况下,你可以使用host文件或者别名的方式连接到分发服务器,具体可参考:SQL Server 跨网段(跨机房)复制,下面是订阅服务器设置的具体步骤:

wps_clip_image-26141

(Figure13:查找发布服务器)

wps_clip_image-11818

(Figure14:选择发布)

wps_clip_image-15965

(Figure15:请求订阅)

wps_clip_image-20899

(Figure16:选择订阅数据库)

wps_clip_image-5471

(Figure17:设置帐号密码)

wps_clip_image-13807

(Figure18:同步计划)

wps_clip_image-9122

(Figure19:初始化)

wps_clip_image-29782

(Figure20:创建订阅)

wps_clip_image-23811

(Figure21:订阅之前)

wps_clip_image-23050

(Figure22:订阅后)

 

三、 注意事项

1. Windows Server 2008系统中需要在入站规则中开通21端口才能搭建FTP;

2. FTP有主动和被动之分,所以在设置防火墙的时候需要注意;

 

四、 参考文献

SqlServer数据库同步方案详解(包括跨网段)

如何通过 FTP 传递快照

[转载]MS SQL 统计信息浅析上篇 - 潇湘隐者 - 博客园

mikel阅读(940)

[转载]MS SQL 统计信息浅析上篇 – 潇湘隐者 – 博客园.

统计信息概念

    统计信息是一些对象,这些对象包含在表或索引视图中一列或多列中的数据分布有关的统计信息。数据库查询优化器使用这些统计信息来估计查询结果中的基数或行 数。 通过这些基数估计,查询优化器可以生成高质量的执行计划。 例如,查询优化器可以使用基数估计选择索引查找运算符而不是耗费更多资源的索引扫描运算符,从而提高查询性能。[参考MSDN]

    其实如果你以前没有接触过统计信息,你可以将其看做是数据库为了得到最优的执行计划,统计数据库里面表、索引等对象的一些数据,例如表的记录数、所有列的 平均长度、直方图….等一些优化器需要用到的数据信息。SQL查询优化器是一个基于成本的优化器,类似于ORACLE里面的CBO,那么优化器如果要 得到成本最低的执行计划,就需要收集获取其生成执行计划的参考依据(统计信息数据)

如果你对这些概念性的东西比较模糊的话,那么为了让你形象的认识一下统计信息,请看下图:

clip_image002

 

统计信息参数

     数据库的统计信息相关参数有三个: 自动创建统计信息(Auto Create Statistics)、自动更新统计信息(Auto Update Statistics)、自动异步更新统计信息(Auto Update Statistics Asynchronously),它们都是数据库级别的。

自动创建统计信息(Auto Create Statistics

该参数指定数据库是否自动创建缺少的优化统计信息。如果设置为True, 则将在优化过程中自动生成优化查询需要但缺少的所有统计信息。当开启自动创建统计信息(Auto Create Statistics)选项时,查询优化器会对在谓词中使用到的列,如果这些列的统计信息不可用或缺少时,则会单独对每列创建统计信息。这些统计信息对创 建一个查询计划非常必要。它们创建于那些现有统计对象中不存在直方图的列上,名字包括列名和对象ID的十六进制格 式:_WA_Sys_<column_name>_<XXXX>。这些统计信息用于查询优化器决定使用何种优化后的执行计划。

自动更新统计信息(Auto Update Statistics


该参数指定数据库是否自动更新过期的优化统计信息。如果设置为True,则将在优化过程中自动生成优化查询需要但已过期的所有统计信息。否则不自动更新统计信息。

自动异步更新统计信息(Auto Update Statistics Asynchronously

如果设置为 True,则启动过期统计信息的自动更新,查询在编译前不会等待统计信息被更新。后续查询将使用可用的已更新统计信息。如果设置为 False,则启动过期统计信息的自动更新的查询将等待,直到更新的统计信息可在查询优化计划中使用。将该选项设置为 True 不会产生任何影响,除非自动更新统计信息也设置为 True

关于这三个参数,一般建议开启自动创建统计信息、自动更新统计信息选项,关闭自动异步更新统计信息。

    1:对于自动创建统计信息选项,因为统计信息对查询优化器至关重要,没有统计信息,也就失去了它基于成本的优化器的意义,成为无土之木、无源之水。

    2:对于自动更新统计信息选项,有时候过期的统计信息会导致严重的性能问题,我都碰到过好几起因为过时统计信息导致SQL查询性能问题的案例,就像错误的 地图、错误的导航、错误的指路会让你偏离目的地那样,过时的统计信息往往导致查询优化器选择了次优的执行计划,产生糟糕的性能问题,所以这个参数必须开 启。

    3:对于自动异步更新统计信息选项,这个选项在OLTP环境下很有用,但在数据仓库中有负面影响。至于是否开启,我建议是关闭。默认也是关闭的。不知道这种观念是否正确。

下面是MSDN给予的使用同步更新统计信息\异步更新统计信息的参考意见:

————————————————————————————————————————

在以下情况下应考虑使用同步统计信息:

· 您执行会更改数据分布的操作,例如截断表或对很大百分比的行执行大容量更新。如果您在完成该操作后未更新统计信息,则使用同步统计信息将确保对更改的数据执行查询前统计信息是最新的。

在以下情况下,考虑使用异步统计信息来实现可预测性更高的查询响应时间:

· 您的应用程序频繁执行相同的查询、类似的查询或类似的缓存查询计划。与同步统计信息更新相比,使用异步统计信息更新时您的查询响应时间可能具有更高的可预 测性,因为查询优化器可以执行传入的查询而不必等待最新的统计信息。这避免延迟某些查询,而不延迟其他查询。有关查找类似查询的详细信息,请参阅使用查询和查询计划哈希值查找和优化类似查询

· 您的应用程序遇到了客户端请求超时,这些超时是由于一个或多个查询正在等待更新后的统计信息所导致的。在某些情况下,等待同步统计信息可能会导致应用程序因过长超时而失败。

————————————————————————————————————————

那么先看一下如何通过SQL来查看这三个参数的设置值:

Code Snippet
  1. SELECT  name ,
  2.         is_auto_create_stats_on ,
  3.         is_auto_update_stats_async_on ,
  4.         is_auto_close_on
  5. FROM    sys.databases ;
  6. SELECT  CASE WHEN DATABASEPROPERTYEX(‘DBMonitor’, ‘IsAutoCreateStatistics’) = 1
  7.              THEN ‘Yes’
  8.              ELSE ‘No’
  9.         END AS ‘IsAutoCreateStatistics’ ,
  10.         CASE WHEN DATABASEPROPERTYEX(‘DBMonitor’, ‘IsAutoUpdateStatistics’) = 1
  11.              THEN ‘Yes’
  12.              ELSE ‘No’
  13.         END AS ‘IsAutoUpdateStatistics’ ,
  14.         CASE WHEN DATABASEPROPERTYEX(‘DBMonitor’, ‘Is_Auto_Update_stats_async_on’) = 1
  15.              THEN ‘Yes’
  16.              ELSE ‘No’
  17.         END AS ‘IsAutoUpdateStatsaAyncOn’
  18. GO

那么这三个参数的值保存在哪里呢?其实只要你稍微花一点心思去研究一下,就会发现 其实它是保存在系统表[sys].[sysdbreg]里面,[sys].[sysdbreg]是内部表,默认情况下不可查看,一般你可以通过系统视图 sys.database查看和研究其值的出处。

Code Snippet
  1. SET QUOTED_IDENTIFIER ON
  2. SET ANSI_NULLS ON
  3. GO
  4. CREATE VIEW sys.databases AS
  5.  SELECT d.name, d.id AS database_id,
  6.         r.indepid AS source_database_id,
  7.         d.sid AS owner_sid,
  8.         d.crdate AS create_date,
  9.         d.cmptlevel AS compatibility_level,
  10.         convert(sysname, CollationPropertyFromID(p.cid, ‘name’)) AS collation_name,
  11.         p.user_access, ua.name AS user_access_desc,
  12.         sysconv(bit, d.status & 0x400) AS is_read_only,        — DBR_RDONLY
  13.         sysconv(bit, d.status & 1) AS is_auto_close_on,        — DBR_CLOSE_ON_EXIT
  14.         sysconv(bit, d.status & 0x400000) AS is_auto_shrink_on,— DBR_AUTOSHRINK
  15.         p.state, st.name AS state_desc,
  16.         sysconv(bit, d.status & 0x200000) AS is_in_standby,    — DBR_STANDBY
  17.         sysconv(bit, d.status & 0x40000000) AS is_cleanly_shutdown, — DBR_CLEANLY_SHUTDOWN
  18.         sysconv(bit, d.status & 0x80000000) AS is_supplemental_logging_enabled,— DBR_SUPPLEMENT_LOG
  19.         p.snapshot_isolation_state, si.name AS snapshot_isolation_state_desc,
  20.         sysconv(bit, d.status & 0x800000) AS is_read_committed_snapshot_on,— DBR_READCOMMITTED_SNAPSHOT
  21.         p.recovery_model, ro.name AS recovery_model_desc,
  22.         p.page_verify_option, pv.name AS page_verify_option_desc,
  23.         sysconv(bit, d.status2 & 0x1000000) AS is_auto_create_stats_on,        — DBR_AUTOCRTSTATS
  24.         sysconv(bit, d.status2 & 0x40000000) AS is_auto_update_stats_on,       — DBR_AUTOUPDSTATS
  25.         sysconv(bit, d.status2 & 0x80000000) AS is_auto_update_stats_async_on, — DBR_AUTOUPDSTATSASYNC
  26.         sysconv(bit, d.status2 & 0x4000) AS is_ansi_null_default_on,           — DBR_ANSINULLDFLT
  27.         sysconv(bit, d.status2 & 0x4000000) AS is_ansi_nulls_on,               — DBR_ANSINULLS
  28.         sysconv(bit, d.status2 & 0x2000) AS is_ansi_padding_on,                — DBR_ANSIPADDING
  29.         sysconv(bit, d.status2 & 0x10000000) AS is_ansi_warnings_on,           — DBR_ANSIWARNINGS
  30.         sysconv(bit, d.status2 & 0x1000) AS is_arithabort_on,                  — DBR_ARITHABORT
  31.         sysconv(bit, d.status2 & 0x10000) AS is_concat_null_yields_null_on,    — DBR_CATNULL
  32.         sysconv(bit, d.status2 & 0x800) AS is_numeric_roundabort_on,           — DBR_NUMEABORT
  33.         sysconv(bit, d.status2 & 0x800000) AS is_quoted_identifier_on,         — DBR_QUOTEDIDENT
  34.         sysconv(bit, d.status2 & 0x20000) AS is_recursive_triggers_on,         — DBR_RECURTRIG
  35.         sysconv(bit, d.status2 & 0x2000000) AS is_cursor_close_on_commit_on,   — DBR_CURSCLOSEONCOM
  36.         sysconv(bit, d.status2 & 0x100000) AS is_local_cursor_default,         — DBR_DEFLOCALCURS
  37.         sysconv(bit, d.status2 & 0x20000000) AS is_fulltext_enabled,           — DBR_FTENABLED
  38.         sysconv(bit, d.status2 & 0x200) AS is_trustworthy_on,                  — DBR_TRUSTWORTHY
  39.         sysconv(bit, d.status2 & 0x400) AS is_db_chaining_on,                  — DBR_DBCHAINING
  40.         sysconv(bit, d.status2 & 0x08000000) AS is_parameterization_forced,    — DBR_UNIVERSALAUTOPARAM
  41.         sysconv(bit, d.status2 & 64) AS is_master_key_encrypted_by_server,     — DBR_MASTKEY
  42.         sysconv(bit, d.category & 1) AS is_published,
  43.         sysconv(bit, d.category & 2) AS is_subscribed,
  44.         sysconv(bit, d.category & 4) AS is_merge_published,
  45.         sysconv(bit, d.category & 16) AS is_distributor,
  46.         sysconv(bit, d.category & 32) AS is_sync_with_backup,
  47.         d.svcbrkrguid AS service_broker_guid,
  48.         sysconv(bit, case when d.scope = 0 then 1 else 0 end) AS is_broker_enabled,
  49.         p.log_reuse_wait, lr.name AS log_reuse_wait_desc,
  50.         sysconv(bit, d.status2 & 4) AS is_date_correlation_on,         — DBR_DATECORRELATIONOPT
  51.         sysconv(bit, d.category & 64) AS is_cdc_enabled,
  52.         sysconv(bit, d.status2 & 0x100) AS is_encrypted,               — DBR_ENCRYPTION
  53.         sysconv(bit, d.status2 & 0x8) AS is_honor_broker_priority_on   — DBR_HONORBRKPRI
  54.     FROM master.sys.sysdbreg d OUTER APPLY OpenRowset(TABLE DBPROP, d.id) p
  55.     LEFT JOIN sys.syssingleobjrefs r ON r.depid = d.id AND r.class = 96 AND r.depsubid = 0— SRC_VIEWPOINTDB
  56.     LEFT JOIN sys.syspalvalues st ON st.class = ‘DBST’ AND st.value = p.state
  57.     LEFT JOIN sys.syspalvalues ua ON ua.class = ‘DBUA’ AND ua.value = p.user_access
  58.     LEFT JOIN sys.syspalvalues si ON si.class = ‘DBSI’ AND si.value = p.snapshot_isolation_state
  59.     LEFT JOIN sys.syspalvalues ro ON ro.class = ‘DBRO’ AND ro.value = p.recovery_model
  60.     LEFT JOIN sys.syspalvalues pv ON pv.class = ‘DBPV’ AND pv.value = p.page_verify_option
  61.     LEFT JOIN sys.syspalvalues lr ON lr.class = ‘LRWT’ AND lr.value = p.log_reuse_wait
  62.     WHERE d.id < 0x7fff
  63.         AND has_access(‘DB’, d.id) = 1
  64. GO
  65. CREATE TABLE [sys].[sysdbreg]
  66.     (
  67.       [id] [int] NOT NULL ,
  68.       [name] [sys].[sysname] NOT NULL ,
  69.       [sid] [varbinary](85) NULL ,
  70.       [status] [int] NOT NULL ,
  71.       [status2] [int] NOT NULL ,
  72.       [category] [int] NOT NULL ,
  73.       [crdate] [datetime] NOT NULL ,
  74.       [modified] [datetime] NOT NULL ,
  75.       [svcbrkrguid] [uniqueidentifier] NOT NULL ,
  76.       [scope] [int] NOT NULL ,
  77.       [cmptlevel] [tinyint] NOT NULL
  78.     )
  79.   ON[PRIMARY]
  80. GO

修改统计信息参数

方法1

—-关闭数据库DBMonitor自动创建统计信息功能

USE [master]

GO

ALTER DATABASE [DBMonitor] SET AUTO_CREATE_STATISTICS OFF WITH NO_WAIT

GO

–开启数据库DBMonitor自动创建统计信息功能

USE [master]

GO

ALTER DATABASE [DBMonitor] SET AUTO_CREATE_STATISTICS ON WITH NO_WAIT

GO

–关闭数据库DBMonitor自动更新统计信息功能

USE [master]

GO

ALTER DATABASE [DBMonitor] SET AUTO_UPDATE_STATISTICS OFF WITH NO_WAIT

GO

–启用数据库DBMonitor自动更新统计信息功能

USE [master]

GO

ALTER DATABASE [DBMonitor] SET AUTO_UPDATE_STATISTICS ON WITH NO_WAIT

GO

–关闭数据库DBMonitor自动异步更新统计信息功能

ALTER DATABASE [DBMonitor] SET AUTO_UPDATE_STATISTICS_ASYNC OFF WITH NO_WAIT

GO

 

–启用数据库DBMonitor自动异步更新统计信息功能

ALTER DATABASE [DBMonitor] SET AUTO_UPDATE_STATISTICS_ASYNC ON WITH NO_WAIT

GO

方法2:使用SP_DBOPTION来启用或禁用。

 

SP_DBOPTION DBMonitor, ‘auto update statistics’, ‘ON’;

SP_DBOPTION DBMonitor, ‘auto update statistics’, ‘OFF;

 

方法3:图形化方法启用或禁用

对应的图像化操作:选择所要修改的数据库,单击右键选项”属性“,选择左侧的”选项“,则能看到这三个参数

clip_image004

在 AUTO_UPDATE_STATISTICS 为 ON 时,您可以覆盖数据库范围的统计信息更新行为,并且根据您的应用程序的要求为单独的表、索引或列将自动统计信息更新设为关闭。在 AUTO_UPDATE_STATISTICS 为 ON 时,您可以通过以下方式为表、索引或列禁用和重新启用自动统计信息更新:

· 使用 SP_AUTOSTATS 系统存储过程。这可以禁用或重新启用表或索引的统计信息更新。

· 对于 UPDATE STATISTICS 语句指定 NORECOMPUTE 选项。若要重新启用统计信息更新,请重新运行 UPDATE STATISTICS,但不使用

· NORECOMPUTE 选项。

· 对于 CREATE STATISTICS 语句指定 NORECOMPUTE 选项。若要重新启用统计信息更新,请使用 DROP STATISTICS 删除统计信息,然后运行 CREATE STATISTICS 但不使用 NORECOMPUTE 选项。

· 对 CREATE INDEX 语句指定 STATISTICS_NORECOMPUTE 选项。若要重新启用统计信息更新,您可以运行 ALTER INDEX 且 STATISTICS_NORECOMPUTE = OFF。

· 在 AUTO_UPDATE_STATISTICS 为 OFF 时,不能为单独的表、索引或列将自动更新设为打开。重新启用自动统计信息更新将还原AUTO_UPDATE_STATISTICS 选项指定的行为。如果 AUTO_UPDATE_STATISTICS 选项为OFF,统计信息更新将不会发生。

 

创建统计信息

如何创建统计信息呢,如果选项自动创建统计信息(Auto Create Statistics)开启了,那么数据库会自动创建某些统计信息,另外你也可以通过CREATE STATISTICS 语句手动创建某些统计信息。先看下面的例子:

USE [DBMonitor]

GO

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Test]’) AND type in (N’U’))

DROP TABLE [dbo].[Test]

GO

SELECT * INTO Test FROM sys.objects;

此时你会看到表Test根本没有统计信息,那么我们在表Test上创建一个索引IDX_TEST_OBJECT_ID

CREATE INDEX IDX_TEST_OBJECT_ID ON dbo.Test(object_id);

此时,你刷新一下表Test的统计信息,你会发现多了一个名为“IDX_TEST_OBJECT_ID”的统计信息,你新建多少条索引,在统计信息下就会创建与索引同名的统计信息。

SELECT DISTINCT TYPE FROM dbo.Test ;

执行上面面脚本后,我刷新统计信息,会发现多了一个名为“_WA_Sys_00000006_023D5A04”的统计信息,截图如下:

clip_image006

那么自动创建统计信息的规律或规则是啥呢?或者理解为:什么时候数据库创建统计信息,其实创建统计信息的规则如下:

    1:在索引创建时,SQL SERVER 会自动地在索引所在的列上创建统计信息

    2:当SQL SERVER想要使用某些列上的统计信息,发现没有时,SQL SERVER会自动创建统计信息(前提是要开启自动创建统计信息)。例如上面统计DISTINCT TYPE。

    3:手工使用CREATE STATISTICS之类的语句手工创建需要的统计信息。

关于CREATE STATISTICS的语法,大家可以参考MSDN,这里不做阐述了。需要注意的是统计信息可以通过全表扫描或随机抽样应读取的数据百分比或指定的数据行数,收集统计信息

更新统计信息

随着数据库的DML操作,数据的变更会导致统计信息过期,那么这时就需要更新统计信息。通常数据库通过两种方式更新统计信息:

    1:如果开启了自动更新统计信息(Auto Update Statistics)或自动异步更新统计信息选项,那么数据库会自动更新统计信息。

    2:手动更新统计信息

更新统计信息确保查询使用最新的统计信息编译。不过,更新统计信息会导致查询重新编译。我们建议不要太频繁地更新统计信息,因为需要在改进查询计划和重新编译查询所用时间之间权衡性能。此类特定的性能权衡取决于您的应用程序。

那么数据库什么时候、什么条件下才会更新统计信息呢?

1、 在一个空表中有数据的改动。

2、 当统计信息创建时,表的行数只有500或以下,且后来统计对象中的引导列的更改次数大于500.

3、 当表的统计信息收集时,超过了500行,且统计对象的引导列后来更改次数超过500+表总行数的20%时。

4、 在Tempdb中的表,少于6行且最少有6行被更改

注意:数据库不会为表变量收集统计信息,所以数据量比较大时尽量不要使用表变量。

一般建议不要太频繁地更新统计信息,因为需要在改进查询计划和重新编译查询所用时间之间权衡性能。 这种特定的性能权衡取决于您的应用程序。

手工更新统计信息,一般使用 UPDATE STATISTICS 或存储过程 sp_updatestats 来更新统计信息。

sp_autostats:显示或更改特定索引或统计信息的自动 UPDATE STATISTICS 设置,或者显示和更改当前数据库中指定表或索引视图的所有索引和统计信息的自动 UPDATE STATISTICS 设置

  显示表的所有索引的当前状态

    USE DBMonitor;

    GO

    EXEC sp_autostats ‘dbo.test’;

  启用表的所有索引的自动统计信息

    USE DBMonitor;

    GO

    EXEC sp_autostats ‘dbo.test’,’ON’

  禁用特定索引的自动统计信息

    USE DBMonitor;

    GO

    EXEC sp_autostats ‘dbo.test’,’OFF’, ‘IDX_TEST_OBJECT_ID’;

 

查看统计信息

如果要了解具体的统计信息内容,那么我们首先要知道如何查看具体的统计信息,统计信息保存在那些系统视图里面,如果能很好的回答这两个问题,那么我想你也就能知道统计信息的具体内容是那些了。关于第二个问题,后面章节部分再做探讨。

查看统计信息,我们先由浅入深,由简单到复杂。

sp_helpstats :返回指定表中列和索引的统计信息。

USE DBMonitor;

GO

EXEC sp_helpstats ‘test’ ,’ALL’

下面我们看看sys.sp_helpstats的脚本

View Code

 

2:查看统计信息一般用DBCC SHOW_STATISTICS命令,如下所示

复制代码
DBCC SHOW_STATISTICS('TEST', 'IDX_TEST_OBJECT_ID'); 

Name    Updated     Rows   Rows Sampled  Steps  Density     Average key length String Index Filter Expression           Unfiltered Rows
------------------------------------------------- ------------------
IDX_TEST_OBJECT_ID   09 24 2013  9:06PM   59                   59                   41     1             4                  NO           NULL   59

(1 行受影响)

All density   Average Length Columns
------------- -------------- ---------------------------------------
0.01694915    4              object_id

(1 行受影响)

RANGE_HI_KEY RANGE_ROWS    EQ_ROWS       DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS
------------ ------------- ------------- -------------------- --------------
          0             1             0                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          1             1             1                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          2             1             2                    1
          2             1             2                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1
          1             1             1                    1
          1             1             1                    1
          1             1             1                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          1             1             1                    1
          0             1             0                    1
          1             1             1                    1
          1             1             1                    1
          1             1             1                    1
          0             1             0                    1
          0             1             0                    1
          0             1             0                    1

(41 行受影响)

DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。
复制代码

 

虽然查看统计信息很容易,但是要读懂并能读取一些信息那们就不是那么简单的了。

列名 描述说明
Name 统计信息对象名称
Update 上一次更新统计信息的日期和时间
Rows 在目标索引、统计信息或列中的总行数。如果筛选索引或统计信息,此行数可能小于表的行数。
Rows Sampled 用于统计信息计算的抽样总行数。
Steps 统计信息对象第一个键列的直方图中的值范围数。每个步骤包括在直方图结果中定义的 RANGE_ROWS 和 EQ_ROWS。
Density 查询优化器不使用此值。显示此值的唯一目的是为了向后兼容。密度的计算公式为 1 / distinct rows,其中 distinct rows 是直方图输出中所有步骤的 DISTINCT_RANGE_ROWS 之和。如果对行进行抽样,distinct rows 则基于抽样行的直方图值。
Average Key Length 统计信息对象的键列中,所有抽样值中的每个值的平均字节数
String Index 如果为“是”,则统计信息中包含字符串摘要索引,以支持为 LIKE 条件估算结果集大小。仅当第一个键列的数据类型为charvarcharncharnvarcharvarchar(max)nvarchar(max)textntext 时,才会对此键列创建字符串索引。
Filter Expression 包含在统计信息对象中的表行子集的表达式。NULL = 未筛选的统计信息。有关详细信息,请参阅筛选统计信息。
Unfiltered Rows 应用筛选器表达式前表中的总行数。如果 Filter Expression 为 NULL,Unfiltered Rows 等于行标题值。

下表对指定 DENSITY_VECTOR 时结果集中所返回的列进行了说明。

列名 说明
All Density 针对统计信息对象中的列的每个前缀计算密度(1/ distinct_rows)。 密度包含所有抽样行中的非重复行,包括带有直方图边界点的行。结果为每个密度显示一行。例如,如果统计信息对象包含键列 (A, B, C),结果将报告 (A)、(A,B) 以及 (A, B, C) 的密度。非重复行具有一个不同的列值向量。对于列 (A,B,C),两个不同的向量值的示例为 (4,5,6) 和 (4,5,7)。对于 (A,B),相同的两行具有一个不同的向量值 (4,5)。对于 (A),存在一个不同的值 (4)。
Average Length 每个列前缀的列值向量的平均长度(按字节计)。例如,如果列前缀为列 A 和 B,则长度为列 A 和列 B 的字节之和。
Columns 为其显示 All densityAverage length 的前缀中的列的名称。

下表对指定 HISTOGRAM 选项时结果集中所返回的列进行了说明。

列名

说明
RANGE_HI_KEY 直方图步骤的上限值。
RANGE_ROWS 表中位于直方图步骤内(不包括上限)的行的估算数目。
EQ_ROWS 表中值与直方图步骤的上限值相等的行的估算数目。
DISTINCT_RANGE_ROWS 直方图步骤内(不包括上限)非重复值的估算数目。
AVG_RANGE_ROWS 直方图步骤内(不包括上限)重复值的频率或平均数目(如果 DISTINCT_RANGE_ROWS > 0,则为 RANGE_ROWS / DISTINCT_RANGE_ROWS)。

统计直方图用作在查询执行计划中查询优化器的选择依据

图形化查看

image

删除统计信息

DROP STATISTICS 删除当前数据库的指定表中的多个集合的统计信息。

    DROP STATISTICS Test._WA_Sys_00000006_023D5A04;

需要注意的是不能用DROP STATISTICS 删除有关索引的统计信息。统计信息的保留时间与索引存在的时间相同,当你删除索引时,对应的统计信息也自动删除。如下所示:

DROP STATISTICS Test.IDX_TEST_OBJECT_ID

消息 3739,级别 11,状态 1,第 1 行

无法对索引 ‘Test.IDX_TEST_OBJECT_ID’ 执行 DROP,因为该索引不是统计信息集合。

 

参考资料:

  http://technet.microsoft.com/zh-cn/library/ms190397(v=sql.105).aspx

http://technet.microsoft.com/zh-cn/library/ms187348.aspx
http://blog.csdn.net/dba_huangzj/article/details/8041267
http://blog.csdn.net/zhaowenzhong/article/details/6276878

[转载]微信公众平台Bee.WeiXin开发介绍 - 蜂 - 博客园

mikel阅读(968)

[转载]微信公众平台Bee.WeiXin开发介绍 – 蜂 – 博客园.

我们来看一下如何通过Bee.WeiXin开发微信公众平台。关于微信公众平台的一般性介绍, 这里不做展开。 园里找一找就可以了。 本文主要是介绍Bee.WeXin, 代码已发布到https://beeweixin.codeplex.com/上了。

BeeWeiXin 是针对腾讯的微信公众平台开发一个开发框架。  该项目是基于Bee OPOA Platform 开发的。

包含了以下功能:

1. 基于调用树的微信答复模型。 调用树支持多级(有上下文)响应,  可以通过 文本, 图文, 自定义三种方式响应。 其中自定义可以回复微信公众平台API所提供的三个方式(文本, 图文, 音乐)。

2. 提供了同步微信关注用户列表的功能。

3. 提供了菜单管理功能。

4. 提供了图文管理功能。

5. 原生集成Bee OPOA Platform上的所有功能, 主要是权限管理。

关于调试, 推荐采用园友提供的工具 微信公众帐号开发调试工具发布

预览

配置项说明

配置项均在web.config中【appSettings】配置.

【WeiXinToken】就是公众平台API方式的Token;

Debug】是指当前是否处于调试模式。 调试模式与非调试模式的区别就在于是否对发起的调用进行验证。

【WeiXinUserName】与【WeiXinPassword】 是菜单【微信关注用户】中【重新同步所有用户】所需要的, 将模拟网页登入到微信公众平台中, 然后获取用户列表信息。 若不需要, 可以忽略, 也可以隐藏掉该菜单。

【WeiXinAppId】与【WeiXinAppSec】是菜单【微信菜单管理】中所需要的, 可以管理微信公众账号的菜单。 当然该工具【微信公众帐号开发调试工具发布 】也有此功能, 比较方便。 若不需要, 可以忽略, 也可以隐藏掉该菜单。

【WeiXinController】是默认的系统响应Controller, 具体逻辑请参看代码。

调用链方式的应答

调用链是基于上下文的, 在配置项中有个选项【是否进入调用链】若是的话, 则将此次应答加入调用链中, 以形成上下文。 如【菜单1】是要进入调用链的, 不然该项下面的子项将无法响应。

先看以下图片:

由预览图中, 可以直观的了解调用链可以提供 文本、图文、自定义三种响应。

文本就是一般性的文本应答。 如在Bee.WeiXinDemo项目中已配置了【菜单1】是以文本响应, 节点值是V1001_M1_1。

回复1,test1
回复2,test2
回复3,test3

回复得到如下:

图文就是微信公众平台API中提供的图文信息, 在调用链配置中只要按照顺序输入指定的文章号【该号码是微信文章管理中的Id】就可以了(以逗号分隔)。

如【菜单1】下的test2是图文格式。 进入test2需要先触发菜单1, 然后再触发test2. 如下图:

自定义是融合到Bee OPOA Platform框架中MVC实现的。 所以配置的也是通过Controller, Action来实现。

如【菜单1】下的test3是自定义方式。进入test3需要先触发菜单1, 然后再触发test3. 如下图:

 

不知道各位了解了没有? 建议大家下载源码, 调试下以加深理解, 最好是有空看看代码, 以帮助完善该项目, 呵呵。

【菜单1】下面只有键值【1】,【2】,【3】, 若用户输入其他怎么响应呢?这个时候只要配置键值【*】, 作为其他的配置项应答。

理解调用链上下文

在这种基于调用链的应答中, 上下文是必要的。 怎么理解呢?举例说明吧:我要查询某个产品的某个国家的最近3个月的销售情况。 由于微信平台的特性, 我们不可能像其他应用给用户以直观的查询。

我们只有采用引导式的方式去引导用户得到她想要的结果。 设计的菜单如预览图中【多级菜单】项所示。具体的配置项如下图:

当用户点击菜单按钮触发了【V1001_M1_2】的事件, 系统响应提示文本【上图的1】; 用户根据提示, 选择产品, 输入1, 系统提示输入国家【上图的2】。 由于国家太多, 不太可能配置所有国家的选项, 所有采用了一个通配符的方案【上图的3】, 并采用自定义的方式应答。  代码如下:

复制代码
public WeiXinTextResult CheckCountry()
        {
            string content = ViewData.TryGetValue<string>("content", string.Empty);

            // 验证输入的国家是否合法
            bool validFlag = content.IndexOf("CN") >= 0;

            if (validFlag)
            {
                return WeiXinText("1 最近3个月销售统计\r\n2 最近6个月销售统计");
            }
            else
            {
                // 由于调用链有上下文, 用户输入错误, 需要将当前应答取消。Current.MessageStack.Pop();
                Current.MessageStack.Pop();
                return WeiXinText("国家不合法,请重新输入国家。");
            }
        }
复制代码

由以上代码可以知道上下文可以通过Current.MessageStack获取。

用户输入正确的国家代码, 则引导用户做下一个选择.

自定义MVC响应

这个基本上跟Bee OPOA Platform的方式一模一样。 如【菜单1】下的【Other】项的响应就是采用该方式的。代码如下:

public ActionResult Other()
        {
            return View();
        }

View代码如下:

复制代码
<%@ Page Language="C#" AutoEventWireup="false" Inherits="Bee.Web.BeePageView" %>

<%@ Import Namespace="Bee.Web" %>
<%@ Import Namespace="Bee" %>
<%@ Import Namespace="System.Collections.Generic" %>
<xml>
 <ToUserName><![CDATA[<%=ViewData["FromUserName"] %>]]></ToUserName>
 <FromUserName><![CDATA[<%=ViewData["ToUserName"] %>]]></FromUserName>
 <CreateTime><%=Bee.WeiXin.DateTimeUtil.GetWeixinDateTime(DateTime.Now) %></CreateTime>
 <MsgType><![CDATA]></MsgType>
 <Content>回复1 联系方式 
 回复2 在线咨询</Content>
</xml>
复制代码

框架基类 Bee.WeiXin.Controller.WeiXinControllerBase提供了微信公众平台API所对应的三种响应的ActionResult, 具体请查看源码.

总结

着重解释了调用链的方式。 其他的功能就不说明了, 下载源码, 很容易知道他的用途。 Enjoy!

[转载]可在广域网部署运行的QQ高仿版 -- GG叽叽V2.0,增加网盘和远程磁盘功能(源码) - GG叽叽 - 博客园

mikel阅读(922)

[转载]可在广域网部署运行的QQ高仿版 — GG叽叽V2.0,增加网盘和远程磁盘功能(源码) – GG叽叽 – 博客园.

尽力2~3周发布一个版本,我这次也没有失言。这段时间内,我仿照QQ的微云功能,在GG中增加了网盘的功能,而且,我还自创了一个QQ没有的新的功能:远程磁盘。正如远程桌面一样,远程磁盘允许我们像访问本地磁盘一样来访问在线的其它用户的磁盘。

一.GG V2.0 新增功能展现

(1)网盘:在服务端为每个用户分配一个网盘,用户通过客户端可以访问自己的网盘。就像QQ的微云一样。

(2)远程磁盘:任何一个在线用户,都可以访问其它在线用户的磁盘(先要经过对方的同意)。

(3)无论是网盘、还是远程磁盘,都支持:上传/下载/删除/复制/剪切/粘贴 文件、新建文件夹、重命名。

(4)在GG的实现中,网盘和远程磁盘公用的是同一个组件NDiskBrowser。

废话不多说,还是先上图。新功能入口按钮:

上图中有三处标记:1.按钮可进入 我的网盘;2.按钮可进入 好友的远程磁盘;3.显示和好友之间的P2P通道的状态

下图是网盘截图:

磁盘访问请求截图:

进入远程磁盘(就像打开自己本地硬盘一样):

远程磁盘操作:

 

二.实现思路

虽然提供了源代码,但是,我还是想将主要的思路列一下,这样,大家理解起源码来,会节省更多的时间。

1. 该版本增加了一个新的项目GGLib。

我打算将GGLib作为一个可复用的类库,这样它就可以在GG以外其它的项目中使用。目前,GGLib已经包含了修改后的文件传输显示控件FileTransferingViewer以及网盘组件。

2. 网盘组件:位于GGLib项目的NDisk文件夹下,虽然网盘组件的底层仍然基于ESFramework提供的文件传输功能,但是,其实现还是比较复杂的。

(1)通过网盘组件,既可以访问网盘,也可以访问另一个在线用户的磁盘。

(2)目前版本的GG服务端提供的是一个简单的网盘功能,其在运行目录下的NetworkDisk文件夹下,为需要的用户新建一个文件夹(以UserID命名)作为用户的网盘。

(3)如果需要,我们可以重新实现GGLib.NDisk.Server.INDiskPathManager接口,比如,我们可以使用分布式文件系统,作为网盘的后端存储。

(4)普通文件传送和网盘文件传送,都使用了ESFramework的文件传送功能,在程序中,我是通过TransferingProject的Comment属性来区分二者的(普通文件的Comment为null,网盘则不为null)。

(5)普通文件和网盘文件,也都使用了FileTransferingViewer来显示文件传送进度,我改写了原始的FileTransferingViewer,为其Initialize增加了一个filter参数,使其可以过滤掉无关的文件传送事件。

(6)进入远程磁盘时,默认进入的是“我的电脑”的内容 — 列出所有分区、包括光盘、U盘等,我们可以通过修改程序来改变这一行为,比如,磁盘的主人可以控制自己只允许来访者访问哪一个分区或目录。

(7)基于(6),换个方向,我们就可以实现磁盘共享或目录共享的功能。

3. 数据库:有很多朋友问数据库怎么弄,实际上GG的目前版本还没有用到数据库(后续高级版本会增加),所有的信息都只是在内存中,所以,目前版本的GG做了一些假设:

(1)用户登录帐号随意,但必须为数字组;密码可随意输入。

(2)所有的在线用户都是好友。

4. 语音视频:也有很多朋友问语音视频设备的工作怎么不正常,这个可以直接参考OMCS官方文档:摄像头、麦克风、扬声器设备测试

5. GG使用了最新版本的SkinForm,如果有关于SkinForm的问题,可以直接联系我的好友 威廉乔克斯_汀

 

三.新的想法

有了远程磁盘点子之后,我又产生了一个新的点子,这个新点子的孕育过程是这样的:

我经常晚上在家加班,所以,下班的时候,我就用U盘把项目文档拷回去,在家里的电脑上工作,完毕后,再把更新的文档拷回U盘,第二天再带到公司覆盖 公司电脑上旧的文档。相信有些朋友也有类似的经历,用U盘拷来拷去很麻烦。当然,如果不是保密性的要求,我们也可以使用网盘。无论是U盘还是网盘,都需要 经过“中间站”转一道,不够直接。

如果下面的情形能够实现该多好:下班的时候,我在公司的电脑上能够直接访问家里电脑的磁盘,把需要的文档传送到指定的目录,晚上回去就可以继续工作,完毕后,在家里的电脑上又可以直接访问公司电脑的磁盘,把更新后的文档再传送回去。这样就够直接了,也不需要U盘了。

我想在GG中增加这样的功能,那么,该如何实现了?我 的初步方案如下:增加一个新的项目叫GG受控端,受控端不需要主界面,只要一个托盘,表示其在运行。在家里的电脑上,我用帐号514330登录GG受控 端,到公司后,用514330正常登录GG,在GG的主界面上,就可以列出所有在线的受控端,点击其中一个,就可以访问其磁盘,这就是已有的GG的远程磁 盘的功能了。当前,前提是受控端电脑必需是开启的并且是联网的。

关于这个功能,大家有什么好的想法,可以留言告诉我。

 

四.GG V2.0 源码下载

GG V2.0 源码

 

特别说明:为了减少压缩包的大小(博客园最大上传文件只能为10M,这个限制有点过了),我把Debug目录清空了,大家重新编译生产后,请将Dlls文件夹下的三个dll(VideoEngine.dll,VideoEngineCore.dll,AudioEngineCore.dll)拷贝到运行那个目录下,才可正常启动程序的。

 

我会继续努力争取2~3个星期发布一个新版本,使GG慢慢成熟起来。

大家有什么问题和建议,可以留言,也可以发送email到我邮箱:ggim2013@163.com。

如果你觉得还不错,请粉我,顺便再顶一下啊,呵呵

转载Java中toArray的用法探究java数组与list转换 - 杜红光 - 博客园

mikel阅读(989)

转载(转)Java中toArray的用法探究java数组与list转换 – 杜红光 – 博客园.

一.             Incident

import java.util.ArrayList;

import java.util.List;

 

public class Test {

 

public static void main(String[] args) {

List<String> list = new ArrayList<String>();

list.add(“1”);

list.add(“2”);

String[] tt =(String[]) list.toArray(new String[0]);

 

}

 

}

 

这段代码是没问题的,但我们看到String[] tt =(String[]) list.toArray(new String[0]) 中的参数很奇怪,然而去掉这个参数new String[0]却在运行时报错。。。

 

二.             Root Cause Analysis

经研究发现toArray有两个方法:

public Object[] toArray() {

Object[] result = new Object[size];

System.arraycopy(elementData, 0, result, 0, size);

return result;

}

不带参数的toArray方法,是构造的一个Object数组,然后进行数据拷贝,此时进行转型就会产生ClassCastException,这也就是上述问题的root cause了。

 

 

public Object[] toArray(Object a[]) {

if (a.length < size)

a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);       System.arraycopy(elementData, 0, a, 0, size);

if (a.length > size)

a[size] = null;

return a;

}

而带参数的toArray方法,则是根据参数数组的类型,构造了一个对应类型的,长度跟ArrayList的size一致的空数组,虽然方法本身还 是以 Object数组的形式返回结果,不过由于构造数组使用的ComponentType跟需要转型的ComponentType一致,就不会产生转型 异常。

 

三.             Solutions

因此在使用toArray的时候可以参考以下三种方式

1. Long[] l = new Long[<total size>];

list.toArray(l);

 

2. Long[] l = (Long[]) list.toArray(new Long[0]);

 

3. Long[] a = new Long[<total size>];

Long[] l = (Long[]) list.toArray(a);

 

四.Further Consideration

该容器中的元素已经用泛型限制了,那里面的元素就应该被当作泛型类型的来看了,然而在目前的java中却不是的,当直接String[] tt =(String[]) list.toArray()时,运行报错。回想一下,应该是java中的强制类型转换只是针对单个对象的,想要偷懒,将整个数组转换成另外一种类型的数 组是不行的,这和数组初始化时需要一个个来也是类似的。

 

以上From:    http://hi.baidu.com/%B4%CB%D6%D0%D3%D0%D5%E6%B5%C0/blog/item/8add93ec812dc9deb31cb1b0.html

 

[转载]Android高效加载大图、多图解决方案,有效避免程序OOM - guolin的专栏 - 博客频道 - CSDN.NET

mikel阅读(820)

[转载]Android高效加载大图、多图解决方案,有效避免程序OOM – guolin的专栏 – 博客频道 – CSDN.NET.

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9316683

 

本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文。

 

http://developer.android.com/training/displaying-bitmaps/index.html

 

高效加载大图片

我们在编写Android程序的时候经常要用到许多图片,不同图片总是会有不同的形状、不同的大小,但在大多数情况下,这些图片都会大于我们程序所需要的 大小。比如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。大家应该知道,我们编写的应用程序都 是有一定内存限制的,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常。我们可以通过下面的代码看出每个应用程序最高可用内存是多 少。

  1. int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
  2. Log.d(“TAG”“Max memory is “ + maxMemory + “KB”);  

因 此在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的 图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。下面我们就来看一看,如何对一张大图片进行适当的压 缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方 法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的 bitmap分配内存,这时就会很容易导致OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参 数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是 null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和 outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所 示:

 

  1. BitmapFactory.Options options = new BitmapFactory.Options();  
  2. options.inJustDecodeBounds = true;  
  3. BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
  4. int imageHeight = options.outHeight;  
  5. int imageWidth = options.outWidth;  
  6. String imageType = options.outMimeType;  

为了避免OOM异常,最好在解析每张图片的时候都先检查一下图片的大小,除非你非常信任图片的来源,保证这些图片都不会超出你程序的可用内存。

现在图片的大小已经知道了,我们就可以决定是把整张图片加载到内存中还是加载一个压缩版的图片到内存中。以下几个因素是我们需要考虑的:

 

  • 预估一下加载整张图片所需占用的内存。
  • 为了加载这一张图片你所愿意提供多少内存。
  • 用于展示这张图片的控件的实际大小。
  • 当前设备的屏幕尺寸和分辨率。

 

比如,你的ImageView只有128*96像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的。

 

那我们怎样才能对图片进行压缩呢?通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张 2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。原本加载这张图片需要占用13M的 内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型,即每个像素点占用4个字节)。下面的方法可以根据传入的宽和高,计算出合适的 inSampleSize值:

 

  1. public static int calculateInSampleSize(BitmapFactory.Options options,  
  2.         int reqWidth, int reqHeight) {  
  3.     // 源图片的高度和宽度  
  4.     final int height = options.outHeight;  
  5.     final int width = options.outWidth;  
  6.     int inSampleSize = 1;  
  7.     if (height > reqHeight || width > reqWidth) {  
  8.         // 计算出实际宽高和目标宽高的比率  
  9.         final int heightRatio = Math.round((float) height / (float) reqHeight);  
  10.         final int widthRatio = Math.round((float) width / (float) reqWidth);  
  11.         // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高  
  12.         // 一定都会大于等于目标的宽和高。  
  13.         inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
  14.     }  
  15.     return inSampleSize;  
  16. }  

使 用这个方法,首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将 BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的 inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为 false,就可以得到压缩后的图片了。

  1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
  2.         int reqWidth, int reqHeight) {  
  3.     // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小  
  4.     final BitmapFactory.Options options = new BitmapFactory.Options();  
  5.     options.inJustDecodeBounds = true;  
  6.     BitmapFactory.decodeResource(res, resId, options);  
  7.     // 调用上面定义的方法计算inSampleSize值  
  8.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  9.     // 使用获取到的inSampleSize值再次解析图片  
  10.     options.inJustDecodeBounds = false;  
  11.     return BitmapFactory.decodeResource(res, resId, options);  
  12. }  

下面的代码非常简单地将任意一张图片压缩成100*100的缩略图,并在ImageView上展示。

  1. mImageView.setImageBitmap(  
  2.     decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100100));  

 

使用图片缓存技术

在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用 ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。

为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这 些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又 将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。

 

这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。

 

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

 

在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

 

为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

 

  • 你的设备可以为每个应用程序分配多大的内存?
  • 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
  • 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
  • 图片的尺寸和大小,还有每张图片会占据多少内存空间。
  • 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
  • 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。

 

并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓 存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。

 

下面是一个使用 LruCache 来缓存图片的例子:

 

  1. private LruCache<String, Bitmap> mMemoryCache;  
  2.   
  3. @Override  
  4. protected void onCreate(Bundle savedInstanceState) {  
  5.     // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。  
  6.     // LruCache通过构造函数传入缓存值,以KB为单位。  
  7.     int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
  8.     // 使用最大可用内存值的1/8作为缓存的大小。  
  9.     int cacheSize = maxMemory / 8;  
  10.     mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
  11.         @Override  
  12.         protected int sizeOf(String key, Bitmap bitmap) {  
  13.             // 重写此方法来衡量每张图片的大小,默认返回图片数量。  
  14.             return bitmap.getByteCount() / 1024;  
  15.         }  
  16.     };  
  17. }  
  18.   
  19. public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  20.     if (getBitmapFromMemCache(key) == null) {  
  21.         mMemoryCache.put(key, bitmap);  
  22.     }  
  23. }  
  24.   
  25. public Bitmap getBitmapFromMemCache(String key) {  
  26.     return mMemoryCache.get(key);  
  27. }  

在 这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800×480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。
当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。

  1. public void loadBitmap(int resId, ImageView imageView) {  
  2.     final String imageKey = String.valueOf(resId);  
  3.     final Bitmap bitmap = getBitmapFromMemCache(imageKey);  
  4.     if (bitmap != null) {  
  5.         imageView.setImageBitmap(bitmap);  
  6.     } else {  
  7.         imageView.setImageResource(R.drawable.image_placeholder);  
  8.         BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
  9.         task.execute(resId);  
  10.     }  
  11. }  

BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。

  1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
  2.     // 在后台加载图片。  
  3.     @Override  
  4.     protected Bitmap doInBackground(Integer… params) {  
  5.         final Bitmap bitmap = decodeSampledBitmapFromResource(  
  6.                 getResources(), params[0], 100100);  
  7.         addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
  8.         return bitmap;  
  9.     }  
  10. }  

掌握了以上两种方法,不管是要在程序中加载超大图片,还是要加载大量图片,都不用担心OOM的问题了!不过仅仅是理论地介绍不知道大家能不能完全理解,在后面的文章中我会演示如何在实际程序中灵活运用上述技巧来避免程序OOM,感兴趣的朋友请继续阅读 Android照片墙应用实现,再多的图片也不怕崩溃 。

解决Eclipse中文乱码

mikel阅读(921)

.

使用Eclipse编辑文件经常出现中文乱码或者文件中有中文不能保存的问题,Eclipse提供了灵活的设置文件编码格式的选项,我们可以通过设置编码 格式解决乱码问题。在Eclipse可以从几个层面设置编码格式:Workspace、Project、Content Type、File
本文以Eclipse 3.3(英文)为例加以说明:
1. 设置Workspace的编码格式:
Windows->Preferences… 打开”首选项”窗口,点击左侧导航树到General->Workspace,在右侧视图中找到“Text file encoding”选项设置,一种是默认(Default),另一种是从下拉列表中选择(Other)。Eclipse Workspace默认的编码方式是操作系统的编码格式,这跟操作系统的设置有关系;另外我们可以选择Other单选按钮,然后从按钮右侧的下拉列表中选 择需要的编码格式(GBK、ISO-8859-1、UTF-16、UFT-16 etc.)。
2. 设置Project的编码格式:
在 Workspace中新建的项目默认继承Workspace的编码设置,我们也可以单独更改某个项目的编码格式。右键点击工程,选择 Properties,打开项目属性设置窗口,左侧导航树选择Resource,在右侧视图中找到“Text file encoding”,两种设置项目的编码格式,默认选中的是“Inherited from container (XXX)”(注:XXX为Workspace设置的编码),我们也可以选择其他的编码格式,设置方式同Workspace。
3. 设置Content Type的编码格式:
有 时我们想使整个Workspace某种类型的文件保持同一种编码格式,这就需要用到Content Type设置来达到目的,具体方式如下:Windows->Preferences…打开”首选项”窗口,左侧导航树选择 General->Content Types,在右侧视图中选择Text->Java Source File,在最下侧有Default encoding输入框,手动输入编码格式,点击Update按钮使设置生效(切记啊!)。有两点需要注意一下:
a. 这个设置使Workspace所有项目下的相同类型的文件有相同的编码格式,改变项目的编码设置不影响项目中文件类型的编码设置;
b. Default encoding是手动输入的,输入的编码名称要准确,否则在打开此类型文件时会显示Unsupported Character Encoding。
4. 设置File的编码格式:
我 们还可以单独设置某个文件的编码格式,一种是通过在文件中设置编码格(如:’charset=UTF-8’),还可以通过文件属性设置。右键点击某一文 件,选择Properties,打开文件属性设置对话框,右侧导航树选择Resource,在右侧视图中通过“Text file encoding”选择设置文件的编码格式。

[转载]Android瀑布流照片墙实现,体验不规则排列的美感 - pangbangb - 博客园

mikel阅读(747)

[转载]Android瀑布流照片墙实现,体验不规则排列的美感 – pangbangb – 博客园.

传统界面的布局方式总是行列分明、坐落有序的,这种布局已是司空见惯,在不知不觉中大家都已经对它产生了审美疲劳。这个时候瀑布流布局的出现,就给 人带来了耳目一新的感觉,这种布局虽然看上去貌似毫无规律,但是却有一种说不上来的美感,以至于涌现出了大批的网站和应用纷纷使用这种新颖的布局来设计界 面。

 

记得我在之前已经写过一篇关于如何在Android上实现照片墙功能的文章了,但那个时候是使用的GridView来进行布局的,这种布局方式只适 用于“墙”上的每张图片大小都相同的情况,如果图片的大小参差不齐,在GridView中显示就会非常的难看。而使用瀑布流的布局方式就可以很好地解决这 个问题,因此今天我们也来赶一下潮流,看看如何在Android上实现瀑布流照片墙的功能。

 

首先还是讲一下实现原理,瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干 列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行 压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙, 示意图如下所示。

 

 

听我这么说完后,你可能会觉得瀑布流的布局非常简单嘛,只需要使用三个LinearLayout平分整个屏幕宽度,然后动态地addView()进 去就好了。确实如此,如果只是为了实现功能的话,就是这么简单。可是别忘了,我们是在手机上进行开发,如果不停地往LinearLayout里添加图片, 程序很快就会OOM。因此我们还需要一个合理的方案来对图片资源进行释放,这里仍然是准备使用LruCache算法,对这个算法不熟悉的朋友可以先参考Android高效加载大图、多图方案,有效避免程序OOM 。

 

下面我们就来开始实现吧,新建一个Android项目,起名叫PhotoWallFallsDemo,并选择4.0的API。

 

第一个要考虑的问题是,我们到哪儿去收集这些大小参差不齐的图片呢?这里我事先在百度上搜索了很多张风景图片,并且为了保证它们访问的稳定性,我将 这些图片都上传到了我的CSDN相册里,因此只要从这里下载图片就可以了。新建一个Images类,将所有相册中图片的网址都配置进去,代码如下所示:

 

public class Images {

	public final static String[] imageUrls = new String[] {
			"http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
			"http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
			"http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg" };
}

 

然后新建一个ImageLoader类,用于方便对图片进行管理,代码如下所示:

 

public class ImageLoader {

	/**
	 * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
	 */
	private static LruCache<String, Bitmap> mMemoryCache;

	/**
	 * ImageLoader的实例。
	 */
	private static ImageLoader mImageLoader;

	private ImageLoader() {
		// 获取应用程序最大可用内存
		int maxMemory = (int) Runtime.getRuntime().maxMemory();
		int cacheSize = maxMemory / 8;
		// 设置图片缓存大小为程序最大可用内存的1/8
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				return bitmap.getByteCount();
			}
		};
	}

	/**
	 * 获取ImageLoader的实例。
	 * 
	 * @return ImageLoader的实例。
	 */
	public static ImageLoader getInstance() {
		if (mImageLoader == null) {
			mImageLoader = new ImageLoader();
		}
		return mImageLoader;
	}

	/**
	 * 将一张图片存储到LruCache中。
	 * 
	 * @param key
	 *            LruCache的键,这里传入图片的URL地址。
	 * @param bitmap
	 *            LruCache的键,这里传入从网络上下载的Bitmap对象。
	 */
	public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
		if (getBitmapFromMemoryCache(key) == null) {
			mMemoryCache.put(key, bitmap);
		}
	}

	/**
	 * 从LruCache中获取一张图片,如果不存在就返回null。
	 * 
	 * @param key
	 *            LruCache的键,这里传入图片的URL地址。
	 * @return 对应传入键的Bitmap对象,或者null。
	 */
	public Bitmap getBitmapFromMemoryCache(String key) {
		return mMemoryCache.get(key);
	}

	public static int calculateInSampleSize(BitmapFactory.Options options,
			int reqWidth) {
		// 源图片的宽度
		final int width = options.outWidth;
		int inSampleSize = 1;
		if (width > reqWidth) {
			// 计算出实际宽度和目标宽度的比率
			final int widthRatio = Math.round((float) width / (float) reqWidth);
			inSampleSize = widthRatio;
		}
		return inSampleSize;
	}

	public static Bitmap decodeSampledBitmapFromResource(String pathName,
			int reqWidth) {
		// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
		final BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(pathName, options);
		// 调用上面定义的方法计算inSampleSize值
		options.inSampleSize = calculateInSampleSize(options, reqWidth);
		// 使用获取到的inSampleSize值再次解析图片
		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeFile(pathName, options);
	}

}

 

这里我们将ImageLoader类设成单例,并在构造函数中初始化了LruCache类,把它的最大缓存容量设为最大可用内存的1/8。然后又提供了其它几个方法可以操作LruCache,以及对图片进行压缩和读取。

 

 

 

接下来新建MyScrollView继承自ScrollView,代码如下所示:

 

public class MyScrollView extends ScrollView implements OnTouchListener {

	/**
	 * 每页要加载的图片数量
	 */
	public static final int PAGE_SIZE = 15;

	/**
	 * 记录当前已加载到第几页
	 */
	private int page;

	/**
	 * 每一列的宽度
	 */
	private int columnWidth;

	/**
	 * 当前第一列的高度
	 */
	private int firstColumnHeight;

	/**
	 * 当前第二列的高度
	 */
	private int secondColumnHeight;

	/**
	 * 当前第三列的高度
	 */
	private int thirdColumnHeight;

	/**
	 * 是否已加载过一次layout,这里onLayout中的初始化只需加载一次
	 */
	private boolean loadOnce;

	/**
	 * 对图片进行管理的工具类
	 */
	private ImageLoader imageLoader;

	/**
	 * 第一列的布局
	 */
	private LinearLayout firstColumn;

	/**
	 * 第二列的布局
	 */
	private LinearLayout secondColumn;

	/**
	 * 第三列的布局
	 */
	private LinearLayout thirdColumn;

	/**
	 * 记录所有正在下载或等待下载的任务。
	 */
	private static Set<LoadImageTask> taskCollection;

	/**
	 * MyScrollView下的直接子布局。
	 */
	private static View scrollLayout;

	/**
	 * MyScrollView布局的高度。
	 */
	private static int scrollViewHeight;

	/**
	 * 记录上垂直方向的滚动距离。
	 */
	private static int lastScrollY = -1;

	/**
	 * 记录所有界面上的图片,用以可以随时控制对图片的释放。
	 */
	private List<ImageView> imageViewList = new ArrayList<ImageView>();

	/**
	 * 在Handler中进行图片可见性检查的判断,以及加载更多图片的操作。
	 */
	private static Handler handler = new Handler() {

		public void handleMessage(android.os.Message msg) {
			MyScrollView myScrollView = (MyScrollView) msg.obj;
			int scrollY = myScrollView.getScrollY();
			// 如果当前的滚动位置和上次相同,表示已停止滚动
			if (scrollY == lastScrollY) {
				// 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片
				if (scrollViewHeight + scrollY >= scrollLayout.getHeight()
						&& taskCollection.isEmpty()) {
					myScrollView.loadMoreImages();
				}
				myScrollView.checkVisibility();
			} else {
				lastScrollY = scrollY;
				Message message = new Message();
				message.obj = myScrollView;
				// 5毫秒后再次对滚动位置进行判断
				handler.sendMessageDelayed(message, 5);
			}
		};

	};

	/**
	 * MyScrollView的构造函数。
	 * 
	 * @param context
	 * @param attrs
	 */
	public MyScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		imageLoader = ImageLoader.getInstance();
		taskCollection = new HashSet<LoadImageTask>();
		setOnTouchListener(this);
	}

	/**
	 * 进行一些关键性的初始化操作,获取MyScrollView的高度,以及得到第一列的宽度值。并在这里开始加载第一页的图片。
	 */
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if (changed && !loadOnce) {
			scrollViewHeight = getHeight();
			scrollLayout = getChildAt(0);
			firstColumn = (LinearLayout) findViewById(R.id.first_column);
			secondColumn = (LinearLayout) findViewById(R.id.second_column);
			thirdColumn = (LinearLayout) findViewById(R.id.third_column);
			columnWidth = firstColumn.getWidth();
			loadOnce = true;
			loadMoreImages();
		}
	}

	/**
	 * 监听用户的触屏事件,如果用户手指离开屏幕则开始进行滚动检测。
	 */
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_UP) {
			Message message = new Message();
			message.obj = this;
			handler.sendMessageDelayed(message, 5);
		}
		return false;
	}

	/**
	 * 开始加载下一页的图片,每张图片都会开启一个异步线程去下载。
	 */
	public void loadMoreImages() {
		if (hasSDCard()) {
			int startIndex = page * PAGE_SIZE;
			int endIndex = page * PAGE_SIZE + PAGE_SIZE;
			if (startIndex < Images.imageUrls.length) {
				Toast.makeText(getContext(), "正在加载...", Toast.LENGTH_SHORT)
						.show();
				if (endIndex > Images.imageUrls.length) {
					endIndex = Images.imageUrls.length;
				}
				for (int i = startIndex; i < endIndex; i++) {
					LoadImageTask task = new LoadImageTask();
					taskCollection.add(task);
					task.execute(Images.imageUrls[i]);
				}
				page++;
			} else {
				Toast.makeText(getContext(), "已没有更多图片", Toast.LENGTH_SHORT)
						.show();
			}
		} else {
			Toast.makeText(getContext(), "未发现SD卡", Toast.LENGTH_SHORT).show();
		}
	}

	/**
	 * 遍历imageViewList中的每张图片,对图片的可见性进行检查,如果图片已经离开屏幕可见范围,则将图片替换成一张空图。
	 */
	public void checkVisibility() {
		for (int i = 0; i < imageViewList.size(); i++) {
			ImageView imageView = imageViewList.get(i);
			int borderTop = (Integer) imageView.getTag(R.string.border_top);
			int borderBottom = (Integer) imageView
					.getTag(R.string.border_bottom);
			if (borderBottom > getScrollY()
					&& borderTop < getScrollY() + scrollViewHeight) {
				String imageUrl = (String) imageView.getTag(R.string.image_url);
				Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl);
				if (bitmap != null) {
					imageView.setImageBitmap(bitmap);
				} else {
					LoadImageTask task = new LoadImageTask(imageView);
					task.execute(imageUrl);
				}
			} else {
				imageView.setImageResource(R.drawable.empty_photo);
			}
		}
	}

	/**
	 * 判断手机是否有SD卡。
	 * 
	 * @return 有SD卡返回true,没有返回false。
	 */
	private boolean hasSDCard() {
		return Environment.MEDIA_MOUNTED.equals(Environment
				.getExternalStorageState());
	}

	/**
	 * 异步下载图片的任务。
	 * 
	 * @author guolin
	 */
	class LoadImageTask extends AsyncTask<String, Void, Bitmap> {

		/**
		 * 图片的URL地址
		 */
		private String mImageUrl;

		/**
		 * 可重复使用的ImageView
		 */
		private ImageView mImageView;

		public LoadImageTask() {
		}

		/**
		 * 将可重复使用的ImageView传入
		 * 
		 * @param imageView
		 */
		public LoadImageTask(ImageView imageView) {
			mImageView = imageView;
		}

		@Override
		protected Bitmap doInBackground(String... params) {
			mImageUrl = params[0];
			Bitmap imageBitmap = imageLoader
					.getBitmapFromMemoryCache(mImageUrl);
			if (imageBitmap == null) {
				imageBitmap = loadImage(mImageUrl);
			}
			return imageBitmap;
		}

		@Override
		protected void onPostExecute(Bitmap bitmap) {
			if (bitmap != null) {
				double ratio = bitmap.getWidth() / (columnWidth * 1.0);
				int scaledHeight = (int) (bitmap.getHeight() / ratio);
				addImage(bitmap, columnWidth, scaledHeight);
			}
			taskCollection.remove(this);
		}

		/**
		 * 根据传入的URL,对图片进行加载。如果这张图片已经存在于SD卡中,则直接从SD卡里读取,否则就从网络上下载。
		 * 
		 * @param imageUrl
		 *            图片的URL地址
		 * @return 加载到内存的图片。
		 */
		private Bitmap loadImage(String imageUrl) {
			File imageFile = new File(getImagePath(imageUrl));
			if (!imageFile.exists()) {
				downloadImage(imageUrl);
			}
			if (imageUrl != null) {
				Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(
						imageFile.getPath(), columnWidth);
				if (bitmap != null) {
					imageLoader.addBitmapToMemoryCache(imageUrl, bitmap);
					return bitmap;
				}
			}
			return null;
		}

		/**
		 * 向ImageView中添加一张图片
		 * 
		 * @param bitmap
		 *            待添加的图片
		 * @param imageWidth
		 *            图片的宽度
		 * @param imageHeight
		 *            图片的高度
		 */
		private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) {
			LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
					imageWidth, imageHeight);
			if (mImageView != null) {
				mImageView.setImageBitmap(bitmap);
			} else {
				ImageView imageView = new ImageView(getContext());
				imageView.setLayoutParams(params);
				imageView.setImageBitmap(bitmap);
				imageView.setScaleType(ScaleType.FIT_XY);
				imageView.setPadding(5, 5, 5, 5);
				imageView.setTag(R.string.image_url, mImageUrl);
				findColumnToAdd(imageView, imageHeight).addView(imageView);
				imageViewList.add(imageView);
			}
		}

		/**
		 * 找到此时应该添加图片的一列。原则就是对三列的高度进行判断,当前高度最小的一列就是应该添加的一列。
		 * 
		 * @param imageView
		 * @param imageHeight
		 * @return 应该添加图片的一列
		 */
		private LinearLayout findColumnToAdd(ImageView imageView,
				int imageHeight) {
			if (firstColumnHeight <= secondColumnHeight) {
				if (firstColumnHeight <= thirdColumnHeight) {
					imageView.setTag(R.string.border_top, firstColumnHeight);
					firstColumnHeight += imageHeight;
					imageView.setTag(R.string.border_bottom, firstColumnHeight);
					return firstColumn;
				}
				imageView.setTag(R.string.border_top, thirdColumnHeight);
				thirdColumnHeight += imageHeight;
				imageView.setTag(R.string.border_bottom, thirdColumnHeight);
				return thirdColumn;
			} else {
				if (secondColumnHeight <= thirdColumnHeight) {
					imageView.setTag(R.string.border_top, secondColumnHeight);
					secondColumnHeight += imageHeight;
					imageView
							.setTag(R.string.border_bottom, secondColumnHeight);
					return secondColumn;
				}
				imageView.setTag(R.string.border_top, thirdColumnHeight);
				thirdColumnHeight += imageHeight;
				imageView.setTag(R.string.border_bottom, thirdColumnHeight);
				return thirdColumn;
			}
		}

		/**
		 * 将图片下载到SD卡缓存起来。
		 * 
		 * @param imageUrl
		 *            图片的URL地址。
		 */
		private void downloadImage(String imageUrl) {
			HttpURLConnection con = null;
			FileOutputStream fos = null;
			BufferedOutputStream bos = null;
			BufferedInputStream bis = null;
			File imageFile = null;
			try {
				URL url = new URL(imageUrl);
				con = (HttpURLConnection) url.openConnection();
				con.setConnectTimeout(5 * 1000);
				con.setReadTimeout(15 * 1000);
				con.setDoInput(true);
				con.setDoOutput(true);
				bis = new BufferedInputStream(con.getInputStream());
				imageFile = new File(getImagePath(imageUrl));
				fos = new FileOutputStream(imageFile);
				bos = new BufferedOutputStream(fos);
				byte[] b = new byte[1024];
				int length;
				while ((length = bis.read(b)) != -1) {
					bos.write(b, 0, length);
					bos.flush();
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (bis != null) {
						bis.close();
					}
					if (bos != null) {
						bos.close();
					}
					if (con != null) {
						con.disconnect();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (imageFile != null) {
				Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(
						imageFile.getPath(), columnWidth);
				if (bitmap != null) {
					imageLoader.addBitmapToMemoryCache(imageUrl, bitmap);
				}
			}
		}

		/**
		 * 获取图片的本地存储路径。
		 * 
		 * @param imageUrl
		 *            图片的URL地址。
		 * @return 图片的本地存储路径。
		 */
		private String getImagePath(String imageUrl) {
			int lastSlashIndex = imageUrl.lastIndexOf("/");
			String imageName = imageUrl.substring(lastSlashIndex + 1);
			String imageDir = Environment.getExternalStorageDirectory()
					.getPath() + "/PhotoWallFalls/";
			File file = new File(imageDir);
			if (!file.exists()) {
				file.mkdirs();
			}
			String imagePath = imageDir + imageName;
			return imagePath;
		}
	}

}

 

 

 

MyScrollView是实现瀑布流照片墙的核心类,这里我来重点给大家介绍一下。首先它是继承自ScrollView的,这样就允许用户可以通 过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中 我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前 ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载下一页的图片。

 

 

 

那我们就要来看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一 个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如 果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片 添加进去就可以了。

 

另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心 思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高 的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开 启一个LoadImageTask,将这张图片重新加载到内存中。

 

然后打开或新建activity_main.xml,在里面设置好瀑布流的布局方式,如下所示:

 

<com.example.photowallfallsdemo.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/my_scroll_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <LinearLayout
            android:id="@+id/first_column"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>

        <LinearLayout
            android:id="@+id/second_column"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>

        <LinearLayout
            android:id="@+id/third_column"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >
        </LinearLayout>
    </LinearLayout>

</com.example.photowallfallsdemo.MyScrollView>

 

可以看到,这里我们使用了刚才编写好的MyScrollView作为根布局,然后在里面放入了一个直接子布局LinearLayout用于统计当前 滑动布局的高度,然后在这个布局下又添加了三个等宽的LinearLayout分别作为第一列、第二列和第三列的布局,这样在MyScrollView中 就可以动态地向这三个LinearLayout里添加图片了。

 

 

 

最后,由于我们使用到了网络和SD卡存储的功能,因此还需要在AndroidManifest.xml中添加以下权限:

 

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />

 

这样我们所有的编码工作就已经完成了,现在可以尝试运行一下,效果如下图所示:

 

 

 

 

瀑布流模式的照片墙果真非常美观吧,而且由于我们有非常完善的资源释放机制,不管你在照片墙上添加了多少图片,程序占用内存始终都会保持在一个合理的范围内。

 

好了,今天的讲解到此结束,有疑问的朋友请在下面留言。

源码下载,请点击这里