404频道

学习笔记

2.1 默认构造函数的生成

只有当编译器需要默认构造函数的时候才会合成默认构造函数,并不是类只要没有定义默认构造函数编译器就会合成默认构造函数,而是只有以下四种情况编译器会生成默认构造函数。编译器合成的默认构造函数仅会处理类的基类对象和类中的数据成员对象,对于类中的普通类型的非静态数据成员并不会作任何处理。比如类中一个指针类型的数据成员,编译器合成的默认构造函数不会对该指针作任何处理,该指针就是一个野指针。

带有默认构造函数的类成员对象

一个类没有定义任何构造函数,该类中包含了一个带有默认构造函数(包括了合成的默认构造函数和定义的默认构造函数)的类成员对象,那么编译器需要为此类合成一个默认构造函数,合成默认构造函数的时机为该构造函数被调用时。合成的默认构造函数默认为内联函数,如果不适合使用内联函数,就合成explicit static的构造函数。

默认构造函数、复制构造函数和赋值操作符的生成都是inline。inline函数有静态链接,不会被当前文件之外的文件看到。如果函数过于复杂不适合生成inline函数,会生成一个explicit non_inline static实体。

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
class Dopey
{
public:
Dopey();
};

class Sneezy
{
public:
Sneezy(int);
Sneezy();
};

class Bashful
{
public:
Bashful();
};

class Snow_White
{
public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;

private:
int mumble;
};

void foo()
{
Snow_White snow_white;
}

在上述例子中,foo()中需要调用Bashful的构造函数,编译器会为Bar类生成内联的默认构造函数。Bashful类会生成类似于下面的默认构造函数。

1
2
3
4
5
6
inline Bar::Bar()
{
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy();
bashful.Bashful::Bashful();
}

默认构造函数的生成原则为:如果类A中包含了一个或一个以上的类成员对象,那么类A的默认构造函数必须调用每一个类成员的默认构造函数。但是不会初始化普通类型的变量,因此在上例中必须手动初始化mumble变量。在编译器合成的默认构造函数中类成员变量的默认构造函数的调用次序为成员变量在类中的声明顺序,该顺序和类成员的构造函数初始化列表顺序是一致的。

如果Snow_White类定义了如下的默认构造函数,则编译器会自动在定义的构造函数中增加调用类成员变量的代码,调用类成员变量相应构造函数的顺序仍然和类成员变量在类中的声明顺序一致。

从中可以看出类成员变量的构造函数的调用要早于类构造函数的调用,这一点是在很多面试题中经常会见到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义的默认构造函数,包含了类成员变量sneezy的初始化列表
Snow_White::Snow_White() : sneezy(1024)
{
mumble = 2048;
}

// 编译器扩张后的默认构造函数
Snow_White::Snow_White() : sneezy(1024)
{
dopey.Dopey::Dopey(); // 调用默认构造函数
sneezy.Sneezy::Sneezy(1024); // 自动调用合适的构造函数
bashful.Bashful::Bashful();
mumble = 2048;
}

带有默认构造函数的基类

在继承机制中,一个没有构造函数的子类继承自带有默认构造函数的基类,则子类的构造函数会被合成,并且会调用基类的默认构造函数。若子类没有定义默认构造函数,却定义了多个带参数的构造函数,编译器会扩张所有自定义的构造函数,将调用基类默认构造函数的代码添加到子类的构造函数的最前面。

从这里可以看出继承机制中,首先构造基类,后构造子类,这点也是面试题中经常遇到的。

带有虚函数的类

为了实现虚函数或虚继承机制,编译器需要为每一个类对象设定vptr(指向虚函数表的指针)的初始值,使其指向一个vtbl(虚函数表)的地址。如果类包含构造函数则编译器会生成一些代码来完成此工作;如果类没有任何构造函数,则编译器会在合成的默认构造函数中添加代码来完成此工作。

带有虚基类的类

需要维护内部指针来实现虚继承。

2.2 复制构造函数的生成

复制构造函数被调用有三种情况:

  • 明确的一个对象的内容作为另外一个对象的初始值。如X xx = x或X xx(x)。
  • 对象作为参数传递给函数时。
  • 类对象作为函数返回值时。

合成复制构造函数的情况

如果一个类没有提供显式的复制构造函数,同默认构造函数一样,只有编译器认为需要合成复制构造函数时,编译器才会合成一个。那么问题来了,什么时候编译器才合成复制构造函数呢?书中给出的答案为当一个类不展现出_bitwise copy semantics_1的时候。具体来说有以下四种情况,跟类的默认构造函数的合成基本一致。

  • 当类内包含一个类成员变量且类成员变量声明了复制构造函数。
  • 当类继承的基类有复制构造函数(复制构造函数可以是显示声明或合成的)
  • 一个类中包含了一个或多个虚函数
  • 类继承自一个或多个虚基类

