Gowhich

Durban's Blog

DELL 12G(R420/R620/R720) 的服务器在安装 Ubuntu 的时候会出现诸多的问题,原先的 10.04.x LTS 版本已经不支持了,现在非官方还支持的有 12.04,不过依然有不少问题。根据这个帖子的描述,。
BIOS 会显示如下的 error log:

1
2
Critical CPU1 Status: Processor sensor for CPU1, IERR was asserted
Critical PCIE Fatal Err: Critical Event sensor, bus fatal error [Bus 1 Device 0 Function 0] was asserted

在 DELL 的官方博客上,我们也看到了其说明,DELL 12G 并不支持 Ubuntu 发型版本,但是其 12.04 LTS 及以后的版本可以通过修改一些东西来 workaround。

BIOS 中将 power management 设置为 maximum performance,禁用掉 C-State。

下面的就全是 ubuntu 这二货的 bug 了,难怪到现在 12G 的服务器也没出现在他的 certification 列表上。

给 sb_edac 以及 i7core_edac(3.2.0-28.44 修复) 打上 patch 或者加入 blacklist:

另外 acpi_pad(3.2.0-28.44 修复) 以及 mei(3.2.0-31.50 修复) 模块也要加到 blacklist 中,前者是电源节能的 driver,还在开发中,对于服务器来说也没有必要;后者在 watchdog timer 上会 。

1
2
3
4
5
# cat /etc/modprobe.d/blacklist-dell.conf
blacklist acpi_pad
blacklist mei
blacklist sb_edac
blacklist i7core_edac

这里有篇《Doing battle with a Dell R620 and Ubuntu》供欣赏 ;-)

除了 10.04.x 外,debian-6.0.6 以及 CentOS-6.3 等都会出现一些问题,包括安装过程中找不到网卡(BCM5720)驱动,找不到 RAID 的驱动等问题。换成 Intel 的 I350 的网卡之后,可以找到网卡驱。
总之,Ubuntu 既然想做 server 版本,就要把他给做好,最重要的就是跟主流的硬件厂商(Dell, Hp)的支持程度,要么你就直接在网站上挂个『for test only』牌子让大家知道你做 server 只是玩玩?
另外一件搞笑的事,12.04 的 kernel 已经不区分 server 跟 generic 了,官方的理由是『便于维护』。默认的 I/O 调度也变成了 cfq,这需要人肉改回 deadline;preemption model,tickless 等都会改变。好在这些影响对机器的性能应该不会太大。

这里有一篇关于 12.04 的不完全测评,可以参考下。在不大可能换硬件的情况下,或者说即使换了硬件,二者的支持如果不和谐的话,我们只能换发型版本。

结论:戴尔PowerEdge上不适合安装Ubuntu Server

我们在采购戴尔服务器的时候,常常到遇到一些疑问,例如:PowerEdge 12G服务器能部署CentOS Linux不?有没有前辈实际安装实施过?具体版本要求?以及需要注意哪些?

下面风信网整理了一些这方面信息给大家参考,首先大家都知道戴尔官方是支持RHEL的,我们需要明白RHEL和CentOS的关系:

其实为什么有 CentOS? CentOS 与 RHEL 有什么关系?

RHEL 在发行的时候,有两种方式。一种是二进制的发行方式,另外一种是源代码的发行方式。

无论是哪一种发行方式,你都可以免费获得(例如从网上下载),并再次发布。但如果你使用了他们的在线升级(包括补丁)或咨询服务,就必须要付费。

RHEL 一直都提供源代码的发行方式,CentOS 就是将 RHEL 发行的源代码从新编译一次,形成一个可使用的二进制版本。由于 LINUX 的源代码是 GNU,所以从获得 RHEL 的源代码到编译成新的二进制,都是合法。只是 REDHAT 是商标,所以必须在新的发行版里将 REDHAT 的商标去掉。

REDHAT 对这种发行版的态度是:“我们其实并不反对这种发行版,真正向我们付费的用户,他们重视的并不是系统本身,而是我们所提供的商业服务。”

