├── Part1 ├── Android │ ├── AIDL.md │ ├── ANR问题.md │ ├── APP启动过程.md │ ├── Activity启动过程全解析.md │ ├── Android关于oom的解决方案.md │ ├── Android内存泄漏总结.md │ ├── Android几种进程.md │ ├── Android图片中的三级缓存.md │ ├── Android基础知识.md │ ├── Android开机过程.md │ ├── Android性能优化.md │ ├── Android系统机制.md │ ├── Art和Dalvik区别.md │ ├── Asynctask源码分析.md │ ├── Binder机制.md │ ├── Bitmap的分析与使用.md │ ├── EventBus用法详解.md │ ├── FlowchartDiagram.jpg │ ├── Fragment.md │ ├── Git操作.md │ ├── Handler内存泄漏分析及解决.md │ ├── Listview详解.md │ ├── MVC,MVP,MVVM的区别.md │ ├── MVP.md │ ├── Recyclerview和Listview的异同.md │ ├── SurfaceView.md │ ├── Zygote和System进程的启动过程.md │ ├── 事件分发机制.md │ ├── 开源框架源码分析.md │ ├── 插件化技术学习.md │ ├── 查漏补缺.md │ ├── 热修复技术.md │ ├── 线程通信基础流程分析.md │ └── 自定义控件.md └── DesignPattern │ ├── Builder模式.md │ ├── 代理模式.md │ ├── 单例模式.md │ ├── 原型模式.md │ ├── 外观模式.md │ ├── 常见的面向对象设计原则.md │ ├── 策略模式.md │ ├── 简单工厂.md │ ├── 观察者模式.md │ ├── 责任链模式.md │ └── 适配器模式.md ├── Part2 ├── JVM │ ├── JVM.md │ ├── JVM类加载机制.md │ ├── Java内存区域与内存溢出.md │ └── 垃圾回收算法.md ├── JavaConcurrent │ ├── Java并发基础知识.md │ ├── NIO.md │ ├── Synchronized.md │ ├── Thread和Runnable实现多线程的区别.md │ ├── thread与runable如何实现多线程.md │ ├── volatile变量修饰符.md │ ├── 使用wait notify notifyall实现线程间通信.md │ ├── 可重入内置锁.md │ ├── 多线程环境中安全使用集合API.md │ ├── 守护线程与阻塞线程.md │ ├── 实现内存可见的两种方法比较:加锁和volatile变量.md │ ├── 死锁.md │ ├── 生产者和消费者问题.md │ ├── 线程中断.md │ └── 线程挂起、恢复与终止的正确方法.md └── JavaSE │ ├── ArrayList 、 LinkedList 、 Vector 的底层实现和区别.md │ ├── ArrayList源码剖析.md │ ├── Arraylist.md │ ├── Arraylist和Hashmap如何扩容等.md │ ├── Collection.md │ ├── HashMap源码剖析.md │ ├── HashTable源码剖析.md │ ├── Hashmap的hashcode的作用等.md │ ├── Java中的内存泄漏.md │ ├── Java基础知识.md │ ├── Java集合框架.md │ ├── LinkedHashMap源码剖析.md │ ├── LinkedList源码剖析.md │ ├── Linkedlist.md │ ├── List.md │ ├── Queue.md │ ├── Set.md │ ├── String源码分析.md │ ├── Vector源码剖析.md │ ├── hashmap和hashtable的底层实现和区别,两者和concurrenthashmap的区别。.md │ ├── 从源码分析HashMap.md │ ├── 反射机制.md │ └── 如何表达出Collection及其子类.md ├── Part3 ├── Algorithm │ ├── LeetCode │ │ ├── two-sum.md │ │ └── zigzag-conversion.md │ ├── Lookup │ │ ├── 折半查找.md │ │ └── 顺序查找.md │ ├── Sort │ │ ├── 冒泡排序.md │ │ ├── 归并排序.md │ │ ├── 快速排序.md │ │ └── 选择排序.md │ ├── 剑指Offer │ │ ├── 1.七种方式实现singleton模式.md │ │ ├── 2.二维数组中的查找.md │ │ ├── 合并两个排序的链表.md │ │ ├── 旋转数组的最小数字.md │ │ ├── 面试题11:数值的整数次方.md │ │ ├── 面试题12:打印1到最大的n位数.md │ │ ├── 面试题44:扑克牌的顺子.md │ │ ├── 面试题45:圆圈中最后剩下的数字.md │ │ └── 面试题6:重建二叉树.md │ └── 程序员代码面试指南(左程云) │ │ ├── 1.设计一个有getMin功能的栈.md │ │ ├── 2.由两个栈组成的队列.md │ │ └── 3.如何仅用递归函数和栈操作逆序一个栈.md └── DataStructure │ ├── 数据结构(Java).md │ ├── 数组.md │ ├── 栈和队列.md │ └── 递归和非递归方式实现二叉树先、中、后序遍历.md ├── Part4 ├── Network │ ├── Http协议.md │ ├── Socket.md │ └── TCP与UDP.md └── OperatingSystem │ ├── Linux系统的IPC.md │ └── 操作系统.md ├── Part5 └── ReadingNotes │ ├── 《APP研发录》第1章读书笔记.md │ ├── 《APP研发录》第2章读书笔记.md │ ├── 《Android开发艺术探索》第一章笔记.md │ ├── 《Android开发艺术探索》第三章笔记.md │ ├── 《Android开发艺术探索》第二章笔记.md │ ├── 《Android开发艺术探索》第八章笔记.md │ ├── 《Android开发艺术探索》第十五章笔记.md │ ├── 《Android开发艺术探索》第四章笔记.md │ ├── 《Java编程思想》第一章读书笔记.md │ ├── 《Java编程思想》第二章读书笔记.md │ └── 《深入理解java虚拟机》第12章.md ├── Part6 └── InterviewExperience │ ├── Alibaba.md │ ├── 新浪微博.md │ ├── 网易杭研.md │ ├── 美团.md │ ├── 蜻蜓FM.md │ └── 豌豆荚.md └── README.md /Part1/Android/ANR问题.md: -------------------------------------------------------------------------------- 1 | #ANR 2 | --- 3 | 4 | 1、ANR排错一般有三种类型 5 | 6 | 1. KeyDispatchTimeout(5 seconds) --主要是类型按键或触摸事件在特定时间内无响应 7 | 2. BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成 8 | 3. ServiceTimeout(20 secends) --小概率事件 Service在特定的时间内无法处理完成 9 | 10 | 2、哪些操作会导致ANR 11 | 在主线程执行以下操作: 12 | 1. 高耗时的操作,如图像变换 13 | 2. 磁盘读写,数据库读写操作 14 | 3. 大量的创建新对象 15 | 16 | 17 | 3、如何避免 18 | 19 | 1. UI线程尽量只做跟UI相关的工作 20 | 2. 耗时的操作(比如数据库操作,I/O,连接网络或者别的有可能阻塞UI线程的操作)把它放在单独的线程处理 21 | 3. 尽量用Handler来处理UIThread和别的Thread之间的交互 22 | 23 | 4、解决的逻辑 24 | 1. 使用AsyncTask 25 | 1. 在doInBackground()方法中执行耗时操作 26 | 2. 在onPostExecuted()更新UI 27 | 2. 使用Handler实现异步任务 28 | 1. 在子线程中处理耗时操作 29 | 2. 处理完成之后,通过handler.sendMessage()传递处理结果 30 | 3. 在handler的handleMessage()方法中更新UI 31 | 4. 或者使用handler.post()方法将消息放到Looper中 32 | 33 | 34 | 5、如何排查 35 | 36 | 1. 首先分析log 37 | 2. 从trace.txt文件查看调用stack,adb pull data/anr/traces.txt ./mytraces.txt 38 | 3. 看代码 39 | 4. 仔细查看ANR的成因(iowait?block?memoryleak?) 40 | 41 | 6、监测ANR的Watchdog 42 | 43 | 最近出来一个叫LeakCanary 44 | 45 | #FC(Force Close) 46 | ##什么时候会出现 47 | 1. Error 48 | 2. OOM,内存溢出 49 | 3. StackOverFlowError 50 | 4. Runtime,比如说空指针异常 51 | 52 | ##解决的办法 53 | 1. 注意内存的使用和管理 54 | 2. 使用Thread.UncaughtExceptionHandler接口 55 | -------------------------------------------------------------------------------- /Part1/Android/APP启动过程.md: -------------------------------------------------------------------------------- 1 | #APP启动过程 2 | --- 3 | 4 | ![](http://7xntdm.com1.z0.glb.clouddn.com/activity_start_flow.png) 5 | 6 | * 上图就可以很好的说明App启动的过程 7 | * ActivityManagerService组织回退栈时以ActivityRecord为基本单位,所有的ActivityRecord放在同一个ArrayList里,可以将mHistory看作一个栈对象,索引0所指的对象位于栈底,索引mHistory.size()-1所指的对象位于栈顶 8 | * Zygote进程孵化出新的应用进程后,会执行ActivityThread类的main方法.在该方法里会先准备好Looper和消息队列,然后调用attach方法将应用进程绑定到ActivityManagerService,然后进入loop循环,不断地读取消息队列里的消息,并分发消息。 9 | * ActivityThread的main方法执行后,应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知**应用进程**创建入口Activity的实例,并执行它的生命周期方法 -------------------------------------------------------------------------------- /Part1/Android/Android关于oom的解决方案.md: -------------------------------------------------------------------------------- 1 | #Android关于OOM的解决方案 2 | ##OOM 3 | * 内存溢出(Out Of Memory) 4 | * 也就是说内存占有量超过了VM所分配的最大 5 | 6 | 7 | ##出现OOM的原因 8 | 1. 加载对象过大 9 | 2. 相应资源过多,来不及释放 10 | 11 | ##如何解决 12 | 1. 在内存引用上做些处理,常用的有软引用、强化引用、弱引用 13 | 2. 在内存中加载图片时直接在内存中作处理,如边界压缩 14 | 3. 动态回收内存 15 | 4. 优化Dalvik虚拟机的堆内存分配 16 | 5. 自定义堆内存大小 17 | 18 | 19 | -------------------------------------------------------------------------------- /Part1/Android/Android几种进程.md: -------------------------------------------------------------------------------- 1 | #Android几种进程 2 | --- 3 | 4 | 1. 前台进程:即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的 5 | 2. 可见进程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互 6 | 3. 服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等;当系统要空间运行前两者进程时才会被终止 7 | 4. 后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没了有内存就首先被杀死 8 | 5. 空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的 9 | 10 | 如何避免后台进程被杀死: 11 | 12 | 1. 调用startForegound,让你的Service所在的线程成为前台进程 13 | 2. Service的onStartCommond返回START_STICKY或START_REDELIVER_INTENT 14 | 3. Service的onDestroy里面重新启动自己 -------------------------------------------------------------------------------- /Part1/Android/Android图片中的三级缓存.md: -------------------------------------------------------------------------------- 1 | #Android图片中的三级缓存 2 | --- 3 | 4 | ##为什么要使用三级缓存 5 | 6 | * 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 7 | * 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响 8 | * 特别是,当我们想要重复浏览一些图片时,如果每一次浏览都需要通过网络获取,流量的浪费可想而知 9 | * 所以提出三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量 10 | 11 | ##什么是三级缓存 12 | 13 | * 网络加载,不优先加载,速度慢,浪费流量 14 | * 本地缓存,次优先加载,速度快 15 | * 内存缓存,优先加载,速度最快 16 | 17 | 18 | ##三级缓存原理 19 | 20 | * 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中 21 | * 之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片 22 | * 总之,只在初次访问新内容时,才通过网络获取图片资源 23 | 24 | 25 | 参考链接 26 | 27 | [http://www.jianshu.com/p/2cd59a79ed4a](http://www.jianshu.com/p/2cd59a79ed4a) -------------------------------------------------------------------------------- /Part1/Android/Android开机过程.md: -------------------------------------------------------------------------------- 1 | # Android开机过程 2 | 3 | * BootLoder引导,然后加载Linux内核. 4 | * 0号进程init启动.加载init.rc配置文件,配置文件有个命令启动了zygote进程 5 | * zygote开始fork出SystemServer进程 6 | * SystemServer加载各种JNI库,然后init1,init2方法,init2方法中开启了新线程ServerThread. 7 | * 在SystemServer中会创建一个socket客户端,后续AMS(ActivityManagerService)会通过此客户端和zygote通信 8 | * ServerThread的run方法中开启了AMS,还孵化新进程ServiceManager,加载注册了一溜的服务,最后一句话进入loop 死循环 9 | * run方法的SystemReady调用resumeTopActivityLocked打开锁屏界面 -------------------------------------------------------------------------------- /Part1/Android/Android性能优化.md: -------------------------------------------------------------------------------- 1 | #Android性能优化 2 | --- 3 | ##合理管理内存 4 | --- 5 | ###节制的使用Service 6 | 如果应用程序需要使用Service来执行后台任务的话,只有当任务正在执行的时候才应该让Service运行起来。当启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,系统可以在LRUcache当中缓存的进程数量也会减少,导致切换程序的时候耗费更多性能。我们可以使用IntentService,当后台任务执行结束后会自动停止,避免了Service的内存泄漏。 7 | 8 | ###当界面不可见时释放内存 9 | 当用户打开了另外一个程序,我们的程序界面已经不可见的时候,我们应当将所有和界面相关的资源进行释放。重写Activity的onTrimMemory()方法,然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别,一旦触发说明用户离开了程序,此时就可以进行资源释放操作了。 10 | 11 | ###当内存紧张时释放内存 12 | onTrimMemory()方法还有很多种其他类型的回调,可以在手机内存降低的时候及时通知我们,我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。 13 | 14 | ###避免在Bitmap上浪费内存 15 | 读取一个Bitmap图片的时候,千万不要去加载不需要的分辨率。可以压缩图片等操作。 16 | 17 | ###是有优化过的数据集合 18 | Android提供了一系列优化过后的数据集合工具类,如SparseArray、SparseBooleanArray、LongSparseArray,使用这些API可以让我们的程序更加高效。HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。 19 | 20 | ###知晓内存的开支情况 21 | * 使用枚举通常会比使用静态常量消耗两倍以上的内存,尽可能不使用枚举 22 | * 任何一个Java类,包括匿名类、内部类,都要占用大概500字节的内存空间 23 | * 任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会在一定程序上影响内存的 24 | * 使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节,因此最好使用优化后的数据集合 25 | 26 | ###谨慎使用抽象编程 27 | 在Android使用抽象编程会带来额外的内存开支,因为抽象的编程方法需要编写额外的代码,虽然这些代码根本执行不到,但是也要映射到内存中,不仅占用了更多的内存,在执行效率上也会有所降低。所以需要合理的使用抽象编程。 28 | 29 | ###尽量避免使用依赖注入框架 30 | 使用依赖注入框架貌似看上去把findViewById()这一类的繁琐操作去掉了,但是这些框架为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且将一些你用不到的对象也一并加载到内存中。这些用不到的对象会一直站用着内存空间,可能很久之后才会得到释放,所以可能多敲几行代码是更好的选择。 31 | 32 | ###使用多个进程 33 | 谨慎使用,多数应用程序不该在多个进程中运行的,一旦使用不当,它甚至会增加额外的内存而不是帮我们节省内存。这个技巧比较适用于哪些需要在后台去完成一项独立的任务,和前台是完全可以区分开的场景。比如音乐播放,关闭软件,已经完全由Service来控制音乐播放了,系统仍然会将许多UI方面的内存进行保留。在这种场景下就非常适合使用两个进程,一个用于UI展示,另一个用于在后台持续的播放音乐。关于实现多进程,只需要在Manifast文件的应用程序组件声明一个android:process属性就可以了。进程名可以自定义,但是之前要加个冒号,表示该进程是一个当前应用程序的私有进程。 34 | 35 | ##分析内存的使用情况 36 | --- 37 | 系统不可能将所有的内存都分配给我们的应用程序,每个程序都会有可使用的内存上限,被称为堆大小。不同的手机堆大小不同,如下代码可以获得堆大小: 38 | 39 | ``` 40 | ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); 41 | int heapSize = manager.getMemoryClass(); 42 | ``` 43 | 结果以MB为单位进行返回,我们开发时应用程序的内存不能超过这个限制,否则会出现OOM。 44 | 45 | ###Android的GC操作 46 | Android系统会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。GC操作会从一个叫做Roots的对象开始检查,所有它可以访问到的对象就说明还在使用当中,应该进行保留,而其他的对系那个就表示已经不再被使用了。 47 | 48 | ###Android中内存泄漏 49 | Android中的垃圾回收机制并不能防止内存泄漏的出现导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,导致垃圾回收器无法去回收掉这些对象,也就是出现内存泄漏了。比如说像Activity这样的系统组件,它又会包含很多的控件甚至是图片,如果它无法被垃圾回收器回收掉的话,那就算是比较严重的内存泄漏情况了。 50 | 举个例子,在MainActivity中定义一个内部类,实例化内部类对象,在内部类新建一个线程执行死循环,会导致内部类资源无法释放,MainActivity的控件和资源无法释放,导致OOM,可借助一系列工具,比如LeakCanary。 51 | 52 | ##高性能编码优化 53 | --- 54 | 都是一些微优化,在性能方面看不出有什么显著的提升的。使用合适的算法和数据结构是优化程序性能的最主要手段。 55 | 56 | ###避免创建不必要的对象 57 | 不必要的对象我们应该避免创建: 58 | 59 | * 如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。 60 | * 在没有特殊原因的情况下,尽量使用基本数据类型来代替封装数据类型,int比Integer要更加有效,其它数据类型也是一样。 61 | * 当一个方法的返回值是String的时候,通常需要去判断一下这个String的作用是什么,如果明确知道调用方会将返回的String再进行拼接操作的话,可以考虑返回一个StringBuffer对象来代替,因为这样可以将一个对象的引用进行返回,而返回String的话就是创建了一个短生命周期的临时对象。 62 | * 基本数据类型的数组也要优于对象数据类型的数组。另外两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效的多。 63 | 64 | 尽可能地少创建临时对象,越少的对象意味着越少的GC操作。 65 | 66 | ###静态优于抽象 67 | 如果你并不需要访问一个对系那个中的某些字段,只是想调用它的某些方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,调用速度提升15%-20%,同时也不用为了调用这个方法去专门创建对象了,也不用担心调用这个方法后是否会改变对象的状态(静态方法无法访问非静态字段)。 68 | 69 | ###对常量使用static final修饰符 70 | ``` 71 | static int intVal = 42; 72 | static String strVal = "Hello, world!"; 73 | ``` 74 | 编译器会为上面的代码生成一个初始方法,称为方法,该方法会在定义类第一次被使用的时候调用。这个方法会将42的值赋值到intVal当中,从字符串常量表中提取一个引用赋值到strVal上。当赋值完成后,我们就可以通过字段搜寻的方式去访问具体的值了。 75 | 76 | final进行优化: 77 | 78 | ``` 79 | static final int intVal = 42; 80 | static final String strVal = "Hello, world!"; 81 | ``` 82 | 83 | 这样,定义类就不需要方法了,因为所有的常量都会在dex文件的初始化器当中进行初始化。当我们调用intVal时可以直接指向42的值,而调用strVal会用一种相对轻量级的字符串常量方式,而不是字段搜寻的方式。 84 | 85 | 这种优化方式只对基本数据类型以及String类型的常量有效,对于其他数据类型的常量是无效的。 86 | 87 | ###使用增强型for循环语法 88 | 89 | ``` 90 | static class Counter { 91 | int mCount; 92 | } 93 | 94 | Counter[] mArray = ... 95 | 96 | public void zero() { 97 | int sum = 0; 98 | for (int i = 0; i < mArray.length; ++i) { 99 | sum += mArray[i].mCount; 100 | } 101 | } 102 | 103 | public void one() { 104 | int sum = 0; 105 | Counter[] localArray = mArray; 106 | int len = localArray.length; 107 | for (int i = 0; i < len; ++i) { 108 | sum += localArray[i].mCount; 109 | } 110 | } 111 | 112 | public void two() { 113 | int sum = 0; 114 | for (Counter a : mArray) { 115 | sum += a.mCount; 116 | } 117 | } 118 | ``` 119 | 120 | zero()最慢,每次都要计算mArray的长度,one()相对快得多,two()fangfa在没有JIT(Just In Time Compiler)的设备上是运行最快的,而在有JIT的设备上运行效率和one()方法不相上下,需要注意这种写法需要JDK1.5之后才支持。 121 | 122 | Tips:ArrayList手写的循环比增强型for循环更快,其他的集合没有这种情况。因此默认情况下使用增强型for循环,而遍历ArrayList使用传统的循环方式。 123 | 124 | ###多使用系统封装好的API 125 | 126 | 系统提供不了的Api完成不了我们需要的功能才应该自己去写,因为使用系统的Api很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。 127 | 举个例子,实现数组拷贝的功能,使用循环的方式来对数组中的每一个元素一一进行赋值当然可行,但是直接使用系统中提供的System.arraycopy()方法会让执行效率快9倍以上。 128 | 129 | ###避免在内部调用Getters/Setters方法 130 | 131 | 面向对象中封装的思想是不要把类内部的字段暴露给外部,而是提供特定的方法来允许外部操作相应类的内部字段。但在Android中,字段搜寻比方法调用效率高得多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。但是编写代码还是要按照面向对象思维的,我们应该在能优化的地方进行优化,比如避免在内部调用getters/setters方法。 132 | 133 | ##布局优化技巧 134 | --- 135 | ###重用布局文件 136 | **** 137 | 138 | 标签可以允许在一个布局当中引入另一个布局,那么比如说我们程序的所有界面都有一个公共的部分,这个时候最好的做法就是将这个公共的部分提取到一个独立的布局中,然后每个界面的布局文件当中来引用这个公共的布局。 139 | 140 | Tips:如果我们要在标签中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写xiaoguo将不会生效。 141 | 142 | **** 143 | 144 | 标签是作为标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时引用文件时产生多余的布局嵌套。布局嵌套越多,解析起来就越耗时,性能就越差。因此编写布局文件时应该让嵌套的层数越少越好。 145 | 146 | 举例:比如在LinearLayout里边使用一个布局。里边又有一个LinearLayout,那么其实就存在了多余的布局嵌套,使用merge可以解决这个问题。 147 | 148 | ###仅在需要时才加载布局 149 | 150 | 某个布局当中的元素不是一起显示出来的,普通情况下只显示部分常用的元素,而那些不常用的元素只有在用户进行特定操作时才会显示出来。 151 | 152 | 举例:填信息时不是需要全部填的,有一个添加更多字段的选项,当用户需要添加其他信息的时候,才将另外的元素显示到界面上。用VISIBLE性能表现一般,可以用ViewStub。ViewStub也是View的一种,但是没有大小,没有绘制功能,也不参与布局,资源消耗非常低,可以认为完全不影响性能。 153 | 154 | ``` 155 | 161 | ``` 162 | 163 | ``` 164 | public void onMoreClick() { 165 | ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub); 166 | if (viewStub != null) { 167 | View inflatedView = viewStub.inflate(); 168 | editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1); 169 | editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2); 170 | editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3); 171 | } 172 | } 173 | ``` 174 | 175 | tips:ViewStub所加载的布局是不可以使用标签的,因此这有可能导致加载出来出来的布局存在着多余的嵌套结构。 176 | 177 | -------------------------------------------------------------------------------- /Part1/Android/Android系统机制.md: -------------------------------------------------------------------------------- 1 | #Android系统机制 2 | --- 3 | ###APP启动过程 4 | 5 | 1. Launcher线程捕获onclick的点击事件,调用Launcher.startActivitySafely,进一步调用Launcher.startActivity,最后调用父类Activity的startActivity。 6 | 2. Activity和ActivityManagerService交互,引入Instrumentation,将启动请求交给Instrumentation,调用Instrumentation.execStartActivity 7 | 3. 8 | 9 | ###Android内核解读-应用的安装过程 10 | 11 | [http://blog.csdn.net/singwhatiwanna/article/details/19578947](http://blog.csdn.net/singwhatiwanna/article/details/19578947) 12 | apk的安装过程分为两步: 13 | 14 | 1. 将apk文件复制到程序目录下(/data/app/) 15 | 2. 为应用创建数据目录(/data/data/package name/)、提取dex文件到指定目录(/data/delvik-cache/)、修改系统包管理信息。 16 | 17 | 18 | ###View的事件体系 19 | 20 | 21 | 22 | ###Handler消息机制 23 | 24 | 25 | ###AsyncTask 26 | AyncTask是一个抽象类。 27 | 28 | 需要重写的方法有四个: 29 | 1. onPreExecute() 30 | 这个方法会在后台任务开始之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等 31 | 2. doInBackGround(Params...) 32 | 在子线程中运行,不可更新UI,返回的是执行结果,第三个参数为Void不返回 33 | 3. onProgressUpdate(Progress...) 34 | 利用参数可以进行UI操作。 35 | 4. onPostExecute(Result) 36 | 返回的数据会作为参数传递到此方法中,可以利用返回的一些数据来进行一些UI操作。 37 | 38 | ``` 39 | class DownloadTask extends AsyncTask { 40 | 41 | @Override 42 | protected void onPreExecute() { 43 | progressDialog.show(); 44 | } 45 | 46 | @Override 47 | protected Boolean doInBackground(Void... params) { 48 | try { 49 | while (true) { 50 | int downloadPercent = doDownload(); 51 | publishProgress(downloadPercent); 52 | if (downloadPercent >= 100) { 53 | break; 54 | } 55 | } 56 | } catch (Exception e) { 57 | return false; 58 | } 59 | return true; 60 | } 61 | 62 | @Override 63 | protected void onProgressUpdate(Integer... values) { 64 | progressDialog.setMessage("当前下载进度:" + values[0] + "%"); 65 | } 66 | 67 | @Override 68 | protected void onPostExecute(Boolean result) { 69 | progressDialog.dismiss(); 70 | if (result) { 71 | Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show(); 72 | } else { 73 | Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show(); 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | 启动这个任务: 80 | 81 | ``` 82 | new DownloadTask().execute(); 83 | ``` 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | ###图片缓存机制 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Part1/Android/Art和Dalvik区别.md: -------------------------------------------------------------------------------- 1 | #ART和Dalvik区别 2 | --- 3 | 4 | Art上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是"空间换时间"。 5 | 6 | ART: Ahead of Time Dalvik: Just in Time 7 | 8 | 什么是Dalvik:Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 9 | 10 | 什么是ART:Android操作系统已经成熟,Google的Android团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。 11 | 12 | ART优点: 13 | 14 | 1. 系统性能的显著提升 15 | 2. 应用启动更快、运行更快、体验更流畅、触感反馈更及时 16 | 3. 更长的电池续航能力 17 | 4. 支持更低的硬件 18 | 19 | ART缺点: 20 | 21 | 1. 更大的存储空间占用,可能会增加10%-20% 22 | 2. 更长的应用安装时间 -------------------------------------------------------------------------------- /Part1/Android/Asynctask源码分析.md: -------------------------------------------------------------------------------- 1 | #AsyncTask 2 | --- 3 | 4 | 5 | 首先从Android3.0开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常,这样做是为了避免主线程由于耗时操作所阻塞从而出现ANR现象。AsyncTask封装了线程池和Handler。AsyncTask有两个线程池:SerialExecutor和THREAD_POOL_EXECUTOR。前者是用于任务的排队,默认是串行的线程池:后者用于真正的执行任务。AsyncTask还有一个Handler,叫InternalHandler,用于将执行环境从线程池切换到主线程。AsyncTask内部就是通过InternalHandler来发送任务执行的进度以及执行结束等消息。 6 | 7 | AsyncTask排队执行过程:系统先把参数Params封装为FutureTask对象,它相当于Runnable,接着FutureTask交给SerialExcutor的execute方法,它先把FutureTask插入到任务队列tasks中,如果这个时候没有正在活动的AsyncTask任务,那么就会执行下一个AsyncTask任务,同时当一个AsyncTask任务执行完毕之后,AsyncTask会继续执行其他任务直到所有任务都被执行为止。 8 | 9 | 10 | --- 11 | 12 | 关于线程池,AsyncTask对应的线程池ThreadPoolExecutor都是进程范围内共享的,都是static的,所以是AsyncTask控制着进程范围内所有的子类实例。由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0默认串行执行,不会出现这个问题)。针对这种情况。可以尝试自定义线程池,配合AsyncTask使用。 13 | 14 | -------------------------------------------------------------------------------- /Part1/Android/Binder机制.md: -------------------------------------------------------------------------------- 1 | #Binder机制 2 | --- 3 | 4 | 首先Binder是Android系统进程间通信(IPC)方式之一。 5 | 6 | Binder使用Client-Server通信方式。Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信。 7 | 8 | Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名字为XX的Binder,它位于Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体结点以及ServiceManager对实体的引用,将名字以及新建的引用打包给ServiceManager。ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。但是一个Server若向ServiceManager注册自己Binder就必须通过0这个引用和ServiceManager的Binder通信。Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Clent也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请名字叫XX的Binder的引用。ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder引用,将该引用作为回复发送给发起请求的Client。 9 | 10 | 当然,不是所有的Binder都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向ServiceManager注册名字,所以是匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。 11 | 12 | --- 13 | 14 | 15 | ###为什么Binder只进行了一次数据拷贝? 16 | 17 | 18 | Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。 19 | 20 | 21 | 最底层的是Android的ashmen(Anonymous shared memory)机制,它负责辅助实现内存的分配,以及跨进程所需要的内存共享。AIDL(android interface definition language)对Binder的使用进行了封装,可以让开发者方便的进行方法的远程调用,后面会详细介绍。Intent是最高一层的抽象,方便开发者进行常用的跨进程调用。 22 | 23 | 从英文字面上意思看,Binder具有粘结剂的意思那么它是把什么东西粘接在一起呢?在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动,其中Client、Server、Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘连剂了,其中,核心组件便是Binder驱动程序了,ServiceManager提供了辅助管理的功能,Client和Server正是Binder驱动和ServiceManager提供的基础设施上,进行Client-Server之间的通信。 24 | 25 | 1. Client、Server和ServiceManager实现在用户空间中,Binder驱动实现在内核空间中 26 | 2. Binder驱动程序和ServiceManager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server 27 | 3. Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信 28 | 4. Client和Server之间的进程间通信通过Binder驱动程序间接实现 29 | 5. ServiceManager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力 30 | 31 | 服务器端:一个Binder服务器就是一个Binder类的对象。当创建一个Binder对象后,内部就会开启一个线程,这个线程用户接收binder驱动发送的消息,收到消息后,会执行相关的服务代码。 32 | 33 | Binder驱动:当服务端成功创建一个Binder对象后,Binder驱动也会相应创建一个mRemote对象,该对象的类型也是Binder类,客户就可以借助这个mRemote对象来访问远程服务。 34 | 35 | 客户端:客户端要想访问Binder的远程服务,就必须获取远程服务的Binder对象在binder驱动层对应的binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后,就可以调用相应Binde对象的服务了。 36 | 37 | 在这里我们可以看到,客户是通过Binder驱动来调用服务端的相关服务。首先,在服务端创建一个Binder对象,接着客户端通过获取Binder驱动中Binder对象的引用来调用服务端的服务。在Binder机制中正是借着Binder驱动将不同进程间的组件bind(粘连)在一起,实现通信。 38 | 39 | mmap将一个文件或者其他对象映射进内存。文件被映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会凋零。munmap执行相反的操作,删除特定地址区域的对象映射。 40 | 41 | 当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用。但需注意,直接对该段内存写时不会写入超过当前文件大小的内容。 42 | 43 | 使用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次内存数据:一次从输入文件到共享内存区,另一次从共享内存到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域,而是保持共享区域,直到通信完成为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除内存映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。 44 | 45 | aidl主要就帮助了我们完成了包装数据和解包的过程,并调用了transact过程,而用来传递的数据包我们就称为parcel 46 | 47 | AIDL:xxx.aidl -> xxx.java ,注册service 48 | 49 | 1. 用aidl定义需要被调用方法接口 50 | 2. 实现这些方法 51 | 3. 调用这些方法 52 | 53 | -------------------------------------------------------------------------------- /Part1/Android/EventBus用法详解.md: -------------------------------------------------------------------------------- 1 | #EventBus 2 | --- 3 | 4 | ###概述 5 | 6 | EventBus是一款针对Android优化的发布/订阅(publish/subscribe)事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅。以及将发送者和接收者解耦。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。 7 | 8 | ###EventBus作为一个消息总线,有三个主要的元素: 9 | 10 | * Event:事件。可以是任意类型的对象 11 | * Subscriber:事件订阅者,接收特定的事件。在EventBus中,使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数,具体来说,函数的名字是onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个,这个和 12 | ThreadMode(下面讲)有关。 13 | * Publisher:事件发布者,用于通知 Subscriber 有事件发生。可以在任意线程任意位置发送事件,直接调用eventBus.post(Object) 方法,可以自己实例化 EventBus 14 | 对象,但一般使用默认的单例就好了:EventBus.getDefault(), 根据post函数参数的类型,会自动调用订阅相应类型事件的函数。 15 | 16 | ###关于ThreadMode 17 | 18 | 前面说了,Subscriber的函数只能是那4个,因为每个事件订阅函数都是和一个ThreadMode相关联的,ThreadMode指定了会调用的函数。有以下四个ThreadMode: 19 | 20 | * PostThread:事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程。对应的函数名是onEvent。 21 | * MainThread: 事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的,对应的函数名是onEventMainThread。 22 | * BackgroundThread:事件的处理会在一个后台线程中执行,对应的函数名是onEventBackgroundThread,虽然名字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接执行事件,如果当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。 23 | * Async:事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程(有线程池),但最好限制线程的数目。 24 | 25 | 根据事件订阅都函数名称的不同,会使用不同的ThreadMode,比如果在后台线程加载了数据想在UI线程显示,订阅者只需把函数命名onEventMainThread。 26 | 27 | 对相应的函数名,进一步解释一下: 28 | 29 | **onEvent**:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。 30 | 31 | **onEventMainThread**:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。 32 | 33 | **onEventBackground**:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。 34 | 35 | **onEventAsync**:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync。 36 | 37 | ##基本用法 38 | 39 | ###引入EventBus: 40 | 41 | ``` 42 | compile 'org.greenrobot:eventbus:3.0.0' 43 | ``` 44 | 45 | 定义事件: 46 | 47 | ``` 48 | public class MessageEvent { /* Additional fields if needed */ } 49 | ``` 50 | 51 | 注册事件接收者: 52 | 53 | ``` 54 | eventBus.register(this); 55 | ``` 56 | 57 | 发送事件: 58 | 59 | ``` 60 | eventBus.post(event) 61 | ``` 62 | 63 | 接收消息并处理: 64 | 65 | ``` 66 | public void onEvent(MessageEvent event) {} 67 | ``` 68 | 69 | 注销事件接收: 70 | 71 | ``` 72 | eventBus.unregister(this); 73 | ``` 74 | 75 | 最后,proguard 需要做一些额外处理: 76 | 77 | ``` 78 | #EventBus 79 | -keepclassmembers class ** { 80 | public void onEvent*(**); 81 | void onEvent*(**); 82 | } 83 | ``` 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Part1/Android/FlowchartDiagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anAngryAnt/LearningNotes/02b07f61815770493d5eaa8006ed8c9cfc95030f/Part1/Android/FlowchartDiagram.jpg -------------------------------------------------------------------------------- /Part1/Android/Fragment.md: -------------------------------------------------------------------------------- 1 | #Fragment 2 | 3 | ##为何产生 4 | * 同时适配手机和平板、UI和逻辑的共享。 5 | 6 | ##介绍 7 | * Fragment也会被加入回退栈中。 8 | * Fragment拥有自己的生命周期和接受、处理用户的事件 9 | * 可以动态的添加、替换和移除某个Fragment 10 | 11 | ##生命周期 12 | * 必须依存于Activity 13 | 14 | ![Mou icon](http://img.blog.csdn.net/20140719225005356?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG1qNjIzNTY1Nzkx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 15 | 16 | * Fragment依附于Activity的生命状态 17 | * ![Mou icon](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/FlowchartDiagram.jpg?raw=true) 18 | 19 | 生命周期中那么多方法,懵逼了的话我们就一起来看一下每一个生命周期方法的含义吧。 20 | 21 | ##Fragment生命周期方法含义: 22 | * `public void onAttach(Context context)` 23 | * onAttach方法会在Fragment于窗口关联后立刻调用。从该方法开始,就可以通过Fragment.getActivity方法获取与Fragment关联的窗口对象,但因为Fragment的控件未初始化,所以不能够操作控件。 24 | 25 | * `public void onCreate(Bundle savedInstanceState)` 26 | * 在调用完onAttach执行完之后立刻调用onCreate方法,可以在Bundle对象中获取一些在Activity中传过来的数据。通常会在该方法中读取保存的状态,获取或初始化一些数据。在该方法中不要进行耗时操作,不然窗口不会显示。 27 | 28 | * `public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)` 29 | * 该方法是Fragment很重要的一个生命周期方法,因为会在该方法中创建在Fragment显示的View,其中inflater是用来装载布局文件的,container是``标签的父标签对应对象,savedInstanceState参数可以获取Fragment保存的状态,如果未保存那么就为null。 30 | 31 | * `public void onViewCreated(View view,Bundle savedInstanceState)` 32 | * Android在创建完Fragment中的View对象之后,会立刻回调该方法。其种view参数就是onCreateView中返回的view,而bundle对象用于一般用途。 33 | 34 | * `public void onActivityCreated(Bundle savedInstanceState)` 35 | * 在Activity的onCreate方法执行完之后,Android系统会立刻调用该方法,表示窗口已经初始化完成,从这一个时候开始,就可以在Fragment中使用getActivity().findViewById(Id);来操控Activity中的view了。 36 | 37 | * `public void onStart()` 38 | * 这个没啥可讲的,但有一个细节需要知道,当系统调用该方法的时候,fragment已经显示在ui上,但还不能进行互动,因为onResume方法还没执行完。 39 | 40 | * `public void onResume()` 41 | * 该方法为fragment从创建到显示Android系统调用的最后一个生命周期方法,调用完该方法时候,fragment就可以与用户互动了。 42 | 43 | * `public void onPause()` 44 | * fragment由活跃状态变成非活跃状态执行的第一个回调方法,通常可以在这个方法中保存一些需要临时暂停的工作。如保存音乐播放进度,然后在onResume中恢复音乐播放进度。 45 | 46 | * `public void onStop()` 47 | * 当onStop返回的时候,fragment将从屏幕上消失。 48 | 49 | * `public void onDestoryView()` 50 | * 该方法的调用意味着在 `onCreateView` 中创建的视图都将被移除。 51 | 52 | * `public void onDestroy()` 53 | * Android在Fragment不再使用时会调用该方法,要注意的是~这时Fragment还和Activity藕断丝连!并且可以获得Fragment对象,但无法对获得的Fragment进行任何操作(呵~呵呵~我已经不听你的了)。 54 | 55 | * `public void onDetach()` 56 | * 为Fragment生命周期中的最后一个方法,当该方法执行完后,Fragment与Activity不再有关联(分手!我们分手!!(╯‵□′)╯︵┻━┻)。 57 | 58 | ##Fragment比Activity多了几个额外的生命周期回调方法: 59 | * onAttach(Activity):当Fragment和Activity发生关联时使用 60 | * onCreateView(LayoutInflater, ViewGroup, Bundle):创建该Fragment的视图 61 | * onActivityCreate(Bundle):当Activity的onCreate方法返回时调用 62 | * onDestoryView():与onCreateView相对应,当该Fragment的视图被移除时调用 63 | * onDetach():与onAttach相对应,当Fragment与Activity关联被取消时调用 64 | 65 | ###注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现 66 | 67 | ##Fragment与Activity之间的交互 68 | * Fragment与Activity之间的交互可以通过`Fragment.setArguments(Bundle args)`以及`Fragment.getArguments()`来实现。 69 | 70 | ##Fragment状态的持久化。 71 | 由于Activity会经常性的发生配置变化,所以依附它的Fragment就有需要将其状态保存起来问题。下面有两个常用的方法去将Fragment的状态持久化。 72 | 73 | * 方法一: 74 | * 可以通过`protected void onSaveInstanceState(Bundle outState)`,`protected void onRestoreInstanceState(Bundle savedInstanceState)` 状态保存和恢复的方法将状态持久化。 75 | * 方法二(更方便,让Android自动帮我们保存Fragment状态): 76 | * 我们只需要将Fragment在Activity中作为一个变量整个保存,只要保存了Fragment,那么Fragment的状态就得到保存了,所以呢..... 77 | * `FragmentManager.putFragment(Bundle bundle, String key, Fragment fragment)` 是在Activity中保存Fragment的方法。 78 | * `FragmentManager.getFragment(Bundle bundle, String key)` 是在Activity中获取所保存的Frament的方法。 79 | * 很显然,key就传入Fragment的id,fragment就是你要保存状态的fragment,但,我们注意到上面的两个方法,第一个参数都是Bundle,这就意味着*FragmentManager*是通过Bundle去保存Fragment的。但是,这个方法仅仅能够保存Fragment中的控件状态,比如说EditText中用户已经输入的文字(*注意!在这里,控件需要设置一个id,否则Android将不会为我们保存控件的状态*),而Fragment中需要持久化的变量依然会丢失,但依然有解决办法,就是利用方法一! 80 | * 下面给出状态持久化的事例代码: 81 | ``` 82 | /** Activity中的代码 **/ 83 | FragmentB fragmentB; 84 | 85 | @Override 86 | protected void onCreate(@Nullable Bundle savedInstanceState) { 87 | super.onCreate(savedInstanceState); 88 | setContentView(R.layout.fragment_activity); 89 | if( savedInstanceState != null ){ 90 | fragmentB = (FragmentB) getSupportFragmentManager().getFragment(savedInstanceState,"fragmentB"); 91 | } 92 | init(); 93 | } 94 | 95 | @Override 96 | protected void onSaveInstanceState(Bundle outState) { 97 | if( fragmentB != null ){ 98 | getSupportFragmentManager().putFragment(outState,"fragmentB",fragmentB); 99 | } 100 | 101 | super.onSaveInstanceState(outState); 102 | } 103 | 104 | /** Fragment中保存变量的代码 **/ 105 | 106 | @Nullable 107 | @Override 108 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 109 | AppLog.e("onCreateView"); 110 | if ( null != savedInstanceState ){ 111 | String savedString = savedInstanceState.getString("string"); 112 | //得到保存下来的string 113 | } 114 | View root = inflater.inflate(R.layout.fragment_a,null); 115 | return root; 116 | } 117 | 118 | @Override 119 | public void onSaveInstanceState(Bundle outState) { 120 | outState.putString("string","anAngryAnt"); 121 | super.onSaveInstanceState(outState); 122 | } 123 | 124 | ``` 125 | 126 | ##静态的使用Fragment 127 | 1. 继承Fragment,重写onCreateView决定Fragment的布局 128 | 2. 在Activity中声明此Fragment,就和普通的View一样 129 | 130 | ##Fragment常用的API 131 | * android.support.v4.app.Fragment 主要用于定义Fragment 132 | * android.support.v4.app.FragmentManager 主要用于在Activity中操作Fragment,可以使用FragmentManager.findFragmenById,FragmentManager.findFragmentByTag等方法去找到一个Fragment 133 | * android.support.v4.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词 134 | * 主要的操作都是FragmentTransaction的方法 135 | (一般我们为了向下兼容,都使用support.v4包里面的Fragment) 136 | 137 | 138 | getFragmentManager() // Fragment若使用的是support.v4包中的,那就使用getSupportFragmentManager代替 139 | 140 | 141 | 142 | * 主要的操作都是FragmentTransaction的方法 143 | 144 | ``` 145 | 146 | FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务 147 | transaction.add() 148 | //往Activity中添加一个Fragment 149 | 150 | transaction.remove() 151 | //从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。 152 | 153 | transaction.replace() 154 | //使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~ 155 | 156 | transaction.hide() 157 | //隐藏当前的Fragment,仅仅是设为不可见,并不会销毁 158 | 159 | transaction.show() 160 | //显示之前隐藏的Fragment 161 | 162 | detach() 163 | //当fragment被加入到回退栈的时候,该方法与*remove()*的作用是相同的, 164 | //反之,该方法只是将fragment从视图中移除, 165 | //之后仍然可以通过*attach()*方法重新使用fragment, 166 | //而调用了*remove()*方法之后, 167 | //不仅将Fragment从视图中移除,fragment还将不再可用。 168 | 169 | attach() 170 | //重建view视图,附加到UI上并显示。 171 | 172 | transatcion.commit() 173 | //提交一个事务 174 | 175 | ``` 176 | 177 | ##管理Fragment回退栈 178 | * 跟踪回退栈状态 179 | * 我们通过实现*``OnBackStackChangedListener``*接口来实现回退栈状态跟踪,具体如下 180 | ``` 181 | public class XXX implements FragmentManager.OnBackStackChangedListener 182 | 183 | /** 实现接口所要实现的方法 **/ 184 | 185 | @Override 186 | public void onBackStackChanged() { 187 | //do whatevery you want 188 | } 189 | 190 | /** 设置回退栈监听接口 **/ 191 | getSupportFragmentManager().addOnBackStackChangedListener(this); 192 | 193 | 194 | ``` 195 | 196 | * 管理回退栈 197 | * ``FragmentTransaction.addToBackStack(String)`` *--将一个刚刚添加的Fragment加入到回退栈中* 198 | * ``getSupportFragmentManager().getBackStackEntryCount()`` *-获取回退栈中实体数量* 199 | * ``getSupportFragmentManager().popBackStack(String name, int flags)`` *-根据name立刻弹出栈顶的fragment* 200 | * ``getSupportFragmentManager().popBackStack(int id, int flags)`` *-根据id立刻弹出栈顶的fragment* 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /Part1/Android/Git操作.md: -------------------------------------------------------------------------------- 1 | # Git 操作 2 | 3 | ## git 命令 4 | 5 | * 创建本地仓库 6 | 7 | ``` 8 | git init 9 | ``` 10 | 11 | * 获取远程仓库 12 | 13 | ``` 14 | git clone [url] 15 | 例:git clone https://github.com/you/yourpro.git 16 | ``` 17 | 18 | * 创建远程仓库 19 | 20 | ``` 21 | // 添加一个新的 remote 远程仓库 22 | git remote add [remote-name] [url] 23 | 例:git remote add origin https://github.com/you/yourpro.git 24 | origin:相当于该远程仓库的别名 25 | 26 | // 列出所有 remote 的别名 27 | git remote 28 | 29 | // 列出所有 remote 的 url 30 | git remote -v 31 | 32 | // 删除一个 renote 33 | git remote rm [name] 34 | 35 | // 重命名 remote 36 | git remote rename [old-name] [new-name] 37 | ``` 38 | 39 | * 从本地仓库中删除 40 | 41 | ``` 42 | git rm file.txt // 从版本库中移除,删除文件 43 | git rm file.txt -cached // 从版本库中移除,不删除原始文件 44 | git rm -r xxx // 从版本库中删除指定文件夹 45 | ``` 46 | 47 | * 从本地仓库中添加新的文件 48 | 49 | ``` 50 | git add . // 添加所有文件 51 | git add file.txt // 添加指定文件 52 | ``` 53 | 54 | * 提交,把缓存内容提交到 HEAD 里 55 | 56 | ``` 57 | git commit -m "注释" 58 | ``` 59 | 60 | * 撤销 61 | 62 | ``` 63 | // 撤销最近的一个提交. 64 | git revert HEAD 65 | 66 | // 取消 commit + add 67 | git reset --mixed 68 | 69 | // 取消 commit 70 | git reset --soft 71 | 72 | // 取消 commit + add + local working 73 | git reset --hard 74 | ``` 75 | 76 | * 把本地提交 push 到远程服务器 77 | 78 | ``` 79 | git push [remote-name] [loca-branch]:[remote-branch] 80 | 例:git push origin master:master 81 | ``` 82 | 83 | * 查看状态 84 | 85 | ``` 86 | git status 87 | ``` 88 | 89 | * 从远程库中下载新的改动 90 | 91 | ``` 92 | git fetch [remote-name]/[branch] 93 | ``` 94 | 95 | * 合并下载的改动到分支 96 | 97 | ``` 98 | git merge [remote-name]/[branch] 99 | ``` 100 | 101 | * 从远程库中下载新的改动 102 | 103 | ``` 104 | pull = fetch + merge 105 | 106 | git pull [remote-name] [branch] 107 | 例:git pull origin master 108 | ``` 109 | 110 | * 分支 111 | 112 | ``` 113 | // 列出分支 114 | git branch 115 | 116 | // 创建一个新的分支 117 | git branch (branch-name) 118 | 119 | // 删除一个分支 120 | git branch -d (branch-nam) 121 | 122 | // 删除 remote 的分支 123 | git push (remote-name) :(remote-branch) 124 | ``` 125 | 126 | * 切换分支 127 | 128 | ``` 129 | // 切换到一个分支 130 | git checkout [branch-name] 131 | 132 | // 创建并切换到该分支 133 | git checkout -b [branch-name] 134 | ``` 135 | 136 | ##与github建立ssh通信,让Git操作免去输入密码的繁琐。 137 | * 首先呢,我们先建立ssh密匙。 138 | > ssh key must begin with 'ssh-ed25519', 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521'. -- from github 139 | 140 | 根据以上文段我们可以知道github所支持的ssh密匙类型,这里我们创建ssh-rsa密匙。 141 | 在command line 中输入以下指令:``ssh-keygen -t rsa``去创建一个ssh-rsa密匙。如果你并不需要为你的密匙创建密码和修改名字,那么就一路回车就OK,如果你需要,请您自行Google翻译,因为只是英文问题。 142 | >$ ssh-keygen -t rsa 143 | Generating public/private rsa key pair. 144 | //您可以根据括号中的路径来判断你的.ssh文件放在了什么地方 145 | Enter file in which to save the key (/c/Users/Liang Guan Quan/.ssh/id_rsa): 146 | 147 | * 到 https://github.com/settings/keys 这个地址中去添加一个新的SSH key,然后把你的xx.pub文件下的内容文本都复制到Key文本域中,然后就可以提交了。 148 | * 添加完成之后 我们用``ssh git@github.com`` 命令来连通一下github,如果你在response里面看到了你github账号名,那么就说明配置成功了。 *let's enjoy github ;)* 149 | 150 | 151 | ## gitignore 152 | --- 153 | 154 | 在本地仓库根目录创建 .gitignore 文件。Win7 下不能直接创建,可以创建 ".gitignore." 文件,后面的标点自动被忽略; 155 | 156 | ``` 157 | /.idea // 过滤指定文件夹 158 | /fd/* // 忽略根目录下的 /fd/ 目录的全部内容; 159 | *.iml // 过滤指定的所有文件 160 | !.gitignore // 不忽略该文件 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /Part1/Android/Handler内存泄漏分析及解决.md: -------------------------------------------------------------------------------- 1 | #Handler内存泄漏分析及解决 2 | --- 3 | 4 | ###一、介绍 5 | 6 | 首先,请浏览下面这段handler代码: 7 | 8 | ``` 9 | public class SampleActivity extends Activity { 10 | private final Handler mLeakyHandler = new Handler() { 11 | @Override 12 | public void handleMessage(Message msg) { 13 | // ... 14 | } 15 | } 16 | } 17 | ``` 18 | 19 | 在使用handler时,这是一段很常见的代码。但是,它却会造成严重的内存泄漏问题。在实际编写中,我们往往会得到如下警告: 20 | 21 | ``` 22 | ⚠ In Android, Handler classes should be static or leaks might occur. 23 | 24 | ``` 25 | 26 | ###二、分析 27 | 28 | 1、 Android角度 29 | 30 | 当Android应用程序启动时,framework会为该应用程序的主线程创建一个Looper对象。这个Looper对象包含一个简单的消息队列Message Queue,并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件,例如Activity生命周期方法调用、button点击等,这些消息都会被添加到消息队列中并被逐个处理。 31 | 32 | 另外,主线程的Looper对象会伴随该应用程序的整个生命周期。 33 | 34 | 然后,当主线程里,实例化一个Handler对象后,它就会自动与主线程Looper的消息队列关联起来。所有发送到消息队列的消息Message都会拥有一个对Handler的引用,所以当Looper来处理消息时,会据此回调[Handler#handleMessage(Message)]方法来处理消息。 35 | 36 | 2、 Java角度 37 | 38 | 在java里,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会。 39 | 40 | ###三、泄漏来源 41 | 42 | 请浏览下面一段代码: 43 | 44 | ``` 45 | public class SampleActivity extends Activity { 46 | 47 | private final Handler mLeakyHandler = new Handler() { 48 | @Override 49 | public void handleMessage(Message msg) { 50 | // ... 51 | } 52 | } 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | 58 | // Post a message and delay its execution for 10 minutes. 59 | mLeakyHandler.postDelayed(new Runnable() { 60 | @Override 61 | public void run() { /* ... */ } 62 | }, 1000 * 60 * 10); 63 | 64 | // Go back to the previous Activity. 65 | finish(); 66 | } 67 | } 68 | ``` 69 | 70 | 当activity结束(finish)时,里面的延时消息在得到处理前,会一直保存在主线程的消息队列里持续10分钟。而且,由上文可知,这条消息持有对handler的引用,而handler又持有对其外部类(在这里,即SampleActivity)的潜在引用。这条引用关系会一直保持直到消息得到处理,从而,这阻止了SampleActivity被垃圾回收器回收,同时造成应用程序的泄漏。 71 | 72 | <<<<<<< HEAD 73 | 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。 74 | 75 | ======= 76 | 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。 77 | 78 | >>>>>>> c67abfcfd66909095068cb5f0c8632dc5547131b 79 | ###四、泄漏解决方案 80 | 81 | 首先,上面已经明确了内存泄漏来源: 82 | 83 | 只要有未处理的消息,那么消息会引用handler,非静态的handler又会引用外部类,即Activity,导致Activity无法被回收,造成泄漏; 84 | 85 | Runnable类属于非静态匿名类,同样会引用外部类。 86 | 87 | 为了解决遇到的问题,我们要明确一点:静态内部类不会持有对外部类的引用。所以,我们可以把handler类放在单独的类文件中,或者使用静态内部类便可以避免泄漏。 88 | 89 | 另外,如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样统一不会导致内存泄漏。 90 | 91 | 对于匿名类Runnable,同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。 92 | 93 | ``` 94 | public class SampleActivity extends Activity { 95 | 96 | /** 97 | * Instances of static inner classes do not hold an implicit 98 | * reference to their outer class. 99 | */ 100 | private static class MyHandler extends Handler { 101 | private final WeakReference mActivity; 102 | 103 | public MyHandler(SampleActivity activity) { 104 | mActivity = new WeakReference(activity); 105 | } 106 | 107 | @Override 108 | public void handleMessage(Message msg) { 109 | SampleActivity activity = mActivity.get(); 110 | if (activity != null) { 111 | // ... 112 | } 113 | } 114 | } 115 | 116 | private final MyHandler mHandler = new MyHandler(this); 117 | 118 | /** 119 | * Instances of anonymous classes do not hold an implicit 120 | * reference to their outer class when they are "static". 121 | */ 122 | private static final Runnable sRunnable = new Runnable() { 123 | @Override 124 | public void run() { /* ... */ } 125 | }; 126 | 127 | @Override 128 | protected void onCreate(Bundle savedInstanceState) { 129 | super.onCreate(savedInstanceState); 130 | 131 | // Post a message and delay its execution for 10 minutes. 132 | mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 133 | 134 | // Go back to the previous Activity. 135 | finish(); 136 | } 137 | } 138 | ``` 139 | 140 | ###五、小结 141 | 142 | <<<<<<< HEAD 143 | 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。 144 | 145 | 原文链接: 146 | 147 | [http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820) 148 | ======= 149 | 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。 150 | 151 | 原文链接: 152 | 153 | [http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820) 154 | >>>>>>> c67abfcfd66909095068cb5f0c8632dc5547131b 155 | -------------------------------------------------------------------------------- /Part1/Android/Listview详解.md: -------------------------------------------------------------------------------- 1 | #ListView详解 2 | --- 3 | 直接继承自AbsListView,AbsListView继承自AdapterView,AdapterView又继承自ViewGroup。 4 | 5 | Adpater在ListView和数据源之间起到了一个桥梁的作用 6 | 7 | ###RecycleBin机制 8 | 9 | RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。RecycleBin是AbsListView的一个内部类。 10 | 11 | * RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews中。 12 | * mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的时候将会返回null,所以mActiveViews不能被重复利用。 13 | * addScrapView()用于将一个废弃的View进行缓存,该方法接收一个View参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕)就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapV 14 | * iews和mCurrentScrap这两个List来存储废弃View。 15 | * getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。 16 | * 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。 17 | 18 | View的流程分三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。 19 | -------------------------------------------------------------------------------- /Part1/Android/MVC,MVP,MVVM的区别.md: -------------------------------------------------------------------------------- 1 | #MVC,MVP,MVVM的区别 2 | --- 3 | 4 | 5 | #MVC 6 | 软件可以分为三部分 7 | 8 | * 视图(View):用户界面 9 | * 控制器(Controller):业务逻辑 10 | * 模型(Model):数据保存 11 | 12 | 各部分之间的通信方式如下: 13 | 14 | 1. View传送指令到Controller 15 | 2. Controller完成业务逻辑后,要求Model改变状态 16 | 3. Model将新的数据发送到View,用户得到反馈 17 | 18 | Tips:所有的通信都是单向的。 19 | 20 | #互动模式 21 | 接受用户指令时,MVC可以分为两种方式。一种是通过View接受指令,传递给Controller。 22 | 23 | 另一种是直接通过Controller接受指令 24 | 25 | 26 | #MVP 27 | 28 | MVP模式将Controller改名为Presenter,同时改变了通信方向。 29 | 30 | 1. 各部分之间的通信,都是双向的 31 | 2. View和Model不发生联系,都通过Presenter传递 32 | 3. View非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而Presenter非常厚,所有逻辑都部署在那里。 33 | 34 | 35 | #MVVM 36 | 37 | MVVM模式将Presenter改名为ViewModel,基本上与MVP模式完全一致。 38 | 39 | 唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在ViewModel,反之亦然。 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Part1/Android/MVP.md: -------------------------------------------------------------------------------- 1 | #MVP 2 | --- 3 | ###为什么需要MVP 4 | 5 | 1. 尽量简单 6 | 大部分的安卓应用只使用View-Model结构,程序员现在更多的是和复杂的View打交道而不是解决业务逻辑。当你在应用中只使用Model-View时,到最后,你会发现“所有的事物都被连接到一起”。复杂的任务被分成细小的任务,并且很容易解决。越小的东西,bug越少,越容易debug,更好测试。在MVP模式下的View层将会变得简单,所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。 7 | 2. 后台任务 8 | 当你编写一个Actviity、Fragment、自定义View的时候,你会把所有的和后台任务相关的方法写在一个静态类或者外部类中。这样,你的Task不再和Activity联系在一起,这既不会导致内存泄露,也不依赖于Activity的重建。 9 | 10 | ###优缺点 11 | 12 | 优点: 13 | 14 | 1. 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle 15 | 2. 模块职责划分明显,层次清晰 16 | 3. 隐藏数据 17 | 4. Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下) 18 | 5. 利于测试驱动开发。以前的Android开发是难以进行单元测试的(虽然很多Android开发者都没有写过测试用例,但是随着项目变得越来越复杂,没有测试是很难保证软件质量的;而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧),在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。 19 | 6. View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。 20 | 7. 代码灵活性 21 | 22 | 缺点: 23 | 24 | 1. Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。 25 | 2. 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。 26 | 3. 如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。 27 | 4. 额外的代码复杂度及学习成本。 28 | 29 | **在MVP模式里通常包含4个要素:** 30 | 31 | 1. View :负责绘制UI元素、与用户进行交互(在Android中体现为Activity); 32 | 2. View interface :需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试; 33 | 3. Model :负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合); 34 | 4. Presenter :作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。 -------------------------------------------------------------------------------- /Part1/Android/Recyclerview和Listview的异同.md: -------------------------------------------------------------------------------- 1 | #RecyclerView和ListView的异同 2 | --- 3 | 4 | * ViewHolder是用来保存视图引用的类,无论是ListView亦或是RecyclerView。只不过在ListView中,ViewHolder需要自己来定义,且这只是一种推荐的使用方式,不使用当然也可以,这不是必须的。只不过不使用ViewHolder的话,ListView每次getView的时候都会调用findViewById(int),这将导致ListView性能展示迟缓。而在RecyclerView中使用RecyclerView.ViewHolder则变成了必须,尽管实现起来稍显复杂,但它却解决了ListView面临的上述不使用自定义ViewHolder时所面临的问题。 5 | * 我们知道ListView只能在垂直方向上滚动,Android API没有提供ListView在水平方向上面滚动的支持。或许有多种方式实现水平滑动,但是请想念我,ListView并不是设计来做这件事情的。但是RecyclerView相较于ListView,在滚动上面的功能扩展了许多。它可以支持多种类型列表的展示要求,主要如下: 6 | 7 | 1. LinearLayoutManager,可以支持水平和竖直方向上滚动的列表。 8 | 2. StaggeredGridLayoutManager,可以支持交叉网格风格的列表,类似于瀑布流或者Pinterest。 9 | 3. GridLayoutManager,支持网格展示,可以水平或者竖直滚动,如展示图片的画廊。 10 | 11 | * 列表动画是一个全新的、拥有无限可能的维度。起初的Android API中,删除或添加item时,item是无法产生动画效果的。后面随着Android的进化,Google的Chat Hasse推荐使用ViewPropertyAnimator属性动画来实现上述需求。 12 | 相比较于ListView,RecyclerView.ItemAnimator则被提供用于在RecyclerView添加、删除或移动item时处理动画效果。同时,如果你比较懒,不想自定义ItemAnimator,你还可以使用DefaultItemAnimator。 13 | 14 | * ListView的Adapter中,getView是最重要的方法,它将视图跟position绑定起来,是所有神奇的事情发生的地方。同时我们也能够通过registerDataObserver在Adapter中注册一个观察者。RecyclerView也有这个特性,RecyclerView.AdapterDataObserver就是这个观察者。ListView有三个Adapter的默认实现,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。然而,RecyclerView的Adapter则拥有除了内置的内DB游标和ArrayList的支持之外的所有功能。RecyclerView.Adapter的实现的,我们必须采取措施将数据提供给Adapter,正如BaseAdapter对ListView所做的那样。 15 | * 在ListView中如果我们想要在item之间添加间隔符,我们只需要在布局文件中对ListView添加如下属性即可: 16 | 17 | ``` 18 | android:divider="@android:color/transparent" 19 | android:dividerHeight="5dp" 20 | ``` 21 | * ListView通过AdapterView.OnItemClickListener接口来探测点击事件。而RecyclerView则通过RecyclerView.OnItemTouchListener接口来探测触摸事件。它虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限。 22 | * ListView可以设置选择模式,并添加MultiChoiceModeListener,如下所示: 23 | 24 | ``` 25 | listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); 26 | listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { 27 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { ... } 28 | public void onItemCheckedStateChanged(ActionMode mode, int position, 29 | long id, boolean checked) { ... } 30 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 31 | switch (item.getItemId()) { 32 | case R.id.menu_item_delete_crime: 33 | CrimeAdapter adapter = (CrimeAdapter)getListAdapter(); 34 | CrimeLab crimeLab = CrimeLab.get(getActivity()); 35 | for (int i = adapter.getCount() - 1; i >= 0; i--) { 36 | if (getListView().isItemChecked(i)) { 37 | crimeLab.deleteCrime(adapter.getItem(i)); 38 | } 39 | } 40 | mode.finish(); 41 | adapter.notifyDataSetChanged(); 42 | return true; 43 | default: 44 | return false; 45 | } 46 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { ... } 47 | public void onDestroyActionMode(ActionMode mode) { ... } 48 | }); 49 | ``` 50 | 而RecyclerView则没有此功能。 51 | 52 | [http://www.cnblogs.com/littlepanpc/p/4497290.html](http://www.cnblogs.com/littlepanpc/p/4497290.html) -------------------------------------------------------------------------------- /Part1/Android/SurfaceView.md: -------------------------------------------------------------------------------- 1 | ##为什么要使用SurfaceView来实现动画? 2 | ###因为View的绘图存在以下缺陷: 3 | 1. View缺乏双缓冲机制 4 | 2. 当程序需要更新View上的图像时,程序必须重绘View上显示的整张图片 5 | 3. 新线程无法直接更新View组件 6 | 7 | 8 | 9 | ##SurfaceView的绘图机制 10 | * 一般会与SurfaceView结合使用 11 | * 调用SurfaceView的getHolder()方法即可获得SurfaceView关联的SurfaceHolder 12 | 13 | ##SurfaceHolder提供了如下方法来获取Canvas对象 14 | 1. Canvas lockCanvas():锁定整个SurfaceView对象,获取该Surface上的Canvas 15 | 2. Canvas lockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域,获取该Surface上的Canvas 16 | 3. unlockCanvasAndPost(canvas):释放绘图、提交所绘制的图形,需要注意,当调用SurfaceHolder上的unlockCanvasAndPost方法之后,该方法之前所绘制的图形还处于缓冲之中,下一次lockCanvas()方法锁定的区域可能会“遮挡”它 17 | 18 | 19 | ``` 20 | 21 | public class SurfaceViewTest extends Activity 22 | { 23 | // SurfaceHolder负责维护SurfaceView上绘制的内容 24 | private SurfaceHolder holder; 25 | private Paint paint; 26 | 27 | @Override 28 | public void onCreate(Bundle savedInstanceState) 29 | { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.main); 32 | paint = new Paint(); 33 | SurfaceView surface = (SurfaceView) findViewById(R.id.show); 34 | // 初始化SurfaceHolder对象 35 | holder = surface.getHolder(); 36 | holder.addCallback(new Callback() 37 | { 38 | @Override 39 | public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, 40 | int arg3) 41 | { 42 | } 43 | 44 | @Override 45 | public void surfaceCreated(SurfaceHolder holder) 46 | { 47 | // 锁定整个SurfaceView 48 | Canvas canvas = holder.lockCanvas(); 49 | // 绘制背景 50 | Bitmap back = BitmapFactory.decodeResource( 51 | SurfaceViewTest.this.getResources() 52 | , R.drawable.sun); 53 | // 绘制背景 54 | canvas.drawBitmap(back, 0, 0, null); 55 | // 绘制完成,释放画布,提交修改 56 | holder.unlockCanvasAndPost(canvas); 57 | // 重新锁一次,"持久化"上次所绘制的内容 58 | holder.lockCanvas(new Rect(0, 0, 0, 0)); 59 | holder.unlockCanvasAndPost(canvas); 60 | } 61 | 62 | @Override 63 | public void surfaceDestroyed(SurfaceHolder holder) 64 | { 65 | } 66 | }); 67 | // 为surface的触摸事件绑定监听器 68 | surface.setOnTouchListener(new OnTouchListener() 69 | { 70 | @Override 71 | public boolean onTouch(View source, MotionEvent event) 72 | { 73 | // 只处理按下事件 74 | if (event.getAction() == MotionEvent.ACTION_DOWN) 75 | { 76 | int cx = (int) event.getX(); 77 | int cy = (int) event.getY(); 78 | // 锁定SurfaceView的局部区域,只更新局部内容 79 | Canvas canvas = holder.lockCanvas(new Rect(cx - 50, 80 | cy - 50, cx + 50, cy + 50)); 81 | // 保存canvas的当前状态 82 | canvas.save(); 83 | // 旋转画布 84 | canvas.rotate(30, cx, cy); 85 | paint.setColor(Color.RED); 86 | // 绘制红色方块 87 | canvas.drawRect(cx - 40, cy - 40, cx, cy, paint); 88 | // 恢复Canvas之前的保存状态 89 | canvas.restore(); 90 | paint.setColor(Color.GREEN); 91 | // 绘制绿色方块 92 | canvas.drawRect(cx, cy, cx + 40, cy + 40, paint); 93 | // 绘制完成,释放画布,提交修改 94 | holder.unlockCanvasAndPost(canvas); 95 | } 96 | return false; 97 | } 98 | }); 99 | } 100 | } 101 | 102 | ``` 103 | 104 | 上面的程序为SurfaceHolder添加了一个CallBack实例,该Callback中定义了如下三个方法: 105 | 106 | * void surfaceChanged(SurfaceHolder holder, int format, int width, int height):当一个surface的格式或大小发生改变时回调该方法。 107 | * void surfaceCreated(SurfaceHolder holder):当surface被创建时回调该方法 108 | * void surfaceDestroyed(SurfaceHolder holder):当surface将要被销毁时回调该方法 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Part1/Android/事件分发机制.md: -------------------------------------------------------------------------------- 1 | # 事件分发机制 2 | --- 3 | 4 | 5 | * 对于一个根ViewGroup来说,发生点击事件首先调用dispatchTouchEvent 6 | * 如果这个ViewGroup的onIterceptTouchEvent返回true就表示它要拦截当前事件,接着这个ViewGroup的onTouchEvent就会被调用.如果onIterceptTouchEvent返回false,那么就会继续向下调用子View的dispatchTouchEvent方法 7 | * 当一个View需要处理事件的时候,如果它没有设置onTouchListener,那么直接调用onTouchEvent.如果设置了Listenter 那么就要看Listener的onTouch方法返回值.为true就不调,为false就调onTouchEvent 8 | * View的默认实现会在onTouchEvent里面把touch事件解析成Click之类的事件 9 | * 点击事件传递顺序 Activity -> Window -> View 10 | * 一旦一个元素拦截了某事件,那么一个事件序列里面后续的Move,Down事件都会交给它处理.并且它的onInterceptTouchEvent不会再调用 11 | * View的onTouchEvent默认都会消耗事件,除非它的clickable和longClickable都是false(不可点击),但是enable属性不会影响 -------------------------------------------------------------------------------- /Part1/Android/开源框架源码分析.md: -------------------------------------------------------------------------------- 1 | #框架源码分析 2 | --- 3 | 4 | ###Retrofit 5 | --- 6 | 7 | 8 | ###EventBus 9 | --- 10 | 11 | ###Glide 12 | --- 13 | 14 | -------------------------------------------------------------------------------- /Part1/Android/插件化技术学习.md: -------------------------------------------------------------------------------- 1 | ###Android动态加载dex技术初探 2 | 3 | [http://blog.csdn.net/u013478336/article/details/50734108](http://blog.csdn.net/u013478336/article/details/50734108) 4 | 5 | Android使用Dalvik虚拟机加载可执行程序,所以不能直接加载基于class的jar,而是需要将class转化为dex字节码。 6 | 7 | Android支持动态加载的两种方式是:DexClassLoader和PathClassLoader,DexClassLoader可加载jar/apk/dex,且支持从SD卡加载;PathClassLoader据说只能加载已经安装在Android系统内APK文件。 8 | 9 | ###Android插件化基础 10 | 11 | Android简单来说就是如下操作: 12 | 13 | * 开发者将插件代码封装成Jar或者APK 14 | * 宿主下载或者从本地加载Jar或者APK到宿主中 15 | * 将宿主调用插件中的算法或者Android特定的Class(如Activity) 16 | 17 | ###插件化开发—动态加载技术加载已安装和未安装的apk 18 | 19 | [http://blog.csdn.net/u010687392/article/details/47121729?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io](http://blog.csdn.net/u010687392/article/details/47121729?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io) 20 | 为什么引入动态加载技术? 21 | 22 | * 一个应用程序dex文件的方法数最大不能超过65536个 23 | * 可以让应用程序实现插件化、插拔式结构,对后期维护有益 24 | 25 | 什么是动态加载技术 26 | 27 | 动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源(class、图片、color等等)进而供宿主app使用。 28 | 29 | 关于动态加载使用的类加载器 30 | 31 | * PathClassLoader - 只能加载已经安装的apk,即/data/app目录下的apk。 32 | * DexClassLoader - 能加载手机中未安装的apk、jar、dex,只要能在找到对应的路径。 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | #插件化技术学习 43 | --- 44 | 原因: 45 | 46 | 各大厂商都碰到了AndroidNative平台的瓶颈: 47 | 48 | 1. 从技术上讲,业务逻辑的复杂代码急剧膨胀,各大厂商陆续触到65535方法数的天花板;同时,对模块热更新提出了更高的要求。 49 | 2. 在业务层面上,功能模块的解耦以及维护团队的分离也是大势所趋。 50 | 51 | 插件化技术主要解决两个问题: 52 | 53 | 1. 代码加载 54 | 2. 资源加载 55 | 56 | ###代码加载 57 | 类的加载可以使用Java的ClassLoader机制,还需要组件生命周期管理。 58 | 59 | ###资源加载 60 | 用AssetManager的隐藏方法addAssetPath。 61 | 62 | ##Android插件化原理解析——Hook机制之动态代理 63 | 64 | 使用代理机制进行API Hook进而达到方法增强。 65 | 66 | 静态代理 67 | 68 | 动态代理:可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类。 69 | 70 | ###代理Hook 71 | 72 | 如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,就可以在这个代理对象中为所欲为了;修改参数,替换返回值,称之为Hook。 73 | 74 | 整个Hook过程简要总结如下: 75 | 76 | 1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook public的对象和方法,非public不保证每个版本都一样,需要适配。 77 | 2. 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。 78 | 3. 偷梁换柱-用代理对象替换原始对象 79 | 80 | ##Android插件化原理解析——Hook机制之Binder Hook 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Part1/Android/查漏补缺.md: -------------------------------------------------------------------------------- 1 | #查漏补缺 2 | --- 3 | 4 | 请分析一张400*500尺寸的PNG图片加载到程序中占用内存中的大小 5 | 6 | [http://m.blog.csdn.net/article/details?id=7856519](http://m.blog.csdn.net/article/details?id=7856519) 7 | -------------------------------------------------------------------------------- /Part1/Android/热修复技术.md: -------------------------------------------------------------------------------- 1 | #热修复技术 2 | --- 3 | 4 | APP提早发出去的包,如果出现客户端的问题,实在是干着急,覆水难收。因此线上修复方案迫在眉睫。 5 | 6 | ###概述 7 | 8 | 基于Xposed中的思想,通过修改c层的Method实例描述,来实现更改与之对应的java方法的行为,从而达到修复的目的。 9 | 10 | ###Xposed 11 | 12 | 诞生于XDA论坛,类似一个应用平台,不同的是其提供诸多系统级的应用。可实现许多神奇的功能。Xposed需要以越狱为前提,像是iOS中的cydia。 13 | 14 | Xposed可以修改任何程序的任何java方法(需root),github上提供了XposedInstaller,是一个android app。提供很多framework层,应用层级的程序。开发者可以为其开发一些系统或应用方面的插件,自定义android系统,它甚至可以做动态权限管理(XposedMods)。 15 | 16 | ###Android系统启动与应用启动 17 | 18 | Zygote进程是Android手机系统启动后,常驻的一个名为‘受精卵’的进程。 19 | 20 | * zygote的启动实现脚本在/init.rc文件中 21 | * 启动过程中执行的二进制文件在/system/bin/app_process 22 | 23 | 任何应用程序启动时,会从zygote进程fork出一个新的进程。并装载一些必要的class,invoke一些初始化方法。这其中包括像: 24 | 25 | * ActivityThread 26 | * ServiceThread 27 | * ApplicationPackageManager 28 | 29 | 等应用启动中必要的类,触发必要的方法,比如:handleBindApplication,将此进程与对应的应用绑定的初始化方法;同时,会将zygote进程中的dalvik虚拟机实例复制一份,因此每个应用程序进程都有自己的dalvik虚拟机实例;会将已有Java运行时加载到进程中;会注册一些android核心类的jni方法到虚拟机中,支撑从c到java的启动过程。 30 | 31 | ###Xposed做了手脚 32 | 33 | Xposed在这个过程改写了app_process(源码在Xposed : a modified app_process binary),替换/system/bin/app_process这个二进制文件。然后做了两个事: 34 | 35 | 1. 通过Xposed的hook技术,在上述过程中,对上面提到的那些加载的类的方法hook。 36 | 2. 加载XposedBridge.jar 37 | 38 | 这时hook必要的方法是为了方便开发者为它开发插件,加载XposedBridge.jar是为动态hook提供了基础。在这个时候加载它意味着,所有的程序在启动时,都可以加载这个jar(因为上面提到的fork过程)。结合hook技术,从而达到了控制所有程序的所有方法。 39 | 40 | 为获得/system/bin/目录的读写权限,因而需要以root为前提。 41 | 42 | ###Xposed的hook思想 43 | 44 | 那么Xposed是怎么hook java方法的呢?要从XposedBridge看起,重点在 45 | XposedBridge.hookmethod(原方法的Member对象,含有新方法的XC_MethodHook对象);,这里会调到 46 | 47 | ``` 48 | private native synchronized static void hookMethodNative(Member method, Class declaringClass, int slot, Object additionalInfo); 49 | ``` 50 | 51 | 这个native的方法,通过这个方法,可以让所hook的方法,转向native层的一个c方法。如何做到? 52 | 53 | ``` 54 | When a transmit from java to native occurs, dvm sets up a native stack. 55 | In dvmCallJNIMethod(), dvmPlatformInvoke is used to call the native method(signature in Method.insns). 56 | ``` 57 | 58 | 在jni这个中间世界里,类型数据由jni表来沟通java和c的世界;方法由c++指针结合DVM*系(如dvmSlotToMethod,dvmDecodeIndirectRef等方法)的api方法,操作虚拟机,从而实现java方法与c方法的世界。 59 | 60 | 那么hook的过程是这样:首先通过dexclassload来load所要hook的方法,分析类后,进c层,见代码XposedBridge_hookMethodNative方法,拿到要hook的Method类,然后通过dvmslotTomethod方法获取Method*指针, 61 | 62 | ``` 63 | Method* method = dvmSlotToMethod(declaredClass, slot); 64 | ``` 65 | 66 | declaredClass就是所hook方法所在的类,对应的jobject。slot是Method类中,描述此java对象在vm中的索引;那么通过这个方法,我们就获取了c层的Method指针,通过 67 | 68 | ``` 69 | SET_METHOD_FLAG(method, ACC_NATIVE); 70 | ``` 71 | 72 | 将该方法标记为一个native方法,然后通过 73 | 74 | ``` 75 | method->nativeFunc = &hookedMethodCallback; 76 | ``` 77 | 78 | 定向c层方法到hookedMethodCallback,这样当被hook的java方法执行时,就会调到c层的hookedMethodCallback方法。 79 | 80 | 通过meth->nativeFunc重定向MethodCallBridge到hookedMethodCallback这个方法上,控制这个c++指针是无视java的private的。 81 | 82 | 另外,在method结构体中有 83 | 84 | ``` 85 | method->insns = (const u2*) hookInfo; 86 | ``` 87 | 88 | 用insns指向替换成为的方法,以便hookedMethodCallback可以获取真正期望执行的java方法。 89 | 90 | 现在所有被hook的方法,都指向了hookedMethodCallbackc方法中,然后在此方法中实现调用替换成为的java方法。 91 | 92 | ###从Xposed提炼精髓 93 | 94 | 回顾Xposed,以root为必要条件,在app_process加载XposedBidge.jar,从而实现有hook所有应用的所有方法的能力;而后续动态hook应用内的方法,其实只是load了从zypote进程复制出来的运行时的这个XposedBidge.jar,然后hook而已。因此,若在一个应用范围内的hook,root不是必须的,只是单纯的加载hook的实现方法,即可修改本应用的方法。 95 | 96 | 97 | 98 | 业界内也不乏通过「修改BaseDexClassLoader中的pathList,来动态加载dex」方式实现热修复。后者纯java实现,但需要hack类的优化流程,将打CLASS_ISPREVERIFIED标签的类,去除此标签,以解决类与类引用不在一个dex中的异常问题。这会放弃dex optimize对启动运行速度的优化。原则上,这对于方法数没有大到需要multidex的应用,损失更明显。而前者不触犯原有的优化流程,只点杀需要hook的方法,更为纯粹、有效。 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Part1/Android/线程通信基础流程分析.md: -------------------------------------------------------------------------------- 1 | > 老司机们都知道,Android的线程间通信就靠Handler、Looper、Message、MessageQueue这四个麻瓜兄弟了,那么,他们是怎么运作的呢?下面做一个基于主要源代码的大学生水平的分析。 [原文链接](http://anangryant.leanote.com/post/Handler%E3%80%81Looper%E3%80%81Message%E3%80%81MessageQueue%E5%88%86%E6%9E%90) 2 | 3 | ##Looper(先分析这个是因为能够引出四者的关系) 4 | 在Looper中,维持一个`Thread`对象以及`MessageQueue`,通过Looper的构造函数我们可以知道: 5 | ``` 6 | private Looper(boolean quitAllowed) { 7 | mQueue = new MessageQueue(quitAllowed);//传入的参数代表这个Queue是否能够被退出 8 | mThread = Thread.currentThread(); 9 | } 10 | ``` 11 | `Looper`在构造函数里干了两件事情: 12 | 1. 将线程对象指向了创建`Looper`的线程 13 | 2. 创建了一个新的`MessageQueue` 14 | 15 | 分析完构造函数之后,接下来我们主要分析两个方法: 16 | 1. `looper.loop()` 17 | 2. `looper.prepare()` 18 | 19 | ###looper.loop()(在当前线程启动一个Message loop机制,此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系) 20 | ``` 21 | public static void loop() { 22 | final Looper me = myLooper();//获得当前线程绑定的Looper 23 | if (me == null) { 24 | throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 25 | } 26 | final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue 27 | 28 | // Make sure the identity of this thread is that of the local process, 29 | // and keep track of what that identity token actually is. 30 | Binder.clearCallingIdentity(); 31 | final long ident = Binder.clearCallingIdentity(); 32 | 33 | //进入死循环,不断地去取对象,分发对象到Handler中消费 34 | for (;;) { 35 | Message msg = queue.next(); // 不断的取下一个Message对象,在这里可能会造成堵塞。 36 | if (msg == null) { 37 | // No message indicates that the message queue is quitting. 38 | return; 39 | } 40 | 41 | // This must be in a local variable, in case a UI event sets the logger 42 | Printer logging = me.mLogging; 43 | if (logging != null) { 44 | logging.println(">>>>> Dispatching to " + msg.target + " " + 45 | msg.callback + ": " + msg.what); 46 | } 47 | 48 | //在这里,开始分发Message了 49 | //至于这个target是神马?什么时候被赋值的? 50 | //我们一会分析Handler的时候就会讲到 51 | msg.target.dispatchMessage(msg); 52 | 53 | if (logging != null) { 54 | logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 55 | } 56 | 57 | // Make sure that during the course of dispatching the 58 | // identity of the thread wasn't corrupted. 59 | final long newIdent = Binder.clearCallingIdentity(); 60 | if (ident != newIdent) { 61 | Log.wtf(TAG, "Thread identity changed from 0x" 62 | + Long.toHexString(ident) + " to 0x" 63 | + Long.toHexString(newIdent) + " while dispatching to " 64 | + msg.target.getClass().getName() + " " 65 | + msg.callback + " what=" + msg.what); 66 | } 67 | 68 | //当分发完Message之后,当然要标记将该Message标记为 *正在使用* 啦 69 | msg.recycleUnchecked(); 70 | } 71 | } 72 | ``` 73 | *分析了上面的源代码,我们可以意识到,最重要的方法是:* 74 | 1. `queue.next()` 75 | 2. `msg.target.dispatchMessage(msg)` 76 | 3. `msg.recycleUnchecked()` 77 | 78 | 其实Looper中最重要的部分都是由`Message`、`MessageQueue`组成的有木有!这段最重要的代码中涉及到了四个对象,他们与彼此的关系如下: 79 | 1. `MessageQueue`:装食物的容器 80 | 2. `Message`:被装的食物 81 | 3. `Handler`(msg.target实际上就是`Handler`):食物的消费者 82 | 4. `Looper`:负责分发食物的人 83 | 84 | 85 | ###looper.prepare()(在当前线程关联一个Looper对象) 86 | ``` 87 | private static void prepare(boolean quitAllowed) { 88 | if (sThreadLocal.get() != null) { 89 | throw new RuntimeException("Only one Looper may be created per thread"); 90 | } 91 | //在当前线程绑定一个Looper 92 | sThreadLocal.set(new Looper(quitAllowed)); 93 | } 94 | ``` 95 | 以上代码只做了两件事情: 96 | 1. 判断当前线程有木有`Looper`,如果有则抛出异常(在这里我们就可以知道,Android规定一个线程只能够拥有一个与自己关联的`Looper`)。 97 | 2. 如果没有的话,那么就设置一个新的`Looper`到当前线程。 98 | 99 | -------------- 100 | ##Handler 101 | 由于我们使用Handler的通常性的第一步是: 102 | ``` 103 | Handler handler = new Handler(){ 104 | //你们有没有很好奇这个方法是在哪里被回调的? 105 | //我也是!所以接下来会分析到哟! 106 | @Override 107 | public void handleMessage(Message msg) { 108 | //Handler your Message 109 | } 110 | }; 111 | ``` 112 | 所以我们先来分析`Handler`的构造方法 113 | ``` 114 | //空参数的构造方法与之对应,这里只给出主要的代码,具体大家可以到源码中查看 115 | public Handler(Callback callback, boolean async) { 116 | //打印内存泄露提醒log 117 | .... 118 | 119 | //获取与创建Handler线程绑定的Looper 120 | mLooper = Looper.myLooper(); 121 | if (mLooper == null) { 122 | throw new RuntimeException( 123 | "Can't create handler inside thread that has not called Looper.prepare()"); 124 | } 125 | //获取与Looper绑定的MessageQueue 126 | //因为一个Looper就只有一个MessageQueue,也就是与当前线程绑定的MessageQueue 127 | mQueue = mLooper.mQueue; 128 | mCallback = callback; 129 | mAsynchronous = async; 130 | 131 | } 132 | ``` 133 | *带上问题:* 134 | 1. `Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? 135 | 2. `handler.handleMessage(msg)`在什么时候被回调的? 136 | 137 | ###A1:`Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? 138 | 要分析这个问题,很自然的我们想到从发送消息开始,无论是`handler.sendMessage(msg)`还是`handler.sendEmptyMessage(what)`,我们最终都可以追溯到以下方法 139 | ``` 140 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 141 | //引用Handler中的MessageQueue 142 | //这个MessageQueue就是创建Looper时被创建的MessageQueue 143 | MessageQueue queue = mQueue; 144 | 145 | if (queue == null) { 146 | RuntimeException e = new RuntimeException( 147 | this + " sendMessageAtTime() called with no mQueue"); 148 | Log.w("Looper", e.getMessage(), e); 149 | return false; 150 | } 151 | //将新来的Message加入到MessageQueue中 152 | return enqueueMessage(queue, msg, uptimeMillis); 153 | } 154 | ``` 155 | 156 | 我们接下来分析`enqueueMessage(queue, msg, uptimeMillis)`: 157 | ``` 158 | private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 159 | //显而易见,大写加粗的赋值啊! 160 | **msg.target = this;** 161 | if (mAsynchronous) { 162 | msg.setAsynchronous(true); 163 | } 164 | return queue.enqueueMessage(msg, uptimeMillis); 165 | } 166 | ``` 167 | 168 | 169 | ###A2:`handler.handleMessage(msg)`在什么时候被回调的? 170 | 通过以上的分析,我们很明确的知道`Message`中的`target`是在什么时候被赋值的了,我们先来分析在`Looper.loop()`中出现过的过的`dispatchMessage(msg)`方法 171 | 172 | ``` 173 | public void dispatchMessage(Message msg) { 174 | if (msg.callback != null) { 175 | handleCallback(msg); 176 | } else { 177 | if (mCallback != null) { 178 | if (mCallback.handleMessage(msg)) { 179 | return; 180 | } 181 | } 182 | //看到这个大写加粗的方法调用没! 183 | **handleMessage(msg);** 184 | } 185 | } 186 | ``` 187 | 188 | 加上以上分析,我们将之前分析结果串起来,就可以知道了某些东西: 189 | `Looper.loop()`不断地获取`MessageQueue`中的`Message`,然后调用与`Message`绑定的`Handler`对象的`dispatchMessage`方法,最后,我们看到了`handleMessage`就在`dispatchMessage`方法里被调用的。 190 | 191 | ------------------ 192 | 通过以上的分析,我们可以很清晰的知道Handler、Looper、Message、MessageQueue这四者的关系以及如何合作的了。 193 | 194 | #总结: 195 | 当我们调用`handler.sendMessage(msg)`方法发送一个`Message`时,实际上这个`Message`是发送到**与当前线程绑定**的一个`MessageQueue`中,然后**与当前线程绑定**的`Looper`将会不断的从`MessageQueue`中取出新的`Message`,调用`msg.target.dispathMessage(msg)`方法将消息分发到与`Message`绑定的`handler.handleMessage()`方法中。 196 | 197 | 一个`Thread`对应多个`Handler` 198 | 一个`Thread`对应一个`Looper`和`MessageQueue`,`Handler`与`Thread`共享`Looper`和`MessageQueue`。 199 | `Message`只是消息的载体,将会被发送到**与线程绑定的唯一的**`MessageQueue`中,并且被**与线程绑定的唯一的**`Looper`分发,被与其自身绑定的`Handler`消费。 200 | 201 | ------ 202 | - Enjoy Android :) 如果有误,轻喷,欢迎指正。 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /Part1/Android/自定义控件.md: -------------------------------------------------------------------------------- 1 | #自定义控件 2 | --- 3 | 自定义View的步骤: 4 | 5 | - 自定义View的属性 6 | - 在View的构造方法中获得我们自定义View的步骤 7 | - [3.重写onMeasure](不必须) 8 | - 重写onDraw -------------------------------------------------------------------------------- /Part1/DesignPattern/Builder模式.md: -------------------------------------------------------------------------------- 1 | #Builder模式 2 | --- 3 | 4 | ##模式介绍 5 | --- 6 | 7 | ###模式的定义 8 | 9 | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 10 | 11 | ###模式的使用场景 12 | 13 | 1. 相同的方法,不同的执行顺序,产生不同的事件结果时; 14 | 2. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时; 15 | 3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适; 16 | 17 | 18 | ###Android源码中的模式实现 19 | 20 | 在Android源码中,我们最常用到的Builder模式就是AlertDialog.Builder, 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 : 21 | 22 | ``` 23 | //显示基本的AlertDialog 24 | private void showDialog(Context context) { 25 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 26 | builder.setIcon(R.drawable.icon); 27 | builder.setTitle("Title"); 28 | builder.setMessage("Message"); 29 | builder.setPositiveButton("Button1", 30 | new DialogInterface.OnClickListener() { 31 | public void onClick(DialogInterface dialog, int whichButton) { 32 | setTitle("点击了对话框上的Button1"); 33 | } 34 | }); 35 | builder.setNeutralButton("Button2", 36 | new DialogInterface.OnClickListener() { 37 | public void onClick(DialogInterface dialog, int whichButton) { 38 | setTitle("点击了对话框上的Button2"); 39 | } 40 | }); 41 | builder.setNegativeButton("Button3", 42 | new DialogInterface.OnClickListener() { 43 | public void onClick(DialogInterface dialog, int whichButton) { 44 | setTitle("点击了对话框上的Button3"); 45 | } 46 | }); 47 | builder.create().show(); // 构建AlertDialog, 并且显示 48 | } 49 | ``` 50 | 51 | ##优点与缺点 52 | --- 53 | 54 | ###优点 55 | 56 | * 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节; 57 | * 建造者独立,容易扩展; 58 | * 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。 59 | 60 | ###缺点 61 | 62 | * 会产生多余的Builder对象以及Director对象,消耗内存; 63 | * 对象的构建过程暴露。 -------------------------------------------------------------------------------- /Part1/DesignPattern/代理模式.md: -------------------------------------------------------------------------------- 1 | #代理模式 2 | --- 3 | 4 | ##模式介绍 5 | 6 | 代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 7 | 8 | ##模式的使用场景 9 | 10 | 就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。 11 | 12 | ##角色介绍 13 | 14 | * 抽象对象角色:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。 15 | * 目标对象角色:定义了代理对象所代表的目标对象。 16 | * 代理对象角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。 17 | 18 | ##优点与缺点 19 | --- 20 | 21 | 优点 22 | 23 | 给对象增加了本地化的扩展性,增加了存取操作控制 24 | 25 | 缺点 26 | 27 | 会产生多余的代理类 -------------------------------------------------------------------------------- /Part1/DesignPattern/单例模式.md: -------------------------------------------------------------------------------- 1 | #单例模式 2 | --- 3 | 4 | ###定义 5 | >保证一个类仅有一个实例,并提供一个访问它的全局访问点。 6 | 7 | >Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。 8 | 9 | 10 | * 饿汉式: 11 | ``` 12 | private static Singleton uniqueInstance = new Singleton(); 13 | ``` 14 | * 懒汉式 15 | ``` 16 | private static Singleton uniqueInstance = null; 17 | ``` 18 | 19 | ###功能 20 | 单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。 21 | 22 | ###范围 23 | Java里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现单例类的时候就会创建一个类的实例。 24 | 25 | 懒汉式单例有延迟加载和缓存的思想 26 | 27 | ###优缺点 28 | * 懒汉式是典型的时间换空间 29 | * 饿汉式是典型的空间换时间 30 | 31 | --- 32 | * 不加同步的懒汉式是线程不安全的。比如,有两个线程,一个是线程A,一个是线程B,它们同时调用getInstance方法,就可能导致并发问题。 33 | 34 | * 饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。 35 | 36 | --- 37 | 38 | 如何实现懒汉式的线程安全? 39 | 40 | 加上synchronized即可 41 | 42 | ``` 43 | public static synchronized Singleton getInstance(){} 44 | ``` 45 | 46 | 但这样会降低整个访问的速度,而且每次都要判断。可以用双重检查加锁。 47 | 48 | 双重加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。这是第二重检查。 49 | 50 | 双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。 51 | 52 | ``` 53 | 54 | /** 55 | * 双重检查加锁的单例模式 56 | * @author dream 57 | * 58 | */ 59 | public class Singleton { 60 | 61 | /** 62 | * 对保存实例的变量添加volitile的修饰 63 | */ 64 | private volatile static Singleton instance = null; 65 | private Singleton(){ 66 | 67 | } 68 | 69 | public static Singleton getInstance(){ 70 | //先检查实例是否存在,如果不存在才进入下面的同步块 71 | if(instance == null){ 72 | //同步块,线程安全的创建实例 73 | synchronized (Singleton.class) { 74 | //再次检查实例是否存在,如果不存在才真正的创建实例 75 | instance = new Singleton(); 76 | } 77 | } 78 | return instance; 79 | } 80 | 81 | } 82 | 83 | ``` 84 | 85 | ###一种更好的单例实现方式 86 | 87 | ``` 88 | public class Singleton { 89 | 90 | /** 91 | * 类级的内部类,也就是静态类的成员式内部类,该内部类的实例与外部类的实例 92 | * 没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载 93 | * @author dream 94 | * 95 | */ 96 | private static class SingletonHolder{ 97 | /** 98 | * 静态初始化器,由JVM来保证线程安全 99 | */ 100 | private static Singleton instance = new Singleton(); 101 | } 102 | 103 | /** 104 | * 私有化构造方法 105 | */ 106 | private Singleton(){ 107 | 108 | } 109 | 110 | public static Singleton getInstance(){ 111 | return SingletonHolder.instance; 112 | } 113 | } 114 | 115 | ``` 116 | 117 | 根据《高效Java第二版》中的说法,单元素的枚举类型已经成为实现Singleton的最佳方法。 118 | 119 | ``` 120 | package example6; 121 | 122 | /** 123 | * 使用枚举来实现单例模式的示例 124 | * @author dream 125 | * 126 | */ 127 | public class Singleton { 128 | 129 | /** 130 | * 定义一个枚举的元素,它就代表了Singleton的一个实例 131 | */ 132 | uniqueInstance; 133 | 134 | /** 135 | * 示意方法,单例可以有自己的操作 136 | */ 137 | public void singletonOperation(){ 138 | //功能树立 139 | } 140 | } 141 | 142 | ``` 143 | --- 144 | 145 | ###本质 146 | 控制实例数量 147 | 148 | ###何时选用单例模式 149 | 当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。 150 | 151 | -------------------------------------------------------------------------------- /Part1/DesignPattern/原型模式.md: -------------------------------------------------------------------------------- 1 | #原型模式 2 | --- 3 | 4 | ##模式介绍 5 | 6 | ###模式的定义 7 | 8 | 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 9 | 10 | ### 模式的使用场景 11 | 12 | 1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗; 13 | 2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式; 14 | 3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。 15 | 16 | ##Android源码中的模式实现 17 | 18 | Intent中使用了原型模式 19 | 20 | ``` 21 | Uri uri = Uri.parse("smsto:0800000123"); 22 | Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri); 23 | shareIntent.putExtra("sms_body", "The SMS text"); 24 | 25 | Intent intent = (Intent)shareIntent.clone() ; 26 | startActivity(intent); 27 | ``` 28 | 29 | ##优点与缺点 30 | 31 | ###优点 32 | 原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。 33 | 34 | 35 | ###缺点 36 | 这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。 -------------------------------------------------------------------------------- /Part1/DesignPattern/外观模式.md: -------------------------------------------------------------------------------- 1 | #外观模式 2 | --- 3 | 4 | ###定义 5 | 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 6 | 7 | ###外观模式的目的 8 | 9 | 不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单的使用子系统。 10 | 11 | ###优缺点 12 | 1. 优点 13 | * 松散耦合 14 | * 简单易用 15 | * 更好的划分访问的层次 16 | 2. 缺点 17 | * 过多的或者是不太合理的Facade也容易让人迷惑。到底是调用Facade好还是直接调用模块好。 18 | 19 | ###本质 20 | 封装交互,简化调用 21 | 22 | ###何时选用外观模式 23 | * 如果你希望为复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式。使用外观对象对实现大部分客户需要的功能,从而简化客户的使用。 24 | * 如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性。 25 | * 如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样就可以简化层间调用,也可以松散层次之间的依赖关系。 26 | -------------------------------------------------------------------------------- /Part1/DesignPattern/常见的面向对象设计原则.md: -------------------------------------------------------------------------------- 1 | #常见的面向对象设计原则 2 | 3 | 1. 单一职责原则 SRP 4 | 一个类应该仅有一个引起它变化的原因。 5 | 2. 开放关闭原则 OCP 6 | 一个类应该对外扩展开放,对修改关闭。 7 | 3. 里氏替换原则 LSP 8 | 子类型能够替换掉它们的父类型。 9 | 4. 依赖倒置原则 DIP 10 | 要依赖于抽象,不要依赖于具体类,要做到依赖倒置,应该做到: 11 | * 高层模块不应该依赖底层模块,二者都应该依赖于抽象。 12 | * 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。 13 | 5. 接口隔离原则 ISP 14 | 不应该强迫客户依赖于他们不用的方法。 15 | 6. 最少知识原则 LKP 16 | 只和你的朋友谈话。 17 | 7. 其他原则 18 | * 面向接口编程 19 | * 优先使用组合,而非继承 20 | * 一个类需要的数据应该隐藏在类的内部 21 | * 类之间应该零耦合,或者只有传导耦合,换句话说,类之间要么没关系,要么只使用另一个类的接口提供的操作 22 | * 在水平方向上尽可能统一地分布系统功能 23 | 24 | 25 | -------------------------------------------------------------------------------- /Part1/DesignPattern/策略模式.md: -------------------------------------------------------------------------------- 1 | #策略模式 2 | --- 3 | 4 | ##模式的定义 5 | 6 | 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 7 | 8 | 注:针对同一类型操作,将复杂多样的处理方式分别开来,有选择的实现各自特有的操作。 9 | 10 | ##模式的使用场景 11 | 12 | * 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。 13 | * 需要安全的封装多种同一类型的操作时。 14 | * 出现同一抽象多个子类,而又需要使用if-else 或者 switch-case来选择时。 15 | 16 | ##Android源码中的模式实现 17 | 18 | 19 | 20 | 21 | 策略模式主要用来分离算法,根据相同的行为抽象来做不同的具体策略实现。 22 | 23 | ##优缺点 24 | 25 | ###优点: 26 | 27 | * 结构清晰明了、使用简单直观。 28 | * 耦合度相对而言较低,扩展方便。 29 | * 操作封装也更为彻底,数据更为安全。 30 | 31 | ###缺点: 32 | 33 | * 随着策略的增加,子类也会变得繁多。 -------------------------------------------------------------------------------- /Part1/DesignPattern/简单工厂.md: -------------------------------------------------------------------------------- 1 | #简单工厂 2 | --- 3 | ###接口 4 | 接口是一种特殊的抽象类,跟一般的抽象类相比,接口里的所有方法都是抽象方法,接口里的所有属性都是常量。也就是说接口里面只有方法定义没有任何方法实现。 5 | 6 | 接口的思想是"封装隔离" 7 | 8 | ###简单工厂 9 | 示例代码: 10 | [https://github.com/GeniusVJR/DesignMode_Java/tree/master/SimpleFactory](https://github.com/GeniusVJR/DesignMode_Java/tree/master/SimpleFactory) 11 | 12 | 客户端在调用的时候,不但知道了接口,同时还知道了具体的实现。接口的思想是"封装隔离",而实现类Impl应该是被接口Api封装并同客户端隔离开来的,客户端不应该知道具体的实现类是Impl。 13 | 14 | ###简单工厂的功能 15 | 不仅可以利用简单工厂来创建接口,也可以用简单工厂来创造抽象类,甚至是一个具体的实例。 16 | 17 | ###静态工厂 18 | 没有创建工厂实例的必要,把简单工厂实现成一个工具类,直接使用静态方法。 19 | 20 | ###万能工厂 21 | 一个简单哪工厂可以包含很多用来构造东西的方法,这些方法可以创建不同的接口、抽象类或者是类实例。 22 | 23 | ###简单工厂的优缺点 24 | 1. 优点 25 | * 帮助封装 26 | * 解耦 27 | 2. 缺点 28 | * 可能增加客户端的复杂度 29 | * 不方便扩展子工厂 30 | 31 | ##思考 32 | 33 | 简单工厂的本质是选择实现。 34 | 35 | -------------------------------------------------------------------------------- /Part1/DesignPattern/观察者模式.md: -------------------------------------------------------------------------------- 1 | #观察者模式 2 | --- 3 | 4 | 首先在Android中,我们往ListView添加数据后,都会调用Adapter的notifyDataChanged()方法,其中使用了观察者模式。 5 | 6 | 当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者(AdapterDataSetObserver)的onChanged方法,在onChanged函数中又会调用ListView重新布局的函数使得ListView刷新界面。 7 | 8 | Android中应用程序发送广播的过程: 9 | 10 | - 通过sendBroadcast把一个广播通过Binder发送给ActivityManagerService,ActivityManagerService根据这个广播的Action类型找到相应的广播接收器,然后把这个广播放进自己的消息队列中,就完成第一阶段对这个广播的异步分发。 11 | - ActivityManagerService在消息循环中处理这个广播,并通过Binder机制把这个广播分发给注册的ReceiverDispatcher,ReceiverDispatcher把这个广播放进MainActivity所在线程的消息队列中,就完成第二阶段对这个广播的异步分发: 12 | - ReceiverDispatcher的内部类Args在MainActivity所在的线程消息循环中处理这个广播,最终是将这个广播分发给所注册的BroadcastReceiver实例的onReceive函数进行处理: -------------------------------------------------------------------------------- /Part1/DesignPattern/责任链模式.md: -------------------------------------------------------------------------------- 1 | #责任链模式 2 | --- 3 | 4 | ##模式介绍 5 | 6 | ###模式的定义 7 | 8 | 一个请求沿着一条“链”传递,直到该“链”上的某个处理者处理它为止。 9 | 10 | ###模式的使用场景 11 | 12 | 一个请求可以被多个处理者处理或处理者未明确指定时。 -------------------------------------------------------------------------------- /Part1/DesignPattern/适配器模式.md: -------------------------------------------------------------------------------- 1 | #适配器模式 2 | --- 3 | 定义: 4 | >将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 5 | 6 | 功能: 7 | 8 | >进行转换匹配,目的是复用已有的功能,而不是来实现新的接口。在适配器里实现功能,这种适配器称为智能适配器。 9 | 10 | 优点: 11 | 12 | * 更好的复用性 13 | * 更好的扩展性 14 | 15 | 缺点: 16 | 17 | * 过多的使用适配器,会让系统非常零乱,不容易整体进行把握。 18 | 19 | 本质: 20 | 21 | 转换匹配,复用功能。 22 | 23 | 何时选用适配器模式: 24 | 25 | * 如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口。 26 | * 如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式,到时候需要什么就适配什么。 27 | * 如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况可以选用对象适配器,直接适配这些子类的父类就可以了。 28 | -------------------------------------------------------------------------------- /Part2/JVM/JVM类加载机制.md: -------------------------------------------------------------------------------- 1 | #虚拟机类加载机制 2 | --- 3 | **虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被Java虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。** 4 | 5 | 类从被加载到虚拟内存中开始,到卸载内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中,验证,准备和解析三个部分统称为连接(Linking)。 6 | 7 | ###类加载的过程 8 | 类加载的全过程,加载,验证,准备,解析和初始化这五个阶段。 9 | 10 | --- 11 | 12 | ####加载 13 | 在加载阶段,虚拟机需要完成以下三件事情: 14 | 15 | * 通过一个类的全限定名来获取定义此类的二进制字节流 16 | * 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构 17 | * 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口 18 | 19 | ####验证 20 | 这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能有所不同,但大致上都会完成下面四个阶段的检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。 21 | 22 | **文件格式验证** 23 | 24 | 第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。 25 | 26 | **元数据验证** 27 | 28 | 第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。 29 | 30 | **字节码验证** 31 | 32 | 第三阶段时整个验证过程中最复杂的一个阶段,主要工作是数据流和控制流的分析。在第二阶段对元数据信息中的数据类型做完校验后,这阶段将对类的方法体进行校验分析。这阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。 33 | 34 | **符号引用验证** 35 | 36 | 最后一个阶段的校验发生在虚拟机将符号引用直接转化为直接引用的时候,这个转化动作将在连接的第三个阶段-解析阶段产生。符号引用验证可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性的校验。 37 | 38 | ####准备 39 | 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区进行分配。 40 | 41 | ####解析 42 | 解析阶段是虚拟机将常量池的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。 43 | 44 | * 类或接口的解析 45 | * 字段解析 46 | * 类方法解析 47 | * 接口方法解析 48 | 49 | ####初始化 50 | 前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由Java虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者说初始化阶段是执行类构造器()方法的过程。 51 | 52 | ###类加载器 53 | --- 54 | ####类与类加载器 55 | 虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让程序自己决定如何去获取所需的类。实现这个动作的代码模块被称为"类加载器"。 56 | 57 | ####双亲委派模型 58 | 站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。从Java开发人员的角度来看,类加载器还可以分得更细致一些,绝大部分Java程序都会使用到以下三种系统提供的类加载器: 59 | 60 | * 启动类加载器 61 | * 扩展类加载器 62 | * 应用程序类加载器 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Part2/JVM/Java内存区域与内存溢出.md: -------------------------------------------------------------------------------- 1 | ##内存区域 2 | 3 | Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器,Java虚拟机栈,本地方法栈,Java堆,方法区。下面详细阐述各数据区所存储的数据类型。 4 | 5 | ![这里写图片描述](http://img.blog.csdn.net/20160401142029373) 6 | 7 | **程序计数器(Program Counter Register)** 8 | 9 | 一块较小的内存空间,它是当前线程所执行的子节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的子节码指令,分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。 10 | 11 | 当线程在执行一个Java方法时,该计数器纪录的是正在执行的虚拟机字节吗指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中没有任何OOM(内存溢出:OutOfMemoryError)情况的区域。 12 | 13 | **Java虚拟机栈(Java Virtual Machine Stacks)** 14 | 15 | 该区域也是线程私有的,它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个帧栈,栈它是用于支持虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码都只针对当前的栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。 16 | 在Java虚拟机规范中,对这个区域规定了两种异常情况: 17 | 18 | 1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。 19 | 2. 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemory异常。 20 | 21 | 这两种情况存在着一些互相重叠的部分:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质只是对同一件事情的两种描述而已。其本质上只是对一件事情的两种描述而已。在单线程的操作中,无论是由于栈帧太大,还是虚拟机栈空间太小,当栈空间无法分配时,虚拟机抛出的都是StackOverflowError异常,而不会得到OutOfMemoryError异常。而在多线程环境下,则会抛出OutOfMemory异常。 22 | 23 | 下面详细说明栈帧中所存放的各部分信息的作用和数据结构。 24 | 25 | 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和returnAddress类型(它指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,即在Java程序被编译成Class文件时,就确定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。 26 | 下面详细说明栈帧中所存放的各部分信息的作用和数据结构。 27 | 28 | 1、局部变量表 29 | 局部变量表的容量以变量槽(Slot)为最小单位。在虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小(允许其随着处理器、操作系统或虚拟机的不同而发生变化),一个Slot可以存放一个32位以内的数据类型:boolean、byte、char、short、int、float、reference和returnAddresss。reference是对象的引用类型,returnAddress是为字节指令服务的,它执行了一条字节码指令的地址。对于64位的数据类型(long和double),虚拟机会以高位在前的方式为其分配两个连续的Slot空间。 30 | 31 | 虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始到局部变量表最大的Slot数量,对于32位数据类型的变量,索引n代表第n个Slot,对于64位的,索引n代表第n和第n+1两个Slot。 32 | 33 | 在方法执行时,虚拟机是使用局部变量表来完成参数值到参数变量列表的传递过程的,如果是实例方法(非static),则局部变量表中的第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问这个隐含的参数。其余参数则按照参数表的顺序来排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot。 34 | 35 | 局部变量表中的Slot是可重用的,方法体中定义的变量,作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超过了某个变量的作用域,那么这个变量对应的Slot就可以交给其他变量使用。这样的设计不仅仅是为了节省空间,在某些情况下Slot的复用会直接影响到系统的而垃圾收集行为。 36 | 37 | 2、操作数栈 38 | 39 | 操作数栈又常被称为操作栈,操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1,64为数据类型所占的栈容量为2。当一个方法开始执行时,它的操作栈是空的,在方法的执行过程中,会有各种字节码指令(比如:加操作、赋值元算等)向操作栈中写入和提取内容,也就是入栈和出栈操作。 40 | 41 | Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。 42 | 43 | 基于栈的指令集最主要的优点是可移植性强,主要的缺点是执行速度相对会慢些;而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的优点是执行速度快,主要的缺点是可移植性差。 44 | 45 | 3、动态连接 46 | 47 | 每个栈帧都包含一个指向运行时常量池(在方法区中,后面介绍)中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。 48 | 49 | 4、方法返回地址 50 | 51 | 当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 52 | 53 | 方法退出的过程实际上等同于把当前栈帧出站,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。 54 | 55 | **本地方法栈(Native Method Stacks)** 56 | 57 | 该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。 58 | 59 | **Java堆(Java Heap)** 60 | 61 | Java Heap是Java虚拟机所管理的内存中的最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为"GC堆"。 62 | 63 | 根据Java虚拟机的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemory。 64 | 65 | **方法区(Method Area)** 66 | 67 | 方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为"永久代"。但着这仅仅对于Sun HotSpot来讲,JRocket和IBMJ9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分,而且它和Java Heap一样不需要连续的内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的是String类的intern()方法。 68 | 69 | 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。 70 | 71 | 直接内存(Direct Memory) 72 | 73 | 直接内存并不是虚拟机运行内存时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,它直接从操作系统中分配内存,因此不受Java堆的大小的限制,但是会受到本机总内存的大小及处理器寻址空间的限制,因此它也可能导致OutOfMemoryError异常出现。在Java1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,可以直接从操作系统中分配直接内存,可以直接从操作系统中分配直接内存,即在堆外分配内存,这样能在一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制数据。 74 | 75 | 内存溢出 76 | 77 | 下面给出个内存区域内存溢出的简单测试方法 78 | 79 | ![这里写图片描述](http://img.blog.csdn.net/20160401173849014) 80 | 81 | 这里有一点要重点说明,在多线程情况下,给每个线程的栈分配的内存越大,反而越容易产生内存产生内存溢出一场。操作系统为每个进程分配的内存是有限制的,虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,忽略掉程序计数器消耗的内存(很小),以及进程本身消耗的内存,剩下的内存便给了虚拟机栈和本地方法栈,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少。因此,如果是建立过多的线程导致的内存溢出,在不能减少线程数的情况下,就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。 82 | 另外,由于Java堆内也可能发生内存泄露(Memory Leak),这里简要说明一下内存泄露和内存溢出的区别: 83 | 84 | 内存泄漏是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。Java中一般不会产生内存泄漏,因为有垃圾回收器自动回收垃圾,但这也不绝对,当我们new了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这就会造成内存泄漏。 85 | 86 | 内存溢出是指程序所需要的内存超过了系统所能分配的内存(包括动态扩展)的上限。 87 | 88 | 对象实例化分析 89 | 90 | 对内存分配情况分析最常见的示例便是对象实例化: 91 | 92 | ``` 93 | Object obj = new Object(); 94 | ``` 95 | 96 | 这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中,及时对JVM虚拟机不了解的Java使用这,应该也知道obj会作为引用类型(reference)的数据保存在Java栈的本地变量表中,而会在Java堆中保存该引用的实例化对象,但可能并不知道,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。 97 | 98 | 另外,由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄池和直接使用指针。 99 | 100 | 通过句柄池访问的方式如下: 101 | 102 | ![这里写图片描述](http://img.blog.csdn.net/20160401175131207) 103 | 104 | 通过直接指针访问的方式如下: 105 | 106 | ![这里写图片描述](http://img.blog.csdn.net/20160401175203926) 107 | 108 | 这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存放的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处是速度快,它节省了一次指针定位的时间开销。目前Java默认使用的HotSpot虚拟机采用的便是是第二种方式进行对象访问的。 -------------------------------------------------------------------------------- /Part2/JVM/垃圾回收算法.md: -------------------------------------------------------------------------------- 1 | #垃圾回收算法 2 | --- 3 | 4 | 1. 引用计数法:缺点是无法处理循环引用问题 5 | 2. 标记-清除法:标记所有从根结点开始的可达对象,缺点是会造成内存空间不连续,不连续的内存空间的工作效率低于连续的内存空间,不容易分配内存 6 | 3. 复制算法:将内存空间分成两块,每次将正在使用的内存中存活对象复制到未使用的内存块中,之后清除正在使用的内存块。算法效率高,但是代价是系统内存折半。适用于新生代(存活对象少,垃圾对象多) 7 | 4. 标记-压缩算法:标记-清除的改进,清除未标记的对象时还将所有的存活对象压缩到内存的一端,之后,清理边界所有空间既避免碎片产生,又不需要两块同样大小的内存快,性价比高。适用于老年代。 8 | 5. 分代 9 | 10 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/Java并发基础知识.md: -------------------------------------------------------------------------------- 1 | #Java并发 2 | --- 3 | 4 | (Executor框架和多线程基础) 5 | 6 | **Thread与Runable如何实现多线程** 7 | 8 | Java 5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。 9 | 10 | 实现Runnable接口相比继承Thread类有如下优势: 11 | 12 | 1. 可以避免由于Java的单继承特性而带来的局限 13 | 2. 增强程序的健壮性,代码能够被多个程序共享,代码与数据是独立的 14 | 3. 适合多个相同程序代码的线程区处理同一资源的情况 15 | 16 | 补充:Java 5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,代码如下所示: 17 | 18 | ``` 19 | class MyTask implements Callable { 20 | private int upperBounds; 21 | 22 | public MyTask(int upperBounds) { 23 | this.upperBounds = upperBounds; 24 | } 25 | 26 | @Override 27 | public Integer call() throws Exception { 28 | int sum = 0; 29 | for(int i = 1; i <= upperBounds; i++) { 30 | sum += i; 31 | } 32 | return sum; 33 | } 34 | 35 | } 36 | 37 | public class Test { 38 | 39 | public static void main(String[] args) throws Exception { 40 | List> list = new ArrayList<>(); 41 | ExecutorService service = Executors.newFixedThreadPool(10); 42 | for(int i = 0; i < 10; i++) { 43 | list.add(service.submit(new MyTask((int) (Math.random() * 100)))); 44 | } 45 | 46 | int sum = 0; 47 | for(Future future : list) { 48 | while(!future.isDone()) ; 49 | sum += future.get(); 50 | } 51 | 52 | System.out.println(sum); 53 | } 54 | } 55 | ``` 56 | 57 | **线程同步的方法有什么;锁,synchronized块,信号量等** 58 | 59 | 60 | 61 | **锁的等级:方法锁、对象锁、类锁** 62 | 63 | **生产者消费者模式的几种实现,阻塞队列实现,sync关键字实现,lock实现,reentrantLock等** 64 | 65 | **ThreadLocal的设计理念与作用,ThreadPool用法与优势(这里在Android SDK原生的AsyncTask底层也有使用)** 66 | 67 | **线程池的底层实现和工作原理(建议写一个雏形简版源码实现)** 68 | 69 | **几个重要的线程api,interrupt,wait,sleep,stop等等** 70 | 71 | **写出生产者消费者模式。** 72 | 73 | **ThreadPool用法与优势。** 74 | 75 | **Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。** 76 | 77 | **wait()和sleep()的区别。** 78 | 79 | sleep()方法是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用sleep 不会释放对象锁。wait()是Object 类的方法,对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。 80 | 81 | 82 | 83 | ###3、IO(IO,NIO,目前okio已经被集成Android包) 84 | 85 | **IO框架主要用到什么设计模式** 86 | 87 | JDK的I/O包中就主要使用到了两种设计模式:Adatper模式和Decorator模式。 88 | 89 | 90 | **NIO包有哪些结构?分别起到的作用?** 91 | 92 | 93 | **NIO针对什么情景会比IO有更好的优化?** 94 | 95 | **OKIO底层实现** 96 | 97 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/NIO.md: -------------------------------------------------------------------------------- 1 | #NIO 2 | 3 | --- 4 | 5 | Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 6 | 7 | ###Java NIO: Channels and Buffers(通道和缓冲区) 8 | 9 | 标准的俄IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入通道也类似。 10 | 11 | ###Java NIO: Non-blocking IO(非阻塞IO) 12 | 13 | Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。 14 | 15 | ###Java NIO: Selectors(选择器) 16 | 17 | Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。 18 | 19 | NIO由以下核心部分组成: 20 | 21 | * Channels 22 | * Buffers 23 | * Selectors 24 | 25 | **Channel和Buffer** 26 | 27 | 基本上,所有的IO和NIO都从一个Channel开始。Channel有点像流。数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中 28 | 29 | ![](http://ifeve.com/wp-content/uploads/2013/06/overview-channels-buffers1.png) 30 | 31 | Channel的实现 32 | 33 | * FileChannel 34 | * DatagramChannel 35 | * SocketChannel 36 | * ServerSocketChannel 37 | 38 | 这些通道涵盖了UDP和TCP网络IO,以及文件IO。 39 | 40 | 以下是Java NIO里关键的Buffer实现 41 | 42 | * ByteBuffer 43 | * CharBuffer 44 | * DoubleBuffer 45 | * FloatBuffer 46 | * IntBuffer 47 | * LongBuffer 48 | * ShortBuffer 49 | 50 | 这些Buffer覆盖了你能通过IO发送的基本数据类型:byte,short,int,long,float,double和char 51 | 52 | Java NIO还有个MappedByteBuffer,用于表示内存映射文件。 53 | 54 | **Selextor** 55 | 56 | Selector允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每一个连接的流量都很低,使用Selector就会很方便。 57 | 58 | 例如,在一个聊天服务器中 59 | 60 | 这是在一个单线程中使用一个Selector处理3个Channel的图示: 61 | 62 | ![](http://ifeve.com/wp-content/uploads/2013/06/overview-selectors.png) 63 | 64 | 要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接送等。 65 | 66 | 67 | ##Channel 68 | --- 69 | 70 | Java NIO的通道类似流,但又有些不同: 71 | 72 | * 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。 73 | * 通道可以异步的读写。 74 | * 通道的数据总是要先读到一个Buffer,或者总要从一个Buffer中写入。 75 | 76 | 正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。 77 | 78 | **Channel的实现** 79 | 80 | * FileChannel 从文件中读取数据 81 | * DataChannel 能通过UDP读写网络中的数据 82 | * SocketChannel 能通过TCP读写网络中的数据 83 | * ServerSocketChannel 可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel 84 | 85 | 86 | 基本的Channel示例 87 | 88 | 下面是一个使用FileChannel读取数据到Buffer中的示例 89 | 90 | ``` 91 | RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); 92 | FileChannel inChannel = aFile.getChannel(); 93 | ByteBuffer buf = ByteBuffer.allocate(48); 94 | int bytesRead = inChannel.read(buf); 95 | while (bytesRead != -1) { 96 | System.out.println("Read " + bytesRead); 97 | buf.flip(); 98 | while(buf.hasRemaining()){ 99 | System.out.print((char) buf.get()); 100 | } 101 | buf.clear(); 102 | bytesRead = inChannel.read(buf); 103 | } 104 | aFile.close(); 105 | 106 | ``` 107 | 注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。 108 | 109 | 110 | ##Buffer 111 | 112 | Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。 113 | 114 | 缓冲区本质是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问这块内存。 115 | 116 | **Buffer的基本用法** 117 | 118 | 使用Buffer读写数据一般遵循以下四个步骤: 119 | 120 | 1. 写入数据到Buffer 121 | 2. 调用flip()方法 122 | 3. 从Buffer中读取数据 123 | 4. 调用clear()方法或者compact()方法 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/Synchronized.md: -------------------------------------------------------------------------------- 1 | #synchronized 2 | --- 3 | 4 | 在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确保在某一时刻,方法内只允许有一个线程。 5 | 6 | 采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。 7 | 8 | 这里就使用同步机制获取互斥锁的情况,进行几点说明: 9 | 10 | 1. 如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。 11 | 2. 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问synchronized同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁。 12 | 3. 访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响。 13 | 4. 持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。当一个线程A持有一个对象级别锁(即进入了synchronized修饰的代码块或方法中)时,线程也有可能被交换出去,此时线程B有可能获取执行该对象中代码的时间,但它只能执行非同步代码(没有用synchronized修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有对象级别锁,当A线程退出同步代码时(即释放了对象级别锁),如果B线程此时再运行,便会获得该对象级别锁,从而执行synchronized中的代码。 14 | 5. 持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。例如,在一个类中有三个synchronized方法a,b,c,当线程A正在执行一个实例对象M中的方法a时,它便获得了该对象级别锁,那么其他的线程在执行同一实例对象(即对象M)中的代码时,便会在所有的synchronized方法处阻塞,即在方法a,b,c处都要被阻塞,等线程A释放掉对象级别锁时,其他的线程才可以去执行方法a,b或者c中的代码,从而获得该对象级别锁。 15 | 6. 使用synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj为对象的引用,如果获取了obj对象上的对象级别锁,在并发访问obj对象时时,便会在其synchronized代码处阻塞等待,直到获取到该obj对象的对象级别锁。当obj为this时,便是获取当前对象的对象级别锁。 16 | 7. 类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。 17 | 8. 互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。 18 | 19 | ##内存可见性 20 | 21 | 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性。我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且还希望确保当一个线程修改了对象状态后,其他线程能够看到该变化。而线程的同步恰恰也能够实现这一点。 22 | 23 | 内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。为了确保所有的线程都能看到共享变量的最新值,可以在所有执行读操作或写操作的线程上加上同一把锁。下图示例了同步的可见性保证。 24 | 25 | ![](http://img.blog.csdn.net/20131212211029125) 26 | 27 | 当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,这种情况下可以保证,当锁被释放前,A看到的所有变量值(锁释放前,A看到的变量包括y和x)在B获得同一个锁后同样可以由B看到。换句话说,当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个锁保护的同步代码块中的所有操作结果。如果在线程A unlock M之后,线程B才进入lock M,那么线程B都可以看到线程A unlock M之前的操作,可以得到i=1,j=1。如果在线程B unlock M之后,线程A才进入lock M,那么线程B就不一定能看到线程A中的操作,因此j的值就不一定是1。 28 | 29 | 现在考虑如下代码: 30 | 31 | ``` 32 | public class MutableInteger 33 | { 34 | private int value; 35 | 36 | public int get(){ 37 | return value; 38 | } 39 | public void set(int value){ 40 | this.value = value; 41 | } 42 | } 43 | ``` 44 | 45 | 以上代码中,get和set方法都在没有同步的情况下访问value。如果value被多个线程共享,假如某个线程调用了set,那么另一个正在调用get的线程可能会看到更新后的value值,也可能看不到。 46 | 47 | 通过对set和get方法进行同步,可以使MutableInteger成为一个线程安全的类,如下: 48 | 49 | ``` 50 | public class SynchronizedInteger 51 | { 52 | private int value; 53 | 54 | public synchronized int get(){ 55 | return value; 56 | } 57 | public synchronized void set(int value){ 58 | this.value = value; 59 | } 60 | } 61 | ``` 62 | 63 | 对set和get方法进行了同步,加上了同一把对象锁,这样get方法可以看到set方法中value值的变化,从而每次通过get方法取得的value的值都是最新的value值。 64 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/Thread和Runnable实现多线程的区别.md: -------------------------------------------------------------------------------- 1 | #Thread和Runnable实现多线程的区别 2 | --- 3 | 4 | Java中实现多线程有两种方法:继承Thread、实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下优势: 5 | 6 | 1. 可以避免由于Java的单继承特性而带来的局限 7 | 2. 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的 8 | 3. 适合多个相同程序的线程区处理同一资源的情况 9 | 10 | 首先通过Thread类实现 11 | 12 | ``` 13 | class MyThread extends Thread{ 14 | private int ticket = 5; 15 | public void run(){ 16 | for (int i=0;i<10;i++) 17 | { 18 | if(ticket > 0){ 19 | System.out.println("ticket = " + ticket--); 20 | } 21 | } 22 | } 23 | } 24 | 25 | public class ThreadDemo{ 26 | public static void main(String[] args){ 27 | new MyThread().start(); 28 | new MyThread().start(); 29 | new MyThread().start(); 30 | } 31 | } 32 | ``` 33 | 34 | 运行结果: 35 | 36 | ``` 37 | ticket = 5 38 | ticket = 4 39 | ticket = 5 40 | ticket = 5 41 | ticket = 4 42 | ticket = 3 43 | ticket = 2 44 | ticket = 1 45 | ticket = 4 46 | ticket = 3 47 | ticket = 3 48 | ticket = 2 49 | ticket = 1 50 | ticket = 2 51 | ticket = 1 52 | ``` 53 | 54 | 每个线程单独卖了5张票,即独立的完成了买票的任务,但实际应用中,比如火车站售票,需要多个线程去共同完成任务,在本例中,即多个线程共同买5张票。 55 | 56 | 通过实现Runnable借口实现的多线程程序 57 | 58 | ``` 59 | class MyThread implements Runnable{ 60 | private int ticket = 5; 61 | public void run(){ 62 | for (int i=0;i<10;i++) 63 | { 64 | if(ticket > 0){ 65 | System.out.println("ticket = " + ticket--); 66 | } 67 | } 68 | } 69 | } 70 | 71 | public class RunnableDemo{ 72 | public static void main(String[] args){ 73 | MyThread my = new MyThread(); 74 | new Thread(my).start(); 75 | new Thread(my).start(); 76 | new Thread(my).start(); 77 | } 78 | } 79 | ``` 80 | 81 | 运行结果 82 | 83 | ``` 84 | ticket = 5 85 | ticket = 2 86 | ticket = 1 87 | ticket = 3 88 | ticket = 4 89 | ``` 90 | 91 | * 在第二种方法(Runnable)中,ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测。ticket并不是原子操作。 92 | * 在第一种方法中,我们new了3个Thread对象,即三个线程分别执行三个对象中的代码,因此便是三个线程去独立地完成卖票的任务;而在第二种方法中,我们同样也new了3个Thread对象,但只有一个Runnable对象,3个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象,作为参数分别传入3个Thread对象中,那么3个线程便会独立执行各自Runnable对象中的代码,即3个线程各自卖5张票。 93 | * 在第二种方法中,由于3个Thread对象共同执行一个Runnable对象中的代码,因此可能会造成线程的不安全,比如可能ticket会输出-1(如果我们System.out....语句前加上线程休眠操作,该情况将很有可能出现),这种情况的出现是由于,一个线程在判断ticket为1>0后,还没有来得及减1,另一个线程已经将ticket减1,变为了0,那么接下来之前的线程再将ticket减1,便得到了-1。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执行每次for循环中的操作。而在第一种方法中,并不需要加入同步操作,因为每个线程执行自己Thread对象中的代码,不存在多个线程共同执行同一个方法的情况。 94 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/thread与runable如何实现多线程.md: -------------------------------------------------------------------------------- 1 | ##Thread与Runable如何实现多线程? 2 | 3 | Java实现多线程有两种途径:继承Thread类或者实现Runnable接口。Runnable是接口 4 | 5 | Runnable是接口,建议用接口的方式生成线程,因为接口可以实现多继承,况且Runnable只有一个run方法,很适合继承。 6 | 7 | 在使用Thread方法的时候只需继承Thread,并且new一个实例出来,调用start()方法既可以启动一个线程 8 | 9 | [ java多线程 Thread 和Runnable](http://blog.csdn.net/luyysea/article/details/7995351) -------------------------------------------------------------------------------- /Part2/JavaConcurrent/volatile变量修饰符.md: -------------------------------------------------------------------------------- 1 | #volatile变量修饰符 2 | --- 3 | 4 | ##volatile用处说明 5 | 6 | 在JDK1.2之前,Java的类型模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变的非常重要。 7 | 8 | 在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另一个线程还在继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 9 | 10 | 要解决这个问题,就需要把变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般来说,多任务环境下,各任务间共享的变量都应该加volatile修饰符。 11 | 12 | volatile修饰de成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 13 | 14 | Java语言规范中指出:为了获得最佳速度,允许线程保存成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值进行比较。 15 | 16 | 这样当多个线程同时与某个对象交互时,就必须注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示JVM:对于这个成员变量,不能保存它的私有拷贝,而应直接与共享成员变量交互。 17 | 18 | volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。 19 | 20 | 使用建议:在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,或者为常量时,没必要使用volatile。 21 | 22 | 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 23 | 24 | 下面给出一段代码,通过其运行结果来说明使用关键字volatile产生的差异,但实际上遇到了意料之外的问题: 25 | 26 | ``` 27 | 28 | ``` -------------------------------------------------------------------------------- /Part2/JavaConcurrent/使用wait notify notifyall实现线程间通信.md: -------------------------------------------------------------------------------- 1 | #使用wait/notify/notifyAll实现线程间通信 2 | --- 3 | 4 | 在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调用notify()方法或notifyAll()方法),在线程中调用notify()方法或notifyAll()方法,将通知其他线程从wait()方法处返回。 5 | 6 | Object是所有类的超类,它有5个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的类都从Object继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为final,因此在子类中不能覆写任何一个方法。 7 | 8 | 这里详细说明一下各个方法在使用中需要注意的几点: 9 | 10 | 1、wait() 11 | 12 | ``` 13 | public final void wait() throws InterruptedException,IllegalMonitorStateException 14 | ``` 15 | 16 | 该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。 17 | 18 | 2、notify() 19 | 20 | ``` 21 | public final native void notify() throws IllegalMonitorStateException 22 | ``` 23 | 24 | 该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。 25 | 26 | 该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。 27 | 28 | 3、notifyAll() 29 | 30 | ``` 31 | public final native void notifyAll() throws IllegalMonitorStateException 32 | ``` 33 | 34 | 该方法与notify()方法的工作方式相同,重要的一点差异是: 35 | 36 | notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。 37 | 38 | 4、wait(long)和wait(long,int) 39 | 40 | 显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。 41 | 42 | 43 | 深入理解: 44 | 45 | * 如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。 46 | * 当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。 47 | * 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。 48 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/可重入内置锁.md: -------------------------------------------------------------------------------- 1 | #可重入内置锁 2 | --- 3 | 4 | 每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。 5 | 6 | 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“线程”,而不是调用。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程所持有,当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应地递减。当计数值为0时,这个锁将被释放。 7 | 8 | 重入进一步提升了加锁行为的封装性,因此简化了面向对象并发代码的开发。分析如下程序: 9 | 10 | ``` 11 | public class Father 12 | { 13 | public synchronized void doSomething(){ 14 | ...... 15 | } 16 | } 17 | 18 | public class Child extends Father 19 | { 20 | public synchronized void doSomething(){ 21 | ...... 22 | super.doSomething(); 23 | } 24 | } 25 | ``` 26 | 27 | 子类覆写了父类的同步方法,然后调用父类中的方法,此时如果没有可重入的锁,那么这段代码件产生死锁。 28 | 29 | 由于Fither和Child中的doSomething方法都是synchronized方法,因此每个doSomething方法在执行前都会获取Child对象实例上的锁。如果内置锁不是可重入的,那么在调用super.doSomething时将无法获得该Child对象上的互斥锁,因为这个锁已经被持有,从而线程会永远阻塞下去,一直在等待一个永远也无法获取的锁。重入则避免了这种死锁情况的发生。 30 | 31 | 同一个线程在调用本类中其他synchronized方法/块或父类中的synchronized方法/块时,都不会阻碍该线程地执行,因为互斥锁时可重入的。 -------------------------------------------------------------------------------- /Part2/JavaConcurrent/多线程环境中安全使用集合API.md: -------------------------------------------------------------------------------- 1 | #多线程环境中安全使用集合API 2 | --- 3 | 4 | 在集合API中,最初设计的Vector和Hashtable是多线程安全的。例如:对于Vector来说,用来添加和删除元素的方法是同步的。如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不必要的时候如果滥用同步化,也有可能会带来死锁。因此,对于更改集合内容的方法,没有一个是同步化的。集合本质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施。 5 | 6 | 在Collections类 中有多个静态方法,它们可以获取通过同步方法封装非同步集合而得到的集合: 7 | 8 | * public static Collection synchronizedCollention(Collection c) 9 | * public static List synchronizedList(list l) 10 | * public static Map synchronizedMap(Map m) 11 | * public static Set synchronizedSet(Set s) 12 | * public static SortedMap synchronizedSortedMap(SortedMap sm) 13 | * public static SortedSet synchronizedSortedSet(SortedSet ss) 14 | 15 | 16 | 这些方法基本上返回具有同步集合方法版本的新类。比如,为了创建多线程安全且由ArrayList支持的List,可以使用如下代码: 17 | 18 | ``` 19 | List list = Collection.synchronizedList(new ArrayList()); 20 | ``` 21 | 22 | 注意,ArrayList实例马上封装起来,不存在对未同步化ArrayList的直接引用(即直接封装匿名实例)。这是一种最安全的途径。如果另一个线程要直接引用ArrayList实例,它可以执行非同步修改。 23 | 24 | 下面给出一段多线程中安全遍历集合元素的示例。我们使用Iterator逐个扫描List中的元素,在多线程环境中,当遍历当前集合中的元素时,一般希望阻止其他线程添加或删除元素。安全遍历的实现方法如下: 25 | 26 | ``` 27 | import java.util.*; 28 | 29 | public class SafeCollectionIteration extends Object { 30 | public static void main(String[] args) { 31 | //为了安全起见,仅使用同步列表的一个引用,这样可以确保控制了所有访问 32 | //集合必须同步化,这里是一个List 33 | List wordList = Collections.synchronizedList(new ArrayList()); 34 | 35 | //wordList中的add方法是同步方法,会获取wordList实例的对象锁 36 | wordList.add("Iterators"); 37 | wordList.add("require"); 38 | wordList.add("special"); 39 | wordList.add("handling"); 40 | 41 | //获取wordList实例的对象锁, 42 | //迭代时,阻塞其他线程调用add或remove等方法修改元素 43 | synchronized ( wordList ) { 44 | Iterator iter = wordList.iterator(); 45 | while ( iter.hasNext() ) { 46 | String s = (String) iter.next(); 47 | System.out.println("found string: " + s + ", length=" + s.length()); 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | 这里需要注意的是:在Java语言中,大部分的线程安全类都是相对线程安全的,它能保证对这个对象单独的操作时线程安全的,我们在调用的时候不需要额外的保障措施,但是对于一些特定的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。例如Vector、HashTable、Collections的synchronizedXxxx()方法包装的集合等。 -------------------------------------------------------------------------------- /Part2/JavaConcurrent/守护线程与阻塞线程.md: -------------------------------------------------------------------------------- 1 | #守护线程与阻塞线程的四种情况 2 | --- 3 | 4 | Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 5 | 6 | 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为如果没有了守护者,也就没有继续运行程序的必要了。如果有非守护线程仍然活着,VM就不会退出。 7 | 8 | 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。 9 | 10 | 虽然守护线程可能非常有用,但必须小心确保其它所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。因此,不要再守护线程中执行业务逻辑操作(比如对数据的读写等)。 11 | 12 | 还有几点: 13 | 14 | 1. setDaemon(true)必须在调用线程的start()方法之前设置,否则会跑出IllegalThreadStateException异常。 15 | 2. 在守护线程中产生的新线程也是守护线程 16 | 3. 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。 17 | 18 | 19 | ##线程阻塞 20 | 21 | 线程可以阻塞于四种状态: 22 | 23 | 1. 当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断 24 | 2. 当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒 时间为止(若指定了超时值的话) 25 | 3. 线程阻塞与不同的I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间 26 | 4. 线程也可以阻塞等待获取某个对象锁的排它性访问权限(即等待获得synchronized语句必须的锁时阻塞) 27 | 28 | 并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应。 29 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/实现内存可见的两种方法比较:加锁和volatile变量.md: -------------------------------------------------------------------------------- 1 | #并发编程中实现内存可见的两种方法比较:加锁和volatile变量 2 | --- 3 | 4 | 1. volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。 5 | 6 | 2. 从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。 7 | 8 | 3. 在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。 9 | 10 | 4. 加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。 11 | 12 | 当且仅当满足以下所有条件时,才应该使用volatile变量: 13 | 14 | 1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。 15 | 2. 该变量没有包含在具有其他变量的不变式中。 16 | 17 | 18 | 总结:在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的。尤其在、jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升。 -------------------------------------------------------------------------------- /Part2/JavaConcurrent/死锁.md: -------------------------------------------------------------------------------- 1 | #死锁 2 | --- 3 | 4 | 当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形: 5 | 6 | 线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2。接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程B对lock2的释放。如果此时线程B在持有lock2的时候,也在试图获取lock1,因为线程A正持有lock1,因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。 7 | 8 | 下面给出一个两个线程间产生死锁的示例,如下: 9 | 10 | ``` 11 | 12 | public class Deadlock { 13 | 14 | private String objID; 15 | 16 | public Deadlock(String id) { 17 | objID = id; 18 | } 19 | 20 | public synchronized void checkOther(Deadlock other) { 21 | print("entering checkOther()"); 22 | try { Thread.sleep(2000); } 23 | catch ( InterruptedException x ) { } 24 | print("in checkOther() - about to " + "invoke 'other.action()'"); 25 | //调用other对象的action方法,由于该方法是同步方法,因此会试图获取other对象的对象锁 26 | other.action(); 27 | print("leaving checkOther()"); 28 | } 29 | 30 | public synchronized void action() { 31 | print("entering action()"); 32 | try { Thread.sleep(500); } 33 | catch ( InterruptedException x ) { } 34 | print("leaving action()"); 35 | } 36 | 37 | public void print(String msg) { 38 | threadPrint("objID=" + objID + " - " + msg); 39 | } 40 | 41 | public static void threadPrint(String msg) { 42 | String threadName = Thread.currentThread().getName(); 43 | System.out.println(threadName + ": " + msg); 44 | } 45 | 46 | public static void main(String[] args) { 47 | final Deadlock obj1 = new Deadlock("obj1"); 48 | final Deadlock obj2 = new Deadlock("obj2"); 49 | 50 | Runnable runA = new Runnable() { 51 | public void run() { 52 | obj1.checkOther(obj2); 53 | } 54 | }; 55 | 56 | Thread threadA = new Thread(runA, "threadA"); 57 | threadA.start(); 58 | 59 | try { Thread.sleep(200); } 60 | catch ( InterruptedException x ) { } 61 | 62 | Runnable runB = new Runnable() { 63 | public void run() { 64 | obj2.checkOther(obj1); 65 | } 66 | }; 67 | 68 | Thread threadB = new Thread(runB, "threadB"); 69 | threadB.start(); 70 | 71 | try { Thread.sleep(5000); } 72 | catch ( InterruptedException x ) { } 73 | 74 | threadPrint("finished sleeping"); 75 | 76 | threadPrint("about to interrupt() threadA"); 77 | 78 | threadA.interrupt(); 79 | 80 | try { Thread.sleep(1000); } 81 | catch ( InterruptedException x ) { } 82 | 83 | threadPrint("about to interrupt() threadB"); 84 | threadB.interrupt(); 85 | 86 | try { Thread.sleep(1000); } 87 | catch ( InterruptedException x ) { } 88 | 89 | threadPrint("did that break the deadlock?"); 90 | } 91 | } 92 | 93 | ``` 94 | 95 | 运行结果: 96 | 97 | ``` 98 | threadA: objID=obj1 - entering checkOther() 99 | threadB: objID=obj2 - entering checkOther() 100 | threadA: objID=obj1 - in checkOther() - about to invoke 'other.action()' 101 | threadB: objID=obj2 - in checkOther() - about to invoke 'other.action()' 102 | main: finished sleeping 103 | main: about to interrupt() threadA 104 | main: about to interrupt() threadB 105 | main: did that break the deadlock? 106 | ``` 107 | 108 | 从结果中可以看出,在执行到other.action()时,由于两个线程都在试图获取对方的锁,但对方都没有释放自己的锁,因而便产生了死锁,在主线程中试图中断两个线程,但都无果。 109 | 110 | 大部分代码并不容易产生死锁,死锁可能在代码中隐藏相当长的时间,等待不常见的条件地发生,但即使是很小的概率,一旦发生,便可能造成毁灭性的破坏。避免死锁是一件困难的事,遵循以下原则有助于规避死锁: 111 | 112 | 1. 只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法; 113 | 2. 尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂; 114 | 3. 创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁; -------------------------------------------------------------------------------- /Part2/JavaConcurrent/生产者和消费者问题.md: -------------------------------------------------------------------------------- 1 | #生产者和消费者问题 2 | 3 | 4 | ``` 5 | package 生产者消费者; 6 | 7 | public class ProducerConsumerTest { 8 | 9 | public static void main(String[] args) { 10 | PublicResource resource = new PublicResource(); 11 | new Thread(new ProducerThread(resource)).start(); 12 | new Thread(new ConsumerThread(resource)).start(); 13 | new Thread(new ProducerThread(resource)).start(); 14 | new Thread(new ConsumerThread(resource)).start(); 15 | new Thread(new ProducerThread(resource)).start(); 16 | new Thread(new ConsumerThread(resource)).start(); 17 | } 18 | } 19 | ``` 20 | ``` 21 | package 生产者消费者; 22 | /** 23 | * 生产者线程,负责生产公共资源 24 | * @author dream 25 | * 26 | */ 27 | public class ProducerThread implements Runnable{ 28 | 29 | private PublicResource resource; 30 | 31 | 32 | public ProducerThread(PublicResource resource) { 33 | this.resource = resource; 34 | } 35 | 36 | 37 | @Override 38 | public void run() { 39 | while (true) { 40 | try { 41 | Thread.sleep((long) (Math.random() * 1000)); 42 | } catch (InterruptedException e) { 43 | e.printStackTrace(); 44 | } 45 | resource.increase(); 46 | } 47 | } 48 | 49 | 50 | } 51 | ``` 52 | 53 | ``` 54 | package 生产者消费者; 55 | 56 | /** 57 | * 消费者线程,负责消费公共资源 58 | * @author dream 59 | * 60 | */ 61 | public class ConsumerThread implements Runnable{ 62 | 63 | private PublicResource resource; 64 | 65 | 66 | public ConsumerThread(PublicResource resource) { 67 | this.resource = resource; 68 | } 69 | 70 | 71 | @Override 72 | public void run() { 73 | while (true) { 74 | try { 75 | Thread.sleep((long) (Math.random() * 1000)); 76 | } catch (InterruptedException e) { 77 | // TODO Auto-generated catch block 78 | e.printStackTrace(); 79 | } 80 | resource.decrease(); 81 | } 82 | 83 | } 84 | 85 | 86 | } 87 | ``` 88 | 89 | ``` 90 | package 生产者消费者; 91 | 92 | /** 93 | * 公共资源类 94 | * @author dream 95 | * 96 | */ 97 | public class PublicResource { 98 | 99 | private int number = 0; 100 | private int size = 10; 101 | 102 | /** 103 | * 增加公共资源 104 | */ 105 | public synchronized void increase() 106 | { 107 | while (number >= size) { 108 | try { 109 | wait(); 110 | } catch (InterruptedException e) { 111 | // TODO Auto-generated catch block 112 | e.printStackTrace(); 113 | } 114 | number++; 115 | System.out.println("生产了1个,总共有" + number); 116 | notifyAll(); 117 | } 118 | } 119 | 120 | 121 | /** 122 | * 减少公共资源 123 | */ 124 | public synchronized void decrease() 125 | { 126 | while (number <= 0) { 127 | try { 128 | wait(); 129 | } catch (InterruptedException e) { 130 | // TODO Auto-generated catch block 131 | e.printStackTrace(); 132 | } 133 | } 134 | number--; 135 | System.out.println("消费了1个,总共有" + number); 136 | notifyAll(); 137 | } 138 | } 139 | 140 | ``` 141 | -------------------------------------------------------------------------------- /Part2/JavaConcurrent/线程中断.md: -------------------------------------------------------------------------------- 1 | #线程中断 2 | --- 3 | 4 | ##使用interrupt()中断线程 5 | 6 | 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行。 7 | 8 | 演示休眠线程的中断 9 | 10 | ``` 11 | 12 | public class SleepInterrupt extends Object implements Runnable{ 13 | 14 | @Override 15 | public void run() { 16 | 17 | try { 18 | System.out.println("in run() - about to sleep for 20 seconds"); 19 | Thread.sleep(20000); 20 | System.out.println("in run() - woke up"); 21 | } catch (InterruptedException e) { 22 | System.out.println("in run() - interrupted while sleeping"); 23 | //处理完中断异常后,返回到run()方法入口 24 | //如果没有return,线程不会实际被中断,它会继续打印下面的信息 25 | return; 26 | } 27 | System.out.println("in run() - leaving normally"); 28 | } 29 | 30 | public static void main(String[] args) { 31 | SleepInterrupt si = new SleepInterrupt(); 32 | Thread t = new Thread(si); 33 | t.start(); 34 | //住线程休眠2秒,从而确保刚才启动的线程有机会执行一段时间 35 | try { 36 | Thread.sleep(2000); 37 | } catch (InterruptedException e) { 38 | e.printStackTrace(); 39 | } 40 | System.out.println("in main() - interrupting other thread"); 41 | //中断线程t 42 | t.interrupt(); 43 | System.out.println("in main() - leaving"); 44 | } 45 | 46 | } 47 | 48 | ``` 49 | 50 | 运行结果如下: 51 | 52 | ``` 53 | in run() - about to sleep for 20 seconds 54 | in main() - interrupting other thread 55 | in main() - leaving 56 | in run() - interrupted while sleeping 57 | ``` 58 | 59 | 主线程启动新线程后,自身休眠2秒钟,允许新线程获得运行时间。新线程打印信息“about to sleep for 20 seconds”后,继而休眠20秒钟,大约2秒钟后,main线程通知新线程中断,那么新线程的20秒的休眠将被打断,从而抛出InterruptException异常,执行跳转到catch块,打印出“interrupted while sleeping”信息,并立即从run()方法返回,然后消亡,而不会打印出catch块后面的“leaving normally”信息。 60 | 61 | 请注意:由于不确定的线程规划,上图运行结果的后两行可能顺序相反,这取决于主线程和新线程哪个先消亡。但前两行信息的顺序必定如上图所示。 62 | 63 | 另外,如果将catch块中的return语句注释掉,则线程在抛出异常后,会继续往下执行,而不会被中断,从而会打印出”leaving normally“信息。 64 | 65 | ##待决中断 66 | --- 67 | 在上面的例子中,sleep()方法的实现检查到休眠线程被中断,它会相当友好地终止线程,并抛出InterruptedException异常。另外一种情况,如果线程在调用sleep()方法前被中断,那么该中断称为待决中断,它会在刚调用sleep()方法时,立即抛出InterruptedException异常。 68 | 69 | ``` 70 | 71 | public class PendingInterrupt extends Object{ 72 | 73 | public static void main(String[] args) { 74 | //如果输入了参数,则在main线程中中断当前线程(即main线程) 75 | if(args.length > 0){ 76 | Thread.currentThread().interrupt(); 77 | } 78 | //获取当前时间 79 | long startTime = System.currentTimeMillis(); 80 | try { 81 | Thread.sleep(2000); 82 | System.out.println("was NOT interrupted"); 83 | } catch (InterruptedException e) { 84 | System.out.println("was interrupted"); 85 | } 86 | //计算中间代码执行的时间 87 | System.out.println("elapsedTime=" + (System.currentTimeMillis() - startTime)); 88 | } 89 | } 90 | 91 | ``` 92 | 93 | 如果PendingInterrupt不带任何命令行参数,那么线程不会被中断,最终输出的时间差距应该在2000附近(具体时间由系统决定,不精确),如果PendingInterrupt带有命令行参数,则调用中断当前线程的代码,但main线程仍然运行,最终输出的时间差距应该远小于2000,因为线程尚未休眠,便被中断,因此,一旦调用sleep()方法,会立即打印出catch块中的信息。执行结果如下: 94 | 95 | ``` 96 | was NOT interrupted 97 | elapsedTime=2001 98 | 99 | ``` 100 | 101 | 这种模式下,main线程中断它自身。除了将中断标志(它是Thread的内部标志)设置为true外,没有其他任何影响。线程被中断了,但main线程仍然运行,main线程继续监视实时时钟,并进入try块,一旦调用sleep()方法,它就会注意到待决中断的存在,并抛出InterruptException。于是执行跳转到catch块,并打印出线程被中断的信息。最后,计算并打印出时间差。 102 | 103 | ##使用isInterrupted()方法判断中断状态 104 | --- 105 | 106 | 可以在Thread对象上调用isInterrupted()方法来检查任何线程的中断状态。这里需要注意:线程一旦被中断,isInterrupted()方法便会返回true,而一旦sleep()方法抛出异常,它将清空中断标志,此时isInterrupted()方法将返回false。 107 | 108 | 下面的代码演示了isInterrupted()方法的使用: 109 | 110 | ``` 111 | 112 | public class InterruptCheck extends Object{ 113 | 114 | public static void main(String[] args) { 115 | Thread t = Thread.currentThread(); 116 | System.out.println("Point A: t.isInterrupted()=" + t.isInterrupted()); 117 | //待决中断,中断自身 118 | t.interrupt(); 119 | System.out.println("Point B: t.isInterrupted()=" + t.isInterrupted()); 120 | System.out.println("Point C: t.isInterrupted()=" + t.isInterrupted()); 121 | 122 | try { 123 | Thread.sleep(2000); 124 | System.out.println("was NOT interrupted"); 125 | } catch (InterruptedException e) { 126 | System.out.println("was interrupted"); 127 | } 128 | //跑出异常后,会清除中断标志,这里会返回false 129 | System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted()); 130 | } 131 | 132 | } 133 | 134 | ``` 135 | 136 | 运行结果如下: 137 | 138 | ``` 139 | Point A: t.isInterrupted()=false 140 | Point B: t.isInterrupted()=true 141 | Point C: t.isInterrupted()=true 142 | was interrupted 143 | Point D: t.isInterrupted()=false 144 | 145 | ``` 146 | 147 | ##使用Thread.interrupted()方法判断中断状态 148 | --- 149 | 150 | 可以使用Thread.interrupted()方法来检查当前线程的中断状态(并隐式重置为false)。又由于它是静态方法,因此不能在特定的线程上使用,而只能报告调用它的线程的中断状态,如果线程被中断,而且中断状态尚不清楚,那么,这个方法返回true。与isInterrupted()不同,它将自动重置中断状态为false,第二次调用Thread.interrupted()方法,总是返回false,除非中断了线程。 151 | 152 | 如下代码演示了Thread.interrupted()方法的使用: 153 | 154 | ``` 155 | 156 | public class InterruptReset extends Object{ 157 | 158 | public static void main(String[] args) { 159 | System.out.println( 160 | "Point X: Thread.interrupted()=" + Thread.interrupted()); 161 | Thread.currentThread().interrupt(); 162 | System.out.println( 163 | "Point Y: Thread.interrupted()=" + Thread.interrupted()); 164 | System.out.println( 165 | "Point Z: Thread.interrupted()=" + Thread.interrupted()); 166 | } 167 | 168 | } 169 | 170 | ``` 171 | 172 | 运行结果 173 | 174 | 175 | ``` 176 | Point X: Thread.interrupted()=false 177 | Point Y: Thread.interrupted()=true 178 | Point Z: Thread.interrupted()=false 179 | 180 | ``` 181 | 182 | 从结果中可以看出,当前线程中断自身后,在Y点,中断状态为true,并由Thread.interrupted()自动重置为false,那么下次调用该方法得到的结果便是false。 183 | 184 | ##补充 185 | --- 186 | 187 | yield和join方法的使用 188 | 189 | * join方法用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再执行。 190 | * yield可以直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行。 -------------------------------------------------------------------------------- /Part2/JavaConcurrent/线程挂起、恢复与终止的正确方法.md: -------------------------------------------------------------------------------- 1 | #线程挂起、恢复与终止的正确方法(含代码) 2 | --- 3 | 4 | ##挂起和恢复线程 5 | 6 | Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的。如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件——其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁。另外,在长时间计算期间挂起线程也可能导致问题。 7 | 8 | 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: 9 | 10 | ``` 11 | 12 | ``` -------------------------------------------------------------------------------- /Part2/JavaSE/ArrayList 、 LinkedList 、 Vector 的底层实现和区别.md: -------------------------------------------------------------------------------- 1 | #Java基础之集合List-ArrayList、LinkedList、Vector的底层实现和区别 2 | 3 | 4 | 5 | 1. ArrayList底层实际是采用数组实现的(并且该数组的类型是Object类型的) 6 | 2. 如果jdk6,采用Array.copyOf()方法来生成一个新的数组,如果是jdk5,采用的是System.arraycopy()方法(当添加的数据量大于数组的长度的时候) 7 | 3. List list = new ArrayList()时,底层会生成一个长度为10的数组来存放对象 8 | 4. ArrayList、Vector底部都是采用数组实现的 9 | 5. 对于ArrayList,方法都不是同步的,对于Vector,大部分public方法都是同步的 10 | 6. LinkedList采用双向循环列表 11 | 7. 对于ArrayList,查询速度很快,增加和删除(非最后一个节点)操作非常慢(本质上由数组的特性决定的) 12 | 8. 对于LinkedList,查询速度非常慢,增加和删除操作非常快(本质上是由双向循环列表决定的) 13 | 14 | 参考博客: 15 | 16 | [http://blog.csdn.net/sundenskyqq/article/details/27630179](http://blog.csdn.net/sundenskyqq/article/details/27630179) 17 | -------------------------------------------------------------------------------- /Part2/JavaSE/Arraylist.md: -------------------------------------------------------------------------------- 1 | # ArrarList 2 | --- 3 | * 是一个类 4 | * 实现的接口:List、Collectin、Iterable、Serializable、Cloneable、RandomAccess 5 | * 子类:AttributeList、RoleList、RoleUnresolvedList 6 | 7 | ##简介 8 | --- 9 | * 它是顺序表 10 | * 大小可变 11 | * 允许null元素 12 | * 可LinkedList一样,不具备线程同步的安全性、但速度较快 13 | * 第一次定义的时候没有指定数组的长度则长度是0,在第一次添加的时候判断如果是空则追加10。 14 | 15 | ##容量是如何变化的 16 | --- 17 | 在源码中: 18 | 19 | ``` 20 | private transient Object[] elementData; 21 | ``` 22 | 用于保存对象。它会随着元素的添加而改变大小。在下面的叙述中,容量指的是elementData.length,而不是size。 23 | 24 | >size不是容量,ArrayList对象占用的内存不是由size决定的。 25 | >size的大小在每次调用add(E e)方法时加1。 26 | >如果是调用ArrayList(Collection c)构造方法,则size的初始值为c 27 | 28 | * 如果在初始化时,没有指定大小,则容量大小为0。 29 | * 当大小为0时,第一次添加元素容量大小变为10。 30 | * 之后每次添加时,会先确保容量是否够用 31 | * 如果不够用,每次增加的容量为 newCapacity = oldCapacity + (oldCapacity >> 1);即,每次增加为原来的1.5倍。 32 | 33 | ##和Vector的区别 34 | --- 35 | * Vector线程安全 36 | * ArrayList线程不安全,速度快 37 | 38 | ##和LinkedList的区别 39 | --- 40 | * ArrayList是顺序表,LinkedList是链表 41 | * ArrayList查找,修改方便,LinkedList添加、删除方便 42 | 43 | ##方法 44 | --- 45 | **void ensureCapacity(int minCapacity)** 46 | 设置容量能容纳下minCapacity个元素 47 | 48 | * 如果 -------------------------------------------------------------------------------- /Part2/JavaSE/Arraylist和Hashmap如何扩容等.md: -------------------------------------------------------------------------------- 1 | #Arraylist和HashMap如何扩容? 2 | 3 | 4 | 5 | #负载因子有什么作用? 6 | 7 | 8 | 9 | 10 | #如何保证读写进程安全? 11 | -------------------------------------------------------------------------------- /Part2/JavaSE/Collection.md: -------------------------------------------------------------------------------- 1 | 2 | ####线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。 3 | 4 | # Collection 5 | *** 6 | * 是最基本的集合接口 7 | * 继承的接口:Iterable 8 | * 子接口:List、Set、Queue等 9 | 10 | ##简介 11 | * 一个Collection代表一组Object,即Collection的元素(Elements),一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。 12 | * Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。 13 | 14 | ##如何遍历Collection中的每一个元素? 15 | * 不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素,代码如下: 16 | 17 | 18 | 19 | ``` 20 | 21 | Iterator it = collection.iterator(); // 获得一个迭代子 22 | while(it.hasNext())   23 | { 24 | Object obj = it.next(); // 得到下一个元素 25 | } 26 | ``` 27 | 28 | ## 方法 29 | 30 | **retainAll(Collection c)**:保留,交运算 31 | **addAll(Collection c)**:添加,并运算 32 | **removeAll(Collection c)**:移除,减运算 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Part2/JavaSE/Hashmap的hashcode的作用等.md: -------------------------------------------------------------------------------- 1 | #HashMap的hashcode的作用? 2 | 1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的; 3 | 2. 如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同; 4 | 3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点; 5 | 4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。 6 | 7 | #什么时候需要重写? 8 | 9 | 一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为什么要重载hashCode呢?就HashMap来说,好比HashMap就是一个大内存块,里面有很多小内存块,小内存块里面是一系列的对象,可以利用hashCode来查找小内存块hashCode%size(小内存块数量),所以当equal相等时,hashCode必须相等,而且如果是object对象,必须重载hashCode和equal方法。 10 | 11 | [对于equal和hashcode的理解,何时需要重写](http://blog.csdn.net/qq352773277/article/details/41675407) 12 | 13 | #如何解决哈希冲突? 14 | 15 | 1. 线性探查法(Linear Probing) 16 | 2. 线性补偿探测法 17 | 3. 随机探测 18 | 19 | [解决哈希(HASH)冲突的主要方法](http://blog.csdn.net/lightty/article/details/11191971) 20 | 21 | 22 | 23 | #查找的时候流程是如何? 24 | 25 | 1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有 26 | 例如内存中有这样的位置 27 | 0 1 2 3 4 5 6 7 28 | 而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。 29 | 但如果用hashcode那就会使效率提高很多。 30 | 我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。 31 | 2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。 32 | 也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。 33 | 那么。重写了equals(),为什么还要重写hashCode()呢? 34 | 想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊 35 | 36 | 37 | 总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。 38 | 39 | 于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。关于哈希算法,这里就不详细介绍。可以这样简单理解,hashCode方法实际上返回的就是对象存储位置的映像。 40 | 41 | 这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就能定位到它应该放置的存储位置。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就表示发生冲突了,散列表对于冲突有具体的解决办法,但最终还会将新元素保存在适当的位置。这样一来,实际调用equals方法的次数就大大降低了,几乎只需要一两次。 42 | 43 | [深入解析hashcode,hashMap源码](http://blog.csdn.net/zhuanshenweiliu/article/details/39177447) 44 | -------------------------------------------------------------------------------- /Part2/JavaSE/Java中的内存泄漏.md: -------------------------------------------------------------------------------- 1 | #Java中的内存泄漏 2 | --- 3 | 4 | 1.Java内存回收机制 5 | 6 | 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。 7 | 8 | 2.Java内存泄漏引起的原因 9 | 10 | 内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。 11 | 12 | Java内存泄漏的根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。具体主要有如下几大类: 13 | 14 | 1、静态集合类引起内存泄漏: 15 | 16 | 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。 17 | 18 | 例如 19 | 20 | ``` 21 | Static Vector v = new Vector(10); 22 | for (int i = 1; i<100; i++) 23 | { 24 | Object o = new Object(); 25 | v.add(o); 26 | o = null; 27 | } 28 | ``` 29 | 30 | 在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。 31 | 32 | 2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。 33 | 34 | 例如: 35 | 36 | ``` 37 | public static void main(String[] args) 38 | { 39 | Set set = new HashSet(); 40 | Person p1 = new Person("唐僧","pwd1",25); 41 | Person p2 = new Person("孙悟空","pwd2",26); 42 | Person p3 = new Person("猪八戒","pwd3",27); 43 | set.add(p1); 44 | set.add(p2); 45 | set.add(p3); 46 | System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! 47 | p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 48 | 49 | set.remove(p3); //此时remove不掉,造成内存泄漏 50 | 51 | set.add(p3); //重新添加,居然添加成功 52 | System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! 53 | for (Person person : set) 54 | { 55 | System.out.println(person); 56 | } 57 | } 58 | ``` 59 | 60 | 3、监听器 61 | 62 | 在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。 63 | 64 | 4、各种连接 65 | 66 | 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。 67 | 68 | 5、内部类和外部模块的引用 69 | 70 | 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: 71 | public void registerMsg(Object b); 72 | 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。 73 | 74 | 6、单例模式 75 | 76 | 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏,考虑下面的例子: 77 | 78 | ``` 79 | class A{ 80 | public A(){ 81 | B.getInstance().setA(this); 82 | } 83 | .... 84 | } 85 | //B类采用单例模式 86 | class B{ 87 | private A a; 88 | private static B instance=new B(); 89 | public B(){} 90 | public static B getInstance(){ 91 | return instance; 92 | } 93 | public void setA(A a){ 94 | this.a=a; 95 | } 96 | //getter... 97 | } 98 | ``` 99 | 100 | 101 | 显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况 102 | -------------------------------------------------------------------------------- /Part2/JavaSE/Java集合框架.md: -------------------------------------------------------------------------------- 1 | Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组、链表、栈、队列、集合、哈希表等。学习Java集合框架下大致可以分为如下五个部分:List列表、Set集合、Map映射、迭代器(Iterator、Enumeration)、工具类(Arrays、Collections)。 2 | 3 | Java集合类的整体框架如下: 4 | 5 | ![](http://img.blog.csdn.net/20140628144205625) 6 | 7 | 从上图中可以看出,集合类主要分为两大类:Collection和Map。 8 | 9 | Collection是List、Set等集合高度抽象出来的接口,它包含了这些集合的基本操作,它主要又分为两大部分:List和Set。 10 | 11 | List接口通常表示一个列表(数组、队列、链表、栈等),其中的元素可以重复,常用实现类为ArrayList和LinkedList,另外还有不常用的Vector。另外,LinkedList还是实现了Queue接口,因此也可以作为队列使用。 12 | 13 | Set接口通常表示一个集合,其中的元素不允许重复(通过hashcode和equals函数保证),常用实现类有HashSet和TreeSet,HashSet是通过Map中的HashMap实现的,而TreeSet是通过Map中的TreeMap实现的。另外,TreeSet还实现了SortedSet接口,因此是有序的集合(集合中的元素要实现Comparable接口,并覆写Compartor函数才行)。 14 | 我们看到,抽象类AbstractCollection、AbstractList和AbstractSet分别实现了Collection、List和Set接口,这就是在Java集合框架中用的很多的适配器设计模式,用这些抽象类去实现接口,在抽象类中实现接口中的若干或全部方法,这样下面的一些类只需直接继承该抽象类,并实现自己需要的方法即可,而不用实现接口中的全部抽象方法。 15 | 16 | Map是一个映射接口,其中的每个元素都是一个key-value键值对,同样抽象类AbstractMap通过适配器模式实现了Map接口中的大部分函数,TreeMap、HashMap、WeakHashMap等实现类都通过继承AbstractMap来实现,另外,不常用的HashTable直接实现了Map接口,它和Vector都是JDK1.0就引入的集合类。 17 | 18 | Iterator是遍历集合的迭代器(不能遍历Map,只用来遍历Collection),Collection的实现类都实现了iterator()函数,它返回一个Iterator对象,用来遍历集合,ListIterator则专门用来遍历List。而Enumeration则是JDK1.0时引入的,作用与Iterator相同,但它的功能比Iterator要少,它只能再Hashtable、Vector和Stack中使用。 19 | 20 | Arrays和Collections是用来操作数组、集合的两个工具类,例如在ArrayList和Vector中大量调用了Arrays.Copyof()方法,而Collections中有很多静态方法可以返回各集合类的synchronized版本,即线程安全的版本,当然了,如果要用线程安全的结合类,首选Concurrent并发包下的对应的集合类。 -------------------------------------------------------------------------------- /Part2/JavaSE/Linkedlist.md: -------------------------------------------------------------------------------- 1 | #LinkedList 2 | --- 3 | * 是一个类 4 | * 实现的接口:List、Collection、Iterable、Serializable、Cloneable、Deque,Queue 5 | * 子类:没有子类 6 | 7 | ##简介 8 | --- 9 | * 它是链表 10 | * 它还是队列、双端队列 11 | * 它还可以用作堆栈 12 | * 和ArrayList一样,不具有线程安全性 13 | 14 | ##关于添加元素 15 | --- 16 | **boolean add(E e)** 17 | 添加到链表末尾 18 | 19 | **void add(int index, E e)** 20 | 添加到指定位置 21 | 22 | **boolean addAll(int index, Collection c)** 23 | 24 | **boolean addAll(Collection c)** 25 | 26 | -------------------------------------------------------------------------------- /Part2/JavaSE/List.md: -------------------------------------------------------------------------------- 1 | # List 2 | *** 3 | * 是一个接口 4 | * 继承的接口:Collection 5 | * 间接继承的接口:Iterable 6 | * 实现类:ArrayList、LinkedList、Vector等 7 | 8 | ##简介 9 | * List是有序的**Collection** 10 | - 可以对每个元素的插入位置进行精准控制 11 | - 可以根据索引访问元素 12 | * 允许重复元素 13 | * 有自己的迭代器 ListIterator 14 | * 如果元素包含自身,equals()和hashCode()不再是良定义的 15 | 16 | ## 方法 17 | **boolean add(E e)**: 添加到末尾 18 | **void add(int index, E e)**: 添加到指定位置 19 | 20 | **E set(int index, E e)**: 设置指定位置的元素,返回一个E 21 | **get(int index)**: 获得指定位置的元素 22 | 23 | **Iterator iterator()** 24 | **ListIterator listIterator()** 25 | **ListIterator listIterator(int index)** 26 | 27 | **int indexOf(E e)** 28 | **int lastIndexOf(E e)** 29 | 30 | **List subList(int fromIndex, int toIndex)** 31 | 32 | ##子类介绍 33 | 1. ArrayList是一个可改变大小的数组,当更多的元素加入到ArrayList中时,其大小将会动态的增长。内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组 34 | 2. LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义。 35 | LinkedList还实现了Queue接口,该接口比List提供了更多的方法,包括offer(),peek(),poll等 36 | 3. Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。 37 | Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%. 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Part2/JavaSE/Queue.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | --- 3 | * 是一个泛型接口 4 | * 父接口:Collection 5 | * 子接口:Deque 6 | 7 | ##简介 8 | --- 9 | * 是个队列 10 | * 插入、提取、检查操作都存在两种形式,一种在操作失败后返回特殊值,一种抛出异常 11 | 12 | ##方法 13 | --- 14 | 1. boolean add(E e)和 boolean offer(E e)添加元素 15 | 失败时,add()抛出异常,offer()返回false 16 | 2. E element()和E peek()获取但不移除 17 | 失败时,element抛出异常,peek()返回null 18 | 3. E remove()和E poll()获取并移除 19 | 失败时,remove()抛出异常,poll()返回null 20 | 21 | ##子类介绍 22 | --- 23 | ###Deque 24 | * 双端队列 25 | * 可以实现队列。也可以用作栈 26 | -------------------------------------------------------------------------------- /Part2/JavaSE/Set.md: -------------------------------------------------------------------------------- 1 | #Set 2 | --- 3 | * 是一个泛型接口 4 | * 继承了接口Collection 5 | * 子接口:NavigableSet、SortedSet 6 | * 子类:EnumSet、HashSet、LinkedHashSet、TreeSet、AbstractSet等 7 | * 不允许重复元素 8 | 9 | 两个注意点 10 | --- 11 | 12 | ``` 13 | 1. Set中的元素的类,必须有一个有效的equals方法。 14 | 2. 对Set的构造方法,传入的Collection对象中重复的元素会只留下一个 15 | ``` -------------------------------------------------------------------------------- /Part2/JavaSE/hashmap和hashtable的底层实现和区别,两者和concurrenthashmap的区别。.md: -------------------------------------------------------------------------------- 1 | #HashMap 和 HashTable 的底层实现和区别,两者和 ConcurrentHashMap 的区别。 2 | 3 | HashMap和HashTable源代码级别的区别 4 | 5 | 1. 最明显的区别在于Hashtable是同步的(每个方法都是synchronized),而HashMap则不是 6 | 7 | 8 | [](http://www.cnblogs.com/langtianya/archive/2013/03/19/2970273.html) -------------------------------------------------------------------------------- /Part2/JavaSE/从源码分析HashMap.md: -------------------------------------------------------------------------------- 1 | #HashMap 2 | --- 3 | 4 | 5 | ###HashMap和Hashtable的区别: 6 | 7 | 1. Hashtable的大部分方法做了同步,HashMap没有,因此,HashMap不是线程安全的。 8 | 2. Hashtable不允许key或者value使用null值,而HashMap可以。 9 | 3. 在内部算法上,它们对key的hash算法和hash值到内存索引的映射算法不同。 10 | 11 | ###HashMap的实现原理 12 | 13 | 简单说,HashMap就是将key做hash算法,然后将hash所对应的数据映射到内存地址,直接取得key所对应的数据。在HashMap中。底层数据结构使用的是数组,所谓的内存地址即数组的下标索引。HashMap的高性能需要保证以下几点: 14 | 15 | * hash算法必须高效 16 | * hash值到内存地址(数组索引)的算法是快速的 17 | * 根据内存地址(数组索引)可以直接取得对应的值 18 | 19 | 如何保证hash算法高效,hash算法有关的代码如下: 20 | 21 | ``` 22 | int hash = hash(key.hashCode()); 23 | public native int hashCode(); 24 | static int hash(int h){ 25 | h ^= (h >>> 20) ^ (h >>> 12); 26 | return h ^ (h >>> 7) ^ (h >>> 4); 27 | } 28 | ``` 29 | 30 | 第一行代码是HashMap用于计算key的hash值,它前后调用了Object类的hashCode()方法和HashMap的内部函数hash()。Object类的hashCode()方法默认是native的实现,可以认为不存在性能问题。而hash()函数的实现全部基于位运算,因此,也是高效的。 31 | 32 | 当取得key的hash值后,需要通过hash值得到内存地址: 33 | 34 | ``` 35 | int i = indexFor(hash, table.length); 36 | static int indexFor(int h, int length){ 37 | return h & (length - 1); 38 | } 39 | ``` 40 | 41 | indexFor()函数通过将hash值和数组长度按位与直接得到数组索引。 42 | 最后由indexFor()函数返回的数组索引直接通过数组下标便可取得对应的值,直接的内存访问速度也是相当的快,因此,可认为HashMap是高性能的。 43 | 44 | ###Hash冲突 45 | 46 | 如图3.11所示,需要存放到HashMap中的两个元素1和2,通过hash计算后,发现对应在内存中的同一个地址,如何处理? 47 | 其实HashMap的底层实现使用的是数组,但是数组内的元素并不是简单的值。而是一个Entry类的对象。因此,对HashMap结构贴切描述如图3.12所示。 48 | 49 | ![这里写图片描述](http://img.blog.csdn.net/20160509103524275) 50 | 51 | 52 | 可以看到,HashMap的内部维护着一个Entry数组,每一个Entry表项包括key、value、next和hash几项。next部分指向另外一个Entry。进一步阅读HashMap的put()方法源码,可以看到当put()操作有冲突时,新的Entry依然会被安放在对应的索引下标内,并替换原有的值。同时为了保证旧值不丢失,会将新的Entry的next指向旧值。这便实现了在一个数组索引空间内存放多个值项。因此,如图3.12所示,HashMap实际上是一个链表的数组。 53 | 54 | ``` 55 | public V put(K key, V value){ 56 | if(key == null) 57 | return putForNullKey(value); 58 | int hash = hash(key.hashCode()); 59 | int i = indexFor(hash, table.length); 60 | for(Entry e = table[i]; e != null; e = e.next){ 61 | Object k; 62 | //如果当前的key已经存在于HashMap中 63 | if(e.hash == hash && ((k = e.key) == key || key.equals(k))) 64 | { 65 | V oldValue = e.value; //取得旧值 66 | e.value = value; 67 | e.recordAccess(this); 68 | return oldValue; //返回旧值 69 | } 70 | } 71 | modCount++; 72 | addEntry(hash, key, value, i); //添加当前的表项到i位置 73 | return null; 74 | } 75 | ``` 76 | 77 | addEntry()方法的实现如下: 78 | 79 | ``` 80 | void addEntry(int hash, K key, V value, int bucketIndex){ 81 | Entry e = table[bucketIndex]; 82 | //将新增元素放到i的位置,并让它的next指向旧的元素 83 | table[bucketIndex] = new Entry(hash, key, value, e); 84 | if(size++ >= threshold){ 85 | resize(2 * table.length); 86 | } 87 | } 88 | ``` 89 | 90 | 基于HashMap的这种实现机制,只要hashCode和hash()方法实现的足够好,能够尽可能的减少冲突的产生,那么对HashMap的操作几乎等价于对数组的随机访问操作,具有很好的性能。但是,如果hashCode()或者hash()方法实现较差,在大量冲突产生的情况下,HashMap事实上就退化为几个链表,对HashMap的操作等价于遍历链表,此时性能很差。 91 | 92 | ###容量参数 93 | 94 | 除hashCode()的实现外,影响HashMap性能的还有它的容量参数。和ArrayList和Vector一样,这种基于数组的结构,不可避免的需要在数组空间不足时,进行扩展。而数组的重组相对而言较为耗时,因此对其作一定了解有助于优化HashMap的性能。 95 | 96 | HashMap提供了两个可以指定初始化大小的构造函数: 97 | 98 | ``` 99 | public HashMap(int initialCapacity) 100 | public HashMap(int initialCapacity, float loadFactor) 101 | ``` 102 | 103 | 其中initialCapacity指定了HashMap的初始容量,loadFactor指定了其负载因子。初始容量即数组的大小,HashMap会使用大于等于initialCapacity并且是2的指数次幂的最小的整数作为内置数组的大小。负载因子又叫填充比,它是介于0和1之间的浮点数,它决定了HashMap在扩容之前,其内部数组的填充度。默认情况下,HashMap初始大小为16,负载因子为0.75。 104 | 105 | **负载因子 = 元素个数/内部数组总大小** 106 | 107 | 在实际使用中,负载因子也可以设置为大于1的数,但如果这样做,HashMap将必然产生大量冲突,因为这无疑是在尝试往只有10个口袋的包里放15件物品,必然有几只口袋要大于一个物件。因此,通常不会这么使用。 108 | 109 | 在HashMap内部,还维护了一个threshold变量,它始终被定义为当前数组总容量和负载因子的乘积,它表示HashMap的阈值。当HashMap的实际容量超过阈值时,HashMap便会进行扩容。因此,HashMap的实际容量超过阈值时,HashMap便会进行扩容。因此,HashMap的实际填充率不会超过负载因子。 110 | 111 | HashMap扩容的代码如下: 112 | 113 | ``` 114 | void resize(int newCapacity){ 115 | Entry[] oldTable = table; 116 | int oldCapacity= oldTable.length; 117 | if(oldCapacity == MAXMUM_CAPACITY){ 118 | threhold = Integer.MAX_VALUE; 119 | return; 120 | } 121 | //建立新的数组 122 | Entry[] newTable = new Entry[newCapacity]; 123 | //将原有数组转到新的数组中 124 | transfer(newTable); 125 | table = newTable; 126 | //重新设置阈值,为新的容量和负载因子的乘积 127 | threshold = (int)(newCapacity * loadFactory); 128 | } 129 | ``` 130 | 131 | 其中,数组迁移逻辑主要在transfer()函数中实现,该函数实现和注释如下: 132 | 133 | ``` 134 | void transfer(Entry[] newTable){ 135 | Entry[] src = table; 136 | int newCapacity = newTable.length; 137 | //遍历数组内所有表项 138 | for(int j = 0; j < src.length; j++){ 139 | Entry e = src[j]; 140 | //当该表项索引有值存在时,则进行迁移 141 | if(e != null){ 142 | src[j] = null; 143 | do{ 144 | //进行数据迁移 145 | Entry next = e.next; 146 | //计算该表现在新数组内的索引,并放置到新的数组中 147 | //建立新的链表关系 148 | int i = indexFor(e,hash, newCapacity); 149 | e.next = newTable[i]; 150 | newTable[i] = e; 151 | e = next; 152 | }while(e != null) 153 | } 154 | } 155 | } 156 | ``` 157 | 158 | 很明显,HashMap的扩容操作会遍历整个HashMap,应该尽量避免该操作发生,设置合理的初始大小和负载因子,可以有效的减少HashMap扩容的次数。 159 | 160 | 161 | **参考书籍:《Java程序性能优化》** -------------------------------------------------------------------------------- /Part2/JavaSE/反射机制.md: -------------------------------------------------------------------------------- 1 | #反射机制 2 | 3 | 反射技术:其实就是动态加载一个指定的类,并获取该类中所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。 4 | 5 | 6 | 反射的好处:大大增强了程序的扩展性。 7 | 8 | 反射的基本步骤: 9 | 10 | 1. 获得Class对象,就是获得指定的名称的字节码文件对象 11 | 2. 实例化对象,获得类的属性、方法或者构造函数 12 | 3. 访问属性、调用方法、调用构造函数创建对象 13 | 14 | 15 | -------------------------------------------------------------------------------- /Part2/JavaSE/如何表达出Collection及其子类.md: -------------------------------------------------------------------------------- 1 | #如何介绍数据结构 2 | 3 | 1. 是泛型?接口?类? 4 | 2. 是个什么? 5 | 3. 相比父类的特点? 6 | 4. 相比于其他类型的特点? 7 | 5. 自己的特殊方法? 8 | 6. 子类? 9 | 10 | 11 | #Collection 12 | --- 13 | 1. 是一个泛型的接口 14 | 2. 继承了超级接口Iterable 15 | 3. 每个Collection的对象包含了一组对象 16 | 4. 所有的实现类都有两个构造方法,一个是无参构造方法,第二个是用另外一个Collection对象作为构造方法的参数 17 | 5. 遍历Collection使用Iterator迭代器实现 18 | 6. retainAll(collection),AddAll(),removeAll(c)分别对应了集合的交并差运算 19 | 7. 没有具体的直接实现,但提供了更具体的子接口,如Set、List等 20 | 21 | 22 | #List 23 | --- 24 | 1. 是一个接口,继承了接口Collection 25 | 2. List是有序的Collection,能够精确控制插入、获取的位置 26 | 3. 和Set接口的最大区别是,List允许重复值,Set不能 27 | 4. 它的直接实现类有ArrayList,LinkedList,Vector等 28 | 5. List有自己的迭代器ListIterator,可以通过这个迭代器进行逆序的迭代,以及用迭代器设置元素的值 29 | 30 | #ArrayList 31 | --- 32 | 1. ArrayList实现了Collection接口 33 | 2. ArrayList是一个顺序表。大小可变。 34 | 3. ArrayList相比LinkedList在查找和修改元素上比较快,但是在添加和删除上比LinkedList慢 35 | 4. ArrayList相比Vector是线程不安全的 36 | 37 | 38 | #LinkedList 39 | --- 40 | 1. LinkedList泛型接口 41 | 2. 链表 42 | 3. 由于实现了Deque接口,所以它还是一个双端队列 -------------------------------------------------------------------------------- /Part3/Algorithm/LeetCode/two-sum.md: -------------------------------------------------------------------------------- 1 | #two-sum 2 | --- 3 | 4 | Question 5 | 6 | ``` 7 | Given an array of integers, find two numbers such that they add up to a specific target number. 8 | The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based. 9 | You may assume that each input would have exactly one solution. 10 | Input: numbers={2, 7, 11, 15}, target=9 11 | Output: index1=1, index2=2 12 | ``` 13 | 14 | 15 | 题目大意: 16 | 17 | ``` 18 | 给定一个整数数组,找到2个数字,这样他们就可以添加到一个特定的目标号。功能twosum应该返回两个数字,他们总计达目标数,其中index1必须小于index2。请注意,你的答案返回(包括指数和指数)不为零的基础。你可以假设每个输入都有一个解决方案。 19 | 输入数字numbers= { 2,7,11,15 },目标= 9输出:index1 = 1,index2= 2 20 | ``` 21 | 22 | 解题思路: 23 | 24 | ``` 25 | 可以申请额外空间来存储目标数减去从头遍历的数,记为key,如果hashMap中存在该key,就可以返回两个索引了。 26 | ``` 27 | 28 | 29 | 代码; 30 | 31 | ``` 32 | import java.util.HashMap; 33 | 34 | public class Solution { 35 | 36 | public int[] twoSum(int[] numbers, int target) { 37 | HashMap map = new HashMap<>(); 38 | for(int i = 0; i < numbers.length; i++){ 39 | if(map.get(numbers[i]) != null){ 40 | int[] result = {map.get(numbers[i]) + 1, i+1}; 41 | return result; 42 | }else { 43 | map.put(target - numbers[i], i); 44 | } 45 | } 46 | int[] result = {}; 47 | return result; 48 | } 49 | } 50 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/LeetCode/zigzag-conversion.md: -------------------------------------------------------------------------------- 1 | #zigzag-conversion 2 | --- 3 | 4 | 题目描述 5 | 6 | ``` 7 | The string"PAYPALISHIRING"is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility) 8 | 9 | P A H N 10 | A P L S I I G 11 | Y I R 12 | 13 | And then read line by line:"PAHNAPLSIIGYIR" 14 | 15 | Write the code that will take a string and make this conversion given a number of rows: 16 | 17 | string convert(string text, int nRows); 18 | 19 | convert("PAYPALISHIRING", 3)should return"PAHNAPLSIIGYIR". 20 | 21 | ``` 22 | 23 | 题目大意 24 | 25 | ``` 26 | 27 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/Lookup/折半查找.md: -------------------------------------------------------------------------------- 1 | #折半查找 2 | --- 3 | 4 | 基本原理:每次查找都对半分,但要求数组是有序的 5 | 6 | 7 | ``` 8 | public class Solution { 9 | 10 | public static int BinarySearch(int[] sz,int key){ 11 | int low = 0; 12 | int high = sz.length - 1; 13 | 14 | while (low <= high) { 15 | int middle = (low + high) / 2; 16 | if(sz[middle] == key){ 17 | return middle; 18 | }else if(sz[middle] > key){ 19 | high = middle - 1; 20 | }else { 21 | low = middle + 1; 22 | } 23 | } 24 | return -1; 25 | } 26 | } 27 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/Lookup/顺序查找.md: -------------------------------------------------------------------------------- 1 | #顺序查找 2 | --- 3 | 4 | 基本原理:依次遍历 5 | 6 | ``` 7 | public class Solution { 8 | 9 | public static int SequenceSearch(int[] sz, int key) { 10 | for (int i = 0; i < sz.length; i++) { 11 | if (sz[i] == key) { 12 | return i; 13 | } 14 | } 15 | return -1; 16 | } 17 | } 18 | 19 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/Sort/冒泡排序.md: -------------------------------------------------------------------------------- 1 | #冒泡排序: 2 | --- 3 | * 背景介绍: 是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F) 4 | * 算法规则: 由于算法每次都将一个最大的元素往上冒,我们可以将待排序集合(0...n)看成两部分,一部分为(k..n)的待排序unsorted集合,另一部分为(0...k)的已排序sorted集合,每一次都在unsorted集合从前往后遍历,选出一个数,如果这个数比其后面的数大,则进行交换。完成一轮之后,就肯定能将这一轮unsorted集合中最大的数移动到集合的最后,并且将这个数从unsorted中删除,移入sorted中。 5 | 6 | * 代码实现(Java版本) 7 | ``` 8 | public void sort(int[] args) 9 | { 10 | //第一层循环从数组的最后往前遍历 11 | for (int i = args.length - 1; i > 0 ; --i) { 12 | //这里循环的上界是 i - 1,在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除” 13 | for (int j = 0; j < i; j++) { 14 | //保证在相邻的两个数中比较选出最大的并且进行交换(冒泡过程) 15 | if (args[j] > args[j+1]) { 16 | int temp = args[j]; 17 | args[j] = args[j+1]; 18 | args[j+1] = temp; 19 | } 20 | } 21 | } 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /Part3/Algorithm/Sort/归并排序.md: -------------------------------------------------------------------------------- 1 | #归并排序: 2 | --- 3 | * 背景介绍: 是创建在归并操作上的一种有效的排序算法,效率为O(n log n)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F) 4 | * **算法规则: 像快速排序一样,由于归并排序也是分治算法,因此可使用分治思想:**
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4.重复步骤3直到某一指针到达序列尾
5.将另一序列剩下的所有元素直接复制到合并序列尾 5 | 6 | * 代码实现(Java版本) 7 | 8 | public void mergeSort(int[] ints, int[] merge, int start, int end) 9 | { 10 | if (start >= end) return; 11 | 12 | int mid = (end + start) >> 1; 13 | 14 | mergeSort(ints, merge, start, mid); 15 | mergeSort(ints, merge, mid + 1, end); 16 | 17 | merge(ints, merge, start, end, mid); 18 | 19 | } 20 | 21 | private void merge(int[] a, int[] merge, int start, int end,int mid) 22 | { 23 | int i = start; 24 | int j = mid+1; 25 | int pos = start; 26 | while( i <= mid || j <= end ){ 27 | if( i > mid ){ 28 | while( j <= end ) merge[pos++] = a[j++]; 29 | break; 30 | } 31 | 32 | if( j > end ){ 33 | while( i <= mid ) merge[pos++] = a[i++]; 34 | break; 35 | } 36 | 37 | merge[pos++] = a[i] >= a[j] ? a[j++] : a[i++]; 38 | } 39 | 40 | for (pos = start; pos <= end; pos++) 41 | a[pos] = merge[pos]; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Part3/Algorithm/Sort/快速排序.md: -------------------------------------------------------------------------------- 1 | 2 | #快速排序: 3 | --- 4 | * 背景介绍: 又称划分交换排序(partition-exchange sort),一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序n个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F) ** 5 | * 算法规则: 本质来说,快速排序的过程就是不断地将无序元素集递归分割,一直到所有的分区只包含一个元素为止。
由于快速排序是一种分治算法,我们可以用分治思想将快排分为三个步骤:
1.分:设定一个分割值,并根据它将数据分为两部分
2.治:分别在两部分用递归的方式,继续使用快速排序法
3.合:对分割的部分排序直到完成 6 | 7 | * 代码实现(Java版本) 8 | ``` 9 | public int dividerAndChange(int[] args, int start, int end) 10 | { 11 | //标准值 12 | int pivot = args[start]; 13 | while (start < end) { 14 | // 从右向左寻找,一直找到比参照值还小的数值,进行替换 15 | // 这里要注意,循环条件必须是 当后面的数 小于 参照值的时候 16 | // 我们才跳出这一层循环 17 | while (start < end && args[end] >= pivot) 18 | end--; 19 | 20 | if (start < end) { 21 | swap(args, start, end); 22 | start++; 23 | } 24 | 25 | // 从左向右寻找,一直找到比参照值还大的数组,进行替换 26 | while (start < end && args[start] < pivot) 27 | start++; 28 | 29 | if (start < end) { 30 | swap(args, end, start); 31 | end--; 32 | } 33 | } 34 | 35 | args[start] = pivot; 36 | return start; 37 | } 38 | 39 | public void sort(int[] args, int start, int end) 40 | { 41 | //当分治的元素大于1个的时候,才有意义 42 | if ( end - start > 1) { 43 | int mid = 0; 44 | mid = dividerAndChange(args, start, end); 45 | // 对左部分排序 46 | sort(args, start, mid); 47 | // 对右部分排序 48 | sort(args, mid + 1, end); 49 | } 50 | } 51 | 52 | private void swap(int[] args, int fromIndex, int toIndex) 53 | { 54 | args[fromIndex] = args[toIndex]; 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /Part3/Algorithm/Sort/选择排序.md: -------------------------------------------------------------------------------- 1 | #选择排序: 2 | --- 3 | * 背景介绍: 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F) 4 | * 算法规则: 将待排序集合(0...n)看成两部分,在起始状态中,一部分为(k..n)的待排序unsorted集合,另一部分为(0...k)的已排序sorted集合,在待排序集合中挑选出最小元素并且记录下标i,若该下标不等于k,那么 unsorted[i] 与 sorted[k]交换 ,一直重复这个过程,直到unsorted集合中元素为空为止。 5 | 6 | * 代码实现(Java版本) 7 | ``` 8 | public void sort(int[] args) 9 | { 10 | int len = args.length; 11 | for (int i = 0,k = 0; i < len; i++,k = i) { 12 | // 在这一层循环中找最小 13 | for (int j = i + 1; j < len; j++) { 14 | // 如果后面的元素比前面的小,那么就交换下标,每一趟都会选择出来一个最小值的下标 15 | if (args[k] > args[j]) k = j; 16 | } 17 | 18 | if (i != k) { 19 | int tmp = args[i]; 20 | args[i] = args[k]; 21 | args[k] = tmp; 22 | } 23 | } 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/1.七种方式实现singleton模式.md: -------------------------------------------------------------------------------- 1 | #面试题2.七种方式实现Singleton模式 2 | --- 3 | ``` 4 | 5 | public class Test { 6 | 7 | /** 8 | * 单例模式,懒汉式,线程安全 9 | */ 10 | public static class Singleton { 11 | private final static Singleton INSTANCE = new Singleton(); 12 | 13 | private Singleton() { 14 | 15 | } 16 | 17 | public static Singleton getInstance() { 18 | return INSTANCE; 19 | } 20 | } 21 | 22 | /** 23 | * 单例模式,懒汉式,线程不安全 24 | */ 25 | public static class Singleton2 { 26 | private static Singleton2 instance = null; 27 | 28 | private Singleton2() { 29 | 30 | } 31 | 32 | public static Singleton2 getInstance() { 33 | if (instance == null) { 34 | instance = new Singleton2(); 35 | } 36 | return instance; 37 | } 38 | } 39 | 40 | /** 41 | * 单例模式,饿汉式,线程安全,多线程环境下效率不高 42 | */ 43 | public static class Singleton3 { 44 | private static Singleton3 instance = null; 45 | 46 | private Singleton3() { 47 | 48 | } 49 | 50 | public static synchronized Singleton3 getInstance() { 51 | if (instance == null) { 52 | instance = new Singleton3(); 53 | } 54 | return instance; 55 | } 56 | } 57 | 58 | /** 59 | * 单例模式,懒汉式,变种,线程安全 60 | */ 61 | public static class Singleton4 { 62 | private static Singleton4 instance = null; 63 | 64 | static { 65 | instance = new Singleton4(); 66 | } 67 | 68 | private Singleton4() { 69 | 70 | } 71 | 72 | public static Singleton4 getInstance() { 73 | return instance; 74 | } 75 | } 76 | 77 | /** 78 | * 单例模式,使用静态内部类,线程安全(推荐) 79 | */ 80 | public static class Singleton5 { 81 | private final static class SingletonHolder { 82 | private static final Singleton5 INSTANCE = new Singleton5(); 83 | } 84 | 85 | private static Singleton5 getInstance() { 86 | return SingletonHolder.INSTANCE; 87 | } 88 | } 89 | 90 | /** 91 | * 静态内部类,使用枚举方式,线程安全(推荐) 92 | */ 93 | public enum Singleton6 { 94 | INSTANCE; 95 | public void whateverMethod() { 96 | 97 | } 98 | } 99 | 100 | /** 101 | * 静态内部类,使用双重校验锁,线程安全(推荐) 102 | */ 103 | public static class Singleton7 { 104 | private volatile static Singleton7 instance = null; 105 | 106 | private Singleton7() { 107 | 108 | } 109 | 110 | public static Singleton7 getInstance() { 111 | if (instance == null) { 112 | synchronized (Singleton7.class) { 113 | if (instance == null) { 114 | instance = new Singleton7(); 115 | } 116 | } 117 | } 118 | return instance; 119 | } 120 | } 121 | 122 | } 123 | 124 | ``` 125 | -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/2.二维数组中的查找.md: -------------------------------------------------------------------------------- 1 | #面试题3:二维数组中的查找 2 | --- 3 | 4 | 这个题我在面试今日头条的时候面试官让我写过这个题目的代码,所以比较重要。 5 | 6 | ###题目描述 7 | 8 | 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 9 | 输入描述: 10 | 11 | ``` 12 | array: 待查找的二维数组 13 | target:查找的数字 14 | ``` 15 | 16 | 17 | 输出描述: 18 | 19 | ``` 20 | 查找到返回true,查找不到返回false 21 | ``` 22 | 23 | 解题思路: 24 | 25 | ``` 26 | 27 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/合并两个排序的链表.md: -------------------------------------------------------------------------------- 1 | #合并两个排序的链表 2 | --- 3 | 4 | 题目: 5 | 6 | >输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。例如输入图中的链表1和链表2,则合并之后的升序链表3所示: 7 | 8 | ![](http://img.blog.csdn.net/20150801211607173?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center =500x200) 9 | 10 | 容易犯的错误: 11 | 12 | 1. 写代码之前没有对合并的过程想清楚,最终合并出来的链表要么中间断开了要么并没有做到递增排序 13 | 2. 代码在鲁棒性方面存在问题,程序一旦有特殊的输入(如空链表)就会崩溃。 14 | 15 | -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/旋转数组的最小数字.md: -------------------------------------------------------------------------------- 1 | #旋转数组的最小数字 2 | --- 3 | 4 | 题目 5 | 6 | >题目: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为旋转。 输入一个递增的排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小元素为1. 7 | 8 | 9 | 解法: 10 | 11 | >旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素,而且最小的元素刚好是这两个子数组的分界线。 12 | 13 | >和二分查找一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。 -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/面试题11:数值的整数次方.md: -------------------------------------------------------------------------------- 1 | #面试题11 数值的整数次方 2 | --- 3 | 4 | 题目: 5 | 6 | >实现函数double Power(double base,int exponent),求base的exponent次方,不得使用库函数,同时不需要考虑大数问题。 7 | 8 | 看到了很多人会这样写: 9 | 10 | ``` 11 | public static double powerWithExponent(double base,int exponent){ 12 | double result = 1.0; 13 | for(int i = 1; i <= exponent; i++){ 14 | result = result * base; 15 | } 16 | return result; 17 | } 18 | ``` 19 | 20 | 输入的指数(exponent)小于1即是零和负数时怎么办? 21 | 22 | 当指数为负数的时候,可以先对指数求绝对值,然后算出次方的结果之后再取倒数,当底数(base)是零且指数是负数的时候,如果不做特殊处理,就会出现对0求倒数从而导致程序运行出错。最后,由于0的0次方在数学上是没有意义的,因此无论是输出0还是1都是可以接受的。 23 | 24 | ``` 25 | public double power(double base, int exponent) throws Exception { 26 | double result = 0.0; 27 | if (equal(base, 0.0) && exponent < 0) { 28 | throw new Exception("0的负数次幂无意义"); 29 | } 30 | if (equal(exponent, 0)) { 31 | return 1.0; 32 | } 33 | if (exponent < 0) { 34 | result = powerWithExponent(1.0 / base, -exponent); 35 | } else { 36 | result = powerWithExponent(base, exponent); 37 | } 38 | return result; 39 | } 40 | 41 | private double powerWithExponent(double base, int exponent) { 42 | double result = 1.0; 43 | for (int i = 1; i <= exponent; i++) { 44 | result = result * base; 45 | } 46 | return result; 47 | } 48 | 49 | // 判断两个double型数据,计算机有误差 50 | private boolean equal(double num1, double num2) { 51 | if ((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001)) { 52 | return true; 53 | } else { 54 | return false; 55 | } 56 | } 57 | ``` 58 | 59 | 一个细节,再判断底数base是不是等于0时,不能直接写base==0,这是因为在计算机内表示小数时(包括float和double型小数)都有误差。判断两个数是否相等,只能判断 它们之间的绝对值是不是在一个很小的范围内。如果两个数相差很小,就可以认为它们相等。 60 | 61 | 还有更快的方法。 62 | 63 | 如果我们的目标是求出一个数字的32次方,如果我们已经知道了它的16次方,那么只要在16次方的基础上再平方一次就好了,依此类推,我们求32次方只需要做5次乘法。 64 | 65 | 我们可以利用如下公式: 66 | 67 | ![](http://img.blog.csdn.net/20150731084039653) 68 | 69 | ``` 70 | private double powerWithExponent2(double base,int exponent){ 71 | if(exponent == 0){ 72 | return 1; 73 | } 74 | if(exponent == 1){ 75 | return base; 76 | } 77 | double result = powerWithExponent2(base, exponent >> 1); 78 | result *= result; 79 | if((exponent&0x1) == 1){ 80 | result *= base; 81 | } 82 | return result; 83 | } 84 | ``` 85 | 86 | 我们用右移运算代替除2,用位与运算符代替了求余运算符(%)来判断一个数是奇数还是偶数。位运算的效率比乘除法及求余运算的效率要高很多。 87 | -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/面试题12:打印1到最大的n位数.md: -------------------------------------------------------------------------------- 1 | # 面试题12 打印1到最大的n位数 2 | --- 3 | 4 | 题目: 5 | 6 | >题目:输入数字n,按顺序打印出从1最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。 7 | 8 | 我们能写出如下代码: 9 | 10 | ``` 11 | 12 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/面试题44:扑克牌的顺子.md: -------------------------------------------------------------------------------- 1 | #面试题44:扑克牌的顺子 2 | --- 3 | 4 | 题目: 5 | 6 | ``` 7 | 从扑克牌中随机抽 5 张牌,判断是不是顺子,即这 5 张牌是不是连续的。 2-10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大小王可以看成任意的 数字。 8 | ``` 9 | 10 | >解题思路:我们可以把5张牌看成是由5个数字组成的俄数组。大小王是特殊的数字,我们可以把它们都定义为0,这样就可以和其他的牌区分开来。 11 | 12 | 13 | >首先把数组排序,再统计数组中0的个数,最后统计排序之后的数组中相邻数字之间的空缺总数。如果空缺的总数小于或者等于0的个数,那么这个数组就是连续的,反之则不连续。如果数组中的非0数字重复出现,则该数组是不连续的。换成扑克牌的描述方式就是如果一幅牌里含有对子,则不可能是顺子。 14 | 15 | 详细代码: 16 | 17 | ``` 18 | import java.util.Arrays; 19 | 20 | public class Solution { 21 | 22 | public boolean isContinuous(int[] number){ 23 | if(number == null){ 24 | return false; 25 | } 26 | Arrays.sort(number); 27 | int numberZero = 0; 28 | int numberGap = 0; 29 | //计算数组中0的个数 30 | for(int i = 0;i < number.length&&number[i] == 0; i++){ 31 | numberZero++; 32 | } 33 | //统计数组中的间隔数目 34 | int small = numberZero; 35 | int big = small + 1; 36 | while(bignumberZero)?false:true; 46 | 47 | } 48 | } 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/面试题45:圆圈中最后剩下的数字.md: -------------------------------------------------------------------------------- 1 | #面试题45 圆圈中最后剩下的数字 2 | --- 3 | 4 | 题目 5 | 6 | >0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求这个圆圈里剩下的最后一个数字。 7 | 8 | 9 | 解法: 10 | 11 | >可以创建一个总共有n个结点的环形链表,然后每次在这个链表中删除第m个结点。我们发现使用环形链表里重复遍历很多遍。重复遍历当然对时间效率有负面的影响。这种方法每删除一个数字需要m步运算,总共有n个数字,因此总的时间复杂度为O(mn)。同时这种思路还需要一个辅助的链表来模拟圆圈,其空间复杂度O(n)。接下来我们试着找到每次被删除的数字有哪些规律,希望能够找到更加高效的算法。 12 | 13 | 14 | >首先我们定义一个关于n和m的方程f(n,m),表示每次在n个数字0,1,。。。n-1中每次删除第m个数字最后剩下的数字。 15 | 16 | >在这n个数字中,第一个被删除的数字是(m-1)%n.为了简单起见,我们把(m-1)%n记为k,那么删除k之后剩下的n-1个数字为0,1,。。。。k-1,k+1,.....n-1。并且下一次删除从数字k+1,......n-1,0,1,....k-1。该序列最后剩下的数字也应该是关于n和m的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此该函数不同于前面的函数,即为f'(n-1,m)。最初序列最后剩下的数字f(n,m)一定是删除一个数字之后的序列最后剩下的数字,即f(n,m)=f'(n-1,m). 17 | 18 | >接下来我么把剩下的这n-1个数字的序列k+1,....n-1,0,1,,,,,,,k-1做一个映射,映射的结果是形成一个从0到n-2的序列 19 | 20 | ``` 21 | k+1 ------> 0 22 | k+2 ---------> 1 23 | 。。。。 24 | n-1 ----- > n-k-2 25 | 0 -------> n-k-1 26 | 1 ---------> n-k 27 | ..... 28 | k-1 ---------> n-k 29 | ``` 30 | 31 | >我们把映射定义为p,则p(x) = (x-k-1)%n。它表示如果映射前的数字是x,那么映射后的数字是(x-k-1)%n.该映射的逆映射是p-1(x)= (x+k+1)%n. 32 | 33 | >由于映射之后的序列和最初的序列具有同样的形式,即都是从0开始的连续序列,因此仍然可以用函数f来表示,记为f(n-1,m).根据我们的映射规则,映射之前的序列中最后剩下的数字f'(n-1,m) = p-1[(n-1,m)] = [f(n-1,m)+k+1]%n ,把k= (m-1)%n代入f(n,m) = f'(n-1,m) =[f(n-1,m)+m]%n. 34 | 35 | >经过上面的复杂的分析,我们终于找到一个递归的公示。要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列和最后剩下的数字,并以此类推。当n-1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0.我们把这种关系表示为: 36 | 37 | ![这里写图片描述](http://img.blog.csdn.net/20160705171823279) 38 | 39 | 代码如下: 40 | 41 | ``` 42 | public static int lastRemaining(int n, int m){ 43 | if(n < 1 || m < 1){ 44 | return -1; 45 | } 46 | int last = 0; 47 | for(int i = 2; i <= n; i++){ 48 | last = (last + m) % i; 49 | } 50 | return last; 51 | } 52 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/剑指Offer/面试题6:重建二叉树.md: -------------------------------------------------------------------------------- 1 | #面试题6:重建二叉树 2 | --- 3 | 4 | 题目: 5 | 6 | ``` 7 | 输入二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设前序遍历和中序遍历结果中都不包含重复的数字,例如输入的前序遍历序列 {1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}重建出如图所示的二叉 树。 8 | ``` 9 | 10 | 解题思路: 11 | 12 | ``` 13 | 前序遍历第一个结点是父结点,中序遍历如果遍历到父结点,那么父结点前面的结点是左子树的结点,后边的结点的右子树的结点,这样我们可以找到左、右子树的前序遍历和中序遍历,我们可以用同样的方法去构建左右子树,可以用递归完成。 14 | ``` 15 | 16 | 17 | 代码: 18 | 19 | 20 | ``` 21 | public class BinaryTreeNode { 22 | 23 | public static int value; 24 | public BinaryTreeNode leftNode; 25 | public BinaryTreeNode rightNode; 26 | } 27 | 28 | ``` 29 | 30 | ``` 31 | public class Solution { 32 | 33 | public static BinaryTreeNode constructCore(int[] preorder, int[] inorder) throws Exception { 34 | if (preorder == null || inorder == null) { 35 | return null; 36 | } 37 | if (preorder.length != inorder.length) { 38 | throw new Exception("长度不一样,非法的输入"); 39 | } 40 | BinaryTreeNode root = new BinaryTreeNode(); 41 | for (int i = 0; i < inorder.length; i++) { 42 | if (inorder[i] == preorder[0]) { 43 | root.value = inorder[i]; 44 | System.out.println(root.value); 45 | root.leftNode = constructCore(Arrays.copyOfRange(preorder, 1, i + 1), 46 | Arrays.copyOfRange(inorder, 0, i)); 47 | root.rightNode = constructCore(Arrays.copyOfRange(preorder, i + 1, preorder.length), 48 | Arrays.copyOfRange(inorder, i + 1, inorder.length)); 49 | } 50 | } 51 | return root; 52 | 53 | } 54 | } 55 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/程序员代码面试指南(左程云)/1.设计一个有getMin功能的栈.md: -------------------------------------------------------------------------------- 1 | #设计一个有getMin功能的栈 2 | --- 3 | 4 | 实现一个特殊的栈,在实现栈的基本功能的基础上,在实现返回栈中最小元素的操作。 5 | 6 | 要求: 7 | 8 | 1. pop、push、getMin操作的时间复杂度都是O(1) 9 | 2. 设计的栈类型可以使用现成的栈结构 10 | 11 | 解题: 12 | 13 | 14 | ``` 15 | package chapter01_stackandqueue; 16 | 17 | import java.util.Stack; 18 | 19 | /** 20 | * 21 | * 实现一个特殊的栈,在实现栈的基本功能的基础上,在实现返回栈中最小元素的操作。 要求: 1. pop、push、getMin操作的时间复杂度都是O(1) 22 | * 2. 设计的栈类型可以使用现成的栈结构 23 | * 24 | * @author dream 25 | * 26 | */ 27 | public class Problem01_GetMinStack { 28 | 29 | public static class MyStack1 { 30 | 31 | /** 32 | * 两个栈,其中stacMin负责将最小值放在栈顶,stackData通过获取stackMin的peek()函数来获取到栈中的最小值 33 | */ 34 | private Stack stackData; 35 | private Stack stackMin; 36 | 37 | /** 38 | * 在构造函数里面初始化两个栈 39 | */ 40 | public MyStack1() { 41 | stackData = new Stack(); 42 | stackMin = new Stack(); 43 | } 44 | 45 | /** 46 | * 该函数是stackData弹出栈顶数据,如果弹出的数据恰好等于stackMin的数据,那么stackMin也弹出 47 | * @return 48 | */ 49 | public Integer pop() { 50 | Integer num = (Integer) stackData.pop(); 51 | if (num == getmin()) { 52 | return (Integer) stackMin.pop(); 53 | } 54 | return null; 55 | } 56 | 57 | /** 58 | * 该函数是先判断stackMin是否为空,如果为空,就push新的数据,如果这个数小于stackMin中的栈顶元素,那么stackMin需要push新的数,不管怎么样 59 | * stackData都需要push新的数据 60 | * @param value 61 | */ 62 | public void push(Integer value) { 63 | if (stackMin.isEmpty()) { 64 | stackMin.push(value); 65 | } 66 | 67 | else if (value < getmin()) { 68 | stackMin.push(value); 69 | } 70 | stackData.push(value); 71 | } 72 | 73 | /** 74 | * 该函数是当stackMin为空的话第一次也得push到stackMin的栈中,返回stackMin的栈顶元素 75 | * @return 76 | */ 77 | public Integer getmin() { 78 | if (stackMin == null) { 79 | throw new RuntimeException("stackMin is empty"); 80 | } 81 | return (Integer) stackMin.peek(); 82 | 83 | } 84 | } 85 | 86 | public static void main(String[] args) throws Exception { 87 | /** 88 | * 要注意要将MyStack1声明成静态的,静态内部类不持有外部类的引用 89 | */ 90 | MyStack1 stack1 = new MyStack1(); 91 | stack1.push(3); 92 | System.out.println(stack1.getmin()); 93 | stack1.push(4); 94 | System.out.println(stack1.getmin()); 95 | stack1.push(1); 96 | System.out.println(stack1.getmin()); 97 | System.out.println(stack1.pop()); 98 | System.out.println(stack1.getmin()); 99 | 100 | System.out.println("============="); 101 | } 102 | 103 | } 104 | 105 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/程序员代码面试指南(左程云)/2.由两个栈组成的队列.md: -------------------------------------------------------------------------------- 1 | #2.由两个栈组成的队列 2 | --- 3 | 4 | 题目: 5 | 6 | ``` 7 | 编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)。 8 | ``` 9 | 10 | 解题: 11 | 12 | ``` 13 | /** 14 | * 15 | * 编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)。 16 | * 17 | * @author dream 18 | * 19 | */ 20 | public class Problem02_TwoStacksImplementQueue { 21 | 22 | public static class myQueue{ 23 | 24 | Stack stack1; 25 | Stack stack2; 26 | 27 | public myQueue() { 28 | stack1 = new Stack(); 29 | stack2 = new Stack(); 30 | } 31 | /** 32 | * add只负责往stack1里面添加数据 33 | * @param newNum 34 | */ 35 | public void add(Integer newNum){ 36 | stack1.push(newNum); 37 | } 38 | 39 | /** 40 | * 这里要注意两点: 41 | * 1.stack1要一次性压入stack2 42 | * 2.stack2不为空,stack1绝不能向stack2压入数据 43 | * @return 44 | */ 45 | public Integer poll(){ 46 | if(stack1.isEmpty() && stack2.isEmpty()){ 47 | throw new RuntimeException("Queue is Empty"); 48 | }else if(stack2.isEmpty()){ 49 | while (!stack1.isEmpty()) { 50 | stack2.push(stack1.pop()); 51 | } 52 | } 53 | return stack2.pop(); 54 | } 55 | 56 | public Integer peek(){ 57 | if(stack1.isEmpty() && stack2.isEmpty()){ 58 | throw new RuntimeException("Queue is Empty"); 59 | }else if(stack2.isEmpty()){ 60 | while (!stack1.isEmpty()) { 61 | stack2.push(stack1.pop()); 62 | } 63 | } 64 | return stack2.peek(); 65 | } 66 | } 67 | 68 | public static void main(String[] args) { 69 | myQueue mQueue = new myQueue(); 70 | mQueue.add(1); 71 | mQueue.add(2); 72 | mQueue.add(3); 73 | System.out.println(mQueue.peek()); 74 | System.out.println(mQueue.poll()); 75 | System.out.println(mQueue.peek()); 76 | System.out.println(mQueue.poll()); 77 | System.out.println(mQueue.peek()); 78 | System.out.println(mQueue.poll()); 79 | 80 | } 81 | } 82 | 83 | ``` -------------------------------------------------------------------------------- /Part3/Algorithm/程序员代码面试指南(左程云)/3.如何仅用递归函数和栈操作逆序一个栈.md: -------------------------------------------------------------------------------- 1 | #3.如何仅用递归函数和栈操作逆序一个栈 2 | --- 3 | 4 | 题目: 5 | 6 | ``` 7 | 一个栈一次压入了1、2、3、4、5,那么从栈顶到栈底分别为5、4、3、2、1.将这个栈转置后,从栈顶到栈底为1、2、3、4、5,也就是实现栈中元素的逆序,但是只能用递归函数来实现,不能用其他数据结构。 8 | ``` 9 | 10 | 解题: 11 | 12 | ``` 13 | /** 14 | * 一个栈一次压入了1、2、3、4、5,那么从栈顶到栈底分别为5、4、3、2、1.将这个栈转置后, 15 | * 从栈顶到栈底为1、2、3、4、5, 16 | * 也就是实现栈中元素的逆序,但是只能用递归函数来实现,不能用其他数据结构。 17 | * @author dream 18 | * 19 | */ 20 | public class Problem03_ReverseStackUsingRecursive { 21 | 22 | public static void reverse(Stack stack) { 23 | if (stack.isEmpty()) { 24 | return; 25 | } 26 | int i = getAndRemoveLastElement(stack); 27 | reverse(stack); 28 | stack.push(i); 29 | } 30 | 31 | /** 32 | * 这个函数就是删除栈底元素并返回这个元素 33 | * @param stack 34 | * @return 35 | */ 36 | public static int getAndRemoveLastElement(Stack stack) { 37 | int result = stack.pop(); 38 | if (stack.isEmpty()) { 39 | return result; 40 | } else { 41 | int last = getAndRemoveLastElement(stack); 42 | stack.push(result); 43 | return last; 44 | } 45 | } 46 | 47 | public static void main(String[] args) { 48 | Stack test = new Stack(); 49 | test.push(1); 50 | test.push(2); 51 | test.push(3); 52 | test.push(4); 53 | test.push(5); 54 | reverse(test); 55 | while (!test.isEmpty()) { 56 | System.out.println(test.pop()); 57 | } 58 | 59 | } 60 | 61 | } 62 | 63 | ``` -------------------------------------------------------------------------------- /Part3/DataStructure/数据结构(Java).md: -------------------------------------------------------------------------------- 1 | #数据结构和算法: 2 | --- 3 | (推荐书籍:剑指offer、编程之美、Cracking、程序员代码面试指南,特别是这四本书上的重复题) 4 | 5 | **数组与链表** 6 | 7 | ####数组: 8 | 9 | 创建 10 | 11 | ``` 12 | int c[] = {2,3,6,10,99}; 13 | int[] d = new int[10]; 14 | ``` 15 | 16 | 17 | 18 | ``` 19 | /** 20 | * 数组检索 21 | * @param args 22 | */ 23 | public static void main(String[] args) { 24 | String name[]; 25 | 26 | name = new String[5]; 27 | name[0] = "egg"; 28 | name[1] = "erqing"; 29 | name[2] = "baby"; 30 | 31 | for(int i = 0; i < name.length; i++){ 32 | System.out.println(name[i]); 33 | } 34 | } 35 | ``` 36 | ``` 37 | /** 38 | * 插入 39 | * 40 | * @param old 41 | * @param value 42 | * @param index 43 | * @return 44 | */ 45 | public static int[] insert(int[] old, int value, int index) { 46 | for (int k = old.length - 1; k > index; k--) 47 | old[k] = old[k - 1]; 48 | old[index] = value; 49 | return old; 50 | } 51 | 52 | /** 53 | * 遍历 54 | * 55 | * @param data 56 | */ 57 | public static void traverse(int data[]) { 58 | for (int j = 0; j < data.length; j++) { 59 | System.out.println(data[j] + " "); 60 | } 61 | } 62 | 63 | /** 64 | * 删除 65 | * 66 | * @param old 67 | * @param index 68 | * @return 69 | */ 70 | public static int[] delete(int[] old, int index) { 71 | for (int h = index; h < old.length - 1; h++) { 72 | old[h] = old[h + 1]; 73 | } 74 | old[old.length - 1] = 0; 75 | return old; 76 | } 77 | ``` 78 | 79 | Tips:数组中删除和增加元素的原理:增加元素,需要将index后面的依次往后移动,然后将值插入index位置,删除则是将后面的值一次向前移动。 80 | 81 | 数组表示相同类型的一类数据的集合,下标从0开始。 82 | 83 | 84 | ####单链表: 85 | 86 | ![](http://img.my.csdn.net/uploads/201304/13/1365855052_1221.jpg) 87 | 88 | 89 | 90 | **队列和栈,出栈与入栈。** 91 | 92 | **链表的删除、插入、反向。** 93 | 94 | **字符串操作。** 95 | 96 | **Hash表的hash函数,冲突解决方法有哪些。** 97 | 98 | **各种排序:冒泡、选择、插入、希尔、归并、快排、堆排、桶排、基数的原理、平均时间复杂度、最坏时间复杂度、空间复杂度、是否稳定。** 99 | 100 | **快排的partition函数与归并的Merge函数。** 101 | 102 | **对冒泡与快排的改进。** 103 | 104 | **二分查找,与变种二分查找。** 105 | 106 | **二叉树、B+树、AVL树、红黑树、哈夫曼树。** 107 | 108 | **二叉树的前中后续遍历:递归与非递归写法,层序遍历算法。** 109 | 110 | 二叉树的前序遍历: 111 | 112 | 113 | 114 | **图的BFS与DFS算法,最小生成树prim算法与最短路径Dijkstra算法。** 115 | 116 | **KMP算法。** 117 | 118 | **排列组合问题。** 119 | 120 | **动态规划、贪心算法、分治算法。(一般不会问到)** 121 | 122 | **大数据处理:类似10亿条数据找出最大的1000个数.........等等** -------------------------------------------------------------------------------- /Part3/DataStructure/数组.md: -------------------------------------------------------------------------------- 1 | #数组 2 | --- 3 | 4 | ``` 5 | /** 6 | * 普通数组的Java代码 7 | * @author dream 8 | * 9 | */ 10 | public class GeneralArray { 11 | 12 | private int[] a; 13 | private int size; //数组的大小 14 | private int nElem; //数组中有多少项 15 | 16 | public GeneralArray(int max){ 17 | this.a = new int[max]; 18 | this.size = max; 19 | this.nElem = 0; 20 | } 21 | 22 | public boolean find(int searchNum){ //查找某个值 23 | int j; 24 | for(j = 0; j < nElem; j++){ 25 | if(a[j] == searchNum){ 26 | break; 27 | } 28 | } 29 | if(j == nElem){ 30 | return false; 31 | }else { 32 | return true; 33 | } 34 | } 35 | 36 | 37 | public boolean insert(int value){ //插入某个值 38 | if(nElem == size){ 39 | System.out.println("数组已满"); 40 | return false; 41 | } 42 | a[nElem] = value; 43 | nElem++; 44 | return true; 45 | } 46 | 47 | public boolean delete(int value){ //删除某个值 48 | int j; 49 | for(j = 0; j < nElem; j++){ 50 | if(a[j] == value){ 51 | break; 52 | } 53 | } 54 | if(j == nElem){ 55 | return false; 56 | } 57 | if(nElem == size){ 58 | for(int k = j; k < nElem - 1; k++){ 59 | a[k] = a[k+1]; 60 | } 61 | }else { 62 | for(int k = j; k < nElem; k++){ 63 | a[k] = a[k+1]; 64 | } 65 | } 66 | nElem--; 67 | return true; 68 | } 69 | 70 | public void display(){ //打印整个数组 71 | for(int i = 0; i < nElem; i++){ 72 | System.out.println(a[i] + " "); 73 | } 74 | System.out.println(""); 75 | } 76 | } 77 | 78 | ``` 79 | 80 | ``` 81 | /** 82 | * 有序数组的Java代码 83 | * @author dream 84 | * 85 | */ 86 | 87 | /** 88 | * 对于数组这种数据结构, 89 | * 线性查找的话,时间复杂度为O(N), 90 | * 二分查找的话时间为O(longN), 91 | * 无序数组插入的时间复杂度为O(1), 92 | * 有序数组插入的时间复杂度为O(N), 93 | * 删除操作的时间复杂度均为O(N)。 94 | * @author dream 95 | * 96 | */ 97 | public class OrderedArray { 98 | 99 | private long[] a; 100 | private int size; //数组的大小 101 | private int nElem; //数组中有多少项 102 | 103 | public OrderedArray(int max){ //初始化数组 104 | this.a = new long[max]; 105 | this.size = max; 106 | this.nElem = 0; 107 | } 108 | 109 | public int size(){ //返回数组实际有多少值 110 | return this.nElem; 111 | } 112 | 113 | /** 114 | * 二分查找 115 | * @param searchNum 116 | * @return 117 | */ 118 | public int find(long searchNum){ 119 | int lower = 0; 120 | int upper = nElem - 1; 121 | int curr; 122 | while (true) { 123 | curr = (lower + upper) / 2; 124 | if(a[curr] == searchNum){ 125 | return curr; 126 | }else if(lower > upper){ 127 | return -1; 128 | }else { 129 | if(a[curr] < searchNum){ 130 | lower = curr + 1; 131 | }else { 132 | upper = curr - 1; 133 | } 134 | } 135 | } 136 | } 137 | 138 | 139 | public boolean insert(long value){ //插入某个值 140 | if(nElem == size){ 141 | System.out.println("数组已满!"); 142 | return false; 143 | } 144 | int j; 145 | for(j = 0; j < nElem; j++){ 146 | if(a[j] > value){ 147 | break; 148 | } 149 | } 150 | 151 | for(int k = nElem; k > j; k++){ 152 | a[k] = a[k-1]; 153 | } 154 | a[j] = value; 155 | nElem++; 156 | return true; 157 | } 158 | 159 | 160 | 161 | public boolean delete(long value){ //删除某个值 162 | int j = find(value); 163 | if(j == -1){ 164 | System.out.println("没有该元素!"); 165 | return false; 166 | } 167 | 168 | if(nElem == size){ 169 | for(int k = j; k < nElem - 1; k++){ 170 | a[k] = a[k+1]; 171 | } 172 | a[nElem-1] = 0; 173 | }else { 174 | for(int k = j; k < nElem; k++){ 175 | a[k] = a[k+1]; 176 | } 177 | } 178 | nElem--; 179 | return true; 180 | } 181 | 182 | 183 | public void display(){ //打印整个数组 184 | for(int i = 0; i < nElem; i++){ 185 | System.out.println(a[i] + " "); 186 | } 187 | System.out.println(""); 188 | } 189 | } 190 | 191 | ``` -------------------------------------------------------------------------------- /Part3/DataStructure/栈和队列.md: -------------------------------------------------------------------------------- 1 | #栈和队列 2 | --- 3 | ##栈 4 | --- 5 | 6 | 栈只允许访问一个数据项:即最后插入的数据。溢出这个数据才能访问倒数第二个插入的数据项。它是一种"后进先出"的数据结构。 7 | 8 | 栈最基本的操作是出栈(Pop)、入栈(Push),还有其他扩展操作,如查看栈顶元素,判断栈是否为空、是否已满,读取栈的大小等。 9 | 10 | ``` 11 | /** 12 | * 栈是先进后出 13 | * 只能访问栈顶的数据 14 | * @author dream 15 | * 16 | */ 17 | 18 | /** 19 | * 基于数组来实现栈的基本操作 20 | * 数据项入栈和出栈的时间复杂度均为O(1) 21 | * @author dream 22 | * 23 | */ 24 | public class ArrayStack { 25 | 26 | private long[] a; 27 | private int size; //栈数组的大小 28 | private int top; //栈顶 29 | 30 | public ArrayStack(int maxSize){ 31 | this.size = maxSize; 32 | this.a = new long[size]; 33 | this.top = -1; //表示空栈 34 | } 35 | 36 | public void push(long value){ //入栈 37 | if(isFull()){ 38 | System.out.println("栈已满!"); 39 | return; 40 | } 41 | a[++top] = value; 42 | } 43 | 44 | public long peek(){ //返回栈顶内容,但不删除 45 | if(isEmpty()){ 46 | System.out.println("栈中没有数据"); 47 | return 0; 48 | } 49 | return a[top]; 50 | } 51 | 52 | 53 | public long pop(){ //弹出栈顶内容 54 | if(isEmpty()){ 55 | System.out.println("栈中没有数据!"); 56 | return 0; 57 | } 58 | return a[top--]; 59 | } 60 | 61 | public int size(){ 62 | return top + 1; 63 | } 64 | 65 | /** 66 | * 判断是否满了 67 | * @return 68 | */ 69 | public boolean isFull(){ 70 | return (top == size - 1); 71 | } 72 | 73 | /** 74 | * 是否为空 75 | * @return 76 | */ 77 | public boolean isEmpty(){ 78 | return (top == -1); 79 | } 80 | 81 | 82 | public void display(){ 83 | for (int i = top; i >= 0; i--) { 84 | System.out.println(a[i] + " "); 85 | } 86 | System.out.println(""); 87 | } 88 | 89 | } 90 | 91 | ``` 92 | 93 | ##队列 94 | --- 95 | 96 | 依然使用数组作为底层容器来实现一个队列的封装 97 | 98 | ``` 99 | /** 100 | * 队列也可以用数组来实现,不过这里有个问题,当数组下标满了后就不能再添加了, 101 | * 但是数组前面由于已经删除队列头的数据了,导致空。所以队列我们可以用循环数组来实现, 102 | * @author dream 103 | * 104 | */ 105 | public class RoundQueue { 106 | 107 | private long[] a; 108 | private int size; //数组大小 109 | private int nItems; //实际存储数量 110 | private int front; //头 111 | private int rear; //尾 112 | 113 | public RoundQueue(int maxSize){ 114 | this.size = maxSize; 115 | a = new long[size]; 116 | front = 0; 117 | rear = -1; 118 | nItems = 0; 119 | } 120 | 121 | public void insert(long value){ 122 | if(isFull()){ 123 | System.out.println("队列已满"); 124 | return; 125 | } 126 | rear = ++rear % size; 127 | a[rear] = value; //尾指针满了就循环到0处,这句相当于下面注释内容 128 | nItems++; 129 | } 130 | 131 | public long remove(){ 132 | if(isEmpty()){ 133 | System.out.println("队列为空!"); 134 | return 0; 135 | } 136 | nItems--; 137 | front = front % size; 138 | return a[front++]; 139 | } 140 | 141 | public void display(){ 142 | if(isEmpty()){ 143 | System.out.println("队列为空!"); 144 | return; 145 | } 146 | int item = front; 147 | for(int i = 0;i < nItems; i++){ 148 | System.out.println(a[item++ % size] + " "); 149 | } 150 | System.out.println(""); 151 | } 152 | 153 | public long peek(){ 154 | if(isEmpty()){ 155 | System.out.println("队列为空!"); 156 | return 0; 157 | } 158 | return a[front]; 159 | } 160 | 161 | public boolean isFull(){ 162 | return (nItems == size); 163 | } 164 | 165 | public boolean isEmpty(){ 166 | return (nItems == 0); 167 | } 168 | 169 | public int size(){ 170 | return nItems; 171 | } 172 | 173 | 174 | } 175 | 176 | ``` 177 | 178 | 和栈一样,队列中插入数据项和删除数据项的时间复杂度均为O(1) 179 | 180 | 还有个优先级队列,优先级队列是比栈和队列更专用的数据结构。优先级队列与上面普通的队列相比,主要区别在于队列中的元素是有序的,关键字最小(或者最大)的数据项总在队头。数据项插入的时候会按照顺序插入到合适的位置以确保队列的顺序。优先级队列的内部实现可以用数组或者一种特别的树——堆来实现。这里用数组实现优先级队列。 181 | 182 | ``` 183 | 184 | public class PriorityQueue { 185 | 186 | private long[] a; 187 | private int size; 188 | private int nItems; //元素个数 189 | 190 | public PriorityQueue(int maxSize){ 191 | size = maxSize; 192 | nItems = 0; 193 | a = new long[size]; 194 | } 195 | 196 | public void insert(long value){ 197 | if(isFull()){ 198 | System.out.println("队列已满!"); 199 | return; 200 | } 201 | int j; 202 | if(nItems == 0){ //空队列直接添加 203 | a[nItems++] = value; 204 | }else { 205 | //将数组中的数字依照下标按照从大到小排列 206 | for(j=nItems-1; j>=0; j--){ 207 | if(value > a[j]){ 208 | a[j+1] = a[j]; 209 | } 210 | else { 211 | break; 212 | } 213 | } 214 | a[j+1] = value; 215 | nItems++; 216 | } 217 | } 218 | 219 | public long remove(){ 220 | if(isFull()){ 221 | System.out.println("队列为空!"); 222 | return 0; 223 | } 224 | return a[--nItems]; 225 | } 226 | 227 | public long peekMin(){ 228 | return a[nItems - 1]; 229 | } 230 | 231 | public boolean isFull(){ 232 | return (nItems == size); 233 | } 234 | 235 | public boolean isEmpty(){ 236 | return (nItems == 0); 237 | } 238 | 239 | public int size(){ 240 | return nItems; 241 | } 242 | 243 | public void display(){ 244 | for(int i = nItems - 1;i >= 0; i--){ 245 | System.out.println(a[i] + " "); 246 | } 247 | System.out.println(" "); 248 | } 249 | } 250 | 251 | ``` 252 | 253 | 优先级队列中,插入操作需要O(N)的时间,而删除操作则需要O(1)的时间。 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /Part3/DataStructure/递归和非递归方式实现二叉树先、中、后序遍历.md: -------------------------------------------------------------------------------- 1 | ###递归和非递归方式实现二叉树先、中、后序遍历 2 | 3 | 先序遍历顺序为根、左、右,中序遍历顺序为左、根、右,后序遍历是左、右、根。 4 | 5 | 递归实现: 6 | 7 | 8 | ``` 9 | public class Node { 10 | 11 | public int value; 12 | public Node left; 13 | public Node right; 14 | 15 | public Node(int data){ 16 | this.value = data; 17 | } 18 | } 19 | ``` 20 | 21 | ``` 22 | /** 23 | * 前序遍历 24 | * @param head 25 | */ 26 | public void preOrderRecur(Node head){ 27 | if(head == null){ 28 | return; 29 | } 30 | System.out.println(head.value + " "); 31 | preOrderRecur(head.left); 32 | preOrderRecur(head.right); 33 | } 34 | 35 | /** 36 | * 中序遍历 37 | * @param head 38 | */ 39 | public void inOderRecur(Node head){ 40 | if(head == null){ 41 | return; 42 | } 43 | inOderRecur(head.left); 44 | System.out.println(head.value + " "); 45 | inOderRecur(head.right); 46 | } 47 | 48 | /** 49 | * 后序遍历 50 | * @param head 51 | */ 52 | public void posOrderRecur(Node head){ 53 | if(head == null){ 54 | return; 55 | } 56 | posOrderRecur(head.left); 57 | posOrderRecur(head.right); 58 | System.out.println(head.value + ""); 59 | } 60 | ``` 61 | 62 | 63 | 64 | 非递归遍历 65 | 66 | -------------------------------------------------------------------------------- /Part4/Network/Http协议.md: -------------------------------------------------------------------------------- 1 | #Http协议 2 | --- 3 | * 默认端口:80 4 | 5 | ##Http协议的主要特点 6 | --- 7 | 1. 支持客户/服务器模式 8 | 2. 简单快速:客户向服务端请求服务时,只需传送请求方式和路径。 9 | 3. 灵活:允许传输任意类型的数据对象。由Content-Type加以标记。 10 | 4. 无连接:每次响应一个请求,响应完成以后就断开连接。 11 | 5. 无状态:服务器不保存浏览器的任何信息。每次提交的请求之间没有关联。 12 | 13 | ###非持续性和持续性 14 | --- 15 | * HTTP1.0默认非持续性;HTTP1.1默认持续性 16 | 17 | ####持续性 18 | 浏览器和服务器建立TCP连接后,可以请求多个对象 19 | ####非持续性 20 | 浏览器和服务器建立TCP连接后,只能请求一个对象 21 | 22 | ###非流水线和流水线 23 | --- 24 | 类似于组成里面的流水操作 25 | 26 | * 流水线:不必等到收到服务器的回应就发送下一个报文。 27 | * 非流水线:发出一个报文,等到响应,再发下一个报文。类似TCP。 28 | 29 | ####POST和GET的区别 30 | 31 | | Post一般用于更新或者添加资源信息 | Get一般用于查询操作,而且应该是安全和幂等的 | 32 | | ------------- |:-------------:| 33 | | Post更加安全 | Get会把请求的信息放到URL的后面 | 34 | | Post传输量一般无大小限制 | Get不能大于2KB | 35 | | Post执行效率低 | Get执行效率略高 | 36 | 37 | 38 | ####为什么POST效率低,Get效率高 39 | --- 40 | * Get将参数拼成URL,放到header消息头里传递 41 | * Post直接以键值对的形式放到消息体中传递。 42 | * 但两者的效率差距很小很小 43 | 44 | 45 | ##Https 46 | --- 47 | * 端口号是443 48 | * 是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议。 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Part4/Network/Socket.md: -------------------------------------------------------------------------------- 1 | #Socket 2 | --- 3 | ###使用TCP 4 | --- 5 | 客户端 6 | 7 | ``` 8 | Socket socket = new Socket("ip", 端口); 9 | 10 | InputStream is = socket.getInputStream(); 11 | DataInputStream dis = new DataInputStream(is); 12 | 13 | OutputStream os = socket.getOutputStream(); 14 | DataInputStream dos = new DataOutputStream(os); 15 | ``` 16 | 17 | 服务器端 18 | 19 | ``` 20 | ServerSocket serverSocket = new ServerSocket(端口); 21 | Socket socket = serverSocket.accept(); 22 | //获取流的方式与客户端一样 23 | ``` 24 | 25 | 读取输入流 26 | 27 | ``` 28 | byte[] buffer = new byte[1024]; 29 | do{ 30 | int count = is.read(buffer); 31 | if(count <= 0){ break; } 32 | else{ 33 | // 对buffer保存或者做些其他操作 34 | } 35 | } 36 | while(true); 37 | 38 | 39 | ``` 40 | 41 | 42 | 使用UDP 43 | --- 44 | 客户端和服务器端一样的 45 | 46 | ``` 47 | DatagramSocket socket = new DatagramSocket(端口); 48 | InetAddress serverAddress = InetAddress.getbyName("ip"); 49 | //发送 50 | DatagramPackage packet = new DatagramPacket(buffer, length, host, port); 51 | socket.send(packet); 52 | //接收 53 | byte[] buf = new byte[1024]; 54 | DatagramPacket packet = new DatagramPacket(buf, 1024); 55 | Socket.receive(packet); 56 | ``` -------------------------------------------------------------------------------- /Part4/Network/TCP与UDP.md: -------------------------------------------------------------------------------- 1 | #TCP与UDP 2 | --- 3 | 4 | **面向报文的传输方式**是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。 5 | **面向字节流**的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。 6 | 7 | TCP协议 8 | ----- 9 | 10 | 11 | ---------- 12 | 13 | - Transmission Control Protocol,传输控制协议 14 | - 面向连接的协议 15 | - 需要三次握手建立连接 16 | - 需要四次挥手断开连接 17 | - TCP报头最小长度:20字节 18 | 19 | 20 | 三次握手的过程: 21 | -------- 22 | 23 | 24 | ---------- 25 | 26 | 1. 客户端发送:SYN = 1, SEQ = X, 端口号 27 | 2. 服务器回复:SYN = 1, ACK = X + 1, SEQ = Y 28 | 3. 客户端发送:ACK = Y + 1, SEQ = X + 1 29 | 30 | > 确认应答信号ACK = 收到的SEQ + 1。 31 | 连接建立中,同步信号SYN始终为1。连接建立后,同步信号SYN=0。 32 | 33 | 四次挥手过程 34 | ------ 35 | 36 | ---------- 37 | 38 | 39 | 1. A向B提出停止连接请求,FIN = 1 40 | 2. B收到,ACK = 1 41 | 3. B向A提出停止连接请求,FIN = 1 42 | 4. A收到,ACK = 1 43 | 44 | **优点:** 45 | 46 | 47 | ---------- 48 | 49 | - 可靠,稳定 50 | 1、传递数据前,会有三次握手建立连接 51 | 2、传递数据时,有确认、窗口、重传、拥塞控制 52 | 3、传递数据后,会断开连接节省系统资源 53 | 54 | **缺点:** 55 | 56 | ---------- 57 | 58 | - 传输慢,效率低,占用系统资源高 59 | 1、传递数据前,建立连接需要耗时 60 | 2、传递数据时,确认、重传、拥塞等会消耗大量时间以及CPU和内存等硬件资源 61 | 62 | - 易被攻击 63 | 1、因为有确认机制,三次握手等机制,容易被人利用,实现DOS 、DDOS攻击 64 | 65 | **如何保证接收的顺序性:** 66 | 67 | ---------- 68 | TCP协议使用SEQ和ACK机制保证了顺序性 69 | TCP的每个报文都是有序号的。确认应答信号ACK=收到的SEQ+1 70 | 71 | 72 | 73 | 74 | 75 | UDP协议 76 | ----- 77 | 78 | 79 | ---------- 80 | 81 | - User Data Protocol,用户数据包协议 82 | - 面向无连接的协议 83 | - UDP报头只有8字节 84 | 85 | **简介:** 86 | 87 | ---------- 88 | 89 | - 传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快的把它扔到网络上 90 | - 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制 91 | - 在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段 92 | - 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息 93 | - UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小 94 | - 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制 95 | - UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表。 96 | - UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。 97 | 98 | > 使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。 99 | 100 | **优点:** 101 | 102 | 103 | ---------- 104 | 105 | 106 | - 传输速率快 107 | 1、传输数据前,不需要像TCP一样建立连接 108 | 2、传输数据时,没有确认、窗口、重传、拥塞控制等机制 109 | 110 | - 较安全 111 | 1、由于没有了TCP的一些机制,被攻击者利用的漏洞就少了 112 | 113 | **缺点:** 114 | 115 | 116 | ---------- 117 | 118 | 119 | 120 | - 不可靠,不稳定 121 | 1、由于没有了TCP的机制,在数据传输时如果网络不好,很可能丢包 122 | 123 | 124 | **用UDP协议通讯时怎样得知目标机是否获得了数据包** 125 | 126 | 127 | ---------- 128 | 129 | 130 | 仿造TCP的做法,每发一个UDP包,都在里面加一个SEQ序号,接收方收到包后,将SEQ序号回复给发送方。如果发送方在指定时间以内没有收到回应,说明丢包了。 131 | 132 | 133 | 134 | TCP与UDP的区别 135 | ---------- 136 | 137 | 138 | ---------- 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 |
TCP面向有链接的通信服务UDP面向无连接的通信服务
TCP提供可靠的通信传输UDP不可靠,会丢包
TCP保证数据顺序UDP不保证
TCP数据无边界UDP有边界
TCP速度快UDP速度慢
TCP面向字节流UDP面向报文
TCP一对一UDP可以一对一,一对多
TCP报头至少20字节UDP报头8字节
TCP有流量控制,拥塞控制UDP没有
178 | 179 | 180 | 181 | **为什么UDP比TCP快** 182 | 183 | 184 | ---------- 185 | 186 | 187 | 1. TCP需要三次握手 188 | 2. TCP有拥塞控制,控制流量等机制 189 | 190 | 191 | 192 | **为什么TCP比UDP可靠** 193 | 194 | 195 | ---------- 196 | 197 | 198 | 1. TCP是面向有连接的,建立连接之后才发送数据;而UDP则不管对方存不存在都会发送数据。 199 | 2. TCP有确认机制,接收端每收到一个正确包都会回应给发送端。超时或者数据包不完整的话发送端会重传。UDP没有。因此可能丢包。 200 | 201 | 202 | 203 | **什么时候使用TCP** 204 | 205 | 206 | ---------- 207 | 208 | 209 | 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 210 | 在日常生活中,常见使用TCP协议的应用如下: 211 | 浏览器,用的HTTP 212 | FlashFXP,用的FTP 213 | Outlook,用的POP、SMTP 214 | Putty,用的Telnet、SSH 215 | QQ文件传输 216 | 217 | 218 | 219 | **什么时候应该使用UDP:** 220 | 221 | 222 | ---------- 223 | 224 | 225 | 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 226 | 比如,日常生活中,常见使用UDP协议的应用如下: 227 | QQ语音 228 | QQ视频 229 | TFTP 230 | 231 | 232 | 233 | **TCP无边界,UDP有边界** 234 | 235 | 236 | ---------- 237 | **TCP无边界** 238 | 239 | 客户端分多次发送数据给服务器,若服务器的缓冲区够大,那么服务器端会在客户端发送完之后一次性接收过来,所以是无边界的; 240 | 241 | **UDP有边界** 242 | 243 | 客户端每发送一次,服务器端就会接收一次,也就是说发送多少次就会接收多少次,因此是有边界的。 244 | 245 | -------------------------------------------------------------------------------- /Part4/OperatingSystem/Linux系统的IPC.md: -------------------------------------------------------------------------------- 1 | # Linux系统的IPC 2 | 3 | **线程之间不存在通信,因为本来就共享同一片内存** 4 | 5 | 各个线程可以访问进程中的公共变量,资源,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。数据之间的相互制约包括 6 | 1、直接制约关系,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着,这种关系可以称之为同步关系 7 | 2、间接制约关系,即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互斥是一种制约关系更小的同步 8 | 9 | ## 线程间同步 10 | 11 | * 临界区 12 | * 每个线程中访问临界资源的代码,一个线程拿到临界区的所有权后,可以多次重入.只有前一个线程放弃,后一个才可以进来 13 | 14 | * 互斥量 15 | * 互斥量就是简化版的信号量 16 | * 互斥量由于也有线程所有权的概念,故也只能进行线程间的资源互斥访问,不能由于线程同步 17 | * 由于互斥量是内核对象,因此其可以进行进程间通信,同时还具有一个很好的特性,就是在进程间通信时完美的解决了"遗弃"问题 18 | 19 | 20 | * 信号量 21 | * 信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,PV操作 22 | * 信号量也是内核对象,也可以进行进程间通信 23 | 24 | ## 进程间通信 25 | 26 | * 管道 27 | * 半双工 28 | * 只能在具有父子关系的进程间使用 29 | * FIFO的共享内存实现 30 | * 命名管道 31 | * 以linux中的文件的形式存在 32 | * 不要求进程有用父子关系,甚至通过网络也是可以的 33 | * 半双工 34 | * 信号量 35 | * 有up和down操作 36 | * 共享内存实现 37 | 38 | 39 | * 消息队列 40 | * 队列数据结构 41 | * 共享内存实现 42 | * 信号 43 | * 较为复杂的方式,用于通知进程某事件已经发生.例如kill信号 44 | 45 | 46 | * 共享内存 47 | * 将同一个物理内存附属到两个进程的虚拟内存中 48 | * socket 49 | * 可以跨网络通信 -------------------------------------------------------------------------------- /Part4/OperatingSystem/操作系统.md: -------------------------------------------------------------------------------- 1 | #OS 2 | 3 | **进程和线程** 4 | 5 | 关系: 6 | 7 | 一个进程可以创建和撤销另一个线程,同一个进程中的线程可以并发执行。 8 | 9 | **死锁的必要条件,怎么处理死锁。** 10 | 11 | **Window内存管理方式:段存储,页存储,段页存储。** 12 | 13 | **进程的几种状态和转换** 14 | 15 | **什么是虚拟内存。** 16 | 17 | **Linux下的IPC几种通信方式** 18 | 19 | 1. 管道(pipe):管道可用于具有亲缘关系的进程间的通信,是一种半双工的方式,数据只能单向流动,允许一个进程和另一个与它有公共祖先的进程之间进行通信。 20 | 2. 命名管道(named pipe):命名管道克服了管道没有名字的限制,同时除了具有管道的功能外(也是半双工),它还允许无亲缘关系进程间的通信。命令管道在文件系统中有对应的文件名。命令管道通过命令mkfifo或系统调用mkfifo来创建。 21 | 3. 信号(signal):信号是比较复杂的通信方式,用于通知接收进程有某种事件发生了,除了进程间通信外,进程还可以发送信号给进程本身。 22 | 4. 消息队列:消息队列是消息的链接表,包括Posix消息队列和system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程可以读走队列中的消息。消息队列克服了信号承载信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。 23 | 5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的IPC形式。是针对其他通信机制运行效率低而设计的。往往与其他通信机制,如信号量结合使用,来达到进程间的同步及互斥。 24 | 6. 内存映射:内存映射允许任何多个进程间通信每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。 25 | 7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。 26 | 8. 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。 27 | 28 | **逻辑地址、物理地址的区别** 29 | 30 | **进程调度算法** -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Android开发艺术探索》第一章笔记.md: -------------------------------------------------------------------------------- 1 | #《Android开发艺术探索》第一章笔记 2 | --- 3 | 4 | 注:此篇笔记只记录重难点,对于基础和详细内容请自行学习《Android开发艺术探索》。 5 | 6 | 7 | (1) onStart和onResume的区别是onStart可见,还没有出现在前台,无法和用户进行交互。onResume获取到焦点可以和用户交互。 8 | 9 | (2) 新Activity是透明主题时,旧Activity不会走onStop; 10 | 11 | (3)Activity切换时,旧Activity的onPause会先执行,然后才会启动新的Activity; 12 | 13 | (4)Activity在异常情况下被回收时,onSaveInstanceState方法会被回调,回调时机是在onStop之前,当Activity被重新创建的时候,onRestoreInstanceState方法会被回调,时序在onStart之后; 14 | 15 | (5)Activity的LaunchMode 16 | 17 | a. 18 | standard 系统默认。每次启动会重新创建新的实例,谁启动了这个Activity,这个Activity就在谁的栈里。 19 | 20 | b. 21 | singleTop 栈顶复用模式。该Activity的onNewIntent方法会被回调,onCreate和onStart并不会被调用。 22 | 23 | c. 24 | singleTask 栈内复用模式。只要该Activity在一个栈中存在,都不会重新创建,onNewIntent会被回调。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,然后把这个Activity放进去;如果存在,就会创建到已经存在的这个栈中。 25 | 26 | d. 27 | singleInstance。具有此种模式的Activity只能单独存在于一个任务栈。 28 | 29 | (5) 标识Activity任务栈名称的属性:TaskAffinity,默认为应用包名。 30 | 31 | (6) IntentFilter匹配规则。 32 | 33 | a. 34 | action匹配规则:要求intent中的action存在且必须和过滤规则中的其中一个相同 区分大小写; 35 | 36 | b. 37 | category匹配规则:系统会默认加上一个android.intent.category.DEAFAULT,所以intent中可以不存在category,但如果存在就必须匹配其中一个; 38 | 39 | c. 40 | data匹配规则:data由两部分组成,mimeType和URI,要求和action相似。如果没有指定URI,URI但默认值为content和file(schema) -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Android开发艺术探索》第二章笔记.md: -------------------------------------------------------------------------------- 1 | ##《Android开发艺术探索》第二章笔记 2 | --- 3 | 4 | ##IPC 5 | * Inter-Process Communication的缩写。含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。 6 | 7 | ##进程和线程的区别 8 | * 按照操作系统的描述,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。 9 | * 进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。 10 | 11 | ##多进程分为两种 12 | * 第一种情况是一个应用因为某些原因自身需要采用多线程模式来实现。 13 | * 另一种情况是当前应用需要向其他应用获取数据 14 | 15 | ##Android中的多进程模式 16 | 通过给四大组件指定android:process属性,我们可以开启多线程模式 17 | 18 | * 进程名以":"开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程,而进程名不以":"开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。 19 | * Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。 20 | 21 | ##多进程模式的运行机制 22 | * Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在不同的内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。 23 | * 所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。 24 | 25 | 一般来说,使用多进程会造成如下几个方面的问题: 26 | 27 | * 静态成员和单例模式完全失效 28 | * 线程同步机制完全失效 29 | 30 | 31 | 不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象 32 | 33 | * SharedPreference的可靠性下降 34 | 35 | SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这时因为SharedPreferences底层是通过读写XML文件来实现的,并发写显然是可能出问题的,甚至并发读写都有可能发生问题 36 | 37 | * Application会多次创建 38 | 39 | 运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的。同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application的。 40 | 41 | ##IPC基础概念介绍 42 | * Serializable 43 | 44 | 是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。 45 | 46 | ``` 47 | 48 | private static final long serialVersionUID = 8711368828010083044L 49 | 50 | ``` 51 | 通过Serializable方来实现对象的序列化,如下代码: 52 | ``` 53 | 54 | //序列化过程 55 | User user = new User(0, "jake", true); 56 | ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt")); 57 | out.writeObject(user); 58 | out.close(); 59 | 60 | //反序列化过程 61 | ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt")); 62 | User newUser = (User)in.readObject(); 63 | in.close(); 64 | 65 | ``` 66 | 67 | 原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同时才能够正常的被反序列化。serialVersionUID的详细工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他的中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些变换 68 | 69 | 给serialVersionUID制定为1L或者采用Eclipse根据当前类结构去生成的hash值,这两者并没有本质区别。 70 | 71 | * 静态成员变量属于类不属于对象,所以不会参与序列化过程 72 | * 其次用transient关键字标记的成员变量不参与序列化过程 73 | 74 | 75 | ##Parcelable接口 76 | Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent的Binder传递 77 | Parcelable的方法说明: 78 | 79 | | 方法 | 功能 | 标记位 | 80 | | ------------- |:-------------:| -----:| 81 | | createFromParcel(Parcel in) | 从序列化的对象中创建原始对象 | | 82 | | newArray[int size] | 创建指定长度的原始对象数组 | | 83 | | User(Parcel in) | 从序列化的对象中创建原始对象 | | 84 | | write ToParcel(Parcel out, int flags) | 将当前对象写入序列化结构中,其中flags标识有两种值0或1(参见右侧标记位)。为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0 | PARCELABLE_WRITE_RETURN_VALUE | 85 | | describeContents | 返回当前对象的内容描述。如果含有文件描述符,返回1(参见右侧标记位),否则返回0,几乎所有的情况都返回0 | CONTENTS_FILE_DESCRIPTOR | 86 | 87 | 88 | * 系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。 89 | 90 | ##如何选取 91 | Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化需要大量I/O操作。而Parceleble是Android中的序列化方式,因此更适合在Android平台上,缺点是麻烦,但是效率高,这是Android推荐的序列化方式,所以我们要首选Parcelable。Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化之后通过网络传输,但是过程稍显复杂,因此在这两种情况下建议大家使用Serializable。 92 | 93 | 94 | ##Binder 95 | * 继承了IBinder接口 96 | * Binder是一种跨进程通信方式 97 | * 是ServiceManager连接各种Manager(ActivityManager,WindowManager等)和相应ManagerService的桥梁 98 | * 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务器会返回一个包含了服务器端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者是数据,这里的服务包含了普通服务和基于AIDL的服务 99 | 100 | aidl工具根据aidl文件自动生成的java接口的解析:首先,它声明了几个接口方法,同时还声明了几个整型的id用于标识这些方法,id用于标识在transact过程中客户端所请求的到底是哪个方法;接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub内部的代理类Proxy来完成。 101 | 所以,这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。 下面分析其中的方法: 102 | 103 | * asInterface(android.os.IBinder obj):用于将服务器端的Binder对象转化成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端是在同一进程中,那么这个方法返回的是服务端的Stub对象本身,否则返回的是系统封装的Stub.Proxy对象。 104 | * asBinder:返回当前Binder对象 105 | * onTransact:这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。 106 | 这个方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags) 107 | 服务端通过code可以知道客户端请求的目标方法,接着从data中取出所需的参数,然后执行目标方法,执行完毕之后,将结果写入到reply中。如果此方法返回false,说明客户端的请求失败,利用这个特性可以做权限验证(即验证是否有权限调用该服务)。 108 | * Proxy#[Method]:代理类中的接口方法,这些方法运行在客户端,当客户端远程调用此方法时,它的内部实现是:首先创建该方法所需要的参数,然后把方法的参数信息写入到_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。 109 | 110 | ######首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以不管Binder是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。 111 | 112 | ###Binder两种重要的方法 113 | 1. linkToDeath 114 | 2. unlinkToDeath 115 | Binder运行在服务端,如果由于某种服务端异常终止了的话会导致客户端的远程调用失败、所以Binder提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath方法可以给Binder设置一个死亡代理,当Binder死亡的时候客户端就会收到通知,然后就可以重新发起连接从而恢复连接了。 116 | ###如何给Binder设置死亡代理 117 | 1、声明一个DeathRecipient对象、DeathRecipient是一个接口,其内部只有一个方法bindDied,实现这个方法就可以在Binder死亡的时候收到通知了。 118 | 119 | ``` 120 | 121 | private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { 122 | @Override 123 | public void binderDied() { 124 | if (mRemoteBookManager == null) return; 125 | mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); 126 | mRemoteBookManager = null; 127 | // TODO:这里重新绑定远程Service 128 | } 129 | }; 130 | 131 | ``` 132 | 133 | 2、在客户端绑定远程服务成功之后,给binder设置死亡代理 134 | 135 | ``` 136 | 137 | mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); 138 | 139 | ``` 140 | 141 | ##Android的IPC方式 142 | 143 | 1、 使用Bundle 144 | 145 | Bundle实现了Parcelable接口,Activity、Service和Receiver都支持在Intent中传递Bundle数据 146 | 147 | 2、 使用文件共享 148 | 149 | 这种方式简单,适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题,SharedPreferences是一个特例,虽然它也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下、系统对它的读写就变的不可靠,当面对高并发读写访问的时候,有很大几率会丢失,因此,不建议在进程间通信中使用SharedPreferences。 150 | 151 | 3、 使用Messenger 152 | 153 | Messenger是一种轻量级的IPC方案,它的底层实现就是AIDL。Messenger是以串行的方式处理请求的,即服务端只能一个个处理,不存在并发执行的情形。 154 | 155 | 4、 使用AIDL 156 | 157 | 大致流程:首先建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。 158 | 1.AIDL支持的数据类型:基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL; 159 | 2.某些类即使和AIDL文件在同一个包中也要显式import进来; 160 | 3.AIDL中除了基本数据类,其他类型的参数都要标上方向:in、out或者inout; 161 | 4.AIDL接口中支持方法,不支持声明静态变量; 162 | 5.为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。 163 | 6.RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。 164 | 165 | 5、使用ContentProvider 166 | 167 | 1.ContentProvider主要以表格的形式来组织数据,并且可以包含多个表; 168 | 2.ContentProvider还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider; 169 | 3.ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行; 170 | 4.要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者; 171 | 172 | 6、使用Socket 173 | 174 | 套接字,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中TCP和UDP协议。 175 | 176 | * TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传功能,因此具有很高的稳定性 177 | * UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能,在性能上,UDP具有更好的效率,其缺点是不保证数据能够正确传输,尤其是在网络拥塞的情况下。 178 | 179 | ##Binder连接池 180 | * 当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。 181 | Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行,从而避免了重复创建Service的过程。 182 | * 建议在AIDL开发工作中引入BinderPool机制。 183 | 184 | ##选用合适的IPC方式 185 | ![Mou icon](http://hujiaweibujidao.github.io/images/androidart_ipc.png) 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Android开发艺术探索》第八章笔记.md: -------------------------------------------------------------------------------- 1 | ##理解Window和WindowManager 2 | 3 | Window是一个抽象类,它的具体实现是PhoneWindow。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际是View的直接管理者。 4 | 5 | ###8.1 Window和WindowManager 6 | 7 | 为了分析Window的工作机制,先通过代码了解如何使用WindowManager添加一个Window,下面一段代码将一个Button添加到屏幕坐标为(100, 300)的位置上 8 | 9 | ``` 10 | mFloatingButton = new Button(this); 11 | mFloatingButton.setText("test button"); 12 | mLayoutParams = new WindowManager.LayoutParams( 13 | LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, 14 | PixelFormat.TRANSPARENT);//0,0 分别是type和flags参数,在后面分别配置了 15 | mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 16 | | LayoutParams.FLAG_NOT_FOCUSABLE 17 | | LayoutParams.FLAG_SHOW_WHEN_LOCKED; 18 | mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR; 19 | mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; 20 | mLayoutParams.x = 100; 21 | mLayoutParams.y = 300; 22 | mFloatingButton.setOnTouchListener(this); 23 | mWindowManager.addView(mFloatingButton, mLayoutParams); 24 | ``` 25 | Flags参数表示Window的属性,以下列举常用的选项: 26 | 27 | * FLAG_NOT_FOCUSABLE:表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启动FLAG_NOT_TOUCH_MODEL,最终事件会传递给下层的具有焦点的Window 28 | * FLAG_NOT_TOUCH_MODAL:在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件。 29 | * FLAG_SHOW_WHEN_LOCKED:开启此模式可以让显示在锁屏的界面 30 | 31 | 32 | 33 | 34 | Type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。应用类Window对应着一个Activity。子Window不能单独存在,它需要附属在特定的父Window之中,比如常见的一些Dialog就是一个子Window。系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏这些都是系统Window。 35 | 36 | Window是分层的,每个Window都有对应的z-ordered,层级最大的会覆盖在层级小的Window上面,这和HTML中的z-index的概念是完全一致的。在三类Window中,应用Window的层级范围是1~99,子Window的层级范围是1000~1999,系统Window的层级范围是2000~2999,这些层级属性范围对应着WindowManager.LayoutParams的type参数。 37 | 38 | 如果采用TYPE_SYSTEM_ERROR,只需要为type参数指定这个层级即可: 39 | 40 | mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR 41 | 42 | 同时声明权限: 43 | 44 | 45 | 46 | WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。 47 | 48 | ###8.2 Window的内部机制 49 | 50 | Window是一个抽象的概念,并不是实际存在的,它是以View的形式存在,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。 51 | 52 | ####8.2.1 Window的添加过程 53 | 54 | Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl类。WindowManager的实现类对于addView、updateView和removeView方法都是委托给WindowManagerGlobal类。 55 | 56 | WindowManagerGlobal的addView方法分为如下几步: 57 | 58 | 1. 检查参数是否合法,如果是子Window那么还需要调整一些布局参数 59 | 2. 创建ViewRootImpl并将View添加到列表中 60 | 3. 通过ViewRootImpl来更新界面并完成Window的添加过程 61 | 62 | ####8.2.2 Window的删除过程 63 | 64 | 和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的。 65 | 真正删除View的逻辑在dispatchDetachedFromWindow方法的内部实现。dispatchDetachedFromWindow方法主要做四件事: 66 | 67 | 1. 垃圾回收的工作,比如清除数据和消息,移除回调。 68 | 2. 通过Session的remove方法删除Window,mWindowSession.remove(mWindow),这同样是一个IP C过程,最终会调用WindowManagerService的removeWindow方法 69 | 3. 调用View的dispatchDetachedFromWindow方法,在内部调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。 70 | 4. 调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots、mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。 71 | 72 | ####8.2.3 Window的更新过程 73 | 74 | 首先需要更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams方法来实现的。在ViewRootImpl中会通过scheduleTrversals方法来对View重新布局,包括测量、布局、重绘三个过程。除了View本身的重绘以外,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的,同样是一个IPC过程。 75 | 76 | ##Window的创建过程 77 | 78 | ###8.3.1 Activity的Window创建过程 79 | 80 | 1、Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量。 81 | 82 | 2、Activity实现了Window的Callback接口,当Window接收到外界的状态变化时就会调用Activity的方法,例如onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等。 83 | 84 | 3、Activity的Window是由PolicyManager来创建的,它的真正实现是Policy类,它会新建一个PhoneWindow对象,Activity的setContentView的实现是由PhoneWindow来实现的。 85 | PhoneWindow方法大致遵循如下几个步骤: 86 | 87 | 1. 如果没有DecorView,那么就创建它 88 | 2. 将View添加到DecorView的mContentParent中 89 | 3. 回调Activity的onCreateChanged方法通知Activity视图已经发生改变 90 | 91 | ###8.3.2 Dialog的Window创建过程 92 | 93 | Dialog的Window的创建过程和Activity类似,有如下步骤: 94 | 95 | 1. 创建Window:Diolog中Window的创建同样是通过PolicyManager的makeNewWindow方法来完成的,创建后的对象实际上就是PhoneWindow。 96 | 2. 初始化DecorView并将Dialog的视图添加到DecorView中 97 | 3. 将DecorView添加到Window中并显示:普通的Dialog有一个特殊之处,就是必须采用Activity的Context,如果采用Application的Context,那么就会报错。应用token只有Activity拥有,所以这里只需要Activity作为Context来显示对话框即可。 98 | 99 | ###8.3.3 Toast的Window创建过程 100 | 101 | 在Toast的内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口。 102 | 103 | Toast属于系统Window,它内部的视图由两种方式指定:一种是系统默认的演示,另一种是通过setView方法来指定一个自定义的View 104 | 105 | Toast具有定时取消功能,所以系统采用了Handler。Toast的显示和隐藏是IPC过程,都需要NotificationManagerService(NMS)来实现,在Toast和NMS进行IPC过程时,NMS会跨进程回调Toast中的TN类中的方法,TN类是一个Binder类,运行在Binder线程池中,所以需要通过Handler将其切换到当前发送Toast请求所在的线程,所以Toast无法在没有Looper的线程中弹出。 106 | 107 | 对于非系统应用来说,mToastQueue最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial of Service,拒绝服务)。因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Android开发艺术探索》第十五章笔记.md: -------------------------------------------------------------------------------- 1 | #Android性能优化 2 | --- 3 | Android不可能无限制的使用内存和CPU资源,过多的使用内存会导致内存溢出,即OOM。而过多的使用CPU资源,一般是指做大量的耗时任务,会导致手机变的卡顿甚至出现程序无法响应的情况,即ANR。 4 | 5 | ###15.1.1布局优化 6 | 7 | 1、如何进行布局优化? 8 | 9 | * 首先删除布局中无用的控件和层级 10 | * 其次有选择的使用性能较低的ViewGroup。 11 | * 布局优化的另一种手段是采用标签、标签、ViewStub。标签主要用于布局重用,标签一般和配合使用,它可以降低减少布局的层级,而ViewStub则提供了按需加载的功能,当需要时才会将ViewStub中的布局加载到内存,提高了程序的初始化效率。 12 | 13 | 2、标签只支持android:layout_开头的属性,android:id属性例外。 14 | 15 | 3、ViewStub继承了View,它非常轻量级且宽/高都是0,因此它本身并不参与任何的布局和绘制过程。ViewStub的意义在于按需加载所需的布局文件,在实际开发中,有很多布局文件在正常情况下不会显示,比如网络异常时的界面,这个时候就没有必要在整个界面初始化的时候将其加载起来,通过ViewStub就可以做到在使用的时候再加载,提高了程序初始化的性能。 16 | 17 | 如下所示,android:id是ViewStub的id,而android:inflatedId是布局的根元素的id。 18 | 19 | ``` 20 | 25 | ``` 26 | 27 | 需要加载ViewStub中的布局的时候,可以按照如下两种方式进行: 28 | 29 | ``` 30 | ((ViewStub)findViewById(R.id.xx)).setVisibility(View.VISIBLE); 31 | ``` 32 | 33 | 或者 34 | 35 | ``` 36 | View importPanel = ((ViewStub)findViewById(R.id.stub_import)).inflate(); 37 | ``` 38 | 39 | ###15.1.1绘制优化 40 | 41 | 绘制优化是指View的onDraw方法要避免执行大量的操作: 42 | 43 | * 在onDraw中不要创建新的局部对象,这是因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁的gc,降低了程序的执行效率。 44 | * onDraw方法中不要指定耗时任务,也不能执行成千上万次的循环操作,View的绘制帧率保证60fps是最佳的,这就要求每帧的绘制时间不超过16ms,虽然程序很难保证16ms这个时间,但是尽量降低onDraw方法的复杂度总是切实有效的。 45 | 46 | 47 | ###15.1.3内存泄漏优化 48 | 49 | 可能导致内存泄漏的场景很多,例如静态变量、单例模式、属性动画、AsyncTask、Handler等等 50 | 51 | 52 | ###15.1.4响应速度优化和ANR日志分析 53 | 54 | 1. ANR出现的情况:Activity如果5秒内没有响应屏幕触摸事件或者键盘输入事件就会ANR。而BroadcastReceiver如果10s没有执行完操作也会出现ANR。 55 | 2. 当一个进程发生了ANR之后,系统会在/data/anr目录下创建一个文件traces.txt,通过分析这个文件就能定位ANR的原因。 56 | 57 | ###15.1.5ListView和Bitmap优化 58 | 59 | 1. ListView优化:采用ViewHolder并避免在getView方法中执行耗时操作;根据列表的滑动状态来绘制任务的执行效率;可以尝试开启硬件加速期来使ListView的滑动更加流畅。 60 | 2. Bitmap优化:根据需要对图片进行采样,主要是通过BitmapFactory.Options来根据需要对图片进行采样,采样主要用到了BitmapFactory.Options的inSampleSize参数。 61 | 62 | ###15.1.6线程优化 63 | 64 | 1. 采用线程池,避免程序中存在大量的Thread。线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还能有效的控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致阻塞现象的发生。 65 | 66 | ###15.1.7一些性能优化建议 67 | 68 | * 避免 创建过多的对象 69 | * 不要过多的使用枚举,枚举占用的内存空间要比整形大 70 | * 常量请用static final来修饰 71 | * 使用一些Android特有的数据结构,比如SparseArray和Pair等,它们都具有更好的性能 72 | * 适当使用软引用和弱引用 73 | * 采用内存缓存和磁盘缓存 74 | * 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏 75 | 76 | 77 | ###15.2内存泄漏分析之MAT工具 78 | 79 | MAT是功能强大的内存分析工具,主要有Histograms和Dominator Tree等功能 80 | 81 | 82 | ###15.3提高程序的可维护性 83 | 84 | 1. 命名要规范,要能正确地传达出变量或者方法的含义,少用缩写,关于变量的前缀可以参考Android源码的命名方式,比如私有成员以m开头,静态成员以s开头,常量则全部用大写字母表示,等等。 85 | 2. 代码的排版上需要留出合理的空白来区分不同的代码块,其中同类变量的声明要放在一起,两类变量之间要留出一行空白作为区分。 86 | 3. 合理的命名风格,仅在非常关键的代码添加注释。 87 | 88 | 89 | -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Android开发艺术探索》第四章笔记.md: -------------------------------------------------------------------------------- 1 | ##View的工作原理 2 | --- 3 | 4 | ###4.1 初识ViewRoot和DecorView 5 | 6 | 1、ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立连接。 7 | 8 | 2、View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout、draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器的放置位置,而draw则负责将View绘制在屏幕上。 9 | 10 | 3、performTraversals会依次调用performMeasure、performLayout、performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中performMeasure会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中对所有的子元素进行measure过程,这个时候measure流程就会从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素就会重复父容器的measure过程,如此反复就完成了整个View树的遍历。 11 | 12 | 4、measure过程中决定了View的宽/高,Measure完成以后,可以通过getMeasureWidth和getMeasureHeight方法来获取到View测量后的宽/高,在几乎所有的情况下它都等同于View最终的宽高。layout决定了View的四个顶点的坐标和View的实际的宽高,通过getWidth和getHeight方法可以获得最终的宽高。draw过程决定了View的显示。 13 | 14 | 5、DecorView其实是一个FrameLayout,其中包含了一个竖直方向的LinearLayout,上面是标题栏,下面是内容栏(id为android.R.id.content)。View层的事件都先经过DecorView,然后才传给我们的View。 15 | 16 | ###理解MeasureSpec 17 | 18 | 1、MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的内存分配,为了方便操作,其提供了打包和解包方法。SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize。 19 | 20 | SpecMode有三类,每一类都表示特殊的含义: 21 | 22 | 1. UNSPECIFIED 父容器不对View有任何的限制,要多大给多大,这种情况下一般用于系统内部,表示一种测量的状态。 23 | 2. EXACTLY 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent和具体的数值这两种模式 24 | 3. AT_MOST 父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content 25 | 26 | 2、MeasureSpec和LayoutParams的对应关系 27 | 28 | 在View测量的时候系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。 29 | 30 | MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步确定View的宽高。对于DecorView,它的MeasureSpec由窗口的尺寸和其自身的LayoutParams来决定;对于普通View,它的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定 31 | 32 | 3、当view采用固定宽高时,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式,并且大小是LayoutParams中的大小。 33 | 当view的宽高是match_parent时,如果父容器的模式是精确模式,那么view也是精确模式,并且大小是父容器的剩余空间;如果父容器是最大模式,那么view也是最大模式,并且大小是不会超过父容器的剩余空间。 34 | 当view的宽高是wrap_content时,不管父容器的模式是精确模式还是最大模式,view的模式总是最大模式,并且大小不超过父容器的剩余空间。 35 | 36 | ###4.3 View的工作流程 37 | 38 | 1、View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕了。如果View还没有测量完毕,那么获得的宽和高都是0。下面是四种解决该问题的方法: 39 | 40 | * Activity/View#onWindowsChanged方法 41 | 42 | onWindowFocusChanged方法表示View已经初始化完毕了,宽高已经准备好了,这个时候去获取是没问题的。这个方法会被调用多次,当Activity继续执行或者暂停执行的时候,这个方法都会被调用。 43 | 44 | * View.post(runnable) 45 | 46 | 通过post将一个Runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。 47 | 48 | * ViewTreeObsever 49 | 50 | 使用ViewTreeObserver的众多回调方法可以完成这个功能,比如使用onGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法将被回调。伴随着View树的变化,这个方法也会被多次调用。 51 | 52 | * view.measure(int widthMeasureSpec, int heightMeasureSpec) 53 | 54 | 通过手动对View进行measure来得到View的宽高,这个要根据View的LayoutParams来处理: 55 | 56 | match_parent:无法measure出具体的宽高 57 | 58 | wrap_content:如下measure,设置最大值 59 | 60 | ``` 61 | int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST); 62 | 63 | int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST); 64 | 65 | view.measure(widthMeasureSpec, heightMeasureSpec); 66 | ``` 67 | 68 | 69 | 70 | 精确值:例如100px 71 | 72 | ``` 73 | 74 | int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); 75 | 76 | int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); 77 | 78 | view.measure(widthMeasureSpec, heightMeasureSpec); 79 | ``` 80 | 81 | 82 | 2、在View的默认实现中,View的测量宽高和最终宽高时相等的,只不过测量宽高形成于measure过程,而最终宽高形成于layout过程。 83 | 84 | 3、draw过程大概有下面几步: 85 | 86 | 1. 绘制背景:background.draw(canvas); 87 | 2. 绘制自己:onDraw(); 88 | 3. 绘制children:dispatchDraw; 89 | 4. 绘制装饰:onDrawScrollBars 90 | 91 | ###4.4自定义View 92 | 93 | 作者将自定义View分为以下4类: 94 | 95 | 1. 继承view重写onDraw方法 96 | 2. 继承ViewGroup派生特殊的Layout 97 | 3. 继承特定的View(比如TextView) 98 | 4. 继承特殊的ViewGroup(比如LinearLayout) 99 | 100 | 自定义View须知: 101 | 102 | 1. 让View支持wrap_content 103 | 2. 如果有必要,让你的View支持padding 104 | 3. 尽量不要在View中使用Handler,没必要 105 | 4. View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow 106 | 5. View带有滑动嵌套情形时,需要处理好滑动冲突 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Java编程思想》第一章读书笔记.md: -------------------------------------------------------------------------------- 1 | #《Java编程思想》第一章 2 | --- 3 | ##对象导论 4 | 5 | 封装 6 | 7 | * 被隐藏(也即封装)的部分通常代表对象内部脆弱的部分,它们很容易被程序员所毁坏,因此将实现隐藏起来可以减少程序的bug。 8 | * 隐藏是通过访问控制修饰符(public、protected、包访问、private)来实现的。 9 | * 访问控制的第一个存在原因就是让调用者无法直接触及他们不应该触及的部分,但从另一方面来看,其实这不失为一项服务,因为他们可以很容易地看出哪些东西对他们来说很重要,而哪些东西可能不关心;访问控制的第二个存在原因就是允许库设计者可以改变类的内部的工作方式而不用担心会影响到调用者。 10 | 11 | 继承 12 | 13 | * 代码复用:复用是面向对象程序设计所提供最了不起的优点之一。 14 | * 最简单的代码复用就是直接调用类的方法,此外,我们还可以将该类置于某个新类中,使它成为新类的属性成员。新的类也可由任意数量、任意类型的其他对象以任意可以实现新的类中想要功能的方式所组成,这种使用现有的类合成新的类方式称为组合复用。 15 | * 组合复用带来了极大的灵活性,使得它能在运行时动态的修改其实现行为,但继承并不具备这样的灵活性,因为继承是在编译时期就已经确定其行为,在运行时期是不能修改的。 16 | * 继承两种实现方式,第一种方式非常直接:直接在子类中添加新的方法,即扩展父类接口。第二种方式就是子类覆写父类方法,但不新增父类没有接口。 17 | * “is-a是一个”与“is-like-a像是一个”。继承时,我们使用第一种还是第二种方式呢?这可能引起争论:继承应该只覆盖基类的方法,而并不添加在基类中没有的新方法吗?如果这样做,就意味着子类与基类是完全相同的类型,因为它们具有完全相同的接口,结果可以用一个子类对象来完全替代一个基类对象,这可被认为是纯粹替代,通常称之为替代原则,这也是一种理想的方式,我们经常称这种情况下的子类与基类的关系是“is-a是一个”;有时必须在子类型中添加新的接口,这样也就扩展了接口,这个新的类型仍可以替代基类,但是这种替代并不完美,因为基类无法访问新添加的方法,这种情况下我们可以描述为“is-like-a像是一个”关系。 18 | 19 | 多态 20 | 21 | * 一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,而向对象程序设计语言使用了后期绑定的概念。 22 | * 方法的调用就是编译器将产生对一个具体函数名字的调用,前期绑定是在运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP中,程序直到运行时才能够确定代码的地址,为了执行后期绑定,Java编译器使用了一小段特殊代码来替代绝对地址调用,这段代码使用对象中存储的信息来计算方法体的地址,根据这段特殊代码,每个对象都可以具有不同的行为表现。 23 | * 在某些语言中,必须明确地声明某个方法具备后期绑定属性所带来的灵活性,如C++使用virtual关键字来实现,在默认情况下,C++不是动态绑定的,而在Java中,动态绑定是默认行为,不需要添加额外的关键字来实现多态。 24 | 25 | Java语言支持四种类型 26 | 27 | 接口(interface)、类(class)、数组(array)和基本类型(primitive)。前三种类型通常被称为引用类型(reference type),类实例和数组是对象(object),而基本类型的值则不是对象。类的成员(member)由它的域(field)、方法(method)、成员类(member class)和成员接口(member interface)组成。方法签名(signature)由它的名称和所有参数类型组成;签名不包括它的返回类型。 28 | 29 | 类与类之间的关系 30 | 31 | 类和类、类和接口、接口和接口之间有如下几种关系:泛化关系、实现关系、关联关系(聚合、合成)、依赖关系。 32 | 33 | * 泛化:表示类与类之间的继承关系,使用extends关键字来表示。在图形上使用虚线三角形箭头表示。 34 | 35 | * 实现:表示类与接口之间的实现关系,使用implements关键字来表示。在图形上使用实线三角形箭头表示。 36 | 37 | * 关联:类与类之间的联接。关联可以是双向的,也可以是单向的,双向的关联可以有两个箭头或都没有箭头。单向的关联有一个箭头,表示关联的方向。在Java里,关联关系是使用实例变量实现的。在每一个关联的端点,还可以有一个基数,表时这一端的类可以有几个实例。常见的基数有:0..1(零个或者一个实例)、0..*或者*(没限制,可以是零)、1(只有一个实例)、1..*(至少有一个实例)。一个关联关系可以进一步确定为聚合与合成关系。在图形上使用实线的箭头表示。 38 | 39 | * 聚合:是关联关系的一种,是强的关联关系,聚合是整体和个体之间的关系。关联与聚合仅仅从Java语法是上是分辨不出的,需要考察所涉及的类之间的逻辑关系。如果不确定是聚合关系,可以将之设置为关联关系。图形是在实线的箭头的尾部多一个空心菱形。 40 | 41 | * 合成:是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。整体消亡,则部分与消亡。图形是在实线的箭头的尾部多一个黑心菱形。 42 | 43 | * 依赖:类与类之间的连接,依赖总是单向的。表示一个类依赖于另一个类的定义。一般而言,依赖关系在Java里体现为局部变量、方法的参数、以及对静态方法的调用。但如果对方出现在实例变量中,那关系就超过了依赖关系,而成了关联关系了。在图形上使用虚线的箭头表示。 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Part5/ReadingNotes/《Java编程思想》第二章读书笔记.md: -------------------------------------------------------------------------------- 1 | #《Java编程思想第二章》 2 | --- 3 | 4 | ##一切都是对象 5 | 6 | ###对象存放位置与生命周期 7 | 8 | C++创建的对象可以存放在栈、静态存储区与堆(heap)中,放在栈中的对象用完后不需手动释放,会自动销毁,但放在堆中的对象需手动释放,栈中的对象所需空间与生命周期都是确定的,堆中的对象内存分配是动态的,在运行时才知道需要多少内存以及生命周期,如果说在堆上创建对象,编译器就会对它的生命周期一无所知,C++就需要以编程的方式来确定何时销毁对象,这可能因不正确处理而导致内存泄漏,而Java则提供了自动垃圾回收机制。 9 | 10 | Java对象的创建采用了动态内存分配策略,即创建的堆都是放在堆中的。 11 | 12 | ###数据内存分配 13 | 14 | 寄存器——位于处理器内部,这是最快的存储区,大小极其有限,一般不能直接控制,但C和C++允许你向编译器建议寄存器分配方式。 15 | 16 | 堆栈——位于通用RAM(随机访问存储器)中,堆栈指针向下移动,则分配新的内存;若向上移动,则释放内存。这是一种快速有效的分配方法,速度仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些Java数据存储于堆栈中(如对象引用),但是Java对象并不存储于其中。 17 | 18 | 堆——一种通用的内存池(也位于RAM区),用于存放所有的Java对象,堆不同于堆栈的好处是,编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆栈分配存储有很大的灵活性。当然,这种灵活性导致了分配需要更多的时间,时间效率上不如堆栈。 19 | 20 | 常量存储:常量值通常直接存放到程序代码内部,这样做是安全的,因为它们永远不会被改变。 21 | 22 | 非RAM存储:数据可完全存活于程序之外,在没有运行机制时也可以存在,如持久化对象的存放。 23 | 24 | JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。 25 | 26 | Java字节码的执行有两种方式: 27 | 28 | 1. 即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。 29 | 2. 解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。 30 | 31 | 通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。 32 | 33 | ###基本类型 34 | 35 | void属于基本类型,但只能用来修饰方法,不能用来修饰变量。 36 | 37 | * 只要两个操作数中有一个是double类型的,另一个将会被转换成double类型,并且结果也是double类型; 38 | 39 | * 否则,只要两个操作数中有一个是float类型的,另一个将会被转换成float类型,并且结果也是float类型; 40 | 41 | * 否则,只要两个操作数中有一个是long类型的,另一个将会被转换成long类型,并且结果也是long类型; 42 | 43 | * 否则,两个操作数(包括byte、short、int、char)都将会被转换成int类型,并且结果也是int类型。 44 | 45 | Java提供了两个用于高精度计算类:BigInteger和BigDecimal,大体属于“包装器类”范畴,但都没有对应的基本类型。 46 | 47 | BigInteger支持任意精度的整数,可表示任何大小的整数值。 48 | 49 | BigDecimal支持任意精度的定点数,例如,可以用它进行精确的货币计算。 50 | 51 | ###引用与对象生存周期 52 | 53 | ``` 54 | { 55 | 56 | String s = new String("a string"); 57 | 58 | } 59 | ``` 60 | 61 | 引用s在作用域终点就消失了,然而,s指向的String对象继续占据内存,最后由垃圾回收器回收。 62 | 63 | ###方法签名 64 | 65 | 方法名和参数列表(合起来被称为“方法签名”)唯一地标识出某个方法。 66 | 67 | ###static修饰字段与方法的区别 68 | 69 | 一个static字段对每个对象来说都只有一份空间,而非static字段则是对每个对象都有一个存储空间,但是如果static作用于某个方法时,差别却没有那么大,static方法的一个重要的用法就是在不创建任何对象的前提下就可以调用它,这一点对定义main()方法很重要,该方法是运行时程序的一个入口。 70 | 71 | 72 | -------------------------------------------------------------------------------- /Part5/ReadingNotes/《深入理解java虚拟机》第12章.md: -------------------------------------------------------------------------------- 1 | #深入理解Java虚拟机读书笔记 2 | --- 3 | ###第12章 4 | 5 | ####主内存和工作内存 6 | java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。为了获得较好的执行效能,Java内存模型咩有并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码执行顺序这类权利。 7 | 8 | Java内存模型规定了所有的变量都存储在主内存中(Main Memory)中(此处的主内存和介绍物理硬件时的主内存名字一样,两者也可以互相类比,但此处仅是虚拟机内存的一部分)。每条线程还有自己的工作内存(Working Memory,可与前面所讲的处理器高速缓存类比),线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程也无法直接访问对方工作内存中的变量,线程间变量的传递需要通过主内存来完成,线程、主内存、工作内存的交互关系如图: 9 | 10 | ![这里写图片描述](http://img.blog.csdn.net/20160321091659271) 11 | 12 | ####内存间交互操作 13 | 14 | 关于主内存和工作内存之间具体的交互协议,即一个遍历那个如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了一下八种操作来完成: 15 | 16 | * lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。 17 | * unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 18 | * read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。 19 | * load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 20 | * use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。 21 | * assign(赋值):作用于工作内存的变量,它把一个从执行引擎收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码时执行这个指令。 22 | * store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。 23 | * write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。 24 | 25 | 如果要把一个变量从主内存复制到工作内存,那就要按顺序执行read和load操作,如果要把变量从工作内存同步回主内存,就要按照顺序地执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,而没有保证必须是连续执行。Java内存模型规定了在执行上述八种基本操作时必须满足时必须满足如下规则: 26 | 27 | * 不允许read和load、store和write操作之一单独出现 28 | * 不允许一个线程丢弃它的最近的assign操作 29 | * 不允许一个线程无原因的(没有发生过任何assign操作)把数据从线程的工作内存同步到主内存中 30 | * 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量 31 | * 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁 32 | * 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。 33 | * 如果一个变量事先没有被lock锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定住的变量。 34 | * 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write) 35 | 36 | ####对于volatile型变量的特殊规则 37 | 当一个变量被定义成volatile之后,它将具备两种特性,第一是保证此变量对所有线程的可见性,这里的可见性是指当一条线程修改了这个变量的值,新值对于其他的线程是可以立即得知的。 38 | 39 | 由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized或java.util.concurrent中的原子类)来保证原子性 40 | 41 | * 运算结果并不依赖于变量的当前值,或者能够确保只有单一的线程修改变量的值 42 | * 变量不需要与其他的状态变量共同参与不变约束 43 | 44 | volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢上一些,因为它需要在本地代码中插入许多内存屏障(Memory Barrier或Memory Fence)指令来保证处理器不发生乱序执行。不过即便如此,大多数场景volatile的总开销仍然要比锁来的低。 45 | 46 | ####对于long和double型变量的特殊规则 47 | Java内存模型要求对于lock、unlock、read、load、assign、use、store和write这八个操作都具有原子性,但是对于64位的数据类型(long和double),允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load、store、read和write这四个操作的原子性。 48 | 49 | ####原子性、可见性与有序性 50 | Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本生就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步代码块只能串行的进入。 51 | 52 | ####先行发生原则 53 | 先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。 54 | 55 | 以下是Java内存模型下一些天然的先行发生关系,这些关系无需任何同步器协助就已经存在。 56 | 57 | * 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确的说是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。 58 | * 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而后面是指事件上的先后顺序。 59 | * volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的后面同样是指时间上的先后顺序。 60 | * 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。 61 | * 线程终止规则(Thread Termination Rule):对线程所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join() 方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。 62 | * 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。 63 | * 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。 64 | * 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。 65 | 66 | ####线程的实现 67 | 线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度(线程是CPU调度的最基本单位)。 68 | 69 | 实现线程主要有三种方式: 70 | 71 | 1. 使用内核线程实现 72 | 2. 使用用户线程实现 73 | 3. 混合实现 74 | 4. Java线程的实现 75 | 76 | ####Java线程调度 77 | 线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式(Cooperative Threads-Scheduling)线程调度和抢占式(Preemptive Threads-Scheduling)线程调度。 78 | 79 | 使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上去。协同式多线程的最大好处是实现简单,而且由于线程要把自己的事情干完后才会进行线程切换,切换操作对自己是可知的,所以没有什么线程同步的问题。坏处是线程执行时间不可控制,甚至如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。 80 | 81 | 抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(在Java中,Thread.yield()可以让出执行时间,但是要获取执行时间的话,线程本身是没有什么办法的)。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会有一个线程导致整个进程阻塞的问题,Java使用的线程调度方式就是抢占式调度。 82 | 83 | 84 | ####状态转换 85 | Java定义了5种进程状态 86 | 87 | * 新建(New):创建后尚未启动的线程处于这种状态。 88 | * 运行(Runnable):Runnable包括了操作系统状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待CPU为它分配执行时间。 89 | * 无限期等待(Waiting):处于这种状态的进程不会被分配CPU执行时间,它们要等待被其他线程显示的唤醒。以下方法会让线程陷入无限期的等待状态: 90 | * 没有设置Timeout参数的Object.wait()方法 91 | * 没有设置Timeout参数的Thread.join()方法 92 | * LockSupport.park()方法 93 | * 限期等待(Timed Waiting):处于这种状态的进程不会被分配CPU执行时间,不过无需等待被其他线程显示的唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态: 94 | * Thread.sleep()方法 95 | * 设置了Timeout参数的Object.wait()方法 96 | * 设置了Timeout参数的Thread.join()方法 97 | * LockSupport.parkNanos()方法 98 | * LockSupport.parkUnitil()方法 99 | * 阻塞(Blocked):进程被阻塞了,阻塞状态和等待状态的区别是:阻塞状态在等待着获取到一个排它锁,这个事件将在另一个线程放弃这个锁的时候发生;而等待状态则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。 100 | * 结束(Terminated):已终止线程的线程状态,线程已经结束执行。 -------------------------------------------------------------------------------- /Part6/InterviewExperience/Alibaba.md: -------------------------------------------------------------------------------- 1 | #Alibaba 2 | --- 3 | 4 | 一面 5 | 6 | * 说一下你怎么学习安卓的? 7 | * 项目中遇到哪些问题,如何解决的? 8 | * Android事件分发机制? 9 | * 三级缓存底层实现? 10 | * HashMap底层实现,hashCode如何对应bucket? 11 | * Java的垃圾回收机制,引用计数法两个对象互相引用如何解决? 12 | * 用过的开源框架的源码分析 13 | * Acticity的生命周期,Activity异常退出该如何处理? 14 | * tcp和udp的区别,tcp如何保证可靠的,丢包如何处理? 15 | 16 | 二面: 17 | 18 | * 标号1-n的n个人首尾相接,1到3报数,报到3的退出,求最后一个人的标号 19 | * 给定一个字符串,求第一个不重复的字符 abbcad -> c 20 | 21 | 22 | 面试者:陶程 -------------------------------------------------------------------------------- /Part6/InterviewExperience/新浪微博.md: -------------------------------------------------------------------------------- 1 | #新浪微博 2 | --- 3 | 一面 4 | --- 5 | 6 | 静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它? 7 | 8 | ``` 9 | 静态内部类:使用static修饰的内部类 10 | 匿名内部类:使用new生成的内部类 11 | 因为内部类的产生依赖于外部类,持有的引用是类名.this。 12 | ``` 13 | 14 | ArrayList和Vector的主要区别是什么? 15 | 16 | ``` 17 | ArrayList在Java1.2引入,用于替换Vector 18 | 19 | Vector: 20 | 21 | 线程同步 22 | 当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍 23 | 24 | ArrayList: 25 | 26 | 线程不同步,但性能很好 27 | 当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小 28 | ``` 29 | 30 | [Java集合类框架](http://yuweiguocn.github.io/2016/01/06/java-collection/) 31 | 32 | 33 | Java中try catch finally的执行顺序 34 | 35 | ``` 36 | 先执行try中代码发生异常执行catch中代码,最后一定会执行finally中代码 37 | ``` 38 | 39 | switch是否能作用在byte上,是否能作用在long上,是否能作用在String上? 40 | 41 | ``` 42 | switch支持使用byte类型,不支持long类型,String支持在java1.7引入 43 | ``` 44 | 45 | Activity和Fragment生命周期有哪些? 46 | 47 | ``` 48 | Activity——onCreate->onStart->onResume->onPause->onStop->onDestroy 49 | 50 | Fragment——onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach 51 | ``` 52 | 53 | 54 | onInterceptTouchEvent()和onTouchEvent()的区别? 55 | 56 | ``` 57 | onInterceptTouchEvent()用于拦截触摸事件 58 | onTouchEvent()用于处理触摸事件 59 | ``` 60 | 61 | RemoteView在哪些功能中使用 62 | 63 | ``` 64 | APPwidget和Notification中 65 | ``` 66 | 67 | SurfaceView和View的区别是什么? 68 | 69 | ``` 70 | SurfaceView中采用了双缓存技术,在单独的线程中更新界面 71 | View在UI线程中更新界面 72 | ``` 73 | 74 | 讲一下android中进程的优先级? 75 | 76 | ``` 77 | 前台进程 78 | 可见进程 79 | 服务进程 80 | 后台进程 81 | 空进程 82 | ``` 83 | 84 | tips:静态类持有Activity引用会导致内存泄露 85 | 86 | 87 | ##二面 88 | 89 | * service生命周期,可以执行耗时操作吗? 90 | * JNI开发流程 91 | * Java线程池,线程同步 92 | * 自己设计一个图片加载框架 93 | * 自定义View相关方法 94 | * http ResponseCode 95 | * 插件化,动态加载 96 | * 性能优化,MAT 97 | * AsyncTask原理 98 | * 65k限制 99 | * Serializable和Parcelable 100 | * 文件和数据库哪个效率高 101 | * 断点续传 102 | * WebView和JS 103 | * 所使用的开源框架的实现原理,源码 104 | 105 | [codekk:开源框架源码解析](http://codekk.com/open-source-project-analysis) 106 | 107 | 108 | [Android基础——Service](http://yuweiguocn.github.io/2016/03/28/android-basic-service/) 109 | 110 | [Android基础——IntentService](http://yuweiguocn.github.io/2016/03/31/android-basic-intentservice/) 111 | 112 | [Android开发指导——Service](http://yuweiguocn.github.io/2016/04/02/android-guide-service/) 113 | 114 | [Android开发指导——绑定Service](http://yuweiguocn.github.io/2016/03/31/android-guide-bound-service/) 115 | 116 | [Android开发指导——进程间通信AIDL](http://yuweiguocn.github.io/2016/03/31/android-guide-aidl/) 117 | 118 | [Android面试基础知识总结(一)](http://yuweiguocn.github.io/2016/03/26/android-interview-basic-1/) 119 | 120 | [Android面试——APP性能优化](http://yuweiguocn.github.io/2016/04/10/android-interview-peformance/) 121 | 122 | [Android中Java和JavaScript交互](http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/) 123 | 124 | [WebView 远程代码执行漏洞浅析](http://jaq.alibaba.com/blog.htm?spm=0.0.0.0.oMsDAl&id=48) 125 | 126 | [WebView中的Java与JavaScript提供【安全可靠】的多样互通方案](https://github.com/pedant/safe-java-js-webview-bridge) 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /Part6/InterviewExperience/网易杭研.md: -------------------------------------------------------------------------------- 1 | #网易杭研 2 | --- 3 | 4 | ###一面: 5 | 6 | * 自我介绍 7 | * Android中ClassLoader和java中有什么关系和区别? 8 | * 熟不熟jvm,说一下Jvm的自动内存管理? 9 | * 语言基础,String类可以被继承吗?为什么? 10 | * Final能修饰什么?(当时我说class、field、method,他说还有吗?然后又叫我不要在意,后来回想起,应该是问到我在参数里面要不要用final,接下来是因为匿名内部类) 11 | * Java中有内存泄露吗?(先说本质,再结合handler+匿名内部类)当时如何分析的? 12 | * 描述下Aidl?觉得aidl有什么缺陷(这里在这个问题上回答有欠缺) 13 | * **评价一下我,如果顺利进网易,需要往技术栈加什么点尽快投入业务?** 14 | 15 | 16 | 17 | ###二面: 18 | 19 | * 用过什么开源,举一个例子?(volley) 20 | * Activity生命周期?情景:现在在一张act1点了新的act2,周期如何? 21 | * Act的launchMode,有没有结合项目用过(自己的程序锁和微信的PC端登陆对比,不过我现在又发现,应该大约估计可能是动态加载的一个缺陷,如果有找到相关信息,请务必跟我说。具体问题就是,当在PC端登录时,Android终端的微信会跳出,即使wechat的task不是在fore,当按下确认,返回的是wechat,而不是自己先前的app) 22 | * View的绘制原理,有没有用canvas自己画过ui? 23 | * 以后想做Android什么方向?(中间件+SDK) 24 | * 怎么看待前端和后端? 25 | * 如果学前端会如何学? 26 | * 优缺点?兴趣? 27 | * 想不想来杭州? 28 | * **评价一下我?往技术栈加什么?** 29 | 30 | 31 | ###三面HR: 32 | 33 | * 为什么想来网易? 34 | * 有投其他公司吗? 35 | * 网易最吸引你的是什么? 36 | * 想来杭州吗? 37 | * **评价一下我?** -------------------------------------------------------------------------------- /Part6/InterviewExperience/美团.md: -------------------------------------------------------------------------------- 1 | #美团 2 | --- 3 | 4 | 一面 5 | 6 | - 自我介绍 7 | - 面向对象三大特性 8 | - Java虚拟机,垃圾回收 9 | - GSON 10 | - RxJava+Retrofit 11 | - 图片缓存,三级缓存 12 | - Android启动模式 13 | - 四大组件 14 | - Fragment生命周期,嵌套 15 | - AsyncTask机制 16 | - Handler机制 17 | 18 | 二面 19 | 20 | - 面试官写程序,看错误。 21 | - 面试官写程序让判断GC引用计数法循环引用会发生什么情况 22 | - Android进程间通信,Binder机制 23 | - Handler消息机制,postDelayed会造成线程阻塞吗?对内存有什么影响? 24 | - Debug和Release状态的不同 25 | - 实现stack 的pop和push接口 要求: 26 | - 1.用基本的数组实现 27 | - 2.考虑范型 28 | - 3.考虑下同步问题 29 | - 4.考虑扩容问题 30 | 31 | 面试者:陶程 -------------------------------------------------------------------------------- /Part6/InterviewExperience/蜻蜓FM.md: -------------------------------------------------------------------------------- 1 | #蜻蜓FM 2 | --- 3 | 4 | 一面 5 | 6 | - Toolbar的使用 7 | - 如何判断本地缓存的时候数据需要从网络端获取 8 | - 跨进程间通信 9 | - Handler消息机制 10 | - SharedPreference实现 11 | - 快速排序 12 | - 项目难点 13 | 14 | 15 | 之后让去现场面,没去... 16 | 17 | 面试人:陶程 -------------------------------------------------------------------------------- /Part6/InterviewExperience/豌豆荚.md: -------------------------------------------------------------------------------- 1 | #豌豆荚三面 2 | --- 3 | 豌豆荚一面 4 | 5 | * 介绍一下你的项目 6 | * 网络框架的搭建 7 | * 图片加载框架的实现 8 | * 写个图片浏览器,说出你的思路 9 | * 上网站写代码,如下: 10 | 有一个容器类 ArrayList,保存整数类型的元素,现在要求编写一个帮助类,类内提供一个帮助函数,帮助函数的功能是删除 容器中<10的元素。 11 | 12 | 豌豆荚二面 13 | 14 | * Activity的启动模式 15 | * 事件分发机制 16 | 17 | * 写代码,LeetCode上股票利益最大化问题 18 | * 写代码,剑指offer上第一次只出现一次的字符 19 | 20 | 豌豆荚三面 21 | 22 | * 聊项目,聊大学做过的事 23 | * 写代码,反转字符串 24 | * 写代码,字符串中出现最多的字符。 25 | 26 | 27 | 面试者:陶程 --------------------------------------------------------------------------------