其中前面两种情况必须将成员变量或基类的复制构造函数的调用插入到合成的复制构造函数中,因此不是按照按比特复制的。第三和第四点分别用下面两小节来说明。

重新设定虚函数表的指针

当编译器需要在类对象中设定一个指向虚函数表的指针时,该类就不能再使用按位复制的复制构造函数了。

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
class ZooAnimal
{
public:
ZooAnimal();
virtual ~ZooAnimal();

virtual void animate();
virtual void draw();
};

class Bear : public ZooAnimal
{
public:
Bear();
void animate();
void draw();
virtual void dance();
};
void foo()
{
// yogi的vptr指向Bear的虚函数表
Bear yogi;
// franny的vptr指向ZooAnimal的虚函数表
ZooAnimal franny = yogi;
draw(yogi); // 调用Bear::draw()
draw(franny); // 调用ZooAnimal::draw()
}

Image Title

合成出来的ZooAnimal的复制构造函数会明确设定对象的vptr指向ZooAnimal的虚函数表,而不是从右值中复制过来的值。

处理virtual base class subobjects

虚基类的存在需要特别处理,一个类对象如果以另外一个类对象作为初始值,而后者有一个virtual base class subobjects,也会使按比特复制的复制构造函数失效。

每一个编译器都必须让派生的类对象的virtual base class subobjects位置在执行期准备完毕。按比特复制的复制构造函数可能会破坏virtual base class subobjects的位置,因此编译器必须在自己合成出来的复制构造函数中修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ZooAnimal
{
public:
ZooAnimal();
virtual ~ZooAnimal();

virtual void animate();
virtual void draw();
};

class Raccoon : public virtual ZooAnimal
{
public:
Raccoon();
Raccoon(int val);
};

class RedPanda : public Raccoon
{
public:
RedPanda();
RedPanda(int val);
};

文章的内容没有完全理解,虚继承机制使用较少,可以暂时不用理解。

2.3 程序转化语意学

本节涉及到了编译器优化的相关细节,由于较容易理解,可以直接看书上内容,对工作帮助不大。包括类对象的初始化优化,函数参数的初始化优化,函数返回值的初始化优化,使用者层面的优化和编译器层面的优化。

如果不是上节指定的四种情况,不需要显示的声明复制构造函数,因为显示的声明的复制构造函数往往效率不如编译器合成的复制构造函数效率高。编译器合成的复制构造函数利用memcpy()或memset()函数来合成,效率最高。

2.4 类成员的初始化列表

说到类成员的初始化列表必然想起一个经常出现的面试题:成员初始化列表的顺序是按照成员变量在类中声明的顺序。如果成员初始化列表的顺序和成员变量在类中声明的顺序不一致时某些编译器会提示警告。编译器将成员初始化列表的代码插入到构造函数的最开始位置,优先级跟调用类类型的成员变量的默认构造函数是一致的,都是跟类类型成员变量在类中的声明次序相关。

类成员初始化必须使用成员初始化列表的四种方式:

  • 初始化一个引用类型的成员变量
  • 初始化一个const的成员变量
  • 调用基类的构造函数,且基类的构造函数采用成员初始化列表的方式
  • 调用类成员的构造函数,且类成员的构造函数采用成员初始化列表的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
class  Word
{
public:
Word()
{
_name = 0;
_cnt = 0;
}

private:
String _name;
int _cnt;
};

此例子在构造函数中对成员变量进行测试,编译器对构造函数的扩张方式可能会生成如下的伪码:

1
2
3
4
5
6
7
8
Word::Word()
{
_name.String::String();
String temp = String(0);
_name.String::operator=(temp);
temp.String::~String();
_cnt = 0;
}

构造函数中生成了一个临时性的String对象,这浪费了一部分开销。如果将构造函数该成如下的定义方式:

1
2
3
4
Word() : _name(0)
{
_cnt = 0;
}

即将其中的类成员变量更改为成员初始化列表的方式来初始化,编译器会自动将构造函数扩张为如下方式,这样减少了临时对象,提供了程序效率。

1
2
3
4
5
Word::Word()
{
_name.String::String(0);
_cnt = 0;
}

引申

下面例子是对本章内容的一个简单概况,也是面试题中经常碰到的。

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
class A
{
public:
A()
{
printf("A\n");
}

~A()
{
printf("~A\n");
}
};

class B
{
public:
B(int n)
{
printf("B_%d\n", n);
}
~B()
{
printf("~B\n");
}
};

class Base
{
public:
Base()
{
printf("Base\n");
}

virtual ~Base()
{
printf("~Base\n");
}
};

class Derived : public Base
{
public:
Derived() : _m(1), _b(_m)
{
printf("Derived\n");
}

~Derived()
{
printf("~Derived\n");
}

int _m;

// 下面两个类类型的成员遍历的构造函数的调用次序跟在类中的声明次序是相关的
B _b; // 类类型的类成员变量,初始化列表中包含该变量
A _a; // 类类型的类成员变量
};

