PHP单例模式的学习笔记

 更新时间:2016年11月25日 14:59  点击:1894
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。


单例模式的特点:

1、构造函数需要标记为private(访问控制:防止外部代码使用new操作符创建对象),单例类不能在其他类中实例化,只能被其自身实例化;

2、拥有一个保存类的实例的静态成员变量

3、拥有一个访问这个实例的公共的静态方法(常用getInstance()方法进行实例化单例类,通过instanceof操作符可以检测到类是否已经被实例化)

另外,需要创建__clone()方法防止对象被复制(克隆)

DEMO(链接数据库):

 代码如下 复制代码

/* 例子:数据库连接职责的集中控制 */
class Database {
 
private $_db;
static $_instance;
 
private function __construct() {
$this->_db = pg_connect('dbname=example_db');
}
 
private __clone() {};
 
public static function getInstance() {
 if(! (self::$_instance instanceof self) )
 {
 self::$_instance = new self();
 }
 return self::$_instance;
 
}
 
public function query($sql) {
// 使用 $this->_db 执行一个查询
return pg_query($this->_db, $sql);
}
}

刚看PHP手册中 Boolean 布尔类型 数据类型时,看到了评论中一个关于运算符优先级的问题,以前也讲过在实际项目中不要出现这些需要人为判断运算符优先级的代码,都应该使用括号来规避这个问题的出现

周五一朋友来着一段代码来问我,说他看不懂这段代码,觉得这段代码有问题,代码类似:

 代码如下 复制代码

$array1 = array(1, 2);
$array2 = array(3, 4);

$result = empty($array1) == 3 ? $array1 : $array2;

我一看应该是运算符优先级的问题。
按理说,这些运算符优先级的问题是不应该让它出现在项目代码中的,只能让它在面试问题中,程序员应该在书写代码过程中使用括号来避免这种不确定问题的出现。项目中的代码应该是那种我们一看就知道结果的,而不是需要我们费劲脑子才能知道结果的。这种优先级代码的结果有时候并不是我们相信的那样,看看下面的例子:

 代码如下 复制代码

<?php
if ($a = 100 && $b = 200) {
     var_dump($a, $b);
}

上面代码也许你会认为是:

 代码如下 复制代码
<?php
if ($a = (100 && $b) = 200) {
     var_dump($a, $b);
}

但是实际上不是这样的,实际结果是:

 代码如下 复制代码
<?php
if ($a = (100 && $b = 200)) {
     var_dump($a, $b);
}

至于为什么会这样?那是因为PHP并不完全遵守优先级的定义,PHP手册上中有说明

Note: Although = has a lower precedence than most other operators, PHP will still allow expressions similar to the following: if (!$a = foo()), in which case the return value of foo() is put into $a.

代码:

代码1:

 代码如下 复制代码

<?php
$x=TRUE;
$y=FALSE;
$z=$y OR $x; ?>
代码2:
<?php
$x=TRUE;
$y=FALSE;
$z=$y || $x;
?>

这种代码在实际项目中是不应该出现的,都应该用下面的代码替换:

 代码如下 复制代码

$z = ($y OR $x);

$z = ($y || $x);

文章主要是一个站长在学习php过程中一些用到的函数与方法及对各种方法的理解与简单的实例,下面全部放出来希望对你学习php有帮助。

PHP使用header函数设置HTTP头的示例方法

 代码如下 复制代码