所以,CentOS 可以得到 RHEL 的所有功能,甚至是更好的软件。但 CentOS 并不向用户提供商业支持,当然也不负上任何商业责任。

我正逐步将我的 RHEL 转到 CentOS 上,因为我不希望为 RHEL 升级而付费。当然,这是因为我已经有多年的 UNIX 使用经验,因此 RHEL 的商业技术支持对我来说并不重要。

但如果你是单纯的业务型企业,那么我还是建议你选购 RHEL 软件并购买相应服务。这样可以节省你的 IT 管理费用,并可得到专业服务。

一句话,选用 CentOS 还是 RHEL,取决于你所在公司是否拥有相应的技术力量。

戴尔的官方说法:

所以,我们不难得出一个结论,CentOS其实就是RHEL代码重新编译的一种衍生版的Linux操作系统。我们应该可以从戴尔12代服务器对 RHEL的支持,间接地得出其对应CentOS支持的大致情况。当然这个不能保证100%完全一致,重新编译也可能略有不同,但已十之八九。

请查阅戴尔服务器的系统兼容表:戴尔PowerEdge服务器对操作系统支持的兼容性矩阵表

其中RHEL兼容性查询见:https://hardware.redhat.com/list.cgi?product=Red%20Hat%20Hardware%20Certification&quicksearch=dell&showall=1

通过RHEL的兼容性,我们就可以大致了解CentOS版本对应12G服务器的支持

比如:PowerEdge R720支持RHEL的情况如下,你猜CentOS会怎样呢?

抽空做了个实例测试

我们看出戴尔PowerEdge 12G服务器在RHEL x_64支持6.1 以上版本,所以我的测试特意下载了CentOS发布的最新的x_64 6.4版

ISO文件可以非常方便地在www.centos.org 上下载

测试环境,戴尔标准的PowerEdge R620,H710p,Broadcom BCM5720。

结论:CentOS提供了戴尔PowerEdge R620的系统原生驱动支持(Native driver support),即安装盘已经带有所有需要的驱动,包括PERC,网卡等。过程非常简单,无需额外的周折

yii的relations里self::BELONGS_TO默认是用当前指定的键跟关联表的主键进行join,例如:

Post

1
2
3
return array(
'reply' => array(self::BELONGS_TO, 'BlogPostReply', 'postid'),
);

默认生成的sql类似 on id = postid,id是本表的主键,postid是表BookPostReply的一个字段(主键)

但是需要生成 on BookPostReply.postid = t.postid

关联非主键字段

方法一,改成如下:

1
2
3
return array(
'reply' => array(self::BELONGS_TO, 'BookPostReply', '', 'on' => 't.postid=reply.postid'),
);

方法二:

array(self::BELONGS_TO,’对应的模型’,array(‘本模型的外键’=>’对应模型的主键’))

这样也可以定义的,只要明确指定主键和对应表的外键就可以了。

官方开发指南里是这样写的:

1
array('fkc1'=>'pkc1','fkc2'=>'pkc2')

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

执行关系查询

懒惰导入查询方法

最简单的方法就是为AR对象添加一个关联属性,

例:

// 获取PK为10的POST对象 $post=Post::model()->findByPk(10);

// 获取这个POST的作者 $author=$post->author;

如果没有关联的对象,那么将返回NULL或者一个空数组;BELONGS_TO和HAS_ONE结果为NULL,而HAS_MANY和MANY_MANY返回一个空数组。

上面的这种“懒惰导入”方法使用起来非常方便,但是在一些场景下不是非常的效率,比如,如果我们想访问N个POST的作者的信息,使用这种懒惰导入的方法将会执行N个join查询;

急切导入查询方法

下面介绍是一种“急切导入”方法:在使用find和findAll时,使用with()方法,例:

1
$posts=Post::model()->with('author')->findAll()

这样就可以在一次查询时连同查询其他信息了;with方法可以接受多个关系:

1
$posts=Post::model()->with('author','categories')->findAll();

这样就可以将作者和类别的信息一并进行查询;同样,with还支持多重急切导入

1
$posts=Post::model()->with( 'author.profile', 'author.posts', 'categories')->findAll();