int main()
{
// 调用基类的构造函数->调用子类类类型成员变量的构造函数->调用子类的构造函数
Derived derived;
return 0;
// 根据栈的特点,类析构的次序跟构造是相反的
}

上述代码执行的结果为:

1
2
3
4
5
6
7
8
Base
B_1
A
Derived
~Derived
~A
~B
~Base

总结

本章讲述了合成的默认构造函数、合成的复制构造函数和构造函数的成员初始化列表。其中如果类没有定义默认构造函数,只有在文中提到的四种情况下编译器才会合成默认构造函数。合成的复制构造函数在需要的时候编译器就会生成,默认是按对象比特复制的方式实现,有四种情况下编译器是不按照比特复制的方式。


[1] bitwise copy semantics书中翻译为“位逐次拷贝”,就是按照内存中的字节进行复制类,感觉翻译不如不翻译好理解。

C++对象模型是深入了解C++必须掌握知识,而《深度探索C++对象模型》一书基本是理解C++对象模型的必须之作。可惜本书看起来更像是作者Stanley B.Lippman的随笔,语言诙谐,跟作者的另外一本经典之作《C++ Primer》有着天壤之别,侯捷的翻译也是晦涩难懂,跟侯捷翻译的其他作品也有一定差距(这两位大师还真是凑到一块了),所以这本书看起来还是很吃力的。这里挑选文中重点记录笔记,忽略扯淡部分,以备忘。

C++的额外开销

C++相比C语言多出了封装、继承和多态等特性,新特性的增加必然会以牺牲一部分性能为代价。额外开销主要是由virtual引起的,包括:

  • 虚函数机制:需要在运行期间动态绑定。
  • 虚基类:多次出现在继承中的基类有一个单一且被共享的实体。

C++对象模型

C++类成员变量包括:静态成员变量和非静态成员变量。成员函数包括:静态成员函数、非静态成员函数和虚函数。

单一类的对象模型

1
2
3
4
5
6
7
8
9
10
11
12
class Point {
public:
Point(float xval);
virtual ~Point();
float x() const; /* 非静态成员函数 */
static int PointCount(); /* 静态成员函数 */

protected:
virtual ostream& print(ostream &os) const; /* 虚函数 */
float _x; /* 非静态成员变量 */
static int _point_count; /* 静态成员函数 */
}

该类的c++对象模型如下图:
Image Title

通过图中可以看出:

  • 非静态数据成员直接放到了类的对象里面。
  • 静态数据成员放到所有的类对象的外面,即静态存储区。
  • 静态和非静态的成员函数放在类对象之外,即代码区。
  • 如果类中存在虚函数,则会产生一个虚函数表(vtbl),表中的表项存储了指向虚函数的指针。在类对象的开始位置添加一个指向虚函数表的指针(vptr)。vptr的赋值由类的构造函数和赋值运算符自动完成。
  • 虚函数表的第一项指向用来作为动态类型识别用的type_info对象。

C++支持的编程范式(programming paradigms)

  • 程序模型:通俗的理解成C语言的面向过程编程方式。
  • 抽象数据类型模型:通过类封装成为了一种新的数据类型,该数据类型有别于基本数据类型。
  • 面向对象模型:利用封装、继承和多态的特性。

C++支持多态的方式

  • 隐含的转换操作,例如通过父类的指针指向子类的对象。shape *ps = new circle();
  • 通过虚函数机制。
  • 通过dynamic_cast强制类型转换。如if (circle *pc = dynamic_cast<circle*>(ps))

类对象的内存构成

  • 非静态数据成员。
  • 由于内存对齐而添加到非静态数据成员中的空白。
  • 为了支持虚机制(包括:虚函数和虚继承)而额外占用的内存。

利用工具查看对象模型

查看C++类的对象模型有两种比较简便的方式,一种是使用Virtual Studio在调试模式下查看对象的组成,但是往往显示的对象不全;另外一种是通过Virtual Studio中cl命令来静态查看。

本文选择使用cl工具的方式,cl命令位于C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin目录下,为了方便使用可以将该变量添加到Path环境变量中。在命令行中执行cl命令,提示“计算机中丢失mspdb80.dll”,该文件位于C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE目录下,将msobj80.dll,mspdb80.dll,mspdbcore.dll,mspdbsrv.exe四个文件复制到C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin目录下即可。

通过cl xxx.cpp /d1reportSingleClassLayoutXX命令即可查看文件中类的对象模型,其中该命令最后的XX需要更换为要查看的类名,中间没有空格。

执行上述命令时提示:无法打开文件“LIBCMT.lib”。该文件位于C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib目录下,将该目录添加到环境变量lib中。重新打开命令行执行cl提示:无法打开文件“kernel32.lib”,将C:\Program Files\Microsoft SDKs\Windows\v6.0A\Lib添加lib环境变量中。