//定义编码
header( Content-Type:text/html;charset=utf-8 );
//Atom
header(Content-type: application/atom+xml);
//CSS
header(Content-type: text/css);
//Javascript
header(Content-type: text/javascript);
//JPEG Image
header(Content-type: image/jpeg);
//JSON
header(Content-type: application/json);
//PDF
header(Content-type: application/pdf);
//RSS
header(Content-Type: application/rss+xml; charset=ISO-8859-1);
//Text (Plain)
header(Content-type: text/plain);
//XML
header(Content-type: text/xml);
// ok
header(HTTP/1.1 200 OK);
//设置一个404头:
header(HTTP/1.1 404 Not Found);
//设置地址被永久的重定向
header(HTTP/1.1 301 Moved Permanently);
//转到一个新地址
header(Location: http://www.example.org/);
//文件延迟转向:
header(Refresh: 10; url=http://www.example.org/);
print You will be redirected in 10 seconds;
//当然,也可以使用html语法实现
// <meta http-equiv="refresh" content="10;http://www.example.org/ />
// override X-Powered-By: PHP:
header(X-Powered-By: PHP/4.4.0);
header(X-Powered-By: Brain/0.6b);
//文档语言
header(Content-language: en);
//告诉浏览器最后一次修改时间
$time = time() - 60; // or filemtime($fn), etc
header(Last-Modified: .gmdate(D, d M Y H:i:s, $time). GMT);
//告诉浏览器文档内容没有发生改变
header(HTTP/1.1 304 Not Modified);
//设置内容长度
header(Content-Length: 1234);
//设置为一个下载类型
header(Content-Type: application/octet-stream);
header(Content-Disposition: attachment; filename="example.zip");
header(Content-Transfer-Encoding: binary);
// load the file to send:
readfile(example.zip);
// 对当前文档禁用缓存
header(Cache-Control: no-cache, no-store, max-age=0, must-revalidate);
header(Expires: Mon, 26 Jul 1997 05:00:00 GMT); // Date in the past
header(Pragma: no-cache);
//设置内容类型:
header(Content-Type: text/html; charset=iso-8859-1);
header(Content-Type: text/html; charset=utf-8);
header(Content-Type: text/plain); //纯文本格式
header(Content-Type: image/jpeg); //JPG***
header(Content-Type: application/zip); // ZIP文件
header(Content-Type: application/pdf); // PDF文件
header(Content-Type: audio/mpeg); // 音频文件
header(Content-Type: application/x-shockw**e-flash); //Flash动画

//显示登陆对话框
header(HTTP/1.1 401 Unauthorized);
header(WWW-Authenticate: Basic realm="Top Secret");
print Text that will be displayed if the user hits cancel or ;
print enters wrong login data;

php中static静态变量的使用方法详解
php中的变量作用范围的另一个重要特性就是静态变量(static 变量)。静态变量仅在局部函数域中存在且只被初始化一次,当程序执行离开此作用域时,其值不会消失,会使用上次执行的结果。

编程实例:

 代码如下 复制代码
function test()
{
  static $aa = 0;
  return $aa++;
}
$aa = "1000";
echo $aa;
echo test();
echo test();
echo $aa;
 

本函数每调用test()都会输出 $aa的值并加一。
上文代码运行输出:

1000
0
1
1000
 静态变量也提供了一种处理递归函数的方法。递归函数是一种自己调用自己的方法。写递归函数时要小心,因为可能会无穷递归下去,没有出口.务必确保 有方法来中止递归。

一维数组按照元素或者键值分组变为二维数组
有时候查询数据库记录后会对数据库查询结果进行分组,即将一维数组变为二维数组,方便调用使用(通常是json)

 代码如下 复制代码

$arr = array(
    '0'=>array(
            'firmware'=>'f1',
            'version'=>'1',
        ),
    '1'=>array(
            'firmware'=>'f1',
            'version'=>'2',
        ),
    '2'=>array(
            'firmware'=>'f1',
            'version'=>'3',
        ),
    '3'=>array(
            'firmware'=>'f2',
            'version'=>'1',
        ),
    '4'=>array(
            'firmware'=>'f2',
            'version'=>'2',
        ),
    );
    $new_arr  =  array();
    foreach ($arr as $row ){
        $new_arr[$row['firmware']][] = $row['version'];
    }
    var_dump($new_arr);
转换后

Array
(
    [f1] => Array
        (
            [0] => 1
            [1] => 2
            [2] => 3
        )

    [f2] => Array
        (
            [0] => 1
            [1] => 2
        )

)

PHP的静态绑定和动态绑定(private/public)
子类Foo的对象调用了test()方法,test()方法调用了$this->testPrivate();这个$this此时应该是子类的引用,按理说应该调用子类的testPrivate()方法,实际上却调用了父类的testPrivate()方法

 代码如下 复制代码

class Bar
{
    public function test() {
        $this->testPrivate();
        $this->testPublic();
    }

    public function testPublic() {
        echo "Bar::testPublicn";
    }
   
    private function testPrivate() {
        echo "Bar::testPrivaten";
    }
}

class Foo extends Bar
{
    public function testPublic() {
        echo "Foo::testPublicn";
    }
   
    private function testPrivate() {
        echo "Foo::testPrivaten";
    }
}

$myFoo = new Foo();
$myFoo->test();
// 运行结果
// Bar->testPrivate
// Foo->testPublic

这是PHP的动态绑定和静态绑定的一种情况。

public是动态绑定,在编译期不绑定,所以在运行期调用父类的test()方法的时候,会解析为子类的public方法。

而private是私有的,不会继承到子类,在编译期就绑定了,是一种“静态”的绑定(类似5.3后的self)。

与这个相关的是LSB,静态延迟绑定,PHP5.3因为有了这个特性之后,使PHP的OOP得到加强

public: 可以被继承,也可以被外部调用。

private: 不可以被继承,也不可以被外部调用。

protected: 可以被继承,但不能被外部调用。

PHP三种运行方式mod_php5/cgi/fast-cgi
a.通过HTTPServer内置的模块来实现,
例如Apache的mod_php5,类似的Apache内置的mod_perl可以对perl支持;

b.通过CGI来实现
这个就好比之前perl的CGI,该种方式的缺点是性能差,因为每次服务器遇到这些脚本都需要重新启动脚本解析器来执行脚本然后将结果返回给服务器;另一方面就是不太安全;该方面几乎很少使用了。

c.最新出现一种叫做FastCGI。
所谓FastCGI就是对CGI的改进。它一般采用C/S结构,一般脚本处理器会启动一个或者多个daemon进 程,每次HTTPServer遇到脚本的时候,直接交付给FastCGI的进程来执行,然后将得到的结果(通常为html)返回给浏览器。

该种方法的问题存在一个小问题是当遇到大流量的频繁请求的话,脚本处理器的daemon进程可能会超负荷从而变得很慢,甚至发生内存泄漏;

但是比较起Apache的内置模块的方式的优点是由于Server和脚本解析器完全分开各负其责,因此服务器不再臃肿,可以专心地进行静态文件响 应或者将动态脚本解析器的结果返回给用户客户端。所以比较起Apache的内置模块方式,有时候性能要提高很多。有人测试可能会达到 Apache+mod_php的5~10倍。

三种常用模式:
Apache+mod_php5;

lightppd+spawn-fcgi;

nginx+PHP-FPM

我们可以使用到生产环境中的:
0) 如果不是server cluster的话:
可以使用以上任一种,不过有各种测试表明nginx+PHP-FPM性能优越,但是Apache+mod_php5很经典模块多,比如对.htaccess等的支持。

如果构建server cluster的话:
1) nginx作为反向代理服务器,后台多台Apache+mod_php5。
nginx处理静态文件,及对php并发请求对后台多台app server的负载均衡;