上面的代码不仅会返回autho和categories信息,还会返回作者的profile和posts信息

这种“急切导入”方法也支持CDbCriteria::with,下面这两种实现方式效果一样:

1
2
3
4
$criteria=new CDbCriteria; 
$criteria->with=array( 'author.profile', 'author.posts', 'categories', );
$posts=Post::model()->findAll($criteria);
//or $posts=Post::model()->findAll(array( 'with'=>array( 'author.profile', 'author.posts', 'categories',);

关系查询选项

前面提过,在申明关系时可以添加额外的选项,这些选项都是一些key-value对,是用来定制关系查询的,总结如下:

select

定义从AR类中被select的列集合,如果定义为*,则表示查询所有列

condition

定义where语句,默认为空。

params

生成SQL语句的参数,这个需要用一个key-value对的数组来表示;

on

ON语句,这个条件用来通过AND添加一个joining condintion语句

order

ORDER语句

with

和当前对象一起导出的相关对象列表,要注意如果使用不正确,有可能导致无限死循环;

joinType

定义join的类别,默认为LEFT OUTER JOIN

alias

定义别名,当多个表中有相同的column name时,需要为表格定义alias,然后使用tablename.columnname来指定不同的column

together

这个只在HAS_MANY, MANY_MANY时有用,在实现跨表查询时,可以用这个参数来控制性能。正常用不到,不详细讲述;

join

JOIN语句

group

GROUP语句

having

HAVING语句

index

这个值用来设定返回的结果数组以哪个column做为index值,如果不设定这个值的话,将从0开始组织结果数组。

除此之外还包含下面几个选项,在“懒惰导出”的特定关系时可用limit,返回结果数量的限制,不适用于BELONG_TO关系

offset

offset结果数量的值,不适用于BELONG_TO关系

下面代码,显示上面选项的一些使用:

1
2
3
4
5
6
7
8
class User extends CActiveRecord { 
public function relations() {
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id', 'order'=>'posts.create_time DESC', 'with'=>'categories'),
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
);
}
}

此时,我们使用$author->posts时,会返回固定ORDER的POST信息

Mac OS系统有一个很方便的功能就是文件预览,在Finder中选中一个文件,按下空格键就能够预览其中的内容。支持图片、文档、视频等类型。在iOS4.0系统中,官方SDK提供了一个QLPreviewController,使用它就可以让我们的App在iPhone/iPad中直接预览各个文件了。官方的开发文档中说明其支持的文件类型有:

  1. iWork文档
  2. 微软Office97以上版本的文档
  3. RTF文档
  4. PDF文件
  5. 图片文件
  6. 文本文件和CSV文件

使用方法也很简单,直接alloc出一个QLPreviewController对象,用presentModalViewController方法把它调出来即可。要指定QLPreviewController预览那个文件,只要直接实现它的代理方法previewItemAtIndex,返回一个NSURL对象即可:

1
2
3
4
- (id)previewController:(QLPreviewController *)previewController previewItemAtIndex:(NSInteger)idx
{
return [NSURL fileURLWithPath:[NSString stringWithFormat:@“%@/Documents/files/%@”, NSHomeDirectory(), [fileList objectAtIndex:currentIndex]]];
}

使用GameKit 发送复杂的数据,

通过GameKit以字符串的形式发送颜色信息,这里涉及到一个知识点就是序列化数据和反序列化数据

关于复杂数据的发送有两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(NSString *) stringFromColor
{
const CGFloat *c = CGColorGetComponents(self.CGColor);
CGColorSpaceModel csm = CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
return (csm == kCGColorSpaceModelRGB) ?
[NSString stringWithFormat:@"%0.2f %0.2f %0.2f %0.2f", c[0], c[1], c[2], c[3]]
:
[NSString stringWithFormat:@"%0.2f %0.2f %0.2f %0.2f", c[0], c[0], c[0], c[1]];

}

+(UIColor *) colorWithString: (NSString *) colorString
{
const CGFloat c[4];
sscanf([colorString cStringUsingEncoding:NSUTF8StringEncoding], "%f %f %f %f", &c[0], &c[1], &c[2], &c[3]);
return [UIColor colorWithRed:c[0] green:c[1] blue:c[2] alpha:c[3]];
}