本程序将讲解java调用C语言写的二进制文件,并将二进制文件中的内容利用Java读出。

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union data
{
int inter;
char ch;
};

struct Test
{
int length;
char arr[20];

void toBigEndian()
{
union data c;
c.inter = 1;
if(c.ch == 1)
{
// 小端
unsigned char temp;
unsigned char *tempData = (unsigned char *)&length;
for (int i=0; i < sizeof(int) / 2; i++)
{
temp = tempData[i];
tempData[i] = tempData[sizeof(int) - i - 1];
tempData[sizeof(int) - i - 1] = temp;
}
}
}
};

int main()
{
Test test;
memset(&test, 0, sizeof(Test));
test.length = 0x12345678;
strcpy(test.arr, "hello world");
test.toBigEndian();
FILE *file = fopen("test.txt", "w+");
fwrite(&test, sizeof(Test), 1, file);
fclose(file);
return 1;
}

本例子中的C程序将一个包含int变量和char数组的结构体写入文件中。

其中需要考虑到机器的大小端问题,java程序采用的大端字节序,因此这里将C的结构体在写入文件时转换成大端字节序。在将结构体写入到文件时,将其中的int类型变量转换成大端字节序,如果机器本身即为大端字节序则不需要转换字节序。

java端读取的文件代码如下:

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
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


public class Main {

public static int byte2int(byte[] res) {
int targets = (res[3] & 0xff) | (res[2] << 8) | (res[1] << 16) | (res[0] << 24);
return targets;
}

/**
* @param args
*/
public static void main(String[] args) {
File file = new File("test.txt");
if (!file.exists()) {
System.out.println("文件不存在");
return;
}

byte[] data = new byte[50];

try {
FileInputStream fis = new FileInputStream(file);
int size = fis.read(data);
System.out.println("读取到" + size + "个字节的数据");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

// 转换完成的int值
int value = byte2int(data);
System.out.printf("%x\n", value);


StringBuffer sb = new StringBuffer();
for (int i=4; i<24; i++) {
System.out.print((char)data[i]);
}
}
}

Image Title

药是什么?药是人类文明的发展过程中不断克服自身疾病的必然产物。谁让外星人造人的时候没有把人类制造的那么完美,要是人类除了生老死之外没有病这个状态,或许药物就不会产生。

现实生活中,过度依赖药物的人比比皆是,尤其是在中国。在中国,药物已经用到了满天飞的程度了。得个小感冒会去药店买药,医生除了推荐你感冒药之外,肯定会推荐你消炎药。以至于大众普遍认为,有炎症必须吃消炎药。感冒烧到38度,医院医生会推荐你打针。感冒烧到38.5度,医生会推荐你挂上三天吊瓶,美其名曰挂吊瓶好的快,如果患者的心理能起到恢复快的作用的话,那么挂吊瓶应该是管用的。

我没去过西方国家,但我猜测在西方国家,医药分离的占多数。得病了想吃个消炎药解解馋可没那么容易,消炎药岂能是想吃就吃,没有医生的处方药店怎敢卖给患者,又不是口香糖。想打个针过过瘾更是难了,喜欢花钱让针扎着屁股玩的活中国人比较喜欢。要想挂个吊瓶数滴答玩,除非烧到了40度一星期高烧不退,否则还是自己在家挂瓶自来水数着玩吧。以上有杜撰的成分,但是话激理不歪,你懂得。

要知道中国较西方的文明程度还有差距。西方发明的西药在西方都谨慎使用了,反倒在中国大行其道。谁家没一抽屉瓶瓶罐罐,这可都是毒药啊。除了可以拉动GDP外,有理由怀疑中国担心未来人口老龄化加剧给经济发展造成压力而采取的措施。

是时候阐述本文的观点了,药物不是万能的,能少用尽量少用。

中药讲究是药三分毒,换成西药是药五分毒应该毫不夸张吧。当时治好了我们身体的病症,感觉一身轻松了,但是后患无穷。人类本身就具备一定的修复能力,而且修复能力很强。在人类自我修复能力许可的范围内非要硬用药物加速治疗,那造成的必然结果就是身体下次不干了,有药物可以对抗疾病,那用身体的修复能力干嘛。久而久之,身体就再也扛不住疾病了。

吃西药多了往往治的病好了,却带来了其他疾病。比如感冒了吃消炎药,吃着吃着把胃给吃坏了。治感冒的医生才不会管你的其他身体部位情况,反正胃吃坏了找不到他。这也是医院分那么多科室的一个弊端,只从局部看问题,不能从整体上看问题,所以看到的问题总是片面的。

解决药物依赖的问题从三方面着手解决。一方面要从心理上战胜自己,不要迷信药物,更不能依赖药物。另一方面,多运动,生命在于运动,真理中的真理。第三,眼光放长远,多看其他国家,多了解长寿的人是怎么生存的。

真心希望过度依赖药物的人可以走出这个误区。

本文摘录《C/C++程序员面试宝典》一书中我认为需要注意的地方。

数组指针与指针数组的区别

数组指针即指向数组的指针,定义数组指针的代码如int (*ap)[2],定义了一个指向包含两个元素的数组的数组指针。
如果数组的每一个元素都是一个指针,则该数组为指针数组。实例代码如下:

1
char *chararr[] = {"C", "C++", "Java"};

int (*ap)[2]int *ap[2]的区别就是前一个是数组指针,后一个是指针数组。因为”[]”的优先级高于”*”,决定了这两个表达式的不同。

public、protected、private

修饰成员变量或成员函数