2) nginx作为反向代理器,后台多台PHP-FPM
nginx处理静态文件及将php并发请求发送到后台php-fpm来解析;

三种变量命名规则(匈牙利法,小驼峰法,大驼峰法)
1. 匈牙利命名:
•开头字母用变量类型的缩写,其余部分用变量的英文或英文的缩写,要求单词第一个字母大写。
•For example: long lsum = 0;"l"是类型的缩写;
2. 小驼峰式:(little camel-case)
•第一个单词首字母小写,后面其他单词首字母大写。
•For example: string firstName = string.Empty;
3. 大驼峰式:(big camel-case)
•每个单词的第一个字母都大写;
•For example:string FirstName = string.Empty;
解决 Json 中文转码问题

 代码如下 复制代码
//代码
$data = array(
    'status'=>'1',
    'method'=>'登陆',
    'message'=>'成功',
);
echo json_encode($data);
//显示
{"status":"1","method":"u767bu9646","message":"u6210u529f"}

json_encode 只能接受utf-8格式的数据,最终的json中中文部分被替换为unicode编码。我们要解决的就是将对象转换为json并保证对象内部的中文在json中仍然是以正常的中文出现。

先将类中的中文字段进行url编码(urlencode),然后再对对象进行json编码(jsonencode),最后url解码(urldecode)json,即最终的json,里面的中文依旧是那个中文。

 代码如下 复制代码