这就是颜色转字符串,字符串转颜色的方法,这里实现支持ios5

这点代码放在了DrawView文件中

整个发送复杂数据的流程如下

ComplexObjectsViewController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// ComplexObjectsViewController.h
// SendingComplexObjects
//
// Created by david on 13-9-6.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "GameKitHelper.h"
#import "DrawView.h"

#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] initWithTitle:TITLE style:UIBarButtonItemStylePlain target:self action:SELECTOR]
#define COLOR_ARRAY [NSArray arrayWithObjects:[UIColor whiteColor], [UIColor lightGrayColor], [UIColor darkGrayColor], [UIColor redColor], [UIColor orangeColor], [UIColor yellowColor], [UIColor greenColor], [UIColor blueColor], [UIColor purpleColor], nil]
#define BASE_TINT [UIColor darkGrayColor]

#define DATAPATH [NSString stringWithFormat:@"%@/Documents/drawing.archive", NSHomeDirectory()]

@interface ComplexObjectsViewController : UIViewController

-(void) archiveInterface;

@end

ComplexObjectsViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//
// ComplexObjectsViewController.m
// SendingComplexObjects
//
// Created by david on 13-9-6.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import "ComplexObjectsViewController.h"

@interface ComplexObjectsViewController ()

@end

@implementation ComplexObjectsViewController