  • public:可以被该类中的函数、子类的函数、友元函数和该类的对象访问。
  • protected:可以被该类中的函数、子类的函数和友元函数访问,不能被该类的对象访问。
  • private:只能由该类中的函数或友元函数访问。
  • 默认为private权限。

用在继承中

  • public:基类成员保持自己的访问级别,基类的public成员为派生类的public成员,基类的protected成员为派生类的protected成员。
  • protected:基类的public和protected成员在派生类中未protected成员。
  • private:基类的所有成员在派生类中为private成员。

Image Title

今天下午新办的工资银行卡办下来了,原先工资卡用的招商银行的金卡,现在新办了个农村信用社的普通卡,有种从城市的大马路走到了胡同里的城中村的感觉。于是大家高高兴兴的蹦蹦跳跳的屁颠屁颠的去领来的新的工资卡。为什么大家会高兴呢?这也是本文的因子,因为大家都已经有两个月没发工资了,新卡到说明工资在不久的将来也会到,大家当然高兴啦。

可是深入想一下,大家真的应该高兴吗?工资本来就是公司单方面拖欠大家的,给大家造成的损失员工宽宏大量没有跟公司计较也就罢了。工资关系到民生,直接影响了员工每个人的生活。要是搁到法国估计一天不发工资,员工早就集体罢工了吧。换卡势必会造成大家的麻烦,原来的银行卡可能就要销户,新的银行是否足够方便。我们却是心宽体胖,公司的错既往不咎,只要看到发工资的希望我照样努力为公司奉献。

这就好比是在中国假期几天高速路不收费,有车一族就喜出望外,“在中国真幸福,可以赚到国家便宜了”。殊不知,世界已建成14万公里收费公路,10万公里在我国。不知道了解这个之后,有车一族还笑得出来吗?我估计连哭的心态都有了。

那大家应该是什么心态去领工资卡呢?平常心就好。至少不至于表现出屁颠屁颠的心态吧。

中国两千多年的封建统治对人民的思想影响是深远的,人们心中早就建立起了人分三六九等的等级观念。而且这种等级观念影响是深远的,悄无声息的深入到我们所谓的“社会主义国家”内部的各个角落。电视里整天的宫廷剧,格格与皇阿玛齐飞,太监共丫鬟一色,这些可是典型的民权不公,民生不平。我们可是就靠这些文化垃圾养大的,心里怎么会没有了奴隶的思想呢?百姓心中的怕官、傍官的思想及考官的行为直接复制到了现在不曾改变,奴隶的心态也就不会改变。

好在现代文明来了,互联网时代来了。西方的文明渐渐深入,我们有的救。打倒xx,打倒xx,期待来一场轰轰烈烈的革新吧。

如果说奴隶的心态为“给你点阳光你就灿烂,给你点洪水你就泛滥”,期待我们的心态变成“没有阳光也要比比灿烂,没有洪水也要试图泛滥”。

题图:《被释放的姜戈》电影海报

Image Title

这是来自一个保守主义者的geeker的傻X的吐槽。

最近股市暴跌,我却在思考为何有股票这个东西。我从未入市且对股市一点兴趣都没有甚至是反感。我所认识的股市游戏规则就是价位低的时候大股东入市,价位高的时候大股东抛售,大股东一吐一吞钱到手了,股民被掏空了。很多股民却用侥幸心理来入市,有赔有赚,出市的时候裤子都赔进去了。

搞不懂那么多经济学专家、博士、硕士、学士,却搞不定一个经济学。若是真搞不懂,索性就不要去研究,反正学了也白学,浪费这个资源误人歧途干啥。一个砖家说熊,另一个砖家说牛,相互对掐有意思吗?

为什么搞股市这个东西,这是嫌经济学不够复杂吗?股市难道能推动人类社会的进步?股市是企业融资的一种手段,而能上市的公司往往是相对不太缺钱的,而最缺钱的是小型创业公司。这也就早就了很多公司把上市套现作为了一个目标,这不明摆着投机取巧,一夜暴富。传销是集合了底层的力量资金而构成了金字塔,企业通过上市手段获取到了股民的资本来运作而发展自身,只不过金字塔只有两层罢了。

假如没有股市,很多上班族就会坐在办公桌上安心工作,而不用时时刻刻关心着像过山车一样的死难看的折线,也不用设个老板键提心吊胆的担心自己的boss悄悄走到自己的面前。难道安安心心全身心投入工作不是更好?

假如没有股市,或许就少了一个行业,一部分人就可以全身心投入到其他行业,带动其他行业的发展,推动历史进步,岂不快哉!

假如没有股市,就不会有人赚发了之后,别人也总想着不劳而获,间接助长了人们投机取巧的气焰。

假如没有股市,就不会因此而发家,当然这是少数。郭美美的母亲也不会用4万块钱赚到100万,也不会有郭美美的今天,也不会有红十字会的今天,当然这只是个笑话。没有股市郭照样可以炫富,因为人家本来就不是靠的股市。没有股市红十字会照样会没落,因为他们的本质就是那样。

假如没有股市,就不会有人因为股市而跳楼的新闻,就不会有人将养老钱都搭进去了,就不会…

请不要职责我,因为我是一个傻X,永远不要和傻子讲道理,否则你也会变成一个傻X!

最近有同事想离职了,上午在跟领导一番谈话之后又有些犹豫了。大概领导会说些公司状况及个人发展之类的话语来说服同事不要离职,用大腿想想都知道的事。同事肯定会说自己的几个抱怨,领导肯定会一一还击,到最后领导领导一摊手“看没什么顾虑的吧,年轻人就是冲动,你再想想吧,先回去好好干活吧”。

我在这里想说的事不要管别人怎么想,做自己,坚持自己的想法。

一千个人眼里有一千个郭美美。有人说郭美美的自拍照真漂亮,有人说郭美美卸妆之后直接毁三观,有人说是郭美美毁掉了红十字会,有人说没有郭美美红十字会一样玩完,有人说郭美美炫富就是为了出名,有人说郭美美是偶像,有人说郭美美是呕像,有人说郭美美为咱们的游戏代言应该很棒,有人说…

同一件事情在不同人眼里就会有不同的看法。当然我的观点,在别人看来也许是那么的反感或不屑一顾。

做技术的天天跟机器打交道,往往语言表达能力较弱,不善辩,更别提忽悠别人了。经过领导只言片语的轰炸,自己已经是茫然不知所措,自己最初的坚持在渐渐退去,别人的想法正在填充你的大脑,恭喜你被洗脑了。但是这种洗脑往往是暂时性的,往往当是生效,事后理性分析一下觉得“不对啊,我当时怎么了”。所以搞传销的一培训就是在一个鸟拉屎的地方培训上几个月来彻底洗脑。

我还好,至少我会坚持自己的观点。记得我上次离职时,领导找我谈话,领导叽里呱啦说了很多,我明知道不对头,但却愣是无力反驳,那时候心里的就想说“求求你,别说了”。

离职对同事自身而言是好事。同事工作两年,在公司工作一年多了,由于公司业绩差,工资非但没有涨过,最近甚至都拖欠了一个月的工资了,而且如果待在公司未来一年内都不涨工资的可能性极大。换个工作薪水可以提高,更可以接触到新鲜的技术,对自己无论在物质还是在技术上都是有利无害。我可以想到的唯一坏处就是舍不得公司,舍不得同事,舍不得那个靠窗的小办公桌。可是这些算个屁,公司的发展跟普通员工毛关系都没有,公司好坏员工拿到的都是稀薄的工资,何况公司已经发不下工资来了,是公司待你不厚道。感情在涉及到金钱后往往变得一文不值。

就公司而言是坏事。公司恨不得不发你工资光给公司干活。领导之所以留你是因为你对公司的业务熟悉,公司如果再重新招人没个一个月的时间很难上手。假如换成了在公司里天天闲的蛋疼的人提离职,公司可能就巴不得立马卷铺盖走人。领导留人只能说明领导对你的肯定,其他的说明不了什么。公司的前途未卜,领导凭什么忽悠员工留下来?

这是一个相互炒鱿鱼的时代。员工跟公司之间是对等的关系,公司对员工不满意可以解除合同,员工对公司不满意照样可以将公司炒掉换个新鲜的工作。

做自己,坚持自己的观点,适当聆听别人的建议,才会赢!

本文是翻译的solrcloud的官方英文文档,本文仅将文中重点翻译,原文地址点这里。英文水平不咋地,翻译篇文章也算练练手。

Image Title

SolrCloud

SolrCloud是Solr的分布式集群。可以通过集群来搭建一个高可用性,容错性的Solr服务。当想搭建一个大规模,容错性,分布式索引,查询性能好的Solr服务时可以采用SolrCloud。

关于SolrCores和Collections的一点小知识

在单机运行时,单独的索引叫做SolrCore。如果想要创建多个索引,可以创建多个SolrCore。利用SolrCloud,一个索引可以存放在不同的Solr服务上。意味着一个单独的索引可以由不同的机器上的SolrCore组成。不同机器上的SolrCore组成了逻辑上的索引,这些SolrCore叫做collection。组成collection的SolrCore包括了数据索引和备份。

例子A: 简单两个shard集群

Image Title
这个例子简单创建了包含两个solr服务的集群,一个collection的数据分布到两个不同的shard上。
因为在这个例子中我们需要两个服务器,这里仅简单的复制example的数据作为第二个服务器,复制example目录之前需要确保里面没有索引数据。

1
2
rm -r example/solr/collection1/data/*
cp -r example example2

下面的命令会启动一个solr服务并启动一个新的solr集群。

1
2
cd example
java -Dbootstrap_confdir=./solr/collection1/conf -Dcollection.configName=myconf -DzkRun -DnumShards=2 -jar start.jar
  • -DzkRun 参数会在solr服务中启动一个内置的zookeeper服务。
  • -Dbootstrap_confdir=./solr/collection1/conf 因为在zookeeper中没有solr配置信息,这一参数会将本地的./solr/conf目录下的配置信息上传到zookeeper中作为myconf配置参数。myconf是在下面的collection.configName参数中指定的。
  • -Dcollection.configName=myconf 为新的collection设置配置名称。如果不加这个参数配置默认名称为configuration1
  • -DnumShards=2 划分索引到逻辑分区的个数。

浏览http://localhost(本地主机):8983/solr/#/~cloud可以看到集群的状态。

通过目录树可以看到配置文件已经上传到了/configs/myconf/目录下,一个叫collection1的collection已经创建,在collection1下是shard的列表,这些shard组成了完整的collection。

接下来准备启动第二个服务器,因为没有明确的设置shard的id,该服务会自动分配到shard2。

启动第二个服务,并将其指向集群。

1
2
cd example2
java -Djetty.port=7574 -DzkHost=localhost:9983 -jar start.jar
  • -Djetty.port=7574来指定Jetty的端口号。
  • -DzkHost=localhost:9983用来指定Zookeeper集群。在本例中,在第一个Solr服务中运行了一个单独的Zookeeper服务。默认情况下,Zookeeper的端口号为Solr服务的端口号加上1000,即9983。

通过访问http://localhost(本地主机):8983/solr/#/~cloud,在collection1中就可以看到shard1和shard2。

Image Title

下面对一些文档建立索引。

1
2
3
4
5
6
7
cd exampledocs

java -Durl=http://localhost:8983/solr/collection1/update -jar post.jar ipod_video.xml

java -Durl=http://localhost:8983/solr/collection1/update -jar post.jar monitor.xml

java -Durl=http://localhost:8983/solr/collection1/update -jar post.jar mem.xml

无论是向集群中的任何一台服务器请求都会得到全部的collection:http://localhost:8983/solr/collection1/select?q=*:*

假如想更改配置,可以在关闭所有服务之后删除solr/zoo_data目录下的所有内容。

实际测试插入速度要比单个服务慢。

例子B:简单的两个shard重复的shard集群

Image Title
本例子会通过复制shard1和shard2来创建上一个例子。额外的shard备份可以有高可用性和容错性,简单提升索引的查询能力。

首先,在运行先前的例子中我们已经有了两个shard和一些索引文档。然后简单的复制这两个服务:

1
2
cp -r example exampleB
cp -r example2 example2B

然后,在不同的端口上启动两个新的服务:

1
2
cd exampleB
java -Djetty.port=8900 -DzkHost=localhost:9983 -jar start.jar
1
2
cd example2B
java -Djetty.port=7500 -DzkHost=localhost:9983 -jar start.jar

重新浏览网址http://localhost(本地主机):8983/solr/#/~cloud,检查四个solr节点是否已经都启动。
Image Title
因为我们已经告诉Solr我们需要两个逻辑上的shard,启动后的实例3和4会自动的成为原来shard的备份。

向集群中的任意一个服务发起查询:http://localhost:7500/solr/collection1/select?q=*:*。多次发起这个查询并查看solr服务的日志。可以观察到Solr通过备份对请求做了平衡,通过不同的服务来处理请求。

为了证明高可用性,在除了运行Zookeeper的服务上按下CTRL-C。(在例子C中将会讨论Zookeeper的冗余)当服务终止后,发送另外一个查询请求到其他服务,仍然能够看到所有的结果。

在没一个shard至少还有一个服务时,SolrCloud仍然可以提供服务。可以通过关闭每一个实例来查看结果。假如关闭了一个shard的所有的服务,到其他服务的请求就会收到503错误。为了能够返回其他可用的shard中的文档,可以在请求中增加参数:shards.tolerant=true

SolrCloud用leaders和overseer来作为具体的实现。一些节点或备份将会扮演特殊的角色。不需要担心杀死了leader或overseer,假如杀死了其中的一个,集群会自动选择一个新的leader或overseer,并自动接管工作。任何的Solr实例都可以成为这种角色。

例子C:两个shard集群,shard带备份和zookeeper集群

Image Title

在例子B中问题是虽然有足够的Solr服务器可以避免集群挂掉,但是仅有一个zookeeper服务来维持集群的状态。假如zookeeper服务挂掉了,分布式的查询还是可以工作的,因为solr服务记录了zookeeper最后一次报告的状态。问题是没有新的服务器或客户端能发现集群的状态,集群的状态也不会改变。

运行多个zookeeper服务可以保证zookeeper服务具有高可用性。每一个zookeeper服务需要知道集群中的其他服务,大部分服务需要提供服务。例如,一个含有三个zookeeper服务的集群允许其中一个失败剩余的两个仍然可以提供服务。五个zookeeper服务的集群可以允许一次失败两个。

从产品角度考虑,推荐使用单独的zookeeper服务而不是solr服务中集成的zookeeper服务。你可以从这里读取到更多的zookeeper集群。在这个简单的例子中,我们仅简单的使用了集成的zookeeper。

首先,停止四个服务,并清空zookeeper中的数据作为一个新的开始。

1
rm -r example*/solr/zoo_data

我们仍然将服务分别运行在8983,7574,8900,7500端口。默认是在端口号+1000的端口上启动一个zookeeper服务,第一次运行的时候在另外三台服务器上zookeeper的地址分别为:localhost:9983,localhost:8574,localhost:9900。

为了方便通过第一个服务上传solr的配置到zookeeper集群中。在第二个zookeeper服务启动之前程序会阻塞。这是因为zookeeper在工作的时候需要其他服务。

1
2
3
cd example

java -Dbootstrap_confdir=./solr/collection1/conf -Dcollection.configName=myconf -DzkRun -DzkHost=localhost:9983,localhost:8574,localhost:9900 -DnumShards=2 -jar start.jar
1
2
cd example2
java -Djetty.port=7574 -DzkRun -DzkHost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar
1
2
cd exampleB
java -Djetty.port=8900 -DzkRun -DzkHost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar
1
2
cd example2B
java -Djetty.port=7500 -DzkHost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar

现在我们运行了三个内置的zookeeper服务,如果一个服务挂掉之后其他一切正常。为了证明,在exampleB上按下CTRL+C杀掉服务,然后浏览http://localhost:8983/solr/#/~cloud来核实zookeeper服务仍然可以工作。

需要注意的是,当运行在多个机器上,需要在每一台机器上设置-DzkRun=hostname:port属性。

ZooKeeper

多个zookeeper服务同时运行来避免错误和高可用性叫做ensemble。从产品角度,推荐运行外部的zookeeper ensemble来代替solr集成的zookeeper。浏览zookeeper官方网站下载和运行一个zookeeper ensemble。可以参考Getting StartedZooKeeper Admin。非常简单就可以运行。可以坚持使用solr来运行zookeeper集群,但是必须知道zookeeper集群不是非常容易动态改变的。除非solr增加对zookeeper更好的支持,重新开始是最好的改变方式。zookeeper和solr是两个不同的进程是最好的方式。

当solr运行内置的zookeeper服务时,默认会使用solr服务的端口号加上1000作为zookeeper的客户端端口号。另外,默认会增加一个zookeeper的客户端端口号和两个zookeeper的选举端口号。所以在第一个例子中,solr运行在8983端口,内置的zookeeper服务运行在9983端口作为客户端端口,9984和9985作为服务端端口。

当增加了更多zookeeper节点可以提高读性能,但是会稍微降低写性能。当集群状态稳定的时候,Solr用的Zookeeper非常少。下面有一些优化zookeeper的建议:

