├── .gitignore
├── .travis.yml
├── README.md
├── develop.sh
├── img
├── android-activity-lifecircle.jpg
├── android-animation-demo.gif
├── android-animation-eachpng.jpg
├── android-animation-onepng.jpg
├── android-lanchmode-singleinstance.gif
├── android-lanchmode-singletask.gif
├── android-lanchmode-singletop.gif
├── android-lanchmode-standard.gif
├── android-listview.jpg
├── android-service-callback.png
├── android-service-lifecircle.png
├── android-system-architecture.jpg
├── basic-design-gop.png
├── gcd-deadlock-1.png
├── gcd-deadlock-2.png
├── gcd-deadlock-3.png
├── gcd-deadlock-4.png
├── gcd-deadlock-5.png
├── hash-table.jpg
├── ios-nsoperation-lifecycle.png
├── ios-runtime-class.png
├── ios-runtime-method-resolve.png
├── tcp-connection-closed-four-way-handshake.png
└── tcp-connection-made-three-way-handshake.png
├── package.json
├── serve.sh
├── source
├── Android
│ ├── Java
│ │ ├── Questions.md
│ │ └── README.md
│ ├── Questions.md
│ ├── README.md
│ └── basic
│ │ ├── Activity-Service-Lifecircle.md
│ │ ├── Android-Animation.md
│ │ ├── Android-Arch.md
│ │ ├── Android-Large-Image.md
│ │ ├── Android-LaunchMode.md
│ │ ├── Android-handler-thread-looper.md
│ │ ├── ListView-Optimize.md
│ │ └── README.md
├── README.md
├── SUMMARY.md
├── Server
│ ├── README.md
│ ├── Web
│ │ └── Spring.md
│ └── db
│ │ ├── DB-Index.md
│ │ ├── README.md
│ │ └── Transaction.md
├── basic
│ ├── README.md
│ ├── algo
│ │ ├── DP.md
│ │ ├── Greedy.md
│ │ ├── Hash-Table.md
│ │ ├── Linked-List.md
│ │ ├── README.md
│ │ ├── Random.md
│ │ ├── Sorting.md
│ │ └── Tree.md
│ ├── arch
│ │ ├── Arch.md
│ │ ├── Concurrency.md
│ │ ├── Disk-And-File.md
│ │ ├── Memory-Management.md
│ │ ├── OS.md
│ │ └── README.md
│ ├── compiler
│ │ ├── Compiler-Arch.md
│ │ └── README.md
│ ├── design
│ │ ├── GOP.md
│ │ ├── MVC.md
│ │ ├── OO-Basic.md
│ │ └── README.md
│ ├── network
│ │ ├── HTTP.md
│ │ ├── HTTPS.md
│ │ ├── IP.md
│ │ ├── README.md
│ │ ├── Socket-Programming-Basic.md
│ │ ├── TCP.md
│ │ └── UDP.md
│ └── scm
│ │ ├── Git.md
│ │ ├── README.md
│ │ └── SVN.md
├── book.json
└── iOS
│ ├── Cocoa-Touch
│ ├── Animation.md
│ ├── Design.md
│ ├── Event-Handling.md
│ ├── File-System.md
│ ├── Multithreading.md
│ ├── Network.md
│ ├── Performance.md
│ ├── README.md
│ ├── UIApplication.md
│ ├── UIView-Basic.md
│ └── UIViewController.md
│ ├── More.md
│ ├── ObjC-Basic
│ ├── Block.md
│ ├── Class.md
│ ├── MM.md
│ ├── Objective-C-Introspection.md
│ ├── Objective-C-Message.md
│ ├── README.md
│ ├── Runloop.md
│ └── Runtime.md
│ ├── Questions.md
│ ├── README.md
│ └── Swift
│ ├── Class.md
│ ├── Function-And-Closure.md
│ ├── README.md
│ └── Struct-And-Enum.md
└── update.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node rules:
2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
3 | .grunt
4 |
5 | ## Dependency directory
6 | ## Commenting this out is preferred by some people, see
7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
8 | node_modules
9 |
10 | # Book build output
11 | _book
12 |
13 | # eBook build output
14 | *.epub
15 | *.mobi
16 | *.pdf
17 |
18 | # macOS
19 | .DS_Store
20 | ._*
21 |
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5.8.0"
4 | cache:
5 | directories:
6 | - node_modules
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 笔试面试知识整理
2 | ================
3 |
4 | [](https://travis-ci.org/HIT-Alibaba/interview)
5 |
6 | ### 在线阅读
7 |
8 | 本文档使用 [Gitbook](https://github.com/GitbookIO/gitbook) 制作,[在线阅读地址](http://hit-alibaba.github.io/interview/index.html)。
9 |
10 | 所有引用内容版权归原作者所有。
11 |
12 | 使用 [知识共享“署名-非商业性使用-相同方式共享 3.0 中国大陆”许可协议](https://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 授权。
13 |
14 | ### 贡献者:
15 |
16 | * [skyline75489](https://github.com/skyline75489)
17 | * [winlandiano](https://github.com/winlandiano)
18 | * [dodola](https://github.com/dodola)
19 | * [AveryLiu](https://github.com/AveryLiu)
20 | * [JackAlan](https://github.com/AlanMelody)
21 |
22 |
23 | ### 更多优秀教程:
24 |
25 | * [what-happens-when-zh_CN](https://github.com/skyline75489/what-happens-when-zh_CN)——你可能想象不到,浏览器中有如此多的不为人知
26 | * [Heart First Java Web](https://github.com/skyline75489/Heart-First-JavaWeb) ——可能是第一个说人话的 Java Web 入门教程
27 | * [learnrx-zh-cn](https://github.com/skyline75489/learnrx-zh-cn)——Rx 官方出品互动式教程,中文精心翻译,新手老手都能有所收获
28 |
29 | ### 联系作者
30 |
31 | [Telegram 群](https://t.me/joinchat/LrG3NxxPhcgvNJ6pB49UPg)
32 |
--------------------------------------------------------------------------------
/develop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd ..
3 | mkdir interview-gitbook
4 | cd interview-gitbook
5 | git init
6 | git remote add origin https://github.com/HIT-Alibaba/interview.git
7 | git fetch
8 | git checkout gh-pages
9 | cd ../interview
10 | cd source
11 | gitbook install
12 | cd ..
13 |
14 |
--------------------------------------------------------------------------------
/img/android-activity-lifecircle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-activity-lifecircle.jpg
--------------------------------------------------------------------------------
/img/android-animation-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-animation-demo.gif
--------------------------------------------------------------------------------
/img/android-animation-eachpng.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-animation-eachpng.jpg
--------------------------------------------------------------------------------
/img/android-animation-onepng.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-animation-onepng.jpg
--------------------------------------------------------------------------------
/img/android-lanchmode-singleinstance.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-lanchmode-singleinstance.gif
--------------------------------------------------------------------------------
/img/android-lanchmode-singletask.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-lanchmode-singletask.gif
--------------------------------------------------------------------------------
/img/android-lanchmode-singletop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-lanchmode-singletop.gif
--------------------------------------------------------------------------------
/img/android-lanchmode-standard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-lanchmode-standard.gif
--------------------------------------------------------------------------------
/img/android-listview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-listview.jpg
--------------------------------------------------------------------------------
/img/android-service-callback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-service-callback.png
--------------------------------------------------------------------------------
/img/android-service-lifecircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-service-lifecircle.png
--------------------------------------------------------------------------------
/img/android-system-architecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/android-system-architecture.jpg
--------------------------------------------------------------------------------
/img/basic-design-gop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/basic-design-gop.png
--------------------------------------------------------------------------------
/img/gcd-deadlock-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/gcd-deadlock-1.png
--------------------------------------------------------------------------------
/img/gcd-deadlock-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/gcd-deadlock-2.png
--------------------------------------------------------------------------------
/img/gcd-deadlock-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/gcd-deadlock-3.png
--------------------------------------------------------------------------------
/img/gcd-deadlock-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/gcd-deadlock-4.png
--------------------------------------------------------------------------------
/img/gcd-deadlock-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/gcd-deadlock-5.png
--------------------------------------------------------------------------------
/img/hash-table.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/hash-table.jpg
--------------------------------------------------------------------------------
/img/ios-nsoperation-lifecycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/ios-nsoperation-lifecycle.png
--------------------------------------------------------------------------------
/img/ios-runtime-class.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/ios-runtime-class.png
--------------------------------------------------------------------------------
/img/ios-runtime-method-resolve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/ios-runtime-method-resolve.png
--------------------------------------------------------------------------------
/img/tcp-connection-closed-four-way-handshake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/tcp-connection-closed-four-way-handshake.png
--------------------------------------------------------------------------------
/img/tcp-connection-made-three-way-handshake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/img/tcp-connection-made-three-way-handshake.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "interview",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "pretest": "gitbook install source",
6 | "test": "gitbook build source"
7 | },
8 | "devDependencies": {
9 | "gitbook-cli": "^1.0.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/serve.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd source
3 | gitbook serve
4 |
--------------------------------------------------------------------------------
/source/Android/Java/Questions.md:
--------------------------------------------------------------------------------
1 | 1. 垃圾回收机制。。。(主要从下面几方面解答 GC原理、最好画图解释一下年轻代(Eden区和Survival区)、年老代、比例分配及为啥要这样分代回收)
2 | 2. 对象分配问题,堆栈里的问题,详细的会问道方法区、堆、程序计数器、本地方法栈、虚拟机栈,问题入口从String a,new String("")开始
3 | 3. 关键字,private protected public static final 组合着问
4 | 4. Object类里面有哪几种方法,作用
5 | 5. equals 和 hashCode方法,重写equals的原则()
6 | 6. 向上转型
7 | 7. Java引用类型(强引用,软引用,弱引用,虚引用)
8 | 8. 线程相关的,主要是volitate,synchorized,wait(),notify(),notifyAll(),join()
9 | 9. Exception和Error
10 | 10. 反射的用途
11 | 11. HashMap实现原理(数组+链表),查找数据的时间复杂度
12 | 12. List有哪些子类,各有什么区别
13 | 13. NIO相关,缓冲区、通道、selector。。。(不熟,面了这么多,挂在这里。其实主要是表现在同步阻塞和异步,传输方式不同。标准IO无法实现非阻塞模式、文件锁、读选择、分散聚集等)
14 | 14. 内存泄露,举个例子
15 | 15. OOM是怎么出现的,有哪几块JVM区域会产生OOM,如何解决(对于该问题,建议去《Java特种兵》的3.6章)
16 | 16. Java里面的观察者模式实现
17 | 17. 单例实现(我一般用enum写,不容易被挑毛病)
18 | 18. 用Java模拟一个栈,并能够做到扩容,并且能有同步锁。(用数组实现)
19 | 19. Java泛型机制,泛型机制的优点,以及类型变量
--------------------------------------------------------------------------------
/source/Android/Java/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/Android/Java/README.md
--------------------------------------------------------------------------------
/source/Android/Questions.md:
--------------------------------------------------------------------------------
1 | 13. 什么是ANR,如何避免
2 | 14. [[ListView原理与优化|ListView-Optimize]]
3 | 15. ContentProvider实现原理
4 | 16. 介绍Binder机制
5 | 17. 匿名共享内存,使用场景
6 | 18. 如何自定义View,如果要实现一个转盘圆形的View,需要重写View中的哪些方法?(onLayout,onMeasure,onDraw)
7 | 19. Android事件分发机制
8 | 20. Socket和LocalSocket
9 | 21. [[如何加载大图片|Android-Large-Image]]
10 | 22. HttpClient和URLConnection的区别,怎么使用https
11 | 23. Parcelable和Serializable区别
12 | 24. Android里跨进程传递数据的几种方案。(Binder,文件[面试官说这个不算],Socket,匿名共享内存(Anonymous Shared Memory))
13 | 25. 布局文件中,layout_gravity 和 gravity 以及 weight的作用。
14 | 26. ListView里的ViewType机制
15 | 27. TextView怎么改变局部颜色(SpannableString或者HTML)
16 | 28. Activity A 跳转到 Activity B,生命周期的执行过程是啥?(此处有坑 ActivityA的OnPause和ActivityB的onResume谁先执行)
17 | 29. Android中Handler声明非静态对象会发出警告,为什么,非得是静态的?(Memory Leak)
18 | 30. ListView使用过程中是否可以调用addView(不能,话说这题考来干啥。。。)
19 | 31. [[Android中的Thread, Looper和Handler机制(附带HandlerThread与AsyncTask)|Android-handler-thread-looper]]
20 | 32. Application类的作用
21 | 33. View的绘制过程
22 | 34. 广播注册后不解除注册会有什么问题?(内存泄露)
23 | 35. 属性动画(Property Animation)和补间动画(Tween Animation)的区别,为什么在3.0之后引入属性动画([官方解释:调用简单](http://android-developers.blogspot.com/2011/05/introducing-viewpropertyanimator.html))
24 | 36. 有没有使用过EventBus或者Otto框架,主要用来解决什么问题,内部原理
25 | 37. 设计一个网络请求框架(可以参考Volley框架)
26 | 38. 网络图片加载框架(可以参考BitmapFun)
27 | 39. Android里的LRU算法原理
28 | 40. BrocastReceive里面可不可以执行耗时操作?
29 | 41. Service onBindService 和startService 启动的区别
--------------------------------------------------------------------------------
/source/Android/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/Android/README.md
--------------------------------------------------------------------------------
/source/Android/basic/Activity-Service-Lifecircle.md:
--------------------------------------------------------------------------------
1 | ##Activity生命周期
2 | ###总论
3 | 了解Activity的生命周期,需要了解:
4 |
5 | 1. 四种状态
6 | 2. 七个重要方法
7 | 3. 三个嵌套循环
8 | 4. 其他
9 |
10 | 首先在开头放出生命周期的一张总图:
11 | 
12 |
13 | ###四种状态
14 | 四种状态包括
15 |
16 | + 活动(Active/Running)状态
17 | + 暂停(Paused)状态
18 | + 停止(Stopped)状态
19 | + 非活动(Dead)状态
20 |
21 | ####1. 活动(Active/Running)状态
22 | 当Activity运行在屏幕前台(处于当前任务活动栈的最上面),此时它获取了焦点能响应用户的操作,属于运行状态,同一个时刻只会有一个Activity 处于活动(Active)或运行(Running)状态。
23 |
24 | 此状态由onResume()进入,由onPause()退出
25 |
26 | ####2. 暂停(Paused)状态
27 | 当Activity失去焦点(如在它之上有另一个透明的Activity或返回桌面)它将处于暂停, 再进而进入其他状态。暂停的Activity仍然是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接),但是当系统内存极小时可以被系统杀掉。Android7.0后, 多窗口模式下失去焦点的Activity也将进入onPause,但这不意味着Activity中的活动(动画、视频)等会暂停。虽然官方文档使用的是"an activity is going into the background" 来描述,但这不意味着一个Toast或者由本Activity创建的Dialog会调用onPause。结合[这里](https://hit-alibaba.github.io/interview/Android/basic/Android-LaunchMode.html)对Activity的栈机制不难理解,只要当前Activity仍处于栈顶,系统就默认其仍处于活跃状态。
28 |
29 | 此状态由onPause()进入,可能下一步进入onResume()或者onCreate()重新唤醒软件,或者被onStop()杀掉
30 |
31 | ####3. 停止(Stopped)状态
32 | 完全被另一个Activity遮挡时处于停止状态,它仍然保留着所有的状态和成员信息。只是对用户不可见,当其他地方需要内存时它往往被系统杀掉。
33 |
34 | 该状态由onStop()进入,如果被杀掉,可能进入onCreate()或onRestart(),如果彻底死亡,进入onDestroy()
35 |
36 | ###Service生命周期
37 | Service有两种启动方式:
38 |
39 | + `startService()` 启动本地服务`Local Service`
40 | + `bindService()` 启动远程服务`Remote Service`
41 |
42 | 远程服务允许暴露接口并让系统内不同程序相互注册调用。Local Service无法抵抗一些系统清理程序如MIUI自带的内存清除。
43 |
44 | 具体如何防止自己的Service被杀死可以看这个博客[Android开发之如何保证Service不被杀掉(broadcast+system/app)](http://blog.csdn.net/mad1989/article/details/22492519),已经做到很变态的程度了。此外今天看到[如何看待 MIUI 工程师袁军对 QQ 后台机制的评论?](http://www.zhihu.com/question/28876912#answer-12365467),QQ的开启一个像素在前台的做法真的是…呵呵
45 |
46 | 两种不同的启动方式决定了`Service`具有两种生命周期的可能(并非互斥的两种)。概括来说,`Service`在被创建之后都会进入回调`onCreate()`方法,随后根据启动方式分别回调`onStartCommand()`方法和`onBind()`方法。如果`Service`是经由`bindService()`启动,则需要所有client全部调用`unbindService()`才能将`Service`释放等待系统回收。
47 |
48 | 一张图解释:
49 |
50 | 
51 |
52 | 回调方法的结构下图解释的很明白:
53 |
54 | 
55 |
56 | 参考博客:[圣骑士Wind的博客:Android Service的生命周期](http://www.cnblogs.com/mengdd/archive/2013/03/24/2979944.html)
--------------------------------------------------------------------------------
/source/Android/basic/Android-Animation.md:
--------------------------------------------------------------------------------
1 | ##Android中的动画
2 |
3 | ###综述
4 |
5 | Android中的动画分为补间动画(Tweened Animation)和逐帧动画(Frame-by-Frame Animation)。没有意外的,补间动画是在几个关键的节点对对象进行描述又系统进行填充。而逐帧动画是在固定的时间点以一定速率播放一系列的drawable资源。下面对两种动画进行分别简要说明。
6 |
7 | ###补间动画
8 |
9 | 补间动画分为如下种
10 |
11 | + Alpha 淡入淡出
12 | + Scale 缩放
13 | + Rotate 旋转
14 | + Translate 平移
15 |
16 | 这些动画是可以同时进行和顺次进行的。需要用到AnimationSet来实现。调用AnimationSet.addAnimation()即可。
17 | 实现方法举例:
18 |
19 | ```
20 | (Button)btn = (Button)findViewById(...);
21 | AnimationSet as = new AnimationSet(false);//新建AnimationSet实例
22 | TranslateAnimation ta = new TranslateAnimation(//新建平移动画实例,在构造函数中传入平移的始末位置
23 | Animation.RELATIVE_TO_SELF, 0f,
24 | Animation.RELATIVE_TO_SELF, 0.3f,
25 | Animation.RELATIVE_TO_SELF, 0f,
26 | Animation.RELATIVE_TO_SELF, 0.3f);
27 | ta.setStartOffset(0);//AnimationSet被触发后立刻执行
28 | ta.setInterpolator(new AccelerateDecelerateInterpolator());//加入一个加速减速插值器
29 | ta.setFillAfter(true);//动画结束后保持该状态
30 | ta.setDuration(700);//设置动画时长
31 |
32 | ScaleAnimation sa = new ScaleAnimation(1f, 0.1f, 1f, 0.1f,//构造一个缩放动画实例,构造函数参数传入百分比和缩放中心
33 | ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
34 | ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
35 | sa.setInterpolator(new AccelerateDecelerateInterpolator());//加入一个加速减速插值器
36 | sa.setDuration(700);//设置时长
37 | sa.setFillAfter(true);//动画结束后保持该状态
38 | sa.setStartOffset(650);//AnimationSet触发后650ms启动动画
39 |
40 | AlphaAnimation aa = new AlphaAnimation(1f, 0f);//构造一个淡出动画,从100%变为0%
41 | aa.setDuration(700);//设置时长
42 | aa.setStartOffset(650);//AnimationSet触发后650ms启动动画
43 | aa.setFillAfter(true);//动画结束后保持该状态
44 |
45 | as.addAnimation(ta);
46 | as.addAnimation(sa);
47 | as.addAnimation(aa);//将动画放入AnimationSet中
48 |
49 | btn.setOnClickListener(new OnClickListener(){
50 | public void onClick(View view){
51 | btn.startAnimation(as);//触发动画
52 | }
53 | }
54 | ```
55 | 该段代码实现了先平移,然后边缩小边淡出。
56 |
57 | 具体的代码实现需要注意各个参数所代表的含义,比较琐碎,建议阅读文档熟悉。在这里不做过多讲解,文档说的已经很清楚了。
58 | 文档连接http://developer.android.com/reference/android/view/animation/Animation.html
59 |
60 | ###逐帧动画
61 |
62 | 这一部分只涉及非常基础的知识。逐帧动画适用于更高级的动画效果,原因可想而知。我们可以将每帧图片资源放到drawable下然后代码中canvas.drawBitmap(Bitmap, Matrix, Paint)进行动画播放,但这样就将动画资源与代码耦合,如果哪天美工说我要换一下效果就呵呵了。因此我们要做的是将资源等信息放入配置文件然后教会美工怎么改配置文件,这样才有时间去刷知乎而不被打扰^_^。
63 | 大致分为两种方法:
64 |
65 | + 每一帧是一张png图片中
66 | + 所有动画帧都存在一张png图片中
67 |
68 | 当然还有的专门的游戏公司有自己的动画编辑器,这里不加说明。
69 |
70 | ####每一帧是一张png
71 |
72 | 说的就是这个效果:
73 |
74 | 
75 |
76 | 在animation1.xml文件中进行如下配置:
77 | ```
78 |
79 |
82 | >
83 |
84 |
85 |
86 |
87 |
88 | ```
89 | 在JAVA文件中我们进行如下加载:
90 | ```
91 | ImageView animationIV;
92 | AnimationDrawable animationDrawable;
93 |
94 | animationIV.setImageResource(R.drawable.animation1);
95 | animationDrawable = (AnimationDrawable) animationIV.getDrawable();
96 | animationDrawable.start();
97 | ```
98 |
99 | 注意动画的播放是按照xml文件中的顺序顺次播放,如果要考虑到循环播放的时候应该写两个xml一个正向一个反向才能很好地循环播放。
100 |
101 | ####所有动画在一张png中
102 |
103 | 说的就是这个效果:
104 |
105 | 
106 | animation.xml的配置:
107 |
108 | ```
109 | 010001.png
110 |
111 | frame
112 | {{378, 438}, {374, 144}}
113 | offset
114 | {-2, 7}
115 | sourceColorRect
116 | {{61, 51}, {374, 144}}
117 | sourceSize
118 | {500, 260}
119 |
120 | 010002.png
121 |
122 | frame
123 | {{384, 294}, {380, 142}}
124 | offset
125 | {1, 7}
126 | sourceColorRect
127 | rotate
128 |
129 | {{61, 52}, {380, 142}}
130 | sourceSize
131 | {500, 260}
132 |
133 | …
134 | ```
135 |
136 | 其中:
137 |
138 | + frame 指定在原图中截取的框大小;
139 | + offeset 指定原图中心与截图中心偏移的向量;
140 | + rotate若为true顺时针旋转90°;
141 | + sourceColorRect 截取原图透明部分的大小
142 | + sourceSize 原图大小
143 |
144 | JAVA的加载方式与第一种方法相同。
145 |
146 | 在使用过程中一定要注意内存资源的回收和drawable的压缩,一不小心可能爆掉。
147 |
148 | 本文参考博闻:
149 |
150 | + [.plist中各个key的含义](http://blog.csdn.net/laogong5i0/article/details/9293763)
151 | + [Android游戏中的动画制作](http://www.embedu.org/Column/Column401.htm)
152 | + [Android研究院值游戏开发](http://www.xuanyusong.com/archives/242)
153 | + [用Animation-list实现逐帧动画](http://www.open-open.com/lib/view/open1344504946405.html)
154 |
155 | 最后放一张demo:
156 | 
--------------------------------------------------------------------------------
/source/Android/basic/Android-Arch.md:
--------------------------------------------------------------------------------
1 | 总的来说,Android的系统体系结构分为**四层**,自顶向下分别是:
2 |
3 | + 应用程序(Applications)
4 | + 应用程序框架(Application Frameworks)
5 | + 系统运行库与Android运行环境(Libraris & Android Runtime)
6 | + Linux内核(Linux Kernel)
7 |
8 | *安卓系统结构示意图*
9 | 
10 |
11 | 下面对每层进行详细说明
12 |
13 | ###1. 应用程序(Applications)
14 |
15 | Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等。所有的应用程序都是使用JAVA语言编写的。通常开发人员就处在这一层。
16 |
17 | ###2. 应用程序框架(Application Frameworks)
18 |
19 | 提供应用程序开发的各种API进行快速开发,也即隐藏在每个应用后面的是一系列的服务和系统,大部分使用Java编写,所谓官方源码很多也就是看这里,其中包括:
20 |
21 | + 丰富而又可扩展的视图(Views),可以用来构建应用程序, 它包括列表(lists),网格(grids),文本框(text boxes),按钮(buttons), 甚至可嵌入的web浏览器。
22 | + 内容提供器(Content Providers)使得应用程序可以访问另一个应用程序的数据(如联系人数据库), 或者共享它们自己的数据
23 | + 资源管理器(Resource Manager)提供 非代码资源的访问,如本地字符串,图形,和布局文件( layout files )。
24 | + 通知管理器 (Notification Manager) 使得应用程序可以在状态栏中显示自定义的提示信息。
25 | + 活动管理器( Activity Manager) 用来管理应用程序生命周期并提供常用的导航回退功能。
26 |
27 | ###3. 系统运行库与Android运行环境(Libraris & Android Runtime)
28 |
29 | ####1) 系统运行库
30 |
31 | Android 包含一些C/C++库,这些库能被Android系统中不同的组件使用。它们通过 Android 应用程序框架为开发者提供服务。以下是一些核心库:
32 | * **Bionic系统 C 库** - 一个从 BSD 继承来的标准 C 系统函数库( libc ), 它是专门为基于 embedded linux 的设备定制的。
33 | * **媒体库** - 基于 PacketVideo OpenCORE;该库支持多种常用的音频、视频格式回放和录制,同时支持静态图像文件。编码格式包括MPEG4, H.264, MP3, AAC, AMR, JPG, PNG 。
34 | * **Surface Manager** - 对显示子系统的管理,并且为多个应用程序提 供了2D和3D图层的无缝融合。这部分代码
35 | * **Webkit,LibWebCore** - 一个最新的web浏览器引擎用,支持Android浏览器和一个可嵌入的web视图。鼎鼎大名的 Apple Safari背后的引擎就是Webkit
36 | * **SGL** - 底层的2D图形引擎
37 | * **3D libraries** - 基于OpenGL ES 1.0 APIs实现;该库可以使用硬件 3D加速(如果可用)或者使用高度优化的3D软加速。
38 | * **FreeType** -位图(bitmap)和矢量(vector)字体显示。
39 | * **SQLite** - 一个对于所有应用程序可用,功能强劲的轻型关系型数据库引擎。
40 | * 还有部分上面没有显示出来的就是硬件抽象层。其实Android并非讲所有的设备驱动都放在linux内核里面,而是实现在userspace空间,这么做的主要原因是GPL协议,Linux是遵循该 协议来发布的,也就意味着对 linux内核的任何修改,都必须发布其源代码。而现在这么做就可以避开而无需发布其源代码,毕竟它是用来赚钱的。 而 在linux内核中为这些userspace驱动代码开一个后门,就可以让本来userspace驱动不可以直接控制的硬件可以被访问。而只需要公布这个 后门代码即可。一般情况下如果要将Android移植到其他硬件去运行,只需要实现这部分代码即可。包括:显示器驱动,声音,相机,GPS,GSM等等
41 |
42 | ####2) Android运行环境
43 | 该核心库提供了JAVA编程语言核心库的大多数功能。
每一个Android应用程序都在它自己的进程中运 行,都拥有一个独立的Dalvik虚拟 机实例。Dalvik被设计成一个设备可以同时高效地运行多个虚拟系统。 Dalvik虚拟机执行(.dex)的Dalvik可执行文件,该格式文件针对小内存使用做了 优化。同时虚拟机是基于寄存器的,所有的类都经由JAVA编译器编译,然后通过SDK中 的 "dx" 工具转化成.dex格式由虚拟机执行。
44 | ###4. Linux内核(Linux Kernel)
45 |
46 | Android的核心系统服务依赖于Linux 2.6 内核,如安全性,内存管理,进程管理, 网络协议栈和驱动模型。 Linux 内核也同时作为硬件和软件栈之间的抽象层。其外还对其做了部分修改,主要涉及两部分修改:
47 | 1. Binder (IPC):提供有效的进程间通信,虽然linux内核本身已经提供了这些功能,但Android系统很多服务都需要用到该功能,为了某种原因其实现了自己的一套。
48 | 2. 电源管理:主要是为了省电,毕竟是手持设备嘛,低耗电才是我们的追求。
49 |
50 | 注:最后附上原博连接[懒虫一个V:android系统体系结构](http://blog.csdn.net/spy19881201/article/details/5775484),关于谷歌Android源码的目录结构并未一并贴出可在原博查阅
--------------------------------------------------------------------------------
/source/Android/basic/Android-Large-Image.md:
--------------------------------------------------------------------------------
1 | ***这里记录的是另一篇[关于Bitmap加载](http://winlandiano.github.io/%E6%8A%80%E6%9C%AF/2014/10/25/Bitmap-load)的文章,感觉方向类似所以直接迁移过来。如有改进的同学欢迎随时更新***
2 |
3 | 对于一些大屏高分智能机,加载一个Bitmap总是致命的。这两天恰巧在两个不同的项目中遇到了Bitmap的问题。这两个问题都直接导致App在大屏高分屏中崩溃了。现在这里记录下我分别尝试过的几种方法和用过的感觉。下面的使用方法是按照我感觉到有效性从低到高排序的,当然越是有效地实现起来也越繁琐(虽然最繁琐的也没费多大劲)。随时更新:
4 |
5 | 写在前面:Android使用Bitmap是将图片解压缩为原始格式,所以大小的计算方式大致可以描述如下:
6 |
7 | >图片会被解压缩为矩阵(分辨率),假设图片的分辨率是3776 * 2520,每一点又是由ARGB色组成,每个色素占4个Byte,所以加载这张图片要消耗的内存为: 3776 * 2520 * 4byte = 38062080byte
8 | 大约要38MB的内存,大小略有出入,因为图片还有一些Exif信息需要存储,会比仅靠分辨率计算要大一些。(卧槽这段的样式真的是逆天了Medium真的是不考虑天朝人的感受)
9 |
10 | 1.在加载大Bitmap的时候,一些手机会使用硬件加速。这个硬件加载的内存上线有的是24MB,我们可以手动关闭这个限制。当然这需要面临很大的风险,那就是不可控的崩溃。方法是在AndroidMainifest.xml里加入突出显示语句:
11 |
12 | {% highlight xml %}
13 | scaleY && scaleY >= 1) {
40 | scale = scaleX;
41 | }
42 | if (scaleX < scaleY && scaleX >= 1) {
43 | scale = scaleY;
44 | }
45 |
46 | // false表示读取图片像素数组到内存中,opts参数控制依照设定的采样率
47 | opts.inJustDecodeBounds = false;
48 | // 采样率
49 | opts.inSampleSize = scale;
50 | Bitmap bitmap = BitmapFactory.decodeFile(“/sdcard/a.jpg”, opts);
51 | iv_bigimage.setImageBitmap(bitmap);
52 | {% endhighlight %}
53 |
54 | 这里我们可以类似的看一下Bitmap的另一个方法[`public static BitmapcreateScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)`](http://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap%28android.graphics.Bitmap,%20int,%20int,%20boolean%29)
55 |
56 | 文档如下
57 |
58 | >Creates a new bitmap, scaled from an existing bitmap, when possible. If the specified width and height are the same as the current width and height of the source bitmap, the source bitmap is returned and no new bitmap is created.
59 |
60 | 可以看出,最坏情况下也是`return the source bitmap`。对于过与变态的屏幕(如pad)只能另辟蹊径。但是对一般的智能机,这一招就足够用了。例如我最近写的一个模块memory直接降至25%。
61 |
62 | 另外用这个方法也不用再几个drawable中分别放资源文件了。参照[Android加载大分辨率图片到手机内存中的实例方法](http://www.jb51.net/article/43462.htm)
--------------------------------------------------------------------------------
/source/Android/basic/Android-LaunchMode.md:
--------------------------------------------------------------------------------
1 | ###Android Activity的Launch Mode
2 | ####综述
3 | 对安卓而言,Activity有四种启动模式,它们是:
4 |
5 | * standard 标准模式,每次都新建一个实例对象
6 | * singleTop 如果在任务栈顶发现了相同的实例则重用,否则新建并压入栈顶
7 | * singleTask 如果在任务栈中发现了相同的实例,将其上面的任务终止并移除,重用该实例。否则新建实例并入栈
8 | * singleInstance 允许不同应用,进程线程等共用一个实例,无论从何应用调用该实例都重用
9 |
10 | 想要感受一下的话写一个小demo,然后自己启动自己再点返回键就看出来了。下面详细说说每一种启动模式
11 |
12 | ####standard
13 | 一张图就很好理解
14 |
15 | 
16 |
17 | 什么配置都不写的话就是这种启动模式。但是每次都新建一个实例的话真是过于浪费,为了优化应该尽量考虑余下三种方式。
18 |
19 | ####singleTop
20 | 每次扫描栈顶,如果在任务栈顶发现了相同的实例则重用,否则新建并压入栈顶。
21 |
22 | 
23 |
24 | 配制方法实在Mainifest.xml中进行:
25 |
26 | ```
27 |
31 |
32 | ```
33 |
34 | ####singleTask
35 | 与singleTop的区别是singleTask会扫描整个任务栈并制定策略。上效果图:
36 |
37 | 
38 |
39 | 使用时需要小心因为会将之前入栈的实例之上的实例全部移除,需要格外小心逻辑。
40 |
41 | 配制方法:
42 | ```
43 |
47 |
48 | ```
49 |
50 | ####singleInstance
51 | 这个的理解可以这么看:在微信里点击“用浏览器打开”一个朋友圈,然后切到QQ再用浏览器开一个网页,再跑到哪里再开一个页面。每次我们都在Activity中试图启动另一个浏览器Activity,但是在浏览器端看来,都是调用了同一个自己。因为使用了singleInstance模式,不同应用调用的Activity实际上是共享的。
52 |
53 | 上说明图:
54 |
55 | 
56 |
57 | 配制方法:
58 | ```
59 |
63 |
64 | ```
65 |
66 | ####参考博客
67 | 传送门:
68 |
69 | + [http://www.cnblogs.com/fanchangfa/archive/2012/08/25/2657012.html](Android中Activity启动模式详解)
70 | + [Android入门:Activity四种启动模式](http://www.cnblogs.com/meizixiong/archive/2013/07/03/3170591.html)
--------------------------------------------------------------------------------
/source/Android/basic/Android-handler-thread-looper.md:
--------------------------------------------------------------------------------
1 | # Android中的Thread, Looper和Handler机制(附带HandlerThread与AsyncTask)
2 |
3 | ## Thread,Looper和Handler的关系
4 |
5 | 与Windows系统一样,Android也是消息驱动型的系统。引用一下消息驱动机制的四要素:
6 |
7 | + 接收消息的“消息队列”
8 | + 阻塞式地从消息队列中接收消息并进行处理的“线程”
9 | + 可发送的“消息的格式”
10 | + “消息发送函数”
11 |
12 | 与之对应,Android中的实现对应了
13 |
14 | + 接收消息的“消息队列” ——【MessageQueue】
15 | + 阻塞式地从消息队列中接收消息并进行处理的“线程” ——【Thread+Looper】
16 | + 可发送的“消息的格式” ——【Message】
17 | + “消息发送函数”——【Handler的post和sendMessage】
18 |
19 | 一个`Looper`类似一个消息泵。它本身是一个死循环,不断地从`MessageQueue`中提取`Message`或者Runnable。而`Handler`可以看做是一个`Looper`的暴露接口,向外部暴露一些事件,并暴露`sendMessage()`和`post()`函数。
20 |
21 | 在安卓中,除了`UI线程`/`主线程`以外,普通的线程(先不提`HandlerThread`)是不自带`Looper`的。想要通过UI线程与子线程通信需要在子线程内自己实现一个`Looper`。开启Looper分***三步走***:
22 |
23 | 1. 判定是否已有`Looper`并`Looper.prepare()`
24 | 2. 做一些准备工作(如暴露handler等)
25 | 3. 调用`Looper.loop()`,线程进入阻塞态
26 |
27 | 由于每一个线程内最多只可以有一个`Looper`,所以一定要在`Looper.prepare()`之前做好判定,否则会抛出`java.lang.RuntimeException: Only one Looper may be created per thread`。为了获取Looper的信息可以使用两个方法:
28 |
29 | + Looper.myLooper()
30 | + Looper.getMainLooper()
31 |
32 | `Looper.myLooper()`获取当前线程绑定的Looper,如果没有返回`null`。`Looper.getMainLooper()`返回主线程的`Looper`,这样就可以方便的与主线程通信。注意:**在`Thread`的构造函数中调用`Looper.myLooper`只会得到主线程的`Looper`**,因为此时新线程还未构造好
33 |
34 | 下面给一段代码,通过Thread,Looper和Handler实现线程通信:
35 |
36 | ### MainActivity.java
37 | ```
38 | public class MainActivity extends Activity {
39 | public static final String TAG = "Main Acticity";
40 | Button btn = null;
41 | Button btn2 = null;
42 | Handler handler = null;
43 | MyHandlerThread mHandlerThread = null;
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 | setContentView(R.layout.activity_main);
49 | btn = (Button)findViewById(R.id.button);
50 | btn2 = (Button)findViewById(R.id.button2);
51 | Log.d("MainActivity.myLooper()", Looper.myLooper().toString());
52 | Log.d("MainActivity.MainLooper", Looper.getMainLooper().toString());
53 |
54 |
55 | btn.setOnClickListener(new View.OnClickListener() {
56 | @Override
57 | public void onClick(View view) {
58 | mHandlerThread = new MyHandlerThread("onStartHandlerThread");
59 | Log.d(TAG, "创建myHandlerThread对象");
60 | mHandlerThread.start();
61 | Log.d(TAG, "start一个Thread");
62 | }
63 | });
64 |
65 | btn2.setOnClickListener(new View.OnClickListener() {
66 | @Override
67 | public void onClick(View view) {
68 | if(mHandlerThread.mHandler != null){
69 | Message msg = new Message();
70 | msg.what = 1;
71 | mHandlerThread.mHandler.sendMessage(msg);
72 | }
73 |
74 | }
75 | });
76 | }
77 | }
78 | ```
79 |
80 | ### MyHandlerThread.java
81 |
82 | ```
83 | public class MyHandlerThread extends Thread {
84 | public static final String TAG = "MyHT";
85 |
86 | public Handler mHandler = null;
87 |
88 | @Override
89 | public void run() {
90 | Log.d(TAG, "进入Thread的run");
91 | Looper.prepare();
92 | Looper.prepare();
93 | mHandler = new Handler(Looper.myLooper()){
94 | @Override
95 | public void handleMessage(Message msg){
96 | Log.d(TAG, "获得了message");
97 | super.handleMessage(msg);
98 | }
99 | };
100 | Looper.loop();
101 | }
102 | }
103 | ```
104 |
105 | ***
106 |
107 | ## HandlerThread 和 AsyncTask
108 |
109 | ### HandlerThread
110 |
111 | Android为了方便对`Thread`和`Handler`进行封装,也就是`HandlerThread`。文档中对`HandlerThread`的定义是:
112 |
113 | >Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
114 |
115 | `HandlerThread`继承自`Thread`,说白了就是`Thread`加上一个一个`Looper`。分析下面的代码:
116 |
117 | ```
118 | public class MyHandlerThread extends HandlerThread{
119 | @Override
120 | public void run(){
121 | if(Looper.myLooper == null){
122 | Looper.prepare();
123 | }
124 | super.run();
125 | }
126 | }
127 | ```
128 |
129 | 会抛出`java.lang.RuntimeException: Only one Looper may be created per thread`错误。如果我们把super.run()注释掉就不会有这样的错误。显然在`super.run()`中进行了Looper的绑定。
130 |
131 | ### AsyncTask
132 |
133 | AsyncTask是谷歌对Thread和Handler的进一步封装,完全隐藏起了这两个概念,而用`doInBackground(Params... params)`取而代之。但需要注意的是AsyncTask的效率不是很高而且资源代价也比较重,只有当进行一些小型操作时为了方便起见使用。这一点在官方文档写的很清楚:
134 |
135 | >AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.
136 |
137 | 由于使用比较简单应该不需要细说。如有需要会在未来更新。
--------------------------------------------------------------------------------
/source/Android/basic/ListView-Optimize.md:
--------------------------------------------------------------------------------
1 | ##ListView原理与优化
2 | ###原理:ListView与Adapter
3 | ListView的实现离不开Adapter。可以这么理解:ListView中给出了数据来的时候,View如何实现的具体方式,相当于MVC中的V;而Adapter提供了相当于MVC中的C,指挥了ListView的数据加载等行为。
4 |
5 | 提一个问题:假设ListView中有10W个条项,那内存中会缓存10W个吗?答案当然是否定的。那么是如何实现的呢?下面这张图可以清晰地解释其中的原理:
6 | 
7 |
8 | 可以看到当一个View移出可视区域的时候,设为View1,它会被标记Recycle,然后可能:
9 |
10 | + 新进入的View2与View1类型相同,那么在getView方法传入的convertView就不是null而就是View1。换句话说,View1被重用了
11 | + 新进入的View2与View1类型不同,那么getView传入的convertView就是null,这是需要new一个View。当内存紧张时,View1就会被GC
12 |
13 | ###ListView的优化(以异步加载Bitmap优化为例)
14 | 首先概括的说ListView优化分为三级缓存:
15 |
16 | + 内存缓存
17 | + 文件缓存
18 | + 网络读取
19 |
20 | 简要概括就是在getView中,如果加载过一个图片,放入Map类型的一个MemoryCache中(示例代码使用的是Collections.synchronizedMap(new LinkedHashMap(10, 1.5f, true))来维护一个试用LRU的堆)。如果这里获取不到,根据View被Recycle之前放入的TAG中记录的uri从文件系统中读取文件缓存。如果本地都找不到,再去网络中异步加载。
21 |
22 | 这里有几个注意的优化点:
23 |
24 | 1. 从文件系统中加载图片也没有内存中加载那么快,甚至可能内存中加载也不够快。因此在ListView中应设立busy标志位,当ListView滚动时busy设为true,停止各个view的图片加载。否则可能会让UI不够流畅用户体验度降低。
25 | 2. 文件加载图片放在子线程实现,否则快速滑动屏幕会卡
26 | 3. 开启网络访问等耗时操作需要开启新线程,应使用线程池避免资源浪费,最起码也要用AsyncTask。
27 | 4. Bitmap从网络下载下来最好先放到文件系统中缓存。这样一是方便下一次加载根据本地uri直接找到,二是如果Bitmap过大,从本地缓存可以方便的使用Option.inSampleSize配合Bitmap.decodeFile(ui, options)或Bitmap.createScaledBitmap来进行内存压缩
28 |
29 |
30 | **原博文有非常好的代码示例: [ Listview异步加载图片之优化篇(有图有码有解释)](http://blog.chinaunix.net/uid-29134536-id-4094813.html)非常值得看看。
31 |
32 | 此外Github上也有仓库:https://github.com/geniusgithub/SyncLoaderBitmapDemo
--------------------------------------------------------------------------------
/source/Android/basic/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/Android/basic/README.md
--------------------------------------------------------------------------------
/source/README.md:
--------------------------------------------------------------------------------
1 | 笔试面试知识整理
2 | ================
3 |
4 | 本文档使用 [Gitbook](https://github.com/GitbookIO/gitbook) 制作,[Github 仓库地址](https://github.com/HIT-Alibaba/interview)。
5 |
6 | 所有引用内容版权归原作者所有。
7 |
8 | 使用 [知识共享“署名-非商业性使用-相同方式共享 3.0 中国大陆”许可协议](https://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 授权。
9 |
10 | ### 贡献者:
11 |
12 | * [skyline75489](https://github.com/skyline75489)
13 | * [winlandiano](https://github.com/winlandiano)
14 | * [dodola](https://github.com/dodola)
15 | * [AveryLiu](https://github.com/AveryLiu)
16 | * [JackAlan](https://github.com/AlanMelody)
17 |
18 |
--------------------------------------------------------------------------------
/source/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [基础知识](basic/README.md)
4 | * [计算机网络](basic/network/README.md)
5 | * [HTTP 协议](basic/network/HTTP.md)
6 | * [HTTP over SSL/TLS](basic/network/HTTPS.md)
7 | * [TCP 协议](basic/network/TCP.md)
8 | * [UDP 协议](basic/network/UDP.md)
9 | * [IP 协议](basic/network/IP.md)
10 | * [Socket 编程](basic/network/Socket-Programming-Basic.md)
11 | * [数据结构与算法](basic/algo/README.md)
12 | * [链表](basic/algo/Linked-List.md)
13 | * [树](basic/algo/Tree.md)
14 | * [哈希表](basic/algo/Hash-Table.md)
15 | * [排序](basic/algo/Sorting.md)
16 | * [搜索]()
17 | * [字符串]()
18 | * [向量/矩阵]()
19 | * [随机](basic/algo/Random.md)
20 | * [贪心](basic/algo/Greedy.md)
21 | * [动态规划](basic/algo/DP.md)
22 | * [体系结构与操作系统](basic/arch/README.md)
23 | * [体系结构基础](basic/arch/Arch.md)
24 | * [操作系统基础](basic/arch/OS.md)
25 | * [并发技术](basic/arch/Concurrency.md)
26 | * [内存管理](basic/arch/Memory-Management.md)
27 | * [磁盘与文件](basic/arch/Disk-And-File.md)
28 | * [编译原理](basic/compiler/README.md)
29 | * [编译器架构](basic/compiler/Compiler-Arch.md)
30 | * [设计模式](basic/design/README.md)
31 | * [面向对象基础](basic/design/OO-Basic.md)
32 | * [四人帮设计模式](basic/design/GOP.md)
33 | * [MVC 与 MVVM](basic/design/MVC.md)
34 | * [版本控制](basic/scm/README.md)
35 | * [Git](basic/scm/Git.md)
36 | * [SVN](basic/scm/SVN.md)
37 | * [iOS 开发](iOS/README.md)
38 | * [Objective-C 语言基础](iOS/ObjC-Basic/README.md)
39 | * [类与对象](iOS/ObjC-Basic/Class.md)
40 | * [Block 编程](iOS/ObjC-Basic/Block.md)
41 | * [Objective-C Runtime](iOS/ObjC-Basic/Runtime.md)
42 | * [Objective-C 内存管理](iOS/ObjC-Basic/MM.md)
43 | * [Runloop](iOS/ObjC-Basic/Runloop.md)
44 | * [Cocoa Touch](iOS/Cocoa-Touch/README.md)
45 | * [事件处理](iOS/Cocoa-Touch/Event-Handling.md)
46 | * [UIApplication](iOS/Cocoa-Touch/UIApplication.md)
47 | * [UIView](iOS/Cocoa-Touch/UIView-Basic.md)
48 | * [UIViewController](iOS/Cocoa-Touch/UIViewController.md)
49 | * [动画](iOS/Cocoa-Touch/Animation.md)
50 | * [网络编程](iOS/Cocoa-Touch/Network.md)
51 | * [并发编程](iOS/Cocoa-Touch/Multithreading.md)
52 | * [文件系统](iOS/Cocoa-Touch/File-System.md)
53 | * [设计模式](iOS/Cocoa-Touch/Design.md)
54 | * [性能](iOS/Cocoa-Touch/Performance.md)
55 | * [Swift](iOS/Swift/README.md)
56 | * [类与对象](iOS/Swift/Class.md)
57 | * [结构体与枚举](iOS/Swift/Struct-And-Enum.md)
58 | * [函数与闭包](iOS/Swift/Function-And-Closure.md)
59 | * [面试问题](iOS/Questions.md)
60 | * [更多资料](iOS/More.md)
61 |
62 | * [Android 开发](Android/README.md)
63 | * [Java 基础](Android/Java/README.md)
64 | * [面试问题](Android/Java/Questions.md)
65 | * [Android 基础](Android/basic/README.md)
66 | * [Android 系统架构](Android/basic/Android-Arch.md)
67 | * [Activity/Service 生命周期](Android/basic/Activity-Service-Lifecircle.md)
68 | * [Android 中的动画(补帧与逐帧)](Android/basic/Android-Animation.md)
69 | * [Activity 的 4 种启动模式](Android/basic/Android-LaunchMode.md)
70 | * [ListView原理与优化](Android/basic/ListView-Optimize.md)
71 | * [Android 中的 Thread, Looper 和 Handler 机制](Android/basic/Android-handler-thread-looper.md)
72 | * [面试问题](Android/Questions.md)
73 |
74 | * [后端开发](Server/README.md)
75 | * [Spring Web](Server/Web/Spring.md)
76 | * [数据库系统](Server/db/README.md)
77 | * [事务处理](Server/db/Transaction.md)
78 | * [索引](Server/db/DB-Index.md)
--------------------------------------------------------------------------------
/source/Server/README.md:
--------------------------------------------------------------------------------
1 | 后端内容
--------------------------------------------------------------------------------
/source/Server/Web/Spring.md:
--------------------------------------------------------------------------------
1 | Spring Web
2 | ==========
--------------------------------------------------------------------------------
/source/Server/db/DB-Index.md:
--------------------------------------------------------------------------------
1 |
2 | 数据库创建索引能够大大提高系统的性能。
3 | 第一,通过创建唯一性的索引,可以保证数据库表中每一行数据的唯一性。
4 | 第二,可以大大加快数据的检索速度,这也使创建索引的最主要的原因。
5 | 第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
6 | 第四,在使用分组和排序子句进行数据检索时,同样可以显著的减少查询中查询中分组和排序的时间。
7 | 第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
8 |
9 | 增加索引也有许多不利的方面。
10 | 第一,创建索引和维护索引需要消耗时间,这种时间随着数量的增加而增加。
11 | 第二,索引需要占物理空间,除了数据表占据数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要额空间就会更大。
12 | 第三,当对表中的数据进行增加,删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
13 |
14 | 应该对如下的列建立索引
15 |
16 | 1. 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构。
17 | 2. 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度。
18 | 3. 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。
19 | 4. 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
20 | 5. 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。
21 |
22 | 有些列不应该创建索引
23 |
24 | 1. 在查询中很少使用或者作为参考的列不应该创建索引。
25 | 2. 对于那些只有很少数据值的列也不应该增加索引(比如性别,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度)。
26 | 3. 对于那些定义为text,image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
27 | 4. 当修改性能远远大于检索性能时,不应该创建索引,因为修改性能和检索性能是矛盾的。
28 |
29 | 创建索引的方法:直接创建和间接创建(在表中定义主键约束或者唯一性约束时,同时也创建了索引)。
30 | 索引的特征:
31 | 唯一性索引和复合索引。唯一性索引保证在索引列中的全部数据是唯一的,不会包含冗余数据。复合索引就是一个索引创建在两个列或者多个列上。可以减少一在一个表中所创建的索引数量。
32 |
--------------------------------------------------------------------------------
/source/Server/db/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/Server/db/README.md
--------------------------------------------------------------------------------
/source/Server/db/Transaction.md:
--------------------------------------------------------------------------------
1 | ## 事务的概念
2 |
3 | 事务的概念来自于两个独立的需求:并发数据库访问,系统错误恢复。
4 |
5 | 一个事务是可以被看作一个单元的一系列SQL语句的集合。
6 |
7 | ## 事务的特性(ACID)
8 |
9 | * A, atomacity 原子性
10 | 事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。
11 |
12 | * C, consistency 一致性
13 |
14 | 事务将数据库从一种一致状态转变为下一种一致状态。也就是说,事务在完成时,必须使所有的数据都保持一致状态(各种 constraint 不被破坏)。
15 |
16 | * I, isolation 隔离性
17 | 由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。换句话说,一个事务的影响在该事务提交前对其他事务都不可见。
18 |
19 | * D, durability 持久性
20 |
21 | 事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
22 |
23 | ## 事务的隔离级别
24 |
25 | 如果不对数据库进行并发控制,可能会产生异常情况:
26 |
27 | 1. 脏读(Dirty Read)
28 |
29 | 当一个事务读取另一个事务尚未提交的修改时,产生脏读。
30 |
31 | 同一事务内不是脏读。
32 | 一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚,也就是说读取出的数据其实是错误的。
33 |
34 | 2. 非重复读(Nonrepeatable Read)
35 | 一个事务对同一行数据重复读取两次,但是却得到了不同的结果。同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。
36 |
37 | 3. 幻像读(Phantom Reads)
38 | 事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
39 |
40 | 当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。
41 |
42 | 4. 丢失修改(Lost Update)
43 |
44 | 第一类:当两个事务更新相同的数据源,如果第一个事务被提交,第二个却被撤销,那么连同第一个事务做的更新也被撤销。
45 |
46 | 第二类:有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
47 |
48 |
49 | 为了兼顾并发效率和异常控制,在标准SQL规范中,定义了4个事务隔离级别,( Oracle 和 SQL Server 对标准隔离级别有不同的实现 )
50 |
51 | 1. 未提交读(Read Uncommitted)
52 |
53 | 直译就是"读未提交",意思就是即使一个更新语句没有提交,但是别的事务可以读到这个改变。
54 |
55 | Read Uncommitted允许脏读。
56 |
57 | 2. 已提交读(Read Committed)
58 |
59 | 直译就是"读提交",意思就是语句提交以后,即执行了 Commit 以后别的事务就能读到这个改变,只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别。
60 |
61 | Read Commited 不允许脏读,但会出现非重复读。
62 |
63 |
64 | 3. 可重复读(Repeatable Read):
65 |
66 | 直译就是"可以重复读",这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的。
67 |
68 | Repeatable Read 不允许脏读,不允许非重复读,但是会出现幻象读。
69 |
70 |
71 | 4. 串行读(Serializable)
72 |
73 | 直译就是"序列化",意思是说这个事务执行的时候不允许别的事务并发执行。完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
74 |
75 | Serializable 不允许不一致现象的出现。
76 |
77 | ## 事务隔离的实现——锁
78 |
79 | 1. 共享锁(S锁)
80 |
81 | 用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。
82 |
83 | 2. 更新锁(U锁)
84 |
85 | 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
86 |
87 | 3. 独占锁(X锁,也叫排他锁)
88 |
89 | 一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享缩。写是独占锁,可以有效的防止“脏读”。
90 |
91 | Read Uncommited 如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
92 |
93 | Read Committed 读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。可以通过“瞬间共享读锁”和“排他写锁”实现。
94 |
95 | Repeatable Read 读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。可以通过“共享读锁”和“排他写锁”实现。
96 |
97 | Serializable 读加共享锁,写加排他锁,读写互斥。
98 |
99 |
100 | ### 参考资料
101 |
102 | * [百度百科:数据库事务](http://baike.baidu.com/view/1298364.htm)
103 | * [数据库事务隔离级别与锁](http://www.cnblogs.com/tqsummer/archive/2010/07/11/1775209.html)
104 | * [SQL SERVER的锁机制(四)——概述(各种事务隔离级别发生的影响)](http://www.cnblogs.com/chillsrc/archive/2013/04/27/3047547.html)
105 | * [数据库锁](http://www.cnblogs.com/zhouqianhua/archive/2011/04/15/2017049.html)
106 | * [关于数据库事务、隔离级别、锁的理解与整理](http://news.e800.com.cn/articles/2011/0803/492650.shtml)
107 | * [Innodb中的事务隔离级别和锁的关系](http://tech.meituan.com/innodb-lock.html)
--------------------------------------------------------------------------------
/source/basic/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/basic/README.md
--------------------------------------------------------------------------------
/source/basic/algo/DP.md:
--------------------------------------------------------------------------------
1 | ## 动态规划
2 |
3 | 建议观看 MIT [算法导论-动态规划](http://open.163.com/movie/2010/12/L/4/M6UTT5U0I_M6V2U1HL4.html)中的课程。
4 |
5 | 适用于动态规划的问题,需要满足**最优子结构**和**无后效性**,动态规划的求解过程,在于找到**状态转移方程**,进行**自底向上**的求解。
6 |
7 | ## 例题
8 |
9 | #### 爬楼梯问题 [LeetCode 70](https://leetcode.com/problems/climbing-stairs/)
10 |
11 | 经典的动态规划问题之一,容易找到其状态转移方程为 `dp[i] = dp[i-1] + dp[i-2]`,从基础的 1 和 2 个台阶两个状态开始,自底向上求解:
12 |
13 | ```cpp
14 | int climbStairs(int n) {
15 | if (n == 1) {
16 | return 1;
17 | }
18 |
19 | int* dp = new int[n+1]();
20 | dp[1] = 1;
21 | dp[2] = 2;
22 |
23 | for (int i = 3; i <= n; i++) {
24 | dp[i] = dp[i-1] + dp[i-2];
25 | }
26 |
27 | return dp[n];
28 | }
29 | ```
30 |
31 | 从上面的代码中看到,`dp[i]` 只依赖 `dp[i-1]` 和 `dp[i-2]`,因此可以将代码简化:
32 |
33 | ```cpp
34 | int climbStairs(int n) {
35 | int f0 = 1, f1 = 1, i, f2;
36 | for (i=2; i<=n; i++) {
37 | f2 = f0 + f1;
38 | f0 = f1;
39 | f1 = f2;
40 | }
41 | return f1;
42 | }
43 | ```
44 |
45 | 容易看出其实结果就是 fibonacci 数列的第 n 项。
46 |
47 | #### 连续子数组的最大和 [LeetCode 53](https://leetcode.com/problems/maximum-subarray/)
48 |
49 | 用 `dp[n`] 表示元素 n 作为末尾的连续序列的最大和,容易想到状态转移方程为`dp[n] = max(dp[n-1] + num[n], num[n])`,从第 1 个元素开始,自顶向上求解:
50 |
51 | ```cpp
52 | int maxSubArray(vector& nums) {
53 | int* dp = new int[nums.size()]();
54 |
55 | dp[0] = nums[0];
56 | int result = dp[0];
57 |
58 | for (int i = 1; i < nums.size(); i++) {
59 | dp[i] = max(dp[i-1] + nums[i], nums[i]);
60 | result = max(result, dp[i]);
61 | }
62 |
63 | return result;
64 | }
65 | ```
66 |
67 | 类似前一个问题,这个问题当中,求解 `dp[i]` 只依赖 `dp[i-1]`,因此可以使用变量来存储,简化代码:
68 |
69 | ```cpp
70 | int maxSubArray(int A[], int n) {
71 | int result = INT_MIN;
72 | int f = 0;
73 | for (int i=0; i < n; i++) {
74 | f = max(f + A[i], A[i]);
75 | result = max(result, f);
76 | }
77 | return result;
78 | }
79 | ```
80 |
81 | #### House Robber [LeetCode 198](https://leetcode.com/problems/house-robber/)
82 |
83 | 对于一个房子,有抢和不抢两种选择,容易得到状态转移方程 `dp[i+1] = max(dp[i-1] + nums[i], dp[i])`,示例代码如下:
84 |
85 | ```cpp
86 | int rob(vector& nums) {
87 | int n = nums.size();
88 | if (n == 0) {
89 | return 0;
90 | }
91 |
92 | vector dp = vector(n + 1);
93 |
94 | dp[0] = 0;
95 | dp[1] = nums[0];
96 |
97 | for (int i = 1; i < nums.size(); i++) {
98 | int v = nums[i];
99 | dp[i+1] = max(dp[i-1] + v, dp[i]);
100 | }
101 |
102 | return dp[n];
103 | }
104 | ```
105 |
106 | 同样的,可以使用两个变量简化代码:
107 |
108 | ```cpp
109 | int rob(vector& nums) {
110 | int n = nums.size();
111 | if (n == 0) {
112 | return 0;
113 | }
114 |
115 | int prev1 = 0;
116 | int prev2 = 0;
117 |
118 | for (int i = 0; i < nums.size(); i++) {
119 | int v = nums[i];
120 | int temp = prev1;
121 | prev1 = max(prev2 + v, prev1);
122 | prev2 = temp;
123 | }
124 |
125 | return prev1;
126 | }
127 | ```
128 |
129 | #### 最长回文子串 [LeetCode 5](https://leetcode.com/problems/longest-palindromic-substring/)
130 |
131 | 用 `dp[i][j]` 表示子串 i 到 j 是否是回文,使用动态规划求解:
132 |
133 | ```cpp
134 | string longestPalindrome(string s) {
135 | int m = s.size();
136 | if (m == 0) {
137 | return "";
138 | }
139 | vector> dp(m, vector(m, 0));
140 | int start = 0;
141 | int length = 1;
142 |
143 | for (int i = 0; i < m; i++) {
144 | // 单个字符属于回文,例如 abcd
145 | dp[i][i] = 1;
146 |
147 | // 连续两个字符相同属于回文,例如 abb
148 | if (i < m - 1) {
149 | if (s[i] == s[i + 1]) {
150 | dp[i][i + 1] = 1;
151 | start = i;
152 | length = 2;
153 | }
154 | }
155 | }
156 |
157 | for (int len = 2; len <= m; len++) {
158 | for (int i = 0; i < m - len; i++) {
159 | int j = i + len;
160 | // 扩展长度
161 | if (dp[i + 1][j - 1] == 1 && s[i] == s[j]) {
162 | dp[i][j] = 1;
163 |
164 | if (j - i + 1 > length) {
165 | start = i;
166 | length = j - i + 1;
167 | }
168 | }
169 | }
170 | }
171 |
172 | return s.substr(start, length);
173 | }
174 | ```
175 |
176 | #### 最小编辑距离 [LeetCode 72](https://leetcode.com/problems/edit-distance/)
177 |
178 | 用 `dp[i][j]` 表示从 `word[0..i)` 转换到 `word[0..j)` 的最小操作,使用动态规划求解:
179 |
180 | ```cpp
181 | int minDistance(string word1, string word2) {
182 | int m = word1.size();
183 | int n = word2.size();
184 | vector> dp(m + 1, vector(n + 1, 0));
185 |
186 | // 全部删除,操作数量为 i
187 | for (int i = 0; i <= m; i++) {
188 | dp[i][0] = i;
189 | }
190 |
191 | for (int j = 0; j <= n; j++) {
192 | dp[0][j] = j;
193 | }
194 |
195 | for (int i = 1; i <= m; i++) {
196 | for (int j = 1; j <= n; j++) {
197 | // 末尾字符相同,不需要编辑
198 | if (word1[i - 1] == word2[j - 1]) {
199 | dp[i][j] = dp[i - 1][j - 1];
200 | } else {
201 | // 末尾字符不同,三种编辑情况,取最小值
202 | dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1;
203 | }
204 | }
205 | }
206 |
207 | return dp[m][n];
208 | }
209 | ```
210 |
--------------------------------------------------------------------------------
/source/basic/algo/Greedy.md:
--------------------------------------------------------------------------------
1 | ## 贪心算法
2 |
3 | 建议观看MIT[算法导论-贪心算法](http://open.163.com/movie/2010/12/1/S/M6UTT5U0I_M6V2U3R1S.html)中的课程。
4 |
--------------------------------------------------------------------------------
/source/basic/algo/Hash-Table.md:
--------------------------------------------------------------------------------
1 | 哈希表(Hash Table,也叫散列表),是根据关键码值 (Key-Value) 而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。哈希表的实现主要需要解决两个问题,哈希函数和冲突解决。
2 |
3 | ## 哈希函数
4 |
5 | 哈希函数也叫散列函数,它对不同的输出值得到一个固定长度的消息摘要。理想的哈希函数对于不同的输入应该产生不同的结构,同时散列结果应当具有同一性(输出值尽量均匀)和雪崩效应(微小的输入值变化使得输出值发生巨大的变化)。
6 |
7 | ## 冲突解决
8 |
9 | 现实中的哈希函数不是完美的,当两个不同的输入值对应一个输出值时,就会产生“碰撞”,这个时候便需要解决冲突。
10 |
11 | 常见的冲突解决方法有开放定址法,链地址法,建立公共溢出区等。实际的哈希表实现中,使用最多的是链地址法
12 |
13 | #### 链地址法
14 |
15 | 链地址法的基本思想是,为每个 Hash 值建立一个单链表,当发生冲突时,将记录插入到链表中。
16 |
17 | 例 2 设有 8 个元素 { a,b,c,d,e,f,g,h } ,采用某种哈希函数得到的地址分别为: {0 , 2 , 4 , 1 , 0 , 8 , 7 , 2} ,当哈希表长度为 10 时,采用链地址法解决冲突的哈希表如下图所示:
18 |
19 | 
20 |
21 | ### 参考资料
22 |
23 | * [哈希表的 C 实现](http://www.cnblogs.com/xiekeli/archive/2012/01/13/2321207.html)
24 | * [解决哈希表的冲突-开放地址法和链地址法](http://blog.csdn.net/w_fenghui/article/details/2010387)
--------------------------------------------------------------------------------
/source/basic/algo/Linked-List.md:
--------------------------------------------------------------------------------
1 | ## 例题
2 |
3 | #### 单链表翻转 [LeetCode 206](https://leetcode.com/problems/reverse-linked-list/)
4 |
5 | 这个问题可以使用递归和非递归两种方法解决。
6 |
7 | 递归算法实现:
8 |
9 | ```cpp
10 | ListNode* reverseList(ListNode* head)
11 | {
12 | if(NULL == head || NULL == head->next)
13 | return head;
14 | ListNode * p = reverseList(head->next);
15 | head->next->next = head;
16 | head->next = NULL;
17 |
18 | return p;
19 | }
20 | ```
21 |
22 | 非递归算法实现:
23 |
24 | ```cpp
25 | ListNode* reverseList(ListNode* head) {
26 | ListNode *curr = head;
27 | if (curr == NULL) {
28 | return NULL;
29 | }
30 |
31 | ListNode *prev = NULL, *temp = NULL;
32 | while (curr != NULL) {
33 | temp = curr->next;
34 | curr->next = prev;
35 | prev = curr;
36 | curr = temp;
37 | }
38 |
39 | return prev;
40 | }
41 | ```
42 |
43 | #### 单链表判断是否有环 [LeetCode 141](https://leetcode.com/problems/linked-list-cycle/)
44 |
45 | 最容易想到的思路是存一个所有 Node 地址的 Hash 表,从头开始遍历,将 Node 存到 Hash 表中,如果出现了重复,则说明链表有环。
46 |
47 | 一个经典的方法是双指针(也叫快慢指针),使用两个指针遍历链表,一个指针一次走一步,另一个一次走两步,如果链表有环,两个指针必然相遇。
48 |
49 | 双指针算法实现:
50 |
51 | ```cpp
52 | bool hasCycle(ListNode *head) {
53 | if (head == nullptr) {
54 | return false;
55 | }
56 | ListNode *fast,*slow;
57 | slow = head;
58 | fast = head->next;
59 | while (fast && fast->next) {
60 | slow = slow->next;
61 | fast = fast->next->next;
62 | if (slow == fast) {
63 | return true;
64 | }
65 | }
66 | return false;
67 | }
68 | ```
69 |
70 | #### 单链表找环入口 [LeetCode 141](https://leetcode.com/problems/linked-list-cycle-ii/)
71 |
72 | 作为上一题的扩展,为了找到环所在的位置,在快慢指针相遇的时候,此时慢指针没有遍历完链表,再设置一个指针从链表头部开始遍历,这两个指针相遇的点,就是链表环的入口。
73 |
74 | 算法实现:
75 |
76 | ```cpp
77 | ListNode *detectCycle(ListNode *head) {
78 | if (head == nullptr) {
79 | return nullptr;
80 | }
81 | ListNode *fast,*slow;
82 | slow = head;
83 | fast = head;
84 | while (fast && fast->next) {
85 | slow = slow->next;
86 | fast = fast->next->next;
87 | if (slow == fast) {
88 | ListNode *slow2 = head;
89 | while (slow2 != slow) {
90 | slow = slow->next;
91 | slow2 = slow2->next;
92 | }
93 | return slow2;
94 | }
95 | }
96 | return nullptr;
97 | }
98 | ```
99 |
100 | #### 单链表找交点 [LeetCode 160](https://leetcode.com/problems/intersection-of-two-linked-lists/)
101 |
102 | 和找环的方法类似,同样可以使用 Hash 表存储所有节点,发现重复的节点即交点。
103 |
104 | 一个容易想到的方法是,先得到两个链表的长度,然后得到长度的差值 distance,两个指针分别从两个链表头部遍历,其中较长链表指针先走 distance 步,然后同时向后走,当两个指针相遇的时候,即链表的交点:
105 |
106 | ```cpp
107 | int getListLength(ListNode *head) {
108 | if (head == nullptr) {
109 | return 0;
110 | }
111 | int length = 0;
112 | ListNode *p = head;
113 | while (p!=nullptr) {
114 | p = p->next;
115 | length ++;
116 | }
117 | return length;
118 | }
119 |
120 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
121 | int lengthA = getListLength(headA);
122 | int lengthB = getListLength(headB);
123 |
124 | if (lengthA > lengthB) {
125 | std::swap(headA, headB);
126 | };
127 | int distance = abs(lengthB - lengthA);
128 | ListNode *p1 = headA;
129 | ListNode *p2 = headB;
130 | while(distance--) {
131 | p2 = p2->next;
132 | }
133 | while (p1 != nullptr && p2 != nullptr) {
134 | if (p1 == p2)
135 | return p1;
136 | p1 = p1->next;
137 | p2 = p2->next;
138 | }
139 | return NULL;
140 | }
141 | ```
142 |
143 | 另一个较快的方法时,两个指针 pa,pb 分别从 headA,headB开始遍历,当 pa 遍历到尾部的时候,指向 headB,当 pb 遍历到尾部的时候,转向 headA。当两个指针再次相遇的时候,如果两个链表有交点,则指向交点,如果没有则指向 NULL:
144 |
145 | ```cpp
146 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
147 | ListNode *pa = headA;
148 | ListNode *pb = headB;
149 |
150 | while (pa != pb) {
151 | pa = pa != nullptr ? pa->next : headB;
152 | pb = pb != nullptr ? pb->next : headA;
153 | }
154 |
155 | return pa;
156 | }
157 | ```
158 |
159 |
160 | #### 单链表找中间节点 [LeetCode 876](https://leetcode.com/problems/middle-of-the-linked-list/)
161 |
162 | 用快慢指针法,当快指针走到链表结尾时,慢指针刚好走到链表的中间:
163 |
164 | ```cpp
165 | ListNode* middleNode(ListNode* head) {
166 | ListNode *slow = head;
167 | ListNode *fast = head;
168 | while (fast && fast->next) {
169 | slow = slow->next;
170 | fast = fast->next->next;
171 | }
172 |
173 | return slow;
174 | }
175 | ```
176 |
177 | #### 单链表合并 [LeetCode 21](https://leetcode.com/problems/merge-two-sorted-lists/)
178 |
179 | 两个链表本身都是排序过的,把两个链表从头节点开始,逐个节点开始进行比较,最后剩下的节点接到尾部:
180 |
181 | ```cpp
182 | ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) {
183 | if (l1 == nullptr) {
184 | return l2;
185 | }
186 | if (l2 == nullptr) {
187 | return l1;
188 | }
189 | ListNode dummy(-1);
190 | ListNode *p = &dummy;
191 | for (; l1 && l2; p = p->next) {
192 | if (l1->val < l2->val) {
193 | p->next = l1;
194 | l1 = l1->next;
195 | } else {
196 | p->next = l2;
197 | l2 = l2->next;
198 | }
199 | }
200 | p->next = l1 != nullptr ? l1 : l2;
201 | return dummy.next;
202 | }
203 | ```
--------------------------------------------------------------------------------
/source/basic/algo/README.md:
--------------------------------------------------------------------------------
1 | #### 推荐资源
2 |
3 | **网站**
4 |
5 | * [LeetCode](https://leetcode.com/)
6 | * [力扣(LeetCode 中文)](https://leetcode-cn.com/)
7 | * [领扣 LintCode](https://www.lintcode.com/)
8 |
9 | **题解**
10 |
11 | * [LeetCode 题解(旧版,只有老题,已不再更新)](https://github.com/soulmachine/leetcode)
12 | * [LeetCode 题解(有新题,还在更新)](https://github.com/haoel/leetcode)
13 | * [图解 LeetCode](https://github.com/MisterBooo/LeetCodeAnimation)
14 |
--------------------------------------------------------------------------------
/source/basic/algo/Random.md:
--------------------------------------------------------------------------------
1 | ## 洗牌算法
2 |
3 | 洗牌算法,顾名思义,就是只利用一次循环等概率的取到不同的元素(牌)。
4 |
5 | 如果元素存在于数组中,即可将每次 random 到的元素 与 最后一个元素进行交换,然后 count--,即可。
6 |
7 | 这相当于把这个元素删除,代码如下:
8 |
9 | ```cpp
10 | #include
11 | #include
12 | using namespace std;
13 |
14 | const int maxn = 10;
15 |
16 | int a[maxn];
17 |
18 | int randomInt(int a) {
19 | return rand()%a;
20 | }
21 | void swapTwoElement(int*x,int*y) {
22 | int temp;
23 | temp=*x;
24 | *x=*y;
25 | *y=temp;
26 | }
27 |
28 | int main(){
29 | int count = sizeof(a)/sizeof(int);
30 | int count_b = count;
31 | srand((unsigned)time(NULL));
32 | for (int i = 0; i < count; ++i) { a[i] = i; }
33 | for (int i = 0; i < count_b; ++i) {
34 | int random = randomInt(count);
35 | cout< array[j + 1]) // 若这里的条件是 >=,则变成不稳定排序
50 | {
51 | Swap(array, j, j+1);
52 | }
53 | }
54 | }
55 | }
56 | ```
57 |
58 | #### 优化
59 |
60 | 在非最坏的情况下,冒泡排序过程中,可以检测到整个序列是否已经排序完成,进而可以避免掉后续的循环:
61 |
62 | ```csharp
63 | private static void BubbleSort(int[] array)
64 | {
65 | for (var i = 0; i < array.Length - 1; i++)
66 | {
67 | var swapped = false;
68 | for (var j = 0; j < array.Length - 1; j++)
69 | {
70 | if (array[j] > array[j + 1])
71 | {
72 | Swap(array, j, j+1);
73 | swapped = true;
74 | }
75 | }
76 |
77 | if (!swapped) // 没有发生交互,证明排序已经完成
78 | {
79 | break;
80 | }
81 | }
82 | }
83 | ```
84 |
85 | 进一步地,在每轮循环之后,可以确认,最后一次发生交换的位置之后的元素,都是已经排好序的,因此可以不再比较那个位置之后的元素,大幅度减少了比较的次数:
86 |
87 | ```csharp
88 | private static void BubbleSort(int[] array)
89 | {
90 | var n = array.Length;
91 | for (var i = 0; i < array.Length - 1; i++)
92 | {
93 | var newn = 0;
94 | for (var j = 0; j < n - 1; j++)
95 | {
96 | if (array[j] > array[j + 1])
97 | {
98 | Swap(array, j, j+1);
99 | newn = j + 1; // newn 以及之后的元素,都是排好序的
100 | }
101 | }
102 |
103 | n = newn;
104 |
105 | if (n == 0)
106 | {
107 | break;
108 | }
109 | }
110 | }
111 | ```
112 |
113 | 更进一步地,为了优化之前提到的乌龟和兔子问题,可以进行双向的循环,正向循环把最大元素移动到末尾,逆向循环把最小元素移动到最前,这种优化过的冒泡排序,被称为鸡尾酒排序:
114 |
115 | ```csharp
116 | private static void CocktailSort(int[] array)
117 | {
118 | var begin = 0;
119 | var end = array.Length - 1;
120 | while (begin <= end)
121 | {
122 | var newBegin = end;
123 | var newEnd = begin;
124 |
125 | for (var j = begin; j < end; j++)
126 | {
127 | if (array[j] > array[j + 1])
128 | {
129 | Swap(array, j, j + 1);
130 | newEnd = j + 1;
131 | }
132 | }
133 |
134 | end = newEnd - 1;
135 |
136 | for (var j = end; j > begin - 1; j--)
137 | {
138 | if (array[j] > array[j + 1])
139 | {
140 | Swap(array, j, j + 1);
141 | newBegin = j;
142 | }
143 | }
144 |
145 | begin = newBegin + 1;
146 | }
147 | }
148 | ```
149 |
150 | ### 插入排序
151 |
152 | 插入排序也是一个简单的排序算法,它的思想是,每次只处理一个元素,从后往前查找,找到该元素合适的插入位置,最好的情况下,即正序有序(从小到大),这样只需要比较n次,不需要移动。因此时间复杂度为O(n) ,最坏的情况下,即逆序有序,这样每一个元素就需要比较n次,共有n个元素,因此实际复杂度为O(n²) 。
153 |
154 | #### 算法实现:
155 |
156 | ```csharp
157 | private static void InsertionSort(int[] array)
158 | {
159 | int i = 1;
160 | while (i < array.Length)
161 | {
162 | var j = i;
163 | while (j > 0 && array[j - 1] > array[j])
164 | {
165 | Swap(array, j, j - 1);
166 | j--;
167 | }
168 |
169 | i++;
170 | }
171 | }
172 | ```
173 |
174 | ### 快排
175 |
176 | 快排是经典的 divide & conquer 问题,如下用于描述快排的思想、伪代码、代码、复杂度计算以及快排的变形。
177 |
178 | #### 快排的思想
179 |
180 | 如下的三步用于描述快排的流程:
181 |
182 | - 在数组中随机取一个值作为标兵
183 | - 对标兵左、右的区间进行划分(将比标兵大的数放在标兵的右面,比标兵小的数放在标兵的左面,如果倒序就反过来)
184 | - 重复如上两个过程,直到选取了所有的标兵并划分(此时每个标兵决定的区间中只有一个值,故有序)
185 |
186 | ##### 伪代码
187 |
188 | 如下是快排的主体伪代码
189 |
190 | ```
191 | QUCIKSORT(A, p, r)
192 | if p < r
193 | q = PARTITION(A, p, r)
194 | QUICKSORT(A, p, q-1)
195 | QUICKSORT(A, q+1, r)
196 | ```
197 |
198 | 如下是用于选取标兵以及划分的伪代码
199 |
200 | ```
201 | PARTITION(A, p, r)
202 | x = A[r]
203 | i = p - 1
204 | for j = p to r - 1
205 | if A[j] <= x
206 | i++
207 | swap A[i] with A[j]
208 | swap A[i+1] with A[j]
209 | return i+1
210 | ```
211 |
212 | ##### 代码
213 |
214 | ```Swift
215 | func quickSort(inout targetArray: [Int], begin: Int, end: Int) {
216 | if begin < end {
217 | let pivot = partition(&targetArray, begin: begin, end: end)
218 | quickSort(&targetArray, begin: begin, end: pivot - 1)
219 | quickSort(&targetArray, begin: pivot + 1, end: end)
220 | }
221 | }
222 |
223 | func partition(inout targetArray: [Int], begin: Int, end: Int) -> Int {
224 | let value = targetArray[end]
225 | var i = begin - 1
226 | for j in begin ..< end {
227 | if targetArray[j] <= value {
228 | i += 1;
229 | swapTwoValue(&targetArray[i], b: &targetArray[j])
230 | }
231 | }
232 | swapTwoValue(&targetArray[i+1], b: &targetArray[end])
233 | return i+1
234 | }
235 |
236 | func swapTwoValue(inout a: Int, inout b: Int) {
237 | let c = a
238 | a = b
239 | b = c
240 | }
241 |
242 | var testArray :[Int] = [123,3333,223,231,3121,245,1123]
243 |
244 | quickSort(&testArray, begin: 0, end: testArray.count-1)
245 | ```
246 |
247 | ##### 复杂度分析
248 |
249 | 在最好的情况下,每次 partition 都会把数组一分为二,所以时间复杂度 T(n) = 2T(n/2) + O(n)
250 |
251 | 解为 T(n) = O(nlog(n))
252 |
253 | 在最坏的情况下,数组刚好和想要的结果顺序相同,每次 partition 到的都是当前无序区中最小(或最大)的记录,因此只得到一个比上一次划分少一个记录的子序列。T(n) = O(n) + T(n-1)
254 |
255 | 解为 T(n) = O(n²)
256 |
257 | 在平均的情况下,快排的时间复杂度是 O(nlog(n))
258 |
259 | ##### 变形
260 |
261 | 可以利用快排的 PARTITION 思想求数组中第K大元素这样的问题,步骤如下:
262 |
263 | - 在数组中随机取一个值作为标兵,左右分化后其顺序为X
264 | - 如果 X == Kth 说明这就是第 K 大的数
265 | - 如果 X > Kth 说明第 K 大的数在标兵左边,继续在左边寻找第 Kth 大的数
266 | - 如果 X < Kth 说明第 K 大的数在标兵右边,继续在右边需找第 Kth - X 大的数
267 |
268 | 这个问题的时间复杂度是 O(n)
269 |
270 | T(n) = n + n/2 + n/4 + ... = O(n)
271 |
272 | ### 参考资料
273 |
274 | 1. [各种基本排序算法的总结](http://blog.sina.com.cn/s/blog_4080505a0101iewt.html)
275 | 2. [常用排序算法小结](http://blog.csdn.net/whuslei/article/details/6442755)
276 | 3. [八大排序算法总结](http://blog.csdn.net/yexinghai/article/details/4649923)
277 | 4. [QuickSort](https://en.wikipedia.org/wiki/Quicksort)
278 |
--------------------------------------------------------------------------------
/source/basic/algo/Tree.md:
--------------------------------------------------------------------------------
1 | ## 基本知识
2 |
3 | ### 二叉树
4 |
5 | **二叉树**:二叉树是有限个结点的集合,这个集合或者是空集,或者是由一个根结点和两株互不相交的二叉树组成,其中一株叫根的做左子树,另一棵叫做根的右子树。
6 |
7 | **二叉树的性质**:
8 |
9 | * 性质1:在二叉树中第 i 层的结点数最多为2^(i-1)(i ≥ 1)
10 | * 性质2:高度为k的二叉树其结点总数最多为2^k-1( k ≥ 1)
11 | * 性质3:对任意的非空二叉树 T ,如果叶结点的个数为 n0,而其度为 2 的结点数为 n2,则:`n0 = n2 + 1`
12 |
13 | **满二叉树**:深度为k且有2^k -1个结点的二叉树称为满二叉树
14 |
15 | **完全二叉树**:深度为 k 的,有n个结点的二叉树,当且仅当其每个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应,称之为完全二叉树。(除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点)
16 |
17 | * 性质4:具有 n 个结点的完全二叉树的深度为 log2n + 1
18 |
19 | **注意**:
20 |
21 | * 仅有前序和后序遍历,不能确定一个二叉树,必须有中序遍历的结果
22 |
23 | ### 堆
24 |
25 | 如果一棵完全二叉树的任意一个非终端结点的元素都不小于其左儿子结点和右儿子结点(如果有的话)
26 | 的元素,则称此完全二叉树为最大堆。
27 |
28 | 同样,如果一棵完全二叉树的任意一个非终端结点的元素都不大于其左儿子结点和右儿子结点(如果
29 | 有的话)的元素,则称此完全二叉树为最小堆。
30 |
31 | **最大堆的根结点中的元素在整个堆中是最大的;**
32 |
33 | **最小堆的根结点中的元素在整个堆中是最小的。**
34 |
35 | ### 哈弗曼树
36 |
37 | * 定义:给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。
38 |
39 | * 构造:
40 |
41 | 假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
42 |
43 | 1. 将w1、w2、…,wn看成是有 n 棵树的森林(每棵树仅有一个结点);
44 | 2. 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
45 | 3. 从森林中删除选取的两棵树,并将新树加入森林;
46 | 4. 重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
47 |
48 |
49 | ### 二叉排序树
50 |
51 | 二叉排序树(Binary Sort Tree)又称二叉查找树(Binary Search Tree),亦称二叉搜索树。
52 |
53 | 二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
54 |
55 | 1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
56 | 2. 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
57 | 3. 左、右子树也分别为二叉排序树;
58 | 4. 没有键值相等的节点
59 |
60 | 二分查找的时间复杂度是O(log(n)),最坏情况下的时间复杂度是O(n)(相当于顺序查找)
61 |
62 | ### 平衡二叉树
63 |
64 | 平衡二叉树(balanced binary tree),又称 AVL 树。它或者是一棵空树,或者是具有如下性质的二叉树:
65 |
66 | 1. 它的左子树和右子树都是平衡二叉树,
67 | 2. 左子树和右子树的深度之差的绝对值不超过1。
68 |
69 | 平衡二叉树是对二叉搜索树(又称为二叉排序树)的一种改进。二叉搜索树有一个缺点就是,树的结构是无法预料的,随意性很大,它只与节点的值和插入的顺序有关系,往往得到的是一个不平衡的二叉树。在最坏的情况下,可能得到的是一个单支二叉树,其高度和节点数相同,相当于一个单链表,对其正常的时间复杂度有O(log(n))变成了O(n),从而丧失了二叉排序树的一些应该有的优点。
70 |
71 |
72 | ### B-树
73 |
74 | **B-树**:B-树是一种非二叉的查找树, 除了要满足查找树的特性,还要满足以下结构特性:
75 |
76 | 一棵 m 阶的B-树:
77 |
78 | 1. 树的根或者是一片叶子(一个节点的树),或者其儿子数在 2 和 m 之间。
79 | 2. 除根外,所有的非叶子结点的孩子数在 m/2 和 m 之间。
80 | 3. 所有的叶子结点都在相同的深度。
81 |
82 | B-树的平均深度为logm/2(N)。执行查找的平均时间为O(logm);
83 |
84 | ### Trie 树
85 |
86 | Trie 树,又称前缀树,字典树, 是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
87 |
88 | Trie 树查询和插入时间复杂度都是 O(n),是一种以空间换时间的方法。当节点树较多的时候,Trie 树占用的内存会很大。
89 |
90 | Trie 树常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
91 |
92 | ## 例题
93 |
94 | ### 二叉树的遍历
95 |
96 | #### 二叉树前中后序遍历
97 |
98 | 二叉树的前中后序遍历,使用递归算法实现最为简单,以前序遍历([LeetCode 144](https://leetcode.com/problems/binary-tree-preorder-traversal/))为例:
99 |
100 | ```cpp
101 | void preorder(TreeNode *p, vector& result) {
102 | if (p == NULL) {
103 | return;
104 | }
105 |
106 | result.push_back(p->val);
107 | preorder(p->left, result);
108 | preorder(p->right, result);
109 | }
110 |
111 | vector preorderTraversal(TreeNode* root) {
112 | vector result;
113 | if (root == nullptr) {
114 | return result;
115 | }
116 |
117 | preorder(root, result);
118 | return result;
119 | }
120 | ```
121 |
122 | 二叉树的非递归遍历,主要的思想是使用栈(Stack)来进行存储操作,记录经过的节点。
123 |
124 | 非递归前序遍历([LeetCode 144](https://leetcode.com/problems/binary-tree-preorder-traversal/)):
125 |
126 | ```cpp
127 | vector preorderTraversal(TreeNode* root) {
128 | TreeNode *p = root;
129 | vector result;
130 | if (!p) {
131 | return result;
132 | }
133 |
134 | stack q;
135 | while (p || !q.empty()) {
136 | if (p) {
137 | result.push_back(p->val);
138 | q.push(p);
139 | p = p->left;
140 | }
141 | else {
142 | p = q.top();
143 | q.pop();
144 | p = p->right;
145 | }
146 | }
147 | return result;
148 | }
149 | ```
150 |
151 | 非递归中序遍历([LeetCode 94](https://leetcode.com/problems/binary-tree-inorder-traversal/)):
152 |
153 | ```cpp
154 | vector inorderTraversal(TreeNode* root) {
155 | TreeNode *p = root;
156 | vector result;
157 | if (!p) {
158 | return result;
159 | }
160 |
161 | stack q;
162 | while (p || !q.empty()) {
163 | if (p) {
164 | q.push(p);
165 | p = p->left;
166 | }
167 | else {
168 | p = q.top();
169 | result.push_back(p->val);
170 | q.pop();
171 | p = p->right;
172 | }
173 | }
174 | return result;
175 | }
176 | ```
177 |
178 | 非递归遍历中,后序遍历相对更难实现,因为需要在遍历完左右子节点之后,再遍历根节点,因此不能直接将根节点出栈。这里使用一个 last 指针记录上次出栈的节点,当且仅当节点的右孩子为空(top->right == NULL),或者右孩子已经出栈(top->right == last),才将本节点出栈:
179 |
180 | 非递归后序遍历([LeetCode 145](https://leetcode.com/problems/binary-tree-postorder-traversal/)):
181 |
182 | ```cpp
183 | vector postorderTraversal(TreeNode* root) {
184 | TreeNode *p = root;
185 | vector result;
186 | if (!p) {
187 | return result;
188 | }
189 |
190 | TreeNode *top, *last = NULL;
191 | stack q;
192 | while (p || !q.empty()) {
193 | if (p) {
194 | q.push(p);
195 | p = p->left;
196 | } else {
197 | top = q.top();
198 | if (top->right == NULL || top->right == last) {
199 | q.pop();
200 | result.push_back(top->val);
201 | last = top;
202 | } else {
203 | p = top->right;
204 | }
205 | }
206 | }
207 |
208 | return result;
209 | }
210 | ```
211 |
212 | #### 二叉树层序遍历 [LeetCode 102](https://leetcode.com/problems/binary-tree-level-order-traversal/)
213 |
214 | 二叉树层序遍历有两种方法,分别是深度优先和广度优先:
215 |
216 | 深度优先(DFS)实现:
217 |
218 | ```cpp
219 | void traversal(TreeNode *root, int level, vector> &result) {
220 | if (!root) {
221 | return;
222 | }
223 | // 保证每一层只有一个vector
224 | if (level > result.size()) {
225 | result.push_back(vector());
226 | }
227 | result[level-1].push_back(root->val);
228 | traversal(root->left, level+1, result);
229 | traversal(root->right, level+1, result);
230 | }
231 |
232 | vector > levelOrder(TreeNode *root) {
233 | vector> result;
234 | traversal(root, 1, result);
235 | return result;
236 | }
237 | ```
238 |
239 | 广度优先(BFS)实现:
240 |
241 | ```cpp
242 | vector> levelOrder(TreeNode* root) {
243 | std:queue q;
244 | TreeNode *p;
245 |
246 | vector> result;
247 | if (root == NULL) return result;
248 |
249 | q.push(root);
250 |
251 | while (!q.empty()) {
252 | int size = q.size();
253 | vector levelResult;
254 |
255 | for (int i = 0; i < size; i++) {
256 | p = q.front();
257 | q.pop();
258 |
259 | levelResult.push_back(p->val);
260 |
261 | if (p->left) {
262 | q.push(p->left);
263 | }
264 | if (p->right) {
265 | q.push(p->right);
266 | }
267 | }
268 |
269 | result.push_back(levelResult);
270 | }
271 |
272 | return result;
273 | }
274 | ```
275 |
276 | ### 二叉树子树 [LeetCode 572](https://leetcode.com/problems/subtree-of-another-tree/)
277 |
278 | 判断二叉树是否是另一棵二叉树的子树,使用递归实现:
279 |
280 | ```cpp
281 | bool isSubtree(TreeNode* s, TreeNode* t) {
282 | if (!s) return false;
283 | if (sameTree(s, t)) return true;
284 | return isSubtree(s->left, t) || isSubtree(s->right, t);
285 | }
286 |
287 | bool sameTree(TreeNode* s, TreeNode* t) {
288 | if (!s && !t) return true;
289 | if (!s || !t) return false;
290 | if (s->val != t->val) return false;
291 | return sameTree(s->left, t->left) && sameTree(s->right, t->right);
292 | }
293 | ```
294 |
295 | ### 翻转二叉树 [LeetCode 226](https://leetcode.com/problems/invert-binary-tree/)
296 |
297 | 交互树的左右儿子节点,使用递归实现:
298 |
299 | ```cpp
300 | TreeNode* invertTree(TreeNode* root) {
301 | if (root == nullptr) {
302 | return nullptr;
303 | }
304 | TreeNode *tmp = root->left;
305 | root->left = root->right;
306 | root->right = tmp;
307 | if (root->left) {
308 | invertTree(root->left);
309 | }
310 | if (root->right) {
311 | invertTree(root->right);
312 | }
313 | return root;
314 | }
315 | ```
316 |
317 | ### 参考资料
318 |
319 | * [百度百科:哈弗曼树](http://baike.baidu.com/view/127820.htm)
320 | * [百度百科:二叉排序树](http://baike.baidu.com/view/647462.htm)
321 | * [百度百科:平衡二叉树](http://baike.baidu.com/view/593144.htm)
322 | * [平衡二叉树及其应用场景](http://blog.csdn.net/huiguixian/article/details/6360682)
323 | * [百度百科:B-树](http://baike.baidu.com/view/2228473.htm)
324 | * [前缀树](http://www.iteye.com/topic/1132573)
325 | * [百度百科:前缀树](http://baike.baidu.com/view/9875057.htm)
--------------------------------------------------------------------------------
/source/basic/arch/Arch.md:
--------------------------------------------------------------------------------
1 | ## 冯·诺依曼体系结构
2 |
3 | 1. 计算机处理的数据和指令一律用二进制数表示
4 | 2. 顺序执行程序
5 |
6 | 计算机运行过程中,把要执行的程序和处理的数据首先存入主存储器(内存),计算机执行程序时,将自动地并按顺序从主存储器中取出指令一条一条地执行,这一概念称作顺序执行程序。
7 |
8 | 3. 计算机硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成。
9 |
10 | ## 数据的机内表示
11 |
12 | ### 二进制表示
13 |
14 | 1. 机器数
15 |
16 | 由于计算机中符号和数字一样,都必须用二进制数串来表示,因此,正负号也必须用0、1来表示。
17 |
18 | 用最高位0表示正、1表示负, 这种正负号数字化的机内表示形式就称为“机器数”,而相应的机器外部用正负号表示的数称为“真值”,将一个真值表示成二进制字串的机器数的过程就称为编码。
19 |
20 | 2. 原码
21 |
22 | 原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
23 |
24 | [+1]原 = 0000 0001
25 |
26 | [-1]原 = 1000 0001
27 |
28 | 第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:
29 |
30 | [1111 1111 , 0111 1111]
31 |
32 | 即
33 |
34 | [-127 , 127]
35 |
36 | 原码是人脑最容易理解和计算的表示方式
37 |
38 | 3. 反码
39 |
40 | 反码的表示方法是:
41 |
42 | 正数的反码是其本身
43 |
44 | 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
45 |
46 | [+1] = [00000001]原 = [00000001]反
47 |
48 | [-1] = [10000001]原 = [11111110]反
49 |
50 | 可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算
51 |
52 | 4. 补码
53 |
54 | 补码的表示方法是:
55 |
56 | 正数的补码就是其本身
57 |
58 | 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1。 (即在反码的基础上+1)
59 |
60 | [+1] = [00000001]原 = [00000001]反 = [00000001]补
61 |
62 | [-1] = [10000001]原 = [11111110]反 = [11111111]补
63 |
64 | 对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.
65 |
66 | 5. 定点数与浮点数
67 |
68 | 定点数是小数点固定的数。在计算机中没有专门表示小数点的位,小数点的位置是约定默认的。一般固定在机器数的最低位之后,或是固定在符号位之后。前者称为定点纯整数,后者称为定点纯小数。
69 |
70 | 定点数表示法简单直观,但是数值表示的范围太小,运算时容易产生溢出。
71 |
72 |
73 | 浮点数是小数点的位置可以变动的数。为增大数值表示范围,防止溢出,采用浮点数表示法。浮点表示法类似于十进制中的科学计数法。
74 |
75 | 在计算机中通常把浮点数分成阶码和尾数两部分来表示,其中阶码一般用补码定点整数表示,尾数一般用补码或原码定点小数表示。为保证不损失有效数字,对尾数进行规格化处理,也就是平时所说的科学记数法,即保证尾数的最高位为1,实际数值通过阶码进行调整
76 |
77 |
78 | 阶符表示指数的符号位、阶码表示幂次、数符表示尾数的符号位、尾数表示规格化后的小数值。
79 |
80 | `N = 尾数×基数阶码(指数)`
81 |
82 |
83 | ### 位(Bit)、字节(Byte)、字(Word)
84 |
85 | 位:"位(bit)"是电子计算机中最小的数据单位。每一位的状态只能是0或1。
86 |
87 | 字节:8个二进制位构成1个"字节(Byte)",它是存储空间的基本计量单位。1个字节可以储存1个英文字母或者半个汉字,换句话说,1个汉字占据2个字节的存储空间。
88 |
89 | 字:"字"由若干个字节构成,字的位数叫做字长,不同档次的机器有不同的字长。例如一台8位机,它的1个字就等于1个字节,字长为8位。如果是一台16位机,那么,它的1个字就由2个字节构成,字长为16位。字是计算机进行数据处理和运算的单位。
90 |
91 | ### 字节序
92 |
93 | 字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序。
94 |
95 | 小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;
96 |
97 | 大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。
98 |
99 | 基于X86平台的PC机是小端字节序的,而有的嵌入式平台则是大端字节序的。所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。
100 |
101 | 比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:
102 |
103 | Big Endian
104 | 低地址 高地址
105 | ---------------------------------------------------->
106 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
107 | | 12 | 34 | 56 | 78 |
108 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
109 |
110 | Little Endian
111 | 低地址 高地址
112 | ---------------------------------------------------->
113 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
114 | | 78 | 56 | 34 | 12 |
115 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
116 |
117 |
118 | 从上面两图可以看出,采用Big Endian方式存储数据是符合我们人类的思维习惯的。
119 |
120 |
121 | 联合体`union`的存放顺序是所有成员都从低地址开始存放,利用该特性,就能判断CPU对内存采用Little-endian还是Big-endian模式读写。
122 |
123 | 示例代码如下:
124 |
125 | ```c
126 | union test{
127 | short i;
128 | char str[sizeof(short)];
129 | }tt;
130 |
131 | void main()
132 | {
133 | tt.i = 0x0102;
134 | if(sizeof(short) == 2)
135 | {
136 | if(tt.str[0] == 1 && tt.str[1] == 2)
137 | printf("大端字节序");
138 | else if(tt.str[0] = 2 && tt.str[1] == 1)
139 | printf("小端字节序");
140 | else
141 | printf("结果未知");
142 | }
143 | else
144 | printf("sizof(short)=%d,不等于2",sizeof(short));
145 | }
146 | ```
147 |
148 | ### 字节对齐
149 |
150 | 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
151 |
152 | * 为什么要进行字节对齐?
153 |
154 | 1. 某些平台只能在特定的地址处访问特定类型的数据;
155 | 2. 最根本的原因是效率问题,字节对齐能提⾼存取数据的速度。
156 |
157 | 比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量,但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
158 |
159 | * 字节对齐的原则
160 |
161 | 1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在 offset 为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储。
162 |
163 | 2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储。)
164 |
165 | 3. 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
166 |
167 | ### 参考资料
168 |
169 | * [百度百科:冯·诺依曼体系结构](http://baike.baidu.com/view/9427882.htm)
170 | * [二进制原码、反码、补码](http://blog.csdn.net/yinyhy/article/details/8732118)
171 | * [什么是位、字节、字、KB、MB?](http://jingyan.baidu.com/article/ad310e808e90b71849f49eae.html)
172 | * [定点数与浮点数](http://jsjedu.hxu.edu.cn/dxjsjjc/kcnr/wlkj/03architecture/detail/3-1-5.htm)
173 | * [大小字节序](http://blog.csdn.net/mfc5158/article/details/6991374)
174 | * [浅谈字节序(Byte Order)及其相关操作](http://www.cnblogs.com/JeffreyZhao/archive/2010/02/10/byte-order-and-related-library.html)
175 | * [大小端字节序的判断](http://blog.csdn.net/lyh66/article/details/7478474)
176 | * [百度百科:字节对齐](http://baike.baidu.com/view/1523557.htm)
177 | * [五分钟搞定内存对齐](http://blog.csdn.net/hairetz/article/details/4084088)
178 |
--------------------------------------------------------------------------------
/source/basic/arch/Concurrency.md:
--------------------------------------------------------------------------------
1 | ## 多任务
2 |
3 | 在上古时代,CPU 资源十分昂贵,如果让 CPU 只能运行一个程序,那么当 CPU 空闲下来(例如等待 I/O 时),CPU 就空闲下来了。为了让 CPU 得到更好的利用,人们编写了一个监控程序,如果发现某个程序暂时无须使用 CPU 时,监控程序就把另外的正在等待 CPU 资源的程序启动起来,以充分利用 CPU 资源。这种方法被称为 **多道程序(Multiprogramming)**。
4 |
5 | 对于多道程序来说,最大的问题是程序之间不区分轻重缓急,对于交互式程序来说,对于 CPU 计算时间的需求并不多,但是对于响应速度却有比较高的要求。而对于计算类程序来说则正好相反,对于响应速度要求低,但是需要长时间的 CPU 计算。想象一下我们同时在用浏览器上网和听音乐,我们希望浏览器能够快速响应,同时也希望音乐不停掉。这时候多道程序就没法达到我们的要求了。于是人们改进了多道程序,使得每个程序运行一段时间之后,都主动让出 CPU 资源,这样每个程序在一段时间内都有机会运行一小段时间。这样像浏览器这样的交互式程序就能够快速地被处理,同时计算类程序也不会受到很大影响。这种程序协作方式被称为 **分时系统(Time-Sharing System)**。
6 |
7 | 在分时系统的帮助下,我们可以边用浏览器边听歌了,但是如果某个程序出现了错误,导致了死循环,不仅仅是这个程序会出错,整个系统都会死机。为了避免这种情况,一个更为先进的操作系统模式被发明出来,也就是我们现在很熟悉的 **多任务(Multi-tasking)系统**。操作系统从最底层接管了所有硬件资源。所有的应用程序在操作系统之上以 **进程(Process)** 的方式运行,每个进程都有自己独立的地址空间,相互隔离。CPU 由操作系统统一进行分配。每个进程都有机会得到 CPU,同时在操作系统控制之下,如果一个进程运行超过了一定时间,就会被暂停掉,失去 CPU 资源。这样就避免了一个程序的错误导致整个系统死机。如果操作系统分配给各个进程的运行时间都很短,CPU 可以在多个进程间快速切换,就像很多进程都同时在运行的样子。几乎所有现代操作系统都是采用这样的方式支持多任务,例如 Unix,Linux,Windows 以及 macOS。
8 |
9 | ## 进程
10 |
11 | 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
12 |
13 | 进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
14 |
15 | ### 进程的基本状态
16 |
17 | 1. 等待态:等待某个事件的完成;
18 | 2. 就绪态:等待系统分配处理器以便运行;
19 | 3. 运行态:占有处理器正在运行。
20 |
21 | 运行态→等待态 往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的。
22 |
23 | 等待态→就绪态 则是等待的条件已满足,只需分配到处理器后就能运行。
24 |
25 | 运行态→就绪态 不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等。
26 |
27 | 就绪态→运行态 系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态
28 |
29 |
30 | ### 进程调度
31 |
32 | #### 调度种类
33 |
34 | 高级、中级和低级调度作业从提交开始直到完成,往往要经历下述三级调度:
35 |
36 | * 高级调度:(High-Level Scheduling)又称为作业调度,它决定把后备作业调入内存运行;
37 | * 中级调度:(Intermediate-Level Scheduling)又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
38 | * 低级调度:(Low-Level Scheduling)又称为进程调度,它决定把就绪队列的某进程获得CPU;
39 |
40 | #### 非抢占式调度与抢占式调度
41 |
42 | * 非抢占式
43 |
44 | 分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
45 |
46 | * 抢占式
47 |
48 | 操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
49 |
50 | #### 调度策略的设计
51 |
52 | 响应时间: 从用户输入到产生反应的时间
53 |
54 | 周转时间: 从任务开始到任务结束的时间
55 |
56 | CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待的时间。
57 |
58 | #### 调度算法
59 |
60 | 1. FIFO或First Come, First Served (FCFS)
61 |
62 | 调度的顺序就是任务到达就绪队列的顺序。
63 |
64 | 公平、简单(FIFO队列)、非抢占、不适合交互式。未考虑任务特性,平均等待时间可以缩短
65 |
66 | 2. Shortest Job First (SJF)
67 |
68 | 最短的作业(CPU区间长度最小)最先调度。
69 |
70 | 可以证明,SJF可以保证最小的平均等待时间。
71 |
72 | * Shortest Remaining Job First (SRJF)
73 |
74 | SJF的可抢占版本,比SJF更有优势。
75 |
76 | SJF(SRJF): 如何知道下一CPU区间大小?根据历史进行预测: 指数平均法。
77 |
78 |
79 | 3. 优先权调度
80 |
81 | 每个任务关联一个优先权,调度优先权最高的任务。
82 |
83 | 注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象。
84 |
85 | FCFS是RR的特例,SJF是优先权调度的特例。这些调度算法都不适合于交互式系统。
86 |
87 | 4. Round-Robin(RR)
88 |
89 | 设置一个时间片,按时间片来轮转调度(“轮叫”算法)
90 |
91 | 优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多;
92 |
93 | 如何确定时间片?
94 |
95 | 时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
96 |
97 | 5. 多级队列调度
98 |
99 | * 按照一定的规则建立多个进程队列
100 | * 不同的队列有固定的优先级(高优先级有抢占权)
101 | * 不同的队列可以给不同的时间片和采用不同的调度方法
102 |
103 | 存在问题1:没法区分I/O bound和CPU bound;
104 |
105 | 存在问题2:也存在一定程度的“饥饿”现象;
106 |
107 | 6. 多级反馈队列
108 |
109 | 在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。
110 |
111 | 可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。
112 |
113 | 最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。
114 |
115 | ### 进程同步
116 |
117 | #### 临界资源与临界区
118 |
119 | 在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。
120 |
121 | 对于临界资源的访问,必须是互斥进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。
122 |
123 | 对于临界区的访问过程分为四个部分:
124 |
125 | 1. 进入区:查看临界区是否可访问,如果可以访问,则转到步骤二,否则进程会被阻塞
126 | 2. 临界区:在临界区做操作
127 | 3. 退出区:清除临界区被占用的标志
128 | 4. 剩余区:进程与临界区不相关部分的代码
129 |
130 | 解决临界区问题可能的方法:
131 |
132 | 1. 一般软件方法
133 | 2. 关中断方法
134 | 3. 硬件原子指令方法
135 | 4. 信号量方法
136 |
137 | #### 信号量
138 |
139 | 信号量是一个确定的二元组(s,q),其中s是一个具有非负初值的整形变量,q是一个初始状态为空的队列,整形变量s表示系统中某类资源的数目:
140 |
141 | * 当其值 ≥ 0 时,表示系统中当前可用资源的数目
142 | * 当其值 < 0 时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目
143 |
144 | 除信号量的初值外,信号量的值仅能由P操作和V操作更改,操作系统利用它的状态对进程和资源进行管理
145 |
146 | P操作:
147 |
148 | P操作记为P(s),其中s为一信号量,它执行时主要完成以下动作:
149 |
150 |
151 | s.value = s.value - 1; /*可理解为占用1个资源,若原来就没有则记帐“欠”1个*/
152 |
153 | 若s.value ≥ 0,则进程继续执行,否则(即s.value < 0),则进程被阻塞,并将该进程插入到信号量s的等待队列s.queue中
154 |
155 | 说明:实际上,P操作可以理解为分配资源的计数器,或是使进程处于等待状态的控制指令
156 |
157 | V操作:
158 |
159 | V操作记为V(s),其中s为一信号量,它执行时,主要完成以下动作:
160 |
161 | s.value = s.value + 1;/*可理解为归还1个资源,若原来就没有则意义是用此资源还1个欠帐*/
162 |
163 | 若s.value > 0,则进程继续执行,否则(即s.value ≤ 0),则从信号量s的等待队s.queue中移出第一个进程,使其变为就绪状态,然后返回原进程继续执行
164 |
165 | 说明:实际上,V操作可以理解为归还资源的计数器,或是唤醒进程使其处于就绪状态的控制指令
166 |
167 |
168 | 信号量方法实现:生产者 − 消费者互斥与同步控制
169 |
170 | semaphore fullBuffers = 0; /*仓库中已填满的货架个数*/
171 | semaphore emptyBuffers = BUFFER_SIZE;/*仓库货架空闲个数*/
172 | semaphore mutex = 1; /*生产-消费互斥信号*/
173 |
174 | Producer()
175 | {
176 | while(True)
177 | {
178 | /*生产产品item*/
179 | emptyBuffers.P();
180 | mutex.P();
181 | /*item存入仓库buffer*/
182 | mutex.V();
183 | fullBuffers.V();
184 | }
185 | }
186 |
187 | Consumer()
188 | {
189 | while(True)
190 | {
191 | fullBuffers.P();
192 | mutex.P();
193 | /*从仓库buffer中取产品item*/
194 | mutex.V();
195 | emptyBuffers.V();
196 | /*消费产品item*/
197 | }
198 | }
199 |
200 |
201 | #### 死锁
202 |
203 | 死锁: 多个进程因循环等待资源而造成无法执行的现象。
204 |
205 | 死锁会造成进程无法执行,同时会造成系统资源的极大浪费(资源无法释放)。
206 |
207 | 死锁产生的4个必要条件:
208 |
209 | * 互斥使用(Mutual exclusion)
210 |
211 | 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
212 |
213 | * 不可抢占(No preemption)
214 |
215 | 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
216 |
217 | * 请求和保持(Hold and wait)
218 |
219 | 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
220 |
221 | * 循环等待(Circular wait)
222 | 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
223 |
224 | 死锁避免——银行家算法
225 |
226 | 思想: 判断此次请求是否造成死锁若会造成死锁,则拒绝该请求
227 |
228 | ### 进程间通信
229 |
230 | 本地进程间通信的方式有很多,可以总结为下面四类:
231 |
232 | * 消息传递(管道、FIFO、消息队列)
233 | * 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
234 | * 共享内存(匿名的和具名的)
235 | * 远程过程调用(Solaris门和Sun RPC)
236 |
237 | ## 线程
238 |
239 | 多进程解决了前面提到的多任务问题。然而很多时候不同的程序需要共享同样的资源(文件,信号量等),如果全都使用进程的话会导致切换的成本很高,造成 CPU 资源的浪费。于是就出现了线程的概念。
240 |
241 | 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
242 |
243 | 线程具有以下属性:
244 |
245 | 1. 轻型实体
246 | 线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
247 |
248 | 2. 独立调度和分派的基本单位。
249 |
250 | 在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
251 |
252 | 3. 可并发执行。
253 | 在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
254 |
255 | 4. 共享进程资源。
256 | 在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
257 | 线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
258 |
259 | 使用 pthread 线程库实现的生产者-消费者模型:
260 |
261 | ``` c
262 | #include
263 | #include
264 |
265 | #include
266 | #define BUFFER_SIZE 10
267 |
268 | static int buffer[BUFFER_SIZE] = { 0 };
269 | static int count = 0;
270 |
271 | pthread_t consumer, producer;
272 | pthread_cond_t cond_producer, cond_consumer;
273 | pthread_mutex_t mutex;
274 |
275 | void* consume(void* _){
276 | while(1){
277 | pthread_mutex_lock(&mutex);
278 | while(count == 0){
279 | printf("empty buffer, wait producer\n");
280 | pthread_cond_wait(&cond_consumer, &mutex);
281 | }
282 |
283 | count--;
284 | printf("consume a item\n");
285 | pthread_mutex_unlock(&mutex);
286 | pthread_cond_signal(&cond_producer);
287 | //pthread_mutex_unlock(&mutex);
288 | }
289 | pthread_exit(0);
290 | }
291 |
292 | void* produce(void* _){
293 | while(1){
294 | pthread_mutex_lock(&mutex);
295 | while(count == BUFFER_SIZE){
296 | printf("full buffer, wait consumer\n");
297 | pthread_cond_wait(&cond_producer, &mutex);
298 | }
299 |
300 | count++;
301 | printf("produce a item.\n");
302 | pthread_mutex_unlock(&mutex);
303 | pthread_cond_signal(&cond_consumer);
304 | //pthread_mutex_unlock(&mutex);
305 | }
306 | pthread_exit(0);
307 | }
308 |
309 | int main() {
310 |
311 | pthread_mutex_init(&mutex, NULL);
312 | pthread_cond_init(&cond_consumer, NULL);
313 | pthread_cond_init(&cond_producer, NULL);
314 |
315 | int err = pthread_create(&consumer, NULL, consume, (void*)NULL);
316 | if(err != 0){
317 | printf("consumer thread created failed\n");
318 | exit(1);
319 | }
320 |
321 | err = pthread_create(&producer, NULL, produce, (void*)NULL);
322 | if(err != 0){
323 | printf("producer thread created failed\n");
324 | exit(1);
325 | }
326 |
327 | pthread_join(producer, NULL);
328 | pthread_join(consumer, NULL);
329 |
330 | //sleep(1000);
331 |
332 | pthread_cond_destroy(&cond_consumer);
333 | pthread_cond_destroy(&cond_producer);
334 | pthread_mutex_destroy(&mutex);
335 |
336 |
337 | return 0;
338 | }
339 | ```
340 |
341 | ## 锁
342 |
343 | 这里讨论的主要是多线程编程中需要使用的锁,网上有关于锁的文章实在是非常多而且乱套,让新手不知道从何下手。这里我们不去钻名词和概念的牛角尖,而是直接从本质上试图解释一下锁这个很常用的多线程编程工具。
344 |
345 | 锁要解决的是线程之间争取资源的问题,这个问题大概有下面几个角度:
346 |
347 | * 资源是否是独占(独占锁 - 共享锁)
348 | * 抢占不到资源怎么办(互斥锁 - 自旋锁)
349 | * 自己能不能重复抢(重入锁 - 不可重入锁)
350 | * 竞争读的情况比较多,读可不可以不加锁(读写锁)
351 |
352 | 上面这几个角度不是互相独立的,在实际场景中往往要它们结合起来,才能构造出一个合适的锁。
353 |
354 | ### 独占锁 - 共享锁
355 |
356 | 当一个共享资源只有一份的时候,通常我们使用独占锁,常见的即各个语言当中的 Mutex。当共享资源有多份时,可以使用前面提到的 Semaphere。
357 |
358 | ### 互斥锁 - 自旋锁
359 |
360 | 对于互斥锁来说,如果一个线程已经锁定了一个互斥锁,第二个线程又试图去获得这个互斥锁,则第二个线程将被挂起(即休眠,不占用 CPU 资源)。
361 |
362 | 在计算机系统中,频繁的挂起和切换线程,也是有成本的。自旋锁就是解决这个问题的。
363 |
364 | 自旋锁,指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
365 |
366 | 容易看出,当资源等待的时间较长,用互斥锁让线程休眠,会消耗更少的资源。当资源等待的时间较短时,使用自旋锁将减少线程的切换,获得更高的性能。
367 |
368 | 较新版本的 Java 中的 `synchornized` 和 .NET 中的 `lock`(`Monitor`) 的实现,是结合了两种锁的特点。简单说,它们在发现资源被抢占之后,会先试着自旋等待一段时间,如果等待时间太长,则会进入挂起状态。通过这样的实现,可以较大程度上挖掘出锁的性能。
369 |
370 | ### 重入锁 - 不可重入锁
371 |
372 | 可重入锁(ReetrantLock),也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。
373 |
374 | 使用可重入锁时,在同一线程中多次获取锁,不会导致死锁。使用不可重入锁,则会导致死锁发生。
375 |
376 | Java 中的 `synchornized` 和 .NET 中的 `lock`(`Monitor`) 都是可重入的。
377 |
378 | ### 读写锁
379 |
380 | 有些情况下,对于共享资源读竞争的情况远远多于写竞争,这种情况下,对读操作每次都进行加速,是得不偿失的。读写锁就是为了解决这个问题。
381 |
382 | 读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。简单可以总结为,读读不互斥,读写互斥,写写互斥。
383 |
384 | 对读写锁来说,有一个升级和降级的概念,即当前获得了读锁,想把当前的锁变成写锁,称为升级,反之称为降级。锁的升降级本身也是为了提升性能,通过改变当前锁的性质,避免重复获取锁。
385 |
386 | ## 协程
387 |
388 | 协程,又称微线程,纤程。英文名 Coroutine。
389 |
390 | 协程可以理解为用户级线程,协程和线程的区别是:线程是抢占式的调度,而协程是协同式的调度,协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。
391 |
392 | 使用协程(python)改写生产者-消费者问题:
393 |
394 | ```python
395 | import time
396 |
397 | def consumer():
398 | r = ''
399 | while True:
400 | n = yield r
401 | if not n:
402 | return
403 | print('[CONSUMER] Consuming %s...' % n)
404 | time.sleep(1)
405 | r = '200 OK'
406 |
407 | def produce(c):
408 | next(c) #python 3.x中使用next(c),python 2.x中使用c.next()
409 | n = 0
410 | while n < 5:
411 | n = n + 1
412 | print('[PRODUCER] Producing %s...' % n)
413 | r = c.send(n)
414 | print('[PRODUCER] Consumer return: %s' % r)
415 | c.close()
416 |
417 | if __name__=='__main__':
418 | c = consumer()
419 | produce(c)
420 | ```
421 |
422 |
423 | 可以看到,使用协程不再需要显式地对锁进行操作。
424 |
425 | ## IO多路复用
426 |
427 | ### 基本概念
428 |
429 | IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
430 |
431 | 1. 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
432 | 2. 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
433 | 3. 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
434 | 4. 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
435 | 5. 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
436 |
437 | 与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
438 |
439 | ### 常见的IO复用实现
440 |
441 | * select(Linux/Windows/BSD)
442 | * epoll(Linux)
443 | * kqueue(BSD/Mac OS X)
444 |
445 |
446 | ### 参考资料
447 |
448 | * [浅谈进程同步与互斥的概念](http://www.cnblogs.com/CareySon/archive/2012/04/14/Process-SynAndmutex.html)
449 | * [进程间同步——信号量](http://blog.csdn.net/feiyinzilgd/article/details/6765764)
450 | * [百度百科:线程](http://baike.baidu.com/view/1053.htm)
451 | * [进程、线程和协程的理解](http://blog.leiqin.info/2012/12/02/%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E7%9A%84%E7%90%86%E8%A7%A3.html)
452 | * [协程](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868328689835ecd883d910145dfa8227b539725e5ed000)
453 | * [IO多路复用之select总结](http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html)
454 | * [银行家算法](http://blog.csdn.net/yaopeng_2005/article/details/6935235)
455 |
--------------------------------------------------------------------------------
/source/basic/arch/Disk-And-File.md:
--------------------------------------------------------------------------------
1 | ## 磁盘调度
2 |
3 | 磁盘访问延迟 = 队列时间 + 控制器时间 + 寻道时间 + 旋转时间 + 传输时间
4 |
5 | 磁盘调度的目的是减小延迟,其中前两项可以忽略,寻道时间是主要矛盾。
6 |
7 | ### 磁盘调度算法
8 |
9 | * FCFS
10 |
11 | 先进先出的调度策略,这个策略具有公平的优点,因为每个请求都会得到处理,并且是按照接收到的顺序进行处理。
12 |
13 | * SSTF(Shortest-seek-time First 最短寻道时间优先)
14 |
15 | 选择使磁头从当前位置开始移动最少的磁盘I/O请求,所以 SSTF 总是选择导致最小寻道时间的请求。
16 |
17 | 总是选择最小寻找时间并不能保证平均寻找时间最小,但是能提供比 FCFS 算法更好的性能,会存在饥饿现象。
18 |
19 | * SCAN
20 |
21 | SSTF+中途不回折,每个请求都有处理机会。
22 |
23 | SCAN 要求磁头仅仅沿一个方向移动,并在途中满足所有未完成的请求,直到它到达这个方向上的最后一个磁道,或者在这个方向上没有其他请求为止。
24 |
25 | 由于磁头移动规律与电梯运行相似,SCAN 也被称为电梯算法。
26 |
27 | SCAN 算法对最近扫描过的区域不公平,因此,它在访问局部性方面不如 FCFS 算法和 SSTF 算法好。
28 |
29 | * C-SCAN
30 |
31 | SCAN+直接移到另一端,两端请求都能很快处理。
32 |
33 | 把扫描限定在一个方向,当访问到某个方向的最后一个磁道时,磁道返回磁盘相反方向磁道的末端,并再次开始扫描。
34 |
35 | 其中“C”是Circular(环)的意思。
36 |
37 | * LOOK 和 C-LOOK
38 |
39 | 釆用SCAN算法和C-SCAN算法时磁头总是严格地遵循从盘面的一端到另一端,显然,在实际使用时还可以改进,即磁头移动只需要到达最远端的一个请求即可返回,不需要到达磁盘端点。这种形式的SCAN算法和C-SCAN算法称为LOOK和C-LOOK调度。这是因为它们在朝一个给定方向移动前会查看是否有请求。
40 |
41 | ## 文件系统
42 |
43 | ### 分区表
44 |
45 | * MBR:支持最大卷为2 TB(Terabytes)并且每个磁盘最多有4个主分区(或3个主分区,1个扩展分区和无限制的逻辑驱动器)
46 | * GPT:支持最大卷为18EB(Exabytes)并且每磁盘的分区数没有上限,只受到操作系统限制(由于分区表本身需要占用一定空间,最初规划硬盘分区时,留给分区表的空间决定了最多可以有多少个分区,IA-64版Windows限制最多有128个分区,这也是EFI标准规定的分区表的最小尺寸。另外,GPT分区磁盘有备份分区表来提高分区数据结构的完整性。
47 |
48 | ### RAID 技术
49 |
50 | 磁盘阵列(Redundant Arrays of Independent Disks,RAID),独立冗余磁盘阵列之。原理是利用数组方式来作磁盘组,配合数据分散排列的设计,提升数据的安全性。
51 |
52 | * RAID 0
53 |
54 | RAID 0是最早出现的RAID模式,需要2块以上的硬盘,可以提高整个磁盘的性能和吞吐量。
55 |
56 | RAID 0没有提供冗余或错误修复能力,其中一块硬盘损坏,所有数据将遗失。
57 |
58 | * RAID 1
59 |
60 | RAID 1就是镜像,其原理为在主硬盘上存放数据的同时也在镜像硬盘上写一样的数据。
61 |
62 | 当主硬盘(物理)损坏时,镜像硬盘则代替主硬盘的工作。因为有镜像硬盘做数据备份,所以RAID 1的数据安全性在所有的RAID级别上来说是最好的。
63 |
64 | 但无论用多少磁盘做RAID 1,仅算一个磁盘的容量,是所有RAID中磁盘利用率最低的。
65 |
66 | * RAID 2
67 |
68 | 这是RAID 0的改良版,以汉明码(Hamming Code)的方式将数据进行编码后分区为独立的比特,并将数据分别写入硬盘中。因为在数据中加入了错误修正码(ECC,Error Correction Code),所以数据整体的容量会比原始数据大一些,RAID2最少要三台磁盘驱动器方能运作。
69 |
70 |
71 | * RAID 3
72 | 采用Bit-interleaving(数据交错存储)技术,它需要通过编码再将数据比特分割后分别存在硬盘中,而将同比特检查后单独存在一个硬盘中,但由于数据内的比特分散在不同的硬盘上,因此就算要读取一小段数据资料都可能需要所有的硬盘进行工作,所以这种规格比较适于读取大量数据时使用。
73 |
74 | * RAID 4
75 |
76 | 它与RAID 3不同的是它在分区时是以区块为单位分别存在硬盘中,但每次的数据访问都必须从同比特检查的那个硬盘中取出对应的同比特数据进行核对,由于过于频繁的使用,所以对硬盘的损耗可能会提高。(块交织技术,Block interleaving)
77 |
78 | **RAID 2/3/4 在实际应用中很少使用**
79 |
80 | * RAID 5
81 |
82 | RAID Level 5是一种储存性能、数据安全和存储成本兼顾的存储解决方案。它使用的是Disk Striping(硬盘分区)技术。
83 |
84 | RAID 5至少需要三块硬盘,RAID 5不是对存储的数据进行备份,而是把数据和相对应的奇偶校验信息存储到组成RAID5的各个磁盘上,并且奇偶校验信息和相对应的数据分别存储于不同的磁盘上。
85 |
86 | RAID 5 允许一块硬盘损坏。
87 |
88 | 实际容量 `Size = (N-1) * min(S1, S2, S3 ... SN)`
89 |
90 | * RAID 6
91 |
92 | 与RAID 5相比,RAID 6增加第二个独立的奇偶校验信息块。两个独立的奇偶系统使用不同的算法,数据的可靠性非常高,即使两块磁盘同时失效也不会影响数据的使用。
93 |
94 | RAID 6 至少需要4块硬盘。
95 |
96 | 实际容量 `Size = (N-2) * min(S1, S2, S3 ... SN)`
97 |
98 | * RAID 10/01(RAID 1+0,RAID 0+1)
99 |
100 | RAID 10是先镜射再分区数据,再将所有硬盘分为两组,视为是RAID 0的最低组合,然后将这两组各自视为RAID 1运作。
101 |
102 | RAID 01则是跟RAID 10的程序相反,是先分区再将数据镜射到两组硬盘。它将所有的硬盘分为两组,变成RAID 1的最低组合,而将两组硬盘各自视为RAID 0运作。
103 |
104 | 当RAID 10有一个硬盘受损,其余硬盘会继续运作。RAID 01只要有一个硬盘受损,同组RAID 0的所有硬盘都会停止运作,只剩下其他组的硬盘运作,可靠性较低。如果以六个硬盘建RAID 01,镜射再用三个建RAID 0,那么坏一个硬盘便会有三个硬盘脱机。因此,RAID 10远较RAID 01常用,零售主板绝大部份支持RAID 0/1/5/10,但不支持RAID 01。
105 |
106 | RAID 10 至少需要4块硬盘,且硬盘数量必须为偶数。
107 |
108 | ### 常见文件系统
109 |
110 | * Windows: FAT, FAT16, FAT32, NTFS
111 | * Linux: ext2/3/4, btrfs, ZFS
112 | * Mac OS X: HFS+
113 |
114 | ### Linux文件权限
115 |
116 | Linux文件采用10个标志位来表示文件权限,如下所示:
117 |
118 | -rw-r--r-- 1 skyline staff 20B 1 27 10:34 1.txt
119 | drwxr-xr-x 5 skyline staff 170B 12 23 19:01 ABTableViewCell
120 |
121 | 第一个字符一般用来区分文件和目录,其中:
122 |
123 | * d:表示是一个目录,事实上在ext2fs中,目录是一个特殊的文件。
124 | * -:表示这是一个普通的文件。
125 | * l: 表示这是一个符号链接文件,实际上它指向另一个文件。
126 | * b、c:分别表示区块设备和其他的外围设备,是特殊类型的文件。
127 | * s、p:这些文件关系到系统的数据结构和管道,通常很少见到。
128 |
129 | 第2~10个字符当中的每3个为一组,左边三个字符表示所有者权限,中间3个字符表示与所有者同一组的用户的权限,右边3个字符是其他用户的权限。
130 |
131 | 这三个一组共9个字符,代表的意义如下:
132 |
133 | * r(Read,读取):对文件而言,具有读取文件内容的权限;对目录来说,具有浏览目录的权限
134 | * w(Write,写入):对文件而言,具有新增、修改文件内容的权限;对目录来说,具有删除、移动目录内文件的权限。
135 | * x(eXecute,执行):对文件而言,具有执行文件的权限;对目录来说该用户具有进入目录的权限。
136 |
137 | 权限的掩码可以使用十进制数字表示:
138 |
139 | * 如果可读,权限是二进制的100,十进制是4;
140 | * 如果可写,权限是二进制的010,十进制是2;
141 | * 如果可运行,权限是二进制的001,十进制是1;
142 |
143 | * 具备多个权限,就把相应的 4、2、1 相加就可以了:
144 |
145 | 若要 rwx 则 4+2+1=7
146 | 若要 rw- 则 4+2=6
147 | 若要 r-x 则 4+1=5
148 | 若要 r-- 则 =4
149 | 若要 -wx 则 2+1=3
150 | 若要 -w- 则 =2
151 | 若要 --x 则 =1
152 | 若要 --- 则 =0
153 |
154 | 默认的权限可用umask命令修改,用法非常简单,只需执行umask 777命令,便代表屏蔽所有的权限,因而之后建立的文件或目录,其权限都变成000,
155 |
156 | 依次类推。通常root帐号搭配umask命令的数值为022、027和 077,普通用户则是采用002,这样所产生的权限依次为755、750、700、775。
157 |
158 | #### chmod命令
159 |
160 | chmod命令非常重要,用于改变文件或目录的访问权限。用户用它控制文件或目录的访问权限。
161 |
162 | 该命令有两种用法。一种是包含字母和操作符表达式的文字设定法;另一种是包含数字的数字设定法。
163 |
164 | 1. 文字设定法
165 |
166 | chmod [who] [+ | - | =] [mode] 文件名
167 |
168 | 命令中各选项的含义为:
169 |
170 | 操作对象who可是下述字母中的任一个或者它们的组合:
171 |
172 | * u 表示“用户(user)”,即文件或目录的所有者。
173 | * g 表示“同组(group)用户”,即与文件属主有相同组ID的所有用户。
174 | * o 表示“其他(others)用户”。
175 | * a 表示“所有(all)用户”。它是系统默认值。
176 |
177 | 操作符号可以是:
178 |
179 | * + 添加某个权限。
180 | * - 取消某个权限。
181 | * = 赋予给定权限并取消其他所有权限(如果有的话)。
182 |
183 |
184 | 设置mode所表示的权限可用下述字母的任意组合:
185 |
186 | * r 可读。
187 | * w 可写。
188 | * x 可执行。
189 | * X 只有目标文件对某些用户是可执行的或该目标文件是目录时才追加x 属性。
190 | * s 在文件执行时把进程的属主或组ID置为该文件的文件属主。方式“u+s”设置文件的用户ID位,“g+s”设置组ID位。
191 | * t 保存程序的文本到交换设备上。
192 | * u 与文件属主拥有一样的权限。
193 | * g 与和文件属主同组的用户拥有一样的权限。
194 | * o 与其他用户拥有一样的权限。
195 |
196 | 文件名:以空格分开的要改变权限的文件列表,支持通配符。
197 |
198 | 在一个命令行中可给出多个权限方式,其间用逗号隔开。例如:`chmod g+r,o+r example` 使同组和其他用户对文件example 有读权限。
199 |
200 | 2. 数字设定法
201 |
202 | 直接使用数字表示的权限来更改:
203 |
204 | 例: $ chmod 644 mm.txt
205 |
206 |
207 | #### chgrp命令
208 |
209 | 功能:改变文件或目录所属的组。
210 |
211 | 语法:chgrp [选项] group filename
212 |
213 | 例:$ chgrp - R book /opt/local /book
214 |
215 | 改变/opt/local /book/及其子目录下的所有文件的属组为book。
216 |
217 | #### chown命令
218 |
219 | 功能:更改某个文件或目录的属主和属组。这个命令也很常用。例如root用户把自己的一个文件拷贝给用户xu,为了让用户xu能够存取这个文件,root用户应该把这个文件的属主设为xu,否则,用户xu无法存取这个文件。
220 |
221 | 语法:chown [选项] 用户或组 文件
222 |
223 | 说明:chown将指定文件的拥有者改为指定的用户或组。用户可以是用户名或用户ID。组可以是组名或组ID。文件是以空格分开的要改变权限的文件列表,支持通配符。
224 |
225 | 例:把文件shiyan.c的所有者改为wang。
226 |
227 | chown wang shiyan.c
228 |
229 |
230 | ### 参考资料
231 |
232 | * [操作系统中的磁盘调度算法](http://www.educity.cn/windows/621734.html)
233 | * [百度百科:磁盘阵列](http://baike.baidu.com/view/63423.htm)
234 | * [维基百科:RAID](https://zh.wikipedia.org/wiki/RAID)
235 | * [Linux文件权限详解](http://blog.chinaunix.net/uid-25052030-id-174343.html)
236 | * [修改Linux文件权限命令:chmod](http://www.cnblogs.com/avril/archive/2010/03/23/1692809.html)
237 |
238 |
--------------------------------------------------------------------------------
/source/basic/arch/Memory-Management.md:
--------------------------------------------------------------------------------
1 | ## 内存管理基础
2 |
3 | ### 程序可执行文件的结构
4 |
5 | 一个程序的可执行文件在内存中的结果,从大的角度可以分为两个部分:只读部分和可读写部分。只读部分包括程序代码(.text)和程序中的常量(.rodata)。可读写部分(也就是变量)大致可以分成下面几个部分:
6 |
7 | * `.data`: 初始化了的全局变量和静态变量
8 | * `.bss`: 即 Block Started by Symbol, 未初始化的全局变量和静态变量(这个我感觉上课真的没讲过啊我去。。。)
9 | * `heap`: 堆,使用 malloc, realloc, 和 free 函数控制的变量,堆在所有的线程,共享库,和动态加载的模块中被共享使用
10 | * `stack`: 栈,函数调用时使用栈来保存函数现场,自动变量(即生命周期限制在某个 scope 的变量)也存放在栈中。
11 |
12 | 下面就各个分区具体解释一下:
13 |
14 | #### data 和 bss 区
15 |
16 | 这两个区经常放在一起说,因为他们都是用来存储全局变量和静态变量的,区别在于 data 区存放的是初始化过的, bss 区存放的是没有初始化过的,例如:
17 |
18 | ```c
19 | int val = 3;
20 | char string[] = "Hello World";
21 | ```
22 |
23 | 这两个变量的**值**会一开始被存储在 .text 中(因为值是写在代码里面的),在程序启动时会拷贝到 .data 去区中。
24 |
25 | 而不初始化的话,像下面这样:
26 |
27 | ```c
28 | static int i;
29 | ```
30 |
31 | 这个变量就会被放在 bss 区中。
32 |
33 | **答疑一** 静态变量和全局变量
34 |
35 | 这两个概念都是很常见的概念,又经常在一起使用,很容易造成混淆。
36 |
37 | `全局变量`:在一个代码文件(具体说应该一个 [translation unit/compilation unit](https://en.wikipedia.org/wiki/Translation_unit_(programming)))当中,一个变量要么定义在函数中,要么定义在在函数外面。当定义在函数外面时,这个变量就有了全局作用域,成为了全局变量。全局变量不光意味着这个变量可以在整个文件中使用,也意味着这个变量可以在其他文件中使用(这种叫做 [external linkage](https://en.wikipedia.org/wiki/External_linkage))。当有如下两个文件时;
38 |
39 | a.c
40 |
41 | ```c
42 | #include
43 |
44 | int a;
45 |
46 | int compute(void);
47 |
48 | int main()
49 | {
50 | a = 1;
51 | printf("%d %d\n", a, compute());
52 | return 0;
53 | }
54 | ```
55 |
56 | b.c
57 |
58 | ```c
59 | int a;
60 |
61 | int compute(void)
62 | {
63 | a = 0;
64 | return a;
65 | }
66 | ```
67 |
68 | 在 Link 过程中会产生重复定义错误,因为有两个全局的 `a` 变量,Linker 不知道应该使用哪一个。为了避免这种情况,就需要引入 static。
69 |
70 | `静态变量`: 指使用 static 关键字修饰的变量,static 关键字对变量的作用域进行了限制,具体的限制如下:
71 |
72 | * 在函数外定义:全局变量,但是只在当前文件中可见(叫做 internal linkage)
73 | * 在函数内定义:全局变量,但是只在此函数内可见(同时,在多次函数调用中,变量的值不会丢失)
74 | * (C++)在类中定义:全局变量,但是只在此类中可见
75 |
76 |
77 | 对于全局变量来说,为了避免上面提到的重复定义错误,我们可以在一个文件中使用 static,另一个不使用。这样使用 static 的就会使用自己的 `a` 变量,而没有用 static 的会使用全局的 `a` 变量。当然,最好两个都使用 static,避免更多可能的命名冲突。
78 |
79 | *注意*:'静态'这个中文翻译实在是有些莫名其妙,给人的感觉像是不可改变的,而实际上 static 跟不可改变没有关系,不可改变的变量使用 const 关键字修饰,注意不要混淆。
80 |
81 |
82 | *Bonus 部分 —— extern*: extern 是 C 语言中另一个关键字,用来指示变量或函数的定义在别的文件中,使用 extern 可以在多个源文件中共享某个变量,例如[这里](https://stackoverflow.com/questions/1433204/how-do-i-use-extern-to-share-variables-between-source-files-in-c)的例子。 extern 跟 static 在含义上是“水火不容”的,一个表示不能在别的地方用,一个表示要去别的地方找。如果同时使用的话,有两种情况,一种是先使用 static,后使用 extern ,即:
83 |
84 | ```c
85 | static int m;
86 | extern int m;
87 | ```
88 |
89 | 这种情况,后面的 m 实际上就是前面的 m 。如果反过来:
90 |
91 | ```c
92 | extern int m;
93 | static int m;
94 | ```
95 |
96 | 这种情况的行为是未定义的,编译器也会给出警告。
97 |
98 |
99 | **答疑二** 程序在内存和硬盘上不同的存在形式
100 |
101 | 这里我们提到的几个区,是指程序在内存中的存在形式。和程序在硬盘上存储的格式不是完全对应的。程序在硬盘上存储的格式更加复杂,而且是和操作系统有关的,具体可以参考[这里](https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats)。一个比较明显的例子可以帮你区分这个差别:之前我们提到过未定义的全局变量存储在 `.bss` 区,这个区域不会占用可执行文件的空间(一般只存储这个区域的长度),但是却会占用内存空间。这些变量没有定义,因此可执行文件中不需要存储(也不知道)它们的值,在程序启动过程中,它们的值会被初始化成 0 ,存储在内存中。
102 |
103 |
104 | ### 栈
105 |
106 | 栈是用于存放本地变量,内部临时变量以及有关上下文的内存区域。程序在调用函数时,操作系统会自动通过压栈和弹栈完成保存函数现场等操作,不需要程序员手动干预。
107 |
108 | 栈是一块连续的内存区域,栈顶的地址和栈的最大容量是系统预先规定好的。能从栈获得的空间较小。如果申请的空间超过栈的剩余空间时,例如递归深度过深,将提示stackoverflow。
109 |
110 | 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
111 |
112 | ### 堆
113 |
114 | 堆是用于存放除了栈里的东西之外所有其他东西的内存区域,当使用`malloc`和`free`时就是在操作堆中的内存。对于堆来说,释放工作由程序员控制,容易产生memory leak。
115 |
116 | 堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
117 |
118 | 对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,永远都不可能有一个内存块从栈中间弹出。
119 |
120 | 堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
121 |
122 | 计算机底层并没有对堆的支持,堆则是C/C++函数库提供的,同时由于上面提到的碎片问题,都会导致堆的效率比栈要低。
123 |
124 | ## 内存分配
125 |
126 | * 虚拟地址:用户编程时将代码(或数据)分成若干个段,每条代码或每个数据的地址由段名称 + 段内相对地址构成,这样的程序地址称为虚拟地址
127 | * 逻辑地址:虚拟地址中,段内相对地址部分称为逻辑地址
128 | * 物理地址:实际物理内存中所看到的存储地址称为物理地址
129 |
130 | * 逻辑地址空间:在实际应用中,将虚拟地址和逻辑地址经常不加区分,通称为逻辑地址。逻辑地址的集合称为逻辑地址空间
131 | * 线性地址空间:CPU地址总线可以访问的所有地址集合称为线性地址空间
132 | * 物理地址空间:实际存在的可访问的物理内存地址集合称为物理地址空间
133 |
134 | * MMU(Memery Management Unit内存管理单元):实现将用户程序的虚拟地址(逻辑地址) → 物理地址映射的CPU中的硬件电路
135 | * 基地址:在进行地址映射时,经常以段或页为单位并以其最小地址(即起始地址)为基值来进行计算
136 | * 偏移量:在以段或页为单位进行地址映射时,相对于基地址的地址值
137 |
138 | 虚拟地址先经过分段机制映射到线性地址,然后线性地址通过分页机制映射到物理地址。
139 |
140 | ## 虚拟内存
141 |
142 | * 请求调页,也称按需调页,即对不在内存中的“页”,当进程执行时要用时才调入,否则有可能到程序结束时也不会调入
143 |
144 | ### 页面置换算法
145 |
146 | * FIFO算法
147 |
148 | 先入先出,即淘汰最早调入的页面。
149 |
150 | * OPT(MIN)算法
151 |
152 | 选未来最远将使用的页淘汰,是一种最优的方案,可以证明缺页数最小。
153 |
154 | 可惜,MIN需要知道将来发生的事,只能在理论中存在,实际不可应用。
155 |
156 | * LRU(Least-Recently-Used)算法
157 |
158 | 用过去的历史预测将来,选最近最长时间没有使用的页淘汰(也称最近最少使用)。
159 |
160 | LRU准确实现:计数器法,页码栈法。
161 |
162 | 由于代价较高,通常不使用准确实现,而是采用近似实现,例如Clock算法。
163 |
164 | **内存抖动现象**:页面的频繁更换,导致整个系统效率急剧下降,这个现象称为内存抖动(或颠簸)。抖动一般是内存分配算法不好,内存太小引或者程序的算法不佳引起的。
165 |
166 | **Belady现象**:对有的页面置换算法,页错误率可能会随着分配帧数增加而增加。
167 |
168 | FIFO会产生Belady异常。
169 |
170 | 栈式算法无Belady异常,LRU,LFU(最不经常使用),OPT都属于栈式算法。
171 |
172 |
173 | #### 参考资料
174 |
175 | * https://en.wikipedia.org/wiki/Data_segment
176 | * https://stackoverflow.com/questions/12798486/bss-segment-in-c/12799389#12799389
177 | * https://stackoverflow.com/questions/7837190/c-c-global-vs-static-global
178 | * https://stackoverflow.com/questions/572547/what-does-static-mean-in-a-c-program/572550#572550
--------------------------------------------------------------------------------
/source/basic/arch/OS.md:
--------------------------------------------------------------------------------
1 | ## 操作系统提供的服务
2 |
3 | 操作系统的五大功能,分别为:作业管理、文件管理、存储管理、输入输出设备管理、进程及处理机管理
4 |
5 | ## 中断与系统调用
6 |
7 | ### 中断
8 |
9 | 所谓的中断就是在计算机执行程序的过程中,由于出现了某些特殊事情,使得CPU暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。中断一般分为三类:
10 |
11 | 1. 由计算机硬件异常或故障引起的中断,称为内部异常中断;
12 | 2. 由程序中执行了引起中断的指令而造成的中断,称为软中断(这也是和我们将要说明的系统调用相关的中断);
13 | 3. 由外部设备请求引起的中断,称为外部中断。简单来说,对中断的理解就是对一些特殊事情的处理。
14 |
15 | 与中断紧密相连的一个概念就是中断处理程序了。当中断发生的时候,系统需要去对中断进行处理,对这些中断的处理是由操作系统内核中的特定函数进行的,这些处理中断的特定的函数就是我们所说的中断处理程序了。
16 |
17 | 另一个与中断紧密相连的概念就是中断的优先级。中断的优先级说明的是当一个中断正在被处理的时候,处理器能接受的中断的级别。中断的优先级也表明了中断需要被处理的紧急程度。每个中断都有一个对应的优先级,当处理器在处理某一中断的时候,只有比这个中断优先级高的中断可以被处理器接受并且被处理。优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略。
18 |
19 | 典型的中断优先级如下所示:
20 |
21 | 机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断
22 |
23 | 当发生软件中断时,其他所有的中断都可能发生并被处理;但当发生磁盘中断时,就只有时钟中断和机器错误中断能被处理了。
24 |
25 | ##### 系统调用
26 |
27 | 在讲系统调用之前,先说下进程的执行在系统上的两个级别:用户级和核心级,也称为用户态和系统态(user mode and kernel mode)。
28 |
29 | 程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如说打开某一设备、创建文件、读写文件等,就需要向操作系统发出调用服务的请求,这就是系统调用。
30 |
31 | Linux系统有专门的函数库来提供这些请求操作系统服务的入口,这个函数库中包含了操作系统所提供的对外服务的接口。当进程发出系统调用之后,它所处的运行状态就会由用户态变成核心态。但这个时候,进程本身其实并没有做什么事情,这个时候是由内核在做相应的操作,去完成进程所提出的这些请求。
32 |
33 | 系统调用和中断的关系就在于,当进程发出系统调用申请的时候,会产生一个软件中断。产生这个软件中断以后,系统会去对这个软中断进行处理,这个时候进程就处于核心态了。
34 |
35 | 那么用户态和核心态之间的区别是什么呢?(以下区别摘至《UNIX操作系统设计》)
36 |
37 | 1. 用户态的进程能存取它们自己的指令和数据,但不能存取内核指令和数据(或其他进程的指令和数据)。然而,核心态下的进程能够存取内核和用户地址
38 | 2. 某些机器指令是特权指令,在用户态下执行特权指令会引起错误
39 |
40 | 对此要理解的一个是,在系统中内核并不是作为一个与用户进程平行的估计的进程的集合,内核是为用户进程运行的。
41 |
42 |
43 | ### 参考资料
44 |
45 | * [Linux系统的中断、系统调用于调度概述](http://www.linuxidc.com/Linux/2012-11/74486.htm)
46 |
--------------------------------------------------------------------------------
/source/basic/arch/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/basic/arch/README.md
--------------------------------------------------------------------------------
/source/basic/compiler/Compiler-Arch.md:
--------------------------------------------------------------------------------
1 | ## 词法分析器
2 |
3 | ## 语法分析器
4 |
5 | ## 语义分析及中间代码生成
6 |
7 | ## 代码优化
8 |
9 | ## 代码生成
--------------------------------------------------------------------------------
/source/basic/compiler/README.md:
--------------------------------------------------------------------------------
1 | 欢迎补充内容
2 |
--------------------------------------------------------------------------------
/source/basic/design/GOP.md:
--------------------------------------------------------------------------------
1 | # 设计模式
2 | ## 简介
3 |
4 | 在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
5 | 四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。
6 | - 对接口编程而不是对实现编程。
7 | - 优先使用对象组合而不是继承。
8 |
9 | ## 设计模式六大原则
10 |
11 | - 单一职责原则:即一个类应该只负责一项职责
12 | - 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
13 | - 依赖倒转原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
14 | - 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
15 | - 迪米特法则:一个对象应该对其他对象保持最少的了解
16 | - 开闭原则:对扩展开放,对修改关闭
17 |
18 | ## 设计模式归纳
19 |
20 | 
21 |
22 | ## 参考:
23 |
24 | - [设计模式六大原则](https://www.cnblogs.com/shijingjing07/p/6227728.html)
25 | - [23中设计模式](https://www.cnblogs.com/pony1223/p/7608955.html)
26 |
--------------------------------------------------------------------------------
/source/basic/design/MVC.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/basic/design/MVC.md
--------------------------------------------------------------------------------
/source/basic/design/OO-Basic.md:
--------------------------------------------------------------------------------
1 | ### 面向对象的基本特征
2 |
3 | **面向对象的三个基本特征是:封装、继承、多态**
4 |
5 | * 封装
6 |
7 | 封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
8 |
9 | * 继承
10 |
11 | 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
12 |
13 | 要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
14 |
15 | * 多态性
16 |
17 | 多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
18 |
19 | 实现多态,有两种方式,覆盖和重载。覆盖和重载的区别在于,覆盖在运行时决定,重载是在编译时决定。并且覆盖和重载的机制不同,例如在 Java 中,重载方法的签名必须不同于原先方法的,但对于覆盖签名必须相同。
20 |
21 | ### 参考资料
22 |
23 | 1. [面向对象的三个基本特征](http://blog.csdn.net/wfwd/article/details/763753)
--------------------------------------------------------------------------------
/source/basic/design/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/basic/design/README.md
--------------------------------------------------------------------------------
/source/basic/network/HTTPS.md:
--------------------------------------------------------------------------------
1 | ## HTTPS 基本过程
2 |
3 | HTTPS 即 HTTP over TLS,是一种在加密信道进行 HTTP 内容传输的协议。
4 |
5 | > TLS 的早期版本叫做 SSL。SSL 的 1.0, 2.0, 3.0 版本均已经被废弃,出于安全问题考虑广大浏览器也不再对老旧的 SSL 版本进行支持了,因此这里我们就统一使用 TLS 名称了。
6 |
7 | TLS 的基本过程如下(取自 [what-happens-when-zh_CN](https://github.com/skyline75489/what-happens-when-zh_CN#tls)):
8 |
9 | * 客户端发送一个 ``ClientHello`` 消息到服务器端,消息中同时包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和压缩算法。
10 | * 服务器端向客户端返回一个 ``ServerHello`` 消息,消息中包含了服务器端的 TLS 版本,服务器所选择的加密和压缩算法,以及数字证书认证机构(Certificate Authority,缩写 CA)签发的服务器公开证书,证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程,直到协商生成一个新的对称密钥。证书中还包含了该证书所应用的域名范围(Common Name,简称 CN),用于客户端验证身份。
11 | * 客户端根据自己的信任 CA 列表,验证服务器端的证书是否可信。如果认为可信(具体的验证过程在下一节讲解),客户端会生成一串伪随机数,使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥
12 | * 服务器端使用自己的私钥解密上面提到的随机数,然后使用这串随机数生成自己的对称主密钥
13 | * 客户端发送一个 ``Finished`` 消息给服务器端,使用对称密钥加密这次通讯的一个散列值
14 | * 服务器端生成自己的 hash 值,然后解密客户端发送来的信息,检查这两个值是否对应。如果对应,就向客户端发送一个 ``Finished`` 消息,也使用协商好的对称密钥加密
15 | * 从现在开始,接下来整个 TLS 会话都使用对称秘钥进行加密,传输应用层(HTTP)内容
16 |
17 | 从上面的过程可以看到,TLS 的完整过程需要三个算法(协议),密钥交互算法,对称加密算法,和消息认证算法(TLS 的传输会使用 MAC(message authentication code) 进行完整性检查)。
18 |
19 | 我们以 Github 网站使用的 TLS 为例,使用浏览器可以看到它使用的加密为 `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`。其中密钥交互算法是 `ECDHE_RSA`,对称加密算法是 `AES_128_GCM`,消息认证(MAC)算法为 `SHA256`。
20 |
21 | ## TLS 证书机制
22 |
23 | HTTPS 过程中很重要的一个步骤,是服务器需要有 CA 颁发的证书,客户端根据自己的信任 CA 列表验证服务器的身份。现代浏览器中,证书验证的过程依赖于证书信任链。
24 |
25 | 所谓证书信任链,即一个证书要依靠上一级证书来证明自己是可信的,最顶层的证书被称为根证书,拥有根证书的机构被称为根 CA。
26 |
27 | 还是以 Github 为例,在浏览器中我们可以看到它的证书信任链如下:
28 |
29 | `DigiCert High Assurance EV Root CA` -> `DigiCert SHA2 Extended Validation Server CA` -> `Github.com`
30 |
31 | 从上到下即 Root CA -> 二级 CA -> 网站。
32 |
33 | 前面提到,证书当中包括 CN(Common Name),浏览器在验证证书的同时,也会验证 CN 的正确性。即不光需要验证“这是一个合法的证书”,还需要验证“这是一个用于 Github.com 的证书”。
34 |
35 | 既然所有的信任,最终要落到根 CA 上,根证书本身又是怎么获得的呢?答案也很简单,根证书一般是操作系统自带的。不管是桌面系统 Windows,macOS 还是移动端系统 Android, iOS 都会内置一系列根证书。随着操作系统本身的升级,根证书也会随着升级进行更新。
36 |
37 | 对浏览器而已,浏览器当然也有选择信任某个根证书的权利。Chrome 浏览器一般是跟随系统根证书信任的。Firefox 浏览器通常是使用自带的一套证书信任机制,不受系统证书的影响。
38 |
39 | 在使用 `curl` 等工具时,我们还可以自行选择证书进行信任。
40 |
41 | > 有权威的信任,最终都要落到一个单点信任,不管是 Root CA,还是微软,苹果,谷歌等操作系统厂商。
42 |
43 | ## 中间人攻击
44 |
45 | HTTPS 的过程并不是密不透风的,HTTPS 有若干漏洞,给中间人攻击(Man In The Middle Attack,简称 MITM)提供了可能。
46 |
47 | 所谓中间人攻击,指攻击者与通讯的两端分别建立独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。
48 |
49 | ### SSL 剥离
50 |
51 | SSL 剥离即阻止用户使用 HTTPS 访问网站。由于并不是所有网站都只支持 HTTPS,大部分网站会同时支持 HTTP 和 HTTPS 两种协议。用户在访问网站时,也可能会在地址栏中输入 `http://` 的地址,第一次的访问完全是明文的,这就给了攻击者可乘之机。通过攻击 DNS 响应,攻击者可以将自己变成中间人。
52 |
53 | > DNS 作为基于 UDP 的协议是相当不安全的,为了保证 DNS 的安全可以使用 DNS over TCP 等机制,这里不赘述了。
54 |
55 | ### HSTS
56 |
57 | 为了防止上面说的这种情况,一种叫做 HSTS 的技术被引入了。HSTS(HTTP Strict Transport Security)是用于强制浏览器使用 HTTPS 访问网站的一种机制。它的基本机制是在服务器返回的响应中,加上一个特殊的头部,指示浏览器对于此网站,强制使用 HTTPS 进行访问:
58 |
59 | ```plaintext
60 | Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
61 | ```
62 |
63 | 可以看到如果这个过期时间非常长,就是导致在很长一段时间内,浏览器都会强制使用 HTTPS 访问该网站。
64 |
65 | HSTS 有一个很明显的缺点,是需要等待第一个服务器的影响中的头部才能生效,但如果第一次访问该网站就被攻击呢?为了解决这个问题,浏览器中会带上一些网站的域名,被称为 HSTS preload list。对于在这个 list 的网站来说,直接强制使用 HTTPS。
66 |
67 | ### 伪造证书攻击
68 |
69 | HSTS 只解决了 SSL 剥离的问题,然而即使在全程使用 HTTPS 的情况下,我们仍然有可能被监听。
70 |
71 | 假设我们想访问 `www.google.com`,但我们的 DNS 服务器被攻击了,指向的 IP 地址并非 Google 的服务器,而是攻击者的 IP。当攻击者的服务器也有合法的证书的时候,我们的浏览器就会认为对方是 Google 服务器,从而信任对方。这样,攻击者便可以监听我们和谷歌之前的所有通信了。
72 |
73 | 可以看到攻击者有两步需要操作,第一步是需要攻击 DNS 服务器。第二步是攻击者自己的证书需要被用户信任,这一步对于用户来说是很难控制的,需要证书颁发机构能够控制自己不滥发证书。
74 |
75 | > 2015 年 Google 称发现赛门铁克旗下的 Thawte 未经同意签发了众多域名的数千个证书,其中包括 Google 旗下的域名和不存在的域名。当年 12 月,Google 发布公告称 Chrome、Android 及其他 Google 产品将不再信任赛门铁克旗下的"Class 3 Public Primary CA"根证书。
76 |
77 | > 2016 年 Mozilla 发现沃通 CA 存在严重的信任问题,例如偷签 `github.com` 的证书,故意倒填证书日期绕过浏览器对 SHA-1 证书的限制等,将停止信任 WoSign 和 StartCom 签发的新证书。
78 |
79 | ### HPKP
80 |
81 | HPKP 技术是为了解决伪造证书攻击而诞生的。
82 |
83 | HPKP(Public Key Pinning Extension for HTTP)在 HSTS 上更进一步,HPKP 直接在返回头中存储服务器的公钥指纹信息,一旦发现指纹和实际接受到的公钥有差异,浏览器就可以认为正在被攻击:
84 |
85 | ```plaintext
86 | Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubDomains][; report-uri="reportURI"]
87 | ```
88 |
89 | 和 HSTS 类似,HPKP 也依赖于服务器的头部返回,不能解决第一次访问的问题,浏览器本身也会内置一些 HPKP 列表。
90 |
91 | > HPKP 技术仍然不能阻止第一次访问的攻击问题,部署和配置 HPKP 相当繁琐,一旦网站配置错误,就会导致网站证书验证失败,且在过期时间内无法有效恢复。HPKP 的机制也引来了一些安全性问题。Chrome 67 中废除了对 HPKP 的支持,在 Chrome 72 中 HPKP 被彻底移除。
92 |
93 |
--------------------------------------------------------------------------------
/source/basic/network/IP.md:
--------------------------------------------------------------------------------
1 | ## IP 协议简介
2 |
3 | IP 协议位于 TCP/IP 协议的第三层——网络层。与传输层协议相比,网络层的责任是提供点到点(hop by hop)的服务,而传输层(TCP/UDP)则提供端到端(end to end)的服务。
4 |
5 | ## IP 地址的分类
6 |
7 | ### A类地址
8 |
9 | ### B类地址
10 |
11 | ### C类地址
12 |
13 | ### D 类地址
14 |
15 | ## 广播与多播
16 |
17 | 广播和多播仅用于UDP(TCP是面向连接的)。
18 |
19 | * 广播
20 |
21 | 一共有四种广播地址:
22 |
23 | 1. 受限的广播
24 |
25 | 受限的广播地址为255.255.255.255。该地址用于主机配置过程中IP数据报的目的地址,在任何情况下,router不转发目的地址为255.255.255.255的数据报,这样的数据报仅出现在本地网络中。
26 |
27 | 2. 指向网络的广播
28 |
29 | 指向网络的广播地址是主机号为全1的地址。A类网络广播地址为netid.255.255.255,其中netid为A类网络的网络号。
30 |
31 | 一个router必须转发指向网络的广播,但它也必须有一个不进行转发的选择。
32 |
33 | 3. 指向子网的广播
34 |
35 | 指向子网的广播地址为主机号为全1且有特定子网号的地址。作为子网直接广播地址的IP地址需要了解子网的掩码。例如,router收到128.1.2.255的数据报,当B类网路128.1的子网掩码为255.255.255.0时,该地址就是指向子网的广播地址;但是如果子网掩码为255.255.254.0,该地址就不是指向子网的广播地址。
36 |
37 | 4. 指向所有子网的广播
38 |
39 | 指向所有子网的广播也需要了解目的网络的子网掩码,以便与指向网络的广播地址区分开来。指向所有子网的广播地址的子网号和主机号为全1.例如,如果子网掩码为255.255.255.0,那么128.1.255.255就是一个指向所有子网的广播地址。
40 |
41 | 当前的看法是这种广播是陈旧过时的,更好的方式是使用多播而不是对所有子网的广播。
42 |
43 | 广播示例:
44 |
45 | ```
46 | PING 192.168.0.255 (192.168.0.255): 56 data bytes
47 | 64 bytes from 192.168.0.107: icmp_seq=0 ttl=64 time=0.199 ms
48 | 64 bytes from 192.168.0.106: icmp_seq=0 ttl=64 time=45.357 ms
49 | 64 bytes from 192.168.0.107: icmp_seq=1 ttl=64 time=0.203 ms
50 | 64 bytes from 192.168.0.106: icmp_seq=1 ttl=64 time=269.475 ms
51 | 64 bytes from 192.168.0.107: icmp_seq=2 ttl=64 time=0.102 ms
52 | 64 bytes from 192.168.0.106: icmp_seq=2 ttl=64 time=189.881 ms
53 | ```
54 |
55 | 可以看到的确收到了来自两个主机的答复,其中 192.168.0.107 是本机地址。
56 |
57 | * 多播
58 |
59 | 多播又叫组播,使用D类地址,D类地址分配的28bit均用作多播组号而不再表示其他。
60 |
61 | 多播组地址包括1110的最高4bit和多播组号。它们通常可以表示为点分十进制数,范围从224.0.0.0到239.255.255.255。
62 |
63 | 多播的出现减少了对应用不感兴趣主机的处理负荷。
64 |
65 | 多播的特点:
66 |
67 | * 允许一个或多个发送者(组播源)发送单一的数据包到多个接收者(一次的,同时的)的网络技术
68 | * 可以大大的节省网络带宽,因为无论有多少个目标地址,在整个网络的任何一条链路上只传送单一的数据包
69 | * 多播技术的核心就是针对如何节约网络资源的前提下保证服务质量。
70 |
71 | 多播示例:
72 |
73 | ```
74 | PING 224.0.0.1 (224.0.0.1): 56 data bytes
75 | 64 bytes from 192.168.0.107: icmp_seq=0 ttl=64 time=0.081 ms
76 | 64 bytes from 192.168.0.106: icmp_seq=0 ttl=64 time=123.081 ms
77 | 64 bytes from 192.168.0.107: icmp_seq=1 ttl=64 time=0.122 ms
78 | 64 bytes from 192.168.0.106: icmp_seq=1 ttl=64 time=67.312 ms
79 | 64 bytes from 192.168.0.107: icmp_seq=2 ttl=64 time=0.132 ms
80 | 64 bytes from 192.168.0.106: icmp_seq=2 ttl=64 time=447.073 ms
81 | 64 bytes from 192.168.0.107: icmp_seq=3 ttl=64 time=0.132 ms
82 | 64 bytes from 192.168.0.106: icmp_seq=3 ttl=64 time=188.800 ms
83 | ```
84 |
85 | ## BGP
86 |
87 | * 边界网关协议(BGP)是运行于 TCP 上的一种自治系统的路由协议
88 |
89 | * BGP 是唯一一个用来处理像因特网大小的网络的协议,也是唯一能够妥善处理好不相关路由域间的多路连接的协议
90 |
91 | * BGP是一种外部网关协议(Exterior Gateway Protocol,EGP),与OSPF、RIP等内部网关协议(Interior Gateway Protocol,IGP)不同,BGP不在于发现和计算路由,而在于控制路由的传播和选择最佳路由
92 |
93 | * BGP使用TCP作为其传输层协议(端口号179),提高了协议的可靠性
94 |
95 | * BGP既不是纯粹的矢量距离协议,也不是纯粹的链路状态协议
96 |
97 | * BGP支持CIDR(Classless Inter-Domain Routing,无类别域间路由)
98 |
99 | * 路由更新时,BGP只发送更新的路由,大大减少了BGP传播路由所占用的带宽,适用于在Internet上传播大量的路由信息
100 |
101 | * BGP路由通过携带AS路径信息彻底解决路由环路问题
102 |
103 | * BGP提供了丰富的路由策略,能够对路由实现灵活的过滤和选择
104 |
105 | * BGP易于扩展,能够适应网络新的发展
106 |
107 |
108 | ### 参考资料
109 |
110 | * [多播与广播](http://www.cnblogs.com/Torres_fans/archive/2011/03/21/1990377.html)
111 | * [TCP_IP:广播和多播](http://blog.sina.com.cn/s/blog_ac9fdc0b0101pw7w.html)
112 | * [百度百科:BGP](http://baike.baidu.com/view/875886.htm?fromtitle=bgp&fromid=91408&type=syn)
113 |
--------------------------------------------------------------------------------
/source/basic/network/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/basic/network/README.md
--------------------------------------------------------------------------------
/source/basic/network/Socket-Programming-Basic.md:
--------------------------------------------------------------------------------
1 | ## Socket 基本概念
2 |
3 | Socket 是对 TCP/IP 协议族的一种封装,是应用层与TCP/IP协议族通信的中间软件抽象层。从设计模式的角度看来,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
4 |
5 | Socket 还可以认为是一种网络间不同计算机上的进程通信的一种方法,利用三元组(ip地址,协议,端口)就可以唯一标识网络中的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。
6 |
7 |
8 | Socket 起源于 Unix ,Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开(open) –> 读写(write/read) –> 关闭(close)”模式来进行操作。因此 Socket 也被处理为一种特殊的文件。
9 |
10 | ## 写一个简易的 WebServer
11 |
12 | 一个简易的 Server 的流程如下:
13 |
14 | - 1.建立连接,接受一个客户端连接。
15 | - 2.接受请求,从网络中读取一条 HTTP 请求报文。
16 | - 3.处理请求,访问资源。
17 | - 4.构建响应,创建带有 header 的 HTTP 响应报文。
18 | - 5.发送响应,传给客户端。
19 |
20 | 省略流程 3,大体的程序与调用的函数逻辑如下:
21 |
22 | - socket() 创建套接字
23 | - bind() 分配套接字地址
24 | - listen() 等待连接请求
25 | - accept() 允许连接请求
26 | - read()/write() 数据交换
27 | - close() 关闭连接
28 |
29 | 代码如下:
30 |
31 | ```cpp
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 |
42 | using namespace std;
43 |
44 | const int port = 9090;
45 | const int buffer_size = 1<<20;
46 | const int method_size = 1<<10;
47 | const int filename_size = 1<<10;
48 | const int common_buffer_size = 1<<10;
49 |
50 | void handleError(const string &message);
51 | void requestHandling(int *sock);
52 | void sendError(int *sock);
53 | void sendData(int *sock, char *filename);
54 | void sendHTML(int *sock, char *filename);
55 | void sendJPG(int *sock, char *filename);
56 |
57 | int main()
58 | {
59 | int server_sock;
60 | int client_sock;
61 |
62 | struct sockaddr_in server_address;
63 | struct sockaddr_in client_address;
64 |
65 | socklen_t client_address_size;
66 |
67 | server_sock = socket(PF_INET, SOCK_STREAM, 0);
68 |
69 | if (server_sock == -1)
70 | {
71 | handleError("socket error");
72 | }
73 |
74 | memset(&server_address,0,sizeof(server_address));
75 | server_address.sin_family = AF_INET;
76 | server_address.sin_addr.s_addr = htonl(INADDR_ANY);
77 | server_address.sin_port = htons(port);
78 |
79 | if(bind(server_sock,(struct sockaddr*)&server_address, sizeof(server_address)) == -1){
80 | handleError("bind error");
81 | }
82 |
83 | if(listen(server_sock, 5) == -1) {
84 | handleError("listen error");
85 | }
86 |
87 | while(true) {
88 | client_address_size = sizeof(client_address);
89 | client_sock = accept(server_sock, (struct sockaddr*) &client_address, &client_address_size);
90 |
91 | if (client_sock == -1) {
92 | handleError("accept error");
93 | }
94 | requestHandling(&client_sock);
95 | }
96 |
97 | //system("open http://127.0.0.1:9090/index.html");
98 | close(server_sock);
99 |
100 | return 0;
101 | }
102 |
103 | void requestHandling(int *sock){
104 | int client_sock = *sock;
105 | char buffer[buffer_size];
106 | char method[method_size];
107 | char filename[filename_size];
108 |
109 | read(client_sock, buffer, sizeof(buffer)-1);
110 |
111 | if(!strstr(buffer, "HTTP/")) {
112 | sendError(sock);
113 | close(client_sock);
114 | return;
115 | }
116 |
117 | strcpy(method, strtok(buffer," /"));
118 | strcpy(filename, strtok(NULL, " /"));
119 |
120 | if(0 != strcmp(method, "GET")) {
121 | sendError(sock);
122 | close(client_sock);
123 | return;
124 | }
125 |
126 | sendData(sock, filename);
127 | }
128 |
129 | void sendData(int *sock, char *filename) {
130 | int client_sock = *sock;
131 | char buffer[common_buffer_size];
132 | char type[common_buffer_size];
133 |
134 | strcpy(buffer, filename);
135 |
136 | strtok(buffer, ".");
137 | strcpy(type, strtok(NULL, "."));
138 |
139 | if(0 == strcmp(type, "html")){
140 | sendHTML(sock, filename);
141 | }else if(0 == strcmp(type, "jpg")){
142 | sendJPG(sock, filename);
143 | }else{
144 | sendError(sock);
145 | close(client_sock);
146 | return ;
147 | }
148 | }
149 |
150 | void sendHTML(int *sock, char *filename) {
151 | int client_sock = *sock;
152 | char buffer[buffer_size];
153 | FILE *fp;
154 |
155 | char status[] = "HTTP/1.0 200 OK\r\n";
156 | char header[] = "Server: A Simple Web Server\r\nContent-Type: text/html\r\n\r\n";
157 |
158 | write(client_sock, status, strlen(status));
159 | write(client_sock, header, strlen(header));
160 |
161 | fp = fopen(filename, "r");
162 | if(!fp){
163 | sendError(sock);
164 | close(client_sock);
165 | handleError("failed to open file");
166 | return ;
167 | }
168 |
169 | fgets(buffer,sizeof(buffer), fp);
170 | while(!feof(fp)) {
171 | write(client_sock, buffer, strlen(buffer));
172 | fgets(buffer, sizeof(buffer), fp);
173 | }
174 |
175 | fclose(fp);
176 | close(client_sock);
177 | }
178 |
179 | void sendJPG(int *sock, char *filename) {
180 | int client_sock = *sock;
181 | char buffer[buffer_size];
182 | FILE *fp;
183 | FILE *fw;
184 |
185 | char status[] = "HTTP/1.0 200 OK\r\n";
186 | char header[] = "Server: A Simple Web Server\r\nContent-Type: image/jpeg\r\n\r\n";
187 |
188 | write(client_sock, status, strlen(status));
189 | write(client_sock, header, strlen(header));
190 |
191 | fp = fopen(filename, "rb");
192 | if(NULL == fp){
193 | sendError(sock);
194 | close(client_sock);
195 | handleError("failed to open file");
196 | return ;
197 | }
198 |
199 | fw = fdopen(client_sock, "w");
200 | fread(buffer, 1, sizeof(buffer), fp);
201 | while (!feof(fp)){
202 | fwrite(buffer, 1, sizeof(buffer), fw);
203 | fread(buffer, 1, sizeof(buffer), fp);
204 | }
205 |
206 | fclose(fw);
207 | fclose(fp);
208 | close(client_sock);
209 | }
210 |
211 | void handleError(const string &message) {
212 | cout<Bad Request400 Bad Request