- (void)viewDidLoad
{
[super viewDidLoad];

self.view.backgroundColor = [UIColor blackColor];
self.navigationController.navigationBar.tintColor = BASE_TINT;

//Retrieve (or create) the drawing surface
[self unarchiveInterface];

//Set up the color picking segmented controller
NSMutableArray *items = [NSMutableArray array];
for(UIColor *color in COLOR_ARRAY)
{
[items addObject:[self swatchWithColor:color]];
}

UISegmentedControl *seg = [[UISegmentedControl alloc] initWithItems:items];
seg.tag = 102;
seg.segmentedControlStyle = UISegmentedControlStyleBar;
seg.center = CGPointMake(160.0f, 416.0f - 15.0f);
seg.tintColor = BASE_TINT;
seg.selectedSegmentIndex = 0;
[seg addTarget:self
action:@selector(colorChange:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:seg];

self.navigationItem.leftBarButtonItem = BARBUTTON(@"清除", @selector(doClear));

[GameKitHelper sharedInstance].dataDelegate = self.view;
[GameKitHelper sharedInstance].sessionID = @"Drawing Together";
[GameKitHelper assignViewController:self];

}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

-(UIImage *) swatchWithColor:(UIColor *)color
{
float side = 20.0f;
UIGraphicsBeginImageContext(CGSizeMake(side, side));
CGContextRef context = UIGraphicsGetCurrentContext();
[color setFill];
CGContextFillRect(context, CGRectMake(0.0f, 0.0f, side, side));
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}

//Transmit a clear request to the draw view
-(void) doClear
{
[(DrawView *)[self.view viewWithTag:101] clear];
}

//Transmit a color change request to the draw view
-(void) colorChange:(UISegmentedControl *)seg
{
UIColor *color = [COLOR_ARRAY objectAtIndex:seg.selectedSegmentIndex];
DrawView *dv = (DrawView *)[self.view viewWithTag:101];
dv.currentColor = color;
}

//Save the interface to file
-(void) archiveInterface
{
DrawView *dv = (DrawView *)[self.view viewWithTag:101];
[NSKeyedArchiver archiveRootObject:dv
toFile:DATAPATH];

}

-(void) unarchiveInterface
{
DrawView *dv = [NSKeyedUnarchiver unarchiveObjectWithFile:DATAPATH];
if(!dv)
{
dv = [[DrawView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 416.0f - 30.0f)];
dv.userInteractionEnabled = YES;
dv.tag = 101;
[self.view addSubview:dv];
}
}

@end

这里的GameKitHelper.h和GameKitHelper.m文件可以到这里下载GameKitHelper

Xcode5 试用版发布了, 对于iOS开发者而言,仁者见仁,智者见智。 于我来说, 我更关心的是 storyboard 框架是否突破了原有的禁锢。

说来还有一段故事。我一向是推崇storyboard 框架的, 对于iOS 初学者,若想后浪推前浪,一定要善用storyboard 技术,因为它可以让你在UI实现上事半而功倍。 当Xcode4.2 所推出的storyboard 框架也有其先天的不足。 具体表现在,整个UI视图都容纳到一个storyboard 文件中, 当多人修改这个文件时,会造成凌乱, 无法merge。 从而,storyboard技术从一开始,就被贴上了一个“缺陷”的标签。 那就是,不适用团队协同开发。 可谓一丑遮百俊。

也正是这个诟病, storyboard 框架推行起来,困难重重。 尤其是习惯了通过编码写UI的程序员,更是抵触。

那么,这次Xcode5的发布, 是否对storyboard 框架有所改进呢? 经过测试,果然没人失望,这正是我所期待的。

我们可以做个小实验,以便快速了解storyboard 框架的改进。

  1. 通过Xcode 4.2 或4.5 或4.6 创建一个工程, 在storybaord 文件上,添加一个UIScrollview ,并在UIScrollview 内添加一些常用控件,比如: UILabel、 UIButton 等。 保存。

storyboard 文件,从本质上说,是一个 xib 文件, 更具体地说,是一个XML文件。

选中这个storyboard Open As > Source Code

  1. 通过Xcode5 打开以上工程, 再打开这个storyboard文件

从提示文字可以看出, 当用Xcode5打开这个文件时,这个文件会自动升级, 而且是不可逆的。 这就是意味着, 这个文件结构将发生变化。 选中 “upgrade”, 继续.

这时, 再通过Open As > Source Code, 查看这个storyboard 文件结构。 你会惊喜地发现,xcode5 下的storyboard 文件结构比Xcode4 下,规整了很多, 更为重要的是, 这个XML的代码量大大缩减。 Xcode5下的storyboard 代码值规整,完全可以让你轻松地直接编写xml。

至此,可以说,Xcode4下的storyboard框架之良莠参半,在Xcode5下已达到可圈可点。 它解决了 xib 文件的merge 问题, 从而使得团队协同使用storyboard 框架成为可能。

在了解GameKit这一块的时候,有个是关于监控的,就是监控错误的输出,然后显示,在项目中我们可以巧妙的使用这个技巧来完成错误日志的输出然后提交的给我们,然后我们进行错误日志的分析,来改善我们的项目

这里是选自cookbook的示例

MonitorGameKitViewController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//
// MonitorGameKitViewController.h
// MonitorGameKit
//
// Created by david on 13-9-4.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "GameKitHelper.h"

#define COOKBOOK_PURPLE_COLOR [UIColor colorWithRed:0.20392f green:0.19607f blue:0.61176f alpha:1.0f]
#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] initWithTitle:TITLE style:UIBarButtonItemStylePlain target:self action:SELECTOR]
#define STDERR_OUT [NSHomeDirectory() stringByAppendingPathComponent:@"tmp/stderr.txt"]


@interface MonitorGameKitViewController : UIViewController
@property (strong, nonatomic) IBOutlet UITextView *textView;

@end

MonitorGameKitViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//
// MonitorGameKitViewController.m
// MonitorGameKit
//
// Created by david on 13-9-4.
// Copyright (c) 2013年 WalkerFree. All rights reserved.
//

#import "MonitorGameKitViewController.h"

@interface MonitorGameKitViewController ()

@end

@implementation MonitorGameKitViewController

@synthesize textView;

-(void) listenForStderr: (NSTimer *) timer
{
NSString *contents = [NSString stringWithContentsOfFile:STDERR_OUT
encoding:NSUTF8StringEncoding
error:NULL];
contents = [contents stringByReplacingOccurrencesOfString:@"\n"
withString:@"\n\n"];
if([contents isEqualToString:textView.text])
{
return ;
}
[textView setText:contents];
textView.contentOffset = CGPointMake(0.0f, MAX(textView.contentSize.height - textView.frame.size.height, 0.0f));

}

- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.tintColor = COOKBOOK_PURPLE_COLOR;
[GameKitHelper sharedInstance].sessionID = @"Peeking at GameKit";
[GameKitHelper assignViewController:self];

freopen([STDERR_OUT fileSystemRepresentation], "w", stderr);
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:@selector(listenForStderr:)
userInfo:nil
repeats:YES];
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