//代码
foreach ( $data as $key => $value ) {
    $newData[$key] = urlencode ( $value );
}
echo urldecode(json_encode($newData));
//显示
{"status":"1","method":"登陆","message":"成功"}

Ajax跨域请求CORS错误
编写Json api供其他站点查询调用的时候有时候使用ajax方式获取,此时,可能ACAO的设置,那么将发生CROS错误。

//报错
XMLHttpRequest cannot load http://www.111cn.net . No 'Access-Control-Allow-Origin' header is present on the requested resource.
//解决办法

 代码如下 复制代码

header('Access-Control-Allow-Origin: *'); 
header('Access-Control-Allow-Origin: ccdd.com');
codeigniter指定地址实现HTTPS访问管理
//启动hooks
//app/config/config.php
$config['enable_hooks'] = TRUE;

//hooks配置
///app/config/hooks.php
$hook['post_controller_constructor'][] = array(
        'function' => 'check_ssl',
        'filename' => 'ssl.php',
        'filepath' => 'hooks',
    );

//插件编写
//app/hooks/ssl.php
function check_ssl(){
$CI =& get_instance();
$class = $CI->router->fetch_class();
$method = $CI->router->fetch_method();


$ssl = $CI->config->item('ssl_class_method');  
$partial = $CI->config->item('no_ssl_class_method');   

if(in_array($class.'/'.$method,$ssl)){
    //force_ssl();
    $CI =&get_instance();
    $CI->config->config['base_url'] = str_replace('http://', 'https://', $CI->config->config['base_url']);
    if ($_SERVER['SERVER_PORT'] != 443) redirect($CI->uri->uri_string());
}
else if(in_array($class.'/'.$method,$partial))
{
        return;
    }
    else{
        //unforce_ssl
        $CI =&get_instance();
        $CI->config->config['base_url'] = str_replace('https://', 'http://', $CI->config->config['base_url']);
        if ($_SERVER['SERVER_PORT'] == 443) redirect($CI->uri->uri_string());
    }
}

//config 配置需要使用https的 class method
//app/config/config.php
$config['ssl_class_method'] = array(
    'U_class/A_method',
    'V_class/B_method',
    'W_class/C_method',
    'X_class/D_method',
    ); //强制使用ssl
$config['no_ssl_class_method'] = array();

//强制不使用ssl

