├── 2020Android最新大厂面试真题总结:Flutter+NDK+性能优化+源码解析+开源框架等.md ├── Docs ├── Android开源库源码分析.md ├── Android扩展知识点.md ├── Android知识点汇总.md ├── Gradle知识点汇总.md ├── Java知识点汇总.md ├── 常见面试算法题汇总.md ├── 计算机网络基础.md └── 设计模式汇总.md ├── README.md └── image └── 群二维码.png /2020Android最新大厂面试真题总结:Flutter+NDK+性能优化+源码解析+开源框架等.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 1、写在前面 4 | 5 | 本人目前就读于华南理工软件工程专业大四,2019年春季招聘拿了腾讯、CVTE、华为、YY等公司的实习offer,从2019年7月起在腾讯深圳实习,已拿到转正offer,2019秋季应届生招聘只投了阿里和华为两家公司,运气较好,有幸都拿到了offer,秋招也暂时告一段落。除了面经,想写一写学习和招聘的个人经验总结。学习和招聘都是和个人思维强相关的东西,我的经验也不一定对,我只是综合我个人以及周围相关同学的经验、踩过的坑,给后面的学弟学妹们一些启发。 6 | 7 | ### 2、正文 8 | 9 | 首先,我的相关经验仅限于技术/开发类招聘,产品、策划等方向我并不了解,不过我在腾讯和招聘过程中也认识了不少相关方向的小伙伴,如果有相关需求的同学也可以找我咨询他们的联系方式。 10 | 11 | 回到正题,招聘相关的经验,我准备从时间点、准备、方向和面试经验四个方面来说。 12 | 13 | #### 2.1、时间点 14 | 15 | 技术/开发类的招聘,对于本科生而言,需要重点关注两个时间点:实习生春季招聘和应届生秋季招聘,也就是大三下学期的那一年的3月开始(如我是2016届入学,也就是2019年3月左右开始的)的春季招聘,主要面向实习生招聘,实习时间一般为7—11月;和那一年9月开始的秋季招聘,主要面向应届生招聘,入职时间也就是毕业之后。这个3月、9月都不是精确地概念,只是集中在那个时间段。 16 | 17 | 这两个时间点对于招聘是相当重要的,因为这两个时间点,不论大、小公司都会集中进行招聘相关的工作,如果错过了这两个时间点,后面都会很困难,也就是说如果你想在招聘过程中顺利的找到心仪的工作,提前准备是必不可少的,特别是4月和9月都是招聘的高峰期,如果你在这两个高峰期没有准备充分,可能在面试中就不能发挥出自己的实力,错过很多进入大公司的机会。 18 | 19 | 对于春季招聘,我的建议是2月份左右开始规划和准备(我这里讲的准备是对以往的知识进行梳理和复习,并不是指2月份才开始学习);对于秋季招聘,这个要看你个人的规划,一般在实习期间也可以进行适当的准备和复习,特别是对于实习不满意或者没有转正的同学,在实习期间复习也是必不可少的。 20 | 21 | 除了这两个时间点,也会有一些公司有补招、散招,不过还是抓住正式招聘比较有优势,特别是大家一起准备的话,效果会更好。另外,实习和秋招的机会都要好好把握,特别是实习的机会比较重要,找到一个好的实习可以为秋招的简历增色不少(特别是bat实习的转正率都会很高,可以避免秋招更激烈的竞争),如果对自己的基础知识不自信的同学,一定要提前准备,努力了都会有一个让自己满意的结果。 22 | 23 | #### 2.2、准备 24 | 25 | 对于一名本科生来说,如何准备笔试和面试是最棘手的,因为笔试、面试不像是某一门学科的考试,面试往往考察的是你的一个综合能力,但对于技术类面试来说,其实面试的考察还是比较有针对性的,按照以往的经验来说,准备可以从两个方面来准备:基础知识和岗位方向准备。 26 | 27 | ##### 2.2.1、面试刷题 28 | 29 | 基础知识不是别的,就是课本知识:操作系统、数据结构、数据库、计算机网络、常见算法等,这是基本上每个面试官或多或少都会提及的,并且**一定要记住,你项目做得再多,基础知识不掌握好肯定不行**。 30 | 31 | 对于基础知识的掌握最好可以去看一看相关面经,因为和考试不同,考试往往是一套比较成熟的试题,而面试对于基础知识的掌握往往在原理上,比如操作系统期末考试的考察往往是通过题目,而面试官往往会问你:和我说一说虚拟内存是如何在操作系统中实现的,这时你就要通过画图、口语表述的方式把这个原理说清楚,和做题的区别还是较大的。这里我给大家整理了一些大厂面试真题(含解析)免去了大家在网络上找面经的时间,这些大厂面试题其实大部分都是重合的,所以说对于你复习有很大的好处,并且往往也能给你复习找一个方向。 32 | 我从基础-中级-高级开始一步一步逐步深入,这些面试问题一样都有分类整理,点击【[学习](https://shimo.im/docs/QVGDhCjVKvQ6r6TJ)】给你分享喔(附答案解析)。 33 | 34 | **比如基础部分:生命周期,Context,动画......** 35 | 36 | ![](https://upload-images.jianshu.io/upload_images/23087078-db9b5a8e218e6d83.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) 37 | 38 | **中级部分:HashMap,Kotlin,HTTP,Flutter......** 39 | 40 | ![](https://upload-images.jianshu.io/upload_images/23087078-9f4555cb44a188ae.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) 41 | 42 | **高级部分:性能优化,Android Framework,Android优秀三方库源码,热修复、插件化、模块化、组件化、Gradle......** 43 | 44 | ![](https://upload-images.jianshu.io/upload_images/23087078-aef0bdd815d1382c.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) 45 | **其他大厂BATJ面试真题解析** 46 | ![](https://upload-images.jianshu.io/upload_images/23087078-21c8225f01973111.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 47 | **阿里P6-P7进阶学习视频** 48 | ![](https://upload-images.jianshu.io/upload_images/23087078-bd23b90e0f1448a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 49 | 50 | 51 | **对于基础知识的复习**,不同岗位方向的重点也不一样,这也是我们需要注意的,举个例子,Java后台方向的基础知识可能更加侧重于Java、JVM、数据库等内容,Android方向的基础知识可能侧重于Java、操作系统等等,大家在复习的时候也需要进行相应的针对性的复习。 52 | 53 | ##### 2.2.2、岗位方向知识 54 | 55 | 岗位方向知识就是与你所投递的岗位相关的知识,并且它和简历内容是紧密结合在一起的,因为面试官不会孤立地去问你问题,而是通过简历上的内容进行提问,这也要说到我们常见的一个误区,就是大学生项目一定要做精、做深入而不是做多,特别是对于你岗位方向的知识进行一个深入的了解而不是只停留在表面。因为对于面试官来说,大学生项目只不过是小孩子过家家(针对绝大多数普通本科生来说),他肯定不会对项目有特别大的兴趣,而是希望看到你在这个项目或者说实践中的学习能力、解决问题的能力的体现。 56 | 57 | 举个例子,针对安卓方向,也就是我面试的时候选择的方向来说,很多同学学习安卓有很多或大或小的Demo和App,可能使用了很多市面上很流行的框架,比如Retrofit、RxJava,但面试官往往会问,你有没有深入了解过Retrofit?有没有了解过它的实现?又比如对于安卓这个方向来说,面试官往往会问你Android触摸机制的实现你有没有了解过?这些都是常见的Android端面试问题,往往就考察了我们学习的深入程度,所以我才说同学们在做项目的过程中,可能你的项目不是很大,只是个个人作品,但你一定要在学习和做项目的过程中进行深入的学习和探索,比如可以对项目中使用到的框架啊的源码进行学习和总结,往往了解的越深入,越能让你在众多求职者中脱颖而出。 58 | 59 | 对于不同方向的准备,这里我也建议大家上网搜集一些相关岗位的面经(前面提到的牛客上资料很丰富,也有很多网站上也有相关资料),对于其他方向我也没有什么经验,Android方向的同学可以参考我自己在牛客上发的贴,其实多搜索搜索,基本上都有很多相关资料。 60 | 61 | 最后强调一下,一定不要陷入我一定要做项目的误区,哪怕你项目很少,只要你在某个领域了解的很深入,比如C++、数据库、JVM这些大家耳熟能详的东西,不怕找不到好的工作,所以自己在平时的学习中一定要多钻研多了解一些深入的东西。 62 | 63 | #### 2.3、方向 64 | 65 | 这也是很多同学会疑惑的问题,感觉自己什么都好像挺喜欢,而岗位方向却很多(常见方向可以参见bat招聘方向),这里对于方向的选择我不发表看法(和个人兴趣强相关),但一定要尽早定下自己的方向,因为不论什么方向,你只要掌握的够深入、够好,找工作不成问题,就拿我身边的同学,进腾讯有做前端的、C++后台的、C++客户端的、Android的、IOS的等等,甚至有个牛人拿到了阿里的机器学习offer(这在本科生中之前都认为非常难),所以不论什么方向,一定要尽早确定下来。当然大一和大二的同学可能还没有必要,什么方向感兴趣就去学一学,充分试错,找到最适合自己的方向。大三的同学建议就认准自己的方向进行学习和准备,避免没有一个深入的方向,到时候招聘会比较麻烦,因为毕竟招聘对于广度的要求没有那么高。 66 | 67 | #### 2.4、面试经验 68 | 69 | 自从实习招聘以来,大大小小的面试可能有40+,对于面试也算积累了一定的经验,不一定适用于 每一个同学,大家可以当作一个参考。 70 | 71 | * **好的准备是第一步**:对于任何一场面试,一定要认真对待和准备,比如当时我们面试前都会把常见排序手写一遍(虽然我没被问到,只是举个例子),千万不要凭运气去面试,提前准备是成功的必要条件。 72 | 73 | * **面试过程中的表达能力很重要**:往往面试官一个问题你可能知道,但如何清晰的表述这个问题自己的看法是关键,尽量在大脑中简单构思一下(如果觉得尴尬就说自己太紧张了,思考一下),然后按点来说;另外对于原理性的问题,借助纸笔来表达也是一个好方法,这点在准备的时候也可以进行,对于一个知识点使用画图的方式来理解。我自己在面试中成功率较高的很大原因我觉得并不是实力,而是表达能力较强。 74 | 75 | * **保持自信和谦虚,不懂的问题实话实说**:面试不光是对你知识的考察,很多时候还会考察一些别的因素,比如思维能力、团队合作意识等等,所以尽量放开了说,保持自信,尽量多和面试官交流。比如面试官问一个问题你觉得比较模糊或者不太理解,就直接和面试官说明,多和面试官交流是没错的。另外如果问到一个确实不了解的问题,实话实说,切记不要在专业知识上撒谎,如果被面试官发现了会很不利,因为抛开专业知识不谈,诚信永远是企业选人的第一标准。 76 | 77 | * **保持良好的心态,学会接受失败**:最后这点是最重要的,在学习、招聘面试的过程中,一定要保持一个良好的心态。因为在面试的过程中,特别是一开始面试经验较少,紧张、焦虑、发挥失常等等情况是难免的,所以说面试不过是一个非常非常正常的现象,一定要能接受自己的失败,当你能够接受失败之后,慢慢的积累面试的技巧和经验,你会发现面试实际上并不难。我认识的人中,有面试了7次才进腾讯的(真人真事),有实习去美图没留下来反而秋招进了腾讯的,特别是大三下开始的那一年,你会经历准备、春招面试、实习、秋招面试等等一系列的事,你还要做很多选择,所以一定要能够保持一个良好的心态!当你坚持下来,你才会发现其实并不难。 78 | 79 | ### 3、最后 80 | 81 | 生活中我们都要经历太多失败、太多挫折,面试的过程中我也经历过迷茫和不安,希望大家能够相互帮助,相互支持,在招聘路上取得自己满意的结果!PS:具体面经和知识点请参见我其他的帖子。 82 | 83 | 本人也在一个大佬开一个学交流群里,群里大佬会重点介绍学习准备过程和一些面试技巧,很多小伙伴也在里面交流交流一下,大佬有时间也会解答大家的困惑,需要的同学可以参加一下。 84 | 地址如下: 【[2020金九银十(预热期)备战面试](https://jq.qq.com/?_wv=1027&k=VfOvO6bx)】当然群里也有大家一起更新搜集的资料 ,欢迎大家一起加入。 85 | ![](https://upload-images.jianshu.io/upload_images/23087078-28e0aa901d54ceb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 86 | 87 | 88 | -------------------------------------------------------------------------------- /Docs/Android开源库源码分析.md: -------------------------------------------------------------------------------- 1 | - [LeakCanary](#leakcanary) 2 | - [初始化注册](#%e5%88%9d%e5%a7%8b%e5%8c%96%e6%b3%a8%e5%86%8c) 3 | - [引用泄漏观察](#%e5%bc%95%e7%94%a8%e6%b3%84%e6%bc%8f%e8%a7%82%e5%af%9f) 4 | - [Dump Heap](#dump-heap) 5 | - [EventBus](#eventbus) 6 | - [自定义注解](#%e8%87%aa%e5%ae%9a%e4%b9%89%e6%b3%a8%e8%a7%a3) 7 | - [注册订阅者](#%e6%b3%a8%e5%86%8c%e8%ae%a2%e9%98%85%e8%80%85) 8 | - [发送事件](#%e5%8f%91%e9%80%81%e4%ba%8b%e4%bb%b6) 9 | # LeakCanary 10 | ![](http://ww1.sinaimg.cn/large/006dXScfly1fj22w7flt4j30z00mrtc0.jpg) 11 | 12 | ## 初始化注册 13 | 在清单文件中注册了一个 ContentProvider 用于在应用启动时初始化代码: 14 | 15 | ``leakcanary-leaksentry/*/AndroidManifest.xml`` 16 | ```xml 17 | ··· 18 | 19 | 23 | 24 | ··· 25 | ``` 26 | 27 | 在 LeakSentryInstaller 生命周期 ``onCreate()`` 方法中完成初始化步骤: 28 | 29 | ``LeakSentryInstaller.kt`` 30 | ```kotlin 31 | internal class LeakSentryInstaller : ContentProvider() { 32 | 33 | override fun onCreate(): Boolean { 34 | CanaryLog.logger = DefaultCanaryLog() 35 | val application = context!!.applicationContext as Application 36 | InternalLeakSentry.install(application) 37 | return true 38 | } 39 | ··· 40 | ``` 41 | 42 | 然后分别注册 Activity/Fragment 的监听: 43 | 44 | ``InternalLeakSentry.kt`` 45 | ```kotlin 46 | ··· 47 | fun install(application: Application) { 48 | CanaryLog.d("Installing LeakSentry") 49 | checkMainThread() 50 | if (this::application.isInitialized) { 51 | return 52 | } 53 | InternalLeakSentry.application = application 54 | 55 | val configProvider = { LeakSentry.config } 56 | ActivityDestroyWatcher.install( 57 | application, refWatcher, configProvider 58 | ) 59 | FragmentDestroyWatcher.install( 60 | application, refWatcher, configProvider 61 | ) 62 | listener.onLeakSentryInstalled(application) 63 | } 64 | ··· 65 | ``` 66 | ``ActivityDestroyWatcher.kt`` 67 | ```kotlin 68 | ··· 69 | fun install(application: Application,refWatcher: RefWatcher,configProvider: () -> Config 70 | ) { 71 | val activityDestroyWatcher = ActivityDestroyWatcher(refWatcher, configProvider) 72 | application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks) 73 | } 74 | } 75 | ··· 76 | ``` 77 | ``AndroidOFragmentDestroyWatcher.kt`` 78 | ```kotlin 79 | ··· 80 | override fun watchFragments(activity: Activity) { 81 | val fragmentManager = activity.fragmentManager 82 | fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true) 83 | } 84 | ··· 85 | ``` 86 | ``AndroidXFragmentDestroyWatcher.kt`` 87 | ```kotlin 88 | ··· 89 | override fun watchFragments(activity: Activity) { 90 | if (activity is FragmentActivity) { 91 | val supportFragmentManager = activity.supportFragmentManager 92 | supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true) 93 | } 94 | } 95 | ··· 96 | ``` 97 | 98 | ## 引用泄漏观察 99 | ``RefWatcher.kt`` 100 | ```kotlin 101 | ··· 102 | @Synchronized fun watch(watchedInstance: Any, name: String) { 103 | if (!isEnabled()) { 104 | return 105 | } 106 | removeWeaklyReachableInstances() 107 | val key = UUID.randomUUID().toString() 108 | val watchUptimeMillis = clock.uptimeMillis() 109 | val reference = KeyedWeakReference(watchedInstance, key, name, watchUptimeMillis, queue) 110 | CanaryLog.d( 111 | "Watching %s with key %s", 112 | ((if (watchedInstance is Class<*>) watchedInstance.toString() else "instance of ${watchedInstance.javaClass.name}") + if (name.isNotEmpty()) " named $name" else ""), key 113 | ) 114 | 115 | watchedInstances[key] = reference 116 | checkRetainedExecutor.execute { 117 | moveToRetained(key) 118 | } 119 | } 120 | 121 | @Synchronized private fun moveToRetained(key: String) { 122 | removeWeaklyReachableInstances() 123 | val retainedRef = watchedInstances[key] 124 | if (retainedRef != null) { 125 | retainedRef.retainedUptimeMillis = clock.uptimeMillis() 126 | onInstanceRetained() 127 | } 128 | } 129 | ··· 130 | ``` 131 | 132 | ``InternalLeakCanary.kt`` 133 | ```kotlin 134 | ··· 135 | override fun onReferenceRetained() { 136 | if (this::heapDumpTrigger.isInitialized) { 137 | heapDumpTrigger.onReferenceRetained() 138 | } 139 | } 140 | ··· 141 | ``` 142 | 143 | ## Dump Heap 144 | 发现泄漏之后,获取 Heamp Dump 相关文件: 145 | 146 | ``AndroidHeapDumper.kt`` 147 | ```kotlin 148 | ··· 149 | override fun dumpHeap(): File? { 150 | val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null 151 | ··· 152 | return try { 153 | Debug.dumpHprofData(heapDumpFile.absolutePath) 154 | if (heapDumpFile.length() == 0L) { 155 | CanaryLog.d("Dumped heap file is 0 byte length") 156 | null 157 | } else { 158 | heapDumpFile 159 | } 160 | } catch (e: Exception) { 161 | CanaryLog.d(e, "Could not dump heap") 162 | // Abort heap dump 163 | null 164 | } finally { 165 | cancelToast(toast) 166 | notificationManager.cancel(R.id.leak_canary_notification_dumping_heap) 167 | } 168 | } 169 | ··· 170 | ``` 171 | 172 | ``HeapDumpTrigger.kt`` 173 | ```kotlin 174 | ··· 175 | private fun checkRetainedInstances(reason: String) { 176 | ··· 177 | val heapDumpFile = heapDumper.dumpHeap() 178 | ··· 179 | lastDisplayedRetainedInstanceCount = 0 180 | refWatcher.removeInstancesWatchedBeforeHeapDump(heapDumpUptimeMillis) 181 | 182 | HeapAnalyzerService.runAnalysis(application, heapDumpFile) 183 | } 184 | ··· 185 | ``` 186 | 187 | 启动一个 HeapAnalyzerService 来分析 heapDumpFile: 188 | 189 | ``HeapAnalyzerService.kt`` 190 | ```kotlin 191 | ··· 192 | override fun onHandleIntentInForeground(intent: Intent?) { 193 | ··· 194 | val heapAnalyzer = HeapAnalyzer(this) 195 | val config = LeakCanary.config 196 | 197 | val heapAnalysis = 198 | heapAnalyzer.checkForLeaks( 199 | heapDumpFile, config.referenceMatchers, config.computeRetainedHeapSize, config.objectInspectors, 200 | if (config.useExperimentalLeakFinders) config.objectInspectors else listOf( 201 | AndroidObjectInspectors.KEYED_WEAK_REFERENCE 202 | ) 203 | ) 204 | 205 | config.analysisResultListener(application, heapAnalysis) 206 | } 207 | ··· 208 | ``` 209 | 210 | >Heap Dump 之后,可以查看以下内容: 211 | >- 应用分配了哪些类型的对象,以及每种对象的数量。 212 | >- 每个对象使用多少内存。 213 | >- 代码中保存对每个对象的引用。 214 | >- 分配对象的调用堆栈。(调用堆栈当前仅在使用Android 7.1及以下时有效。) 215 | 216 | # EventBus 217 | ## 自定义注解 218 | - 申明注解类 219 | ```java 220 | @Documented 221 | @Retention(RetentionPolicy.RUNTIME) 222 | @Target({ElementType.METHOD}) 223 | public @interface Subscribe { 224 | // 线程模式 225 | ThreadMode threadMode() default ThreadMode.POSTING; 226 | 227 | // 是否为粘性事件 228 | boolean sticky() default false; 229 | 230 | // 事件的优先级 231 | int priority() default 0; 232 | } 233 | ``` 234 | 235 | - 注册订阅事件 236 | ```java 237 | @Subscribe(threadMode = ThreadMode.MAIN, priority = 1, sticky = true) 238 | public void onEventMainThreadP1(IntTestEvent event) { 239 | handleEvent(1, event); 240 | } 241 | ``` 242 | 243 | ## 注册订阅者 244 | ```java 245 | EventBus.getDefault().register(object); 246 | ``` 247 | 248 | ``Eventbus.java`` 249 | ```java 250 | public void register(Object subscriber) { 251 | Class subscriberClass = subscriber.getClass(); 252 | List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); 253 | synchronized (this) { 254 | for (SubscriberMethod subscriberMethod : subscriberMethods) { 255 | subscribe(subscriber, subscriberMethod); 256 | } 257 | } 258 | } 259 | ``` 260 | 261 | - 通过反射查找订阅者类里的订阅事件,添加到 ``METHOD_CACHE`` 中 262 | 263 | ``SubscriberMethodFinder.java`` 264 | ```java 265 | private static final Map, List> METHOD_CACHE = new ConcurrentHashMap<>(); 266 | ··· 267 | 268 | List findSubscriberMethods(Class subscriberClass) { 269 | List subscriberMethods = METHOD_CACHE.get(subscriberClass); 270 | if (subscriberMethods != null) { 271 | return subscriberMethods; 272 | } 273 | 274 | if (ignoreGeneratedIndex) { 275 | subscriberMethods = findUsingReflection(subscriberClass); 276 | } else { 277 | subscriberMethods = findUsingInfo(subscriberClass); 278 | } 279 | if (subscriberMethods.isEmpty()) { 280 | throw new EventBusException("Subscriber " + subscriberClass 281 | + " and its super classes have no public methods with the @Subscribe annotation"); 282 | } else { 283 | METHOD_CACHE.put(subscriberClass, subscriberMethods); 284 | return subscriberMethods; 285 | } 286 | } 287 | ··· 288 | 289 | private void findUsingReflectionInSingleClass(FindState findState) { 290 | Method[] methods; 291 | try { 292 | // This is faster than getMethods, especially when subscribers are fat classes like Activities 293 | methods = findState.clazz.getDeclaredMethods(); 294 | } catch (Throwable th) { 295 | // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 296 | methods = findState.clazz.getMethods(); 297 | findState.skipSuperClasses = true; 298 | } 299 | for (Method method : methods) { 300 | int modifiers = method.getModifiers(); 301 | if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { 302 | Class[] parameterTypes = method.getParameterTypes(); 303 | if (parameterTypes.length == 1) { 304 | Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); 305 | if (subscribeAnnotation != null) { 306 | Class eventType = parameterTypes[0]; 307 | if (findState.checkAdd(method, eventType)) { 308 | ThreadMode threadMode = subscribeAnnotation.threadMode(); 309 | findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, 310 | subscribeAnnotation.priority(), subscribeAnnotation.sticky())); 311 | } 312 | } 313 | } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { 314 | String methodName = method.getDeclaringClass().getName() + "." + method.getName(); 315 | throw new EventBusException("@Subscribe method " + methodName + 316 | "must have exactly 1 parameter but has " + parameterTypes.length); 317 | } 318 | } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { 319 | String methodName = method.getDeclaringClass().getName() + "." + method.getName(); 320 | throw new EventBusException(methodName + 321 | " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); 322 | } 323 | } 324 | } 325 | ``` 326 | 327 | ## 发送事件 328 | ```java 329 | EventBus.getDefault().post(object); 330 | ``` 331 | 332 | - 根据事件类型获取到对应的订阅者信息 333 | 334 | ``Subscription.java`` 335 | ```java 336 | final class Subscription { 337 | final Object subscriber; 338 | final SubscriberMethod subscriberMethod; 339 | ··· 340 | } 341 | ``` 342 | 343 | ``EventBus.java`` 344 | ```java 345 | private final Map, CopyOnWriteArrayList> subscriptionsByEventType; 346 | ··· 347 | 348 | private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) { 349 | CopyOnWriteArrayList subscriptions; 350 | synchronized (this) { 351 | subscriptions = subscriptionsByEventType.get(eventClass); 352 | } 353 | if (subscriptions != null && !subscriptions.isEmpty()) { 354 | for (Subscription subscription : subscriptions) { 355 | postingState.event = event; 356 | postingState.subscription = subscription; 357 | boolean aborted = false; 358 | try { 359 | postToSubscription(subscription, event, postingState.isMainThread); 360 | aborted = postingState.canceled; 361 | } finally { 362 | postingState.event = null; 363 | postingState.subscription = null; 364 | postingState.canceled = false; 365 | } 366 | if (aborted) { 367 | break; 368 | } 369 | } 370 | return true; 371 | } 372 | return false; 373 | } 374 | ··· 375 | ``` 376 | 377 | - 根据注册已获得的 ``Method`` 对象调用相关注册方法 378 | 379 | ``EventBus.java`` 380 | 381 | ```java 382 | void invokeSubscriber(Subscription subscription, Object event) { 383 | try { 384 | subscription.subscriberMethod.method.invoke(subscription.subscriber, event); 385 | } catch (InvocationTargetException e) { 386 | handleSubscriberException(subscription, event, e.getCause()); 387 | } catch (IllegalAccessException e) { 388 | throw new IllegalStateException("Unexpected exception", e); 389 | } 390 | } 391 | ``` 392 | 393 | 395 | -------------------------------------------------------------------------------- /Docs/Android扩展知识点.md: -------------------------------------------------------------------------------- 1 | - [ART](#art) 2 | - [ART 功能](#art-%e5%8a%9f%e8%83%bd) 3 | - [预先 (AOT) 编译](#%e9%a2%84%e5%85%88-aot-%e7%bc%96%e8%af%91) 4 | - [垃圾回收优化](#%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e4%bc%98%e5%8c%96) 5 | - [开发和调试方面的优化](#%e5%bc%80%e5%8f%91%e5%92%8c%e8%b0%83%e8%af%95%e6%96%b9%e9%9d%a2%e7%9a%84%e4%bc%98%e5%8c%96) 6 | - [ART GC](#art-gc) 7 | - [Apk 包体优化](#apk-%e5%8c%85%e4%bd%93%e4%bc%98%e5%8c%96) 8 | - [Apk 组成结构](#apk-%e7%bb%84%e6%88%90%e7%bb%93%e6%9e%84) 9 | - [整体优化](#%e6%95%b4%e4%bd%93%e4%bc%98%e5%8c%96) 10 | - [资源优化](#%e8%b5%84%e6%ba%90%e4%bc%98%e5%8c%96) 11 | - [代码优化](#%e4%bb%a3%e7%a0%81%e4%bc%98%e5%8c%96) 12 | - [.arsc文件优化](#arsc%e6%96%87%e4%bb%b6%e4%bc%98%e5%8c%96) 13 | - [lib目录优化](#lib%e7%9b%ae%e5%bd%95%e4%bc%98%e5%8c%96) 14 | - [Hook](#hook) 15 | - [基本流程](#%e5%9f%ba%e6%9c%ac%e6%b5%81%e7%a8%8b) 16 | - [使用示例](#%e4%bd%bf%e7%94%a8%e7%a4%ba%e4%be%8b) 17 | - [Proguard](#proguard) 18 | - [公共模板](#%e5%85%ac%e5%85%b1%e6%a8%a1%e6%9d%bf) 19 | - [常用的自定义混淆规则](#%e5%b8%b8%e7%94%a8%e7%9a%84%e8%87%aa%e5%ae%9a%e4%b9%89%e6%b7%b7%e6%b7%86%e8%a7%84%e5%88%99) 20 | - [aar中增加独立的混淆配置](#aar%e4%b8%ad%e5%a2%9e%e5%8a%a0%e7%8b%ac%e7%ab%8b%e7%9a%84%e6%b7%b7%e6%b7%86%e9%85%8d%e7%bd%ae) 21 | - [检查混淆和追踪异常](#%e6%a3%80%e6%9f%a5%e6%b7%b7%e6%b7%86%e5%92%8c%e8%bf%bd%e8%b8%aa%e5%bc%82%e5%b8%b8) 22 | - [架构](#%e6%9e%b6%e6%9e%84) 23 | - [MVC](#mvc) 24 | - [MVP](#mvp) 25 | - [MVVM](#mvvm) 26 | - [Jetpack](#jetpack) 27 | - [架构](#%e6%9e%b6%e6%9e%84-1) 28 | - [使用示例](#%e4%bd%bf%e7%94%a8%e7%a4%ba%e4%be%8b-1) 29 | - [NDK 开发](#ndk-%e5%bc%80%e5%8f%91) 30 | - [JNI 基础](#jni-%e5%9f%ba%e7%a1%80) 31 | - [数据类型](#%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8b) 32 | - [String 字符串函数操作](#string-%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%87%bd%e6%95%b0%e6%93%8d%e4%bd%9c) 33 | - [常用 JNI 访问 Java 对象方法](#%e5%b8%b8%e7%94%a8-jni-%e8%ae%bf%e9%97%ae-java-%e5%af%b9%e8%b1%a1%e6%96%b9%e6%b3%95) 34 | - [NDK 开发](#ndk-%e5%bc%80%e5%8f%91-1) 35 | - [基础开发流程](#%e5%9f%ba%e7%a1%80%e5%bc%80%e5%8f%91%e6%b5%81%e7%a8%8b) 36 | - [System.loadLibrary()](#systemloadlibrary) 37 | - [CMake 构建 NDK 项目](#cmake-%e6%9e%84%e5%bb%ba-ndk-%e9%a1%b9%e7%9b%ae) 38 | - [常用的 Android NDK 原生 API](#%e5%b8%b8%e7%94%a8%e7%9a%84-android-ndk-%e5%8e%9f%e7%94%9f-api) 39 | - [类加载器](#%e7%b1%bb%e5%8a%a0%e8%bd%bd%e5%99%a8) 40 | - [双亲委托模式](#%e5%8f%8c%e4%ba%b2%e5%a7%94%e6%89%98%e6%a8%a1%e5%bc%8f) 41 | - [DexPathList](#dexpathlist) 42 | # ART 43 | ART 代表 Android Runtime,其处理应用程序执行的方式完全不同于 Dalvik,Dalvik 是依靠一个 Just-In-Time (JIT) 编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运 行。ART 则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫 Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。 44 | 45 | ## ART 功能 46 | ### 预先 (AOT) 编译 47 | ART 引入了预先编译机制,可提高应用的性能。ART 还具有比 Dalvik 更严格的安装时验证。在安装时,ART 使用设备自带的 dex2oat 工具来编译应用。该实用工具接受 DEX 文件作为输入,并为目标设备生成经过编译的应用可执行文件。该工具应能够顺利编译所有有效的 DEX 文件。 48 | 49 | ### 垃圾回收优化 50 | 垃圾回收 (GC) 可能有损于应用性能,从而导致显示不稳定、界面响应速度缓慢以及其他问题。ART 通过以下几种方式对垃圾回收做了优化: 51 | - 只有一次(而非两次)GC 暂停 52 | - 在 GC 保持暂停状态期间并行处理 53 | - 在清理最近分配的短时对象这种特殊情况中,回收器的总 GC 时间更短 54 | - 优化了垃圾回收的工效,能够更加及时地进行并行垃圾回收,这使得 GC_FOR_ALLOC 事件在典型用例中极为罕见 55 | - 压缩 GC 以减少后台内存使用和碎片 56 | 57 | ### 开发和调试方面的优化 58 | - 支持采样分析器 59 | 60 | 一直以来,开发者都使用 Traceview 工具(用于跟踪应用执行情况)作为分析器。虽然 Traceview 可提供有用的信息,但每次方法调用产生的开销会导致 Dalvik 分析结果出现偏差,而且使用该工具明显会影响运行时性能 61 | 62 | ART 添加了对没有这些限制的专用采样分析器的支持,因而可更准确地了解应用执行情况,而不会明显减慢速度。KitKat 版本为 Dalvik 的 Traceview 添加了采样支持。 63 | 64 | 65 | - 支持更多调试功能 66 | 67 | ART 支持许多新的调试选项,特别是与监控和垃圾回收相关的功能。例如,查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程;询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考;过滤特定实例的事件(如断点)等。 68 | 69 | - 优化了异常和崩溃报告中的诊断详细信息 70 | 71 | 当发生运行时异常时,ART 会为您提供尽可能多的上下文和详细信息。ART 会提供 ``java.lang.ClassCastException``、``java.lang.ClassNotFoundException`` 和 ``java.lang.NullPointerException`` 的更多异常详细信息(较高版本的 Dalvik 会提供 ``java.lang.ArrayIndexOutOfBoundsException`` 和 ``java.lang.ArrayStoreException`` 的更多异常详细信息,这些信息现在包括数组大小和越界偏移量;ART 也提供这类信息)。 72 | 73 | ## ART GC 74 | ART 有多个不同的 GC 方案,这些方案包括运行不同垃圾回收器。默认方案是 CMS(并发标记清除)方案,主要使用粘性 CMS 和部分 CMS。粘性 CMS 是 ART 的不移动分代垃圾回收器。它仅扫描堆中自上次 GC 后修改的部分,并且只能回收自上次 GC 后分配的对象。除 CMS 方案外,当应用将进程状态更改为察觉不到卡顿的进程状态(例如,后台或缓存)时,ART 将执行堆压缩。 75 | 76 | 除了新的垃圾回收器之外,ART 还引入了一种基于位图的新内存分配程序,称为 RosAlloc(插槽运行分配器)。此新分配器具有分片锁,当分配规模较小时可添加线程的本地缓冲区,因而性能优于 DlMalloc。 77 | 78 | 与 Dalvik 相比,ART CMS 垃圾回收计划在很多方面都有一定的改善: 79 | 80 | - 与 Dalvik 相比,暂停次数从 2 次减少到 1 次。Dalvik 的第一次暂停主要是为了进行根标记,即在 ART 中进行并发标记,让线程标记自己的根,然后马上恢复运行。 81 | 82 | - 与 Dalvik 类似,ART GC 在清除过程开始之前也会暂停 1 次。两者在这方面的主要差异在于:在此暂停期间,某些 Dalvik 环节在 ART 中并发进行。这些环节包括 java.lang.ref.Reference 处理、系统弱清除(例如,jni 弱全局等)、重新标记非线程根和卡片预清理。在 ART 暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根,这些操作有助于缩短暂停时间。 83 | 84 | - 相对于 Dalvik,ART GC 改进的最后一个方面是粘性 CMS 回收器增加了 GC 吞吐量。不同于普通的分代 GC,粘性 CMS 不移动。系统会将年轻对象保存在一个分配堆栈(基本上是 java.lang.Object 数组)中,而非为其设置一个专属区域。这样可以避免移动所需的对象以维持低暂停次数,但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长。 85 | 86 | ART GC 与 Dalvik 的另一个主要区别在于 ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用使用的内存。目前,触发堆压缩的事件是 ActivityManager 进程状态的改变。当应用转到后台运行时,它会通知 ART 已进入不再“感知”卡顿的进程状态。此时 ART 会进行一些操作(例如,压缩和监视器压缩),从而导致应用线程长时间暂停。目前正在使用的两个移动 GC 是同构空间压缩和半空间压缩。 87 | 88 | - 半空间压缩将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备,因为它可以比同构空间压缩稍微多节省一点内存。额外节省出的空间主要来自紧密排列的对象,这样可以避免 RosAlloc/DlMalloc 分配器占用开销。由于 CMS 仍在前台使用,且不能从碰撞指针空间中进行收集,因此当应用在前台使用时,半空间还要再进行一次转换。这种情况并不理想,因为它可能引起较长时间的暂停。 89 | 90 | - 同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩,同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。 91 | 92 | # Apk 包体优化 93 | ## Apk 组成结构 94 | | 文件/文件夹 | 作用/功能 95 | |--|-- 96 | | res | 包含所有没有被编译到 .arsc 里面的资源文件 97 | | lib | 引用库的文件夹 98 | | assets | assets文件夹相比于 res 文件夹,还有可能放字体文件、预置数据和web页面等,通过 AssetManager 访问 99 | | META_INF | 存放的是签名信息,用来保证 apk 包的完整性和系统的安全。在生成一个APK的时候,会对所有的打包文件做一个校验计算,并把结果放在该目录下面 100 | | classes.dex | 包含编译后的应用程序源码转化成的dex字节码。APK 里面,可能会存在多个 dex 文件 101 | | resources.arsc | 一些资源和标识符被编译和写入这个文件 102 | | Androidmanifest.xml | 编译时,应用程序的 AndroidManifest.xml 被转化成二进制格式 103 | 104 | ## 整体优化 105 | - 分离应用的独立模块,以插件的形式加载 106 | - 解压APK,重新用 7zip 进行压缩 107 | - 用 apksigner 签名工具 替代 java 提供的 jarsigner 签名工具 108 | 109 | ## 资源优化 110 | - 可以只用一套资源图片,一般采用 xhdpi 下的资源图片 111 | - 通过扫描文件的 MD5 值,找出名字不同,内容相同的图片并删除 112 | - 通过 Lint 工具扫描工程资源,移除无用资源 113 | - 通过 Gradle 参数配置 shrinkResources=true 114 | - 对 png 图片压缩 115 | - 图片资源考虑采用 WebP 格式 116 | - 避免使用帧动画,可使用 Lottie 动画库 117 | - 优先考虑能否用 shape 代码、.9 图、svg 矢量图、VectorDrawable 类来替换传统的图片 118 | 119 | ## 代码优化 120 | - 启用混淆以移除无用代码 121 | - 剔除 R 文件 122 | - 用注解替代枚举 123 | 124 | ## .arsc文件优化 125 | - 移除未使用的备用资源来优化 .arsc 文件 126 | ```groovy 127 | android { 128 | defaultConfig { 129 | ... 130 | resConfigs "zh", "zh_CN", "zh_HK", "en" 131 | } 132 | } 133 | ``` 134 | 135 | ## lib目录优化 136 | - 只提供对主流架构的支持,比如 arm,对于 mips 和 x86 架构可以考虑不提供支持 137 | ```groovy 138 | android { 139 | defaultConfig { 140 | ... 141 | ndk { 142 | abiFilters "armeabi-v7a" 143 | } 144 | } 145 | } 146 | ``` 147 | 148 | # Hook 149 | ## 基本流程 150 | 1、根据需求确定 要 hook 的对象 151 | 2、寻找要hook的对象的持有者,拿到要 hook 的对象 152 | 3、定义“要 hook 的对象”的代理类,并且创建该类的对象 153 | 4、使用上一步创建出来的对象,替换掉要 hook 的对象 154 | 155 | ## 使用示例 156 | ```java 157 | /** 158 | * hook的核心代码 159 | * 这个方法的唯一目的:用自己的点击事件,替换掉 View 原来的点击事件 160 | * 161 | * @param view hook的范围仅限于这个view 162 | */ 163 | @SuppressLint({"DiscouragedPrivateApi", "PrivateApi"}) 164 | public static void hook(Context context, final View view) {// 165 | try { 166 | // 反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者 167 | Method method = View.class.getDeclaredMethod("getListenerInfo"); 168 | method.setAccessible(true);//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限 169 | Object mListenerInfo = method.invoke(view);//这里拿到的就是mListenerInfo对象,也就是点击事件的持有者 170 | 171 | // 要从这里面拿到当前的点击事件对象 172 | Class listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 这是内部类的表示方法 173 | Field field = listenerInfoClz.getDeclaredField("mOnClickListener"); 174 | final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真实的mOnClickListener对象 175 | 176 | // 2. 创建我们自己的点击事件代理类 177 | // 方式1:自己创建代理类 178 | // ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance); 179 | // 方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式 180 | // Proxy.newProxyInstance的3个参数依次分别是: 181 | // 本地的类加载器; 182 | // 代理类的对象所继承的接口(用Class数组表示,支持多个接口) 183 | // 代理类的实际逻辑,封装在new出来的InvocationHandler内 184 | Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() { 185 | @Override 186 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 187 | Log.d("HookSetOnClickListener", "点击事件被hook到了");//加入自己的逻辑 188 | return method.invoke(onClickListenerInstance, args);//执行被代理的对象的逻辑 189 | } 190 | }); 191 | // 3. 用我们自己的点击事件代理类,设置到"持有者"中 192 | field.set(mListenerInfo, proxyOnClickListener); 193 | } catch (Exception e) { 194 | e.printStackTrace(); 195 | } 196 | } 197 | 198 | // 自定义代理类 199 | static class ProxyOnClickListener implements View.OnClickListener { 200 | View.OnClickListener oriLis; 201 | 202 | public ProxyOnClickListener(View.OnClickListener oriLis) { 203 | this.oriLis = oriLis; 204 | } 205 | 206 | @Override 207 | public void onClick(View v) { 208 | Log.d("HookSetOnClickListener", "点击事件被hook到了"); 209 | if (oriLis != null) { 210 | oriLis.onClick(v); 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | # Proguard 217 | Proguard 具有以下三个功能: 218 | - 压缩(Shrink): 检测和删除没有使用的类,字段,方法和特性 219 | - 优化(Optimize) : 分析和优化Java字节码 220 | - 混淆(Obfuscate): 使用简短的无意义的名称,对类,字段和方法进行重命名 221 | 222 | ## 公共模板 223 | ``` 224 | ############################################# 225 | # 226 | # 对于一些基本指令的添加 227 | # 228 | ############################################# 229 | # 代码混淆压缩比,在 0~7 之间,默认为 5,一般不做修改 230 | -optimizationpasses 5 231 | 232 | # 混合时不使用大小写混合,混合后的类名为小写 233 | -dontusemixedcaseclassnames 234 | 235 | # 指定不去忽略非公共库的类 236 | -dontskipnonpubliclibraryclasses 237 | 238 | # 这句话能够使我们的项目混淆后产生映射文件 239 | # 包含有类名->混淆后类名的映射关系 240 | -verbose 241 | 242 | # 指定不去忽略非公共库的类成员 243 | -dontskipnonpubliclibraryclassmembers 244 | 245 | # 不做预校验,preverify 是 proguard 的四个步骤之一,Android 不需要 preverify,去掉这一步能够加快混淆速度。 246 | -dontpreverify 247 | 248 | # 保留 Annotation 不混淆 249 | -keepattributes *Annotation*,InnerClasses 250 | 251 | # 避免混淆泛型 252 | -keepattributes Signature 253 | 254 | # 抛出异常时保留代码行号 255 | -keepattributes SourceFile,LineNumberTable 256 | 257 | # 指定混淆是采用的算法,后面的参数是一个过滤器 258 | # 这个过滤器是谷歌推荐的算法,一般不做更改 259 | -optimizations !code/simplification/cast,!field/*,!class/merging/* 260 | 261 | 262 | ############################################# 263 | # 264 | # Android开发中一些需要保留的公共部分 265 | # 266 | ############################################# 267 | 268 | # 保留我们使用的四大组件,自定义的 Application 等等这些类不被混淆 269 | # 因为这些子类都有可能被外部调用 270 | -keep public class * extends android.app.Activity 271 | -keep public class * extends android.app.Appliction 272 | -keep public class * extends android.app.Service 273 | -keep public class * extends android.content.BroadcastReceiver 274 | -keep public class * extends android.content.ContentProvider 275 | -keep public class * extends android.app.backup.BackupAgentHelper 276 | -keep public class * extends android.preference.Preference 277 | -keep public class * extends android.view.View 278 | -keep public class com.android.vending.licensing.ILicensingService 279 | 280 | 281 | # 保留 support 下的所有类及其内部类 282 | -keep class android.support.** { *; } 283 | 284 | # 保留继承的 285 | -keep public class * extends android.support.v4.** 286 | -keep public class * extends android.support.v7.** 287 | -keep public class * extends android.support.annotation.** 288 | 289 | # 保留 R 下面的资源 290 | -keep class **.R$* { *; } 291 | 292 | # 保留本地 native 方法不被混淆 293 | -keepclasseswithmembernames class * { 294 | native ; 295 | } 296 | 297 | # 保留在 Activity 中的方法参数是view的方法, 298 | # 这样以来我们在 layout 中写的 onClick 就不会被影响 299 | -keepclassmembers class * extends android.app.Activity { 300 | public void *(android.view.View); 301 | } 302 | 303 | # 保留枚举类不被混淆 304 | -keepclassmembers enum * { 305 | public static **[] values(); 306 | public static ** valueOf(java.lang.String); 307 | } 308 | 309 | # 保留我们自定义控件(继承自 View)不被混淆 310 | -keep public class * extends android.view.View { 311 | *** get*(); 312 | void set*(***); 313 | public (android.content.Context); 314 | public (android.content.Context, android.util.AttributeSet); 315 | public (android.content.Context, android.util.AttributeSet, int); 316 | } 317 | 318 | # 保留 Parcelable 序列化类不被混淆 319 | -keep class * implements android.os.Parcelable { 320 | public static final android.os.Parcelable$Creator *; 321 | } 322 | 323 | # 保留 Serializable 序列化的类不被混淆 324 | -keepnames class * implements java.io.Serializable 325 | -keepclassmembers class * implements java.io.Serializable { 326 | static final long serialVersionUID; 327 | private static final java.io.ObjectStreamField[] serialPersistentFields; 328 | !static !transient ; 329 | !private ; 330 | !private ; 331 | private void writeObject(java.io.ObjectOutputStream); 332 | private void readObject(java.io.ObjectInputStream); 333 | java.lang.Object writeReplace(); 334 | java.lang.Object readResolve(); 335 | } 336 | 337 | # 对于带有回调函数的 onXXEvent、**On*Listener 的,不能被混淆 338 | -keepclassmembers class * { 339 | void *(**On*Event); 340 | void *(**On*Listener); 341 | } 342 | 343 | # webView 处理,项目中没有使用到 webView 忽略即可 344 | -keepclassmembers class fqcn.of.javascript.interface.for.webview { 345 | public *; 346 | } 347 | -keepclassmembers class * extends android.webkit.webViewClient { 348 | public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); 349 | public boolean *(android.webkit.WebView, java.lang.String); 350 | } 351 | -keepclassmembers class * extends android.webkit.webViewClient { 352 | public void *(android.webkit.webView, java.lang.String); 353 | } 354 | 355 | # js 356 | -keepattributes JavascriptInterface 357 | -keep class android.webkit.JavascriptInterface { *; } 358 | -keepclassmembers class * { 359 | @android.webkit.JavascriptInterface ; 360 | } 361 | 362 | # @Keep 363 | -keep,allowobfuscation @interface android.support.annotation.Keep 364 | -keep @android.support.annotation.Keep class * 365 | -keepclassmembers class * { 366 | @android.support.annotation.Keep *; 367 | } 368 | ``` 369 | 370 | ## 常用的自定义混淆规则 371 | ```xml 372 | # 通配符*,匹配任意长度字符,但不含包名分隔符(.) 373 | # 通配符**,匹配任意长度字符,并且包含包名分隔符(.) 374 | 375 | # 不混淆某个类 376 | -keep public class com.jasonwu.demo.Test { *; } 377 | 378 | # 不混淆某个包所有的类 379 | -keep class com.jasonwu.demo.test.** { *; } 380 | 381 | # 不混淆某个类的子类 382 | -keep public class * com.jasonwu.demo.Test { *; } 383 | 384 | # 不混淆所有类名中包含了 ``model`` 的类及其成员 385 | -keep public class **.*model*.** {*;} 386 | 387 | # 不混淆某个接口的实现 388 | -keep class * implements com.jasonwu.demo.TestInterface { *; } 389 | 390 | # 不混淆某个类的构造方法 391 | -keepclassmembers class com.jasonwu.demo.Test { 392 | public (); 393 | } 394 | 395 | # 不混淆某个类的特定的方法 396 | -keepclassmembers class com.jasonwu.demo.Test { 397 | public void test(java.lang.String); 398 | } 399 | ``` 400 | 401 | 402 | ## aar中增加独立的混淆配置 403 | ``build.gralde`` 404 | ```gradle 405 | android { 406 | ··· 407 | defaultConfig { 408 | ··· 409 | consumerProguardFile 'proguard-rules.pro' 410 | } 411 | ··· 412 | } 413 | ``` 414 | 415 | ## 检查混淆和追踪异常 416 | 开启 Proguard 功能,则每次构建时 ProGuard 都会输出下列文件: 417 | 418 | - dump.txt 419 | 说明 APK 中所有类文件的内部结构。 420 | 421 | - mapping.txt 422 | 提供原始与混淆过的类、方法和字段名称之间的转换。 423 | 424 | - seeds.txt 425 | 列出未进行混淆的类和成员。 426 | 427 | - usage.txt 428 | 列出从 APK 移除的代码。 429 | 430 | 这些文件保存在 /build/outputs/mapping/release/ 中。我们可以查看 seeds.txt 里面是否是我们需要保留的,以及 usage.txt 里查看是否有误删除的代码。 mapping.txt 文件很重要,由于我们的部分代码是经过重命名的,如果该部分出现 bug,对应的异常堆栈信息里的类或成员也是经过重命名的,难以定位问题。我们可以用 retrace 脚本(在 Windows 上为 retrace.bat;在 Mac/Linux 上为 retrace.sh)。它位于 /tools/proguard/ 目录中。该脚本利用 mapping.txt 文件和你的异常堆栈文件生成没有经过混淆的异常堆栈文件,这样就可以看清是哪里出问题了。使用 retrace 工具的语法如下: 431 | 432 | ```shell 433 | retrace.bat|retrace.sh [-verbose] mapping.txt [] 434 | ``` 435 | 436 | # 架构 437 | ## MVC 438 | ![](https://mmbiz.qpic.cn/mmbiz_png/zKFJDM5V3Wy5xbLTp6JMMdouZiavFxyYCwLhGyLdicyLzgUDKFTZVt1OgU6iaSx2IUwnygzmQzW7Renaa8hmQ62cQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 439 | 440 | 在 Android 中,三者的关系如下: 441 | 442 | ![](https://mmbiz.qpic.cn/mmbiz_png/zKFJDM5V3Wy5xbLTp6JMMdouZiavFxyYCicNvEVMO9vDgukUR29Z1DCacZJwmmH1EEb7gUOZmDxolWexP01O8jfg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 443 | 444 | 由于在 Android 中 xml 布局的功能性太弱,所以 Activity 承担了绝大部分的工作,所以在 Android 中 mvc 更像: 445 | 446 | ![](https://mmbiz.qpic.cn/mmbiz_png/zKFJDM5V3Wy5xbLTp6JMMdouZiavFxyYCOq89MLQX4UM3dgBTQfU72desHb1XbOWRQZINnXOCCdZCuicUiaTHhtEg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 447 | 448 | 总结: 449 | - 具有一定的分层,model 解耦,controller 和 view 并没有解耦 450 | - controller 和 view 在 Android 中无法做到彻底分离,Controller 变得臃肿不堪 451 | - 易于理解、开发速度快、可维护性高 452 | 453 | ## MVP 454 | ![](https://mmbiz.qpic.cn/mmbiz_png/zKFJDM5V3Wy5xbLTp6JMMdouZiavFxyYCLVgibsuVQFguBI8FBdZibLNfpvbpd6njkdGWdyR2UL6TzMOhKHFqLC0Q/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 455 | 456 | 通过引入接口 BaseView,让相应的视图组件如 Activity,Fragment去实现 BaseView,把业务逻辑放在 presenter 层中,弱化 Model 只有跟 view 相关的操作都由 View 层去完成。 457 | 458 | 总结: 459 | - 彻底解决了 MVC 中 View 和 Controller 傻傻分不清楚的问题 460 | - 但是随着业务逻辑的增加,一个页面可能会非常复杂,UI 的改变是非常多,会有非常多的 case,这样就会造成 View 的接口会很庞大 461 | - 更容易单元测试 462 | 463 | ## MVVM 464 | ![](https://mmbiz.qpic.cn/mmbiz_png/zKFJDM5V3Wy5xbLTp6JMMdouZiavFxyYCMygIDD6xo5djkq6Y3jZo53sT2A4kKNaz8JEVRwmUnTmcAwJm0pZVWg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 465 | 466 | 在 MVP 中 View 和 Presenter 要相互持有,方便调用对方,而在 MVP 中 View 和 ViewModel 通过 Binding 进行关联,他们之前的关联处理通过 DataBinding 完成。 467 | 468 | 总结: 469 | - 很好的解决了 MVC 和 MVP 的问题 470 | - 视图状态较多,ViewModel 的构建和维护的成本都会比较高 471 | - 但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源 472 | 473 | # Jetpack 474 | ## 架构 475 | ![](https://developer.android.google.cn/topic/libraries/architecture/images/final-architecture.png) 476 | 477 | ## 使用示例 478 | ``build.gradle`` 479 | ```groovy 480 | android { 481 | ··· 482 | dataBinding { 483 | enabled = true 484 | } 485 | } 486 | dependencies { 487 | ··· 488 | implementation "androidx.fragment:fragment-ktx:$rootProject.fragmentVersion" 489 | implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleVersion" 490 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.lifecycleVersion" 491 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion" 492 | } 493 | ``` 494 | 495 | ``fragment_plant_detail.xml`` 496 | ```xml 497 | 500 | 501 | 502 | 505 | 506 | 507 | 510 | 511 | 514 | 515 | 516 | 517 | ``` 518 | 519 | 520 | ``PlantDetailFragment.kt`` 521 | ```kotlin 522 | class PlantDetailFragment : Fragment() { 523 | 524 | private val args: PlantDetailFragmentArgs by navArgs() 525 | private lateinit var shareText: String 526 | 527 | private val plantDetailViewModel: PlantDetailViewModel by viewModels { 528 | InjectorUtils.providePlantDetailViewModelFactory(requireActivity(), args.plantId) 529 | } 530 | 531 | override fun onCreateView( 532 | inflater: LayoutInflater, 533 | container: ViewGroup?, 534 | savedInstanceState: Bundle? 535 | ): View? { 536 | val binding = DataBindingUtil.inflate( 537 | inflater, R.layout.fragment_plant_detail, container, false).apply { 538 | viewModel = plantDetailViewModel 539 | lifecycleOwner = this@PlantDetailFragment 540 | } 541 | 542 | plantDetailViewModel.plant.observe(this) { plant -> 543 | // 更新相关 UI 544 | } 545 | 546 | return binding.root 547 | } 548 | } 549 | ``` 550 | 551 | ``Plant.kt`` 552 | ```kotlin 553 | data class Plant ( 554 | val name: String 555 | ) 556 | ``` 557 | 558 | ``PlantDetailViewModel.kt`` 559 | ```kotlin 560 | class PlantDetailViewModel( 561 | plantRepository: PlantRepository, 562 | private val plantId: String 563 | ) : ViewModel() { 564 | 565 | val plant: LiveData 566 | 567 | override fun onCleared() { 568 | super.onCleared() 569 | viewModelScope.cancel() 570 | } 571 | 572 | init { 573 | plant = plantRepository.getPlant(plantId) 574 | } 575 | } 576 | ``` 577 | 578 | ``PlantDetailViewModelFactory.kt`` 579 | ```kotlin 580 | class PlantDetailViewModelFactory( 581 | private val plantRepository: PlantRepository, 582 | private val plantId: String 583 | ) : ViewModelProvider.NewInstanceFactory() { 584 | 585 | @Suppress("UNCHECKED_CAST") 586 | override fun create(modelClass: Class): T { 587 | return PlantDetailViewModel(plantRepository, plantId) as T 588 | } 589 | } 590 | ``` 591 | 592 | ``InjectorUtils.kt`` 593 | ```kotlin 594 | object InjectorUtils { 595 | private fun getPlantRepository(context: Context): PlantRepository { 596 | ··· 597 | } 598 | 599 | fun providePlantDetailViewModelFactory( 600 | context: Context, 601 | plantId: String 602 | ): PlantDetailViewModelFactory { 603 | return PlantDetailViewModelFactory(getPlantRepository(context), plantId) 604 | } 605 | } 606 | ``` 607 | 608 | # NDK 开发 609 | > NDK 全称是 Native Development Kit,是一组可以让你在 Android 应用中编写实现 C/C++ 的工具,可以在项目用自己写源代码构建,也可以利用现有的预构建库。 610 | 611 | 使用 NDK 的使用目的有: 612 | - 从设备获取更好的性能以用于计算密集型应用,例如游戏或物理模拟 613 | - 重复使用自己或其他开发者的 C/C++ 库,便利于跨平台。 614 | - NDK 集成了譬如 OpenSL、Vulkan 等 API 规范的特定实现,以实现在 java 层无法做到的功能如提升音频性能等 615 | - 增加反编译难度 616 | 617 | ## JNI 基础 618 | ### 数据类型 619 | - 基本数据类型 620 | 621 | | Java 类型 | Native 类型 | 符号属性 | 字长 622 | |--|--|--|-- 623 | | boolean | jboolean | 无符号 | 8位 624 | | byte | jbyte | 无符号 | 8位 625 | | char | jchar | 无符号 | 16位 626 | | short | jshort | 有符号 | 16位 627 | | int | jnit | 有符号 | 32位 628 | | long | jlong | 有符号 | 64位 629 | | float | jfloat | 有符号 | 32位 630 | | double | jdouble | 有符号 | 64位 631 | 632 | - 引用数据类型 633 | 634 | | Java 引用类型 | Native 类型 | Java 引用类型 | Native 类型 635 | |--|--|--|-- 636 | | All objects | jobject | char[] | jcharArray 637 | | java.lang.Class | jclass | short[] | jshortArray 638 | | java.lang.String | jstring | int[] | jintArray 639 | | Object[] | jobjectArray | long[] | jlongArray 640 | | boolean[] | jbooleanArray | float[] | jfloatArray 641 | | byte[] | jbyteArray | double[] | jdoubleArray 642 | | java.lang.Throwable | jthrowable 643 | 644 | ### String 字符串函数操作 645 | | JNI 函数 | 描述 646 | |--|-- 647 | | GetStringChars / ReleaseStringChars | 获得或释放一个指向 Unicode 编码的字符串的指针(指 C/C++ 字符串) 648 | | GetStringUTFChars / ReleaseStringUTFChars | 获得或释放一个指向 UTF-8 编码的字符串的指针(指 C/C++ 字符串) 649 | | GetStringLength | 返回 Unicode 编码的字符串的长度 650 | | getStringUTFLength | 返回 UTF-8 编码的字符串的长度 651 | | NewString | 将 Unicode 编码的 C/C++ 字符串转换为 Java 字符串 652 | | NewStringUTF | 将 UTF-8 编码的 C/C++ 字符串转换为 Java 字符串 653 | | GetStringCritical / ReleaseStringCritical | 获得或释放一个指向字符串内容的指针(指 Java 字符串) 654 | | GetStringRegion | 获取或者设置 Unicode 编码的字符串的指定范围的内容 655 | | GetStringUTFRegion | 获取或者设置 UTF-8 编码的字符串的指定范围的内容 656 | 657 | ### 常用 JNI 访问 Java 对象方法 658 | ``MyJob.java`` 659 | ```java 660 | package com.example.myjniproject; 661 | 662 | public class MyJob { 663 | 664 | public static String JOB_STRING = "my_job"; 665 | private int jobId; 666 | 667 | public MyJob(int jobId) { 668 | this.jobId = jobId; 669 | } 670 | 671 | public int getJobId() { 672 | return jobId; 673 | } 674 | } 675 | ``` 676 | ``native-lib.cpp`` 677 | ```c++ 678 | #include 679 | 680 | extern "C" 681 | JNIEXPORT jint JNICALL 682 | Java_com_example_myjniproject_MainActivity_getJobId(JNIEnv *env, jobject thiz, jobject job) { 683 | 684 | // 根据实力获取 class 对象 685 | jclass jobClz = env->GetObjectClass(job); 686 | // 根据类名获取 class 对象 687 | jclass jobClz = env->FindClass("com/example/myjniproject/MyJob"); 688 | 689 | // 获取属性 id 690 | jfieldID fieldId = env->GetFieldID(jobClz, "jobId", "I"); 691 | // 获取静态属性 id 692 | jfieldID sFieldId = env->GetStaticFieldID(jobClz, "JOB_STRING", "Ljava/lang/String;"); 693 | 694 | // 获取方法 id 695 | jmethodID methodId = env->GetMethodID(jobClz, "getJobId", "()I"); 696 | // 获取构造方法 id 697 | jmethodID initMethodId = env->GetMethodID(jobClz, "", "(I)V"); 698 | 699 | // 根据对象属性 id 获取该属性值 700 | jint id = env->GetIntField(job, fieldId); 701 | // 根据对象方法 id 调用该方法 702 | jint id = env->CallIntMethod(job, methodId); 703 | 704 | // 创建新的对象 705 | jobject newJob = env->NewObject(jobClz, initMethodId, 10); 706 | 707 | return id; 708 | } 709 | ``` 710 | 711 | ## NDK 开发 712 | ### 基础开发流程 713 | - 在 java 中声明 native 方法 714 | ```java 715 | public class MainActivity extends AppCompatActivity { 716 | 717 | // Used to load the 'native-lib' library on application startup. 718 | static { 719 | System.loadLibrary("native-lib"); 720 | } 721 | 722 | @Override 723 | protected void onCreate(Bundle savedInstanceState) { 724 | super.onCreate(savedInstanceState); 725 | setContentView(R.layout.activity_main); 726 | 727 | Log.d("MainActivity", stringFromJNI()); 728 | } 729 | 730 | private native String stringFromJNI(); 731 | } 732 | ``` 733 | 734 | - 在 ``app/src/main`` 目录下新建 cpp 目录,新建相关 cpp 文件,实现相关方法(AS 可用快捷键快速生成) 735 | 736 | ``native-lib.cpp`` 737 | ``` 738 | #include 739 | 740 | extern "C" JNIEXPORT jstring JNICALL 741 | Java_com_example_myjniproject_MainActivity_stringFromJNI( 742 | JNIEnv *env, 743 | jobject /* this */) { 744 | std::string hello = "Hello from C++"; 745 | return env->NewStringUTF(hello.c_str()); 746 | } 747 | ``` 748 | 749 | >- 函数名的格式遵循遵循如下规则:Java_包名_类名_方法名。 750 | >- extern "C" 指定采用 C 语言的命名风格来编译,否则由于 C 与 C++ 风格不同,导致链接时无法找到具体的函数 751 | >- JNIEnv*:表示一个指向 JNI 环境的指针,可以通过他来访问 JNI 提供的接口方法 752 | >- jobject:表示 java 对象中的 this 753 | >- JNIEXPORT 和 JNICALL:JNI 所定义的宏,可以在 jni.h 头文件中查找到 754 | 755 | - 通过 CMake 或者 ndk-build 构建动态库 756 | 757 | ### System.loadLibrary() 758 | ``java/lang/System.java``: 759 | ```java 760 | @CallerSensitive 761 | public static void load(String filename) { 762 | Runtime.getRuntime().load0(Reflection.getCallerClass(), filename); 763 | } 764 | ``` 765 | 766 | - 调用 ``Runtime`` 相关 native 方法 767 | 768 | ``java/lang/Runtime.java``: 769 | ```java 770 | private static native String nativeLoad(String filename, ClassLoader loader, Class caller); 771 | ``` 772 | 773 | - native 方法的实现如下: 774 | 775 | ``dalvik/vm/native/java_lang_Runtime.cpp``: 776 | ```cpp 777 | static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, 778 | JValue* pResult) 779 | { 780 | ··· 781 | bool success; 782 | 783 | assert(fileNameObj != NULL); 784 | // 将 Java 的 library path String 转换到 native 的 String 785 | fileName = dvmCreateCstrFromString(fileNameObj); 786 | 787 | success = dvmLoadNativeCode(fileName, classLoader, &reason); 788 | if (!success) { 789 | const char* msg = (reason != NULL) ? reason : "unknown failure"; 790 | result = dvmCreateStringFromCstr(msg); 791 | dvmReleaseTrackedAlloc((Object*) result, NULL); 792 | } 793 | ··· 794 | } 795 | ``` 796 | 797 | - ``dvmLoadNativeCode`` 函数实现如下: 798 | 799 | ``dalvik/vm/Native.cpp`` 800 | ```cpp 801 | bool dvmLoadNativeCode(const char* pathName, Object* classLoader, 802 | char** detail) 803 | { 804 | SharedLib* pEntry; 805 | void* handle; 806 | ··· 807 | *detail = NULL; 808 | 809 | // 如果已经加载过了,则直接返回 true 810 | pEntry = findSharedLibEntry(pathName); 811 | if (pEntry != NULL) { 812 | if (pEntry->classLoader != classLoader) { 813 | ··· 814 | return false; 815 | } 816 | ··· 817 | if (!checkOnLoadResult(pEntry)) 818 | return false; 819 | return true; 820 | } 821 | 822 | Thread* self = dvmThreadSelf(); 823 | ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); 824 | // 把.so mmap 到进程空间,并把 func 等相关信息填充到 soinfo 中 825 | handle = dlopen(pathName, RTLD_LAZY); 826 | dvmChangeStatus(self, oldStatus); 827 | ··· 828 | // 创建一个新的 entry 829 | SharedLib* pNewEntry; 830 | pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib)); 831 | pNewEntry->pathName = strdup(pathName); 832 | pNewEntry->handle = handle; 833 | pNewEntry->classLoader = classLoader; 834 | dvmInitMutex(&pNewEntry->onLoadLock); 835 | pthread_cond_init(&pNewEntry->onLoadCond, NULL); 836 | pNewEntry->onLoadThreadId = self->threadId; 837 | 838 | // 尝试添加到列表中 839 | SharedLib* pActualEntry = addSharedLibEntry(pNewEntry); 840 | 841 | if (pNewEntry != pActualEntry) { 842 | ··· 843 | freeSharedLibEntry(pNewEntry); 844 | return checkOnLoadResult(pActualEntry); 845 | } else { 846 | ··· 847 | bool result = true; 848 | void* vonLoad; 849 | int version; 850 | // 调用该 so 库的 JNI_OnLoad 方法 851 | vonLoad = dlsym(handle, "JNI_OnLoad"); 852 | if (vonLoad == NULL) { 853 | ··· 854 | } else { 855 | // 调用 JNI_Onload 方法,重写类加载器。 856 | OnLoadFunc func = (OnLoadFunc)vonLoad; 857 | Object* prevOverride = self->classLoaderOverride; 858 | 859 | self->classLoaderOverride = classLoader; 860 | oldStatus = dvmChangeStatus(self, THREAD_NATIVE); 861 | ··· 862 | version = (*func)(gDvmJni.jniVm, NULL); 863 | dvmChangeStatus(self, oldStatus); 864 | self->classLoaderOverride = prevOverride; 865 | 866 | if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && 867 | version != JNI_VERSION_1_6) 868 | { 869 | ··· 870 | result = false; 871 | } else { 872 | ··· 873 | } 874 | } 875 | 876 | if (result) 877 | pNewEntry->onLoadResult = kOnLoadOkay; 878 | else 879 | pNewEntry->onLoadResult = kOnLoadFailed; 880 | 881 | pNewEntry->onLoadThreadId = 0; 882 | 883 | // 释放锁资源 884 | dvmLockMutex(&pNewEntry->onLoadLock); 885 | pthread_cond_broadcast(&pNewEntry->onLoadCond); 886 | dvmUnlockMutex(&pNewEntry->onLoadLock); 887 | return result; 888 | } 889 | } 890 | ``` 891 | 892 | 912 | 913 | ## CMake 构建 NDK 项目 914 | > CMake 是一个开源的跨平台工具系列,旨在构建,测试和打包软件,从 Android Studio 2.2 开始,Android Sudio 默认地使用 CMake 与 Gradle 搭配使用来构建原生库。 915 | 916 | 启动方式只需要在 ``app/build.gradle`` 中添加相关: 917 | ```groovy 918 | android { 919 | ··· 920 | defaultConfig { 921 | ··· 922 | externalNativeBuild { 923 | cmake { 924 | cppFlags "" 925 | } 926 | } 927 | 928 | ndk { 929 | abiFilters 'arm64-v8a', 'armeabi-v7a' 930 | } 931 | } 932 | ··· 933 | externalNativeBuild { 934 | cmake { 935 | path "CMakeLists.txt" 936 | } 937 | } 938 | } 939 | ``` 940 | 941 | 然后在对应目录新建一个 ``CMakeLists.txt`` 文件: 942 | ```txt 943 | # 定义了所需 CMake 的最低版本 944 | cmake_minimum_required(VERSION 3.4.1) 945 | 946 | # add_library() 命令用来添加库 947 | # native-lib 对应着生成的库的名字 948 | # SHARED 代表为分享库 949 | # src/main/cpp/native-lib.cpp 则是指明了源文件的路径。 950 | add_library( # Sets the name of the library. 951 | native-lib 952 | 953 | # Sets the library as a shared library. 954 | SHARED 955 | 956 | # Provides a relative path to your source file(s). 957 | src/main/cpp/native-lib.cpp) 958 | 959 | # find_library 命令添加到 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。 960 | # 可以使用此变量在构建脚本的其他部分引用 NDK 库 961 | find_library( # Sets the name of the path variable. 962 | log-lib 963 | 964 | # Specifies the name of the NDK library that 965 | # you want CMake to locate. 966 | log) 967 | 968 | # 预构建的 NDK 库已经存在于 Android 平台上,因此,无需再构建或将其打包到 APK 中。 969 | # 由于 NDK 库已经是 CMake 搜索路径的一部分,只需要向 CMake 提供希望使用的库的名称,并将其关联到自己的原生库中 970 | 971 | # 要将预构建库关联到自己的原生库 972 | target_link_libraries( # Specifies the target library. 973 | native-lib 974 | 975 | # Links the target library to the log library 976 | # included in the NDK. 977 | ${log-lib}) 978 | ··· 979 | ``` 980 | - [CMake 命令详细信息文档](https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html) 981 | 982 | ## 常用的 Android NDK 原生 API 983 | | 支持 NDK 的 API 级别 | 关键原生 API | 包括 984 | |--|--|-- 985 | | 3 | Java 原生接口 | #include 986 | | 3 | Android 日志记录 API | #include 987 | | 5 | OpenGL ES 2.0 | #include
#include 988 | | 8 | Android 位图 API | #include 989 | | 9 | OpenSL ES | #include
#include
#include
#include 990 | | 9 | 原生应用 API | #include
#include
#include
··· 991 | | 18 | OpenGL ES 3.0 | #include
#include 992 | | 21 | 原生媒体 API | #include
#include
··· 993 | | 24 | 原生相机 API | #include
#include
··· 994 | | ··· 995 | 996 | # 类加载器 997 | ![](https://img-blog.csdn.net/20161021101447117?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 998 | 999 | ## 双亲委托模式 1000 | 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。 1001 | 1002 | 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子 ClassLoader 再加载一次。如果不使用这种委托模式,那我们就可以随时使用自定义的类来动态替代一些核心的类,存在非常大的安全隐患。 1003 | 1004 | ## DexPathList 1005 | DexClassLoader 重载了 ``findClass`` 方法,在加载类时会调用其内部的 DexPathList 去加载。DexPathList 是在构造 DexClassLoader 时生成的,其内部包含了 DexFile。 1006 | 1007 | ``DexPathList.java`` 1008 | ```java 1009 | ··· 1010 | public Class findClass(String name) { 1011 | for (Element element : dexElements) { 1012 | DexFile dex = element.dexFile; 1013 | if (dex != null) { 1014 | Class clazz = dex.loadClassBinaryName(name, definingContext); 1015 | if (clazz != null) { 1016 | return clazz; 1017 | } 1018 | } 1019 | } 1020 | return null; 1021 | } 1022 | ··· 1023 | ``` 1024 | 1025 | -------------------------------------------------------------------------------- /Docs/Gradle知识点汇总.md: -------------------------------------------------------------------------------- 1 | # 依赖项配置 2 | | 配置 | 说明 3 | |--|-- 4 | | implementation | Gradle 会将依赖项添加到编译类路径,并将依赖项打包到编译输出。不过,当模块配置 implementation 依赖项时,其他模块只有在运行时才能使用该依赖项。 5 | | api | Gradle 会将依赖项添加到编译类路径和编译输出。当一个模块包含 api 依赖项时,会让 Gradle 了解该模块要以传递方式将该依赖项导出到其他模块,以便这些模块在运行时和编译时都可以使用该依赖项。 6 | | compileOnly | Gradle 只会将依赖项添加到编译类路径(也就是说,不会将其添加到编译输出)。 7 | | runtimeOnly | Gradle 只会将依赖项添加到编译输出,以便在运行时使用。也就是说,不会将其添加到编译类路径。 8 | | annotationProcessor | 要添加对作为注解处理器的库的依赖关系,必须使用 annotationProcessor 配置将其添加到注解处理器类路径。 | -------------------------------------------------------------------------------- /Docs/Java知识点汇总.md: -------------------------------------------------------------------------------- 1 | - [JVM](#jvm) 2 | - [JVM 工作流程](#jvm-%e5%b7%a5%e4%bd%9c%e6%b5%81%e7%a8%8b) 3 | - [运行时数据区(Runtime Data Area)](#%e8%bf%90%e8%a1%8c%e6%97%b6%e6%95%b0%e6%8d%ae%e5%8c%baruntime-data-area) 4 | - [方法指令](#%e6%96%b9%e6%b3%95%e6%8c%87%e4%bb%a4) 5 | - [类加载器](#%e7%b1%bb%e5%8a%a0%e8%bd%bd%e5%99%a8) 6 | - [垃圾回收 gc](#%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6-gc) 7 | - [对象存活判断](#%e5%af%b9%e8%b1%a1%e5%ad%98%e6%b4%bb%e5%88%a4%e6%96%ad) 8 | - [垃圾收集算法](#%e5%9e%83%e5%9c%be%e6%94%b6%e9%9b%86%e7%ae%97%e6%b3%95) 9 | - [垃圾收集器](#%e5%9e%83%e5%9c%be%e6%94%b6%e9%9b%86%e5%99%a8) 10 | - [内存模型与回收策略](#%e5%86%85%e5%ad%98%e6%a8%a1%e5%9e%8b%e4%b8%8e%e5%9b%9e%e6%94%b6%e7%ad%96%e7%95%a5) 11 | - [Object](#object) 12 | - [equals 方法](#equals-%e6%96%b9%e6%b3%95) 13 | - [hashCode 方法](#hashcode-%e6%96%b9%e6%b3%95) 14 | - [static](#static) 15 | - [final](#final) 16 | - [String、StringBuffer、StringBuilder](#stringstringbufferstringbuilder) 17 | - [异常处理](#%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86) 18 | - [内部类](#%e5%86%85%e9%83%a8%e7%b1%bb) 19 | - [匿名内部类](#%e5%8c%bf%e5%90%8d%e5%86%85%e9%83%a8%e7%b1%bb) 20 | - [多态](#%e5%a4%9a%e6%80%81) 21 | - [抽象和接口](#%e6%8a%bd%e8%b1%a1%e5%92%8c%e6%8e%a5%e5%8f%a3) 22 | - [集合框架](#%e9%9b%86%e5%90%88%e6%a1%86%e6%9e%b6) 23 | - [HashMap](#hashmap) 24 | - [结构图](#%e7%bb%93%e6%9e%84%e5%9b%be) 25 | - [HashMap 的工作原理](#hashmap-%e7%9a%84%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86) 26 | - [HashMap 与 HashTable 对比](#hashmap-%e4%b8%8e-hashtable-%e5%af%b9%e6%af%94) 27 | - [ConcurrentHashMap](#concurrenthashmap) 28 | - [Base 1.7](#base-17) 29 | - [Base 1.8](#base-18) 30 | - [ArrayList](#arraylist) 31 | - [LinkedList](#linkedlist) 32 | - [CopyOnWriteArrayList](#copyonwritearraylist) 33 | - [反射](#%e5%8f%8d%e5%b0%84) 34 | - [单例](#%e5%8d%95%e4%be%8b) 35 | - [饿汉式](#%e9%a5%bf%e6%b1%89%e5%bc%8f) 36 | - [双重检查模式](#%e5%8f%8c%e9%87%8d%e6%a3%80%e6%9f%a5%e6%a8%a1%e5%bc%8f) 37 | - [静态内部类模式](#%e9%9d%99%e6%80%81%e5%86%85%e9%83%a8%e7%b1%bb%e6%a8%a1%e5%bc%8f) 38 | - [线程](#%e7%ba%bf%e7%a8%8b) 39 | - [属性](#%e5%b1%9e%e6%80%a7) 40 | - [状态](#%e7%8a%b6%e6%80%81) 41 | - [状态控制](#%e7%8a%b6%e6%80%81%e6%8e%a7%e5%88%b6) 42 | - [volatile](#volatile) 43 | - [synchronized](#synchronized) 44 | - [根据获取的锁分类](#%e6%a0%b9%e6%8d%ae%e8%8e%b7%e5%8f%96%e7%9a%84%e9%94%81%e5%88%86%e7%b1%bb) 45 | - [原理](#%e5%8e%9f%e7%90%86) 46 | - [Lock](#lock) 47 | - [锁的分类](#%e9%94%81%e7%9a%84%e5%88%86%e7%b1%bb) 48 | - [悲观锁、乐观锁](#%e6%82%b2%e8%a7%82%e9%94%81%e4%b9%90%e8%a7%82%e9%94%81) 49 | - [自旋锁、适应性自旋锁](#%e8%87%aa%e6%97%8b%e9%94%81%e9%80%82%e5%ba%94%e6%80%a7%e8%87%aa%e6%97%8b%e9%94%81) 50 | - [死锁](#%e6%ad%bb%e9%94%81) 51 | - [引用类型](#%e5%bc%95%e7%94%a8%e7%b1%bb%e5%9e%8b) 52 | - [动态代理](#%e5%8a%a8%e6%80%81%e4%bb%a3%e7%90%86) 53 | - [元注解](#%e5%85%83%e6%b3%a8%e8%a7%a3) 54 | # JVM 55 | ## JVM 工作流程 56 | ![](https://user-gold-cdn.xitu.io/2019/6/23/16b833f4a4906226?w=448&h=592&f=jpeg&s=44057) 57 | 58 | ## 运行时数据区(Runtime Data Area) 59 | ![](https://user-gold-cdn.xitu.io/2019/6/23/16b833f4a499f6fe?w=868&h=497&f=webp&s=46378) 60 | 61 | | 区域 | 说明 62 | |----------|-----| 63 | | 程序计数器 | 每条线程都需要有一个程序计数器,计数器记录的是正在执行的指令地址,如果正在执行的是Natvie 方法,这个计数器值为空(Undefined) | 64 | | java虚拟机栈 | Java 方法执行的内存模型,每个方法执行的时候,都会创建一个栈帧用于保存局部变量表,操作数栈,动态链接,方法出口信息等。一个方法调用的过程就是一个栈帧从VM栈入栈到出栈的过程 | 65 | | 本地方法栈 | 与 VM 栈发挥的作用非常相似,VM 栈执行 Java 方法(字节码)服务,Native 方法栈执行的是 Native 方法服务。| 66 | | Java堆 | 此内存区域唯一的目的就是存放对象实例,几乎所有的对象都在这分配内存 | 67 | | 方法区 | 方法区是各个内存所共享的内存空间,方法区中主要存放被 JVM 加载的类信息、常量、静态变量、即时编译后的代码等数据 | 68 | 69 | ## 方法指令 70 | | 指令 | 说明 71 | |----------|-----| 72 | | invokeinterface | 用以调用接口方法 | 73 | | invokevirtual | 指令用于调用对象的实例方法 | 74 | | invokestatic | 用以调用类/静态方法 | 75 | | invokespecial | 用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法 | 76 | 77 | ## 类加载器 78 | | 类加载器 | 说明 79 | |----------|-----| 80 | | BootstrapClassLoader | Bootstrap 类加载器负责加载 rt.jar 中的 JDK 类文件,它是所有类加载器的父加载器。Bootstrap 类加载器没有任何父类加载器,如果你调用 String.class.getClassLoader(),会返回 null,任何基于此的代码会抛出 NUllPointerException 异常。Bootstrap 加载器被称为初始类加载器 | 81 | | ExtClassLoader | 而 Extension 将加载类的请求先委托给它的父加载器,也就是Bootstrap,如果没有成功加载的话,再从 jre/lib/ext 目录下或者 java.ext.dirs 系统属性定义的目录下加载类。Extension 加载器由 sun.misc.Launcher$ExtClassLoader 实现 | 82 | | AppClassLoader | 第三种默认的加载器就是 System 类加载器(又叫作 Application 类加载器)了。它负责从 classpath 环境变量中加载某些应用相关的类,classpath 环境变量通常由 -classpath 或 -cp 命令行选项来定义,或者是 JAR 中的 Manifest 的 classpath 属性。Application 类加载器是 Extension 类加载器的子加载器 | 83 | 84 | | 工作原理 | 说明 85 | |----------|------| 86 | | 委托机制 | 加载任务委托交给父类加载器,如果不行就向下传递委托任务,由其子类加载器加载,保证 java 核心库的安全性 | 87 | | 可见性机制 | 子类加载器可以看到父类加载器加载的类,而反之则不行 | 88 | | 单一性机制 | 父加载器加载过的类不能被子加载器加载第二次 | 89 | 90 | ## 垃圾回收 gc 91 | ### 对象存活判断 92 | - **引用计数** 93 | 94 | 每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。 95 | 96 | - **可达性分析** 97 | 98 | 从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。不可达对象。 99 | 100 | > 在Java语言中,GC Roots包括: 101 | > - 虚拟机栈中引用的对象。 102 | > - 方法区中类静态属性实体引用的对象。 103 | > - 方法区中常量引用的对象。 104 | > - 本地方法栈中 JNI 引用的对象。 105 | 106 | ### 垃圾收集算法 107 | - **标记 -清除算法** 108 | 109 | “标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。 110 | 111 | 它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 112 | 113 | - **复制算法** 114 | 115 | “复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。 116 | 117 | 这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。 118 | 119 | - **标记-整理算法** 120 | 121 | 复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。 122 | 123 | 根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 124 | 125 | - **分代收集算法** 126 | 127 | GC 分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。 128 | 129 | “分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。 130 | 131 | ### 垃圾收集器 132 | - **CMS收集器** 133 | 134 | > CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。 135 | 136 | 从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括: 137 | 138 | - 初始标记(CMS initial mark) 139 | - 并发标记(CMS concurrent mark) 140 | - 重新标记(CMS remark) 141 | - 并发清除(CMS concurrent sweep) 142 | 143 | 其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。 144 | 145 | 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收集器(新生代使用ParNew) 146 | 147 | - **G1收集器** 148 | 149 | 与CMS收集器相比G1收集器有以下特点: 150 | 151 | 1、空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。 152 | 153 | 2、可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。 154 | 155 | 使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region 的集合。 156 | 157 | G1的新生代收集跟 ParNew 类似,当新生代占用达到一定比例的时候,开始出发收集。和 CMS 类似,G1 收集器收集老年代对象会有短暂停顿。 158 | 159 | ### 内存模型与回收策略 160 | ![](https://mmbiz.qpic.cn/mmbiz_png/qdzZBE73hWsbhfAng9ibqfcbjrqgyRWqAKiaJ2U75SGYwQhs2tuNbXtu8KIpaUsBOaHRKXf7esuuFoMjELFxibIVg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 161 | 162 | Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,Java 堆主要分为2个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2个区。 163 | 164 | - **Eden 区** 165 | 166 | 大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。 167 | 通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。 168 | 169 | - **Survivor 区** 170 | 171 | Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。 172 | 173 | - **Old 区** 174 | 175 | 老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记——整理算法。 176 | 177 | 178 | # Object 179 | ## equals 方法 180 | 对两个对象的地址值进行的比较(即比较引用是否相同) 181 | ```java 182 | public boolean equals(Object obj) { 183 | return (this == obj); 184 | } 185 | ``` 186 | 187 | ## hashCode 方法 188 | hashCode() 方法给对象返回一个 hash code 值。这个方法被用于 hash tables,例如 HashMap。 189 | 190 | 它的性质是: 191 | - 在一个Java应用的执行期间,如果一个对象提供给 equals 做比较的信息没有被修改的话,该对象多次调用 hashCode() 方法,该方法必须始终如一返回同一个 integer。 192 | 193 | - 如果两个对象根据 equals(Object) 方法是相等的,那么调用二者各自的 hashCode() 方法必须产生同一个 integer 结果。 194 | 195 | - 并不要求根据 equals(Object) 方法不相等的两个对象,调用二者各自的 hashCode() 方法必须产生不同的 integer 结果。然而,程序员应该意识到对于不同的对象产生不同的 integer 结果,有可能会提高 hash table 的性能。 196 | 197 | 在 JDK 中,Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。在 String 类,重写了 hashCode 方法 198 | ```java 199 | public int hashCode() { 200 | int h = hash; 201 | if (h == 0 && value.length > 0) { 202 | char val[] = value; 203 | 204 | for (int i = 0; i < value.length; i++) { 205 | h = 31 * h + val[i]; 206 | } 207 | hash = h; 208 | } 209 | return h; 210 | } 211 | ``` 212 | 213 | # static 214 | - static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。 215 | - 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。 216 | - 能通过 this 访问静态成员变量吗? 217 | 所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。 218 | - static是不允许用来修饰局部变量 219 | 220 | # final 221 | - 可以声明成员变量、方法、类以及本地变量 222 | - final 成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误 223 | - final 变量是只读的 224 | - final 申明的方法不可以被子类的方法重写 225 | - final 类通常功能是完整的,不能被继承 226 | - final 变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销 227 | - final 关键字提高了性能,JVM 和 Java 应用都会缓存 final 变量,会对方法、变量及类进行优化 228 | - 方法的内部类访问方法中的局部变量,但必须用 final 修饰才能访问 229 | 230 | # String、StringBuffer、StringBuilder 231 | - String 是 final 类,不能被继承。对于已经存在的 Stirng 对象,修改它的值,就是重新创建一个对象 232 | - StringBuffer 是一个类似于 String 的字符串缓冲区,使用 append() 方法修改 Stringbuffer 的值,使用 toString() 方法转换为字符串,是线程安全的 233 | - StringBuilder 用来替代于 StringBuffer,StringBuilder 是非线程安全的,速度更快 234 | 235 | # 异常处理 236 | - Exception、Error 是 Throwable 类的子类 237 | - Error 类对象由 Java 虚拟机生成并抛出,不可捕捉 238 | - 不管有没有异常,finally 中的代码都会执行 239 | - 当 try、catch 中有 return 时,finally 中的代码依然会继续执行 240 | 241 | | 常见的Error | | | 242 | |------|-----|-----| 243 | | OutOfMemoryError | StackOverflowError | NoClassDeffoundError | 244 | 245 | | 常见的Exception | | | 246 | |------|-----|-----| 247 | | 常见的非检查性异常 | | 248 | | ArithmeticException | ArrayIndexOutOfBoundsException | ClassCastException | 249 | | IllegalArgumentException | IndexOutOfBoundsException | NullPointerException | 250 | | NumberFormatException | SecurityException | UnsupportedOperationException | 251 | | 常见的检查性异常 | | 252 | | IOException | CloneNotSupportedException | IllegalAccessException | 253 | | NoSuchFieldException | NoSuchMethodException | FileNotFoundException 254 | 255 | # 内部类 256 | - 非静态内部类没法在外部类的静态方法中实例化。 257 | - 非静态内部类的方法可以直接访问外部类的所有数据,包括私有的数据。 258 | - 在静态内部类中调用外部类成员,成员也要求用 static 修饰。 259 | - 创建静态内部类的对象可以直接通过外部类调用静态内部类的构造器;创建非静态的内部类的对象必须先创建外部类的对象,通过外部类的对象调用内部类的构造器。 260 | 261 | ## 匿名内部类 262 | - 匿名内部类不能定义任何静态成员、方法 263 | - 匿名内部类中的方法不能是抽象的 264 | - 匿名内部类必须实现接口或抽象父类的所有抽象方法 265 | - 匿名内部类不能定义构造器 266 | - 匿名内部类访问的外部类成员变量或成员方法必须用 final 修饰 267 | 268 | # 多态 269 | - 父类的引用可以指向子类的对象 270 | - 创建子类对象时,调用的方法为子类重写的方法或者继承的方法 271 | - 如果我们在子类中编写一个独有的方法,此时就不能通过父类的引用创建的子类对象来调用该方法 272 | 273 | # 抽象和接口 274 | - 抽象类不能有对象(不能用 new 关键字来创建抽象类的对象) 275 | - 抽象类中的抽象方法必须在子类中被重写 276 | - 接口中的所有属性默认为:public static final ****; 277 | - 接口中的所有方法默认为:public abstract ****; 278 | 279 | # 集合框架 280 | ![](https://user-gold-cdn.xitu.io/2019/6/23/16b833f4a86db5e6?w=643&h=611&f=gif&s=22445) 281 | - List接口存储一组不唯一,有序(插入顺序)的对象, Set接口存储一组唯一,无序的对象。 282 | 283 | ## HashMap 284 | ### 结构图 285 | - **JDK 1.7 HashMap 结构图** 286 | ![](https://user-gold-cdn.xitu.io/2019/6/23/16b833f4ac8f44fd?w=1636&h=742&f=png&s=88323) 287 | 288 | - **JDK 1.8 HashMap 结构图** 289 | ![](https://user-gold-cdn.xitu.io/2018/7/23/164c47f32f9650ba?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 290 | 291 | ### HashMap 的工作原理 292 | HashMap 基于 hashing 原理,我们通过 put() 和 get() 方法储存和获取对象。当我们将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashcode,让后找到 bucket 位置来储存 Entry 对象。当两个对象的 hashcode 相同时,它们的 bucket 位置相同,‘碰撞’会发生。因为 HashMap 使用链表存储对象,这个 Entry 会存储在链表中,当获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。 293 | 294 | **如果 HashMap 的大小超过了负载因子(load factor)定义的容量,怎么办?** 295 | 默认的负载因子大小为 0.75,也就是说,当一个 map 填满了 75% 的 bucket 时候,和其它集合类(如 ArrayList 等)一样,将会创建原来 HashMap 大小的两倍的 bucket 数组,来重新调整 map 的大小,并将原来的对象放入新的 bucket 数组中。这个过程叫作 rehashing,因为它调用 hash 方法找到新的 bucket 位置。 296 | 297 | **为什么 String, Interger 这样的 wrapper 类适合作为键?** 298 | 因为 String 是不可变的,也是 final 的,而且已经重写了 equals() 和 hashCode() 方法了。其他的 wrapper 类也有这个特点。不可变性是必要的,因为为了要计算 hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的 hashcode 的话,那么就不能从 HashMap 中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个 field 声明成 final 就能保证 hashCode 是不变的,那么请这么做吧。因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的 hashcode 的话,那么碰撞的几率就会小些,这样就能提高 HashMap 的性能。 299 | 300 | ### HashMap 与 HashTable 对比 301 | HashMap 是非 synchronized 的,性能更好,HashMap 可以接受为 null 的 key-value,而 Hashtable 是线程安全的,比 HashMap 要慢,不接受 null 的 key-value。 302 | 303 | ``HashMap.java`` 304 | ```java 305 | public class HashMap extends AbstractMap 306 | implements Map, Cloneable, Serializable { 307 | ··· 308 | 309 | public V put(K key, V value) { 310 | return putVal(hash(key), key, value, false, true); 311 | } 312 | ··· 313 | 314 | public V get(Object key) { 315 | Node e; 316 | return (e = getNode(hash(key), key)) == null ? null : e.value; 317 | } 318 | ··· 319 | } 320 | ``` 321 | 322 | ``HashTable.java`` 323 | ```java 324 | public class Hashtable 325 | extends Dictionary 326 | implements Map, Cloneable, java.io.Serializable { 327 | ··· 328 | 329 | public synchronized V put(K key, V value) { 330 | // Make sure the value is not null 331 | if (value == null) { 332 | throw new NullPointerException(); 333 | } 334 | 335 | ··· 336 | addEntry(hash, key, value, index); 337 | return null; 338 | } 339 | ··· 340 | 341 | public synchronized V get(Object key) { 342 | HashtableEntry tab[] = table; 343 | int hash = key.hashCode(); 344 | int index = (hash & 0x7FFFFFFF) % tab.length; 345 | for (HashtableEntry e = tab[index] ; e != null ; e = e.next) { 346 | if ((e.hash == hash) && e.key.equals(key)) { 347 | return (V)e.value; 348 | } 349 | } 350 | return null; 351 | } 352 | ··· 353 | } 354 | ``` 355 | 356 | ## ConcurrentHashMap 357 | ### Base 1.7 358 | 359 | ConcurrentHashMap 最外层不是一个大的数组,而是一个 Segment 的数组。每个 Segment 包含一个与 HashMap 数据结构差不多的链表数组。 360 | 361 | ![](http://www.jasongj.com/img/java/concurrenthashmap/concurrenthashmap_java7.png) 362 | 363 | 在读写某个 Key 时,先取该 Key 的哈希值。并将哈希值的高 N 位对 Segment 个数取模从而得到该 Key 应该属于哪个Segment,接着如同操作 HashMap 一样操作这个 Segment。 364 | 365 | Segment 继承自 ReentrantLock,可以很方便的对每一个 Segmen 上锁。 366 | 367 | 对于读操作,获取 Key 所在的 Segment 时,需要保证可见性。具体实现上可以使用volatile关键字,也可使用锁。但使用锁开销太大,而使用volatile时每次写操作都会让所有CPU内缓存无效,也有一定开销。ConcurrentHashMap 使用如下方法保证可见性,取得最新的Segment: 368 | ```java 369 | Segment s = (Segment)UNSAFE.getObjectVolatile(segments, u) 370 | ``` 371 | 372 | 获取 Segment 中的 HashEntry 时也使用了类似方法: 373 | ```java 374 | HashEntry e = (HashEntry) UNSAFE.getObjectVolatile 375 | (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE) 376 | ``` 377 | 378 | 对于写操作,并不要求同时获取所有 Segment 的锁,因为那样相当于锁住了整个Map。它会先获取该 Key-Value 对所在的 Segment 的锁,获取成功后就可以像操作一个普通的 HashMap 一样操作该 Segment,并保证该 Segment 的安全性。同时由于其它 Segment 的锁并未被获取,因此理论上可支持 concurrencyLevel(等于Segment的个数)个线程安全的并发读写。 379 | 380 | 获取锁时,并不直接使用 lock 来获取,因为该方法获取锁失败时会挂起。事实上,它使用了自旋锁,如果 tryLock 获取锁失败,说明锁被其它线程占用,此时通过循环再次以 tryLock 的方式申请锁。如果在循环过程中该 Key 所对应的链表头被修改,则重置 retry 次数。如果 retry 次数超过一定值,则使用 lock 方法申请锁。 381 | 382 | 这里使用自旋锁是因为自旋锁的效率比较高,但是它消耗 CPU 资源比较多,因此在自旋次数超过阈值时切换为互斥锁。 383 | 384 | ### Base 1.8 385 | 1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存在 HashMap 在 1.7 版本中的问题:查询遍历链表效率太低。因此 1.8 做了一些数据结构上的调整。 386 | 387 | ![](https://user-gold-cdn.xitu.io/2018/7/23/164c47f3756eb206?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 388 | 389 | 其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。 390 | 391 | ``ConcurrentHashMap.java`` 392 | ```java 393 | final V putVal(K key, V value, boolean onlyIfAbsent) { 394 | if (key == null || value == null) throw new NullPointerException(); 395 | int hash = spread(key.hashCode()); 396 | int binCount = 0; 397 | for (Node[] tab = table;;) { 398 | Node f; int n, i, fh; 399 | if (tab == null || (n = tab.length) == 0) 400 | tab = initTable(); 401 | else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 402 | if (casTabAt(tab, i, null, 403 | new Node(hash, key, value, null))) 404 | break; // no lock when adding to empty bin 405 | } 406 | else if ((fh = f.hash) == MOVED) 407 | tab = helpTransfer(tab, f); 408 | else { 409 | V oldVal = null; 410 | synchronized (f) { 411 | if (tabAt(tab, i) == f) { 412 | if (fh >= 0) { 413 | binCount = 1; 414 | ··· 415 | } 416 | else if (f instanceof TreeBin) { 417 | ··· 418 | } 419 | else if (f instanceof ReservationNode) 420 | throw new IllegalStateException("Recursive update"); 421 | } 422 | } 423 | ··· 424 | } 425 | addCount(1L, binCount); 426 | return null; 427 | } 428 | ``` 429 | 430 | ## ArrayList 431 | ArrayList 本质上是一个动态数组,第一次添加元素时,数组大小将变化为 DEFAULT_CAPACITY 10,不断添加元素后,会进行扩容。删除元素时,会按照位置关系把数组元素整体(复制)移动一遍。 432 | 433 | ``ArrayList.java`` 434 | ```java 435 | public class ArrayList extends AbstractList 436 | implements List, RandomAccess, Cloneable, java.io.Serializable 437 | ··· 438 | 439 | // 增加元素 440 | public boolean add(E e) { 441 | ensureCapacityInternal(size + 1); // Increments modCount!! 442 | elementData[size++] = e; 443 | return true; 444 | } 445 | ··· 446 | 447 | // 删除元素 448 | public E remove(int index) { 449 | if (index >= size) 450 | throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 451 | 452 | modCount++; 453 | E oldValue = (E) elementData[index]; 454 | 455 | int numMoved = size - index - 1; 456 | if (numMoved > 0) 457 | System.arraycopy(elementData, index+1, elementData, index, 458 | numMoved); 459 | elementData[--size] = null; // clear to let GC do its work 460 | 461 | return oldValue; 462 | } 463 | ··· 464 | 465 | // 查找元素 466 | public E get(int index) { 467 | if (index >= size) 468 | throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 469 | 470 | return (E) elementData[index]; 471 | } 472 | ··· 473 | } 474 | ``` 475 | 476 | ## LinkedList 477 | LinkedList 本质上是一个双向链表的存储结构。 478 | 479 | ``LinkedList.java`` 480 | ```java 481 | public class LinkedList 482 | extends AbstractSequentialList 483 | implements List, Deque, Cloneable, java.io.Serializable 484 | { 485 | ···· 486 | 487 | private static class Node { 488 | E item; 489 | Node next; 490 | Node prev; 491 | 492 | Node(Node prev, E element, Node next) { 493 | this.item = element; 494 | this.next = next; 495 | this.prev = prev; 496 | } 497 | } 498 | ··· 499 | 500 | // 增加元素 501 | void linkLast(E e) { 502 | final Node l = last; 503 | final Node newNode = new Node<>(l, e, null); 504 | last = newNode; 505 | if (l == null) 506 | first = newNode; 507 | else 508 | l.next = newNode; 509 | size++; 510 | modCount++; 511 | } 512 | ··· 513 | 514 | // 删除元素 515 | E unlink(Node x) { 516 | final E element = x.item; 517 | final Node next = x.next; 518 | final Node prev = x.prev; 519 | 520 | if (prev == null) { 521 | first = next; 522 | } else { 523 | prev.next = next; 524 | x.prev = null; 525 | } 526 | 527 | if (next == null) { 528 | last = prev; 529 | } else { 530 | next.prev = prev; 531 | x.next = null; 532 | } 533 | 534 | x.item = null; 535 | size--; 536 | modCount++; 537 | return element; 538 | } 539 | ··· 540 | 541 | // 查找元素 542 | Node node(int index) { 543 | // assert isElementIndex(index); 544 | 545 | if (index < (size >> 1)) { 546 | Node x = first; 547 | for (int i = 0; i < index; i++) 548 | x = x.next; 549 | return x; 550 | } else { 551 | Node x = last; 552 | for (int i = size - 1; i > index; i--) 553 | x = x.prev; 554 | return x; 555 | } 556 | } 557 | ··· 558 | } 559 | ``` 560 | 561 | 对于元素查询来说,ArrayList 优于 LinkedList,因为 LinkedList 要移动指针。对于新增和删除操作,LinedList 比较占优势,因为 ArrayList 要移动数据。 562 | 563 | ## CopyOnWriteArrayList 564 | CopyOnWriteArrayList 是线程安全容器(相对于 ArrayList),增加删除等写操作通过加锁的形式保证数据一致性,通过复制新集合的方式解决遍历迭代的问题。 565 | 566 | ``CopyOnWriteArrayList.java`` 567 | ```java 568 | public class CopyOnWriteArrayList 569 | implements List, RandomAccess, Cloneable, java.io.Serializable { 570 | 571 | final transient Object lock = new Object(); 572 | ··· 573 | 574 | // 增加元素 575 | public boolean add(E e) { 576 | synchronized (lock) { 577 | Object[] elements = getArray(); 578 | int len = elements.length; 579 | Object[] newElements = Arrays.copyOf(elements, len + 1); 580 | newElements[len] = e; 581 | setArray(newElements); 582 | return true; 583 | } 584 | } 585 | ··· 586 | 587 | // 删除元素 588 | public E remove(int index) { 589 | synchronized (lock) { 590 | Object[] elements = getArray(); 591 | int len = elements.length; 592 | E oldValue = get(elements, index); 593 | int numMoved = len - index - 1; 594 | if (numMoved == 0) 595 | setArray(Arrays.copyOf(elements, len - 1)); 596 | else { 597 | Object[] newElements = new Object[len - 1]; 598 | System.arraycopy(elements, 0, newElements, 0, index); 599 | System.arraycopy(elements, index + 1, newElements, index, 600 | numMoved); 601 | setArray(newElements); 602 | } 603 | return oldValue; 604 | } 605 | } 606 | ··· 607 | 608 | // 查找元素 609 | private E get(Object[] a, int index) { 610 | return (E) a[index]; 611 | } 612 | } 613 | ``` 614 | 615 | # 反射 616 | ```java 617 | try { 618 | Class cls = Class.forName("com.jasonwu.Test"); 619 | //获取构造方法 620 | Constructor[] publicConstructors = cls.getConstructors(); 621 | //获取全部构造方法 622 | Constructor[] declaredConstructors = cls.getDeclaredConstructors(); 623 | //获取公开方法 624 | Method[] methods = cls.getMethods(); 625 | //获取全部方法 626 | Method[] declaredMethods = cls.getDeclaredMethods(); 627 | //获取公开属性 628 | Field[] publicFields = cls.getFields(); 629 | //获取全部属性 630 | Field[] declaredFields = cls.getDeclaredFields(); 631 | Object clsObject = cls.newInstance(); 632 | Method method = cls.getDeclaredMethod("getModule1Functionality"); 633 | Object object = method.invoke(null); 634 | } catch (ClassNotFoundException e) { 635 | e.printStackTrace(); 636 | } catch (IllegalAccessException e) { 637 | e.printStackTrace(); 638 | } catch (InstantiationException e) { 639 | e.printStackTrace(); 640 | } catch (NoSuchMethodException e) { 641 | e.printStackTrace(); 642 | } catch (InvocationTargetException e) { 643 | e.printStackTrace(); 644 | } 645 | ``` 646 | 647 | # 单例 648 | ## 饿汉式 649 | ```java 650 | public class CustomManager { 651 | private Context mContext; 652 | private static final Object mLock = new Object(); 653 | private static CustomManager mInstance; 654 | 655 | public static CustomManager getInstance(Context context) { 656 | synchronized (mLock) { 657 | if (mInstance == null) { 658 | mInstance = new CustomManager(context); 659 | } 660 | 661 | return mInstance; 662 | } 663 | } 664 | 665 | private CustomManager(Context context) { 666 | this.mContext = context.getApplicationContext(); 667 | } 668 | } 669 | ``` 670 | ## 双重检查模式 671 | ```java 672 | public class CustomManager { 673 | private Context mContext; 674 | private volatile static CustomManager mInstance; 675 | 676 | public static CustomManager getInstance(Context context) { 677 | // 避免非必要加锁 678 | if (mInstance == null) { 679 | synchronized (CustomManger.class) { 680 | if (mInstance == null) { 681 | mInstacne = new CustomManager(context); 682 | } 683 | } 684 | } 685 | 686 | return mInstacne; 687 | } 688 | 689 | private CustomManager(Context context) { 690 | this.mContext = context.getApplicationContext(); 691 | } 692 | } 693 | ``` 694 | ## 静态内部类模式 695 | ```java 696 | public class CustomManager{ 697 | private CustomManager(){} 698 | 699 | private static class CustomManagerHolder { 700 | private static final CustomManager INSTANCE = new CustomManager(); 701 | } 702 | 703 | public static CustomManager getInstance() { 704 | return CustomManagerHolder.INSTANCE; 705 | } 706 | } 707 | ``` 708 | 静态内部类的原理是: 709 | 710 | 当 SingleTon 第一次被加载时,并不需要去加载 SingleTonHoler,只有当 getInstance() 方法第一次被调用时,才会去初始化 INSTANCE,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。getInstance 方法并没有多次去 new 对象,取的都是同一个 INSTANCE 对象。 711 | 712 | 虚拟机会保证一个类的 ``()`` 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 ``()`` 方法,其他线程都需要阻塞等待,直到活动线程执行 ``()`` 方法完毕 713 | 714 | 缺点在于无法传递参数,如Context等 715 | 716 | # 线程 717 | 线程是进程中可独立执行的最小单位,也是 CPU 资源(时间片)分配的基本单位。同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。 718 | 719 | ## 属性 720 | | 属性 | 说明 721 | |--|-- 722 | | id | 线程 id 用于标识不同的线程。编号可能被后续创建的线程使用。编号是只读属性,不能修改 723 | | name | 名字的默认值是 Thread-(id) 724 | | daemon | 分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。守护线程通常用于执行不重要的任务,比如监控其他线程的运行情况,GC 线程就是一个守护线程。setDaemon() 要在线程启动前设置,否则 JVM 会抛出非法线程状态异常,可被继承。 725 | | priority | 线程调度器会根据这个值来决定优先运行哪个线程(不保证),优先级的取值范围为 1~10,默认值是 5,可被继承。Thread 中定义了下面三个优先级常量:
- 最低优先级:MIN_PRIORITY = 1
- 默认优先级:NORM_PRIORITY = 5
- 最高优先级:MAX_PRIORITY = 10 726 | 727 | ## 状态 728 | ![](https://pic2.zhimg.com/80/v2-326a2be9b86b1446d75b6f52f54c98fb_hd.jpg) 729 | 730 | | 状态 | 说明 731 | |--|-- 732 | | New | 新创建了一个线程对象,但还没有调用start()方法。 733 | | Runnable | Ready 状态 线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中 获取 cpu 的使用权。Running 绪状态的线程在获得 CPU 时间片后变为运行中状态(running)。 734 | | Blocked | 线程因为某种原因放弃了cpu 使用权(等待锁),暂时停止运行 735 | | Waiting | 线程进入等待状态因为以下几个方法:
- Object#wait()
- Thread#join()
- LockSupport#park() 736 | | Timed Waiting | 有等待时间的等待状态。 737 | | Terminated | 表示该线程已经执行完毕。 738 | 739 | ## 状态控制 740 | - wait() / notify() / notifyAll() 741 | 742 | ``wait()``,``notify()``,``notifyAll()`` 是定义在Object类的实例方法,用于控制线程状态,三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错 ``java.lang.IllegalMonitorStateException``。 743 | 744 | | 方法 | 说明 745 | |--|-- 746 | | ``wait()`` | 线程状态由 的使用权。Running 变为 Waiting, 并将当前线程放入等待队列中 747 | | ``notify()`` | notify() 方法是将等待队列中一个等待线程从等待队列移动到同步队列中 748 | | ``notifyAll() `` | 则是将所有等待队列中的线程移动到同步队列中 749 | 750 | 被移动的线程状态由 Running 变为 Blocked,notifyAll 方法调用后,等待线程依旧不会从 wait() 返回,需要调用 notify() 或者 notifyAll() 的线程释放掉锁后,等待线程才有机会从 wait() 返回。 751 | 752 | - join() / sleep() / yield() 753 | 754 | 在很多情况,主线程创建并启动子线程,如果子线程中需要进行大量的耗时计算,主线程往往早于子线程结束。这时,如果主线程想等待子线程执行结束之后再结束,比如子线程处理一个数据,主线程要取得这个数据,就要用 ``join()`` 方法。 755 | 756 | ``sleep(long)`` 方法在睡眠时不释放对象锁,而 ``join()`` 方法在等待的过程中释放对象锁。 757 | 758 | ``yield()`` 方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。执行了yield方法的线程什么时候会继续运行由线程调度器来决定。 759 | 760 | 761 | # volatile 762 | 当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步,因此在读取 volatile 类型的变量时总会返回最新写入的值。 763 | 764 | ![](https://user-gold-cdn.xitu.io/2019/6/23/16b833f4a48b216e?w=550&h=429&f=png&s=21448) 765 | 766 | 当一个变量定义为 volatile 之后,将具备以下特性: 767 | - 保证此变量对所有的线程的可见性,不能保证它具有原子性(可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的) 768 | - 禁止指令重排序优化 769 | - volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行 770 | 771 | AtomicInteger 中主要实现了整型的原子操作,防止并发情况下出现异常结果,其内部主要依靠 JDK 中的 unsafe 类操作内存中的数据来实现的。volatile 修饰符保证了 value 在内存中其他线程可以看到其值得改变。CAS(Compare and Swap)操作保证了 AtomicInteger 可以安全的修改value 的值。 772 | 773 | # synchronized 774 | 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。 775 | 776 | 在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。 777 | 778 | Monitor 是线程私有的数据结构,每一个线程都有一个可用 monitor record 列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个 monitor 关联,同时 monitor 中有一个 Owner 字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。Monitor 是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的线程同步。 779 | 780 | 781 | ## 根据获取的锁分类 782 | **获取对象锁** 783 | - synchronized(this|object) {} 784 | - 修饰非静态方法 785 | 786 | **获取类锁** 787 | - synchronized(类.class) {} 788 | - 修饰静态方法 789 | 790 | ## 原理 791 | **同步代码块:** 792 | - monitorenter 和 monitorexit 指令实现的 793 | 794 | **同步方法** 795 | - 方法修饰符上的 ACC_SYNCHRONIZED 实现 796 | 797 | # Lock 798 | ```java 799 | public interface Lock { 800 | void lock(); 801 | void lockInterruptibly() throws InterruptedException; 802 | boolean tryLock(); 803 | boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 804 | void unlock(); 805 | Condition newCondition(); 806 | } 807 | ``` 808 | | 方法 | 说明 809 | |--|-- 810 | | ``lock()`` | 用来获取锁,如果锁被其他线程获取,处于等待状态。如果采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在 try{}catch{} 块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。 811 | | ``lockInterruptibly()`` | 通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。 812 | | ``tryLock()`` | tryLock 方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 813 | | ``tryLock(long,TimeUnit)`` | 与 tryLock 类似,只不过是有等待时间,在等待时间内获取到锁返回 true,超时返回 false。 814 | 815 | 816 | ## 锁的分类 817 | ![](https://user-gold-cdn.xitu.io/2019/6/18/16b69b50c9d340a5?w=1372&h=1206&f=png&s=142754) 818 | 819 | ### 悲观锁、乐观锁 820 | 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java 中,synchronized 关键字和 Lock 的实现类都是悲观锁。悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。 821 | 822 | 而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。乐观锁在 Java 中是通过使用无锁编程来实现,最常采用的是 CAS 算法,Java 原子类中的递增操作就通过 CAS 自旋实现。乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。 823 | 824 | ### 自旋锁、适应性自旋锁 825 | 阻塞或唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。 826 | 827 | 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。 828 | 829 | 而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。 830 | 831 | 自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是 10 次,可以使用 -XX:PreBlockSpin 来更改)没有成功获得锁,就应当挂起线程。 832 | 833 | 自旋锁的实现原理同样也是 CAS,AtomicInteger 中调用 unsafe 进行自增操作的源码中的 do-while 循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。 834 | 835 | ### 死锁 836 | 当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资源,都不放弃自己拥有的资源。 837 | 838 | # 引用类型 839 | 强引用 > 软引用 > 弱引用 840 | 841 | | 引用类型 | 说明 | 842 | |------|------| 843 | | StrongReferenc(强引用)| 当一个对象具有强引用,那么垃圾回收器是绝对不会的回收和销毁它的,**非静态内部类会在其整个生命周期中持有对它外部类的强引用**| 844 | | WeakReference (弱引用)| 在垃圾回收器运行的时候,如果对一个对象的所有引用都是弱引用的话,该对象会被回收 | 845 | | SoftReference(软引用)| 如果一个对象只具有软引用,若内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,才会回收这些对象的内存| 846 | | PhantomReference(虚引用) | 一个只被虚引用持有的对象可能会在任何时候被 GC 回收。虚引用对对象的生存周期完全没有影响,也无法通过虚引用来获取对象实例,仅仅能在对象被回收时,得到一个系统通知(只能通过是否被加入到 ReferenceQueue 来判断是否被GC,这也是唯一判断对象是否被 GC 的途径)。| 847 | 848 | # 动态代理 849 | 850 | 示例: 851 | 852 | ```java 853 | // 定义相关接口 854 | public interface BaseInterface { 855 | void doSomething(); 856 | } 857 | 858 | // 接口的相关实现类 859 | public class BaseImpl implements BaseInterface { 860 | @Override 861 | public void doSomething() { 862 | System.out.println("doSomething"); 863 | } 864 | } 865 | 866 | public static void main(String args[]) { 867 | BaseImpl base = new BaseImpl(); 868 | // Proxy 动态代理实现 869 | BaseInterface proxyInstance = (BaseInterface) Proxy.newProxyInstance(base.getClass().getClassLoader(), base.getClass().getInterfaces(), new InvocationHandler() { 870 | @Override 871 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 872 | if (method.getName().equals("doSomething")) { 873 | method.invoke(base, args); 874 | System.out.println("do more"); 875 | } 876 | return null; 877 | } 878 | }); 879 | 880 | proxyInstance.doSomething(); 881 | } 882 | ``` 883 | 884 | ``Proxy.java`` 885 | ```java 886 | public class Proxy implements java.io.Serializable { 887 | 888 | // 代理类的缓存 889 | private static final WeakCache[], Class> 890 | proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 891 | ··· 892 | 893 | // 生成代理对象方法入口 894 | public static Object newProxyInstance(ClassLoader loader, 895 | Class[] interfaces, 896 | InvocationHandler h) 897 | throws IllegalArgumentException 898 | { 899 | Objects.requireNonNull(h); 900 | 901 | final Class[] intfs = interfaces.clone(); 902 | // 找到并生成相关的代理类 903 | Class cl = getProxyClass0(loader, intfs); 904 | 905 | // 调用代理类的构造方法生成代理类实例 906 | try { 907 | final Constructor cons = cl.getConstructor(constructorParams); 908 | final InvocationHandler ih = h; 909 | if (!Modifier.isPublic(cl.getModifiers())) { 910 | cons.setAccessible(true); 911 | } 912 | return cons.newInstance(new Object[]{h}); 913 | } 914 | ··· 915 | } 916 | ··· 917 | 918 | // 定义和返回代理类的工厂类 919 | private static final class ProxyClassFactory 920 | implements BiFunction[], Class> 921 | { 922 | // 所有代理类的前缀 923 | private static final String proxyClassNamePrefix = "$Proxy"; 924 | 925 | // 用于生成唯一代理类名称的下一个数字 926 | private static final AtomicLong nextUniqueNumber = new AtomicLong(); 927 | 928 | @Override 929 | public Class apply(ClassLoader loader, Class[] interfaces) { 930 | 931 | Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); 932 | ··· 933 | 934 | String proxyPkg = null; // 用于定义代理类的包名 935 | int accessFlags = Modifier.PUBLIC | Modifier.FINAL; 936 | 937 | // 确保所有 non-public 的代理接口在相同的包里 938 | for (Class intf : interfaces) { 939 | int flags = intf.getModifiers(); 940 | if (!Modifier.isPublic(flags)) { 941 | accessFlags = Modifier.FINAL; 942 | String name = intf.getName(); 943 | int n = name.lastIndexOf('.'); 944 | String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); 945 | if (proxyPkg == null) { 946 | proxyPkg = pkg; 947 | } else if (!pkg.equals(proxyPkg)) { 948 | throw new IllegalArgumentException( 949 | "non-public interfaces from different packages"); 950 | } 951 | } 952 | } 953 | 954 | if (proxyPkg == null) { 955 | // 如果没有 non-public 的代理接口,使用默认的包名 956 | proxyPkg = ""; 957 | } 958 | 959 | { 960 | List methods = getMethods(interfaces); 961 | Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE); 962 | validateReturnTypes(methods); 963 | List[]> exceptions = deduplicateAndGetExceptions(methods); 964 | 965 | Method[] methodsArray = methods.toArray(new Method[methods.size()]); 966 | Class[][] exceptionsArray = exceptions.toArray(new Class[exceptions.size()][]); 967 | 968 | // 生成代理类的名称 969 | long num = nextUniqueNumber.getAndIncrement(); 970 | String proxyName = proxyPkg + proxyClassNamePrefix + num; 971 | 972 | // Android 特定修改:直接调用 native 方法生成代理类 973 | return generateProxy(proxyName, interfaces, loader, methodsArray, 974 | exceptionsArray); 975 | 976 | // JDK 使用的 ProxyGenerator.generateProxyClas 方法创建代理类 977 | byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 978 | proxyName, interfaces, accessFlags); 979 | try { 980 | return defineClass0(loader, proxyName, 981 | proxyClassFile, 0, proxyClassFile.length); 982 | } ··· 983 | } 984 | } 985 | ··· 986 | 987 | // 最终调用 native 方法生成代理类 988 | @FastNative 989 | private static native Class generateProxy(String name, Class[] interfaces, 990 | ClassLoader loader, Method[] methods, 991 | Class[][] exceptions); 992 | 993 | } 994 | ``` 995 | 996 | ``ProxyGenerator.java`` 997 | ```java 998 | public static byte[] generateProxyClass(final String name, 999 | Class[] interfaces) 1000 | { 1001 | ProxyGenerator gen = new ProxyGenerator(name, interfaces); 1002 | final byte[] classFile = gen.generateClassFile(); 1003 | 1004 | if (saveGeneratedFiles) { 1005 | java.security.AccessController.doPrivileged( 1006 | new java.security.PrivilegedAction() { 1007 | public Void run() { 1008 | try { 1009 | FileOutputStream file = 1010 | new FileOutputStream(dotToSlash(name) + ".class"); 1011 | file.write(classFile); 1012 | file.close(); 1013 | return null; 1014 | } catch (IOException e) { 1015 | throw new InternalError( 1016 | "I/O exception saving generated file: " + e); 1017 | } 1018 | } 1019 | }); 1020 | } 1021 | 1022 | return classFile; 1023 | } 1024 | ``` 1025 | 1026 | # 元注解 1027 | @Retention:保留的范围,可选值有三种。 1028 | 1029 | | RetentionPolicy | 说明 1030 | |----|---- 1031 | | SOURCE | 注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里),如 @Override 1032 | | CLASS | 注解在class文件中可用,但会被 VM 丢弃(该类型的注解信息会保留在源码里和 class 文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义 Retention 值时,默认值是 CLASS。 1033 | | RUNTIME | 注解信息将在运行期 (JVM) 也保留,因此可以通过反射机制读取注解的信息(源码、class 文件和执行的时候都有注解的信息),如 @Deprecated 1034 | 1035 | @Target:可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER等,未标注则表示可修饰所有 1036 | 1037 | @Inherited:是否可以被继承,默认为false 1038 | 1039 | @Documented:是否会保存到 Javadoc 文档中 1040 | -------------------------------------------------------------------------------- /Docs/常见面试算法题汇总.md: -------------------------------------------------------------------------------- 1 | - [排序](#%e6%8e%92%e5%ba%8f) 2 | - [比较排序](#%e6%af%94%e8%be%83%e6%8e%92%e5%ba%8f) 3 | - [冒泡排序](#%e5%86%92%e6%b3%a1%e6%8e%92%e5%ba%8f) 4 | - [归并排序](#%e5%bd%92%e5%b9%b6%e6%8e%92%e5%ba%8f) 5 | - [快速排序](#%e5%bf%ab%e9%80%9f%e6%8e%92%e5%ba%8f) 6 | - [线性排序](#%e7%ba%bf%e6%80%a7%e6%8e%92%e5%ba%8f) 7 | - [计数排序](#%e8%ae%a1%e6%95%b0%e6%8e%92%e5%ba%8f) 8 | - [桶排序](#%e6%a1%b6%e6%8e%92%e5%ba%8f) 9 | - [二叉树](#%e4%ba%8c%e5%8f%89%e6%a0%91) 10 | - [顺序遍历](#%e9%a1%ba%e5%ba%8f%e9%81%8d%e5%8e%86) 11 | - [层次遍历](#%e5%b1%82%e6%ac%a1%e9%81%8d%e5%8e%86) 12 | - [左右翻转](#%e5%b7%a6%e5%8f%b3%e7%bf%bb%e8%bd%ac) 13 | - [最大值](#%e6%9c%80%e5%a4%a7%e5%80%bc) 14 | - [最大深度](#%e6%9c%80%e5%a4%a7%e6%b7%b1%e5%ba%a6) 15 | - [最小深度](#%e6%9c%80%e5%b0%8f%e6%b7%b1%e5%ba%a6) 16 | - [平衡二叉树](#%e5%b9%b3%e8%a1%a1%e4%ba%8c%e5%8f%89%e6%a0%91) 17 | - [链表](#%e9%93%be%e8%a1%a8) 18 | - [删除节点](#%e5%88%a0%e9%99%a4%e8%8a%82%e7%82%b9) 19 | - [翻转链表](#%e7%bf%bb%e8%bd%ac%e9%93%be%e8%a1%a8) 20 | - [中间元素](#%e4%b8%ad%e9%97%b4%e5%85%83%e7%b4%a0) 21 | - [判断是否为循环链表](#%e5%88%a4%e6%96%ad%e6%98%af%e5%90%a6%e4%b8%ba%e5%be%aa%e7%8e%af%e9%93%be%e8%a1%a8) 22 | - [合并两个已排序链表](#%e5%90%88%e5%b9%b6%e4%b8%a4%e4%b8%aa%e5%b7%b2%e6%8e%92%e5%ba%8f%e9%93%be%e8%a1%a8) 23 | - [链表排序](#%e9%93%be%e8%a1%a8%e6%8e%92%e5%ba%8f) 24 | - [删除倒数第N个节点](#%e5%88%a0%e9%99%a4%e5%80%92%e6%95%b0%e7%ac%acn%e4%b8%aa%e8%8a%82%e7%82%b9) 25 | - [两个链表是否相交](#%e4%b8%a4%e4%b8%aa%e9%93%be%e8%a1%a8%e6%98%af%e5%90%a6%e7%9b%b8%e4%ba%a4) 26 | - [栈 / 队列](#%e6%a0%88--%e9%98%9f%e5%88%97) 27 | - [带最小值操作的栈](#%e5%b8%a6%e6%9c%80%e5%b0%8f%e5%80%bc%e6%93%8d%e4%bd%9c%e7%9a%84%e6%a0%88) 28 | - [有效括号](#%e6%9c%89%e6%95%88%e6%8b%ac%e5%8f%b7) 29 | - [用栈实现队列](#%e7%94%a8%e6%a0%88%e5%ae%9e%e7%8e%b0%e9%98%9f%e5%88%97) 30 | - [逆波兰表达式求值](#%e9%80%86%e6%b3%a2%e5%85%b0%e8%a1%a8%e8%be%be%e5%bc%8f%e6%b1%82%e5%80%bc) 31 | - [二分](#%e4%ba%8c%e5%88%86) 32 | - [二分搜索](#%e4%ba%8c%e5%88%86%e6%90%9c%e7%b4%a2) 33 | - [X的平方根](#x%e7%9a%84%e5%b9%b3%e6%96%b9%e6%a0%b9) 34 | - [哈希表](#%e5%93%88%e5%b8%8c%e8%a1%a8) 35 | - [两数之和](#%e4%b8%a4%e6%95%b0%e4%b9%8b%e5%92%8c) 36 | - [连续数组](#%e8%bf%9e%e7%bb%ad%e6%95%b0%e7%bb%84) 37 | - [最长无重复字符的子串](#%e6%9c%80%e9%95%bf%e6%97%a0%e9%87%8d%e5%a4%8d%e5%ad%97%e7%ac%a6%e7%9a%84%e5%ad%90%e4%b8%b2) 38 | - [最多点在一条直线上](#%e6%9c%80%e5%a4%9a%e7%82%b9%e5%9c%a8%e4%b8%80%e6%9d%a1%e7%9b%b4%e7%ba%bf%e4%b8%8a) 39 | - [堆 / 优先队列](#%e5%a0%86--%e4%bc%98%e5%85%88%e9%98%9f%e5%88%97) 40 | - [前K大的数](#%e5%89%8dk%e5%a4%a7%e7%9a%84%e6%95%b0) 41 | - [前K大的数II](#%e5%89%8dk%e5%a4%a7%e7%9a%84%e6%95%b0ii) 42 | - [第K大的数](#%e7%ac%ack%e5%a4%a7%e7%9a%84%e6%95%b0) 43 | - [二叉搜索树](#%e4%ba%8c%e5%8f%89%e6%90%9c%e7%b4%a2%e6%a0%91) 44 | - [验证二叉搜索树](#%e9%aa%8c%e8%af%81%e4%ba%8c%e5%8f%89%e6%90%9c%e7%b4%a2%e6%a0%91) 45 | - [第K小的元素](#%e7%ac%ack%e5%b0%8f%e7%9a%84%e5%85%83%e7%b4%a0) 46 | - [数组 / 双指针](#%e6%95%b0%e7%bb%84--%e5%8f%8c%e6%8c%87%e9%92%88) 47 | - [加一](#%e5%8a%a0%e4%b8%80) 48 | - [删除元素](#%e5%88%a0%e9%99%a4%e5%85%83%e7%b4%a0) 49 | - [删除排序数组中的重复数字](#%e5%88%a0%e9%99%a4%e6%8e%92%e5%ba%8f%e6%95%b0%e7%bb%84%e4%b8%ad%e7%9a%84%e9%87%8d%e5%a4%8d%e6%95%b0%e5%ad%97) 50 | - [我的日程安排表 I](#%e6%88%91%e7%9a%84%e6%97%a5%e7%a8%8b%e5%ae%89%e6%8e%92%e8%a1%a8-i) 51 | - [合并排序数组](#%e5%90%88%e5%b9%b6%e6%8e%92%e5%ba%8f%e6%95%b0%e7%bb%84) 52 | - [贪心](#%e8%b4%aa%e5%bf%83) 53 | - [买卖股票的最佳时机](#%e4%b9%b0%e5%8d%96%e8%82%a1%e7%a5%a8%e7%9a%84%e6%9c%80%e4%bd%b3%e6%97%b6%e6%9c%ba) 54 | - [买卖股票的最佳时机 II](#%e4%b9%b0%e5%8d%96%e8%82%a1%e7%a5%a8%e7%9a%84%e6%9c%80%e4%bd%b3%e6%97%b6%e6%9c%ba-ii) 55 | - [最大子数组](#%e6%9c%80%e5%a4%a7%e5%ad%90%e6%95%b0%e7%bb%84) 56 | - [主元素](#%e4%b8%bb%e5%85%83%e7%b4%a0) 57 | - [字符串处理](#%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%a4%84%e7%90%86) 58 | - [生成括号](#%e7%94%9f%e6%88%90%e6%8b%ac%e5%8f%b7) 59 | - [Excel表列标题](#excel%e8%a1%a8%e5%88%97%e6%a0%87%e9%a2%98) 60 | - [翻转游戏](#%e7%bf%bb%e8%bd%ac%e6%b8%b8%e6%88%8f) 61 | - [翻转字符串中的单词](#%e7%bf%bb%e8%bd%ac%e5%ad%97%e7%ac%a6%e4%b8%b2%e4%b8%ad%e7%9a%84%e5%8d%95%e8%af%8d) 62 | - [转换字符串到整数](#%e8%bd%ac%e6%8d%a2%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%88%b0%e6%95%b4%e6%95%b0) 63 | - [最长公共前缀](#%e6%9c%80%e9%95%bf%e5%85%ac%e5%85%b1%e5%89%8d%e7%bc%80) 64 | - [回文数](#%e5%9b%9e%e6%96%87%e6%95%b0) 65 | - [动态规划](#%e5%8a%a8%e6%80%81%e8%a7%84%e5%88%92) 66 | - [单词拆分](#%e5%8d%95%e8%af%8d%e6%8b%86%e5%88%86) 67 | - [爬楼梯](#%e7%88%ac%e6%a5%bc%e6%a2%af) 68 | - [打劫房屋](#%e6%89%93%e5%8a%ab%e6%88%bf%e5%b1%8b) 69 | - [编辑距离](#%e7%bc%96%e8%be%91%e8%b7%9d%e7%a6%bb) 70 | - [乘积最大子序列](#%e4%b9%98%e7%a7%af%e6%9c%80%e5%a4%a7%e5%ad%90%e5%ba%8f%e5%88%97) 71 | - [矩阵](#%e7%9f%a9%e9%98%b5) 72 | - [螺旋矩阵](#%e8%9e%ba%e6%97%8b%e7%9f%a9%e9%98%b5) 73 | - [判断数独是否合法](#%e5%88%a4%e6%96%ad%e6%95%b0%e7%8b%ac%e6%98%af%e5%90%a6%e5%90%88%e6%b3%95) 74 | - [旋转图像](#%e6%97%8b%e8%bd%ac%e5%9b%be%e5%83%8f) 75 | - [二进制 / 位运算](#%e4%ba%8c%e8%bf%9b%e5%88%b6--%e4%bd%8d%e8%bf%90%e7%ae%97) 76 | - [落单的数](#%e8%90%bd%e5%8d%95%e7%9a%84%e6%95%b0) 77 | - [格雷编码](#%e6%a0%bc%e9%9b%b7%e7%bc%96%e7%a0%81) 78 | - [其他](#%e5%85%b6%e4%bb%96) 79 | - [反转整数](#%e5%8f%8d%e8%bd%ac%e6%95%b4%e6%95%b0) 80 | - [LRU缓存策略](#lru%e7%bc%93%e5%ad%98%e7%ad%96%e7%95%a5) 81 | # 排序 82 | ## 比较排序 83 | ### 冒泡排序 84 | 重复地走访过要排序的数列,每次比较相邻两个元素,如果它们的顺序错误就把它们交换过来,越大的元素会经由交换慢慢“浮”到数列的尾端。 85 | ``` java 86 | public void bubbleSort(int[] arr) { 87 | int temp = 0; 88 | boolean swap; 89 | for (int i = arr.length - 1; i > 0; i--) { // 每次需要排序的长度 90 | // 增加一个swap的标志,当前一轮没有进行交换时,说明数组已经有序 91 | swap = false; 92 | for (int j = 0; j < i; j++) { // 从第一个元素到第i个元素 93 | if (arr[j] > arr[j + 1]) { 94 | temp = arr[j]; 95 | arr[j] = arr[j + 1]; 96 | arr[j + 1] = temp; 97 | swap = true; 98 | } 99 | } 100 | if (!swap){ 101 | break; 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ### 归并排序 108 | 分解待排序的数组成两个各具 n/2 个元素的子数组,递归调用归并排序两个子数组,合并两个已排序的子数组成一个已排序的数组。 109 | ```java 110 | public void mergeSort(int[] arr) { 111 | int[] temp = new int[arr.length]; 112 | internalMergeSort(arr, temp, 0, arr.length - 1); 113 | } 114 | 115 | private void internalMergeSort(int[] arr, int[] temp, int left, int right) { 116 | // 当left == right时,不需要再划分 117 | if (left < right) { 118 | int mid = (left + right) / 2; 119 | internalMergeSort(arr, temp, left, mid); 120 | internalMergeSort(arr, temp, mid + 1, right); 121 | mergeSortedArray(arr, temp, left, mid, right); 122 | } 123 | } 124 | 125 | // 合并两个有序子序列 126 | public void mergeSortedArray(int[] arr, int[] temp, int left, int mid, int right) { 127 | int i = left; 128 | int j = mid + 1; 129 | int k = 0; 130 | while (i <= mid && j <= right) { 131 | temp[k++] = arr[i] < arr[j] ? arr[i++] : arr[j++]; 132 | } 133 | while (i <= mid) { 134 | temp[k++] = arr[i++]; 135 | } 136 | while (j <= right) { 137 | temp[k++] = arr[j++]; 138 | } 139 | // 把temp数据复制回原数组 140 | for (i = 0; i < k; i++) { 141 | arr[left + i] = temp[i]; 142 | } 143 | } 144 | ``` 145 | 146 | ### 快速排序 147 | 在待排序的数组选取一个元素作为基准,将待排序的元素进行分区,比基准元素大的元素放在一边,比其小的放另一边,递归调用快速排序对两边的元素排序。选取基准元素并分区的过程采用双指针左右交换。 148 | ```java 149 | public void quickSort(int[] arr){ 150 | quickSort(arr, 0, arr.length-1); 151 | } 152 | 153 | private void quickSort(int[] arr, int low, int high){ 154 | if (low >= high) 155 | return; 156 | int pivot = partition(arr, low, high); //将数组分为两部分 157 | quickSort(arr, low, pivot - 1); //递归排序左子数组 158 | quickSort(arr, pivot + 1, high); //递归排序右子数组 159 | } 160 | 161 | private int partition(int[] arr, int low, int high){ 162 | int pivot = arr[low]; //基准 163 | while (low < high){ 164 | while (low < high && arr[high] >= pivot) { 165 | high--; 166 | } 167 | arr[low] = arr[high]; //交换比基准大的记录到左端 168 | while (low < high && arr[low] <= pivot) { 169 | low++; 170 | } 171 | arr[high] = arr[low]; //交换比基准小的记录到右端 172 | } 173 | //扫描完成,基准到位 174 | arr[low] = pivot; 175 | //返回的是基准的位置 176 | return low; 177 | } 178 | ``` 179 | 180 | ## 线性排序 181 | ### 计数排序 182 | 根据待排序的数组中最大和最小的元素,统计数组中每个值为i的元素出现的次数,存入数组C的第i项,对所有的计数累加,然后反向填充目标数组。 183 | ```java 184 | public void countSort(int[] arr) { 185 | int max = Integer.MIN_VALUE; 186 | int min = Integer.MAX_VALUE; 187 | for(int i = 0; i < arr.length; i++){ 188 | max = Math.max(max, arr[i]); 189 | min = Math.min(min, arr[i]); 190 | } 191 | 192 | int[] b = new int[arr.length]; // 存储数组 193 | int[] count = new int[max - min + 1]; // 计数数组 194 | 195 | for (int num = min; num <= max; num++) { 196 | // 初始化各元素值为0,数组下标从0开始因此减min 197 | count[num - min] = 0; 198 | } 199 | 200 | for (int i = 0; i < arr.length; i++) { 201 | int num = arr[i]; 202 | count[num - min]++; // 每出现一个值,计数数组对应元素的值+1 203 | // 此时count[i]表示数值等于i的元素的个数 204 | } 205 | 206 | for (int i = min + 1; i <= max; i++) { 207 | count[i - min] += count[i - min - 1]; 208 | // 此时count[i]表示数值<=i的元素的个数 209 | } 210 | 211 | for (int i = 0; i < arr.length; i++) { 212 | int num = arr[i]; // 原数组第i位的值 213 | int index = count[num - min] - 1; //加总数组中对应元素的下标 214 | b[index] = num; // 将该值存入存储数组对应下标中 215 | count[num - min]--; // 加总数组中,该值的总和减少1。 216 | } 217 | 218 | // 将存储数组的值替换给原数组 219 | for(int i=0; i < arr.length;i++){ 220 | arr[i] = b[i]; 221 | } 222 | } 223 | ``` 224 | ### 桶排序 225 | 找出待排序数组中的最大值max、最小值min,数组ArrayList作为桶,桶里放的元素用ArrayList存储。计算每个元素 arr[i] 放的桶,每个桶各自排序,遍历桶数组,把排序好的元素放进输出数组。 226 | ```java 227 | public static void bucketSort(int[] arr){ 228 | int max = Integer.MIN_VALUE; 229 | int min = Integer.MAX_VALUE; 230 | for(int i = 0; i < arr.length; i++){ 231 | max = Math.max(max, arr[i]); 232 | min = Math.min(min, arr[i]); 233 | } 234 | // 桶数 235 | int bucketNum = (max - min) / arr.length + 1; 236 | ArrayList> bucketArr = new ArrayList<>(bucketNum); 237 | for(int i = 0; i < bucketNum; i++){ 238 | bucketArr.add(new ArrayList()); 239 | } 240 | // 将每个元素放入桶 241 | for(int i = 0; i < arr.length; i++){ 242 | int num = (arr[i] - min) / (arr.length); 243 | bucketArr.get(num).add(arr[i]); 244 | } 245 | // 对每个桶进行排序 246 | for(int i = 0; i < bucketArr.size(); i++){ 247 | Collections.sort(bucketArr.get(i)); 248 | for (int j = 0; j < bucketArr.get(i).size(); j++) { 249 | arr[j] = bucketArr.get(i).get(j); 250 | } 251 | } 252 | } 253 | ``` 254 | 255 | # 二叉树 256 | ```java 257 | class TreeNode { 258 | public TreeNode left, right; 259 | public int val; 260 | 261 | public TreeNode(int val) { 262 | this.val = val; 263 | } 264 | } 265 | ``` 266 | 267 | ## 顺序遍历 268 | 先序遍历: 根->左->右 269 | 中序遍历: 左->根->右 270 | 后序遍历: 左->右->根 271 | ```java 272 | // 先序遍历 273 | public void preTraverse(TreeNode root) { 274 | if (root != null) { 275 | System.out.println(root.val); 276 | preTraverse(root.left); 277 | preTraverse(root.right); 278 | } 279 | } 280 | 281 | // 中序遍历 282 | public void inTraverse(TreeNode root) { 283 | if (root != null) { 284 | inTraverse(root.left); 285 | System.out.println(root.val); 286 | inTraverse(root.right); 287 | } 288 | } 289 | 290 | // 后序遍历 291 | public void postTraverse(TreeNode root) { 292 | if (root != null) { 293 | postTraverse(root.left); 294 | postTraverse(root.right); 295 | System.out.println(root.val); 296 | } 297 | } 298 | ``` 299 | ## 层次遍历 300 | ```java 301 | // 层次遍历(DFS) 302 | public static List> levelOrder(TreeNode root) { 303 | List> res = new ArrayList<>(); 304 | if (root == null) { 305 | return res; 306 | } 307 | 308 | dfs(root, res, 0); 309 | return res; 310 | } 311 | 312 | private void dfs(TreeNode root, List> res, int level) { 313 | if (root == null) { 314 | return; 315 | } 316 | if (level == res.size()) { 317 | res.add(new ArrayList<>()); 318 | } 319 | res.get(level).add(root.val); 320 | 321 | dfs(root.left, res, level + 1); 322 | dfs(root.right, res, level + 1); 323 | } 324 | 325 | // 层次遍历(BFS) 326 | public List> levelOrder(TreeNode root) { 327 | List result = new ArrayList(); 328 | 329 | if (root == null) { 330 | return result; 331 | } 332 | 333 | Queue queue = new LinkedList(); 334 | queue.offer(root); 335 | 336 | while (!queue.isEmpty()) { 337 | ArrayList level = new ArrayList(); 338 | int size = queue.size(); 339 | for (int i = 0; i < size; i++) { 340 | TreeNode head = queue.poll(); 341 | level.add(head.val); 342 | if (head.left != null) { 343 | queue.offer(head.left); 344 | } 345 | if (head.right != null) { 346 | queue.offer(head.right); 347 | } 348 | } 349 | result.add(level); 350 | } 351 | 352 | return result; 353 | } 354 | 355 | // "Z"字遍历 356 | public List> zigzagLevelOrder(TreeNode root) { 357 | List> result = new ArrayList<>(); 358 | 359 | if (root == null){ 360 | return result; 361 | } 362 | 363 | Queue queue = new LinkedList<>(); 364 | queue.offer(root); 365 | boolean isFromLeft = false; 366 | while(!queue.isEmpty()){ 367 | int size = queue.size(); 368 | isFromLeft = !isFromLeft; 369 | List list = new ArrayList<>(); 370 | for(int i = 0; i < size; i++){ 371 | TreeNode node; 372 | if (isFromLeft){ 373 | node = queue.pollFirst(); 374 | }else{ 375 | node = queue.pollLast(); 376 | } 377 | list.add(node.val); 378 | 379 | if (isFromLeft){ 380 | if (node.left != null){ 381 | queue.offerLast(node.left); 382 | } 383 | if (node.right != null){ 384 | queue.offerLast(node.right); 385 | } 386 | }else{ 387 | if (node.right != null){ 388 | queue.offerFirst(node.right); 389 | } 390 | if (node.left != null){ 391 | queue.offerFirst(node.left); 392 | } 393 | } 394 | } 395 | result.add(list); 396 | } 397 | 398 | return result; 399 | } 400 | ``` 401 | ## 左右翻转 402 | ```java 403 | public void invert(TreeNode root) { 404 | if (root == null) { 405 | return; 406 | } 407 | TreeNode temp = root.left; 408 | root.left = root.right; 409 | root.right = temp; 410 | 411 | invert(root.left); 412 | invert(root.right); 413 | } 414 | ``` 415 | ## 最大值 416 | ```java 417 | public int getMax(TreeNode root) { 418 | if (root == null) { 419 | return Integer.MIN_VALUE; 420 | } else { 421 | int left = getMax(root.left); 422 | int right = getMax(root.right); 423 | return Math.max(Math.max(left, rigth), root.val); 424 | } 425 | } 426 | ``` 427 | ## 最大深度 428 | ```java 429 | public int maxDepth(TreeNode root) { 430 | if (root == null) { 431 | return 0; 432 | } 433 | 434 | int left = maxDepth(root.left); 435 | int right = maxDepth(root.right); 436 | return Math.max(left, right) + 1; 437 | } 438 | ``` 439 | ## 最小深度 440 | ```java 441 | public int minDepth(TreeNode root) { 442 | if (root == null) { 443 | return 0; 444 | } 445 | 446 | int left = minDepth(root.left); 447 | int right = minDepth(root.right); 448 | 449 | if (left == 0) { 450 | return right + 1; 451 | } else if (right == 0) { 452 | return left + 1; 453 | } else { 454 | return Math.min(left, right) + 1; 455 | } 456 | } 457 | ``` 458 | 459 | ## 平衡二叉树 460 | > 平衡二叉树每一个节点的左右两个子树的高度差不超过1 461 | ```java 462 | public boolean isBalanced(TreeNode root) { 463 | return maxDepth(root) != -1; 464 | } 465 | 466 | private int maxDepth(TreeNode root) { 467 | if (root == null) { 468 | return 0; 469 | } 470 | 471 | int left = maxDepth(root.left); 472 | int right = maxDepth(root.right); 473 | if (left == -1 || right == -1 || Math.abs(left - right) > 1) { 474 | return -1; 475 | } 476 | return Math.max(left, right) + 1; 477 | } 478 | ``` 479 | 480 | # 链表 481 | ```java 482 | public class ListNode { 483 | int val; 484 | ListNode next; 485 | ListNode(int x) { 486 | val = x; 487 | next = null; 488 | } 489 | } 490 | ``` 491 | 492 | ## 删除节点 493 | ```java 494 | public void deleteNode(ListNode node) { 495 | if (node.next == null){ 496 | node = null; 497 | return; 498 | } 499 | // 取缔下一节点 500 | node.val = node.next.val 501 | node.next = node.next.next 502 | } 503 | ``` 504 | 505 | ## 翻转链表 506 | ```java 507 | public ListNode reverse(ListNode head) { 508 | //prev表示前继节点 509 | ListNode prev = null; 510 | while (head != null) { 511 | //temp记录下一个节点,head是当前节点 512 | ListNode temp = head.next; 513 | head.next = prev; 514 | prev = head; 515 | head = temp; 516 | } 517 | return prev; 518 | } 519 | ``` 520 | 521 | 522 | ## 中间元素 523 | ```java 524 | public ListNode findMiddle(ListNode head){ 525 | if(head == null){ 526 | return null; 527 | } 528 | 529 | ListNode slow = head; 530 | ListNode fast = head; 531 | 532 | // fast.next = null 表示 fast 是链表的尾节点 533 | while(fast != null && fast.next != null){ 534 | fast = fast.next.next; 535 | slow = slow.next; 536 | } 537 | return slow; 538 | } 539 | ``` 540 | 541 | ## 判断是否为循环链表 542 | ```java 543 | public Boolean hasCycle(ListNode head) { 544 | if (head == null || head.next == null) { 545 | return false; 546 | } 547 | 548 | ListNode slow = head; 549 | ListNode fast = head.next; 550 | 551 | while (fast != slow) { 552 | if(fast == null || fast.next == null) { 553 | return false; 554 | } 555 | fast = fast.next.next; 556 | slow = slow.next; 557 | } 558 | return true; 559 | } 560 | ``` 561 | ## 合并两个已排序链表 562 | ```java 563 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 564 | ListNode dummy = new ListNode(0); 565 | ListNode lastNode = dummy; 566 | 567 | while (l1 != null && l2 != null) { 568 | if (l1.val < l2.val) { 569 | lastNode.next = l1; 570 | l1 = l1.next; 571 | } else { 572 | lastNode.next = l2; 573 | l2 = l2.next; 574 | } 575 | lastNode = lastNode.next; 576 | } 577 | 578 | if (l1 != null) { 579 | lastNode.next = l1; 580 | } else { 581 | lastNode.next = l2; 582 | } 583 | 584 | return dummy.next; 585 | } 586 | ``` 587 | 588 | ## 链表排序 589 | 可利用归并、快排等算法实现 590 | ```java 591 | // 归并排序 592 | public ListNode sortList(ListNode head) { 593 | if (head == null || head.next == null) { 594 | return head; 595 | } 596 | 597 | ListNode mid = findMiddle(head); 598 | 599 | ListNode right = sortList(mid.next); 600 | mid.next = null; 601 | ListNode left = sortList(head); 602 | 603 | return mergeTwoLists(left, right); 604 | } 605 | 606 | // 快速排序 607 | public ListNode sortList(ListNode head) { 608 | quickSort(head, null); 609 | return head; 610 | } 611 | 612 | private void quickSort(ListNode start, ListNode end) { 613 | if (start == end) { 614 | return; 615 | } 616 | 617 | ListNode pt = partition(start, end); 618 | quickSort(start, pt); 619 | quickSort(pt.next, end); 620 | } 621 | 622 | private ListNode partition(ListNode start, ListNode end) { 623 | int pivotKey = start.val; 624 | ListNode p1 = start, p2 = start.next; 625 | while (p2 != end) { 626 | if (p2.val < pivotKey) { 627 | p1 = p1.next; 628 | swapValue(p1, p2); 629 | } 630 | p2 = p2.next; 631 | } 632 | 633 | swapValue(start, p1); 634 | return p1; 635 | } 636 | 637 | private void swapValue(ListNode node1, ListNode node2) { 638 | int tmp = node1.val; 639 | node1.val = node2.val; 640 | node2.val = tmp; 641 | } 642 | ``` 643 | 644 | ## 删除倒数第N个节点 645 | ```java 646 | public ListNode removeNthFromEnd(ListNode head, int n) { 647 | if (n <= 0) { 648 | return null; 649 | } 650 | 651 | ListNode dummy = new ListNode(0); 652 | dummy.next = head; 653 | 654 | ListNode preDelete = dummy; 655 | for (int i = 0; i < n; i++) { 656 | if (head == null) { 657 | return null; 658 | } 659 | head = head.next; 660 | } 661 | // 此时head为正数第N个节点 662 | while (head != null) { 663 | head = head.next; 664 | preDelete = preDelete.next; 665 | } 666 | preDelete.next = preDelete.next.next; 667 | return dummy.next; 668 | } 669 | ``` 670 | 671 | ## 两个链表是否相交 672 | ```java 673 | public ListNode getIntersectionNode(ListNode headA, ListNode headB) { 674 | if (headA == null || headB == null) { 675 | return null; 676 | } 677 | 678 | ListNode currA = headA; 679 | ListNode currB = headB; 680 | int lengthA = 0; 681 | int lengthB = 0; 682 | 683 | // 让长的先走到剩余长度和短的一样 684 | while (currA != null) { 685 | currA = currA.next; 686 | lengthA++; 687 | } 688 | while (currB != null) { 689 | currB = currB.next; 690 | lengthB++; 691 | } 692 | 693 | currA = headA; 694 | currB = headB; 695 | while (lengthA > lengthB) { 696 | currA = currA.next; 697 | lengthA--; 698 | } 699 | while (lengthB > lengthA) { 700 | currB = currB.next; 701 | lengthB--; 702 | } 703 | 704 | // 然后同时走到第一个相同的地方 705 | while (currA != currB) { 706 | currA = currA.next; 707 | currB = currB.next; 708 | } 709 | // 返回交叉开始的节点 710 | return currA; 711 | } 712 | ``` 713 | 714 | # 栈 / 队列 715 | ## 带最小值操作的栈 716 | > 实现一个栈, 额外支持一个操作:min() 返回栈中元素的最小值 717 | ```java 718 | public class MinStack { 719 | private Stack stack; 720 | private Stack minStack; // 维护一个辅助栈,传入当前栈的最小值 721 | 722 | public MinStack() { 723 | stack = new Stack(); 724 | minStack = new Stack(); 725 | } 726 | 727 | public void push(int number) { 728 | stack.push(number); 729 | if (minStack.isEmpty()) { 730 | minStack.push(number); 731 | } else { 732 | minStack.push(Math.min(number, minStack.peek())); 733 | } 734 | } 735 | 736 | public int pop() { 737 | minStack.pop(); 738 | return stack.pop(); 739 | } 740 | 741 | public int min() { 742 | return minStack.peek(); 743 | } 744 | } 745 | ``` 746 | 747 | ## 有效括号 748 | > 给定一个字符串所表示的括号序列,包含以下字符: '(', ')', '{', '}', '[' and ']', 判定是否是有效的括号序列。括号必须依照 "()" 顺序表示, "()[]{}" 是有效的括号,但 "([)]" 则是无效的括号。 749 | ```java 750 | public boolean isValidParentheses(String s) { 751 | Stack stack = new Stack(); 752 | for (Character c : s.toCharArray()) { 753 | if ("({[".contains(String.valueOf(c))) { 754 | stack.push(c); 755 | } else { 756 | if (!stack.isEmpty() && isValid(stack.peek(), c)) { 757 | stack.pop(); 758 | } else { 759 | return false; 760 | } 761 | } 762 | } 763 | return stack.isEmpty(); 764 | } 765 | 766 | private boolean isValid(char c1, char c2) { 767 | return (c1 == '(' && c2 == ')') || (c1 == '{' && c2 == '}') 768 | || (c1 == '[' && c2 == ']'); 769 | } 770 | ``` 771 | 772 | ## 用栈实现队列 773 | ```java 774 | public class MyQueue { 775 | private Stack outStack; 776 | private Stack inStack; 777 | 778 | public MyQueue() { 779 | outStack = new Stack(); 780 | inStack = new Stack(); 781 | } 782 | 783 | private void in2OutStack(){ 784 | while(!inStack.isEmpty()){ 785 | outStack.push(inStack.pop()); 786 | } 787 | } 788 | 789 | public void push(int element) { 790 | inStack.push(element); 791 | } 792 | 793 | public int pop() { 794 | if(outStack.isEmpty()){ 795 | this.in2OutStack(); 796 | } 797 | return outStack.pop(); 798 | } 799 | 800 | public int top() { 801 | if(outStack.isEmpty()){ 802 | this.in2OutStack(); 803 | } 804 | return outStack.peek(); 805 | } 806 | } 807 | ``` 808 | 809 | ## 逆波兰表达式求值 810 | > 在反向波兰表示法中计算算术表达式的值, ["2", "1", "+", "3", "*"] -> (2 + 1) * 3 -> 9 811 | ```java 812 | public int evalRPN(String[] tokens) { 813 | Stack s = new Stack(); 814 | String operators = "+-*/"; 815 | for (String token : tokens) { 816 | if (!operators.contains(token)) { 817 | s.push(Integer.valueOf(token)); 818 | continue; 819 | } 820 | 821 | int a = s.pop(); 822 | int b = s.pop(); 823 | if (token.equals("+")) { 824 | s.push(b + a); 825 | } else if(token.equals("-")) { 826 | s.push(b - a); 827 | } else if(token.equals("*")) { 828 | s.push(b * a); 829 | } else { 830 | s.push(b / a); 831 | } 832 | } 833 | return s.pop(); 834 | } 835 | ``` 836 | 837 | # 二分 838 | ## 二分搜索 839 | ```java 840 | public int binarySearch(int[] arr, int start, int end, int hkey){ 841 | if (start > end) { 842 | return -1; 843 | } 844 | 845 | int mid = start + (end - start) / 2; //防止溢位 846 | if (arr[mid] > hkey) { 847 | return binarySearch(arr, start, mid - 1, hkey); 848 | } 849 | if (arr[mid] < hkey) { 850 | return binarySearch(arr, mid + 1, end, hkey); 851 | } 852 | return mid; 853 | } 854 | ``` 855 | 856 | ## X的平方根 857 | ```java 858 | public int sqrt(int x) { 859 | if (x < 0) { 860 | throw new IllegalArgumentException(); 861 | } else if (x <= 1) { 862 | return x; 863 | } 864 | 865 | int start = 1, end = x; 866 | // 直接对答案可能存在的区间进行二分 => 二分答案 867 | while (start + 1 < end) { 868 | int mid = start + (end - start) / 2; 869 | if (mid == x / mid) { 870 | return mid; 871 | } else if (mid < x / mid) { 872 | start = mid; 873 | } else { 874 | end = mid; 875 | } 876 | } 877 | if (end > x / end) { 878 | return start; 879 | } 880 | return end; 881 | } 882 | ``` 883 | 884 | # 哈希表 885 | ## 两数之和 886 | > 给一个整数数组,找到两个数使得他们的和等于一个给定的数 target。需要实现的函数twoSum需要返回这两个数的下标。 887 | 888 | 用一个hashmap来记录,key记录target-numbers[i]的值,value记录numbers[i]的i的值,如果碰到一个 889 | numbers[j]在hashmap中存在,那么说明前面的某个numbers[i]和numbers[j]的和为target,i和j即为答案 890 | ```java 891 | public int[] twoSum(int[] numbers, int target) { 892 | 893 | HashMap map = new HashMap<>(); 894 | 895 | for (int i = 0; i < numbers.length; i++) { 896 | if (map.containsKey(numbers[i])) { 897 | return new int[]{map.get(numbers[i]), i}; 898 | } 899 | map.put(target - numbers[i], i); 900 | } 901 | 902 | return new int[]{}; 903 | } 904 | ``` 905 | 906 | ## 连续数组 907 | > 给一个二进制数组,找到 0 和 1 数量相等的子数组的最大长度 908 | 909 | 使用一个数字sum维护到i为止1的数量与0的数量的差值。在loop i的同时维护sum并将其插入hashmap中。对于某一个sum值,若hashmap中已有这个值,则当前的i与sum上一次出现的位置之间的序列0的数量与1的数量相同。 910 | ```java 911 | public int findMaxLength(int[] nums) { 912 | Map prefix = new HashMap<>(); 913 | int sum = 0; 914 | int max = 0; 915 | prefix.put(0, -1); // 当第一个0 1数量相等的情况出现时,数组下标减去-1得到正确的长度 916 | for (int i = 0; i < nums.length; i++) { 917 | int num = nums[i]; 918 | if (num == 0) { 919 | sum--; 920 | } else { 921 | sum++; 922 | } 923 | 924 | if (prefix.containsKey(sum)) { 925 | max = Math.max(max, i - prefix.get(sum)); 926 | } else { 927 | prefix.put(sum, i); 928 | } 929 | } 930 | 931 | return max; 932 | } 933 | ``` 934 | 935 | ## 最长无重复字符的子串 936 | 用HashMap记录每一个字母出现的位置。设定一个左边界, 到当前枚举到的位置之间的字符串为不含重复字符的子串。若新碰到的字符的上一次的位置在左边界右边, 则需要向右移动左边界 937 | ```java 938 | public int lengthOfLongestSubstring(String s) { 939 | if (s == null || s.length() == 0) { 940 | return 0; 941 | } 942 | HashMap map = new HashMap<>(); 943 | int max = Integer.MIN_VALUE; 944 | int start = -1; // 计算无重复字符子串开始的位置 945 | int current = 0; 946 | for (int i = 0; i < s.length(); i++) { 947 | if (map.containsKey(s.charAt(i))) { 948 | int tmp = map.get(s.charAt(i)); 949 | if (tmp >= start) { // 上一次的位置在左边界右边, 则需要向右移动左边界 950 | start = tmp; 951 | } 952 | } 953 | 954 | map.put(s.charAt(i), i); 955 | max = Math.max(max, i - start); 956 | } 957 | return max; 958 | } 959 | ``` 960 | 961 | ## 最多点在一条直线上 962 | > 给出二维平面上的n个点,求最多有多少点在同一条直线上 963 | ```java 964 | class Point { 965 | int x; 966 | int y; 967 | Point() { 968 | x = 0; y = 0; 969 | } 970 | Point(int a, int b) { 971 | x = a; y = b; 972 | } 973 | } 974 | ``` 975 | 通过HashMap记录下两个点之间的斜率相同出现的次数,注意考虑点重合的情况 976 | ```java 977 | public int maxPoints(Point[] points) { 978 | if (points == null) { 979 | return 0; 980 | } 981 | 982 | int max = 0; 983 | for (int i = 0; i < points.length; i++) { 984 | Map map = new HashMap<>(); 985 | int maxPoints = 0; 986 | int overlap = 0; 987 | for (int j = i + 1; j < points.length; j++) { 988 | if (points[i].x == points[j].x && points[i].y == points[j].y) { 989 | overlap++; // 两个点重合的情况记录下来 990 | continue; 991 | } 992 | double rate = (double)(points[i].y - points[j].y) / (points[i].x - points[j].x); 993 | if (map.containsKey(rate)) { 994 | map.put(rate, map.get(rate) + 1); 995 | } else { 996 | map.put(rate, 2); 997 | } 998 | maxPoints = Math.max(maxPoints, map.get(rate)); 999 | } 1000 | if (maxPoints == 0) maxPoints = 1; 1001 | max = Math.max(max, maxPoints + overlap); 1002 | } 1003 | return max; 1004 | } 1005 | ``` 1006 | 1007 | # 堆 / 优先队列 1008 | ## 前K大的数 1009 | ```java 1010 | // 维护一个 PriorityQueue,以返回前K的数 1011 | public int[] topk(int[] nums, int k) { 1012 | int[] result = new int[k]; 1013 | if (nums == null || nums.length < k) { 1014 | return result; 1015 | } 1016 | 1017 | Queue pq = new PriorityQueue<>(); 1018 | for (int num : nums) { 1019 | pq.add(num); 1020 | if (pq.size() > k) { 1021 | pq.poll(); 1022 | } 1023 | } 1024 | 1025 | for (int i = k - 1; i >= 0; i--) { 1026 | result[i] = pq.poll(); 1027 | } 1028 | 1029 | return result; 1030 | } 1031 | ``` 1032 | 1033 | ## 前K大的数II 1034 | > 实现一个数据结构,提供下面两个接口:1.add(number) 添加一个元素 2.topk() 返回前K大的数 1035 | ```java 1036 | public class Solution { 1037 | private int maxSize; 1038 | private Queue minheap; 1039 | public Solution(int k) { 1040 | minheap = new PriorityQueue<>(); 1041 | maxSize = k; 1042 | } 1043 | 1044 | public void add(int num) { 1045 | if (minheap.size() < maxSize) { 1046 | minheap.offer(num); 1047 | return; 1048 | } 1049 | 1050 | if (num > minheap.peek()) { 1051 | minheap.poll(); 1052 | minheap.offer(num); 1053 | } 1054 | } 1055 | 1056 | public List topk() { 1057 | Iterator it = minheap.iterator(); 1058 | List result = new ArrayList(); 1059 | while (it.hasNext()) { 1060 | result.add((Integer) it.next()); 1061 | } 1062 | Collections.sort(result, Collections.reverseOrder()); 1063 | return result; 1064 | } 1065 | } 1066 | ``` 1067 | 1068 | ## 第K大的数 1069 | ```java 1070 | public int kthLargestElement(int k, int[] nums) { 1071 | if (nums == null || nums.length == 0 || k < 1 || k > nums.length){ 1072 | return -1; 1073 | } 1074 | return partition(nums, 0, nums.length - 1, nums.length - k); 1075 | } 1076 | 1077 | private int partition(int[] nums, int start, int end, int k) { 1078 | if (start >= end) { 1079 | return nums[k]; 1080 | } 1081 | 1082 | int left = start, right = end; 1083 | int pivot = nums[(start + end) / 2]; 1084 | 1085 | while (left <= right) { 1086 | while (left <= right && nums[left] < pivot) { 1087 | left++; 1088 | } 1089 | while (left <= right && nums[right] > pivot) { 1090 | right--; 1091 | } 1092 | if (left <= right) { 1093 | swap(nums, left, right); 1094 | left++; 1095 | right--; 1096 | } 1097 | } 1098 | 1099 | if (k <= right) { 1100 | return partition(nums, start, right, k); 1101 | } 1102 | if (k >= left) { 1103 | return partition(nums, left, end, k); 1104 | } 1105 | return nums[k]; 1106 | } 1107 | 1108 | private void swap(int[] nums, int i, int j) { 1109 | int tmp = nums[i]; 1110 | nums[i] = nums[j]; 1111 | nums[j] = tmp; 1112 | } 1113 | ``` 1114 | 1115 | # 二叉搜索树 1116 | ## 验证二叉搜索树 1117 | ```java 1118 | public boolean isValidBST(TreeNode root) { 1119 | return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE); 1120 | } 1121 | 1122 | private boolean isValidBST(TreeNode root, long min, long max){ 1123 | if (root == null) { 1124 | return true; 1125 | } 1126 | 1127 | if (root.val <= min || root.val >= max){ 1128 | return false; 1129 | } 1130 | 1131 | return isValidBST(root.left, min, root.val) && isValidBST(root.right, root.val, max); 1132 | } 1133 | ``` 1134 | 1135 | ## 第K小的元素 1136 | 增加getCount方法来获取传入节点的子节点数(包括自己),从root节点开始判断k值和子节点数的大小决定递归路径是往左还是往右。 1137 | ```java 1138 | public int kthSmallest(TreeNode root, int k) { 1139 | if (root == null) { 1140 | return 0; 1141 | } 1142 | 1143 | int leftCount = getCount(root.left); 1144 | if (leftCount >= k) { 1145 | return kthSmallest(root.left, k); 1146 | } else if (leftCount + 1 == k) { 1147 | return root.val; 1148 | } else { 1149 | return kthSmallest(root.right, k - leftCount - 1); 1150 | } 1151 | } 1152 | 1153 | private int getCount(TreeNode root) { 1154 | if (root == null) { 1155 | return 0; 1156 | } 1157 | 1158 | return getCount(root.left) + getCount(root.right) + 1; 1159 | } 1160 | ``` 1161 | 1162 | # 数组 / 双指针 1163 | ## 加一 1164 | > 给定一个非负数,表示一个数字数组,在该数的基础上+1,返回一个新的数组。该数字按照数位高低进行排列,最高位的数在列表的最前面。 1165 | ```java 1166 | public int[] plusOne(int[] digits) { 1167 | int carries = 1; 1168 | for(int i = digits.length - 1; i >= 0 && carries > 0; i--){ 1169 | int sum = digits[i] + carries; 1170 | digits[i] = sum % 10; 1171 | carries = sum / 10; 1172 | } 1173 | if(carries == 0) { 1174 | return digits; 1175 | } 1176 | 1177 | int[] rst = new int[digits.length + 1]; 1178 | rst[0] = 1; 1179 | for(int i = 1; i < rst.length; i++){ 1180 | rst[i] = digits[i - 1]; 1181 | } 1182 | return rst; 1183 | } 1184 | ``` 1185 | 1186 | ## 删除元素 1187 | > 给定一个数组和一个值,在原地删除与值相同的数字,返回新数组的长度。 1188 | ```java 1189 | public int removeElement(int[] A, int elem) { 1190 | if (A == null || A.length == 0) { 1191 | return 0; 1192 | } 1193 | 1194 | int index = 0; 1195 | for (int i = 0; i < A.length; i++) { 1196 | if (A[i] != elem) { 1197 | A[index++] = A[i]; 1198 | } 1199 | } 1200 | 1201 | return index; 1202 | } 1203 | ``` 1204 | 1205 | ## 删除排序数组中的重复数字 1206 | > 在原数组中“删除”重复出现的数字,使得每个元素只出现一次,并且返回“新”数组的长度。 1207 | ```java 1208 | public int removeDuplicates(int[] A) { 1209 | if (A == null || A.length == 0) { 1210 | return 0; 1211 | } 1212 | 1213 | int size = 0; 1214 | for (int i = 0; i < A.length; i++) { 1215 | if (A[i] != A[size]) { 1216 | A[++size] = A[i]; 1217 | } 1218 | } 1219 | return size + 1; 1220 | } 1221 | ``` 1222 | 1223 | ## 我的日程安排表 I 1224 | > 实现MyCalendar类来存储活动。如果新添加的活动没有重复,则可以添加。类将有方法book(int start,int end)。这代表左闭右开的间隔[start,end)有了预定,范围内的实数x,都满足start <= x < end,返回true。 否则,返回false,并且事件不会添加到日历中。 1225 | 1226 | TreeMap 是一个有序的key-value集合,它通过 [红黑树](http://www.cnblogs.com/skywang12345/p/3245399.html) 实现,继承于AbstractMap,所以它是一个Map,即一个key-value集合。TreeMap可以查询小于等于某个值的最大的key,也可查询大于等于某个值的最小的key。 1227 | 元素的顺序可以改变,并且对新的数组不会有影响。 1228 | ```java 1229 | class MyCalendar { 1230 | TreeMap calendar; 1231 | 1232 | MyCalendar() { 1233 | calendar = new TreeMap(); 1234 | } 1235 | 1236 | public boolean book(int start, int end) { 1237 | Integer previous = calendar.floorKey(start), next = calendar.ceilingKey(start); 1238 | if ((previous == null || calendar.get(previous) <= start) && (next == null || end <= next)) { 1239 | calendar.put(start, end); 1240 | return true; 1241 | } 1242 | return false; 1243 | } 1244 | } 1245 | ``` 1246 | 1247 | ## 合并排序数组 1248 | > 合并两个排序的整数数组A和B变成一个新的数组。可以假设A具有足够的空间去添加B中的元素。 1249 | ```java 1250 | public void mergeSortedArray(int[] A, int m, int[] B, int n) { 1251 | int i = m - 1, j = n - 1, index = m + n - 1; 1252 | while (i >= 0 && j >= 0) { 1253 | if (A[i] > B[j]) { 1254 | A[index--] = A[i--]; 1255 | } else { 1256 | A[index--] = B[j--]; 1257 | } 1258 | } 1259 | while (i >= 0) { 1260 | A[index--] = A[i--]; 1261 | } 1262 | while (j >= 0) { 1263 | A[index--] = B[j--]; 1264 | } 1265 | } 1266 | ``` 1267 | 1268 | # 贪心 1269 | ## 买卖股票的最佳时机 1270 | > 假设有一个数组,它的第i个元素是一支给定的股票在第i天的价格。如果你最多只允许完成一次交易(例如,一次买卖股票),设计一个算法来找出最大利润。 1271 | ```java 1272 | public int maxProfit(int[] prices) { 1273 | if (prices == null || prices.length == 0) { 1274 | return 0; 1275 | } 1276 | 1277 | int min = Integer.MAX_VALUE; //记录最低的价格 1278 | int profit = 0; 1279 | for (int price : prices) { 1280 | min = Math.min(price, min); 1281 | profit = Math.max(price - min, profit); 1282 | } 1283 | 1284 | return profit; 1285 | } 1286 | ``` 1287 | 1288 | ## 买卖股票的最佳时机 II 1289 | > 给定一个数组 prices 表示一支股票每天的价格。可以完成任意次数的交易, 不过不能同时参与多个交易,设计一个算法求出最大的利润。 1290 | 1291 | 贪心:只要相邻的两天股票的价格是上升的, 我们就进行一次交易, 获得一定利润。 1292 | ```java 1293 | public int maxProfit(int[] prices) { 1294 | int profit = 0; 1295 | for (int i = 0; i < prices.length - 1; i++) { 1296 | int diff = prices[i + 1] - prices[i]; 1297 | if (diff > 0) { 1298 | profit += diff; 1299 | } 1300 | } 1301 | return profit; 1302 | } 1303 | ``` 1304 | 1305 | ## 最大子数组 1306 | > 给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。 1307 | ```java 1308 | public int maxSubArray(int[] A) { 1309 | if (A == null || A.length == 0){ 1310 | return 0; 1311 | } 1312 | //max记录全局最大值,sum记录区间和,如果当前sum>0,那么可以继续和后面的数求和,否则就从0开始 1313 | int max = Integer.MIN_VALUE, sum = 0; 1314 | for (int i = 0; i < A.length; i++) { 1315 | sum += A[i]; 1316 | max = Math.max(max, sum); 1317 | sum = Math.max(sum, 0); 1318 | } 1319 | 1320 | return max; 1321 | } 1322 | ``` 1323 | 1324 | ## 主元素 1325 | 给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一(可以假设数组非空,且数组中总是存在主元素)。 1326 | ```java 1327 | public int majorityNumber(List nums) { 1328 | int currentMajor = 0; 1329 | int count = 0; 1330 | 1331 | for(Integer num : nums) { 1332 | if(count == 0) { 1333 | currentMajor = num; 1334 | } 1335 | 1336 | if(num == currentMajor) { 1337 | count++; 1338 | } else { 1339 | count--; 1340 | } 1341 | } 1342 | return currentMajor; 1343 | } 1344 | ``` 1345 | 1346 | # 字符串处理 1347 | ## 生成括号 1348 | > 给定 n,表示有 n 对括号, 请写一个函数以将其生成所有的括号组合,并返回组合结果。 1349 | ```java 1350 | public List generateParenthesis(int n) { 1351 | List res = new ArrayList<>(); 1352 | helper(n, n, "", res); 1353 | return res; 1354 | } 1355 | 1356 | // DFS 1357 | private void helper(int nL, int nR, String parenthesis, List res) { 1358 | // nL 和 nR 分别代表左右括号剩余的数量 1359 | if (nL < 0 || nR < 0) { 1360 | return; 1361 | } 1362 | 1363 | if (nL == 0 && nR == 0) { 1364 | res.add(parenthesis); 1365 | return; 1366 | } 1367 | helper(nL - 1, nR, parenthesis + "(", res); 1368 | if (nL >= nR) { 1369 | return; 1370 | } 1371 | helper(nL, nR - 1, parenthesis + ")", res); 1372 | } 1373 | ``` 1374 | 1375 | ## Excel表列标题 1376 | > 给定一个正整数,返回相应的列标题,如Excel表中所示。如1 -> A,2 -> B...26 -> Z,27 -> AA 1377 | ```java 1378 | public String convertToTitle (int n) { 1379 | StringBuilder str = new StringBuilder(); 1380 | 1381 | while (n > 0) { 1382 | n--; 1383 | str.append ( (char) ( (n % 26) + 'A')); 1384 | n /= 26; 1385 | } 1386 | return str.reverse().toString(); 1387 | } 1388 | ``` 1389 | 1390 | ## 翻转游戏 1391 | > 翻转游戏:给定一个只包含两种字符的字符串:+和-,你和你的小伙伴轮流翻转"++"变成"--"。当一个人无法采取行动时游戏结束,另一个人将是赢家。编写一个函数,计算字符串在一次有效移动后的所有可能状态。 1392 | ```java 1393 | public List generatePossibleNextMoves (String s) { 1394 | List list = new ArrayList(); 1395 | for (int i = -1; (i = s.indexOf ("++", i + 1)) >= 0;) { 1396 | list.add (s.substring (0, i) + "--" + s.substring (i + 2)); 1397 | } 1398 | return list; 1399 | } 1400 | ``` 1401 | 1402 | ## 翻转字符串中的单词 1403 | > 给定一个字符串,逐个翻转字符串中的每个单词。 1404 | ```java 1405 | public String reverseWords(String s) { 1406 | if(s.length() == 0 || s == null){ 1407 | return " "; 1408 | } 1409 | //按照空格将s切分 1410 | String[] array = s.split(" "); 1411 | StringBuilder sb = new StringBuilder(); 1412 | //从后往前遍历array,在sb中插入单词 1413 | for(int i = array.length - 1; i >= 0; i--){ 1414 | if(!array[i].equals("")) { 1415 | if (sb.length() > 0) { 1416 | sb.append(" "); 1417 | } 1418 | 1419 | sb.append(array[i]); 1420 | } 1421 | } 1422 | return sb.toString(); 1423 | } 1424 | ``` 1425 | 1426 | ## 转换字符串到整数 1427 | > 实现atoi这个函数,将一个字符串转换为整数。如果没有合法的整数,返回0。如果整数超出了32位整数的范围,返回INT_MAX(2147483647)如果是正整数,或者INT_MIN(-2147483648)如果是负整数。 1428 | 1429 | ```java 1430 | public int myAtoi(String str) { 1431 | if(str == null) { 1432 | return 0; 1433 | } 1434 | str = str.trim(); 1435 | if (str.length() == 0) { 1436 | return 0; 1437 | } 1438 | 1439 | int sign = 1; 1440 | int index = 0; 1441 | 1442 | if (str.charAt(index) == '+') { 1443 | index++; 1444 | } else if (str.charAt(index) == '-') { 1445 | sign = -1; 1446 | index++; 1447 | } 1448 | long num = 0; 1449 | for (; index < str.length(); index++) { 1450 | if (str.charAt(index) < '0' || str.charAt(index) > '9') { 1451 | break; 1452 | } 1453 | num = num * 10 + (str.charAt(index) - '0'); 1454 | if (num > Integer.MAX_VALUE ) { 1455 | break; 1456 | } 1457 | } 1458 | if (num * sign >= Integer.MAX_VALUE) { 1459 | return Integer.MAX_VALUE; 1460 | } 1461 | if (num * sign <= Integer.MIN_VALUE) { 1462 | return Integer.MIN_VALUE; 1463 | } 1464 | return (int)num * sign; 1465 | } 1466 | ``` 1467 | 1468 | ## 最长公共前缀 1469 | ```java 1470 | public String longestCommonPrefix(String[] strs) { 1471 | if (strs == null || strs.length == 0) { 1472 | return ""; 1473 | } 1474 | String prefix = strs[0]; 1475 | for(int i = 1; i < strs.length; i++) { 1476 | int j = 0; 1477 | while (j < strs[i].length() && j < prefix.length() && strs[i].charAt(j) == prefix.charAt(j)) { 1478 | j++; 1479 | } 1480 | if( j == 0) { 1481 | return ""; 1482 | } 1483 | prefix = prefix.substring(0, j); 1484 | } 1485 | return prefix; 1486 | } 1487 | ``` 1488 | 1489 | ## 回文数 1490 | > 判断一个正整数是不是回文数。回文数的定义是,将这个数反转之后,得到的数仍然是同一个数。 1491 | ```java 1492 | public boolean palindromeNumber(int num) { 1493 | // Write your code here 1494 | if(num < 0){ 1495 | return false; 1496 | } 1497 | int div = 1; 1498 | while(num / div >= 10){ 1499 | div *= 10; 1500 | } 1501 | while(num > 0){ 1502 | if(num / div != num % 10){ 1503 | return false; 1504 | } 1505 | num = (num % div) / 10; 1506 | div /= 100; 1507 | } 1508 | return true; 1509 | } 1510 | ``` 1511 | 1512 | # 动态规划 1513 | ## 单词拆分 1514 | > 给定字符串 s 和单词字典 dict,确定 s 是否可以分成一个或多个以空格分隔的子串,并且这些子串都在字典中存在。 1515 | ```java 1516 | public boolean wordBreak(String s, Set dict) { 1517 | // write your code here 1518 | int maxLength = getMaxLength(dict); 1519 | 1520 | // 长度为n的单词 有n + 1个切割点 比如: _l_i_n_t_ 1521 | boolean[] canBreak = new boolean[s.length() + 1]; 1522 | // 当s长度为0时 1523 | canBreak[0] = true; 1524 | 1525 | for(int i = 1; i < canBreak.length; i++){ 1526 | for(int j = 1; j <= maxLength && j <= i; j++){ 1527 | //i - j 表示从 i 点开始往前j个点的位置 1528 | String str = s.substring(i - j,i); 1529 | //如果此str在词典中 并且 str之前的 字符串可以拆分 1530 | if(dict.contains(str) && canBreak[i - j]){ 1531 | canBreak[i] = true; 1532 | break; 1533 | } 1534 | } 1535 | } 1536 | 1537 | return canBreak[canBreak.length - 1]; 1538 | } 1539 | 1540 | private int getMaxLength(Set dict){ 1541 | int max = 0; 1542 | for(String s : dict){ 1543 | max = Math.max(max,s.length()); 1544 | } 1545 | return max; 1546 | } 1547 | ``` 1548 | 1549 | ## 爬楼梯 1550 | > 假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部? 1551 | ```java 1552 | public int climbStairs(int n) { 1553 | if (n == 0) return 0; 1554 | int[] array = new int[n + 1]; 1555 | array[0] = 1; 1556 | if (array.length > 1) { 1557 | array[1] = 1; 1558 | } 1559 | 1560 | for(int i = 2; i < array.length; i++) { 1561 | array[i] = array[i - 1] + array[i - 2]; 1562 | } 1563 | return array[n]; 1564 | } 1565 | ``` 1566 | 1567 | ## 打劫房屋 1568 | > 假设你是一个专业的窃贼,准备沿着一条街打劫房屋。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,在不触动报警装置的情况下, 你最多可以得到多少钱 。 1569 | ```java 1570 | public long houseRobber(int[] A) { 1571 | if (A.length == 0) return 0; 1572 | long[] res = new long[A.length + 1]; 1573 | res[0] = 0; 1574 | res[1] = A[0]; 1575 | for (int i = 2; i < res.length; i++) { 1576 | res[i] = Math.max(res[i - 2] + A[i - 1], res[i - 1]); 1577 | } 1578 | return res[A.length]; 1579 | } 1580 | ``` 1581 | 1582 | ## 编辑距离 1583 | > 给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。你总共三种操作方法:插入一个字符、删除一个字符、替换一个字符。 1584 | ```java 1585 | public int minDistance(String word1, String word2) { 1586 | // write your code here 1587 | int n = word1.length(); 1588 | int m = word2.length(); 1589 | int[][] dp = new int[n + 1][m + 1]; 1590 | for (int i = 0; i < n + 1; i++){ 1591 | dp[i][0] = i; 1592 | } 1593 | for (int j = 0; j < m + 1; j++){ 1594 | dp[0][j] = j; 1595 | } 1596 | for (int i = 1; i< n + 1; i++){ 1597 | for (int j = 1; j < m + 1; j++){ 1598 | if (word1.charAt(i - 1) == word2.charAt(j - 1)){ 1599 | dp[i][j] = dp[i - 1][j - 1]; 1600 | } else { 1601 | dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])); 1602 | } 1603 | } 1604 | } 1605 | return dp[n][m]; 1606 | } 1607 | ``` 1608 | 1609 | ## 乘积最大子序列 1610 | ```java 1611 | public int maxProduct(List nums) { 1612 | // 分别记录正数最大值和负数最小值 1613 | int[] max = new int[nums.size()]; 1614 | int[] min = new int[nums.size()]; 1615 | 1616 | min[0] = max[0] = nums.get(0); 1617 | int result = nums.get(0); 1618 | for (int i = 1; i < nums.size(); i++) { 1619 | min[i] = max[i] = nums.get(i); 1620 | if (nums.get(i) > 0) { 1621 | max[i] = Math.max(max[i], max[i - 1] * nums.get(i)); 1622 | min[i] = Math.min(min[i], min[i - 1] * nums.get(i)); 1623 | } else if (nums.get(i) < 0) { 1624 | max[i] = Math.max(max[i], min[i - 1] * nums.get(i)); 1625 | min[i] = Math.min(min[i], max[i - 1] * nums.get(i)); 1626 | } 1627 | 1628 | result = Math.max(result, max[i]); 1629 | } 1630 | 1631 | return result; 1632 | } 1633 | ``` 1634 | 1635 | # 矩阵 1636 | ## 螺旋矩阵 1637 | > 给定一个包含 m x n 个要素的矩阵,(m 行, n 列),按照螺旋顺序,返回该矩阵中的所有要素。 1638 | ```java 1639 | public List spiralOrder(int[][] matrix) { 1640 | ArrayList rst = new ArrayList(); 1641 | if(matrix == null || matrix.length == 0) { 1642 | return rst; 1643 | } 1644 | 1645 | int rows = matrix.length; 1646 | int cols = matrix[0].length; 1647 | int count = 0; 1648 | while(count * 2 < rows && count * 2 < cols){ 1649 | for (int i = count; i < cols - count; i++) { 1650 | rst.add(matrix[count][i]); 1651 | } 1652 | 1653 | for (int i = count + 1; i < rows - count; i++) { 1654 | rst.add(matrix[i][cols - count - 1]); 1655 | } 1656 | 1657 | if (rows - 2 * count == 1 || cols - 2 * count == 1) { // 如果只剩1行或1列 1658 | break; 1659 | } 1660 | 1661 | for (int i = cols - count - 2; i >= count; i--) { 1662 | rst.add(matrix[rows - count - 1][i]); 1663 | } 1664 | 1665 | for (int i = rows - count - 2; i >= count + 1; i--) { 1666 | rst.add(matrix[i][count]); 1667 | } 1668 | 1669 | count++; 1670 | } 1671 | return rst; 1672 | } 1673 | ``` 1674 | 1675 | ## 判断数独是否合法 1676 | > 请判定一个数独是否有效。该数独可能只填充了部分数字,其中缺少的数字用 . 表示。 1677 | 1678 | 维护一个HashSet用来记同一行、同一列、同一九宫格是否存在相同数字 1679 | ```java 1680 | public boolean isValidSudoku(char[][] board) { 1681 | Set seen = new HashSet(); 1682 | for (int i=0; i<9; ++i) { 1683 | for (int j=0; j<9; ++j) { 1684 | char number = board[i][j]; 1685 | if (number != '.') 1686 | if (!seen.add(number + " in row " + i) || 1687 | !seen.add(number + " in column " + j) || 1688 | !seen.add(number + " in block " + i / 3 + "-" + j / 3)) 1689 | return false; 1690 | } 1691 | } 1692 | return true; 1693 | } 1694 | ``` 1695 | 1696 | ## 旋转图像 1697 | > 给定一个N×N的二维矩阵表示图像,90度顺时针旋转图像。 1698 | ```java 1699 | public void rotate(int[][] matrix) { 1700 | if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { 1701 | return; 1702 | } 1703 | 1704 | int length = matrix.length; 1705 | 1706 | for (int i = 0; i < length / 2; i++) { 1707 | for (int j = 0; j < (length + 1) / 2; j++){ 1708 | int tmp = matrix[i][j]; 1709 | matrix[i][j] = matrix[length - j - 1][i]; 1710 | matrix[length -j - 1][i] = matrix[length - i - 1][length - j - 1]; 1711 | matrix[length - i - 1][length - j - 1] = matrix[j][length - i - 1]; 1712 | matrix[j][length - i - 1] = tmp; 1713 | } 1714 | } 1715 | } 1716 | ``` 1717 | 1718 | # 二进制 / 位运算 1719 | ## 落单的数 1720 | > 给出 2 * n + 1个数字,除其中一个数字之外其他每个数字均出现两次,找到这个数字。 1721 | 1722 | 异或运算具有很好的性质,相同数字异或运算后为0,并且具有交换律和结合律,故将所有数字异或运算后即可得到只出现一次的数字。 1723 | ```java 1724 | public int singleNumber(int[] A) { 1725 | if(A == null || A.length == 0) { 1726 | return -1; 1727 | } 1728 | int rst = 0; 1729 | for (int i = 0; i < A.length; i++) { 1730 | rst ^= A[i]; 1731 | } 1732 | return rst; 1733 | } 1734 | ``` 1735 | 1736 | ## 格雷编码 1737 | > 格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个二进制的差异。给定一个非负整数 n ,表示该代码中所有二进制的总数,请找出其格雷编码顺序。一个格雷编码顺序必须以 0 开始,并覆盖所有的 2n 个整数。例子——输入:2;输出:[0, 1, 3, 2];解释: 0 - 00,1 - 01,3 - 11,2 - 10 1738 | 1739 | 格雷码生成公式:G(i) = i ^ (i >> 2) 1740 | ```java 1741 | public ArrayList grayCode(int n) { 1742 | ArrayList result = new ArrayList(); 1743 | for (int i = 0; i < (1 << n); i++) { 1744 | result.add(i ^ (i >> 1)); 1745 | } 1746 | return result; 1747 | } 1748 | ``` 1749 | 1750 | # 其他 1751 | ## 反转整数 1752 | > 将一个整数中的数字进行颠倒,当颠倒后的整数溢出时,返回 0 (标记为 32 位整数)。 1753 | ```java 1754 | public int reverseInteger(int n) { 1755 | int reversed_n = 0; 1756 | 1757 | while (n != 0) { 1758 | int temp = reversed_n * 10 + n % 10; 1759 | n = n / 10; 1760 | if (temp / 10 != reversed_n) { 1761 | reversed_n = 0; 1762 | break; 1763 | } 1764 | reversed_n = temp; 1765 | } 1766 | return reversed_n; 1767 | } 1768 | ``` 1769 | 1770 | ## LRU缓存策略 1771 | > 为最近最少使用(LRU)缓存策略设计一个数据结构,它应该支持以下操作:获取数据(get)和写入数据(set)。获取数据get(key):如果缓存中存在key,则获取其数据值(通常是正数),否则返回-1。 写入数据set(key, value):如果key还没有在缓存中,则写入其数据值。当缓存达到上限,它应该在写入新数据之前删除最近最少使用的数据用来腾出空闲位置。 1772 | ```java 1773 | public class LRUCache { 1774 | private class Node{ 1775 | Node prev; 1776 | Node next; 1777 | int key; 1778 | int value; 1779 | 1780 | public Node(int key, int value) { 1781 | this.key = key; 1782 | this.value = value; 1783 | this.prev = null; 1784 | this.next = null; 1785 | } 1786 | } 1787 | 1788 | private int capacity; 1789 | private HashMap hs = new HashMap(); 1790 | private Node head = new Node(-1, -1); 1791 | private Node tail = new Node(-1, -1); 1792 | 1793 | public LRUCache(int capacity) { 1794 | this.capacity = capacity; 1795 | tail.prev = head; 1796 | head.next = tail; 1797 | } 1798 | 1799 | public int get(int key) { 1800 | if( !hs.containsKey(key)) { //key找不到 1801 | return -1; 1802 | } 1803 | 1804 | // remove current 1805 | Node current = hs.get(key); 1806 | current.prev.next = current.next; 1807 | current.next.prev = current.prev; 1808 | 1809 | // move current to tail 1810 | move_to_tail(current); //每次get,使用次数+1,最近使用,放于尾部 1811 | 1812 | return hs.get(key).value; 1813 | } 1814 | 1815 | public void set(int key, int value) { //数据放入缓存 1816 | // get 这个方法会把key挪到最末端,因此,不需要再调用 move_to_tail 1817 | if (get(key) != -1) { 1818 | hs.get(key).value = value; 1819 | return; 1820 | } 1821 | 1822 | if (hs.size() == capacity) { //超出缓存上限 1823 | hs.remove(head.next.key); //删除头部数据 1824 | head.next = head.next.next; 1825 | head.next.prev = head; 1826 | } 1827 | 1828 | Node insert = new Node(key, value); //新建节点 1829 | hs.put(key, insert); 1830 | move_to_tail(insert); //放于尾部 1831 | } 1832 | 1833 | private void move_to_tail(Node current) { //移动数据至尾部 1834 | current.prev = tail.prev; 1835 | tail.prev = current; 1836 | current.prev.next = current; 1837 | current.next = tail; 1838 | } 1839 | } 1840 | ``` 1841 | 1842 | 1843 | 1844 | -------------------------------------------------------------------------------- /Docs/计算机网络基础.md: -------------------------------------------------------------------------------- 1 | - [网络体系的分层结构](#%e7%bd%91%e7%bb%9c%e4%bd%93%e7%b3%bb%e7%9a%84%e5%88%86%e5%b1%82%e7%bb%93%e6%9e%84) 2 | - [HTTP 相关](#http-%e7%9b%b8%e5%85%b3) 3 | - [通用头部](#%e9%80%9a%e7%94%a8%e5%a4%b4%e9%83%a8) 4 | - [请求报文](#%e8%af%b7%e6%b1%82%e6%8a%a5%e6%96%87) 5 | - [请求行](#%e8%af%b7%e6%b1%82%e8%a1%8c) 6 | - [请求方法](#%e8%af%b7%e6%b1%82%e6%96%b9%e6%b3%95) 7 | - [请求头](#%e8%af%b7%e6%b1%82%e5%a4%b4) 8 | - [请求体](#%e8%af%b7%e6%b1%82%e4%bd%93) 9 | - [响应报文](#%e5%93%8d%e5%ba%94%e6%8a%a5%e6%96%87) 10 | - [常见状态码](#%e5%b8%b8%e8%a7%81%e7%8a%b6%e6%80%81%e7%a0%81) 11 | - [响应头](#%e5%93%8d%e5%ba%94%e5%a4%b4) 12 | - [缓存机制](#%e7%bc%93%e5%ad%98%e6%9c%ba%e5%88%b6) 13 | - [HTTP 1.1](#http-11) 14 | - [HTTP 2.0](#http-20) 15 | - [HTTPS](#https) 16 | - [加密原理](#%e5%8a%a0%e5%af%86%e5%8e%9f%e7%90%86) 17 | - [TCP/IP](#tcpip) 18 | - [三次握手](#%e4%b8%89%e6%ac%a1%e6%8f%a1%e6%89%8b) 19 | - [四次挥手](#%e5%9b%9b%e6%ac%a1%e6%8c%a5%e6%89%8b) 20 | - [TCP 与 UDP 的区别](#tcp-%e4%b8%8e-udp-%e7%9a%84%e5%8c%ba%e5%88%ab) 21 | - [Socket](#socket) 22 | - [使用示例](#%e4%bd%bf%e7%94%a8%e7%a4%ba%e4%be%8b) 23 | # 网络体系的分层结构 24 | | 分层 | 说明 25 | | -- | --- 26 | | 应用层(HTTP、FTP、DNS、SMTP 等)| 定义了如何包装和解析数据,应用层是 http 协议的话,则会按照协议规定包装数据,如按照请求行、请求头、请求体包装,包装好数据后将数据传至运输层 27 | | 运输层(TCP、UDP 等) | 运输层有 TCP 和 UDP 两种,分别对应可靠和不可靠的运输。在这一层,一般都是和 Socket 打交道,Socket 是一组封装的编程调用接口,通过它,我们就能操作 TCP、UDP 进行连接的建立等。这一层指定了把数据送到对应的端口号 28 | | 网络层(IP 等) | 这一层IP协议,以及一些路由选择协议等等,所以这一层的指定了数据要传输到哪个IP地址。中间涉及到一些最优线路,路由选择算法等 29 | | 数据链路层(ARP)| 负责把 IP 地址解析为 MAC 地址,即硬件地址,这样就找到了对应的唯一的机器 30 | | 物理层 | 提供二进制流传输服务,也就是真正开始通过传输介质(有线、无线)开始进行数据的传输 31 | 32 | # HTTP 相关 33 | ## 通用头部 34 | 35 | 36 | ## 请求报文 37 | http 请求由三部分组成,分别是:请求行、请求头、请求体 38 | 39 | ### 请求行 40 | 请求行以一个方法符号开头,以空格分开,格式如下: 41 | **Method Request-URI HTTP-Version CRLF** 42 | 43 | | 名称 | 说明 44 | | -- | -- 45 | | Method | 请求方法如 post/get 46 | | Request-URI | 资源标识符(请求路径) 47 | | HTTP-Version | 请求的HTTP协议版本 48 | | CRLF | 回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符) 49 | 50 | #### 请求方法 51 | - HTTP 1.0 52 | 53 | | 名称 | 说明 54 | | -- | -- 55 | | GET | 请求获取 Request-URI 所标识的资源 56 | | POST | 在 Request-URI 所标识的资源后附加新的数据 57 | | HEAD | 请求获取由 Request-URI 所标识的资源的响应消息报头 58 | 59 | - HTTP 1.1 新增 60 | 61 | | 名称 | 说明 62 | | -- | -- 63 | | PUT | 请求服务器存储一个资源,并用 Request-URI 作为其标识 64 | | DELETE | 请求服务器删除 Request-URI 所标识的资源 65 | | TRACE | 请求服务器回送收到的请求信息,主要用于测试或诊断 66 | | CONNECT | 保留将来使用 67 | | OPTIONS | 请求查询服务器的性能,或者查询与资源相关的选项和需求 68 | 69 | - GET & POST 的区别 70 | 71 | | 区别 | 说明 72 | | -- | -- 73 | | 数据传输方式 | GET 请求通过 URL 传输数据,而 POST 的数据通过请求体传输。 74 | | 安全性 | POST的数据因为在请求主体内,所以有一定的安全性保证,而 GET 的数据在 URL 中,通过历史记录,缓存很容易查到数据信息。 75 | | 数据类型不同 | GET只允许 ASCII 字符,而 POST 无限制 76 | | 特性 | GET 是安全无害(只读)且幂等(多次提交等于一次提交),而 POST 是非安全非幂等,可能重复提交表单 77 | 78 | ### 请求头 79 | | Header | 解释 | 示例 80 | |--|--|-- 81 | | Accept | 指定客户端能够接收的内容类型 | Accept: text/plain, text/html,application/json 82 | | Accept-Charset | 浏览器可以接受的字符编码集 | Accept-Charset: iso-8859-5 83 | | Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型。 | Accept-Encoding: compress, gzip 84 | | Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh 85 | | Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges: bytes 86 | | Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== 87 | | Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache 88 | | Connection | 表示是否需要持久连接。(HTTP 1.1默认进行持久连接)| Connection: close 89 | | Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 | Cookie: $Version=1; Skin=new; 90 | | Content-Length | 请求的内容长度 | Content-Length: 348 91 | | Content-Type | 请求的与实体对应的MIME信息 | Content-Type: application/| x-www-form-urlencoded 92 | | Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT 93 | | Expect | 请求的特定的服务器行为 | Expect: 100-continue 94 | | From | 发出请求的用户的Email | From: user@email.com 95 | | Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com 96 | | If-Match | 只有请求内容与实体相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” 97 | | If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT 98 | | If-None-Match | 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” 99 | | If-Range | 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag | If-Range: “737060cd8c284d8af7ad3082f209582d” 100 | | If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT 101 | | Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards: 10 102 | | Pragma | 用来包含实现特定的指令 | Pragma: no-cache 103 | | Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== 104 | | Range | 只请求实体的一部分,指定范围 | Range: bytes=500-999 105 | | Referer | 先前网页的地址,当前请求网页紧随其后,即来路 | Referer: http://www.zcmhi.com/archives... 106 | | TE | 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 | TE: trailers,deflate;q=0.5 107 | | Upgrade | 向服务器指定某种传输协议以便服务器进行转换(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 108 | | User-Agent | User-Agent的内容包含发出请求的用户信息 | User-Agent: Mozilla/5.0 (Linux; X11) 109 | | Via | 通知中间网关或代理服务器地址,通信协议 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) 110 | | Warning | 关于消息实体的警告信息 | Warn: 199 Miscellaneous warning 111 | 112 | ### 请求体 113 | 114 | ## 响应报文 115 | 116 | - 响应报文 117 | 118 | | 名称 | 组成 119 | | -- | -- 120 | | 状态行 | 状态码如 200、协议版本等 121 | | 响应头 | 即返回的 header 122 | | 响应体 | 响应的正文数据 | 123 | 124 | ### 常见状态码 125 | 126 | **2XX 成功** 127 | - 200 OK,表示从客户端发来的请求在服务器端被正确处理 128 | - 204 No content,表示请求成功,但响应报文不含实体的主体部分 129 | - 206 Partial Content,进行范围请求 130 | 131 | **3XX 重定向** 132 | - 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL 133 | - 302 found,临时性重定向,表示资源临时被分配了新的 URL 134 | - 303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源 135 | - 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况 136 | - 307 temporary redirect,临时重定向,和 302 含义相同 137 | 138 | **4XX 客户端错误** 139 | - 400 bad request,请求报文存在语法错误 140 | - 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 141 | - 403 forbidden,表示对请求资源的访问被服务器拒绝 142 | - 404 not found,表示在服务器上没有找到请求的资源 143 | 144 | **5XX 服务器错误** 145 | - 500 internal sever error,表示服务器端在执行请求时发生了错误 146 | - 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 147 | 148 | ### 响应头 149 | 150 | ## 缓存机制 151 | ![](https://upload-images.jianshu.io/upload_images/1445840-c3465ef477e24416.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/930/format/webp) 152 | 153 | - Cache-control 主要包含以下几个字段: 154 | 155 | | 字段 | 说明 156 | | -- | -- 157 | | private | 只有客户端可以缓存 158 | | public | 客户端和代理服务器都可以缓存 159 | | max-age | 缓存的过期时间 160 | | no-cache | 需要使用对比缓存来验证缓存数据,如果服务端确认资源没有更新,则返回304,取本地缓存即可,如果有更新,则返回最新的资源。做对比缓存与 Etag 有关。 161 | | no-store | 这个字段打开,则不会进行缓存,也不会取缓存 162 | 163 | - Etag:当客户端发送第一次请求时服务端会下发当前请求资源的标识码 Etag ,下次再请求时,客户端则会通过 header 里的 If-None-Match 将这个标识码 Etag 带上,服务端将客户端传来的 Etag 与最新的资源 Etag 做对比,如果一样,则表示资源没有更新,返回304。 164 | 165 | ## HTTP 1.1 166 | 对比 1.0,HTTP 1.1 主要区别主要体现在: 167 | - **缓存处理**:在 HTTP 1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。 168 |
169 | - **带宽优化及网络连接的使用**:HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),避免带宽浪费。 170 |
171 | - **错误通知管理**:HTTP 1.1 新增了 24 个错误状态响应码,410(Gone)表示服务器上的某个资源被永久性的删除。 172 |
173 | - **Host 头处理**:HTTP 1.1 的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。 174 |
175 | - **长连接**:HTTP 1.1 支持长连接和请求的流水线理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection:keep-alive。 176 | 177 | 178 | ## HTTP 2.0 179 | Okhttp 支持配置使用 HTTP 2.0 协议,HTTP 2.0 相对于 Http1.x 来说提升是巨大的,主要有以下几点: 180 | - **二进制格式**:http1.x 是文本协议,而 http2.0 是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:Stream Identifier,即标识了该帧属于哪个 request,使得网络传输变得十分灵活。 181 |
182 | - **多路复用**:多个请求共用一个 TCP 连接,多个请求可以同时在这个 TCP 连接上并发,一个request 对应一个 id。 183 |
184 | - **header 压缩**:HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯双方各自cache一份 header fields 表,避免了重复传输,流量消耗,提高效率。 185 |
186 | - **支持服务端推送** 187 | 188 | ## HTTPS 189 | HTTP 的端口号是 80,HTTPS 是 443,HTTPS 需要到 CA 申请证书,一般免费证书很少,需要交费 190 | 191 | SSL 的全称是 Secure Sockets Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。SSL协议在1994年被Netscape发明,后来各个浏览器均支持 SSL,其最新的版本是 3.0 192 | 193 | TLS 的全称是 Transport Layer Security,即安全传输层协议,最新版本的 TLS是 IETF 制定的一种新的协议,它建立在 SSL 3.0 协议规范之上,是SSL 3.0的后续版本。在 TLS 与SSL 3.0 之间存在着显著的差别,主要是它们所支持的加密算法不同,所以 TLS 与 SSL3.0 不能互操作。虽然 TLS 与 SSL 3.0 在加密算法上不同,但在理解 HTTPS 的过程中,可以把 SSL 和 TLS 看做是同一个协议。 194 | 195 | SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。 196 | 197 | ### 加密原理 198 | HTTPS 为了兼顾安全与效率,同时使用了对称加密和非对称加密。数据是被对称加密传输的,对称加密过程需要客户端的一个密钥,为了确保能把该密钥安全传输到服务器端,采用非对称加密对该密钥进行加密传输,总的来说,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密传输。 199 | 200 | ![](https://upload-images.jianshu.io/upload_images/627325-dc83fef6ac2e6c88.png?imageMogr2/auto-orient/strip|imageView2/2/w/648/format/webp) 201 | 202 | 203 | # TCP/IP 204 | IP(Internet Protocol)协议提供了主机和主机间的通信,为了完成不同主机的通信,我们需要某种方式来唯一标识一台主机,这个标识,就是著名的 IP 地址。通过IP地址,IP 协议就能够帮我们把一个数据包发送给对方。 205 | 206 | TCP 的全称是 Transmission Control Protocol,TCP 协议在 IP 协议提供的主机间通信功能的基础上,完成这两个主机上进程对进程的通信。 207 | 208 | ## 三次握手 209 | 所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。 210 | 211 | 三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。 212 | 213 | ![](https://raw.githubusercontent.com/HIT-Alibaba/interview/master/img/tcp-connection-made-three-way-handshake.png) 214 | 215 | - 第一次握手(SYN=1, seq=x): 216 | 217 | 客户端发送一个 TCP 的 SYN 标志位置 1 的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号 (Sequence Number) 字段里。 218 | 219 | 发送完毕后,客户端进入 ``SYN_SEND`` 状态。 220 | 221 | - 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1): 222 | 223 | 服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为 1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即 X+1。 发送完毕后,服务器端进入 ``SYN_RCVD`` 状态。 224 | 225 | - 第三次握手(ACK=1,ACKnum=y+1) 226 | 227 | 客户端再次发送确认包(ACK),SYN 标志位为 0,ACK 标志位为 1,并且把服务器发来 ACK 的序号字段 +1,放在确定字段中发送给对方,并且在数据段放写 ISN 的 +1 228 | 229 | 发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。 230 | 231 | ## 四次挥手 232 | TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。 233 | 234 | ![](https://raw.githubusercontent.com/HIT-Alibaba/interview/master/img/tcp-connection-closed-four-way-handshake.png) 235 | 236 | - 第一次挥手(FIN=1,seq=x) 237 | 238 | 假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。 239 | 240 | 发送完毕后,客户端进入 FIN_WAIT_1 状态。 241 | 242 | - 第二次挥手(ACK=1,ACKnum=x+1) 243 | 244 | 服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。 245 | 246 | 发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。 247 | 248 | - 第三次挥手(FIN=1,seq=y) 249 | 250 | 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。 251 | 252 | 发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。 253 | 254 | - 第四次挥手(ACK=1,ACKnum=y+1) 255 | 256 | 客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。 257 | 258 | 服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。 259 | 260 | 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。 261 | 262 | ## TCP 与 UDP 的区别 263 | | 区别点 | TCP | UDP | 264 | | -------- | -------- | ------ | 265 | | 连接性 | 面向连接 | 无连接 | 266 | | 可靠性 | 可靠 | 不可靠| 267 | | 有序性 | 有序 | 无序 | 268 | | 面向 | 字节流 | 报文(保留报文的边界) | 269 | | 有界性 | 有界 | 无界 | 270 | | 流量控制 | 有(滑动窗口) | 无 | 271 | | 拥塞控制 | 有(慢开始、拥塞避免、快重传、快恢复) | 无 | 272 | | 传输速度 | 慢 | 快 | 273 | | 量级 | 重量级 | 轻量级 | 274 | | 双工性 | 全双工 | 一对一、一对多、多对一、多对多 | 275 | | 头部 | 大(20-60 字节) | 小(8 字节) | 276 | | 应用 | 文件传输、邮件传输、浏览器等 | 即时通讯、视频通话等 | 277 | 278 | # Socket 279 | Socket 是一组操作 TCP/UDP 的 API,像 HttpURLConnection 和 Okhttp 这种涉及到比较底层的网络请求发送的,最终当然也都是通过 Socket 来进行网络请求连接发送,而像 Volley、Retrofit 则是更上层的封装。 280 | 281 | ## 使用示例 282 | 使用 socket 的步骤如下: 283 | - 创建 ServerSocket 并监听客户连接; 284 | - 使用 Socket 连接服务端; 285 | - 通过 Socket.getInputStream()/getOutputStream() 获取输入输出流进行通信。 286 | 287 | ```java 288 | public class EchoClient { 289 | 290 | private final Socket mSocket; 291 | 292 | public EchoClient(String host, int port) throws IOException { 293 | // 创建 socket 并连接服务器 294 | mSocket = new Socket(host, port); 295 | } 296 | 297 | public void run() { 298 | // 和服务端进行通信 299 | Thread readerThread = new Thread(this::readResponse); 300 | readerThread.start(); 301 | 302 | OutputStream out = mSocket.getOutputStream(); 303 | byte[] buffer = new byte[1024]; 304 | int n; 305 | while ((n = System.in.read(buffer)) > 0) { 306 | out.write(buffer, 0, n); 307 | } 308 | } 309 | 310 | private void readResponse() { 311 | try { 312 | InputStream in = mSocket.getInputStream(); 313 | byte[] buffer = new byte[1024]; 314 | int n; 315 | while ((n = in.read(buffer)) > 0) { 316 | System.out.write(buffer, 0, n); 317 | } 318 | } catch (IOException e) { 319 | e.printStackTrace(); 320 | } 321 | } 322 | 323 | 324 | public static void main(String[] argv) { 325 | try { 326 | // 由于服务端运行在同一主机,这里我们使用 localhost 327 | EchoClient client = new EchoClient("localhost", 9877); 328 | client.run(); 329 | } catch (IOException e) { 330 | e.printStackTrace(); 331 | } 332 | } 333 | } 334 | ``` 335 | -------------------------------------------------------------------------------- /Docs/设计模式汇总.md: -------------------------------------------------------------------------------- 1 | - [设计模式分类](#%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%e5%88%86%e7%b1%bb) 2 | - [面向对象六大原则](#%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e5%85%ad%e5%a4%a7%e5%8e%9f%e5%88%99) 3 | - [工厂模式](#%e5%b7%a5%e5%8e%82%e6%a8%a1%e5%bc%8f) 4 | - [单例模式](#%e5%8d%95%e4%be%8b%e6%a8%a1%e5%bc%8f) 5 | - [建造者模式](#%e5%bb%ba%e9%80%a0%e8%80%85%e6%a8%a1%e5%bc%8f) 6 | - [原型模式](#%e5%8e%9f%e5%9e%8b%e6%a8%a1%e5%bc%8f) 7 | - [适配器模式](#%e9%80%82%e9%85%8d%e5%99%a8%e6%a8%a1%e5%bc%8f) 8 | - [观察者模式](#%e8%a7%82%e5%af%9f%e8%80%85%e6%a8%a1%e5%bc%8f) 9 | - [代理模式](#%e4%bb%a3%e7%90%86%e6%a8%a1%e5%bc%8f) 10 | - [责任链模式](#%e8%b4%a3%e4%bb%bb%e9%93%be%e6%a8%a1%e5%bc%8f) 11 | - [策略模式](#%e7%ad%96%e7%95%a5%e6%a8%a1%e5%bc%8f) 12 | - [备忘录模式](#%e5%a4%87%e5%bf%98%e5%bd%95%e6%a8%a1%e5%bc%8f) 13 | # 设计模式分类 14 | | 模式 & 描述 | 包括 15 | |--|-- 16 | | **创建型模式**
提供了一种在创建对象的同时隐藏创建逻辑的方式。| 工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern) 17 | | **结构型模式**
关注类和对象的组合。| 适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern) 18 | | **行为型模式**
特别关注对象之间的通信。| 责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern) 19 | 20 | # 面向对象六大原则 21 | | 原则 | 描述 22 | |--|-- 23 | | 单一职责原则 | 一个类只负责一个功能领域中的相应职责。 24 | | 开闭原则 | 对象应该对于扩展是开放的,对于修改是封闭的。 25 | | 里氏替换原则 | 所有引用基类的地方必须能透明地使用其子类的对象。 26 | | 依赖倒置原则 | 高层模块不依赖低层模块,两者应该依赖其对象;抽象不应该依赖细节;细节应该依赖抽象。 27 | | 接口隔离原则 | 类间的依赖关系应该建立在最小的接口上。 28 | | 迪米特原则 | 也称最少知识原则,一个对象对其他对象有最少的了解。 29 | 30 | # 工厂模式 31 | 适用于复杂对象的创建。 32 | 33 | 示例: 34 | ```java 35 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.demo); 36 | ``` 37 | 38 | ``BitmapFactory.java`` 39 | ```java 40 | // 生成 Bitmap 对象的工厂类 BitmapFactory 41 | public class BitmapFactory { 42 | ··· 43 | public static Bitmap decodeFile(String pathName) { 44 | ··· 45 | } 46 | ··· 47 | 48 | public static Bitmap decodeResource(Resources res, int id, Options opts) { 49 | validate(opts); 50 | Bitmap bm = null; 51 | InputStream is = null; 52 | 53 | try { 54 | final TypedValue value = new TypedValue(); 55 | is = res.openRawResource(id, value); 56 | 57 | bm = decodeResourceStream(res, value, is, null, opts); 58 | } 59 | ··· 60 | return bm; 61 | } 62 | ··· 63 | } 64 | 65 | ``` 66 | 67 | # 单例模式 68 | 确保某一个类只有一个实例,并自动实例化向整个系统提供这个实例,且可以避免产生多个对象消耗资源。 69 | 70 | 示例: 71 | 72 | ``InputMethodManager.java`` 73 | ```java 74 | /** 75 | * Retrieve the global InputMethodManager instance, creating it if it 76 | * doesn't already exist. 77 | * @hide 78 | */ 79 | public static InputMethodManager getInstance() { 80 | synchronized (InputMethodManager.class) { 81 | if (sInstance == null) { 82 | try { 83 | sInstance = new InputMethodManager(Looper.getMainLooper()); 84 | } catch (ServiceNotFoundException e) { 85 | throw new IllegalStateException(e); 86 | } 87 | } 88 | return sInstance; 89 | } 90 | } 91 | ``` 92 | 93 | # 建造者模式 94 | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,适用于初始化的对象比较复杂且参数较多的情况。 95 | 96 | 示例: 97 | ```java 98 | AlertDialog.Builder builder = new AlertDialog.Builder(this) 99 | .setTitle("Title") 100 | .setMessage("Message"); 101 | AlertDialog dialog = builder.create(); 102 | dialog.show(); 103 | ``` 104 | 105 | ``AlertDialog.java`` 106 | ```java 107 | public class AlertDialog extends Dialog implements DialogInterface { 108 | ··· 109 | 110 | public static class Builder { 111 | private final AlertController.AlertParams P; 112 | ··· 113 | 114 | public Builder(Context context) { 115 | this(context, resolveDialogTheme(context, ResourceId.ID_NULL)); 116 | } 117 | ··· 118 | 119 | public Builder setTitle(CharSequence title) { 120 | P.mTitle = title; 121 | return this; 122 | } 123 | ··· 124 | 125 | public Builder setMessage(CharSequence message) { 126 | P.mMessage = message; 127 | return this; 128 | } 129 | ··· 130 | 131 | public AlertDialog create() { 132 | // Context has already been wrapped with the appropriate theme. 133 | final AlertDialog dialog = new AlertDialog(P.mContext, 0, false); 134 | P.apply(dialog.mAlert); 135 | ··· 136 | return dialog; 137 | } 138 | ··· 139 | } 140 | } 141 | ``` 142 | 143 | # 原型模式 144 | 用原型模式实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 145 | 146 | 示例: 147 | 148 | ```java 149 | ArrayList newArrayList = (ArrayList) arrayList.clone(); 150 | ``` 151 | 152 | ``ArrayList.java`` 153 | ```java 154 | /** 155 | * Returns a shallow copy of this ArrayList instance. (The 156 | * elements themselves are not copied.) 157 | * 158 | * @return a clone of this ArrayList instance 159 | */ 160 | public Object clone() { 161 | try { 162 | ArrayList v = (ArrayList) super.clone(); 163 | v.elementData = Arrays.copyOf(elementData, size); 164 | v.modCount = 0; 165 | return v; 166 | } catch (CloneNotSupportedException e) { 167 | // this shouldn't happen, since we are Cloneable 168 | throw new InternalError(e); 169 | } 170 | } 171 | ``` 172 | 173 | # 适配器模式 174 | 适配器模式把一个类的接口变成客户端所期待的另一种接口,从而使原因接口不匹配而无法一起工作的两个类能够在一起工作。 175 | 176 | 示例: 177 | 178 | ```java 179 | RecyclerView recyclerView = findViewById(R.id.recycler_view); 180 | recyclerView.setAdapter(new MyAdapter()); 181 | 182 | private class MyAdapter extends RecyclerView.Adapter { 183 | 184 | @NonNull 185 | @Override 186 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 187 | ··· 188 | } 189 | 190 | ··· 191 | } 192 | ``` 193 | 194 | ``RecyclerView.java`` 195 | ```java 196 | ··· 197 | private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, 198 | boolean removeAndRecycleViews) { 199 | if (mAdapter != null) { 200 | mAdapter.unregisterAdapterDataObserver(mObserver); 201 | mAdapter.onDetachedFromRecyclerView(this); 202 | } 203 | ··· 204 | mAdapterHelper.reset(); 205 | final Adapter oldAdapter = mAdapter; 206 | mAdapter = adapter; 207 | if (adapter != null) { 208 | adapter.registerAdapterDataObserver(mObserver); 209 | adapter.onAttachedToRecyclerView(this); 210 | } 211 | if (mLayout != null) { 212 | mLayout.onAdapterChanged(oldAdapter, mAdapter); 213 | } 214 | mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); 215 | mState.mStructureChanged = true; 216 | } 217 | 218 | ··· 219 | public final class Recycler { 220 | @Nullable 221 | ViewHolder tryGetViewHolderForPositionByDeadline(int position, 222 | boolean dryRun, long deadlineNs) { 223 | ··· 224 | ViewHolder holder = null; 225 | ··· 226 | if (holder == null) { 227 | ··· 228 | holder = mAdapter.createViewHolder(RecyclerView.this, type); 229 | ··· 230 | } 231 | ··· 232 | return holder; 233 | } 234 | } 235 | 236 | ··· 237 | public abstract static class Adapter { 238 | ··· 239 | @NonNull 240 | public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); 241 | 242 | @NonNull 243 | public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { 244 | try { 245 | TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); 246 | final VH holder = onCreateViewHolder(parent, viewType); 247 | ··· 248 | holder.mItemViewType = viewType; 249 | return holder; 250 | } finally { 251 | TraceCompat.endSection(); 252 | } 253 | } 254 | ··· 255 | } 256 | ``` 257 | 258 | # 观察者模式 259 | 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。 260 | 261 | 示例: 262 | ```java 263 | MyAdapter adapter = new MyAdapter(); 264 | recyclerView.setAdapter(adapter); 265 | adapter.notifyDataSetChanged(); 266 | ``` 267 | 268 | ``RecyclerView.java`` 269 | ```java 270 | ··· 271 | private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 272 | 273 | ··· 274 | private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, 275 | boolean removeAndRecycleViews) { 276 | ··· 277 | mAdapter = adapter; 278 | if (adapter != null) { 279 | adapter.registerAdapterDataObserver(mObserver); 280 | adapter.onAttachedToRecyclerView(this); 281 | } 282 | ··· 283 | } 284 | 285 | ··· 286 | public abstract static class Adapter { 287 | private final AdapterDataObservable mObservable = new AdapterDataObservable(); 288 | ··· 289 | public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { 290 | mObservable.registerObserver(observer); 291 | } 292 | 293 | ··· 294 | public final void notifyDataSetChanged() { 295 | mObservable.notifyChanged(); 296 | } 297 | } 298 | 299 | static class AdapterDataObservable extends Observable { 300 | ··· 301 | public void notifyChanged() { 302 | for (int i = mObservers.size() - 1; i >= 0; i--) { 303 | mObservers.get(i).onChanged(); 304 | } 305 | } 306 | ··· 307 | } 308 | 309 | private class RecyclerViewDataObserver extends AdapterDataObserver { 310 | ··· 311 | @Override 312 | public void onChanged() { 313 | assertNotInLayoutOrScroll(null); 314 | mState.mStructureChanged = true; 315 | 316 | processDataSetCompletelyChanged(true); 317 | if (!mAdapterHelper.hasPendingUpdates()) { 318 | requestLayout(); 319 | } 320 | } 321 | ··· 322 | } 323 | ``` 324 | 325 | # 代理模式 326 | 为其他的对象提供一种代理以控制对这个对象的访问。适用于当无法或不想直接访问某个对象时通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。 327 | 328 | 示例: 329 | 330 | ``Context.java`` 331 | ```java 332 | public abstract class Context { 333 | ··· 334 | public abstract void startActivity(@RequiresPermission Intent intent); 335 | ··· 336 | } 337 | ``` 338 | 339 | ``ContextWrapper.java`` 340 | ```java 341 | public class ContextWrapper extends Context { 342 | Context mBase; // 代理类,实为 ContextImpl 对象 343 | ··· 344 | 345 | protected void attachBaseContext(Context base) { 346 | if (mBase != null) { 347 | throw new IllegalStateException("Base context already set"); 348 | } 349 | mBase = base; 350 | } 351 | ··· 352 | 353 | @Override 354 | public void startActivity(Intent intent) { 355 | mBase.startActivity(intent); // 核心工作交由给代理类对象 mBase 实现 356 | } 357 | ··· 358 | } 359 | ``` 360 | ``ContextImpl.java`` 361 | ```java 362 | // Context 的真正实现类 363 | class ContextImpl extends Context { 364 | ... 365 | @Override 366 | public void startActivity(Intent intent) { 367 | warnIfCallingFromSystemProcess(); 368 | startActivity(intent, null); 369 | } 370 | ... 371 | } 372 | ``` 373 | 374 | # 责任链模式 375 | 使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。 376 | 377 | ``ViewGroup.java`` 378 | ```java 379 | @UiThread 380 | public abstract class ViewGroup extends View implements ViewParent, ViewManager { 381 | ··· 382 | private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 383 | View child, int desiredPointerIdBits) { 384 | final boolean handled; 385 | ··· 386 | final MotionEvent transformedEvent; 387 | if (newPointerIdBits == oldPointerIdBits) { 388 | if (child == null || child.hasIdentityMatrix()) { 389 | if (child == null) { 390 | handled = super.dispatchTouchEvent(event); 391 | } else { 392 | ··· 393 | // 获取子 view 处理的结果 394 | handled = child.dispatchTouchEvent(event); 395 | } 396 | return handled; 397 | } 398 | transformedEvent = MotionEvent.obtain(event); 399 | } else { 400 | transformedEvent = event.split(newPointerIdBits); 401 | } 402 | 403 | // Perform any necessary transformations and dispatch. 404 | if (child == null) { 405 | handled = super.dispatchTouchEvent(transformedEvent); 406 | } else { 407 | ··· 408 | // 获取子 view 处理的结果 409 | handled = child.dispatchTouchEvent(transformedEvent); 410 | } 411 | ··· 412 | return handled; 413 | } 414 | ··· 415 | } 416 | ``` 417 | 418 | # 策略模式 419 | 策略模式定义了一系列的算法,并封装起来,提供针对同一类型问题的多种处理方式。 420 | 421 | 示例: 422 | 423 | ```java 424 | // 匀速 425 | animation.setInterpolator(new LinearInterpolator()); 426 | // 加速 427 | animation.setInterpolator(new AccelerateInterpolator()); 428 | ··· 429 | ``` 430 | 431 | ``BaseInterpolator.java`` 432 | ```java 433 | /** 434 | * An abstract class which is extended by default interpolators. 435 | */ 436 | abstract public class BaseInterpolator implements Interpolator { 437 | private @Config int mChangingConfiguration; 438 | /** 439 | * @hide 440 | */ 441 | public @Config int getChangingConfiguration() { 442 | return mChangingConfiguration; 443 | } 444 | 445 | /** 446 | * @hide 447 | */ 448 | void setChangingConfiguration(@Config int changingConfiguration) { 449 | mChangingConfiguration = changingConfiguration; 450 | } 451 | } 452 | ``` 453 | 454 | ``LinearInterpolator.java`` 455 | ```java 456 | @HasNativeInterpolator 457 | public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { 458 | ··· 459 | } 460 | ``` 461 | 462 | ``AccelerateInterpolator.java`` 463 | ```java 464 | @HasNativeInterpolator 465 | public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory { 466 | ··· 467 | } 468 | ``` 469 | 470 | # 备忘录模式 471 | 在不破坏封闭的前提下,在对象之外保存保存对象的当前状态,并且在之后可以恢复到此状态。 472 | 473 | 示例: 474 | 475 | ``Activity.java`` 476 | ```java 477 | // 保存状态 478 | protected void onSaveInstanceState(Bundle outState) { 479 | // 存储当前窗口的视图树的状态 480 | outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); 481 | 482 | outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId); 483 | // 存储 Fragment 的状态 484 | Parcelable p = mFragments.saveAllState(); 485 | if (p != null) { 486 | outState.putParcelable(FRAGMENTS_TAG, p); 487 | } 488 | if (mAutoFillResetNeeded) { 489 | outState.putBoolean(AUTOFILL_RESET_NEEDED, true); 490 | getAutofillManager().onSaveInstanceState(outState); 491 | } 492 | // 调用 ActivityLifecycleCallbacks 的 onSaveInstanceState 进行存储状态 493 | getApplication().dispatchActivitySaveInstanceState(this, outState); 494 | } 495 | 496 | ··· 497 | // onCreate 方法中恢复状态 498 | protected void onCreate(@Nullable Bundle savedInstanceState) { 499 | ··· 500 | if (savedInstanceState != null) { 501 | mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false); 502 | mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID, 503 | View.LAST_APP_AUTOFILL_ID); 504 | 505 | if (mAutoFillResetNeeded) { 506 | getAutofillManager().onCreate(savedInstanceState); 507 | } 508 | 509 | Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); 510 | mFragments.restoreAllState(p, mLastNonConfigurationInstances != null 511 | ? mLastNonConfigurationInstances.fragments : null); 512 | } 513 | mFragments.dispatchCreate(); 514 | getApplication().dispatchActivityCreated(this, savedInstanceState); 515 | ··· 516 | mRestoredFromBundle = savedInstanceState != null; 517 | mCalled = true; 518 | } 519 | ``` 520 | 521 | ``ActivityThread.java`` 522 | ```java 523 | @Override 524 | public void handleStartActivity(ActivityClientRecord r, 525 | PendingTransactionActions pendingActions) { 526 | final Activity activity = r.activity; 527 | ··· 528 | // Start 529 | activity.performStart("handleStartActivity"); 530 | r.setState(ON_START); 531 | ··· 532 | // Restore instance state 533 | if (pendingActions.shouldRestoreInstanceState()) { 534 | if (r.isPersistable()) { 535 | if (r.state != null || r.persistentState != null) { 536 | mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state, 537 | r.persistentState); 538 | } 539 | } else if (r.state != null) { 540 | mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); 541 | } 542 | } 543 | ··· 544 | } 545 | ``` 546 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | >如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料、面试资料,答案解析,进阶、架构资料,都可以加上(点击链接加入群聊[Android高级架构师交流2群](https://jq.qq.com/?_wv=1027&k=ZyMBdnsH)领取。祝愿每一位有追求的Android开发同胞都能进大厂拿高薪! 2 | 3 | # AndroidCollection 4 | 5 | # Android-Notes 6 | Android知识笔记目录: 7 | 8 | ## Java 知识点汇总 9 | 10 | * [JVM](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#jvm) 11 | * [JVM 工作流程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#jvm-工作流程) 12 | * [运行时数据区(Runtime Data Area)](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#运行时数据区runtime-data-area) 13 | * [方法指令](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#方法指令) 14 | * [类加载器](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#类加载器) 15 | * [垃圾回收 gc](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#垃圾回收-gc) 16 | * [对象存活判断](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#对象存活判断) 17 | * [垃圾收集算法](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#垃圾收集算法) 18 | * [垃圾收集器](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#垃圾收集器) 19 | * [内存模型与回收策略](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#内存模型与回收策略) 20 | * [Object](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#object) 21 | * [equals 方法](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#equals-方法) 22 | * [hashCode 方法](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#hashcode-方法) 23 | * [static](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#static) 24 | * [final](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#final) 25 | * [String、StringBuffer、StringBuilder](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#stringstringbufferstringbuilder) 26 | * [异常处理](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#异常处理) 27 | * [内部类](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#内部类) 28 | * [匿名内部类](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#匿名内部类) 29 | * [多态](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#多态) 30 | * [抽象和接口](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#抽象和接口) 31 | * [集合框架](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#集合框架) 32 | * [HashMap](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#hashmap) 33 | * [结构图](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#结构图) 34 | * [HashMap 的工作原理](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#hashmap-的工作原理) 35 | * [HashMap 与 HashTable 对比](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#hashmap-与-hashtable-对比) 36 | * [ConcurrentHashMap](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#concurrenthashmap) 37 | * [Base 1.7](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#base-17) 38 | * [Base 1.8](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#base-18) 39 | * [ArrayList](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#arraylist) 40 | * [LinkedList](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#linkedlist) 41 | * [CopyOnWriteArrayList](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#copyonwritearraylist) 42 | * [反射](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#反射) 43 | * [单例](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#单例) 44 | * [饿汉式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#饿汉式) 45 | * [双重检查模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#双重检查模式) 46 | * [静态内部类模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#静态内部类模式) 47 | * [线程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#线程) 48 | * [状态](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#状态) 49 | * [状态控制](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#状态控制) 50 | * [volatile](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#volatile) 51 | * [synchronized](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#synchronized) 52 | * [根据获取的锁分类](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#根据获取的锁分类) 53 | * [原理](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#原理) 54 | * [Lock](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#lock) 55 | * [锁的分类](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#锁的分类) 56 | * [悲观锁、乐观锁](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#悲观锁乐观锁) 57 | * [自旋锁、适应性自旋锁](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#自旋锁适应性自旋锁) 58 | * [死锁](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#死锁) 59 | * [引用类型](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#引用类型) 60 | * [动态代理](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#动态代理) 61 | * [元注解](https://github.com/274942954/AndroidCollection/blob/master/Docs/Java知识点汇总.md#元注解) 62 | 63 | 64 | ## Android 知识点汇总 65 | 66 | * [Activity](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#activity) 67 | * [生命周期](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#生命周期) 68 | * [启动模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#启动模式) 69 | * [启动过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#启动过程) 70 | * [Fragment](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#fragment) 71 | * [特点](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#特点) 72 | * [生命周期](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#生命周期-1) 73 | * [与Activity通信](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#与activity通信) 74 | * [Service](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#service) 75 | * [启动过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#启动过程-1) 76 | * [绑定过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#绑定过程) 77 | * [生命周期](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#生命周期-2) 78 | * [启用前台服务](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#启用前台服务) 79 | * [BroadcastReceiver](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#broadcastreceiver) 80 | * [注册过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#注册过程) 81 | * [ContentProvider](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#contentprovider) 82 | * [基本使用](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#基本使用) 83 | * [数据存储](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#数据存储) 84 | * [View](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#view) 85 | * [MeasureSpec](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#measurespec) 86 | * [MotionEvent](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#motionevent) 87 | * [VelocityTracker](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#velocitytracker) 88 | * [GestureDetector](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#gesturedetector) 89 | * [Scroller](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#scroller) 90 | * [View 的滑动](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#view-的滑动) 91 | * [View 的事件分发](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#view-的事件分发) 92 | * [在 Activity 中获取某个 View 的宽高](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#在-activity-中获取某个-view-的宽高) 93 | * [Draw 的基本流程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#draw-的基本流程) 94 | * [自定义 View](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#自定义-view) 95 | * [进程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#进程) 96 | * [进程生命周期](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#进程生命周期) 97 | * [多进程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#多进程) 98 | * [进程存活](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#进程存活) 99 | * [OOM_ADJ](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#oom_adj) 100 | * [进程被杀情况](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#进程被杀情况) 101 | * [进程保活方案](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#进程保活方案) 102 | * [Parcelable 接口](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#parcelable-接口) 103 | * [使用示例](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#使用示例) 104 | * [方法说明](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#方法说明) 105 | * [Parcelable 与 Serializable 对比](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#parcelable-与-serializable-对比) 106 | * [IPC](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#ipc) 107 | * [IPC方式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#ipc方式) 108 | * [Binder](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#binder) 109 | * [AIDL 通信](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#aidl-通信) 110 | * [Messenger](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#messenger) 111 | * [Window / WindowManager](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#window--windowmanager) 112 | * [Window 概念与分类](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#window-概念与分类) 113 | * [Window 的内部机制](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#window-的内部机制) 114 | * [Window 的创建过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#window-的创建过程) 115 | * [Activity 的 Window 创建过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#activity-的-window-创建过程) 116 | * [Dialog 的 Window 创建过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#dialog-的-window-创建过程) 117 | * [Toast 的 Window 创建过程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#toast-的-window-创建过程) 118 | * [Bitmap](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#bitmap) 119 | * [配置信息与压缩方式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#配置信息与压缩方式) 120 | * [常用操作](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#常用操作) 121 | * [裁剪、缩放、旋转、移动](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#裁剪缩放旋转移动) 122 | * [Bitmap与Drawable转换](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#bitmap与drawable转换) 123 | * [保存与释放](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#保存与释放) 124 | * [图片压缩](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#图片压缩) 125 | * [BitmapFactory](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#bitmapfactory) 126 | * [Bitmap创建流程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#bitmap创建流程) 127 | * [Option类](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#option类) 128 | * [基本使用](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#基本使用-1) 129 | * [内存回收](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#内存回收) 130 | * [屏幕适配](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#屏幕适配) 131 | * [单位](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#单位) 132 | * [头条适配方案](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#头条适配方案) 133 | * [刘海屏适配](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#刘海屏适配) 134 | * [Context](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#context) 135 | * [SharedPreferences](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#sharedpreferences) 136 | * [获取方式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#获取方式) 137 | * [getPreferences](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#getpreferences) 138 | * [getDefaultSharedPreferences](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#getdefaultsharedpreferences) 139 | * [getSharedPreferences](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#getsharedpreferences) 140 | * [架构](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#架构) 141 | * [apply / commit](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#apply--commit) 142 | * [注意](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#注意) 143 | * [消息机制](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#消息机制) 144 | * [Handler 机制](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#handler-机制) 145 | * [工作原理](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#工作原理) 146 | * [ThreadLocal](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#threadlocal) 147 | * [MessageQueue](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#messagequeue) 148 | * [Looper](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#looper) 149 | * [Handler](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#handler) 150 | * [线程异步](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#线程异步) 151 | * [AsyncTask](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#asynctask) 152 | * [基本使用](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#基本使用-2) 153 | * [工作原理](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#工作原理-1) 154 | * [HandlerThread](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#handlerthread) 155 | * [IntentService](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#intentservice) 156 | * [线程池](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#线程池) 157 | * [RecyclerView 优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#recyclerview-优化) 158 | * [Webview](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#webview) 159 | * [基本使用](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#基本使用-3) 160 | * [WebView](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#webview-1) 161 | * [WebSettings](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#websettings) 162 | * [WebViewClient](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#webviewclient) 163 | * [WebChromeClient](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#webchromeclient) 164 | * [Webview 加载优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#webview-加载优化) 165 | * [内存泄漏](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android知识点汇总.md#内存泄漏) 166 | 167 | ## Android 扩展知识点汇总 168 | 169 | * [ART](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#art) 170 | * [ART 功能](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#art-功能) 171 | * [预先 (AOT) 编译](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#预先-aot-编译) 172 | * [垃圾回收优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#垃圾回收优化) 173 | * [开发和调试方面的优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#开发和调试方面的优化) 174 | * [ART GC](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#art-gc) 175 | * [Apk 包体优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#apk-包体优化) 176 | * [Apk 组成结构](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#apk-组成结构) 177 | * [整体优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#整体优化) 178 | * [资源优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#资源优化) 179 | * [代码优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#代码优化) 180 | * [.arsc文件优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#arsc文件优化) 181 | * [lib目录优化](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#lib目录优化) 182 | * [Hook](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#hook) 183 | * [基本流程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#基本流程) 184 | * [使用示例](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#使用示例) 185 | * [Proguard](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#proguard) 186 | * [公共模板](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#公共模板) 187 | * [常用的自定义混淆规则](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#常用的自定义混淆规则) 188 | * [aar中增加独立的混淆配置](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#aar中增加独立的混淆配置) 189 | * [检查混淆和追踪异常](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#检查混淆和追踪异常) 190 | * [架构](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#架构) 191 | * [MVC](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#mvc) 192 | * [MVP](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#mvp) 193 | * [MVVM](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#mvvm) 194 | * [Jetpack](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#jetpack) 195 | * [架构](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#架构-1) 196 | * [使用示例](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#使用示例-1) 197 | * [NDK 开发](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#ndk-开发) 198 | * [JNI 基础](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#jni-基础) 199 | * [数据类型](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#数据类型) 200 | * [String 字符串函数操作](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#string-字符串函数操作) 201 | * [常用 JNI 访问 Java 对象方法](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#常用-jni-访问-java-对象方法) 202 | * [NDK 开发](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#ndk-开发-1) 203 | * [基础开发流程](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#基础开发流程) 204 | * [System.loadLibrary()](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#systemloadlibrary) 205 | * [CMake 构建 NDK 项目](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#cmake-构建-ndk-项目) 206 | * [常用的 Android NDK 原生 API](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#常用的-android-ndk-原生-api) 207 | * [类加载器](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#类加载器) 208 | * [双亲委托模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#双亲委托模式) 209 | * [DexPathList](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android扩展知识点.md#dexpathlist) 210 | 211 | 212 | ## Android 开源库源码分析 213 | 214 | * [LeakCanary](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#leakcanary) 215 | * [初始化注册](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#初始化注册) 216 | * [引用泄漏观察](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#引用泄漏观察) 217 | * [Dump Heap](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#dump-heap) 218 | * [EventBus](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#eventbus) 219 | * [自定义注解](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#自定义注解) 220 | * [注册订阅者](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#注册订阅者) 221 | * [发送事件](https://github.com/274942954/AndroidCollection/blob/master/Docs/Android开源库源码分析.md#发送事件) 222 | 223 | ## 设计模式汇总 224 | 225 | * [设计模式分类](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#设计模式分类) 226 | * [面向对象六大原则](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#面向对象六大原则) 227 | * [工厂模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#工厂模式) 228 | * [单例模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#单例模式) 229 | * [建造者模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#建造者模式) 230 | * [原型模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#原型模式) 231 | * [适配器模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#适配器模式) 232 | * [观察者模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#观察者模式) 233 | * [代理模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#代理模式) 234 | * [责任链模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#责任链模式) 235 | * [策略模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#策略模式) 236 | * [备忘录模式](https://github.com/274942954/AndroidCollection/blob/master/Docs/设计模式汇总.md#备忘录模式) 237 | 238 | ## Gradle知识点汇总 239 | 240 | * [依赖项配置](https://github.com/274942954/AndroidCollection/blob/master/Docs/Gradle知识点汇总.md#依赖项配置) 241 | 242 | ## 计算机网络基础 243 | 244 | * [网络体系的分层结构](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#网络体系的分层结构) 245 | * [HTTP 相关](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#http-相关) 246 | * [请求报文](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#请求报文) 247 | * [请求行](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#请求行) 248 | * [请求头](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#请求头) 249 | * [响应报文](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#响应报文) 250 | * [常见状态码](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#常见状态码) 251 | * [缓存机制](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#缓存机制) 252 | * [Https](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#https) 253 | * [Http 2.0](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#http-20) 254 | * [TCP/IP](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#tcpip) 255 | * [三次握手](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#三次握手) 256 | * [四次挥手](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#四次挥手) 257 | * [TCP 与 UDP 的区别](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#tcp-与-udp-的区别) 258 | * [Socket](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#socket) 259 | * [使用示例](https://github.com/274942954/AndroidCollection/blob/master/Docs/计算机网络基础.md#使用示例) 260 | 261 | 262 | 263 | ## 常见面试算法题汇总 264 | 265 | * [排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#排序) 266 | * [比较排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#比较排序) 267 | * [冒泡排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#冒泡排序) 268 | * [归并排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#归并排序) 269 | * [快速排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#快速排序) 270 | * [线性排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#线性排序) 271 | * [计数排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#计数排序) 272 | * [桶排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#桶排序) 273 | * [二叉树](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#二叉树) 274 | * [顺序遍历](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#顺序遍历) 275 | * [层次遍历](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#层次遍历) 276 | * [左右翻转](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#左右翻转) 277 | * [最大值](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最大值) 278 | * [最大深度](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最大深度) 279 | * [最小深度](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最小深度) 280 | * [平衡二叉树](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#平衡二叉树) 281 | * [链表](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#链表) 282 | * [删除节点](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#删除节点) 283 | * [翻转链表](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#翻转链表) 284 | * [中间元素](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#中间元素) 285 | * [判断是否为循环链表](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#判断是否为循环链表) 286 | * [合并两个已排序链表](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#合并两个已排序链表) 287 | * [链表排序](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#链表排序) 288 | * [删除倒数第N个节点](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#删除倒数第n个节点) 289 | * [两个链表是否相交](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#两个链表是否相交) 290 | * [栈 / 队列](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#栈--队列) 291 | * [带最小值操作的栈](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#带最小值操作的栈) 292 | * [有效括号](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#有效括号) 293 | * [用栈实现队列](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#用栈实现队列) 294 | * [逆波兰表达式求值](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#逆波兰表达式求值) 295 | * [二分](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#二分) 296 | * [二分搜索](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#二分搜索) 297 | * [X的平方根](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#x的平方根) 298 | * [哈希表](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#哈希表) 299 | * [两数之和](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#两数之和) 300 | * [连续数组](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#连续数组) 301 | * [最长无重复字符的子串](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最长无重复字符的子串) 302 | * [最多点在一条直线上](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最多点在一条直线上) 303 | * [堆 / 优先队列](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#堆--优先队列) 304 | * [前K大的数](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#前k大的数) 305 | * [前K大的数II](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#前k大的数ii) 306 | * [第K大的数](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#第k大的数) 307 | * [二叉搜索树](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#二叉搜索树) 308 | * [验证二叉搜索树](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#验证二叉搜索树) 309 | * [第K小的元素](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#第k小的元素) 310 | * [数组 / 双指针](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#数组--双指针) 311 | * [加一](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#加一) 312 | * [删除元素](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#删除元素) 313 | * [删除排序数组中的重复数字](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#删除排序数组中的重复数字) 314 | * [我的日程安排表 I](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#我的日程安排表-i) 315 | * [合并排序数组](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#合并排序数组) 316 | * [贪心](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#贪心) 317 | * [买卖股票的最佳时机](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#买卖股票的最佳时机) 318 | * [买卖股票的最佳时机 II](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#买卖股票的最佳时机-ii) 319 | * [最大子数组](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最大子数组) 320 | * [主元素](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#主元素) 321 | * [字符串处理](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#字符串处理) 322 | * [生成括号](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#生成括号) 323 | * [Excel表列标题](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#excel表列标题) 324 | * [翻转游戏](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#翻转游戏) 325 | * [翻转字符串中的单词](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#翻转字符串中的单词) 326 | * [转换字符串到整数](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#转换字符串到整数) 327 | * [最长公共前缀](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#最长公共前缀) 328 | * [回文数](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#回文数) 329 | * [动态规划](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#动态规划) 330 | * [单词拆分](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#单词拆分) 331 | * [爬楼梯](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#爬楼梯) 332 | * [打劫房屋](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#打劫房屋) 333 | * [编辑距离](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#编辑距离) 334 | * [乘积最大子序列](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#乘积最大子序列) 335 | * [矩阵](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#矩阵) 336 | * [螺旋矩阵](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#螺旋矩阵) 337 | * [判断数独是否合法](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#判断数独是否合法) 338 | * [旋转图像](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#旋转图像) 339 | * [二进制 / 位运算](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#二进制--位运算) 340 | * [落单的数](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#落单的数) 341 | * [格雷编码](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#格雷编码) 342 | * [其他](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#其他) 343 | * [反转整数](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#反转整数) 344 | * [LRU缓存策略](https://github.com/274942954/AndroidCollection/blob/master/Docs/常见面试算法题汇总.md#lru缓存策略) 345 | 346 | # Contanct Me 347 | 如果觉得看起来比较麻烦,需要PDF版本,或是需要更多学习资料,都可以加上QQ群领取。祝愿每一位有追求的Android开发同胞都能进大厂拿高薪! 348 | 349 | ## QQ群 350 | 351 | Android开发交流QQ群:**1087455512** (备注一下GitHub,免得被我认成打无良广告的) 352 | 353 | -------------------------------------------------------------------------------- /image/群二维码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/274942954/AndroidCollection/939ede1f0a99dad94acd63562b4b7adde3b381cc/image/群二维码.png --------------------------------------------------------------------------------