├── .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 | [![Build Status](https://travis-ci.org/HIT-Alibaba/interview.svg?branch=master)](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 | ![安卓Activity生命周期](https://github.com/HIT-Alibaba/interview/blob/master/img/android-activity-lifecircle.jpg?raw=true) 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 | ![Service生命周期](https://github.com/HIT-Alibaba/interview/blob/master/img/android-service-lifecircle.png?raw=true) 51 | 52 | 回调方法的结构下图解释的很明白: 53 | 54 | ![Service回调方法](https://github.com/HIT-Alibaba/interview/blob/master/img/android-service-callback.png?raw=true) 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 | ![每一帧是一张png例图](https://github.com/HIT-Alibaba/interview/blob/master/img/android-animation-eachpng.jpg?raw=true) 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 | ![所有动画放在一张png中](https://github.com/HIT-Alibaba/interview/blob/master/img/android-animation-onepng.jpg?raw=true) 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 | ![动画demo](https://github.com/HIT-Alibaba/interview/blob/master/img/android-animation-demo.gif?raw=true) -------------------------------------------------------------------------------- /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 | ![Android System Architecture](https://github.com/HIT-Alibaba/interview/blob/master/img/android-system-architecture.jpg?raw=true) 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 | ![standard启动模式](https://github.com/HIT-Alibaba/interview/blob/master/img/android-lanchmode-standard.gif?raw=true) 16 | 17 | 什么配置都不写的话就是这种启动模式。但是每次都新建一个实例的话真是过于浪费,为了优化应该尽量考虑余下三种方式。 18 | 19 | ####singleTop 20 | 每次扫描栈顶,如果在任务栈顶发现了相同的实例则重用,否则新建并压入栈顶。 21 | 22 | ![singleTop](https://github.com/HIT-Alibaba/interview/blob/master/img/android-lanchmode-singletop.gif?raw=true) 23 | 24 | 配制方法实在Mainifest.xml中进行: 25 | 26 | ``` 27 | 31 | 32 | ``` 33 | 34 | ####singleTask 35 | 与singleTop的区别是singleTask会扫描整个任务栈并制定策略。上效果图: 36 | 37 | ![singleTask](https://github.com/HIT-Alibaba/interview/blob/master/img/android-lanchmode-singletask.gif?raw=true) 38 | 39 | 使用时需要小心因为会将之前入栈的实例之上的实例全部移除,需要格外小心逻辑。 40 | 41 | 配制方法: 42 | ``` 43 | 47 | 48 | ``` 49 | 50 | ####singleInstance 51 | 这个的理解可以这么看:在微信里点击“用浏览器打开”一个朋友圈,然后切到QQ再用浏览器开一个网页,再跑到哪里再开一个页面。每次我们都在Activity中试图启动另一个浏览器Activity,但是在浏览器端看来,都是调用了同一个自己。因为使用了singleInstance模式,不同应用调用的Activity实际上是共享的。 52 | 53 | 上说明图: 54 | 55 | ![singleInstance](https://github.com/HIT-Alibaba/interview/blob/master/img/android-lanchmode-singleinstance.gif?raw=true) 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 | ![ListView原理](https://github.com/HIT-Alibaba/interview/blob/master/img/android-listview.jpg?raw=true) 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 | ![hash](https://github.com/HIT-Alibaba/interview/blob/master/img/hash-table.jpg?raw=true) 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 | ![1](/img/basic-design-gop.png) 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< 40 | @interface Singleton : NSObject 41 | + (Singleton *)sharedInstance; 42 | @end 43 | /* Singleton.m */ 44 | #import "Singleton.h" 45 | static Singleton *instance = nil; 46 | @implementation Singleton 47 | + (Singleton *)sharedInstance { 48 | @synchronized (self) { 49 | if (!instance) { 50 | instance = [[super alloc] init]; 51 | } 52 | } 53 | return instance; 54 | } 55 | ``` 56 | 57 | 这种写法也是懒加载,不过虽然保证了线程安全但是由于锁的存在当多线程访问时,性能会降低。 58 | 59 | ### GCD 60 | 61 | 这里主要利用GCD中的dispatch_once方法,这是最普遍也是苹果最推荐的方法,函数原型如下: 62 | 63 | ```objectivec 64 | void dispatch_once( 65 | dispatch_once_t *predicate, 66 | dispatch_block_t block); 67 | ``` 68 | 69 | 单例实现代码如下: 70 | 71 | ```objectivec 72 | /* Singleton.h */ 73 | #import 74 | @interface Singleton : NSObject 75 | + (Singleton *)sharedInstance; 76 | @end 77 | /* Singleton.m */ 78 | #import "Singleton.h" 79 | static Singleton *instance = nil; 80 | @implementation Singleton 81 | + (Singleton *)sharedInstance { 82 | static dispatch_once_t predicate; 83 | dispatch_once(&predicate, ^{ 84 | instance = [[Singleton alloc] init]; 85 | }); 86 | return instance; 87 | } 88 | ``` 89 | 90 | 这样的方法有很多优势,首先满足了线程安全问题,其次很好满足静态分析器要求。 91 | 92 | GCD 可以确保以更快的方式完成这些检测,它可以保证 block 中的代码在任何线程通过 dispatch_once 调用之前被执行,但它不会强制每次调用这个函数都让代码进行同步控制。 93 | 94 | 苹果的文档 [documentation for dispatch_once](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_once) 是这么说的: 95 | 96 | > The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage (including Objective-C instance variables) is undefined. 97 | 98 | 所以,如果你的 predicate 不是静态的、不是全局的,还是不能用GCD。其实如果去看这个函数所在的头文件,你会发现目前它的实现其实是一个宏。 99 | 100 | ## 工厂模式(Factory) 101 | 102 | 工厂模式是另一种常见的设计模式,本质上是使用方法来简化类的选择和初始化过程。 103 | 104 | 下面是一个网上到处都是的简单工厂模式的例子: 105 | 106 | ```objectivec 107 | // 108 | // OperationFactory.m 109 | // FactoryPattern 110 | 111 | #import "OperationFactory.h" 112 | #import "Operation.h" 113 | #import "OperationAdd.h" 114 | #import "OperationSub.h" 115 | #import "OperationMul.h" 116 | #import "OperationDiv.h" 117 | 118 | @implementation OperationFactory 119 | 120 | + (Operation *) createOperat:(char)operate{ 121 | Operation *oper = nil; 122 | switch (operate) { 123 | case '+': 124 | { 125 | oper = [[OperationAdd alloc] init]; 126 | break; 127 | } 128 | case '-': 129 | { 130 | oper = [[OperationSub alloc] init]; 131 | break; 132 | } 133 | case '*': 134 | { 135 | oper = [[OperationMul alloc] init]; 136 | break; 137 | } 138 | case '/': 139 | { 140 | oper = [[OperationDiv alloc] init]; 141 | break; 142 | } 143 | default: 144 | break; 145 | } 146 | return oper; 147 | } 148 | @end 149 | ``` 150 | 151 | 由于 Objective-C 本身的动态特性,还可以用反射来改写: 152 | 153 | ```objectivec 154 | @implementation OperationFactory 155 | + (Operation *) createOperat:(NSString *)operate{ 156 | Operation *oper = nil; 157 | Class class = NSClassFromString(operate); 158 | oper = [(Operation *)[class alloc] init]; 159 | if ([oper respondsToSelector:@selector(getResult)]) { 160 | [oper getResult]; 161 | } 162 | return oper; 163 | } 164 | @end 165 | ``` 166 | 167 | 使用时,可以传入类名,来获取对应类的对象: 168 | 169 | ```objectivec 170 | Operation *oper = [OperationFactory createOperat: @"OperationAdd"]; 171 | oper.numberA = 10; 172 | oper.numberB = 20; 173 | NSLog(@"%f", oper.getResult); 174 | ``` 175 | 176 | ## 委托模式(Delegate) 177 | 178 | 委托模式是 Cocoa 中十分常见的设计模式,在 Cocoa 库中被大量的使用。在 Objective-C 中,委托模式通常使用协议(protocol)来实现。 179 | 180 | 委托模式的示例代码: 181 | 182 | ```objectivec 183 | @protocol PrintDelegate 184 | - (void)print; 185 | @end 186 | 187 | 188 | @interface AClass : NSObject 189 | @property id delegate; 190 | @end 191 | 192 | @implementation AClass 193 | 194 | -(void)sayHello { 195 | [self.delegate print]; 196 | } 197 | 198 | -(void)print { 199 | NSLog(@"Do Print"); 200 | } 201 | @end 202 | 203 | // 使用 AClass 204 | int main(int argc, const char * argv[]) { 205 | @autoreleasepool { 206 | AClass * a = [AClass new]; 207 | a.delegate = a; 208 | [a sayHello]; 209 | } 210 | return 0; 211 | } 212 | ``` 213 | 214 | 这里对象a的 delegate 设置为自己,也可以是任何一个实现了 `PrintDelegate` 协议的对象。 215 | 216 | ## 观察者模式(Observer) 217 | 218 | Cocoa 中提供了两种用于实现观察者模式的办法,一直是使用`NSNotification`,另一种是`KVO(Key Value Observing)`。 219 | 220 | ### NSNotification 221 | 222 | `NSNotification` 基于 Cocoa 自己的消息中心组件 `NSNotificationCenter` 实现。 223 | 224 | 观察者需要统一在消息中心注册,说明自己要观察哪些值的变化。观察者通过类似下面的函数来进行注册: 225 | 226 | ```objectivec 227 | [[NSNotificationCenter defaultCenter] addObserver:self 228 | selector:@selector(printName:) 229 | name: @"messageName" 230 | object:nil]; 231 | ``` 232 | 233 | 上面的函数表明把自身注册成 "messageName" 消息的观察者,当有消息时,会调用自己的 `printName` 方法。 234 | 235 | 消息发送者使用类似下面的函数发送消息: 236 | 237 | ```objectivec 238 | [[NSNotificationCenter defaultCenter] postNotificationName:@"messageName" 239 | object:nil 240 | userInfo:nil]; 241 | ``` 242 | 243 | ### KVO(Key Value Observing) 244 | 245 | KVO的实现依赖于 Objective-C 本身强大的 KVC(Key Value Coding) 特性,可以实现对于某个属性变化的动态监测。 246 | 247 | 示例代码如下: 248 | 249 | ```objectivec 250 | // Book类 251 | @interface Book : NSObject 252 | 253 | @property NSString *name; 254 | @property CGFloat price; 255 | 256 | @end 257 | 258 | // AClass类 259 | @class Book; 260 | @interface AClass : NSObject 261 | 262 | @property (strong) Book *book; 263 | 264 | @end 265 | 266 | @implementation AClass 267 | 268 | - (id)init:(Book *)theBook { 269 | if(self = [super init]){ 270 | self.book = theBook; 271 | [self.book addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil]; 272 | } 273 | return self; 274 | } 275 | 276 | - (void)observeValueForKeyPath:(NSString *)keyPath 277 | ofObject:(id)object 278 | change:(NSDictionary *)change 279 | context:(void *)context{ 280 | if([keyPath isEqual:@"price"]){ 281 | NSLog(@"------price is changed------"); 282 | NSLog(@"old price is %@",[change objectForKey:@"old"]); 283 | NSLog(@"new price is %@",[change objectForKey:@"new"]); 284 | } 285 | } 286 | 287 | - (void)dealloc{ 288 | [self.book removeObserver:self forKeyPath:@"price"]; 289 | } 290 | @end 291 | 292 | // 使用 KVO 293 | int main(int argc, const char * argv[]) { 294 | @autoreleasepool { 295 | Book *aBook = [Book new]; 296 | aBook.price = 10.9; 297 | AClass * a = [[AClass alloc] init:aBook]; 298 | aBook.price = 11; // 输出 price is changed 299 | } 300 | return 0; 301 | } 302 | ``` 303 | 304 | 305 | ### 参考资料 306 | 307 | * [Objective-C中的单例模式](http://arthurchen.blog.51cto.com/2483760/642536/) 308 | * [iPhone开发笔记——简单工厂模式](http://blog.sina.com.cn/s/blog_58af95150101m362.html) 309 | * [详解Objective-C中的委托和协议](http://mobile.51cto.com/iphone-283416.htm) 310 | * [Objective-C中Observer模式的实现](http://blog.csdn.net/zshtiger2414/article/details/6409695) 311 | * [Objective-C KVO编程](http://blog.csdn.net/kindazrael/article/details/7961601) 312 | * [iOS开发系列——Objective-C开发之KVC,KVO](http://www.cnblogs.com/kenshincui/p/3871178.html) -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/Event-Handling.md: -------------------------------------------------------------------------------- 1 | ### 事件分类 2 | 3 | 对于 iOS 设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种: 4 | 5 | 1. 触屏事件(Touch Event) 6 | 2. 运动事件(Motion Event) 7 | 3. 远端控制事件(Remote-Control Event) 8 | 9 | ### 响应者链 10 | 11 | 当发生事件响应时,必须知道由谁来响应事件。在 iOS 中,由响应者链来对事件进行响应。 12 | 13 | 所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。事件将沿着响应者链一直向下传递,直到被接受并做出处理。一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象 ViewController(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow 对象)再到程序(UIApplication 对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。 14 | 15 | 一个典型的事件响应路线如下: 16 | 17 | First Responser --> The Window --> The Application --> nil(丢弃) 18 | 19 | 我们可以通过 `[responder nextResponder]` 找到当前 responder 的下一个 responder,持续这个过程到最后会找到 UIApplication 对象。 20 | 21 | 通常情况下,我们在 First Responder (一般也就是用户当前触控的 View )这里就会响应请求,进入下面的事件分发机制。 22 | 23 | ### 事件分发 24 | 25 | 第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个 UIView 对象),即表示当前该对象正在与用户交互,它是响应者链的开端。响应者链和事件分发的使命都是找出第一响应者。 26 | 27 | iOS 系统检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application 的事件队列,单例的 UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用 `hitTest:withEvent:`方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。 28 | 29 | `hitTest:withEvent:`方法的处理流程如下: 30 | 31 | * 首先调用当前视图的 `pointInside:withEvent:` 方法判断触摸点是否在当前视图内; 32 | * 若返回 NO, 则 `hitTest:withEvent:` 返回 nil,若返回 YES, 则向当前视图的所有子视图 (subviews) 发送 `hitTest:withEvent:` 消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从 subviews 数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕; 33 | * 若第一次有子视图返回非空对象,则 `hitTest:withEvent:` 方法返回此对象,处理结束; 34 | * 如所有子视图都返回空,则 hitTest:withEvent: 方法返回自身 (self)。 35 | 36 | 一个示例性的代码实现如下: 37 | 38 | ```objectivec 39 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ 40 | UIView *touchView = self; 41 | if ([self pointInside:point withEvent:event] && 42 | (!self.hidden) && 43 | self.userInteractionEnabled && 44 | (self.alpha >= 0.01f)) { 45 | 46 | for (UIView *subView in self.subviews) { 47 | [subview convertPoint:point fromView:self]; 48 | UIView *subTouchView = [subView hitTest:subPoint withEvent:event]; 49 | if (subTouchView) { 50 | touchView = subTouchView; 51 | break; 52 | } 53 | } 54 | } else { 55 | touchView = nil; 56 | } 57 | 58 | return touchView; 59 | } 60 | ``` 61 | 62 | #### 说明 63 | 64 | 1. 如果最终 hit-test 没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果 UIWindow 实例和 UIApplication 实例都不能处理该事件,则该事件会被丢弃; 65 | 2. `hitTest:withEvent:` 方法将会忽略隐藏 (hidden=YES) 的视图,禁止用户操作 (`userInteractionEnabled=NO`) 的视图,以及 alpha 级别小于 0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的 bound 区域(父视图的 clipsToBounds 属性为 NO,这样超过父视图 bound 区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别, 因为父视图的 `pointInside:withEvent:` 方法会返回 NO, 这样就不会继续向下遍历子视图了。当然,也可以重写 `pointInside:withEvent:` 方法来处理这种情况。 66 | 3. 我们可以重写 `hitTest:withEvent:` 来达到某些特定的目的。 67 | 68 | [CYLTabBarController](https://github.com/ChenYilong/CYLTabBarController)是一个支持自定义 Tab 控件的开源项目。在 TabBar 当中,为了支持 TabBar 按钮大小超过 TabBar Frame 范围时也可以响应,它的实现就是重载了 hitTest 方法: 69 | 70 | ```objectivec 71 | /* 72 | * 73 | Capturing touches on a subview outside the frame of its superview 74 | * 75 | */ 76 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 77 | { 78 | if (!self.clipsToBounds && !self.hidden && self.alpha > 0) { 79 | for (UIView *subview in self.subviews.reverseObjectEnumerator) { 80 | CGPoint subPoint = [subview convertPoint:point fromView:self]; 81 | UIView *result = [subview hitTest:subPoint withEvent:event]; 82 | if (result != nil) { 83 | return result; 84 | } 85 | } 86 | } 87 | return nil; 88 | } 89 | ``` 90 | 91 | 可以看到和上面的示例代码的差距,主要就在于取消了 `pointInside` 函数的检测,让我们可以捕获到当前 Frame 范围以外的子 View 的触控事件。 92 | 93 | ### 参考资料 94 | 95 | 1. [CocoaTouch 事件处理流程](http://www.cnblogs.com/snake-hand/p/3178070.html) 96 | 2. http://blog.sina.com.cn/s/blog_59fb90df0101ab26.html 97 | -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/File-System.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/iOS/Cocoa-Touch/File-System.md -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/Network.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Cocoa 网络编程 4 | 5 | Cocoa 中网络编程层次结构分为三层,自上而下分别是: 6 | 7 | * Cocoa 层:NSURL,Bonjour,Game Kit,WebKit 8 | * Core Foundation 层:基于 C 的 CFNetwork 和 CFNetServices 9 | * OS 层:基于 C 的 BSD socket 10 | 11 | 这里主要介绍处于 Cocoa 层的基于 NSURL 的一系列方法。在 iOS7 之前,主要使用的网络编程 API 是 NSURLConnection 一族的类,在 iOS7 之后苹果引入了 NSURLSession 类族,用于替代 NSURLConnection。 12 | 13 | **注意:在 Xcode 7 / iOS 9.0 中苹果正式废弃了 NSURLConnection 系列 API,并建议开发者尽快迁移到 NSURLSession。因此下面有关 NSURLConnection 的内容仅作为参考使用。** 14 | 15 | ## NSURLConnection 16 | 17 | CoreFoundation 中提供了一个类 NSURLConnection ,用于处理用户的网络请求,NSURLConnection 基本可以满足我们大多数的网络请求操作。NSURLConnection 本身并不能单独使用,需要与一族网络通信有关的类进行协同工作,包括 NSURLRequest, NSURLResponse,NSURLCache 等等。 18 | 19 | ### 基本的请求操作 20 | 21 | #### 同步请求,使用 sendAsynchronousRequest 方法 22 | 23 | ```objectivec 24 | + (NSData *)sendSynchronousRequest:(NSURLRequest *)request 25 | returningResponse:(NSURLResponse **)response 26 | error:(NSError **)error; 27 | 28 | ``` 29 | 30 | 这个同步请求是阻塞的,并且不可以中途 cancel 掉。我们可以将同步请求放到主线程之外的线程中,执行效果也会类似于异步,比如放到 GCD 的 dispatch_async 里面执行。 31 | 32 | #### 异步请求,使用 sendAsynchronousRequest 33 | 34 | ```objectivec 35 | + (void)sendAsynchronousRequest:(NSURLRequest*) request 36 | queue:(NSOperationQueue*) queue 37 | completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler; 38 | ``` 39 | 这个异步请求是非阻塞的,异步执行后把结果通过 block 回调回来,不能中途 cancel 掉 40 | 41 | #### 异步请求,使用委托 42 | 43 | 首先初始化请求: 44 | 45 | ```objectivec 46 | - (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate; 47 | ``` 48 | 49 | 然后根据需要在 delegate 类(NSURLConnectionDataDelegate协议)里面实现下列代理函数,获取异步请求的返回的数据与结果 50 | 51 | ```objectivec 52 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 53 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 54 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 55 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 56 | ``` 57 | 58 | 这个异步请求是非阻塞的,异步执行后把返回的数据与结果通过 delegate 函数回调回来,可以使用 cancel 中途取消。 59 | 60 | ### 将请求放到后台线程 61 | 62 | 上面提到的 NSURLConnection 的异步方法实际上还是跑在主线程当中,在主线程中执行网络操作会带来两个问题: 63 | 64 | 1. 尽管在网络连接过程中不会对主线程造成阻塞,但是 delegate 的回调方法还是在主线程中执行的。如果我们在回调方法中(特别是 completion 回调)中进行了大量的耗时操作,仍然会造成主线程的阻塞。 65 | 2. NSURLConnection 默认会跑在当前的 runloop 中,并且跑在 Default Mode,当用户执行滚动的 UI 操作时会发生 runloop mode 的切换,也就导致了 NSURLConnection 不能及时执行和完成回调。 66 | 67 | 为了解决这些问题,我们可以让整个 NSURLConnection 都在后台线程中执行。 68 | 69 | #### 怎么做? 70 | 71 | 简单地把`start`函数放到后台的 queue 中是不行的,像下面这样: 72 | 73 | 74 | ```objectivec 75 | dispatch_async(connectionQueue, ^{ 76 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 77 | [request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]]; 78 | 79 | NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; // 没有设置 startImmediately 为 NO,会立即开始 80 | //[connection start]; 这一句没有必要写,写了也一样不能 work。 81 | }); 82 | ``` 83 | 84 | 因为 dispatch_async 开出的线程中,默认 runloop 没有执行,因此线程会立即结束,来不及调用回调方法。我们可以添加代码让 runloop 跑起来: 85 | 86 | ```objectivec 87 | dispatch_async(connectionQueue, ^{ 88 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 89 | [request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]]; 90 | 91 | NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; 92 | [[NSRunLoop currentRunLoop] run]; 93 | }); 94 | ``` 95 | 96 | 这样回调函数才能够被调用,但是这样又带来一个问题,这个线程中 runloop 会一直跑着,导致这个线程也一直不结束,为了让所在线程在完成任务时正确释放掉,我们可以这样做: 97 | 98 | ```objectivec 99 | dispatch_async(connectionQueue, ^{ 100 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 101 | [request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]]; 102 | 103 | NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; 104 | while(!self.finished) { 105 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 106 | } 107 | }); 108 | ``` 109 | 110 | 然后在 finish 回调中执行: 111 | 112 | 113 | ```objectivec 114 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 115 | self.finish = YES; 116 | } 117 | ``` 118 | 119 | 这样的实现实际上是有些 dirty 的,引入了一个死循环来判断是否应该终止 loop。看起来 GCD 并不适合和 NSURLConnection 一起工作。 120 | 121 | 除了 GCD 之外就没有别的办法了吗?幸好,苹果还提供了下面两种方法: 122 | 123 | ##### scheduleInRunLoop:forMode: 124 | 125 | 这个函数可以让我们指定 NSURLConnection 跑在某个 runloop: 126 | 127 | ```objectivec 128 | NSRunLoop* runLoop = [NSRunLoop currentRunLoop]; 129 | [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // 添加 inputSource,让 runloop 保持 alive 130 | [self.connection scheduleInRunLoop:runLoop 131 | forMode:NSDefaultRunLoopMode]; 132 | [self.connection start]; 133 | [runLoop run]; 134 | ``` 135 | 136 | 这样,我们把它加到任意的有 Runloop 的线程中(其实 Cocoa 的线程都是自带 runloop 的,不过没有打开)都可以正常工作了,加到 NSOperationQueue 中也是可以的。 137 | 138 | 知名的开源网络库 AFNetworking 就是这么做的,代码参考[这里](https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFURLConnectionOperation.m#L157)。 139 | 140 | 注意一点,这样做的话, NSURLConnection 任务所在的线程是永远不会退出的,为了让它正确退出,可以在请求完成时结束掉 runloop: 141 | 142 | ```objectivec 143 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 144 | { 145 | CFRunLoopStop(CFRunLoopGetCurrent()); 146 | } 147 | 148 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 149 | { 150 | CFRunLoopStop(CFRunLoopGetCurrent()); 151 | } 152 | ``` 153 | 154 | AFNetworking 中负责响应回调的线程,就是通过 Runloop 来保持永不退出的,一直在后台负责响应回调。 155 | 156 | ##### setDelegateQueue: 157 | 158 | 更简单的方法是直接使用这个函数,直接使用 NSOperationQueue 来管理我们的 Connection: 159 | 160 | ```objectivec 161 | NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:aURLRequest 162 | delegate:self 163 | startImmediately:NO]; 164 | [connection setDelegateQueue:[[NSOperationQueue alloc] init]]; 165 | [connection start]; 166 | ``` 167 | 168 | 如果我们不需要太多自定义功能,这个函数也完全够用了,不需要配置 runloop,不需要担心线程不会正常退出的问题,可以让我们专注于业务代码的编写。 169 | 170 | 注意上面提到的这两个函数只能取其中一个,如果同时用了两个会报错。 171 | 172 | 173 | ## NSURLSession 174 | 175 | http://objccn.io/issue-5-4/ 176 | 177 | ### 参考资料 178 | 179 | * [[深入浅出Cocoa]iOS网络编程系列](http://blog.csdn.net/kesalin/article/details/8798039) 180 | * [Cocoa网络编程总结之NSURLConnection](http://helloitworks.com/771.html) 181 | * http://iosdevelopmentjournal.com/blog/2013/01/27/running-network-requests-in-the-background/ 182 | * https://stackoverflow.com/questions/8941353/ios-dispatch-async-and-nsurlconnection-delegate-functions-not-being-called/13733626#13733626 183 | * https://satanwoo.github.io/2015/09/11/A-New-Start/ 184 | * https://stackoverflow.com/questions/1728631/asynchronous-request-to-the-server-from-background-thread 185 | * https://stackoverflow.com/questions/1363787/is-it-safe-to-call-cfrunloopstop-from-another-thread 186 | * http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/ 187 | * http://nshipster.com/nsoperation/```objectivec -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/Performance.md: -------------------------------------------------------------------------------- 1 | ## 离屏渲染 2 | 3 | 离屏渲染往往会带来界面卡顿的问题,这里将会讨论 当前屏幕渲染、离屏渲染 以及 CPU 渲染 4 | 5 | 在 OpenGL 中,GPU 屏幕渲染有以下两种方式: 6 | 7 | - On-Screen Rendering 8 | 9 | 即当前屏幕渲染,在用于显示的屏幕缓冲区中进行,不需要额外创建新的缓存,也不需要开启新的上下文,所以性能较好,但是受到缓存大小限制等因素,一些复杂的操作无法完成。 10 | 11 | - Off-Screen Rendering 12 | 13 | 即离屏渲染,指的是在 GPU 的当前屏幕缓冲区外开辟新的缓冲区进行操作。 14 | 15 | 相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在如下两个方面: 16 | 17 | - 创建新的缓冲区 18 | - 上下文切换。离屏渲染的整个过程,需要多次切换上下文环境:先从当前屏幕切换到离屏,等待离屏渲染结束后,将离屏缓冲区的渲染结果显示到到屏幕上,这又需要将上下文环境从离屏切换到当前屏幕。 19 | 20 | 当设置了以下属性时,会触发离屏渲染: 21 | 22 | - shouldRasterize(光栅化) 23 | - masks(遮罩) 24 | - shadows(阴影) 25 | - edge antialiasing(抗锯齿) 26 | - group opacity(不透明) 27 | 28 | 为了避免卡顿问题,应当尽可能使用当前屏幕渲染,可以不使用离屏渲染则尽量不用,应当尽量避免使用 layer 的 border、corner、shadow、mask 等技术。必须离屏渲染时,相对简单的视图应该使用 CPU 渲染,相对复杂的视图则使用一般的离屏渲染。 29 | 30 | 如下是 CPU 渲染和离屏渲染的区别: 31 | 32 | 由于GPU的浮点运算能力比CPU强,CPU渲染的效率可能不如离屏渲染。但如果仅仅是实现一个简单的效果,直接使用 CPU 渲染的效率又可能比离屏渲染好,毕竟普通的离屏渲染要涉及到缓冲区创建和上下文切换等耗时操作。对一些简单的绘制过程来说,这个过程有可能用CoreGraphics,全部用CPU来完成反而会比GPU做得更好。一个常见的 CPU 渲染的例子是:重写 `drawRect` 方法,并且使用任何 Core Graphics 的技术进行了绘制操作,就涉及到了 CPU 渲染。整个渲染过程由 CPU 在 App 内同步地完成,渲染得到的`bitmap`最后再交由GPU用于显示。总之,具体使用 CPU 渲染还是使用 GPU 离屏渲染更多的时候需要进行性能上的具体比较才可以。 33 | 34 | 一个常见的性能优化的例子就是如何给 UIView/UIImageView 加圆角。 35 | 36 | 如下是三种加圆角的方式: 37 | 38 | - 设置 cornerRadius 39 | - UIBezierPath 40 | - Core Graphics(为 UIView 加圆角)与直接截取图片(为 UIImageView 加圆角) 41 | 42 | 如下是这三种方法的比较: 43 | 44 | ### cornerRadius 45 | 46 | ```objectivec 47 | view.layer.cornerRadius = 6.0; 48 | view.layer.masksToBounds = YES; 49 | ``` 50 | 这种方式会触发两次离屏渲染,如果在滚动页面中这么做的话就会遇到性能问题。当然我们可以进行缓存以优化性能,如下: 51 | 52 | ```objectivec 53 | view.layer.shouldRasterize = YES; 54 | view.layer.rasterizationScale = [UIScreen mainScreen].scale; 55 | ``` 56 | 57 | shouldRasterize = YES 会使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存,当然要在视图内容不改变的情况下。 58 | 59 | 注意:png 图片 在 UIImageView 这样处理圆角是不会产生离屏渲染的。(ios9.0之后不会离屏渲染,ios9.0之前还是会离屏渲染)。 60 | 61 | ### UIBezierPath 62 | 63 | ```objectivec 64 | - (void)drawRect:(CGRect)rect { 65 | CGRect bounds = self.bounds; 66 | [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip]; 67 | 68 | [self.image drawInRect:bounds]; 69 | } 70 | ``` 71 | 72 | 这种方法会触发一次离屏渲染,很多资料推崇这种写法,但是这种方式会导致内存暴增,并且同样会触发离屏渲染。 73 | 74 | ### Core Graphics(为 UIView 加圆角)与直接截取图片(为 UIImageView 加圆角) 75 | 76 | 正如你所期待的那样,这种方法应该是极具效率的正确的姿势。这里将为 UIView 添加圆角与为 UIImageView 添加圆角进行区分。 77 | 78 | #### 使用 Core Graphics 为 UIView 加圆角 79 | 80 | 这种做法的原理是利用 Core Graphics 自己画出了一个圆角矩形。 81 | 82 | ```Swift 83 | func kt_drawRectWithRoundedCorner(radius radius: CGFloat, 84 | borderWidth: CGFloat, 85 | backgroundColor: UIColor, 86 | borderColor: UIColor) -> UIImage { 87 | UIGraphicsBeginImageContextWithOptions(sizeToFit, false, UIScreen.mainScreen().scale) 88 | let context = UIGraphicsGetCurrentContext() 89 | 90 | CGContextMoveToPoint(context, 开始位置); // 开始坐标右边开始 91 | CGContextAddArcToPoint(context, x1, y1, x2, y2, radius); // 这种类型的代码重复四次 92 | 93 | CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke) 94 | let output = UIGraphicsGetImageFromCurrentImageContext(); 95 | UIGraphicsEndImageContext(); 96 | return output 97 | } 98 | ``` 99 | 100 | 这个方法返回的是 UIImage,有了这个图片后,就可以创建一个 UIImageView 并插入到视图层级的底部: 101 | 102 | ```Swift 103 | extension UIView { 104 | func kt_addCorner(radius radius: CGFloat, 105 | borderWidth: CGFloat, 106 | backgroundColor: UIColor, 107 | borderColor: UIColor) { 108 | let imageView = UIImageView(image: kt_drawRectWithRoundedCorner(radius: radius, 109 | borderWidth: borderWidth, 110 | backgroundColor: backgroundColor, 111 | borderColor: borderColor)) 112 | self.insertSubview(imageView, atIndex: 0) 113 | } 114 | } 115 | ``` 116 | 117 | 在调用时 只需要像这样写: 118 | 119 | ```Swift 120 | let view = UIView(frame: CGRectMake(1,2,3,4)) 121 | view.kt_addCorner(radius: 6) 122 | ``` 123 | 124 | #### 直接截取图片为 UIImageView 加圆角 125 | 126 | 这里的实现思路是直接截取图片: 127 | 128 | ```Swift 129 | extension UIImage { 130 | func kt_drawRectWithRoundedCorner(radius radius: CGFloat, _ sizetoFit: CGSize) -> UIImage { 131 | let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit) 132 | 133 | UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale) 134 | CGContextAddPath(UIGraphicsGetCurrentContext(), 135 | UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.AllCorners, 136 | cornerRadii: CGSize(width: radius, height: radius)).CGPath) 137 | CGContextClip(UIGraphicsGetCurrentContext()) 138 | 139 | self.drawInRect(rect) 140 | CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke) 141 | let output = UIGraphicsGetImageFromCurrentImageContext(); 142 | UIGraphicsEndImageContext(); 143 | 144 | return output 145 | } 146 | } 147 | ``` 148 | 149 | 圆角路径直接用贝塞尔曲线绘制。这个函数的效果是将原来的 UIImage 剪裁出圆角。配合着这函数,我们可以为 UIImageView 拓展一个设置圆角的方法: 150 | 151 | ```Swift 152 | extension UIImageView { 153 | /** 154 | / !!!只有当 imageView 不为nil 时,调用此方法才有效果 155 | 156 | :param: radius 圆角半径 157 | */ 158 | override func kt_addCorner(radius radius: CGFloat) { 159 | self.image = self.image?.kt_drawRectWithRoundedCorner(radius: radius, self.bounds.size) 160 | } 161 | } 162 | ``` 163 | 164 | 在调用时只需要像如下这样写: 165 | 166 | ```Swift 167 | let imageView = let imgView1 = UIImageView(image: UIImage(name: "")) 168 | imageView.kt_addCorner(radius: 6) 169 | ``` 170 | 171 | > 注意:需要小心使用背景颜色。因为没有设置 `masksToBounds`,因此超出圆角的部分依然会被显示。因此不应该再使用背景颜色,可以在绘制圆角矩形时设置填充颜色来达到类似效果。 172 | 173 | #### 总结 174 | 175 | - 如果能够只用 cornerRadius 解决问题,就不用优化。 176 | - 如果必须设置 masksToBounds,可以参考圆角视图的数量,如果数量较少(一页只有几个)也可以考虑不用优化。 177 | - UIImageView 的圆角通过直接截取图片实现,其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。 178 | 179 | ## 参考链接 180 | * [小心别让圆角成了你列表的帧数杀手](http://www.cocoachina.com/ios/20150803/12873.html) 181 | * http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/ 182 | * https://medium.com/ios-os-x-development/perfect-smooth-scrolling-in-uitableviews-fd609d5275a5 183 | * [UIKit性能调优](http://www.jianshu.com/p/619cf14640f3) 184 | * http://articles.cocoahope.com/blog/2013/03/06/applying-rounded-corners -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/iOS/Cocoa-Touch/README.md -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/UIApplication.md: -------------------------------------------------------------------------------- 1 | UIApplication 的核心作用是提供了 iOS 程序运行期间的控制和协作工作。 2 | 3 | 每一个程序在运行期必须有且仅有一个 UIApplication(或则其子类)的一个实例。在程序开始运行的时候,UIApplicationMain 函数是程序进入点,这个函数做了很多工作,其中一个重要的工作就是创建一个 UIApplication 的单例实例。在你的代码中你,你可以通过调用 [UIApplication sharedApplication] 来得到这个单例实例的指针。 4 | 5 | UIApplication 的一个主要工作是处理用户事件,它会起一个队列,把所有用户事件都放入队列,逐个处理,在处理的时候,它会发送当前事件 到一个合适的处理事件的目标控件。此外,UIApplication 实例还维护一个在本应用中打开的 window 列表(UIWindow 实例),这样它就 可以接触应用中的任何一个 UIView 对象。UIApplication 实例会被赋予一个代理对象,以处理应用程序的生命周期事件(比如程序启动和关闭)、系统事件(比如来电、记事项警告)等等。 6 | 7 | ## UIApplicaion 生命周期 8 | 9 | 一个 UIApplication 可以有如下几种状态: 10 | 11 | * `Not running(未运行)`程序没启动 12 | * `Inactive(未激活)`程序在前台运行,不过没有接收到事件。在没有事件处理情况下程序通常停留在这个状态 13 | * `Active(激活)`程序在前台运行而且接收到了事件。这也是前台的一个正常的模式 14 | * `Background(后台)` 程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到之后会进入挂起状态 (Suspended)。有的程序经过特殊的请求后可以长期处于 Background 状态 15 | * `Suspended(挂起)`程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。 16 | 17 | 常见的代理方法有 18 | 19 | 1. `(void)applicationWillResignActive:(UIApplication *)application` 20 | 21 | 说明:当应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件,比如来电话了 22 | 23 | 2. `(void)applicationDidBecomeActive:(UIApplication *)application` 24 | 25 | 说明:当应用程序入活动状态执行,这个刚好跟上面那个方法相反 26 | 27 | 3. `(void)applicationDidEnterBackground:(UIApplication *)application` 28 | 29 | 说明:当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可 30 | 31 | 4. `(void)applicationWillEnterForeground:(UIApplication *)application` 32 | 33 | 说明:当程序从后台将要重新回到前台时候调用,这个刚好跟上面的那个方法相反。 34 | 35 | 5. `(void)applicationWillTerminate:(UIApplication *)application` 36 | 37 | 说明:当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作。这个需要设置 UIApplicationExitsOnSuspend 的键值。 38 | 39 | 6. `(void)applicationDidReceiveMemoryWarning:(UIApplication *)application` 40 | 41 | 说明:iPhone 设备只有有限的内存,如果为应用程序分配了太多内存操作系统会终止应用程序的运行,在终止前会执行这个方法,通常可以在这里进行内存清理工作防止程序被终止 42 | 43 | 7. `(void)applicationSignificantTimeChange:(UIApplication*)application` 44 | 45 | 说明:当系统时间发生改变时执行 46 | 47 | 8. `(void)applicationDidFinishLaunching:(UIApplication*)application` 48 | 49 | 说明:当程序载入后执行 50 | 51 | 下面是一个用于展示整个 App 生命周期的示意图: 52 | 53 | ![UIApplication-Lifecycle](http://i.stack.imgur.com/c2d1D.jpg) 54 | 55 | ### UIApplication Background Task 56 | 57 | 参考资料: 58 | 59 | * [UIApplication 深入学习](http://www.cocoachina.com/ios/20121023/4958.html) -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/UIView-Basic.md: -------------------------------------------------------------------------------- 1 | UIView 表示屏幕上的一块矩形区域,负责渲染区域的内容,并且响应该区域内发生的触摸事件。它在 iOS App 中占有绝对重要的地位,因为 iOS 中几乎所有可视化控件都是 UIView 的子类。 2 | 3 | UIView 可以负责以下几种任务: 4 | 5 | * 绘制和动画 6 | * 布局和子视图管理 7 | * 事件处理 8 | 9 | ## 绘制和动画 10 | 11 | ### 视图绘制 12 | 13 | UIView 是按需绘制的,当整个视图或者视图的一部分由于布局变化,变成可见的,系统会要求视图进行绘制。对于那些需要使用 UIKit 或者 CoreGraphics 进行自定义绘制的视图,系统会调用 `drawRect:` 方法进行绘制。 14 | 15 | 当视图内容发生变化时,需要调用 `setNeedsDisplay` 或者 `setNeedsDisplayInRect:` 方法,告诉系统该重新绘制这个视图了。调用这个方法之后,系统会在下一个绘制周期更新这个视图的内容。由于系统要等到下一个绘制周期才真正进行绘制,可以一次性对多个视图调用 `setNeedsDisplay`,它们会同时被更新。 16 | 17 | 18 | ### 视图的几何属性 19 | 20 | 视图有 frame,center,bounds 等几个基本几何属性,其中: 21 | 22 | * frame 使用的最多,其坐标位置都是相对于父视图的,可以用于确定本视图在父视图中的位置和其自身的大小 23 | * center 的坐标位置也是相对于父视图的,通常用于移动,旋转等动画操作 24 | * bounds 是相对于自身的,通常情况下就是(0,0,width,height), bounds 的含义可以认为是当前 view 被允许绘制的范围 25 | 26 | ### 视图的 ContentMode 27 | 28 | 视图在初次绘制完成后,系统会对绘制结果进行快照,之后尽可能地使用快照,避免重新绘制。如果视图的几何属性发生改变,系统会根据视图的 contentMode 来决定如何改变显示效果。 29 | 30 | 默认的 contentMode 是 `UIViewContentModeScaleToFill` ,系统会拉伸当前的快照,使其符合新的 frame 尺寸。大部分 contentMode 都会对当前的快照进行拉伸或者移动等操作。如果需要重新绘制,可以把 contentMode 设置为 `UIViewContentModeRedraw`,强制视图在改变大小之类的操作时调用`drawRect:`重绘。 31 | 32 | 33 | ### 动画 34 | 35 | 可以以动画的形式改变视图的下面这些属性,只需要告诉系统动画开始和结束时的数值,系统会自动处理中间的过渡过程。 36 | 37 | * frame 38 | * bounds 39 | * center 40 | * transform 41 | * alpha 42 | * backgroundColor 43 | * contentStretch 44 | 45 | ## 布局和子视图管理 46 | 47 | 除了提供视图本身的内容之外,一个视图也可以表现得像一个容器。当一个视图包含其他视图时,两个视图之间就创建了一个父子关系。在这个关系中子视图被称为 subView ,父视图被称为 superView 。一个视图可以包含多个子视图,它们被存放在这个视图的 `subviews` 数组里。添加,删除,以及操作这些子视图的相对位置的函数如下: 48 | 49 | * `addSubview:` 50 | * `insertSubview:...` 51 | * `bringSubviewToFront:` 52 | * `sendSubviewToBack:` 53 | * `exchangeSubviewAtIndex:withSubviewAtIndex:` 54 | * `removeFromSuperview`(子视图调用) 55 | 56 | ### AutoResizing 和 Constraint 57 | 58 | 当一个视图的大小改变时,它的子视图的位置和大小也需要相应地改变。UIView 支持自动布局,也可以手动对子视图进行布局。 59 | 60 | 当下列这些事件发生时,需要进行布局操作: 61 | 62 | * 视图的 bounds 大小改变# 63 | * 用户界面旋转,通常会导致根视图控制器的大小改变 64 | * 视图的 layer 层的 Core Animation sublayers 发生改变 65 | * 程序调用视图的`setNeedsLayout`或`layoutIfNeeded`方法 66 | * 程序调用视图 layer 的`setNeedsLayout`方法 67 | 68 | #### Auto Resizing 69 | 70 | 视图的`autoresizesSubviews`属性决定了在视图大小发生变化时,如何自动调节子视图。 71 | 72 | 可以使用的掩码如下: 73 | 74 | * UIViewAutoresizingNone 75 | * UIViewAutoresizingFlexibleHeight 76 | * UIViewAutoresizingFlexibleWidth 77 | * UIViewAutoresizingFlexibleLeftMargin 78 | * UIViewAutoresizingFlexibleRightMargin 79 | * UIViewAutoresizingFlexibleBottomMargin 80 | * UIViewAutoresizingFlexibleTopMargin 81 | 82 | 可以通过位运算符将它们组合起来,例如 `UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth`。 83 | 84 | #### Constraint 85 | 86 | Constraint 是另一种用于自动布局的方法。本质上,Constraint 就是对 UIView 之间两个属性的一个约束: 87 | 88 | attribute1 == multiplier × attribute2 + constant 89 | 90 | 其中方程两边不一定是等于关系,也可以是大于等于之类的关系。 91 | 92 | Constraint 比 AutoResizing 更加灵活和强大,可以实现复杂的子视图布局。 93 | 94 | #### 自定义 layout 95 | 96 | UIView 当中提供了一个 `layoutSubviews` 函数,UIView 的子类可以重载这个函数,以实现更加复杂和精细的子 View 布局。 97 | 98 | 苹果文档专门强调了,应该只在上面提到的 Autoresizing 和 Constraint 机制不能实现所需要的效果时,才使用 `layoutSubviews`。而且,layoutSubviews 方法只能被系统触发调用,程序员不能手动直接调用该方法。 99 | 100 | 那么 layoutSubviews 方法具体调用的时机有哪些呢?在 stackoverflow 的[这个答案](http://stackoverflow.com/questions/728372/when-is-layoutsubviews-called)里有所讨论,具体有下面几种情况: 101 | 102 | 1. 在父 view 的 autoresize mask 为 ON 的情况下,addSubview 会导致被 add 的 view 调用 layoutSubviews, 同时 add 的 target view 以及它所有的子 view 都会被调用。 103 | 2. setFrame 当新的 frame 和 旧的不同时(即 view 的大小改变时)会调用 layoutSubviews 104 | 3. 滚动一个 UIScollView 会导致这个 scrollView 以及它的父 View 调用 layoutSubviews 105 | 4. 旋转设备会导致当前所响应的 ViewController 的主 View 调用 layoutSubviews 106 | 5. 改变 View 的 size 会导致父 View 调用 layoutSubviews 107 | 6. removeFromSuperview 也会导致父 View 调用 layoutSubviews 108 | 109 | 重载 layoutSubviews 可以让我们实现更复杂的布局效果,[这篇博客](http://bachiscoding.com/2014/12/15/when-will-layoutsubviews-be-invoked/)里以[RDVTabBarController](https://github.com/robbdimitrov/RDVTabBarController)为例进行了简单介绍。 110 | 111 | ## 事件处理 112 | 113 | UIView 是 UIResponder 的子类,可以响应触控事件。 114 | 115 | 通常可以使用 `addGestureRecognizer:` 添加手势识别器来响应触控事件,如果需要手动处理,则按需要重载 UIView 中的下面四个函数: 116 | 117 | * `touchesBegan:withEvent:` 118 | * `touchesMoved:withEvent:` 119 | * `touchesEnded:withEvent:` 120 | * `touchesCancelled:withEvent:` 121 | 122 | 123 | ### 参考资料 124 | 125 | * [UIView详解](http://blog.csdn.net/chengyingzhilian/article/details/7894276) 126 | * [UIView你知道多少](http://www.cnblogs.com/likwo/archive/2011/06/18/2084192.html) 127 | * http://stackoverflow.com/questions/728372/when-is-layoutsubviews-called 128 | -------------------------------------------------------------------------------- /source/iOS/Cocoa-Touch/UIViewController.md: -------------------------------------------------------------------------------- 1 | UIViewController(视图控制器),顾名思义,是 MVC 设计模式中的控制器部分。UIViewController 在 UIKit 中主要功能是用于控制画面的切换,其中的 `view` 属性(UIView 类型)管理整个画面的外观。 2 | 3 | ## UIViewController 生命周期 4 | 5 | ViewController 生命周期的第一步是初始化。不过具体调用的方法还有所不同。如果使用 StoryBoard 来创建 ViewController,我们不需要显式地去初始化,Storyboard 会自动使用 `initWithCoder:` 进行初始化。如果不使用 StoryBoard,我们可以使用 `init:` 函数进行初始化,`init:` 函数在实现过程中还会调用 `initWithNibName:bundle:`。 我们应该尽量避免在 VC 外部调用 `initWithNibName:bundle:`,而是把它放在 VC 的内部(参考[这里](https://stackoverflow.com/questions/2224077/when-should-i-initialize-a-view-controller-using-initwithnibname))。 6 | 7 | 初始化完成后,VC 的生命周期会经过下面几个函数: 8 | 9 | - (void)loadView 10 | - (void)viewDidLoad 11 | - (void)viewWillAppear 12 | - (void)viewWillLayoutSubviews 13 | - (void)viewDidLayoutSubviews 14 | - (void)viewDidAppear 15 | - (void)viewWillDisappear 16 | - (void)viewDidDisappear 17 | 18 | 假设现在有一个 AViewController(简称 Avc) 和 BViewController (简称 Bvc),通过 navigationController 的 push 实现 Avc 到 Bvc 的跳转,下面是各个方法的执行执行顺序: 19 | 20 | 1. A viewDidLoad 21 | 2. A viewWillAppear 22 | 3. A viewDidAppear 23 | 4. B viewDidLoad 24 | 5. A viewWillDisappear 25 | 6. B viewWillAppear 26 | 7. A viewDidDisappear 27 | 8. B viewDidAppear 28 | 29 | 如果再从 Bvc 跳回 Avc,会产生下面的执行顺序: 30 | 31 | 1. B viewWillDisappear 32 | 2. A viewWillAppear 33 | 3. B viewDidDisappear 34 | 4. A viewDidAppear 35 | 36 | 可见 viewDidLoad 只会调用一次,再第二次跳回 Avc 的时候,AViewController 仍然存在于内存中,也就不需要 load 了。 37 | 38 | 注意上面的生命周期中都没有提到有关 ViewController 销毁的内容,在 iOS 4 & 5 中 ViewController 中有一个 `viewDidUnload` 方法。当内存不足,应用收到 Memory warning 时,系统会自动调用当前没在界面上的 ViewController 的 viewDidUnload 方法。 通常情况下,这些未显示在界面上的 ViewController 是 UINavigationController Push 栈中未在栈顶的 ViewController,以及 UITabBarViewController 中未显示的子 ViewController。这些 View Controller 都会在 Memory Warning 事件发生时,被系统自动调用 viewDidUnload 方法。 39 | 40 | 从 iOS 6 开始,viewDidUnload 方法被废弃掉了,应用受到 memory warning 时也不会再调用 viewDidUnload 方法。我们可以通过重载 `- (void)didReceiveMemoryWarning` 和 `-(void)dealloc` 来进行清理工作。 41 | 42 | 43 | ### 参考资料 44 | 45 | 1. [UIViewController生命周期方法执行顺序](http://blog.csdn.net/fanjunxi1990/article/details/16940271) 46 | 2. http://blog.devtang.com/blog/2013/05/18/goodbye-viewdidunload/ 47 | -------------------------------------------------------------------------------- /source/iOS/More.md: -------------------------------------------------------------------------------- 1 | #### 更多学习资料: 2 | 3 | * http://nshipster.cn 4 | * http://objccn.io/ 5 | * http://arigrant.com/ 6 | * http://oncenote.com/ 7 | * https://github.com/100mango/zen 8 | * https://github.com/oa414/objc-zen-book-cn 9 | * https://github.com/nixzhu/dev-blog 10 | * https://github.com/robovm/apple-ios-samples (苹果官方 Sample 代码集合) 11 | * https://github.com/leecade/ios-dev-flow (开发流程总结) 12 | * https://github.com/DaiYue/iOS-good-practices-in-Chinese (iOS 最佳实践) 13 | * https://github.com/tangqiaoboy/iOSBlogCN (iOS 开发博客列表) 14 | * https://github.com/Aufree/trip-to-iOS (iOS 学习资料整理) 15 | * http://www.hrchen.com/2013/05/performance-with-instruments/ (iOS 性能优化) 16 | * https://github.com/huang303513/iOS-Study-Demo 17 | * https://github.com/seedante/iOS-Note 18 | * https://zsisme.gitbooks.io/ios-/content/index.html(iOS Core Animation: Advanced Techniques中文译本) 19 | 20 | #### 编码规范 21 | 22 | * https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html#//apple_ref/doc/uid/10000146-SW1 23 | * https://github.com/raywenderlich/objective-c-style-guide 24 | * https://github.com/github/objective-c-style-guide 25 | 26 | #### 常用的库总结: 27 | 28 | * http://github.ibireme.com/github/list/ios/ 29 | * https://github.com/Tim9Liu9/TimLiu-iOS 30 | * https://github.com/vsouza/awesome-ios 31 | * https://github.com/cjwirth/awesome-ios-ui 32 | 33 | -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/Block.md: -------------------------------------------------------------------------------- 1 | ## Block 基础 2 | 3 | ### Block 语法 4 | 5 | Block 可以认为是一种匿名函数,使用如下语法声明一个 Block 类型: 6 | 7 | ```objectivec 8 | return_type (^block_name)(parameters) 9 | ``` 10 | 11 | 例如: 12 | 13 | ```objectivec 14 | double (^multiplyTwoValues)(double, double); 15 | ``` 16 | 17 | Block 字面值的写法如下: 18 | 19 | ```objectivec 20 | ^ (double firstValue, double secondValue) { 21 | return firstValue * secondValue; 22 | } 23 | ``` 24 | 25 | 上面的写法省略了返回值的类型,也可以显式地指出返回值类型。 26 | 27 | 声明并且定义完一个Block之后,便可以像使用函数一样使用它: 28 | 29 | ```objectivec 30 | double (^multiplyTwoValues)(double, double) = 31 | ^(double firstValue, double secondValue) { 32 | return firstValue * secondValue; 33 | }; 34 | double result = multiplyTwoValues(2,4); 35 | 36 | NSLog(@"The result is %f", result); 37 | ``` 38 | 39 | 同时,Block 也是一种 Objective-C 对象,可以用于赋值,当做参数传递,也可以放入 NSArray 和 NSDictionary 中。 40 | 41 | **注意**:当用于函数参数时,Block 应该放在参数列表的最后一个。 42 | 43 | --- 44 | 45 | 46 | **Bonus:** 由于 Block 的语法是如此的晦涩难记,以至于出现了 [fuckingblocksyntax](http://fuckingblocksyntax.com/) 这样的网站专门用于记录 block 的语法,翻译并摘录如下: 47 | 48 | 作为变量: 49 | 50 | ```objectivec 51 | returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...}; 52 | ``` 53 | 54 | 作为属性: 55 | 56 | ```objectivec 57 | @property (nonatomic, copy) returnType (^blockName)(parameterTypes); 58 | ``` 59 | 60 | 作为函数声明中的参数: 61 | 62 | ```objective-c 63 | - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName; 64 | ``` 65 | 66 | 作为函数调用中的参数: 67 | 68 | ```objectivec 69 | [someObject someMethodThatTakesABlock:^returnType (parameters) {...}]; 70 | ``` 71 | 72 | 作为 typedef: 73 | 74 | ```objectivec 75 | typedef returnType (^TypeName)(parameterTypes); 76 | TypeName blockName = ^returnType(parameters) {...}; 77 | ``` 78 | 79 | ### Block 可以捕获外部变量 80 | 81 | Block 可以捕获来自外部作用域的变量,这是Block一个很强大的特性。 82 | 83 | ```objectivec 84 | - (void)testMethod { 85 | int anInteger = 42; 86 | void (^testBlock)(void) = ^{ 87 | NSLog(@"Integer is: %i", anInteger); 88 | }; 89 | testBlock(); 90 | } 91 | ``` 92 | 93 | 94 | 默认情况下,Block 中捕获的到变量是不能修改的,如果想修改,需要使用`__block`来声明: 95 | 96 | ```objectivec 97 | __block int anInteger = 42; 98 | ``` 99 | 100 | 对于 id 类型的变量,在 MRC 情况下,使用 `__block id x` 不会 retain 变量,而在 ARC 情况下则会对变量进行 retain(即和其他捕获的变量相同)。如果不想在 block 中进行 retain 可以使用 101 | `__unsafe_unretained __block id x`,不过这样可能会导致野指针出现。更好的办法是使用 `__weak` 的临时变量: 102 | 103 | ```objectivec 104 | MyViewController *myController = [[MyViewController alloc] init…]; 105 | // ... 106 | MyViewController * __weak weakMyViewController = myController; 107 | myController.completionHandler = ^(NSInteger result) { 108 | [weakMyViewController dismissViewControllerAnimated:YES completion:nil]; 109 | }; 110 | ``` 111 | 112 | 或者把使用 `__block` 修饰的变量设为 nil,以打破引用循环: 113 | 114 | ```objectivec 115 | MyViewController * __block myController = [[MyViewController alloc] init…]; 116 | // ... 117 | myController.completionHandler = ^(NSInteger result) { 118 | [myController dismissViewControllerAnimated:YES completion:nil]; 119 | myController = nil; 120 | }; 121 | ``` 122 | 123 | ## Block 进阶 124 | 125 | ### 使用 Block 时的注意事项 126 | 127 | 在非 ARC 的情况下,对于 block 类型的属性应该使用 `copy` ,因为 block 需要维持其作用域中捕获的变量。在 ARC 中编译器会自动对 block 进行 copy 操作,因此使用 `strong` 或者 `copy` 都可以,没有什么区别,但是苹果仍然建议使用 `copy` 来指明编译器的行为。 128 | 129 | block 在捕获外部变量的时候,会保持一个强引用,当在 block 中捕获 `self` 时,由于对象会对 block 进行 `copy`,于是便形成了强引用循环: 130 | 131 | ```objectivec 132 | @interface XYZBlockKeeper : NSObject 133 | @property (copy) void (^block)(void); 134 | @end 135 | ``` 136 | 137 | ```objectivec 138 | @implementation XYZBlockKeeper 139 | - (void)configureBlock { 140 | self.block = ^{ 141 | [self doSomething]; // capturing a strong reference to self 142 | // creates a strong reference cycle 143 | }; 144 | } 145 | ... 146 | @end 147 | ``` 148 | 149 | 为了避免强引用循环,最好捕获一个 `self` 的弱引用: 150 | 151 | ```objectivec 152 | - (void)configureBlock { 153 | XYZBlockKeeper * __weak weakSelf = self; 154 | self.block = ^{ 155 | [weakSelf doSomething]; // capture the weak reference 156 | // to avoid the reference cycle 157 | } 158 | } 159 | ``` 160 | 161 | 使用弱引用会带来另一个问题,`weakSelf` 有可能会为 nil,如果多次调用 `weakSelf` 的方法,有可能在 block 执行过程中 `weakSelf` 变为 nil。因此需要在 block 中将 `weakSelf` “强化“ 162 | 163 | ```objectivec 164 | __weak __typeof__(self) weakSelf = self; 165 | NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease]; 166 | [ op addExecutionBlock:^ { 167 | __strong __typeof__(self) strongSelf = weakSelf; 168 | [strongSelf doSomething]; 169 | [strongSelf doMoreThing]; 170 | } ]; 171 | [someOperationQueue addOperation:op]; 172 | ``` 173 | 174 | `__strong` 这一句在执行的时候,如果 WeakSelf 还没有变成 nil,那么就会 retain self,让 self 在 block 执行期间不会变为 nil。这样上面的 `doSomething` 和 `doMoreThing` 要么全执行成功,要么全失败,不会出现一个成功一个失败,即执行到中间 `self` 变成 nil 的情况。 175 | 176 | #### Bonus 177 | 178 | 很多文章对于 weakSelf 的解释中并没有详细说,为什么有可能 block 执行的过程当中 weakSelf 变为 nil,这就涉及到 weak 本身的机制了。weak 置 nil 的操作发生在 dealloc 中,苹果在 [TN2109 - The Deallocation Problem](https://developer.apple.com/library/content/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11) 中指出,最后一个持有 object 的对象被释放的时候,会触发对象的 dealloc,而这个持有者的释放操作就不一定保证发生在哪个线程了。因此 block 执行的过程中 weakSelf 有可能在另外的线程中被置为 nil。 179 | 180 | ### Block 在堆上还是在栈上? 181 | 182 | 首先要指出,Block 在非 ARC 和 ARC 两种环境下的内存机制差别很大。 183 | 184 | 在 MRC 下,Block 默认是分配在栈上的,除非进行显式的 copy: 185 | 186 | ```objectivec 187 | __block int val = 10; 188 | blk stackBlock = ^{NSLog(@"val = %d", ++val);}; 189 | NSLog(@"stackBlock: %@", stackBlock); // stackBlock: <__NSStackBlock__: 0xbfffdb28> 190 | 191 | tempBlock = [stackBlock copy]; 192 | NSLog(@"tempBlock: %@", tempBlock); // tempBlock: <__NSMallocBlock__: 0x756bf20> 193 | ``` 194 | 195 | 想把 Block 用作返回值的时候,也要加入 `copy` 和 `autorelease`: 196 | 197 | ```objectivec 198 | - (blk)myTestBlock { 199 | __block int val = 10; 200 | blk stackBlock = ^{NSLog(@"val = %d", ++val);}; 201 | return [[stackBlock copy] autorelease]; 202 | } 203 | ``` 204 | 205 | 在 ARC 环境下,Block 使用简化了很多,同时 ARC 也更加倾向于把 Block 放到堆上: 206 | 207 | ```objectivec 208 | __block int val = 10; 209 | __strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);}; 210 | NSLog(@"strongPointerBlock: %@", strongPointerBlock); // strongPointerBlock: <__NSMallocBlock__: 0x7625120> 211 | 212 | __weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);}; 213 | NSLog(@"weakPointerBlock: %@", weakPointerBlock); // weakPointerBlock: <__NSStackBlock__: 0xbfffdb30> 214 | 215 | NSLog(@"mallocBlock: %@", [weakPointerBlock copy]); // mallocBlock: <__NSMallocBlock__: 0x714ce60> 216 | 217 | NSLog(@"test %@", ^{NSLog(@"val = %d", ++val);}); // test <__NSStackBlock__: 0xbfffdb18> 218 | ``` 219 | 220 | 可以看到只有显式的 `__weak` 以及纯匿名 Block 是放到栈上的,赋值给 `__strong` 指针(也就是默认赋值)都会导致在堆上创建 Block。 221 | 222 | 对于把 Block 作为函数返回值的情况,ARC 也能自动处理: 223 | 224 | ```objectivec 225 | - (__unsafe_unretained blk) blockTest { 226 | int val = 11; 227 | return ^{NSLog(@"val = %d", val);}; 228 | } 229 | 230 | NSLog(@"block return from function: %@", [self blockTest]); // block return from function: <__NSMallocBlock__: 0x7685640> 231 | ``` 232 | 233 | PS:经过上面的讨论,可以发现巧神的[这篇博客](http://blog.devtang.com/2013/07/28/a-look-inside-blocks/) 中认为在 ARC 情况下不再有 `NSConcreteStackBlock`,其实是不完全准确的。 234 | 235 | #### 参考资料 236 | 237 | * https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW16 238 | * http://blog.waterworld.com.hk/post/block-weakself-strongself 239 | * https://stackoverflow.com/questions/17384599/why-are-block-variables-not-retained-in-non-arc-environments 240 | * https://stackoverflow.com/questions/2746197/dealloc-on-background-thread/24410372#24410372 241 | * http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html 242 | 243 | 244 | -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/Class.md: -------------------------------------------------------------------------------- 1 | ## 类方法 2 | 3 | OC中类的方法只有实例方法和静态方法两种: 4 | 5 | ```objectivec 6 | @interface Controller : NSObject 7 | 8 | + (void)thisIsAStaticMethod; // 静态方法 9 | 10 | – (void)thisIsAnInstanceMethod; // 实例方法 11 | 12 | @end 13 | ``` 14 | 15 | OC 中的方法只要声明在 @interface里,就可以认为都是公有的。实际上,OC 没有像 Java,C++ 中的那种绝对的私有及保护成员方法,仅仅可以对调用者隐藏某些方法。 16 | 17 | 声明和实现都写在 @implementation 里的方法,类的外部是看不到的。 18 | 19 | 可以使用 Category 来实现私有方法: 20 | 21 | ```objectivec 22 | // AClass.h 23 | @interface AClass : NSObject 24 | 25 | -(void)sayHello; 26 | 27 | @end 28 | 29 | // AClass.m 30 | @interface AClass (private) 31 | 32 | -(void)privateSayHello; 33 | 34 | @end 35 | 36 | @implementation AClass 37 | 38 | -(void)sayHello { 39 | [self privateSayHello]; 40 | } 41 | 42 | -(void)privateSayHello { 43 | NSLog(@"Private Hello"); 44 | } 45 | ``` 46 | 47 | 使用这种方法时,外部就不能直接调用到 `privateSayHello` 方法。 48 | 49 | 注意在上面的代码里面,当我们想通过 Category 来进行方法隐藏的时候,我们可以把实现放在主 implementation 里。当我们想扩展别的不能获取到源代码的类,或者想把不同 Category 的实现分开,可以新建 `+CategoryName.m` 文件,在里面进行实现: 50 | 51 | ```objectivec 52 | #import "SystemClass+CategoryName.h" 53 | 54 | @implementation SystemClass ( CategoryName ) 55 | // method definitions 56 | @end 57 | ``` 58 | 59 | 也可以使用 Extension 来实现私有方法: 60 | 61 | ```objectivec 62 | // AClass.h 与上面相同 63 | 64 | // AClass.m 65 | @interface AClass() 66 | 67 | -(void)privateSayHello; 68 | 69 | @end 70 | 71 | @implementation AClass 72 | 73 | -(void)sayHello { 74 | [self privateSayHello]; 75 | } 76 | 77 | -(void)privateSayHello { 78 | NSLog(@"Private Hello"); 79 | } 80 | 81 | @end 82 | ``` 83 | 84 | 与使用 Category 类似,由于声明隐藏在 .m 中,调用者无法看到其声明,也就无法调用 `privateSayHello` 这个方法,会引发编译错误。 85 | 86 | 关于 Category 和 Extension 的一些区别,在[这里](#extension)。 87 | 88 | ## 类变量 89 | 90 | 苹果推荐在现代 Objective-C 中使用 `@property` 来实现成员变量: 91 | 92 | ```objectivec 93 | @interface AClass : NSObject 94 | 95 | @property (nonatomic, copy) NSString *name; 96 | 97 | @end 98 | ``` 99 | 100 | 使用 `@property` 声明的变量可以使用`实例名.变量名`来获取和修改。 101 | 102 | `@property` 可以看做是一种语法糖,在 MRC 下,使用 `@property` 可以看成实现了下面的代码: 103 | 104 | ```objectivec 105 | // AClass.h 106 | @interface AClass : NSObject{ 107 | @public 108 | NSString *_name; 109 | } 110 | 111 | -(NSString*)name; 112 | -(void)setName:(NSString*)newName; 113 | @end 114 | 115 | // AClass.m 116 | @implementation AClass 117 | 118 | -(NSString*)name{ 119 | return _name; 120 | } 121 | 122 | -(void)setName:(NSString *)name{ 123 | if (_name != name) { 124 | [_name release]; 125 | _name = [name copy]; 126 | } 127 | } 128 | @end 129 | ``` 130 | 131 | 也就是说,`@property` 会自动生成 getter 和 setter, 同时进行自动内存管理。 132 | 133 | `@property` 的属性可以有以下几种: 134 | 135 | * readwrite 是可读可写特性;需要生成 getter 方法和 setter 方法 136 | * readonly 是只读特性,只会生成 getter 方法 不会生成 setter 方法,不希望属性在类外改变时使用 137 | * assign 是赋值特性,setter 方法将传入参数赋值给实例变量;仅设置变量时; 138 | * retain 表示持有特性,setter 方法将传入参数先保留,再赋值,传入参数的 retain count 会+1; 139 | * copy 表示拷贝特性,setter 方法将传入对象复制一份;需要完全一份新的变量时。 140 | * nonatomic 和 atomic ,决定编译器生成的 setter getter是否是原子操作。 atomic 表示使用原子操作,可以在一定程度上保证线程安全。一般推荐使用 nonatomic ,因为 nonatomic 编译出的代码更快 141 | 142 | 默认的 `@property` 是 readwrite,assign,atomic。 143 | 144 | 同时,我们还可以使用自己定义 accessor 的名字: 145 | 146 | ```objectivec 147 | @property (getter=isFinished) BOOL finished; 148 | ``` 149 | 150 | 这种情况下,编译器生成的 getter 方法名为 `isFinished`,而不是 `finished`。 151 | 152 | ### @synthesize 和 @dynamic 153 | 154 | 对于现代 OC 来说,在使用 `@property` 时, 编译器默认会进行自动 synthesize,生成 getter 和 setter,同时把 ivar 和属性绑定起来: 155 | 156 | ```objectivec 157 | /// 现代 OC 不再需要手动进行下面的声明,编译器会自动处理 158 | @synthesize propertyName = _propertyName 159 | ``` 160 | 161 | 不需要我们写任何代码,就可以直接使用 getter 和 setter 了。 162 | 163 | 然而并不是所有情况下编译器都会进行自动 synthesize,具体由下面几种: 164 | 165 | * 可读写(readwrite)属性实现了自己的 getter 和 setter 166 | * 只读(readonly)属性实现了自己的 getter 167 | * 使用 `@dynamic`,显式表示不希望编译器生成 getter 和 setter 168 | * protocol 中定义的属性,编译器不会自动 synthesize,需要手动写 169 | * 当重载父类中的属性时,也必须手动写 synthesize 170 | 171 | ## 类的扩展——Protocol, Category 和 Extension 172 | 173 | ### Protocol 174 | 175 | OC是单继承的,OC中的类可以实现多个 protocol 来实现类似 C++ 中多重继承的效果。 176 | 177 | Protocol 类似 Java 中的 interface,定义了一个方法列表,这个方法列表中的方法可以使用 `@required`, `@optional` 标注,以表示该方法是否是客户类必须要实现的方法。 一个 protocol 可以继承其他的 protocol 。 178 | 179 | ```objectivec 180 | @protocol TestProtocol // NSObject也是一个 Protocol,这里即继承 NSObject 里的方法 181 | -(void)print; 182 | @end 183 | 184 | @interface B : NSObject 185 | -(void)print; // 默认方法是 @required 的,即必须实现 186 | @end 187 | 188 | ``` 189 | 190 | Delegate(委托)是 Cocoa 中常见的一种设计模式,其实现依赖于 protocol 这个语言特性。 191 | 192 | #### 含有 property 的 Protocol 193 | 194 | 上面提到过,当 Protocol 中含有 property 时,编译器是不会进行自动 synthesize 的,需要手动处理: 195 | 196 | ```objectivec 197 | @class ExampleClass; 198 | 199 | @protocol ExampleProtocol 200 | 201 | @required 202 | 203 | @property (nonatomic, retain) ExampleClass *item; 204 | 205 | @end 206 | ``` 207 | 208 | 在实现这个 Protocol 的时候,要么再次声明 property: 209 | 210 | ```objectivec 211 | @interface MyObject : NSObject 212 | 213 | @property (nonatomic, retain) ExampleClass *item; 214 | 215 | @end 216 | ``` 217 | 218 | 要么进行手动 synthesize: 219 | 220 | ```objectivec 221 | @interface MyObject : NSObject 222 | @end 223 | 224 | @implementation MyObject 225 | @synthesize item; 226 | 227 | @end 228 | ``` 229 | 230 | 工程自带的 `AppDelegate` 使用了前一种方法,`UIApplicationDelegate` protocol 当中定义了 `window` 属性: 231 | 232 | ```objectivec 233 | @property (nonatomic, retain) UIWindow *window NS_AVAILABLE_IOS(5_0); 234 | ``` 235 | 236 | 在 `AppDelegate.h` 中我们可以看到再次对 `windows` 进行了声明: 237 | 238 | ```objectivec 239 | @interface AppDelegate : UIResponder 240 | 241 | @property (nonatomic, strong) UIWindow *window; 242 | 243 | @end 244 | ``` 245 | 246 | ### Category 247 | 248 | Category 是一种很灵活的扩展原有类的机制,使用 Category 不需要访问原有类的代码,也无需继承。Category提供了一种简单的方式,来实现类的相关方法的模块化,把不同的类方法分配到不同的类文件中。 249 | 250 | Category 常见的使用方法如下: 251 | 252 | ```objectivec 253 | // SomeClass.h 254 | @interface SomeClass : NSObject{ 255 | } 256 | -(void)print; 257 | @end 258 | 259 | // SomeClass+Hello.h 260 | #import "SomeClass.h" 261 | 262 | @interface SomeClass (Hello) 263 | -(void)hello; 264 | @end 265 | 266 | // 实现 267 | #import "SomeClass+Hello.h" 268 | @implementationSomeClass (Hello) 269 | -(void)hello{ 270 | NSLog(@"name:%@ ", @"Jacky"); 271 | } 272 | @end 273 | ``` 274 | 275 | 在使用 Category 时需要注意的一点是,如果有多个命名 Category 均实现了同一个方法(即出现了命名冲突),那么这些方法在运行时只有一个会被调用,具体哪个会被调用是不确定的。因此在给已有的类(特别是 Cocoa 类)添加 Category 时,推荐的函数命名方法是加上前缀: 276 | 277 | ```objectivec 278 | @interface NSSortDescriptor (XYZAdditions) 279 | + (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending; 280 | @end 281 | ``` 282 | 283 | 284 | ### Extension 285 | 286 | Extension 可以认为是一种匿名的 Category, Extension 与 Category 有如下几点显著的区别: 287 | 288 | 1. 使用 Extension 必须有原有类的源码 289 | 2. Extension 声明的方法必须在类的主 @implementation 区间内实现,可以避免使用有名 Category 带来的多个不必要的 implementation 段。 290 | 3. Extension 可以在类中添加新的属性和实例变量,Category 不可以(注:在 Category 中实际上可以通过运行时添加新的属性,下面会讲到) 291 | 4. Extension 里添加的方法必须要有实现(没有实现编译器会给出警告) 292 | 293 | >**注**:现代 ObjC 中 Extension 和 Category 中声明的方法如果没有实现编译器都会给出警告。 294 | 295 | 下面是一个 Extension 的例子: 296 | 297 | ```objectivec 298 | @interface MyClass : NSObject 299 | - (float)value; 300 | @end 301 | 302 | @interface MyClass () { // 注意此处扩展的写法 303 | float value; 304 | } 305 | - (void)setValue:(float)newValue; 306 | @end 307 | 308 | @implementation MyClass 309 | 310 | - (float)value { 311 | return value; 312 | } 313 | 314 | - (void)setValue:(float)newValue { 315 | value = newValue; 316 | } 317 | @end 318 | ``` 319 | 320 | Extension 很常见的用法,是用来给类添加**私有**的变量和方法,用于在类的内部使用。例如在 interface 中定义为 `readonly` 类型的属性,在实现中添加 extension,将其重新定义为 `readwrite`,这样我们在类的内部就可以直接修改它的值,然而外部依然不能调用 `setter` 方法来修改。示例代码如下(来自苹果官方[文档](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW3)): 321 | 322 | `XYZPerson.h` 323 | 324 | ```objectivec 325 | @interface XYZPerson : NSObject 326 | ... 327 | @property (readonly) NSString *uniqueIdentifier; 328 | 329 | @end 330 | ``` 331 | 332 | `XYZPerson.m` 333 | 334 | ```objectivec 335 | @interface XYZPerson () 336 | @property (readwrite) NSString *uniqueIdentifier; 337 | @end 338 | 339 | @implementation XYZPerson 340 | ... 341 | @end 342 | ``` 343 | 344 | #### 如何给已有的类添加属性 345 | 346 | 首先强调一下上面例子中所展示的,Extension 可以给类添加属性,编译器会自动生成 getter,setter 和 ivar。 Category 并不支持这些。如果使用 Category 的话,类似下面这样: 347 | 348 | ```objectivec 349 | @interface XYZPerson (UDID) 350 | @property (readwrite) NSString *uniqueIdentifier; 351 | @end 352 | 353 | @implementation XYZPerson (UDID) 354 | ... 355 | @end 356 | ``` 357 | 358 | 尽管编译可以通过,但是当真正使用 `uniqueIdentifier` 时直接会导致程序崩溃。 359 | 360 | 如果我们手动去 synthesize 呢?像下面这样: 361 | 362 | ```objectivec 363 | @implementation XYZPerson (UDID) 364 | @synthesize uniqueIdentifier; 365 | ... 366 | @end 367 | ``` 368 | 369 | 然而这样做的话,代码直接报编译错误了: 370 | 371 | `@synthesize not allowed in a category's implementation` 372 | 373 | 看来这条路是彻底走不通了。 374 | 375 | 不过我们还有别的方法,想通过 Category 添加属性的话,可以通过 Runtime 当中提供的 associated object 特性。NSHipster 的 [这篇文章](http://nshipster.cn/associated-objects/) 展示了具体的做法。 376 | 377 | #### 如何在类中添加全局变量 378 | 379 | 有些时候我们需要在类中添加某个在类中全局可用的变量,为了避免污染作用域,一个比较好的做法是在 .m 文件中使用 static 变量: 380 | 381 | 382 | ```objectivec 383 | static NSOperationQueue * _personOperationQueue = nil; 384 | 385 | @implementation XYZPerson 386 | ... 387 | @end 388 | ``` 389 | 390 | 由于 static 变量在编译期就是确定的,因此对于 NSObject 对象来说,初始化的值只能是 nil。如何进行类似 init 的初始化呢?可以通过重载 initialize 方法来做: 391 | 392 | ```objectivec 393 | @implementation XYZPerson 394 | - (void)initialize { 395 | if (!_personOperationQueue) { 396 | _personOperationQueue = [[NSOperationQueue alloc] init]; 397 | } 398 | } 399 | @end 400 | ``` 401 | 402 | 为什么这里要判断是否为 nil 呢?因为 `initialize` 方法可能会调用多次,后面会提到。 403 | 404 | 如果是在 Category 中想声明全局变量呢?当然也可以通过 initialize,不过除非必须的情况下,并不推荐在 Category 当中进行方法重载。 405 | 406 | 有一种方法是声明 static 函数,下面的代码来自 [AFNetworking](https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFURLSessionManager.m),声明了一个当前文件范围可用的队列: 407 | 408 | ```objectivec 409 | static dispatch_queue_t url_session_manager_creation_queue() { 410 | static dispatch_queue_t af_url_session_manager_creation_queue; 411 | static dispatch_once_t onceToken; 412 | dispatch_once(&onceToken, ^{ 413 | af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); 414 | }); 415 | 416 | return af_url_session_manager_creation_queue; 417 | } 418 | ``` 419 | 420 | 下面介绍一个有点黑魔法的方法,除了上面两种方法之外,我们还可以通过编译器的 `__attribute__` 特性来实现初始化: 421 | 422 | ```objectivec 423 | __attribute__((constructor)) 424 | static void initialize_Queue() { 425 | _personOperationQueue = [[NSOperationQueue alloc] init]; 426 | } 427 | 428 | @implementation XYZPerson (Operation) 429 | 430 | @end 431 | ``` 432 | 433 | ## 类的导入 434 | 435 | 导入类可以使用 `#include` , `#import` 和 `@class` 三种方法,其区别如下: 436 | 437 | * `#import`是Objective-C导入头文件的关键字,`#include`是C/C++导入头文件的关键字 438 | * 使用`#import`头文件会自动只导入一次,不会重复导入,相当于`#include`和`#pragma once`; 439 | * `@class`告诉编译器需要知道某个类的声明,可以解决头文件的相互包含问题; 440 | 441 | `@class`是放在interface中的,只是在引用一个类,将这个被引用类作为一个类型使用。在实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用`#import`方式引入被引用类。 442 | 443 | ## 类的初始化 444 | 445 | Objective-C 是建立在 Runtime 基础上的语言,类也不例外。OC 中类是初始化也是动态的。在 OC 中绝大部分类都继承自 `NSObject`,它有两个非常特殊的类方法 `load` 和 `initilize`,用于类的初始化 446 | 447 | #### +load 448 | 449 | +load 方法是当类或分类被添加到 Objective-C runtime 时被调用的,实现这个方法可以让我们在类加载的时候执行一些类相关的行为。子类的 +load 方法会在它的所有父类的 +load 方法之后执行,而分类的 +load 方法会在它的主类的 +load 方法之后执行。但是不同的类之间的 +load 方法的调用顺序是不确定的。 450 | 451 | load 方法不会被类自动继承, 每一个类中的 load 方法都不需要像 viewDidLoad 方法一样调用父类的方法。子类、父类和分类中的 +load 方法的实现是被区别对待的。也就是说如果子类没有实现 +load 方法,那么当它被加载时 runtime 是不会去调用父类的 +load 方法的1。同理,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。因此,我们常常可以利用这个特性做一些“邪恶”的事情,比如说方法混淆(Method Swizzling)。FDTemplateLayoutCell 中就使用了这个方法,见[这里](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell/blob/2bead7b80e40e8689201e7c1d6f034e952c9a155/Classes/UITableView%2BFDIndexPathHeightCache.m#L147)。 452 | 453 | #### +initialize 454 | 455 | +initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。 456 | 457 | +initialize 方法的调用与普通方法的调用是一样的,走的都是发送消息的流程。换言之,如果子类没有实现 +initialize 方法,那么继承自父类的实现会被调用;如果一个类的分类实现了 +initialize 方法,那么就会对这个类中的实现造成覆盖。 458 | 459 | ### 注解 460 | 461 |
  • 462 |

    1.举一个例子:有一个 Father 类,实现了 load 方法,打印类名,一个 Son 类继承自前者,没有实现 load 方法。实例出一个 Son 的对象时,结果是会输出父类的名字。但这个例子与之前的结论并不矛盾,这里说的是父类先被加载了,所以调用了父类的 load 方法,而子类被加载时没有调用父类的 load 方法。 暂时没找到例子可以严格的证明此前的结论,所以还是去看源码吧。

    463 | 464 | ### 参考资料 465 | 466 | * [iOS开发基础面试题系列](http://blog.csdn.net/xunyn/article/details/8302787) 467 | * [10个Objective-C基础面试题,iOS面试必备](http://www.oschina.net/news/42288/10-objective-c-interview) 468 | * [Objective-C中“私有方法”的实现"](http://blog.sina.com.cn/s/blog_74e9d98d01013au8.html) 469 | * [Objective-C中@property详解](http://www.cnblogs.com/andyque/archive/2011/08/03/2125728.html) 470 | * [Objective-C中的protocol和delegate](http://www.cnblogs.com/whyandinside/archive/2013/02/28/2937217.html) 471 | * [Objective-C——消息,Category 与 Protocol](http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html) 472 | * [深入理解Objective-C中的@class](http://www.cnblogs.com/martin1009/archive/2012/06/24/2560218.html) 473 | * [Objective-C +load vs +initialize](http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/) 474 | * [深入理解Objective-C:Category](http://tech.meituan.com/DiveIntoCategory.html) 475 | * https://stackoverflow.com/questions/19784454/when-should-i-use-synthesize-explicitly 476 | * http://www.fantageek.com/blog/2014/07/13/property-in-protocol/ 477 | * http://www.friday.com/bbum/2009/09/06/iniailize-can-be-executed-multiple-times-load-not-so-much/ 478 | -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/Objective-C-Introspection.md: -------------------------------------------------------------------------------- 1 | 在 Objective-C 以及其他很多动态语言当中,自省是一种用于判断对象是哪个类型的对象,以及这个对象能处理哪个消息的方法,特别是当你得到的对象是`id`类型时,自省更显得尤其有用。 2 | 3 | ## 类 4 | 5 | `- class` 返回接受者类的类对象 6 | `- isKindOfClass:` 返回一个布尔值,指示接受者是不是一个给定类(或者其子类)的实例 7 | `- isMemberOfClass:` 返回一个布尔值,指示接受者是不是给定类的实例 8 | 9 | ## 消息 10 | 11 | `-respondsToSelector:` 返回一个布尔值,指示接受者是否实现或者继承了父类的方法,可以对给定的消息进行处理。 12 | 13 | ## 代理 14 | 15 | 代理是一种用于替代其他对象或者替代还没有出现的对象的对象。通常情况下,传给代理的消息会被传递给真正的对象,或者使得代理去加载真正的对象。 16 | 17 | `-isProxy` 返回一个布尔值,指示接受者是不是继承自`NSObject`(?) -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/Objective-C-Message.md: -------------------------------------------------------------------------------- 1 | ## 消息 2 | 3 | 在C++或Java里,类与类的行为方法之间的关系非常紧密,一个方法必定属于一个类,且于编译时就已经绑定在一起,所以你不可能调用一个类里没有的方法。而在Objective-C中就比较简单了,类和消息之间是松耦合的,方法调用只是向某个类发送一个消息,该类可以在运行时再确定怎么处理接受到的消息。也就是说,一个类不保证一定会响应接收到的消息,如果收到了一个无法处理的消息,那么程序就是简单报一个错而已。 4 | 5 | Objective-C 是 C 语言的超集,所有的方法在底层都是简单朴素的 C 方法,运行时决定了当传入一个消息时具体哪个方法被调用。 6 | 7 | 像对象发送一个消息的代码类似于这个: 8 | 9 | ```objectivec 10 | id returnValue = [someObject messageName:parameter]; 11 | ``` 12 | 13 | 最终这个代码会变成类似下面这个C方法: 14 | 15 | ```objectivec 16 | id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter); 17 | ``` 18 | 19 | ## 消息传递 20 | 21 | 当一个对象接受到它不能理解的消息时,消息传递机制会被启用。 22 | 23 | 在消息真正“不被处理”之前,有三次可以处理它的机会。 24 | 25 | **第一次是动态方法解析** 26 | 27 | 当一个对象接受到它不能理解的消息时,第一个被调用的方法是一个类方法: 28 | 29 | ```objectivec 30 | + (BOOL)resolveInstanceMethod:(SEL)selector; 31 | ``` 32 | 33 | 这个方法以传入对象的 selector 作为参数,返回一个布尔值,这个值指示了有没有一个实例方法被添加到类中,使得类现在可以处理这个 selector。 34 | 35 | 36 | **如果失败,使用替代接收者** 37 | 38 | 第二次尝试是询问接受这个消息的类,有没有一个替代接受者可以处理这个未知消息,对应的函数是: 39 | 40 | ```objectivec 41 | - (id)forwardingTargetForSelector:(SEL)selector; 42 | ``` 43 | 44 | 未知 selector 被传入,如果有替代者,那么替代者对象会被返回,否则返回nil 45 | 46 | **最后,使用消息传递机制** 47 | 48 | 如果上面这些尝试都失败了,只能使用完成的消息传递机制。首先创建一个`NSInvocation`对象来包装好这个没有处理的消息的所有细节。 49 | 50 | 用于传递消息的方法是: 51 | 52 | ```objectivec 53 | - (void)forwardInvocation:(NSInvocation *)invocation; 54 | ``` 55 | 56 | 这个方法的实现应该总是调用其父类的实现。最终`NSObject`的实现会唤醒`doesNotRecognizeSelector`来触发一个“未处理的 selector” 异常。 57 | 58 | ## Method Swizzling 59 | 60 | 当消息传入一个函数时,实际调用的方法是在运行时决定的。这就允许我们改变某个消息传入时实际调用的方法。通常这种办法用于改变那些看不到源码的类的功能。 61 | 62 | 类的方法列表包含了一系列的 selector 名字和对应的实现之间的映射,用于指导动态消息系统去哪里找某个消息对应的实现。这些对应的实现存储为一种叫做`IMP`的函数指针,其原型如下: 63 | 64 | ```objectivec 65 | id (*IMP)(id, SEL, ...) 66 | ``` 67 | 68 | 要想得到某个 selector 对应的实现,可以使用下面的函数: 69 | 70 | ```objectivec 71 | Method class_getInstanceMethod(Class aClass, SEL aSelector) 72 | ``` 73 | 74 | 想添加一个实现,可以使用下面的函数: 75 | 76 | ```objectivec 77 | BOOL class_addMethod(Class class, SEL originalSelector, Method m1, const char* encoding) 78 | ``` 79 | 80 | 要想交换两个实现,可以使用: 81 | 82 | ```objectivec 83 | void method_exchangeImplementations(Method m1, Method m2) 84 | ``` 85 | 86 | Swizzling 通常被认为是一种黑科技,它容易导致不可预测的行为和难以预知的结果。尽管它不是十分安全,但是只要能做到下面几点,使用它相对而言还是比较安全的: 87 | 88 | * 总是调用方法本来的实现(除非你真的不需要这么做) 89 | * 避免冲突 90 | * 明白你在干什么 91 | * 小心谨慎 92 | 93 | ### 参考资料 94 | 95 | * [Objective-C——消息、Category和Protocol](http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html) -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/iOS/ObjC-Basic/README.md -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/Runloop.md: -------------------------------------------------------------------------------- 1 | Runloop 2 | ======= 3 | 4 | Runloop 是和线程紧密相关的一个基础组件,是很多线程有关功能的幕后功臣。尽管在平常使用中几乎不太会直接用到,理解 Runloop 有利于我们更加深入地理解 iOS 的多线程模型。 5 | 6 | ## Runloop 基本概念 7 | 8 | Runloop 是什么?Runloop 还是比较顾名思义的一个东西,说白了就是一种循环,只不过它这种循环比较高级。一般的 while 循环会导致 CPU 进入忙等待状态,而 Runloop 则是一种“闲”等待,这部分可以类比 Linux 下的 epoll。当没有事件时,Runloop 会进入休眠状态,有事件发生时, Runloop 会去找对应的 Handler 处理事件。Runloop 可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。 9 | 10 | 11 | 盗一张苹果官方文档的图,也是几乎每个讲 Runloop 的文章都会引用的图,大体说明了 Runloop 的工作模式: 12 | 13 | ![Runloop](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/Art/runloop.jpg) 14 | 15 | 图中展现了 Runloop 在线程中的作用:从 input source 和 timer source 接受事件,然后在线程中处理事件。 16 | 17 | ### Runloop 与线程 18 | 19 | Runloop 和线程是绑定在一起的。每个线程(包括主线程)都有一个对应的 Runloop 对象。我们并不能自己创建 Runloop 对象,但是可以获取到系统提供的 Runloop 对象。 20 | 21 | 主线程的 Runloop 会在应用启动的时候完成启动,其他线程的 Runloop 默认并不会启动,需要我们手动启动。 22 | 23 | ### Input Source 和 Timer Source 24 | 25 | 这两个都是 Runloop 事件的来源,其中 Input Source 又可以分为三类 26 | 27 | * Port-Based Sources,系统底层的 Port 事件,例如 CFSocketRef ,在应用层基本用不到 28 | * Custom Input Sources,用户手动创建的 Source 29 | * Cocoa Perform Selector Sources, Cocoa 提供的 performSelector 系列方法,也是一种事件源 30 | 31 | Timer Source 顾名思义就是指定时器事件了。 32 | 33 | ### Runloop Observer 34 | 35 | Runloop 通过监控 Source 来决定有没有任务要做,除此之外,我们还可以用 Runloop Observer 来监控 Runloop 本身的状态。 Runloop Observer 可以监控下面的 runloop 事件: 36 | 37 | * The entrance to the run loop. 38 | * When the run loop is about to process a timer. 39 | * When the run loop is about to process an input source. 40 | * When the run loop is about to go to sleep. 41 | * When the run loop has woken up, but before it has processed the event that woke it up. 42 | * The exit from the run loop. 43 | 44 | ### Runloop Mode 45 | 46 | 在监视与被监视中,Runloop 要处理的事情还挺复杂的。为了让 Runloop 能专心处理自己关心的那部分事情,引入了 Runloop Mode 概念。 47 | 48 | ![Runloop Mode](http://cc.cocimg.com/api/uploads/20150528/1432798883604537.png) 49 | 50 | 如图所示,Runloop Mode 实际上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的 Source,Timer 和 Observer 隔绝开来。Runloop 在某个时刻只能跑在一个 Mode 下,处理这一个 Mode 当中的 Source,Timer 和 Observer。 51 | 52 | 苹果文档中提到的 Mode 有五个,分别是: 53 | 54 | * NSDefaultRunLoopMode 55 | * NSConnectionReplyMode 56 | * NSModalPanelRunLoopMode 57 | * NSEventTrackingRunLoopMode 58 | * NSRunLoopCommonModes 59 | 60 | iOS 中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。 61 | 62 | ### 与 Runloop 相关的坑 63 | 64 | 日常开发中,与 runLoop 接触得最近可能就是通过 NSTimer 了。一个 Timer 一次只能加入到一个 RunLoop 中。我们日常使用的时候,通常就是加入到当前的 runLoop 的 default mode 中,而 ScrollView 在用户滑动时,主线程 RunLoop 会转到 UITrackingRunLoopMode 。而这个时候, Timer 就不会运行。 65 | 66 | 有如下两种解决方案: 67 | 68 | - 第一种: 设置 RunLoop Mode,例如 NSTimer,我们指定它运行于 NSRunLoopCommonModes ,这是一个 Mode 的集合。注册到这个 Mode 下后,无论当前 runLoop 运行哪个 mode ,事件都能得到执行。 69 | - 第二种: 另一种解决 Timer 的方法是,我们在另外一个线程执行和处理 Timer 事件,然后在主线程更新 UI。 70 | 71 | 在 AFNetworking 3.0 中,就有相关的代码,如下: 72 | 73 | ```objectivec 74 | - (void)startActivationDelayTimer { 75 | self.activationDelayTimer = [NSTimer 76 | timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; 77 | [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes]; 78 | } 79 | ``` 80 | 81 | 这里就是添加了一个计时器,由于指定了 NSRunLoopCommonModes,所以不管 RunLoop 出于什么状态,都执行这个计时器任务。 82 | 83 | 84 | #### 参考资料 85 | 86 | * https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1 87 | * http://chun.tips/blog/2014/10/20/zou-jin-run-loopde-shi-jie-%5B%3F%5D-:shi-yao-shi-run-loop%3F/ 88 | * http://www.hrchen.com/2013/07/tricky-runloop-on-ios/ 89 | * http://www.cocoachina.com/ios/20150601/11970.html 90 | * http://www.cocoachina.com/ios/20111111/3487.html 91 | * http://mobile.51cto.com/iphone-386596.htm 92 | * http://blog.ibireme.com/2015/05/18/runloop/ 93 | -------------------------------------------------------------------------------- /source/iOS/ObjC-Basic/Runtime.md: -------------------------------------------------------------------------------- 1 | ## Objective-C Runtime 2 | 3 | ### Runtime 是什么? 4 | 5 | Runtime 是 Objective-C 区别于 C 语言这样的静态语言的一个非常重要的特性。对于 C 语言,函数的调用会在编译期就已经决定好,在编译完成后直接顺序执行。但是 OC 是一门动态语言,函数调用变成了消息发送,在编译期不能知道要调用哪个函数。所以 Runtime 无非就是去解决如何在运行时期找到调用方法这样的问题。 6 | 7 | 对于实例变量有如下的思路: 8 | 9 | > instance -> class -> method -> SEL -> IMP -> 实现函数 10 | 11 | 实例对象中存放 isa 指针以及实例变量,有 isa 指针可以找到实例对象所属的类对象 (类也是对象,面向对象中一切都是对象),类中存放着实例方法列表,在这个方法列表中 SEL 作为 key,IMP 作为 value。 在编译时期,根据方法名字会生成一个唯一的 Int 标识,这个标识就是 SEL。IMP 其实就是函数指针 指向了最终的函数实现。整个 Runtime 的核心就是 objc_msgSend 函数,通过给类发送 SEL 以传递消息,找到匹配的 IMP 再获取最终的实现。如下的这张图描述了对象的内存布局。 12 | 13 | ![](https://raw.githubusercontent.com/WiInputMethod/interview/master/img/ios-runtime-class.png) 14 | 15 | 类中的 super_class 指针可以追溯整个继承链。向一个对象发送消息时,Runtime 会根据实例对象的 isa 指针找到其所属的类,并自底向上直至根类(NSObject)中 去寻找 SEL 所对应的方法,找到后就运行整个方法。 16 | 17 | metaClass是元类,也有 isa 指针、super_class 指针。其中保存了类方法列表。 18 | 19 | 如下是 objc/runtime.h 中定义的类的结构: 20 | 21 | ```objectivec 22 | struct objc_class { 23 | Class isa OBJC_ISA_AVAILABILITY; 24 | 25 | #if !__OBJC2__ 26 | Class super_class OBJC2_UNAVAILABLE; 27 | const char *name OBJC2_UNAVAILABLE; 28 | long version OBJC2_UNAVAILABLE; 29 | long info OBJC2_UNAVAILABLE; 30 | long instance_size OBJC2_UNAVAILABLE; 31 | struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 成员变量地址列表 32 | struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法地址列表 33 | struct objc_cache *cache OBJC2_UNAVAILABLE; // 缓存最近使用的方法地址,以避免多次在方法地址列表中查询,提升效率 34 | struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 遵循的协议列表 35 | #endif 36 | 37 | } OBJC2_UNAVAILABLE; 38 | /* Use `Class` instead of `struct objc_class *` */ 39 | ``` 40 | 41 | ### SEL 与 IMP 42 | 43 | SEL 可以将其理解为方法的 ID. 结构如下: 44 | 45 | ```objectivec 46 | typedef struct objc_selector *SEL; 47 | 48 | struct objc_selector { 49 | char *name; OBJC2_UNAVAILABLE; 50 | char *types; OBJC2_UNAVAILABLE; 51 | }; 52 | ``` 53 | 54 | IMP 可以理解为函数指针,指向了最终的实现。 55 | 56 | SEL 与 IMP 的关系非常类似于 HashTable 中 key 与 value 的关系。OC 中不支持函数重载的原因就是因为一个类的方法列表中不能存在两个相同的 SEL 。但是多个方法却可以在不同的类中有一个相同的 SEL,不同类的实例对象执行相同的 SEL 时,会在各自的方法列表中去根据 SEL 去寻找自己对应的IMP。这使得OC可以支持函数重写。 57 | 58 | ### 消息传递机制 59 | 60 | - objc_msgSend函数的消息处理过程 61 | - 不涵盖消息cache机制 62 | - 需要对Objective-C runtime有一定的了解 63 | 64 | 如下用于描述 objc_msgSend 函数的调用流程: 65 | 66 | - 1.检测 SEL 是否应该被忽略 67 | - 2.检测发送的 target 是否为 nil ,如果是则忽略该消息 68 | - 3. 69 | - 当调用实例方法时,通过 isa 指针找到实例对应的 class 并且在其中的缓存方法列表以及方法列表中进行查询,如果找不到则根据 super_class 指针在父类中查询,直至根类(NSObject 或 NSProxy). 70 | - 当调用类方法时,通过 isa 指针找到实例对应的 metaclass 并且在其中的缓存方法列表以及方法列表中进行查询,如果找不到则根据 super_class 指针在父类中查询,直至根类(NSObject 或 NSProxy). (根据此前的开篇中的图,Root Meta Class 还是有根类的。) 71 | - 如果还没找到则进入消息动态解析过程。 72 | 73 | *由于苹果对OC 2.0 Runtime的具体实现细节未完全开源,本节所引用源代码大部分来自OC 1.0,如有错误,敬请更正* 74 | 75 | 当一个对象 sender 调用代码`[receiver message];`的时候,实际上是调用了runtime的`objc_msgSend`函数,所以OC的方法调用并不像C函数一样能按照地址直接取用,而是经过了一系列的过程。这样的机制使得 runtime 可以在接收到消息后对消息进行特殊处理,这才使OC的一些特性譬如:给 nil 发送消息不崩溃,给类动态添加方法和消息转发等成为可能。也正因为每一次调用方法的时候实际上是调用了一些 runtime 的消息处理函数,OC的方法调用相对于C来说会相对较慢,但 OC 也通过引入 cache 机制来很大程度上的克服了这个缺点。下面我们就从一个对象 sender 调用代码`[receiver message];`这个情景开始,了解消息传递的过程。 76 | 77 | 首先这行代码会被改写成`objc_msgSend(self, _cmd);`,这是一个runtime的函数,其原型为: 78 | 79 | `id objc_msgSend(id self, SEL op, ...)` 80 | 81 | self与_cmd是两个编译器会自动添加的隐藏参数,self是一个指向接收对象的指针,_cmd为方法选择器。这个函数的实现为汇编版本,苹果开源的项目中共有6种对不同平台的汇编实现,本节选取其在x86_64实现的文件objc-msg-x86_64.s 82 | 83 | ```asm 84 | #objc-msg-x86_64.s# 85 | ENTRY _objc_msgSend 86 | // ... 87 | GetIsaFast NORMAL // r11 = self->isa 88 | CacheLookup NORMAL // calls IMP on success 89 | // ... 90 | // cache miss: go search the method lists 91 | LCacheMiss: 92 | // isa still in r11 93 | MethodTableLookup %a1, %a2 // r11 = IMP 94 | cmp %r11, %r11 // set eq (nonstret) for forwarding 95 | jmp *%r11 // goto *imp 96 | END_ENTRY _objc_msgSend 97 | ``` 98 | 99 | 可以看到其调用了`GetIsaFast`,由于self是id类型,而id的原型为`struct objc_object *id;`,所以需要通过id的isa指针获取其所属的类对象,之后调用`CacheLookup`在获取到的类中根据传入的_cmd查找对应方法实现的IMP指针。这两个函数的实现均在同一个文件下,因为暂时我还不了解cache的机制,所以这部分先不深入讨论。CacheLookup函数在命中后会直接调用相应的IMP方法,这就完成了方法的调用。如果cache落空,则跳转至LCacheMiss标签,调用MethodTableLookup方法,这个方法将IMP的值存在r11寄存器里,之后`jmp *%r11`从IMP开始执行,完成方法调用。MethodTableLookup函数实现如下, 100 | 101 | ```asm 102 | .macro MethodTableLookup 103 | MESSENGER_END_SLOW 104 | SaveRegisters 105 | // _class_lookupMethodAndLoadCache3(receiver, selector, class) 106 | movq $0, %a1 107 | movq $1, %a2 108 | movq %r11, %a3 109 | call __class_lookupMethodAndLoadCache3 110 | // IMP is now in %rax 111 | movq %rax, %r11 112 | RestoreRegisters 113 | .endmacro 114 | ``` 115 | 116 | 可以看到其实际上将receiver(即self), selector(即_cmd),class(即self->isa)传递给了_class_lookupMethodAndLoadCache3这个函数,查看该函数的实现后,欢迎重新回到C语言的世界。 117 | 118 | ```objectivec 119 | #objc-class-old.mm# 120 | IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) 121 | { 122 | return lookUpImpOrForward(cls, sel, obj, 123 | YES/*initialize*/, NO/*cache*/, YES/*resolver*/); 124 | } 125 | ``` 126 | 127 | 这个函数进一步调用了`lookUpImpOrForward`,并把cache标签置为NO,意味着忽略第一次不加锁的cache查找。这个函数的返回值要么是对应方法的IMP指针,要么是一个__objc_msgForward_impcache汇编方法的入口,后者对应着消息转发机制,即如果在该对象及其继承链上方的的对象都找不到选择器_cmd的响应方法的话,就调用消息转发函数尝试将该消息转发给其他对象。下面是lookUpImpOrForward的实现,由于代码过长,注释将写在代码之中。 128 | 129 | ```objectivec 130 | #objc-class-old.mm# 131 | IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 132 | bool initialize, bool cache, bool resolver) 133 | { 134 | Class curClass; // 当前类对象 135 | IMP methodPC = nil; // 用于保存最终查找到的函数指针并返回 136 | Method meth; // 定义了方法的一个结构体,可通过meth->imp获取函数指针 137 | bool triedResolver = NO; // 方法解析的标志变量 138 | 139 | methodListLock.assertUnlocked(); 140 | // 不加锁地查找cache,由于之前cache落空,所以肯定找不到,就忽略 141 | // Optimistic cache lookup 142 | if (cache) { 143 | methodPC = _cache_getImp(cls, sel); 144 | if (methodPC) return methodPC; 145 | } 146 | // Check for freed class 147 | if (cls == _class_getFreedObjectClass()) 148 | return (IMP) _freedHandler; 149 | // 确保该类已被初始化,如果没有就调用类方法+initialize,这里也说明了为什么OC的类会在 150 | // 第一次接收消息后调用+initialize进行初始化,相反的,如果想要代码在类注册runtime的 151 | // 时候就运行,可以将代码写在+load方法里 152 | // Check for +initialize 153 | if (initialize && !cls->isInitialized()) { 154 | _class_initialize (_class_getNonMetaClass(cls, inst)); 155 | // If sel == initialize, _class_initialize will send +initialize and 156 | // then the messenger will send +initialize again after this 157 | // procedure finishes. Of course, if this is not being called 158 | // from the messenger then it won't happen. 2778172 159 | } 160 | // The lock is held to make method-lookup + cache-fill atomic 161 | // with respect to method addition. Otherwise, a category could 162 | // be added but ignored indefinitely because the cache was re-filled 163 | // with the old value after the cache flush on behalf of the category. 164 | // 上述英文已述:对消息查找和填充cache加锁,由于填充cache是写操作,所以需要对其 165 | // 加锁以免加入了category之后的cache被旧的cache冲掉,导致category失效。 166 | 167 | // 实际上,如果cache没有命中,但在方法列表中找到了对应的IMP,函数也是会进行cache 168 | // 写入操作。 169 | retry: 170 | methodListLock.lock(); 171 | // 在开启GC选项后忽略retain, release等方法(猜测GC 是 Garbage Collection) 172 | // 这也体现了OC的灵活性,runtime完全有权力忽略一些方法 173 | if (ignoreSelector(sel)) { 174 | methodPC = _cache_addIgnoredEntry(cls, sel); 175 | goto done; 176 | } 177 | // 在加锁的状态下再查找一次cache,如果命中就直接返回IMP指针 178 | // 个人认为再次在加锁状态下查找是因为在与上次查找的间隙中可能 179 | // 有其他类填充了这个cache 180 | methodPC = _cache_getImp(cls, sel); 181 | if (methodPC) goto done; 182 | 183 | // 如果还是没有命中的话就查找该类的方法列表 184 | meth = _class_getMethodNoSuper_nolock(cls, sel); 185 | if (meth) { 186 | // 命中,填充cache,返回IMP指针 187 | log_and_fill_cache(cls, cls, meth, sel); 188 | methodPC = method_getImplementation(meth); 189 | goto done; 190 | } 191 | 192 | // 没有命中,沿着class的继承链向上查找,最后找到的是NSObject(NSProxy除外) 193 | // 而NSObject的superclass为nil 194 | curClass = cls; 195 | while ((curClass = curClass->superclass)) { 196 | // 尝试从超类的cache中加载 197 | meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache); 198 | if (meth) { 199 | // 如果不是forward 200 | if (meth != (Method)1) { 201 | // 在超类中找到IMP,在当前类中进行cache 202 | log_and_fill_cache(cls, curClass, meth, sel); 203 | methodPC = method_getImplementation(meth); 204 | goto done; 205 | } 206 | else { 207 | // 找到forward,跳出循环 208 | // Found a forward:: entry in a superclass. 209 | // Stop searching, but don't cache yet; call method 210 | // resolver for this class first. 211 | break; 212 | } 213 | } 214 | // 超类cache没有命中,从超类的方法列表寻找 215 | meth = _class_getMethodNoSuper_nolock(curClass, sel); 216 | if (meth) { 217 | log_and_fill_cache(cls, curClass, meth, sel); 218 | methodPC = method_getImplementation(meth); 219 | goto done; 220 | } 221 | } 222 | // 使用方法解析并再尝试一次 223 | if (resolver && !triedResolver) { 224 | methodListLock.unlock(); 225 | _class_resolveMethod(cls, sel, inst); 226 | triedResolver = YES; 227 | goto retry; 228 | } 229 | // 没有找到IMP指针,方法解析也没有用,使用消息转发,并将其填充入cache 230 | _cache_addForwardEntry(cls, sel); 231 | methodPC = _objc_msgForward_impcache; 232 | done: 233 | methodListLock.unlock(); 234 | // paranoia: look for ignored selectors with non-ignored implementations 235 | assert(!(ignoreSelector(sel) && methodPC != (IMP)&_objc_ignored_method)); 236 | return methodPC; 237 | } 238 | 239 | ``` 240 | 241 | 我们可以看到每一个类都维护了一个cache,在一个对象调用runtime的objc_msgSend函数后,runtime在接收者所属的类的cache中查找与_cmd所对应的IMP,如果没有命中就寻找当前类的方法列表,再找不到就跳入while循环寻找超类的cache和方法列表,如果这些方法都失效,就调用`_class_resolveMethod`查找正在插入这个类的方法,之后再重新尝试整一个流程,如果最后还是没能找到一个对应的IMP,则调用消息转发机制。 242 | 243 | ### 动态消息解析 244 | 245 | ![](https://raw.githubusercontent.com/WiInputMethod/interview/master/img/ios-runtime-method-resolve.png) 246 | 247 | 如下用于描述动态消息解析的流程: 248 | 249 | - 1.通过 resolveInstanceMethod 得知方法是否为动态添加,YES则通过 class_addMethod 动态添加方法,处理消息,否则进入下一步。dynamic 属性就与这个过程有关,当一个属性声明为 dynamic 时 就是告诉编译器:开发者一定会添加 setter/getter 的实现,而编译时不用自动生成。 250 | - 2.这步会进入 forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则进入第三步。这种方式把消息原封不动地转发给目标对象,有着比较高的效率。如果不能自己的类里面找到替代方法,可以重载这个方法,然后把消息转给其他的对象。 251 | - 3.这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 说明消息无法处理并报错 `unrecognized selector sent to instance`,如果返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果依然不能正确响应消息,则报错 `unrecognized selector sent to instance`. 252 | 253 | 可以利用 2、3 中的步骤实现对接受消息对象的转移,可以实现“多重继承”的效果。 254 | 255 | #### 参考资料 256 | * http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/ 257 | * http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html 258 | * https://github.com/opensource-apple/objc4 259 | -------------------------------------------------------------------------------- /source/iOS/Questions.md: -------------------------------------------------------------------------------- 1 | 2 | * https://github.com/ChenYilong/iOSInterviewQuestions 3 | 4 | * http://draveness.me/guan-yu-xie-ios-wen-ti-de-jie-da/ 5 | 6 | * https://github.com/lzyy/iOS-Developer-Interview-Questions -------------------------------------------------------------------------------- /source/iOS/README.md: -------------------------------------------------------------------------------- 1 | ### iOS 工程师技能树 2 | 3 | http://segmentfault.com/a/1190000002946644 4 | 5 | #### Objective-C 6 | 7 | * Objective-C语言基础 8 | * library,framework的制作 9 | * Runtime 编程 10 | * LLVM 原理和调优 11 | 12 | #### 操作系统 13 | 14 | * iOS内存管理和调优 15 | * iOS的文件系统和沙盒机制 16 | * iOS多线程编程(Thread,GCD,NSOperation) 17 | * iOS网络和服务器编程(NSURLConnection,NSURLSession) 18 | * iOS系统的各种安全机制 19 | 20 | #### 网络编程 21 | 22 | * iOS网络发送机制调整和优化(NSURLSession) 23 | * Socket编程 24 | * 网络传输中的各种保障 25 | * 对传输协议的调整优化 26 | 27 | #### 数据库&持久化方案 28 | 29 | * 常规持久化方案(Keychain,NSUserDefaults,Sqlite,CoreData) 30 | * 数据库的使用和设计(Sqlite) 31 | * 数据结构优化,Sql调优 32 | 33 | #### 图形图像编程 34 | 35 | * UIKit,Core Animation和Core Text的绘制 36 | * Core Graphics, Quartz 2D, Media Player, AV Foundation 37 | * OpenGL ES, GLKit, SpriteKit, SceneKit, Metal 38 | 39 | 40 | #### 数据结构 & 算法 41 | 42 | * 基本的算法和数据结构(排序搜索算法, 数组, 队列) 43 | * 较复杂数据结构的灵活应用(二叉树, 图等) 44 | * 复杂的专项算法(图像识别算法, 拓扑定位等等) 45 | 46 | #### 安全方案 47 | 48 | * 本地数据存储安全(Keychain) 49 | * 授权和身份验证 50 | * 传输安全(对称, 非对称, SSL) 51 | * App代码安全 52 | 53 | #### 业务能力 54 | 55 | * 一般性业务功能需求分析及实现 56 | * 重要业务模块的需求分析及实现 57 | * 中小规模产品的架构,系统设计和实现 58 | * 大规模产品或产品线的架构,系统设计和实现 59 | * 平台级产品的架构,系统设计和实现 60 | -------------------------------------------------------------------------------- /source/iOS/Swift/Class.md: -------------------------------------------------------------------------------- 1 | ## -------------------------------------------------------------------------------- /source/iOS/Swift/Function-And-Closure.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/iOS/Swift/Function-And-Closure.md -------------------------------------------------------------------------------- /source/iOS/Swift/README.md: -------------------------------------------------------------------------------- 1 | ## Swift 2 | 3 | 现在已经出来 Swift 5 了,然而作者已经不做 iOS 了,本部分内容可能很长一段时间都不会更新了。感兴趣的同学欢迎提 PR。 4 | 5 | -------------------------------------------------------------------------------- /source/iOS/Swift/Struct-And-Enum.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HIT-Alibaba/interview/33748d78515dae0edc5bd613c9852625b6ade3c3/source/iOS/Swift/Struct-And-Enum.md -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git pull --rebase 3 | git add -A 4 | git commit -am "update `date`" 5 | git push 6 | if which gitbook > /dev/null; then 7 | cd source 8 | gitbook build 9 | cd _book 10 | cp -R * ../../../interview-gitbook/ 11 | cd ../../../interview-gitbook/ 12 | git add -A 13 | git commit --author="skyline75489 " -am "[CI] auto update" 14 | git push 15 | else 16 | echo "Gitbook not installed." 17 | fi 18 | --------------------------------------------------------------------------------