@end

关于GameKitHelper的文件,可以到这里下载GameKitHelper,如果想支持IOS5的话可以到这里GameKitHelper

这里重要的一点是利用了错误日志重定向到文件。

字符编码简介

1.1. ASCII

ASCII(American Standard Code for Information Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节可以表示256个不同的字符,可以表示所有的英文字符和许多的控制 符号。不过ASCII只用到了其中的一半(\x80以下),这也是MBCS得以实现的基础。

1.2. MBCS

然而计算机世界里很快就有了其他语言,单字节的ASCII已无法满足需求。后来每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使用了多字节来表示字符,如GBxxx、BIGxxx等等,他们的规则是,如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。

这里,IBM发明了一个叫Code Page的概念,将这些编码都收入囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使用CP936表示GBK。

MBCS(Multi-Byte Character Set)是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某一种特定的编码,Windows里根据你设定的区域不同,MBCS指代不同的编码,而Linux里无法使用 MBCS作为编码。在Windows中你看不到MBCS这几个字符,因为微软为了更加洋气,使用了ANSI来吓唬人,记事本的另存为对话框里编码ANSI就是MBCS。同时,在简体中文Windows默认的区域设定里,指代GBK。

1.3. Unicode

后来,有人开始觉得太多编码导致世界变得过于复杂了,让人脑袋疼,于是大家坐在一起拍脑袋想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。

最初的Unicode标准UCS-2使用两个字节表示一个字符,所以你常常可以听到Unicode使用两个字节表示一个字符的说法。但过了不久有人觉得256*256太少了,还是不够用,于是出现了UCS-4标准,它使用4个字节表示一个字符,不过我们用的最多的仍然是UCS-2。

UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已,比如”汉”这个字的码位是6C49。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。

一开始这事很简单,直接使用UCS的码位来保存,这就是UTF-16,比如,”汉”直接使用\x6C\x49保存(UTF-16-BE),或是倒过来使用\x49\x6C保存(UTF-16-LE)。但用着用着美国人觉得自己吃了大亏,以前英文字母只需要一个字节就能保存了,现在大锅饭一吃变成了两个字节,空间消耗大了一倍……于是UTF-8横空出世。

UTF-8是一种很别扭的编码,具体表现在他是变长的,并且兼容ASCII,ASCII字符使用1字节表示。然而这里省了的必定是从别的地方抠出来 的,你肯定也听说过UTF-8里中文字符使用3个字节来保存吧?4个字节保存的字符更是在泪奔……(具体UCS-2是怎么变成UTF-8的请自行搜索)

另外值得一提的是BOM(Byte Order Mark)。我们在储存文件时,文件使用的编码并没有保存,打开时则需要我们记住原先保存时使用的编码并使用这个编码打开,这样一来就产生了许多麻烦。 (你可能想说记事本打开文件时并没有让选编码?不妨先打开记事本再使用文件 -> 打开看看)而UTF则引入了BOM来表示自身编码,如果一开始读入的几个字节是其中之一,则代表接下来要读取的文字使用的编码是相应的编码:

1
2
3
BOM_UTF8 '\xef\xbb\xbf'      
BOM_UTF16_LE '\xff\xfe'
BOM_UTF16_BE '\xfe\xff'

并不是所有的编辑器都会写入BOM,但即使没有BOM,Unicode还是可以读取的,只是像MBCS的编码一样,需要另行指定具体的编码,否则解码将会失败。

你可能听说过UTF-8不需要BOM,这种说法是不对的,只是绝大多数编辑器在没有BOM时都是以UTF-8作为默认编码读取。即使是保存时默认使 用ANSI(MBCS)的记事本,在读取文件时也是先使用UTF-8测试编码,如果可以成功解码,则使用UTF-8解码。记事本这个别扭的做法造成了一个 BUG:如果你新建文本文件并输入”姹塧”然后使用ANSI(MBCS)保存,再打开就会变成”汉a”,你不妨试试 :)