最简单的全局变量函数就是$_SERVER[\'DOCUMENT_ROOT\']与$HTTP_SERVER_VARS[\'DOCUMENT_ROOT\']了,但是我们要调用文件的话就无法这样实现了,具体如何写呢,下面我们一起来看看。

用php开发网站的时候有时候经常要用到当前网站的根目录, 比如包含一个文件:

 代码如下 复制代码

include_once("./includefile.php");

那么要想写根目录的形式应该怎么写呢?

这时候通常用 dirname(__FILE__) 这个函数, 意思是获取当前文件所在的根目录, 

那么这个php语句就是这样的了:

 代码如下 复制代码

include_once(dirname(__FILE__) ."/includefile.php");


将下面的代码放在网站根目录下的一个文件中,以便在其它文件中引用。

路径方式:

 代码如下 复制代码

define('BASE_PATH',str_replace('\\','/',realpath(dirname(__FILE__).'/'))."/");

echo BASE_PATH;


输出结果:E:/www.111cn.net /

url方式:

 代码如下 复制代码

 

$PHP_SELF=$_SERVER['PHP_SELF'];

$url='http://'.$_SERVER['HTTP_HOST'].substr($PHP_SELF,0,strrpos($PHP_SELF,'/')+1);

echo $url;


//注意大小写问题, linux下对大小写敏感, 要养成认真对待大小写的习惯, 以免win主机换到linux下不兼容的问题

php直接输出json格式,很多新手有一个误区,以为用echo json_encode($data);这样就是输出json数据了,没错这样输出文本是json格式文本而不是json数据,正确的写法是应该加一句:

我们要在利用Content-type:text/json才可以正常显示是json数据哦。

 代码如下 复制代码

<?php 
 
header(‘Content-type:text/json’);     //这句是重点,它告诉接收数据的对象此页面输出的是json数据; 
 
$json={“n”:”name”,”p”:”password”};           //虽然这行数据形式上是json格式,如果没有上面那句的话,它是不会被当做json格式的数据被处理的; 
 
echo $json; 
 
?>

json_encode当然也可以输入json字符串了,下面看几个例子。

PHP生成JSON的函数是:json_encode($PHPcode);
PHP解析JSON的函数是:json_decode($JSONcode);

所以JSON的形式有多种,不同的形式在PHP解释出来后的形式也是不同的。
//形式1:完全是对象的形式,这种形式的数据在Javascript中又叫相关数组,与一般数组不同的是,它可以通过字符串作索引来访问(用“[]”或“.”来表示层级)

 代码如下 复制代码

$json='{"item1":{"item11":{"n":"chenling","m":"llll"},"sex":"www.111cn.net","age":"25"},"item2":{"item21":"ling","sex":"女","age":"24"}}';
$J=json_decode($json);
print_r($J);
将输出:
stdClass Object
(
  [item1] => stdClass Object
  (
   [item11] => stdClass Object
   (
     [n] => chenling
     [m] => llll
   )

   [sex] => www.111cn.net
   [age] => 25
  )

  [item2] => stdClass Object
  (
   [item21] => ling
   [sex] => 女
   [age] => 24
  )
)

比如说我要取得了值是chenling的那个属性,则应该这样访问:
$J->item1->item11->n;//这将取得属性n的值:chenling
其实这种访问形式跟访问普通的对象属性差不多,也相当于访问一个3维数组。

//形式2:对象和数组混合

 代码如下 复制代码

$json='{"item1":[{"name":[{"chen":"chenling","ling":"chenli"}],"sex":"男","age":"25"},{"name":"sun","sex":"女","age":"24"}]}';
$J=json_decode($json);
print_r($J);
将输出:
stdClass Object
(
  [item1] => Array
  (
   [0] => stdClass Object
   (
     [name] => Array
     (
       [0] => stdClass Object
       (
         [chen] => chenling
         [ling] => chenli
       )

     )

     [sex] => 男
     [age] => 25
   )

   [1] => stdClass Object
   (
     [name] => sun
     [sex] => 女
     [age] => 24
   )

  )

)

比如说我要取得了值是chenling的那个元素,则应该这样访问:
$J->item1[0]->name[0]->chen;//这将取得元素chen的值:chenling
其实这种访问形式结合了对象和数组的访问方式,也相当于访问一个5维数组。

//形式3:完全数组形式

 代码如下 复制代码

$json='[["item1","item11"],["n","chenling"],["m","llll"]]';
$J=json_decode($json);
print_r($J);
将输出:
Array
(
  [0] => Array
  (
    [0] => item1
    [1] => item11
  )

  [1] => Array
  (
    [0] => n
    [1] => chenling
  )

  [2] => Array
  (
    [0] => m
    [1] => llll
  )
)

比如说我要取得了值是chenling的那个元素,则应该这样访问:
$J[0][1];//这将取得元素值chenling的那个元素
但是用这种方式有一个缺点,就是无法用字符串作为索引,只能用数字,用完全对象的形式可以解决这个问题
其实这种访问形式就是数组的访问方式,相当于访问一个2维数组。

小结:
从上面的例子可以看出JSON有点类似XML,也可以在PHP和Javascript之间传递带结构的数据,使用起来很方便。

[!--infotagslink--]

相关文章

  • php上传图片学习笔记与心得

    我们在php中上传文件就必须使用#_FILE变量了,这个自动全局变量 $_FILES 从 PHP 4.1.0 版本开始被支持。在这之前,从 4.0.0 版本开始,PHP 支持 $HTTP_POST_FILES 数组。这...2016-11-25
  • Smarty模板学习笔记之Smarty简介

    1、简介Smarty是一个使用PHP写出来的模板PHP模板引擎,是目前业界最著名的PHP模板引擎之一。它分离了逻辑代码和外在的内容,提供了一种易于管理和使用的方法,用来将原本与HTML代码混杂在一起PHP代码逻辑分离。简单的讲,目...2014-05-31
  • php Observer观察者模式之学习笔记

    当我们在星际中开地图和几家电脑作战的时候,电脑的几个玩家相当于结盟,一旦我们出兵进攻某一家电脑,其余的电脑会出兵救援。 那么如何让各家电脑知道自己的盟友被攻击了...2016-11-25
  • 学习JavaScript设计模式之单例模式

    这篇文章主要为大家介绍了JavaScript设计模式中的单例模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
  • php单例模式实现方法分析

    本文实例讲述了php单例模式实现方法。...2015-03-15
  • PHP 日期函数 学习笔记介绍

    举一个简单的date例子 我将使用echo命令把内容输出到我们的客户端(浏览器)。我将使用下面的代码做为基础代码。 代码如下 复制代码 <!DOCTY...2016-11-25
  • PHP单例模式静态类解析和实现源代码

    我们建站的时候,开始是每次请求数据库都要重新连接的、这样显然不合理、然后自己封装了一个数据库操作类、DBTools.php、要解决一个连接多次使用的话、最好的办法是使...2016-11-25
  • PHP 单例模式优点意义及如何实现

    单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便...2016-11-25
  • Android学习笔记之多界面切换实例

    一篇Android学习笔记之多界面切换实例,希望对各位朋友有所帮助。 用过VB 、 VC#的朋友都知道,在VB或VC#里要进行窗口(界面)切换很容易 例如在VB、C#里: 有 Fom1、...2016-09-20
  • php中的变量引用传值学习笔记

    引用:意思是将原始对象在内存中的地址传递给目标对象,就相当于原始对象和目标对象指向的是同一个内存地址。此时,如果对目标对象或者原始对象进行修改,内存中的数据也会改...2016-11-25
  • JavaScript中的设计模式 单例模式

    这篇文章主要给大家介绍的是JavaScript中的单例模式,设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,需要的朋友可以参考一下...2021-09-25
  • PHP中的cURL请求及示例学习笔记

    cURL是php中一个很强大的功能,可以模仿各种用户请求,如模仿用户登录,发送php cookie等等操作,下面我来整理一些相关的方法与各位同学看看 备注:使用curl_init函数,必须...2016-11-25
  • C#单例模式(Singleton Pattern)详解

    这篇文章主要为大家详细介绍了C#单例模式Singleton Pattern的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
  • 那些年,我还在学习C# 学习笔记

    那些年学了ASP.NET后,才开始学习C#,说来也怪,怎么学了ASP.NET才来学习C#,其实没有什么的...2020-06-25
  • YII2 WIDGET的学习笔记

    CWidget是所有Widget的基类。CWidget是自包含组件,可以看出是MVC的简略版,CWidget相比Controller,既没有actions,也没有filters widget,英文意思为小工具,小挂件,在程序...2016-11-25
  • metasploit调用nexpose扫描学习笔记

    Metasploit是一个免费的、可下载的框架,通过它可以很容易地获取、开发并对计算机软件漏洞实施攻击。它本身附带数百个已知软件漏洞的专业级漏洞攻击工具 nexpose安...2016-11-25
  • C# 设计模式系列教程-单例模式

    单例模式防止在应用程序中实例化多个对象。这就节约了开销,每个实例都要占用一定的内存,创建对象时需要时间和空间。...2020-06-25
  • php学习笔记之PHP扩展开发教程

    PHP扩展开发不是所有开发者都会操作的一个东西,下面我来演示一个关于PHP扩展开发实现过程,各位同学有需要可进入参考。 我们先假设需要这样一个扩展,提供一个叫tao_s...2016-11-25
  • Yii2学习笔记之汉化yii设置表单的描述

    小编推荐的这篇文章介绍了Yii2学习笔记之汉化yii设置表单的描述,非常实用,有兴趣的同学快来看看吧。 一:汉化框架&#8195;&#8195;框架汉化在这里设置,如果不生效,前台后...2017-07-06
  • php Builder建造器模式学习笔记

    星际里面有不少的任务关,也可以自己编辑地图,画面上有各种地形,建筑和部队,存在一个问题,初始化画面的流程很乱。待解决的问题:完成初始化画面的工作,同时尽量减少各种绘制...2016-11-25