├── Blog
├── 2015-10-20-DNA-Sequncer.md
├── 2016-01-23-thread-pool.md
└── images
│ ├── DNAsequncer
│ ├── DNAsequencer.png
│ ├── Model.png
│ ├── PCRbridge.png
│ ├── capture.png
│ ├── design.png
│ ├── imageprocess.png
│ ├── light.png
│ ├── seq1.PNG
│ ├── seq2.PNG
│ └── seq3.PNG
│ ├── backbonechrome
│ ├── notes-draft.jpg
│ ├── notes-logo.jpg
│ └── notes-settings.jpg
│ ├── external_link.gif
│ ├── external_link_on.gif
│ ├── githubpages
│ ├── bootcamp_1_ssh.jpg
│ ├── bootcamp_1_token.jpg
│ ├── bootcamp_1_win_gitbash.jpg
│ ├── disqus-site.jpg
│ ├── github-account-setting.png
│ ├── other-pages.png
│ ├── ssh-key-set.png
│ └── user-pages.png
│ ├── icons.gif
│ ├── markdown
│ └── Aaron_Swartz.jpg
│ ├── other
│ ├── Tabby-440.png
│ ├── Tabby-pro1.png
│ ├── Tabby-pro2.png
│ ├── auto-edit-bar.jpg
│ └── side-slide.jpg
│ ├── overapi
│ ├── icon-small.png
│ └── overapi.jpg
│ ├── rainingchrome
│ ├── RainingChrome.crx
│ ├── RainingChrome.pem
│ ├── rain.jpg
│ └── update.xml
│ └── threadpool
│ ├── consumer.png
│ ├── producer.png
│ └── threadpool.png
├── Code
├── C++ 实现高性能内存池.md
├── DesignPattern.md
├── JVM.md
├── ObjectPool.md
├── Polymorphism.md
├── Python Numpy教程.md
├── threadpool.md
└── threadpool
│ ├── consumer.png
│ ├── producer.png
│ └── threadpool.png
├── ComputerVision
├── Automatic-license-plate-recognition.md
├── FaceRecognition.md
├── Feature.md
├── ImageRetrieval.md
├── PatternRecognition.md
├── ResourcesInVisualTracking.md
├── SIF Matching with RANSAC.md
├── Saliency.md
├── Segmentation.md
├── pic
│ ├── Automatic001.jpg
│ ├── Automatic002.jpg
│ ├── Automatic003.jpg
│ ├── Automatic004.jpg
│ ├── Automatic005.jpg
│ ├── Automatic006.jpg
│ ├── Automatic007.jpg
│ ├── Automatic008.jpg
│ ├── SIFT001.jpg
│ ├── SIFT002.jpg
│ ├── SIFT003.jpg
│ └── SIFT004.jpg
├── 为什么深度学习几乎成了计算机视觉研究的标配.md
├── 二维码条形码简介.md
├── 人脸识别.md
├── 图像分类笔记.md
├── 基于深度学习的物体检测.md
└── 验证码识别.md
├── LICENSE
├── ML
├── Deep Reinforcement Learning 基础知识(DQN方面).md
├── MachineLearning.md
├── OCR.md
├── RGB图像在CNN中如何进行convolution.md
├── 基于深度学习的物体检测.md
└── 深度学习框架自动求导.md
├── README.md
├── RGB图像在CNN中如何进行convolution.md
└── 作品
├── 一种高通量基因测序碱基荧光图像捕获系统装置及方法专利授权证书.jpg
├── 三位重构项目.pdf
├── 中兴人工智能比赛-人脸认证.pdf
├── 书——机器视觉组态软件Xavis.pdf
├── 双目视觉动态目标测距技术研究与系统实现_终极打印版.doc
└── 工业4.0.png
/Blog/2015-10-20-DNA-Sequncer.md:
--------------------------------------------------------------------------------
1 | layout: post
2 | title: DNA sequncer
3 | category: project
4 | description: 高通量基因测序
5 |
6 | ##一种高通量基因测序碱基荧光图像捕获系统装置
7 | 国内目前没有一家科研机构或者企业具有生产制造商用基因测序仪的能力,基因测序仪市场是一个被国外巨头垄断的市场。
8 | 国外对基因测序技术的研究主要集中在对二代测序技术的进一步完善和对新一代单分子测序技术的研究上。
9 | 国内对基因测序技术的研究主要集中在二代测序仪器的研发、测序技术的应用推广和测序序列的分析,主要的科研机构和企业有中科院北京基因组研究所和深圳华大基因等。
10 |
11 | ###高通量基因测序技术-测序原理
12 | 
13 |
14 | 
15 |
16 | 
17 |
18 | ###基因测序硬件技术-硬件总体设计
19 |
20 | 
21 |
22 | ###桥式PCR扩增模块
23 |
24 | 
25 |
26 | ###荧光图像处理算法
27 |
28 | 
29 | 
30 |
31 | ###总体设计图
32 |
33 | 
34 |
35 |
36 | [ForrestPi]: http://forrestpi.github.io/ "ForrestPi"
37 | [1]: {{ page.url}} ({{ page.title }})
--------------------------------------------------------------------------------
/Blog/2016-01-23-thread-pool.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: thread pool
4 | category: project
5 | description: 线程池
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 | > + 右值引用、move移动语义、forward完美转发减少内存占用率
33 | > + 实现对不同系统平台以及不同编译环境的兼容性
34 | > + 半同步半异步线程池为轻量级线程池库,具有极佳的可伸缩性
35 | > + 开源、通用性强
36 |
37 | ###源码链接###
38 | > [线程池库](https://github.com/ForrestPi/Cplusplus11_project/tree/master/threadPool"ThreadPool")。
39 |
40 |
41 |
42 |
43 | [ForrestPi]: http://forrestpi.github.io/ "ForrestPi"
44 | [1]: {{ page.url}} ({{ page.title }})
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/DNAsequencer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/DNAsequencer.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/Model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/Model.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/PCRbridge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/PCRbridge.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/capture.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/design.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/imageprocess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/imageprocess.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/light.png
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/seq1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/seq1.PNG
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/seq2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/seq2.PNG
--------------------------------------------------------------------------------
/Blog/images/DNAsequncer/seq3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/DNAsequncer/seq3.PNG
--------------------------------------------------------------------------------
/Blog/images/backbonechrome/notes-draft.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/backbonechrome/notes-draft.jpg
--------------------------------------------------------------------------------
/Blog/images/backbonechrome/notes-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/backbonechrome/notes-logo.jpg
--------------------------------------------------------------------------------
/Blog/images/backbonechrome/notes-settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/backbonechrome/notes-settings.jpg
--------------------------------------------------------------------------------
/Blog/images/external_link.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/external_link.gif
--------------------------------------------------------------------------------
/Blog/images/external_link_on.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/external_link_on.gif
--------------------------------------------------------------------------------
/Blog/images/githubpages/bootcamp_1_ssh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/bootcamp_1_ssh.jpg
--------------------------------------------------------------------------------
/Blog/images/githubpages/bootcamp_1_token.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/bootcamp_1_token.jpg
--------------------------------------------------------------------------------
/Blog/images/githubpages/bootcamp_1_win_gitbash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/bootcamp_1_win_gitbash.jpg
--------------------------------------------------------------------------------
/Blog/images/githubpages/disqus-site.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/disqus-site.jpg
--------------------------------------------------------------------------------
/Blog/images/githubpages/github-account-setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/github-account-setting.png
--------------------------------------------------------------------------------
/Blog/images/githubpages/other-pages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/other-pages.png
--------------------------------------------------------------------------------
/Blog/images/githubpages/ssh-key-set.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/ssh-key-set.png
--------------------------------------------------------------------------------
/Blog/images/githubpages/user-pages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/githubpages/user-pages.png
--------------------------------------------------------------------------------
/Blog/images/icons.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/icons.gif
--------------------------------------------------------------------------------
/Blog/images/markdown/Aaron_Swartz.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/markdown/Aaron_Swartz.jpg
--------------------------------------------------------------------------------
/Blog/images/other/Tabby-440.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/other/Tabby-440.png
--------------------------------------------------------------------------------
/Blog/images/other/Tabby-pro1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/other/Tabby-pro1.png
--------------------------------------------------------------------------------
/Blog/images/other/Tabby-pro2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/other/Tabby-pro2.png
--------------------------------------------------------------------------------
/Blog/images/other/auto-edit-bar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/other/auto-edit-bar.jpg
--------------------------------------------------------------------------------
/Blog/images/other/side-slide.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/other/side-slide.jpg
--------------------------------------------------------------------------------
/Blog/images/overapi/icon-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/overapi/icon-small.png
--------------------------------------------------------------------------------
/Blog/images/overapi/overapi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/overapi/overapi.jpg
--------------------------------------------------------------------------------
/Blog/images/rainingchrome/RainingChrome.crx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/rainingchrome/RainingChrome.crx
--------------------------------------------------------------------------------
/Blog/images/rainingchrome/RainingChrome.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOF8opuHTCeKu9XoG
3 | iqxj1MVr+nB3QJENsl7GwzoLR77240HunaLDwZDhpElXqunVNe7yWrJSlWZagmwsN
4 | U+tsQIJO43sNj7Q7g4GN97JZbng59YYXZydiktzMjAGOfww0D4O9BvrM+DdWMhFr1
5 | uwmRoUK3/duJFIww7zyMYJd1vAgMBAAECgYEAkY/G3QYLeF9MALuT4Mm9D1KpvApl
6 | JOhqQcjR3pQnRKArzDAYBByi91UyEpMAOWmNELNuRBxrezTNSjIhBZg1nMKjwdzyB
7 | Anw6qKoDqkA9FpUnla44cvHSe3Iru4cE4dg1JFk2iVm4uD1B8QD4+ZzEsbQHsJHVn
8 | PcbnxkhNx87xkCQQD2fRl7f/QrDdhOfl4Y7k/5MA2abhWVp0moXexv2SQjwOPn1t0
9 | 8gPY2PovyFHfYgWrJGgShqCNznEzF/EX9wDEjAkEA6jASjEejv0F7qXiD1X1uKG2r
10 | V3zWAxkuaGP7jM66aCrBHCxQupGFsJKD18LvuiR6wdaIJNxpAgagt1T1dVhVRQJAI
11 | ZUfwKzJNoh3XRJofZsSeE/suoqfFDmrlm3dhcue2eQw6OhkUhb79J4G8R+KyIuxYd
12 | 7SnLfMxLaZ2yuo2Y6y4wJAI3ggdwqqrF74odRU81LKCmmB2ABaUaHQu5LZGJx+8yP
13 | 2l5PGrls4CmRGf7lQobrAtPcPK7YOWXYzhVDi1GEKmQJBAOUn7dfVqDgJScNeQjwY
14 | F//SHDT1oaG0xBwGtYd5a0FxTbBy2r+gQyV462haQVjeDKuBkBFet+krLVFBGhJJ7
15 | fo=
16 | -----END PRIVATE KEY-----
17 |
--------------------------------------------------------------------------------
/Blog/images/rainingchrome/rain.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/rainingchrome/rain.jpg
--------------------------------------------------------------------------------
/Blog/images/rainingchrome/update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Blog/images/threadpool/consumer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/threadpool/consumer.png
--------------------------------------------------------------------------------
/Blog/images/threadpool/producer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/threadpool/producer.png
--------------------------------------------------------------------------------
/Blog/images/threadpool/threadpool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ForrestPi/Blog/12651436a835fe2e5f16047f0f7e3bf6e222db8f/Blog/images/threadpool/threadpool.png
--------------------------------------------------------------------------------
/Code/C++ 实现高性能内存池.md:
--------------------------------------------------------------------------------
1 | # C++ 实现高性能内存池
2 |
3 |
4 |
5 | 在 C/C++ 中,内存管理是一个非常棘手的问题,我们在编写一个程序的时候几乎不可避免的要遇到内存的分配逻辑,这时候随之而来的有这样一些问题:是否有足够的内存可供分配? 分配失败了怎么办? 如何管理自身的内存使用情况? 等等一系列问题。在一个高可用的软件中,如果我们仅仅单纯的向操作系统去申请内存,当出现内存不足时就退出软件,是明显不合理的。正确的思路应该是在内存不足的时,考虑如何管理并优化自身已经使用的内存,这样才能使得软件变得更加可用。本次项目我们将实现一个内存池,并使用一个栈结构来测试我们的内存池提供的分配性能。最终,我们要实现的内存池在栈结构中的性能,要远高于使用 `std::allocator` 和 `std::vector`
6 |
7 | - C++ 中的内存分配器 `std::allocator`
8 | - 内存池技术
9 | - 手动实现模板链式栈
10 | - 链式栈和列表栈的性能比较
11 |
12 | ### 内存池简介
13 |
14 | 内存池是池化技术中的一种形式。通常我们在编写程序的时候回使用 `new` `delete` 这些关键字来向操作系统申请内存,而这样造成的后果就是每次申请内存和释放内存的时候,都需要和操作系统的系统调用打交道,从堆中分配所需的内存。如果这样的操作太过频繁,就会找成大量的内存碎片进而降低内存的分配性能,甚至出现内存分配失败的情况。
15 |
16 | 而内存池就是为了解决这个问题而产生的一种技术。从内存分配的概念上看,内存申请无非就是向内存分配方索要一个指针,当向操作系统申请内存时,操作系统需要进行复杂的内存管理调度之后,才能正确的分配出一个相应的指针。而这个分配的过程中,我们还面临着分配失败的风险。
17 |
18 | 所以,每一次进行内存分配,就会消耗一次分配内存的时间,设这个时间为 T,那么进行 n 次分配总共消耗的时间就是 nT;如果我们一开始就确定好我们可能需要多少内存,那么在最初的时候就分配好这样的一块内存区域,当我们需要内存的时候,直接从这块已经分配好的内存中使用即可,那么总共需要的分配时间仅仅只有 T。当 n 越大时,节约的时间就越多。
19 |
20 | ## 二、主函数设计
21 |
22 | 我们要设计实现一个高性能的内存池,那么自然避免不了需要对比已有的内存,而比较内存池对内存的分配性能,就需要实现一个需要对内存进行动态分配的结构(比如:链表栈),为此,可以写出如下的代码:
23 |
24 | ```c++
25 | #include // std::cout, std::endl
26 | #include // assert()
27 | #include // clock()
28 | #include // std::vector
29 |
30 | #include "MemoryPool.hpp" // MemoryPool
31 | #include "StackAlloc.hpp" // StackAlloc
32 |
33 | // 插入元素个数
34 | #define ELEMS 10000000
35 | // 重复次数
36 | #define REPS 100
37 |
38 | int main()
39 | {
40 | clock_t start;
41 |
42 | // 使用 STL 默认分配器
43 | StackAlloc > stackDefault;
44 | start = clock();
45 | for (int j = 0; j < REPS; j++) {
46 | assert(stackDefault.empty());
47 | for (int i = 0; i < ELEMS; i++)
48 | stackDefault.push(i);
49 | for (int i = 0; i < ELEMS; i++)
50 | stackDefault.pop();
51 | }
52 | std::cout << "Default Allocator Time: ";
53 | std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
54 |
55 | // 使用内存池
56 | StackAlloc > stackPool;
57 | start = clock();
58 | for (int j = 0; j < REPS; j++) {
59 | assert(stackPool.empty());
60 | for (int i = 0; i < ELEMS; i++)
61 | stackPool.push(i);
62 | for (int i = 0; i < ELEMS; i++)
63 | stackPool.pop();
64 | }
65 | std::cout << "MemoryPool Allocator Time: ";
66 | std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
67 |
68 | return 0;
69 | }
70 | ```
71 |
72 | 在上面的两段代码中,`StackAlloc` 是一个链表栈,接受两个模板参数,第一个参数是栈中的元素类型,第二个参数就是栈使用的内存分配器。
73 |
74 | 因此,这个内存分配器的模板参数就是整个比较过程中唯一的变量,使用默认分配器的模板参数为 `std::allocator`,而使用内存池的模板参数为 `MemoryPool`。
75 |
76 | > std::allocator 是 C++标准库中提供的默认分配器,他的特点就在于我们在 使用 new 来申请内存构造新对象的时候,势必要调用类对象的默认构造函数,而使用 std::allocator 则可以将内存分配和对象的构造这两部分逻辑给分离开来,使得分配的内存是原始、未构造的。
77 |
78 | 下面我们来实现这个链表栈。
79 |
80 | ## 三、模板链表栈
81 |
82 | 栈的结构非常的简单,没有什么复杂的逻辑操作,其成员函数只需要考虑两个基本的操作:入栈、出栈。为了操作上的方便,我们可能还需要这样一些方法:判断栈是否空、清空栈、获得栈顶元素。
83 |
84 | ```c++
85 | #include
86 |
87 | template
88 | struct StackNode_
89 | {
90 | T data;
91 | StackNode_* prev;
92 | };
93 |
94 | // T 为存储的对象类型, Alloc 为使用的分配器, 并默认使用 std::allocator 作为对象的分配器
95 | template >
96 | class StackAlloc
97 | {
98 | public:
99 | // 使用 typedef 简化类型名
100 | typedef StackNode_ Node;
101 | typedef typename Alloc::template rebind::other allocator;
102 |
103 | // 默认构造
104 | StackAlloc() { head_ = 0; }
105 | // 默认析构
106 | ~StackAlloc() { clear(); }
107 |
108 | // 当栈中元素为空时返回 true
109 | bool empty() {return (head_ == 0);}
110 |
111 | // 释放栈中元素的所有内存
112 | void clear();
113 |
114 | // 压栈
115 | void push(T element);
116 |
117 | // 出栈
118 | T pop();
119 |
120 | // 返回栈顶元素
121 | T top() { return (head_->data); }
122 |
123 | private:
124 | //
125 | allocator allocator_;
126 | // 栈顶
127 | Node* head_;
128 | };
129 | ```
130 |
131 | 简单的逻辑诸如构造、析构、判断栈是否空、返回栈顶元素的逻辑都非常简单,直接在上面的定义中实现了,下面我们来实现 `clear()`, `push()` 和 `pop()` 这三个重要的逻辑:
132 |
133 | ```c++
134 | // 释放栈中元素的所有内存
135 | void clear() {
136 | Node* curr = head_;
137 | // 依次出栈
138 | while (curr != 0)
139 | {
140 | Node* tmp = curr->prev;
141 | // 先析构, 再回收内存
142 | allocator_.destroy(curr);
143 | allocator_.deallocate(curr, 1);
144 | curr = tmp;
145 | }
146 | head_ = 0;
147 | }
148 | // 入栈
149 | void push(T element) {
150 | // 为一个节点分配内存
151 | Node* newNode = allocator_.allocate(1);
152 | // 调用节点的构造函数
153 | allocator_.construct(newNode, Node());
154 |
155 | // 入栈操作
156 | newNode->data = element;
157 | newNode->prev = head_;
158 | head_ = newNode;
159 | }
160 |
161 | // 出栈
162 | T pop() {
163 | // 出栈操作 返回出栈元素
164 | T result = head_->data;
165 | Node* tmp = head_->prev;
166 | allocator_.destroy(head_);
167 | allocator_.deallocate(head_, 1);
168 | head_ = tmp;
169 | return result;
170 | }
171 | ```
172 |
173 | ## 总结
174 |
175 | 本节我们实现了一个用于测试性能比较的模板链表栈,目前的代码如下。在下一节中,我们开始详细实现我们的高性能内存池。
176 |
177 | ```c++
178 | // StackAlloc.hpp
179 |
180 | #ifndef STACK_ALLOC_H
181 | #define STACK_ALLOC_H
182 |
183 | #include
184 |
185 | template
186 | struct StackNode_
187 | {
188 | T data;
189 | StackNode_* prev;
190 | };
191 |
192 | // T 为存储的对象类型, Alloc 为使用的分配器,
193 | // 并默认使用 std::allocator 作为对象的分配器
194 | template >
195 | class StackAlloc
196 | {
197 | public:
198 | // 使用 typedef 简化类型名
199 | typedef StackNode_ Node;
200 | typedef typename Alloc::template rebind::other allocator;
201 |
202 | // 默认构造
203 | StackAlloc() { head_ = 0; }
204 | // 默认析构
205 | ~StackAlloc() { clear(); }
206 |
207 | // 当栈中元素为空时返回 true
208 | bool empty() {return (head_ == 0);}
209 |
210 | // 释放栈中元素的所有内存
211 | void clear() {
212 | Node* curr = head_;
213 | while (curr != 0)
214 | {
215 | Node* tmp = curr->prev;
216 | allocator_.destroy(curr);
217 | allocator_.deallocate(curr, 1);
218 | curr = tmp;
219 | }
220 | head_ = 0;
221 | }
222 |
223 | // 入栈
224 | void push(T element) {
225 | // 为一个节点分配内存
226 | Node* newNode = allocator_.allocate(1);
227 | // 调用节点的构造函数
228 | allocator_.construct(newNode, Node());
229 |
230 | // 入栈操作
231 | newNode->data = element;
232 | newNode->prev = head_;
233 | head_ = newNode;
234 | }
235 |
236 | // 出栈
237 | T pop() {
238 | // 出栈操作 返回出栈结果
239 | T result = head_->data;
240 | Node* tmp = head_->prev;
241 | allocator_.destroy(head_);
242 | allocator_.deallocate(head_, 1);
243 | head_ = tmp;
244 | return result;
245 | }
246 |
247 | // 返回栈顶元素
248 | T top() { return (head_->data); }
249 |
250 | private:
251 | allocator allocator_;
252 | Node* head_;
253 | };
254 |
255 | #endif // STACK_ALLOC_H
256 |
257 | // main.cpp
258 |
259 | #include
260 | #include
261 | #include
262 | #include
263 |
264 | // #include "MemoryPool.hpp"
265 | #include "StackAlloc.hpp"
266 |
267 | // 根据电脑性能调整这些值
268 | // 插入元素个数
269 | #define ELEMS 25000000
270 | // 重复次数
271 | #define REPS 50
272 |
273 | int main()
274 | {
275 | clock_t start;
276 |
277 | // 使用默认分配器
278 | StackAlloc > stackDefault;
279 | start = clock();
280 | for (int j = 0; j < REPS; j++) {
281 | assert(stackDefault.empty());
282 | for (int i = 0; i < ELEMS; i++)
283 | stackDefault.push(i);
284 | for (int i = 0; i < ELEMS; i++)
285 | stackDefault.pop();
286 | }
287 | std::cout << "Default Allocator Time: ";
288 | std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
289 |
290 | // 使用内存池
291 | // StackAlloc > stackPool;
292 | // start = clock();
293 | // for (int j = 0; j < REPS; j++) {
294 | // assert(stackPool.empty());
295 | // for (int i = 0; i < ELEMS; i++)
296 | // stackPool.push(i);
297 | // for (int i = 0; i < ELEMS; i++)
298 | // stackPool.pop();
299 | // }
300 | // std::cout << "MemoryPool Allocator Time: ";
301 | // std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
302 |
303 | return 0;
304 | }
305 | ```
306 |
307 | ## 二、设计内存池
308 |
309 | 在节中,我们在模板链表栈中使用了默认构造器来管理栈操作中的元素内存,一共涉及到了 `rebind::other`, `allocate()`, `dealocate()`, `construct()`, `destroy()`这些关键性的接口。所以为了让代码直接可用,我们同样应该在内存池中设计同样的接口:
310 |
311 | ```c++
312 | #ifndef MEMORY_POOL_HPP
313 | #define MEMORY_POOL_HPP
314 |
315 | #include
316 | #include
317 |
318 | template
319 | class MemoryPool
320 | {
321 | public:
322 | // 使用 typedef 简化类型书写
323 | typedef T* pointer;
324 |
325 | // 定义 rebind::other 接口
326 | template struct rebind {
327 | typedef MemoryPool other;
328 | };
329 |
330 | // 默认构造, 初始化所有的槽指针
331 | // C++11 使用了 noexcept 来显式的声明此函数不会抛出异常
332 | MemoryPool() noexcept {
333 | currentBlock_ = nullptr;
334 | currentSlot_ = nullptr;
335 | lastSlot_ = nullptr;
336 | freeSlots_ = nullptr;
337 | }
338 |
339 | // 销毁一个现有的内存池
340 | ~MemoryPool() noexcept;
341 |
342 | // 同一时间只能分配一个对象, n 和 hint 会被忽略
343 | pointer allocate(size_t n = 1, const T* hint = 0);
344 |
345 | // 销毁指针 p 指向的内存区块
346 | void deallocate(pointer p, size_t n = 1);
347 |
348 | // 调用构造函数
349 | template
350 | void construct(U* p, Args&&... args);
351 |
352 | // 销毁内存池中的对象, 即调用对象的析构函数
353 | template
354 | void destroy(U* p) {
355 | p->~U();
356 | }
357 |
358 | private:
359 | // 用于存储内存池中的对象槽,
360 | // 要么被实例化为一个存放对象的槽,
361 | // 要么被实例化为一个指向存放对象槽的槽指针
362 | union Slot_ {
363 | T element;
364 | Slot_* next;
365 | };
366 |
367 | // 数据指针
368 | typedef char* data_pointer_;
369 | // 对象槽
370 | typedef Slot_ slot_type_;
371 | // 对象槽指针
372 | typedef Slot_* slot_pointer_;
373 |
374 | // 指向当前内存区块
375 | slot_pointer_ currentBlock_;
376 | // 指向当前内存区块的一个对象槽
377 | slot_pointer_ currentSlot_;
378 | // 指向当前内存区块的最后一个对象槽
379 | slot_pointer_ lastSlot_;
380 | // 指向当前内存区块中的空闲对象槽
381 | slot_pointer_ freeSlots_;
382 |
383 | // 检查定义的内存池大小是否过小
384 | static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small.");
385 | };
386 |
387 | #endif // MEMORY_POOL_HPP
388 | ```
389 |
390 | 在上面的类设计中可以看到,在这个内存池中,其实是使用链表来管理整个内存池的内存区块的。内存池首先会定义固定大小的基本内存区块(Block),然后在其中定义了一个可以实例化为存放对象内存槽的对象槽(Slot_)和对象槽指针的一个联合。然后在区块中,定义了四个关键性质的指针,它们的作用分别是:
391 |
392 | 1. `currentBlock_`: 指向当前内存区块的指针
393 | 2. `currentSlot_`: 指向当前内存区块中的对象槽
394 | 3. `lastSlot_`: 指向当前内存区块中的最后一个对象槽
395 | 4. `freeSlots_`: 指向当前内存区块中所有空闲的对象槽
396 |
397 | 梳理好整个内存池的设计结构之后,我们就可以开始实现关键性的逻辑了。
398 |
399 | ## 三、实现
400 |
401 | ### MemoryPool::construct() 实现
402 |
403 | `MemoryPool::construct()` 的逻辑是最简单的,我们需要实现的,仅仅只是调用信件对象的构造函数即可,因此:
404 |
405 | ```c++
406 | // 调用构造函数, 使用 std::forward 转发变参模板
407 | template
408 | void construct(U* p, Args&&... args) {
409 | new (p) U (std::forward(args)...);
410 | }
411 | ```
412 |
413 | ### MemoryPool::deallocate() 实现
414 |
415 | `MemoryPool::deallocate()` 是在对象槽中的对象被析构后才会被调用的,主要目的是销毁内存槽。其逻辑也不复杂:
416 |
417 | ```c++
418 | // 销毁指针 p 指向的内存区块
419 | void deallocate(pointer p, size_t n = 1) {
420 | if (p != nullptr) {
421 | // reinterpret_cast 是强制类型转换符
422 | // 要访问 next 必须强制将 p 转成 slot_pointer_
423 | reinterpret_cast(p)->next = freeSlots_;
424 | freeSlots_ = reinterpret_cast(p);
425 | }
426 | }
427 | ```
428 |
429 | ### MemoryPool::~MemoryPool() 实现
430 |
431 | 析构函数负责销毁整个内存池,因此我们需要逐个删除掉最初向操作系统申请的内存块:
432 |
433 | ```c++
434 | // 销毁一个现有的内存池
435 | ~MemoryPool() noexcept {
436 | // 循环销毁内存池中分配的内存区块
437 | slot_pointer_ curr = currentBlock_;
438 | while (curr != nullptr) {
439 | slot_pointer_ prev = curr->next;
440 | operator delete(reinterpret_cast(curr));
441 | curr = prev;
442 | }
443 | }
444 | ```
445 |
446 | ### MemoryPool::allocate() 实现
447 |
448 | `MemoryPool::allocate()` 毫无疑问是整个内存池的关键所在,但实际上理清了整个内存池的设计之后,其实现并不复杂。具体实现如下:
449 |
450 | ```c++
451 | // 同一时间只能分配一个对象, n 和 hint 会被忽略
452 | pointer allocate(size_t n = 1, const T* hint = 0) {
453 | // 如果有空闲的对象槽,那么直接将空闲区域交付出去
454 | if (freeSlots_ != nullptr) {
455 | pointer result = reinterpret_cast(freeSlots_);
456 | freeSlots_ = freeSlots_->next;
457 | return result;
458 | } else {
459 | // 如果对象槽不够用了,则分配一个新的内存区块
460 | if (currentSlot_ >= lastSlot_) {
461 | // 分配一个新的内存区块,并指向前一个内存区块
462 | data_pointer_ newBlock = reinterpret_cast(operator new(BlockSize));
463 | reinterpret_cast(newBlock)->next = currentBlock_;
464 | currentBlock_ = reinterpret_cast(newBlock);
465 | // 填补整个区块来满足元素内存区域的对齐要求
466 | data_pointer_ body = newBlock + sizeof(slot_pointer_);
467 | uintptr_t result = reinterpret_cast(body);
468 | size_t bodyPadding = (alignof(slot_type_) - result) % alignof(slot_type_);
469 | currentSlot_ = reinterpret_cast(body + bodyPadding);
470 | lastSlot_ = reinterpret_cast(newBlock + BlockSize - sizeof(slot_type_) + 1);
471 | }
472 | return reinterpret_cast(currentSlot_++);
473 | }
474 | }
475 | ```
476 |
477 | ## 四、与 std::vector 的性能对比
478 |
479 | 我们知道,对于栈来说,链栈其实并不是最好的实现方式,因为这种结构的栈不可避免的会涉及到指针相关的操作,同时,还会消耗一定量的空间来存放节点之间的指针。事实上,我们可以使用 `std::vector` 中的 `push_back()` 和 `pop_back()` 这两个操作来模拟一个栈,我们不妨来对比一下这个 `std::vector` 与我们所实现的内存池在性能上谁高谁低,我们在 主函数中加入如下代码:
480 |
481 | ```c++
482 | // 比较内存池和 std::vector 之间的性能
483 | std::vector stackVector;
484 | start = clock();
485 | for (int j = 0; j < REPS; j++) {
486 | assert(stackVector.empty());
487 | for (int i = 0; i < ELEMS; i++)
488 | stackVector.push_back(i);
489 | for (int i = 0; i < ELEMS; i++)
490 | stackVector.pop_back();
491 | }
492 | std::cout << "Vector Time: ";
493 | std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
494 | ```
--------------------------------------------------------------------------------
/Code/DesignPattern.md:
--------------------------------------------------------------------------------
1 | ### [初学设计模式的一点感悟](http://blog.hesey.net/2011/01/thinking-in-beginning-of-design-patterns.html)
2 |
3 | 最近几个星期的时间对设计模式做了入门学习,之前读过GoF的那本《[**设计模式**](http://book.douban.com/subject/1052241/)》,感觉非常晦涩。在面向对象开发技术这门课中有设计模式的内容,这才重新拾起这个东西,发现比之前那次学明显容易多了。自己反思了一下,应该是和后来打好了比较好的面向对象思想的基础有关,所以也建议想要学习设计模式的朋友,**先把面向对象的基本思想搞明白**,起码得知道多态是干嘛用的,为什么要多态,多态有什么好处,这些问题可以看我之前写的那篇《[**浅谈多态机制的意义及实现**](http://blog.hesey.net/2010/12/significance-and-implementation-of-polymorphism.html)》。然后再来学习设计模式,才算有迹可循。
4 |
5 | 本文是对设计模式的入门学习做一些感悟和分享,希望能和大家一起多多交流关于模式的心得。
6 |
7 | # 一、基本概念
8 |
9 | > ### 1、被封装的应该是什么
10 | >
11 | > > 通常意义上,我们习惯于认为,面向对象最大的特点就像UML图一样,是由错综复杂的继承和依赖关系组成的。但是[**Alan Shalloway**](http://www.netobjectives.com/bio-alan-shalloway)认为,**对象真正的威力并不在于继承,而是来自封装行为。**
12 | > >
13 | > > 封装是面向对象三大特性之一,一般我们会认为,封装就是对类里面的实例变量进行保护,使其对外部类不可见。我们似乎很少会去关注对方法和行为的封装。就像我们经常做的:把实例变量设为private,把方法设为public,对于实例变量,我们会编写一系列set/get方法来间接修改/获得。
14 | > >
15 | > > 但是问题并不仅仅到此为止。尽管我们把数据隐藏了,但是通过set/get来操作数据,总让人感觉有一种换汤不换药的味道。一种更为深刻的思想是:**封装不仅仅是“数据隐藏”,封装应该被视为“任何形式的隐藏”。除了数据我们还可以隐藏以下各种东西:实现细节、派生类、设计细节、实例化规则。**
16 | > >
17 | > > 最简单的就是我们可以在重构代码时把某些类内部使用的方法标记为private,因为这些方法仅仅是这个类内部使用,并不想给外部类调用。另外一方面,当我们使用抽象类或是接口时,事实上就已经默许了派生和实现的封装:你不会知道这个类将会有哪些子类去继承它。而精髓恰恰在此:**通过多态实现的类型封装比单纯的数据封装有更深刻的意义。**
18 | >
19 | > ### 2、视角框架
20 | >
21 | > > [**Martin Fowler**](http://en.wikipedia.org/wiki/Martin_Fowler)提出了面向对象范型应该考虑的视角。他建议面向对象程序员应该从三个视角去考虑问题:
22 | > >
23 | > > > **1)概念视角:软件是用来做什么的;**
24 | > > >
25 | > > > **2)规约视角:提供了哪些接口(如何调用方法去完成这个任务);**
26 | > > >
27 | > > > **3)实现视角:这些接口/方法该怎么实现。**
28 | > >
29 | > > **在设计时,我们通常是从概念和规约的视角去看待问题**:首先明确软件的目的(概念视角),然后通过设计一系列接口以及接口提供的方法来对程序整体作架构设计(规约视角)。这个阶段我们并不应该过多考虑实现的细节,因为实现相比设计总是容易的。但是**初学者通常会在设计时不由自主地陷入实现的泥潭中:这个设计在代码层面该如何实现?用什么API?究其根本我认为正是因为实现相比设计更具体,这样的问题更容易解决的缘故。**
30 | > >
31 | > > **而在具体实现时,就应该从规约和实现视角去解决问题**:根据接口设计的方法(规约视角),通过实现类的代码做具体实现(实现视角),此时才应该考虑之前的问题。
32 | >
33 | > ### 3、对象究竟是什么
34 | >
35 | > > 传统上对象被视为具有方法的数据,很显然,对象是类的实例,在类这个蓝图中,包含了对象可以拥有的数据变量和方法(仔细想一想你之前对对象的认识是不是和这种观点有类似之处)。
36 | > >
37 | > > 当我们明确了封装更深刻的意义在于对行为的封装时,自然而然地,我们可以对对象作出更大的期待:**对象不仅仅是一个包裹,它应该像一个人一样,去完成一些事情。**所以,对对象更有意义的定义应该是:**对象是具有责任的一个实体。**这样我们就明确了对象的用处,同时也能让人**更加关注对象的责任,关注对象究竟该干什么而不是拥有什么。**不明确这一点,对象职责的解耦就无从谈起。
38 | > >
39 | > > 另一方面,如果我们采用[**Martin Fowler**](http://en.wikipedia.org/wiki/Martin_Fowler)的视角框架来观察对象,我们就可以从三个视角来对对象作更全面的认识:
40 | > >
41 | > > > **1)在概念层次上,对象是一组责任。**
42 | > > >
43 | > > > **2)在规约层次上,对象是一组可以被其他对象或对象自己调用的方法(也称行为)。**
44 | > > >
45 | > > > **3)在实现层次上,对象是代码和数据,以及它们之间的计算交互。**
46 | > >
47 | > > 看,之前提到的那种传统的看法**仅仅只是停留在实现层次上。**
48 |
49 | # 二、设计方法
50 |
51 | > ### 1、设计步骤
52 | >
53 | > > [**Alan Shalloway**](http://www.netobjectives.com/bio-alan-shalloway)认为,传统的**从需求分析中找出名词作为类,找出动词作为方法的设计思想是一种非常有局限性的方式,缺点之一就是会产生大量原本可能并不需要的类。**
54 | > >
55 | > > 在这里我们引入[**Jim Coplien**](http://en.wikipedia.org/wiki/Jim_Coplien)的共性分析和可变性分析的概念。
56 | > >
57 | > > **共性分析就是寻找一些共同的要素,这些要素可以帮助我们理解成员之间的共性在哪里。**
58 | > >
59 | > > **可变性分析建立在共性分析的基础之上,它要求找出元素之间的差异所在。**
60 | > >
61 | > > 具体来说,共性分析帮助我们提取出抽象类或接口作为规范的定义存在,而可变性分析则帮助我们针对这些接口进行差异化的实现。
62 | > >
63 | > > 明确这一个问题的关键目的在于,**我们需要为类组织起结构,而不仅仅是类与类之间单纯的消息传递。**
64 | > >
65 | > > 从架构的视角来看,共性分析为架构提供长效的要素(正如我们所知道的,接口通常不会变化),而可变性分析则促进它适应实际使用所需(不同的实现方式)。共通的概念用抽象类或者接口表示,可变性分析所发现的变化通过具体类实现。
66 | >
67 | > ### 2、组合优先于继承的更多优点
68 | >
69 | > > 设计模式中提倡组合优先原则,当然这是针对继承的许多缺点所做的对策。我们不妨思考一下,继承最大的优点在哪?毫无疑问,是代码复用,面向对象出现之初的一个口号就是复用代码。但是如果继承给我们带来的好处大于其坏处,那在继承时或许我们应该更保守一些。事实上,**重用并不是使用面向对象方法的根本原因。降低维护成本和使代码更加灵活、更容易扩展才是更重要的考虑因素。**为什么我们要设计?目的就在于此。使用正确的面向对象技术当然可能实现重用,但并不是通过直接使用该对象,然后派生新的变体对其重用即可达到的。这样只会产生难以维护的代码。(想想Bridge模式的初衷吧!)
70 | >
71 | > ### 3、整体和部分的关系
72 | >
73 | > > 在谈到整体与部分的关系时,我们必须介绍一本著作,[**Christopher Alexander**](http://en.wikipedia.org/wiki/Christopher_Alexander)的《[**建筑的永恒之道(The Timeless Way of Building)**](http://book.douban.com/subject/1177968/)》。这本书本来是讲建筑的,但其最受追捧的领域却是在软件界。它对模式的探讨带给软件开发人员许多思考,**软件开发是否也像建筑一样,存在某些公式化的模式,只要我们直接去套用,就能像建高楼一样。又快又好地开发出理想的软件?**
74 | > >
75 | > > 首先对这个问题的解答让人失望,直到目前为止,并不存在这样一种套路可以保证软件的质量。1986年,[**Fred Brooks**](http://en.wikipedia.org/wiki/Fred_Brooks)发表了一篇著名的论文《[**No Silver Bullet — Essence and Accidents of Software Engineering**](http://en.wikipedia.org/wiki/No_Silver_Bullet)》,“没有银弹”是许多人的遗憾,也成为软件工程师们的一个梦想。有兴许的朋友可以去读[**Scott Rosenberg**](http://www.wordyard.com/about/)的《[**梦断代码(Dreaming in Code)**](http://book.douban.com/subject/3142280/)》,这本书讲述了两打优秀的程序员在软件开发的过程中的辛酸故事,让人感慨。
76 | > >
77 | > > 《建筑的永恒之道》一书中有这么一段话:**每个部分都因其存在于更大整体的背景中而被赋予了特定的形式。**这是一个分化的过程。它把设计看成是一系列**复杂化**的活动;**结构是通过对整体操作、使其起皱而注入其中的,而不是通过一小部分一小部分添加而成。**在分化的过程中,**整体孕育了部分**:整体的形式及其各个部分是同时产生的。分化过程就好像是胚胎的成长过程。
78 | > >
79 | > > 通常我们在设计软件时都习惯于自底向上设计:先把最底层的工具类实现好,再使用这些工具类实现一些略微高层的类,再通过这些高层的类去组合成更上层的类。这似乎是理所当然的:没有下面的东西,上面的抽象怎么可能正常工作?Alexander却认为,**无论是设计什么,都应该从整体入手,在对整体的不断细化中再考虑具体的部分。**
80 | > >
81 | > > 令人感到微微诧异的是,我们在生活中正是这么做的。假设现在你想要一辆新的自行车,那么也许你会这么去描述它:首先这是一辆自行车而不是摩托车,然后它的整体颜色应该是红色,把手应该是向下弯曲的,有一个车铃在右侧把手上,后轮的中间有变速器,变速器可以调整4道变速……
82 | > >
83 | > > 这不正是从整体到部分的层层细化吗?这正是Alexander所认为的正确的设计之道。
84 | > >
85 | > > 从这里我们不难得出一个结论:**如果我们实现从部分的角度开始考虑设计,最终将预先形成的部分(模块)组合起来,我们便无法得到一个优秀的软件设计。**
86 | >
87 | > ### 4、设计模式的用法
88 | >
89 | > > 首先,**设计模式不是万能的,更不是公式化的**,并不存在某一个问题必须套用某种模式的说法。**模式应该相互配合,共同解决问题,这才是设计模式的正确用法。**
90 | > >
91 | > > 千万不能生搬硬套模式,我个人比较倾向于去深刻体会并理解模式的思想,而不是把模式的名字都记下来,再分别细述。
92 | > >
93 | > > 模式的理想使用我认为应该是这样,举例来说,当我要使用一个旧的类而不能直接使用时,我的脑海里应该自然而然地冒出来在中间加个转换器的想法。当然,这里我很清楚,这个转换器就是适配器。而当我要对一个系统作调用的简化处理时,应该非常自然地冒出来封装入口的思想。注意,在这里我自始至终没有刻意提起Adapter或是Facade模式,名字神马的都是浮云,**理解它们的原理和作用才是重点。**设计模式是被人发现和总结出来的,而不是人为发明的,没有设计模式的时候也有人这么做,我们的思想应该向他们靠拢。
94 | > >
95 | > > 而在进行设计决策时,我们通常会面临好几种不同的解决方案,它们各有利弊,该如何选择?许多开发人员会这样问:“这些实现方式中哪个更好?”这种问法并不明智。问题在于,**往往没有哪个实现方式天生就优于另一个(否则另一个还有存在的意义吗?)。**应该这样问,对于每种实现方式,“**什么情况下它优于其它方式?**”然后再问:“**哪种情况与我的问题领域最相似?**”在这种思考中,我们会逐渐**对不同的解决方案和模式有更深刻的体会和认识。**
96 |
97 | # 三、常用模式的心得体会
98 |
99 | 这里对集中常用的设计模式作一些感悟总结,我喜欢对比模式之间的区别,以此对模式的适用领域和根本思想做深入的分析并理解,但还是要强调,不能把模式孤立看待,必须让模式互相配合,共同解决问题。
100 |
101 | > ### 1、Facade/Adapter/Decorator的联系和区别
102 | >
103 | > > 曾经看到有人把Facade和Adapter两种模式进行了对比,因为它们都存在对类的包装(wrap),事实上我认为Adapter和Decorator更像。**Adapter完全可以理解为是对不符合新接口的旧对象进行装饰**,区别在于在Decorator里,被装饰的对象也继承于装饰者的父类,而Adapter里被适配的对象不行(正是因为不行才需要适配)。同时,Decorator中的父类的作用更侧重于是便于对类的组织。
104 | > >
105 | > > 明确三种模式的目的有助于我们更精确的理解其思想。
106 | > >
107 | > > > **Facade:简化接口,避免复杂逻辑和调用;**
108 | > > >
109 | > > > **Adapter:转换接口,相当于两相插座和三相插头之间的拖线板;**
110 | > > >
111 | > > > **Decorator:增加额外特性。**
112 | >
113 | > ### 2、Facade/Proxy的区别
114 | >
115 | > > Facade通常使用多种对象组合实现功能,但Proxy通常只使用一类对象。此外Proxy的接口方法通常与被调用的对象方法名相同,以此保证调用代理和调用实际对象的方式一致。
116 | > >
117 | > > Facade通常用于简化系统的使用(典型例子就是API)。此外它还有如下两个作用:
118 | > >
119 | > > > **1)通过限定使用入口来对系统进行监测。**
120 | > > >
121 | > > > **2)对系统的修改可以对调用者透明,便于系统切换和更新。**
122 | > >
123 | > > Proxy的典型用途是:
124 | > >
125 | > > > **1)简化对象调用(比如权限控制之类的事情可以在Proxy内部实现,不需要调用者操心)。**
126 | > > >
127 | > > > **2)使对象调用轻量化,原本需要对N个对象进行调用的操作,现在只需通过一个Proxy,内存开销大大减小。**
128 | >
129 | > ### 3、有switch,考虑Strategy模式
130 | >
131 | > > switch语句常常说明:
132 | > >
133 | > > > **1)需要多态行为。**
134 | > > >
135 | > > > **2)存在指责错放(类的职责过多)。**
136 | > >
137 | > > 这里可以采用Strategy模式配合多态简化判断逻辑,**把职责下放给子类,消除集中的父类职责。**
138 | >
139 | > ### 4、Bridge模式
140 | >
141 | > > Bridge模式是典型的“组合优先于继承”的范例,牢记一点:**对象和方法是多对多关系的时候,就可以考虑使用Bridge模式,而不是滥用继承。**此外Bridge模式对系统的可扩展性有极大的帮助。
142 | >
143 | > ### 5、Template Method模式
144 | >
145 | > > 我觉得Template Method模式和重构中的[**Extract Method**](http://www.refactoring.com/catalog/extractMethod.html)非常相似,都是提取公共代码从而消除冗余并避免重复代码潜在的隐患。而Template Method模式最常用的的意图是:**定义一个操作中算法的骨架,而将一些步骤延迟到子类中。在不改变算法的结构的前提下重定义它的步骤。**这种思想和之前讲的反过来,先有父类,定义算法骨架,其中某些步骤是确定的,某些是不确定的或是需要变化的。根据可变性分析,后者就应该延迟到子类实现,子类实现时只需要实现那些会变化的方法即可。
--------------------------------------------------------------------------------
/Code/JVM.md:
--------------------------------------------------------------------------------
1 | ### [浅析Java虚拟机结构与机制](http://blog.hesey.net/2011/04/introduction-to-java-virtual-machine.html)
2 |
3 | 本文旨在给所有希望了解[**JVM**](http://en.wikipedia.org/wiki/Java_Virtual_Machine)(Java Virtual Machine)的同学一个概念性的入门,主要介绍了JVM的组成部分以及它们内部工作的机制和原理。当然本文只是一个简单的入门,不会涉及过多繁杂的参数和配置,感兴趣的同学可以做更深入的研究,在研究JVM的过程中会发现,其实JVM本身就是一个计算机体系结构,很多原理和我们平时的硬件、微机原理、操作系统都有十分相似的地方,所以学习JVM本身也是加深自我对计算机结构认识的一个很好的途径。
4 |
5 | 另外需要注意的是,虽然平时我们用的大多是Sun(现已被Oracle收购)JDK提供的JVM,但是JVM本身是一个[**规范**](http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html),所以可以有多种实现,除了[**Hotspot**](http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136373.html)外,还有诸如Oracle的[**JRockit**](http://www.oracle.com/technetwork/middleware/jrockit/overview/index.html)、IBM的[**J9**](http://en.wikipedia.org/wiki/IBM_J9)也都是非常有名的JVM。
6 |
7 | # 一、JVM结构
8 |
9 | > 下图展示了JVM的主要结构:
10 | >
11 | > [](http://blog.hesey.net/wp-content/uploads/2011/04/1353310.png)
12 | >
13 | > 可以看出,JVM主要由**类加载器子系统、运行时数据区(内存空间)、执行引擎以及与本地方法接口**等组成。其中运行时数据区又由**方法区、堆、Java栈、PC寄存器、本地方法栈**组成。
14 | >
15 | > 从上图中还可以看出,在内存空间中**方法区和堆是所有Java线程共享的**,而**Java栈、本地方法栈、PC寄存器则由每个线程私有**,这会引出一些问题,后文会进行具体讨论。
16 | >
17 | > 众所周知,Java语言具有跨平台的特性,这也是由JVM来实现的。更准确地说,是Sun利用JVM在不同平台上的实现帮我们把平台相关性的问题给解决了,这就好比是HTML语言可以在不同厂商的浏览器上呈现元素(虽然某些浏览器在对W3C标准的支持上还有一些问题)。同时,Java语言支持通过JNI(Java Native Interface)来实现本地方法的调用,但是需要注意到,如果你在Java程序用调用了本地方法,那么你的程序就很可能不再具有跨平台性,即本地方法会破坏平台无关性。
18 |
19 | # 二、类加载器子系统(Class Loader)
20 |
21 | > 类加载器子系统负责加载编译好的.class字节码文件,并装入内存,使JVM可以实例化或以其它方式使用加载后的类。JVM的类加载子系统支持在运行时的动态加载,动态加载的优点有很多,例如可以节省内存空间、灵活地从网络上加载类,动态加载的另一好处是可以通过命名空间的分隔来实现类的隔离,增强了整个系统的安全性。
22 | >
23 | > ### 1、ClassLoader的分类:
24 | >
25 | > **a.启动类加载器(BootStrap Class Loader)**:负责加载rt.jar文件中所有的Java类,即Java的核心类都是由该ClassLoader加载。在Sun JDK中,这个类加载器是由C++实现的,并且在Java语言中无法获得它的引用。
26 | >
27 | > **b.扩展类加载器(Extension Class Loader)**:负责加载一些扩展功能的jar包。
28 | >
29 | > **c.系统类加载器(System Class Loader)**:负责加载启动参数中指定的Classpath中的jar包及目录,通常我们自己写的Java类也是由该ClassLoader加载。在Sun JDK中,系统类加载器的名字叫AppClassLoader。
30 | >
31 | > **d.用户自定义类加载器(User Defined Class Loader)**:由用户自定义类的加载规则,可以手动控制加载过程中的步骤。
32 | >
33 | > ### 2、ClassLoader的工作原理
34 | >
35 | > 类加载分为装载、链接、初始化三步。
36 | >
37 | > **a.装载**
38 | >
39 | > > 通过类的全限定名和ClassLoader加载类,主要是将指定的.class文件加载至JVM。当类被加载以后,在JVM内部就以“类的全限定名+ClassLoader实例ID”来标明类。
40 | > >
41 | > > 在内存中,ClassLoader实例和类的实例都位于堆中,它们的类信息都位于方法区。
42 | > >
43 | > > 装载过程采用了一种被称为“[**双亲委派模型(Parent Delegation Model)**](http://publib.boulder.ibm.com/infocenter/javasdk/v6r0/index.jsp?topic=/com.ibm.java.doc.diagnostics.60/diag/understanding/cl_delegation.html)”的方式,当一个ClassLoader要加载类时,它会先请求它的双亲ClassLoader(其实这里只有两个ClassLoader,所以称为父ClassLoader可能更容易理解)加载类,而它的双亲ClassLoader会继续把加载请求提交再上一级的ClassLoader,直到启动类加载器。只有其双亲ClassLoader无法加载指定的类时,它才会自己加载类。
44 | > >
45 | > > 双亲委派模型是JVM的第一道安全防线,它保证了类的安全加载,这里同时依赖了类加载器隔离的原理:不同类加载器加载的类之间是无法直接交互的,即使是同一个类,被不同的ClassLoader加载,它们也无法感知到彼此的存在。这样即使有恶意的类冒充自己在核心包(例如java.lang)下,由于它无法被启动类加载器加载,也造成不了危害。
46 | > >
47 | > > 由此也可见,如果用户自定义了类加载器,那就必须自己保障类加载过程中的安全。
48 | >
49 | > **b.链接**
50 | >
51 | > > 链接的任务是把二进制的类型信息合并到JVM运行时状态中去。
52 | > >
53 | > > 链接分为以下三步:
54 | > >
55 | > > > a.验证:校验.class文件的正确性,确保该文件是符合规范定义的,并且适合当前JVM使用。
56 | > > >
57 | > > > b.准备:为类分配内存,同时初始化类中的静态变量赋值为默认值。
58 | > > >
59 | > > > c.解析(可选):主要是把类的常量池中的符号引用解析为直接引用,这一步可以在用到相应的引用时再解析。
60 | >
61 | > **c.初始化**
62 | >
63 | > > 初始化类中的静态变量,并执行类中的static代码、构造函数。
64 | > >
65 | > > JVM规范严格定义了何时需要对类进行初始化:
66 | > >
67 | > > > a、通过new关键字、反射、clone、反序列化机制实例化对象时。
68 | > > >
69 | > > > b、调用类的静态方法时。
70 | > > >
71 | > > > c、使用类的静态字段或对其赋值时。
72 | > > >
73 | > > > d、通过反射调用类的方法时。
74 | > > >
75 | > > > e、初始化该类的子类时(初始化子类前其父类必须已经被初始化)。
76 | > > >
77 | > > > f、JVM启动时被标记为启动类的类(简单理解为具有main方法的类)。
78 |
79 | # 三、Java栈(Java Stack)
80 |
81 | > Java栈由栈帧组成,一个帧对应一个方法调用。调用方法时压入栈帧,方法返回时弹出栈帧并抛弃。Java栈的主要任务是存储方法参数、局部变量、中间运算结果,并且提供部分其它模块工作需要的数据。前面已经提到Java栈是线程私有的,这就保证了线程安全性,使得程序员无需考虑栈同步访问的问题,只有线程本身可以访问它自己的局部变量区。
82 | >
83 | > 它分为三部分:局部变量区、操作数栈、帧数据区。
84 | >
85 | > **1、局部变量区**
86 | >
87 | > > 局部变量区是以字长为单位的数组,在这里,byte、short、char类型会被转换成int类型存储,除了long和double类型占两个字长以外,其余类型都只占用一个字长。特别地,boolean类型在编译时会被转换成int或byte类型,boolean数组会被当做byte类型数组来处理。局部变量区也会包含对象的引用,包括类引用、接口引用以及数组引用。
88 | > >
89 | > > 局部变量区包含了方法参数和局部变量,此外,实例方法隐含第一个局部变量this,它指向调用该方法的对象引用。对于对象,局部变量区中永远只有指向堆的引用。
90 | >
91 | > **2、操作数栈**
92 | >
93 | > > 操作数栈也是以字长为单位的数组,但是正如其名,它只能进行入栈出栈的基本操作。在进行计算时,操作数被弹出栈,计算完毕后再入栈。
94 | >
95 | > **3、帧数据区**
96 | >
97 | > > 帧数据区的任务主要有:
98 | > >
99 | > > a.记录指向类的常量池的指针,以便于解析。
100 | > >
101 | > > b.帮助方法的正常返回,包括恢复调用该方法的栈帧,设置PC寄存器指向调用方法对应的下一条指令,把返回值压入调用栈帧的操作数栈中。
102 | > >
103 | > > c.记录异常表,发生异常时将控制权交由对应异常的catch子句,如果没有找到对应的catch子句,会恢复调用方法的栈帧并重新抛出异常。
104 | >
105 | > 局部变量区和操作数栈的大小依照具体方法在编译时就已经确定。调用方法时会从方法区中找到对应类的类型信息,从中得到具体方法的局部变量区和操作数栈的大小,依此分配栈帧内存,压入Java栈。
106 |
107 | # 四、本地方法栈(Native Method Stack)
108 |
109 | > 本地方法栈类似于Java栈,主要存储了本地方法调用的状态。在Sun JDK中,本地方法栈和Java栈是同一个。
110 |
111 | # 五、方法区(Method Area)
112 |
113 | > 类型信息和类的静态变量都存储在方法区中。方法区中对于每个类存储了以下数据:
114 | >
115 | > > a.类及其父类的全限定名(java.lang.Object没有父类)
116 | > >
117 | > > b.类的类型(Class or Interface)
118 | > >
119 | > > c.访问修饰符(public, abstract, final)
120 | > >
121 | > > d.实现的接口的全限定名的列表
122 | > >
123 | > > e.常量池
124 | > >
125 | > > f.字段信息
126 | > >
127 | > > g.方法信息
128 | > >
129 | > > h.静态变量
130 | > >
131 | > > i.ClassLoader引用
132 | > >
133 | > > j.Class引用
134 | >
135 | > 可见类的所有信息都存储在方法区中。由于方法区是所有线程共享的,所以必须保证线程安全,举例来说,如果两个类同时要加载一个尚未被加载的类,那么一个类会请求它的ClassLoader去加载需要的类,另一个类只能等待而不会重复加载。
136 | >
137 | > 此外为了加快调用方法的速度,通常还会为每个非抽象类创建私有的方法表,方法表是一个数组,存放了实例可能被调用的实例方法的直接引用。方法表对于多态有非常重要的意义,具体可以参照《[**浅谈多态机制的意义及实现**](http://blog.hesey.net/2010/12/significance-and-implementation-of-polymorphism.html)》一文中“多态的实现”一节。
138 | >
139 | > 在Sun JDK中,方法区对应了持久代(Permanent Generation),默认最小值为16MB,最大值为64MB。
140 |
141 | # 六、堆(Heap)
142 |
143 | > 堆用于存储对象实例以及数组值。堆中有指向类数据的指针,该指针指向了方法区中对应的类型信息。堆中还可能存放了指向方法表的指针。堆是所有线程共享的,所以在进行实例化对象等操作时,需要解决同步问题。此外,堆中的实例数据中还包含了对象锁,并且针对不同的垃圾收集策略,可能存放了引用计数或清扫标记等数据。
144 | >
145 | > 在堆的管理上,Sun JDK从1.2版本开始引入了分代管理的方式。主要分为新生代、旧生代。分代方式大大改善了垃圾收集的效率。
146 | >
147 | > **1、新生代(New Generation)**
148 | >
149 | > > 大多数情况下新对象都被分配在新生代中,新生代由Eden Space和两块相同大小的Survivor Space组成,后两者主要用于Minor GC时的对象复制(Minor GC的过程在此不详细讨论)。
150 | > >
151 | > > JVM在Eden Space中会开辟一小块独立的[**TLAB**](http://blogs.sun.com/jonthecollector/entry/the_real_thing)(Thread Local Allocation Buffer)区域用于更高效的内存分配,我们知道在堆上分配内存需要锁定整个堆,而在TLAB上则不需要,JVM在分配对象时会尽量在TLAB上分配,以提高效率。
152 | >
153 | > **2、旧生代(Old Generation/Tenuring Generation)**
154 | >
155 | > > 在新生代中存活时间较久的对象将会被转入旧生代,旧生代进行垃圾收集的频率没有新生代高。
156 |
157 | # 七、执行引擎
158 |
159 | > 执行引擎是JVM执行Java字节码的核心,执行方式主要分为**解释执行、编译执行、自适应优化执行、硬件芯片执行**方式。
160 | >
161 | > JVM的指令集是基于栈而非寄存器的,这样做的好处在于可以使指令尽可能紧凑,便于快速地在网络上传输(别忘了Java最初就是为网络设计的),同时也很容易适应通用寄存器较少的平台,并且有利于代码优化,由于Java栈和PC寄存器是线程私有的,线程之间无法互相干涉彼此的栈。每个线程拥有独立的JVM执行引擎实例。
162 | >
163 | > JVM指令由单字节操作码和若干操作数组成。对于需要操作数的指令,通常是先把操作数压入操作数栈,即使是对局部变量赋值,也会先入栈再赋值。注意这里是“通常”情况,之后会讲到由于优化导致的例外。
164 | >
165 | > **1、解释执行**
166 | >
167 | > > 和一些动态语言类似,JVM可以解释执行字节码。Sun JDK采用了[**token-threading**](http://www.complang.tuwien.ac.at/forth/threaded-code.html)的方式,感兴趣的同学可以深入了解一下。
168 | > >
169 | > > 解释执行中有几种优化方式:
170 | > >
171 | > > > **a.栈顶缓存**
172 | > > >
173 | > > > 将位于操作数栈顶的值直接缓存在寄存器上,对于大部分只需要一个操作数的指令而言,就无需再入栈,可以直接在寄存器上进行计算,结果压入操作数站。这样便减少了寄存器和内存的交换开销。
174 | > > >
175 | > > > **b.部分栈帧共享**
176 | > > >
177 | > > > 被调用方法可将调用方法栈帧中的操作数栈作为自己的局部变量区,这样在获取方法参数时减少了复制参数的开销。
178 | > > >
179 | > > > **c.执行机器指令**
180 | > > >
181 | > > > 在一些特殊情况下,JVM会执行机器指令以提高速度。
182 | >
183 | > **2、编译执行**
184 | >
185 | > > 为了提升执行速度,Sun JDK提供了将字节码编译为机器指令的支持,主要利用了[**JIT**](http://en.wikipedia.org/wiki/Just-in-time_compilation)(Just-In-Time)编译器在运行时进行编译,它会在第一次执行时编译字节码为机器码并缓存,之后就可以重复利用。Oracle JRockit采用的是完全的编译执行。
186 | >
187 | > **3、自适应优化执行**
188 | >
189 | > > 自适应优化执行的思想是程序中10%~20%的代码占据了80%~90%的执行时间,所以通过将那少部分代码编译为优化过的机器码就可以大大提升执行效率。自适应优化的典型代表是Sun的Hotspot VM,正如其名,JVM会监测代码的执行情况,当判断特定方法是瓶颈或热点时,将会启动一个后台线程,把该方法的字节码编译为极度优化的、静态链接的C++代码。当方法不再是热区时,则会取消编译过的代码,重新进行解释执行。
190 | > >
191 | > > 自适应优化不仅通过利用小部分的编译时间获得大部分的效率提升,而且由于在执行过程中时刻监测,对内联代码等优化也起到了很大的作用。由于面向对象的多态性,一个方法可能对应了很多种不同实现,自适应优化就可以通过监测只内联那些用到的代码,大大减少了内联函数的大小。
192 | >
193 | > Sun JDK在编译上采用了两种模式:Client和Server模式。前者较为轻量级,占用内存较少。后者的优化程序更高,占用内存更多。
194 | >
195 | > 在Server模式中会进行对象的逃逸分析,即方法中的对象是否会在方法外使用,如果被其它方法使用了,则该对象是逃逸的。对于非逃逸对象,JVM会在栈上直接分配对象(所以对象不一定是在堆上分配的),线程获取对象会更加快速,同时当方法返回时,由于栈帧被抛弃,也有利于对象的垃圾收集。Server模式还会通过分析去除一些不必要的同步,感兴趣的同学可以研究一下Sun JDK 6引入的[**Biased Locking**](http://blogs.sun.com/dave/entry/biased_locking_in_hotspot)机制。
196 | >
197 | > 此外,执行引擎也必须保证线程安全性,因而[**JMM**](http://www.cs.umd.edu/%7Epugh/java/memoryModel/)(Java Memory Model)也是由执行引擎确保的。
--------------------------------------------------------------------------------
/Code/ObjectPool.md:
--------------------------------------------------------------------------------
1 | # ObjectPool
2 |
3 | ## 一个超级对象池的实现
4 |
5 |
6 |
7 | 对象池对于创建开销比较大的对象来说很有意义,为了避免重复创建开销比较大的对象,我们可以通过对象池来优化。对象池的思路比较简单,事先创建好一批对
8 | 象,放到一个集合中,以后每当程序需要新的对象时候,都从对象池里获取,每当程序用完该对象后,都把该对象归还给对象池。这样会避免重复的对象创建,提高
9 | 程序性能。先来看看对象池的简单实现:
10 |
11 | ```c++
12 | #include
13 |
14 | template
15 | class ObjectPool
16 | {
17 | public:
18 |
19 | ObjectPool(size_t unSize) :
20 | m_unSize(unSize)
21 | {
22 | for (size_t unIdx = 0; unIdx < m_unSize; ++ unIdx)
23 | {
24 | m_oPool.push_back(new Object());
25 | }
26 | }
27 |
28 | ~ObjectPool()
29 | {
30 | typename std::list