Python2.x中的编码问题

2.1. str和unicode

str和unicode都是basestring的子类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列。对 UTF-8编码的str’汉’使用len()函数时,结果是3,因为实际上,UTF-8编码的’汉’ == ‘\xE6\xB1\x89’。

unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得,并且len(u’汉’) == 1。

再来看看encode()和decode()两个basestring的实例方法,理解了str和unicode的区别后,这两个方法就不会再混淆了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# coding: UTF-8
u = u'汉'
print repr(u) # u'\u6c49'
s = u.encode('UTF-8')
print repr(s) # '\xe6\xb1\x89'
u2 = s.decode('UTF-8')
print repr(u2) # u'\u6c49'
# 对unicode进行解码是错误的

# s2 = u.decode('UTF-8')

# 同样,对str进行编码也是错误的

# u2 = s.encode('UTF-8')

需要注意的是,虽然对str调用encode()方法是错误的,但实际上Python不会抛出异常,而是返回另外一个相同内容但不同id的str; 对unicode调用decode()方法也是这样。很不理解为什么不把encode()和decode()分别放在unicode和str中而是都放在 basestring中,但既然已经这样了,我们就小心避免犯错吧。

2.2. 字符编码声明

源代码文件中,如果有用到非ASCII字符,则需要在文件头部进行字符编码的声明,如下:

1
#-*- coding: UTF-8 -*-

实际上Python只检查#、coding和编码字符串,其他的字符都是为了美观加上的。另外,Python中可用的字符编码有很多,并且还有许多别名,还不区分大小写,比如UTF-8可以写成u8。参见http://docs.python.org/library/codecs.html#standard-encodings

另外需要注意的是声明的编码必须与文件实际保存时用的编码一致,否则很大几率会出现代码解析异常。现在的IDE一般会自动处理这种情况,改变声明后同时换成声明的编码保存,但文本编辑器控们需要小心 :)

2.3. 读写文件

内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。write()写入时,如果 参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(), 转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件 声明的字符编码进行编码然后写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding: UTF-8
f = open('test.txt')
s = f.read()
f.close()
print type(s) # <type 'str'>
# 已知是GBK编码,解码成unicode
u = s.decode('GBK')

f = open('test.txt', 'w')
# 编码成UTF-8编码的str
s = u.encode('UTF-8')
f.write(s)
f.close()

另外,模块codecs提供了一个open()方法,可以指定一个编码打开文件,使用这个方法打开的文件读取返回的将是unicode。写入时,如 果参数是unicode,则使用open()时指定的编码进行编码后写入;如果是str,则先根据源代码文件声明的字符编码,解码成unicode后再进 行前述操作。相对内置的open()来说,这个方法比较不容易在编码上出现问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# coding: GBK
import codecs
f = codecs.open('test.txt', encoding='UTF-8')
u = f.read()
f.close()
print type(u) # <type 'unicode'>

f = codecs.open('test.txt', 'a', encoding='UTF-8')
# 写入unicode
f.write(u)

# 写入str,自动进行解码编码操作
# GBK编码的str
s = '汉'
print repr(s) # '\xba\xba'
# 这里会先将GBK编码的str解码为unicode再编码为UTF-8写入
f.write(s)
f.close()

2.4. 与编码相关的方法

sys/locale模块中提供了一些获取当前环境下的默认编码的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# coding:gbk
import sys
import locale

def p(f):
print '%s.%s(): %s' % (f.__module__, f.__name__, f())

# 返回当前系统所使用的默认字符编码
p(sys.getdefaultencoding)

# 返回用于转换Unicode文件名至系统文件名所使用的编码
p(sys.getfilesystemencoding)

# 获取默认的区域设置并返回元祖(语言, 编码)
p(locale.getdefaultlocale)

# 返回用户设定的文本数据编码
# 文档提到this function only returns a guess
p(locale.getpreferredencoding)