  1. 最好的情况是zookeeper有一个专用的机器。zookeeper是一个准时的服务,专用的机器可以确保及时响应。当然专用的机器不是必须的。
  2. 当把事务日志和snap-shots放到不同的磁盘上可以提高性能。
  3. 假如zookeeper和solr运行在同一台机器上,利用不同的磁盘可以提高性能。

参考文档

https://wiki.apache.org/solr/SolrCloud

前几天写了篇《在Linux上搭建solr环境》的博文,是基于solr3.6.2的安装。本文仅记录在tomcat7.0.41上搭建solr4.3.1搭建过程中需要注意的地方,其他地方可以参考上一篇博文。

配置完成之后发现http://192.168.20.38:8090/solr无法访问,但是http://192.168.20.38:8090/却可以访问,通过查看tomcat的日志文件localhost.2013-07-03.log,发现里面有如下错误提示。

1
2
严重: Exception starting filter SolrRequestFilter
org.apache.solr.common.SolrException: Could not find necessary SLF4j logging jars. If using Jetty, the SLF4j logging jars need to go in the jetty lib/ext directory. For other containers, the corresponding directory should be used. For more information, see: http://wiki.apache.org/solr/SolrLogging

解决办法:将/solr-4.3.1/example/lib/ext目录下的所有jar文件复制到/apache-tomcat-7.0.41/lib目录下,然后重启tomcat即可。

相关下载

用到的文件

0%