# \xba\xba是'汉'的GBK编码
# mbcs是不推荐使用的编码,这里仅作测试表明为什么不应该用
print r"'\xba\xba'.decode('mbcs'):", repr('\xba\xba'.decode('mbcs'))

#在笔者的Windows上的结果(区域设置为中文(简体, 中国))
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'

一些建议

3.1. 使用字符编码声明,并且同一工程中的所有源代码文件使用相同的字符编码声明。

这点是一定要做到的。

3.2. 抛弃str,全部使用unicode。

按引号前先按一下u最初做起来确实很不习惯而且经常会忘记再跑回去补,但如果这么做可以减少90%的编码问题。如果编码困扰不严重,可以不参考此条。

3.3. 使用codecs.open()替代内置的open()。

如果编码困扰不严重,可以不参考此条。

3.4. 绝对需要避免使用的字符编码:MBCS/DBCS和UTF-16。

这里说的MBCS不是指GBK什么的都不能用,而是不要使用Python里名为’MBCS’的编码,除非程序完全不移植。

Python中编码’MBCS’与’DBCS’是同义词,指当前Windows环境中MBCS指代的编码。Linux的Python实现中没有这种 编码,所以一旦移植到Linux一定会出现异常!另外,只要设定的Windows系统区域不同,MBCS指代的编码也是不一样的。分别设定不同的区域运行 2.4小节中的代码的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#中文(简体, 中国)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'

#英语(美国)
#sys.getdefaultencoding(): UTF-8
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp1252')
#locale.getpreferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'

#德语(德国)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp1252')
#locale.getpreferredencoding(): cp1252
#'\xba\xba'.decode('mbcs'): u'\xba\xba'

#日语(日本)
#sys.getdefaultencoding(): gbk
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp932')
#locale.getpreferredencoding(): cp932
#'\xba\xba'.decode('mbcs'): u'\uff7a\uff7a'

可见,更改区域后,使用mbcs解码得到了不正确的结果,所以,当我们需要使用’GBK’时,应该直接写’GBK’,不要写成’MBCS’。

UTF-16同理,虽然绝大多数操作系统中’UTF-16’是’UTF-16-LE’的同义词,但直接写’UTF-16-LE’只是多写3个字符而 已,而万一某个操作系统中’UTF-16’变成了’UTF-16-BE’的同义词,就会有错误的结果。实际上,UTF-16用的相当少,但用到的时候还是 需要注意。

1,Setting flash messages(设置)

A flash message is used in order to keep a message in session through one or several requests of the same user.

1
Yii::app()->user->setFlash('success', "数据设置成功");

2,Displaying flash messages(显示)

To check for flash messages we use the hasFlash() Method and to obtain the flash message we use the getFlash() Method. Since Yii v1.1.3, there is also a method getFlashes() to fetch all the messages.

1),静态显示

1
2
3
4
5
<?php if(Yii::app()->user->hasFlash('success')):?>
<div class="info">
<?php echo Yii::app()->user->getFlash('success'); ?>
</div>
<?php endif; ?>

全部显示

1
2
3
4
5
6
7
8
$flashMessages = Yii::app()->user->getFlashes();
if ($flashMessages) {
echo '<ul class="flashes">';
foreach($flashMessages as $key => $message) {
echo '<li><div class="flash-' . $key . '">' . $message . "</div></li>\n";
}
echo '</ul>';
}

2),动态显示

1
2
3
4
5
Yii::app()->clientScript->registerScript(
'myHideEffect',
'$(".info").animate({opacity: 1.0}, 3000).fadeOut("slow");',
CClientScript::POS_READY
);

With these lines of code we register a piece of jQuery (already included with YII) javascript code, using ‘myHideEffect’ as ID. It will be inserted in the jQuery’s ready function (CClientScript::POS_READY).

If you’ve ever come across the infuriating error

1
htmlspecialchars(): Invalid multibyte sequence in argument

I have a simple solution for you: Turn display_errors on in your php.ini file!
It turns out there’s a weird bug that doesn’t appear to be getting fixed any time soon that causes htmlspecialchars() to display this error only when display_errors is set to Off.

0%