├── Android 基础 ├── 01.为什么在子线程创建Handler会抛异常?如何正确使用.md ├── 02.自定义控件优化方案.md ├── 04.谈谈Android的事件分发机制.md ├── 05.Android动画有几种,对其理解.md ├── 06.Android 内存泄漏的原因以及解决方案.md ├── 08.ScrollView嵌套ListView的解决方案及其原理.md ├── 111.简述下okhttp和retrofit的使用和联系.md ├── 116.Android10新特性及适配.md ├── 121.getSupportFragmentManager() getFragmentManager() getChildFragmentManager()的区别.md ├── 140.组件化通信如何做到的.md ├── 143.简单比较 SpareArray 和 HashMap.md ├── 145.说说Thread Local的原理.md ├── 151.URI和URL的区别是什么.md ├── 158.简述 kotlin 中 run, apply, let, with,also 的用法和区别.md ├── 173.SharedPreferences 存储比较大或者比较多的键值对会有什么问题吗?为什么?.md ├── 176.Android为什么要为应用程序签名.md ├── 178.如何优雅的使用 SharedPreferences 存储?你有哪些经验.md ├── 179.Android有哪几种进程,是如何管理的?.md ├── 18.既然RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线?.md ├── 180.EventBus是单例的,但是为什么默认构造函数是public的呢.md ├── 183.SharedPreferences 为什么存储大数据就比较占用内存?.md ├── 193.说说 compileSdkVersion、minSdkVersion、targetSdkVersion 的区别和作用.md ├── 23.Android 实现异步的几种方式,原理与各自特点.md ├── 32.Android 的四大组件都需要在清单文件中注册吗?并简述四大组件.md ├── 33.谈一谈对Android中Context理解.md ├── 34.Android线程间通信有几种方法?.md ├── 37.请介绍下Android中常用的五种布局.md ├── 40.Android SharedPreference频繁操作有什么后果?能存多少数据.md ├── 41.理解Activity,View,Window三者关系.md ├── 42.Android进程间通信的几种姿势.md ├── 45.Android的数据存储方式.md ├── 49.四种LaunchMode及其使用场景.md ├── 50.如何减小apk安装包体积.md ├── 51.谈谈 RecyclerView 的性能优化.md ├── 58.ANR异常的产生条件及解决方案.md ├── 61.ListView如何提高效率.md ├── 62.谈谈Android的安全机制.md ├── 64.Broadcast 注册方式与区别.md ├── 73.怎样避免和解决ANR.md ├── 75.Serializable和Parcelable的区别.md ├── 80.简单描述一下Intent 和IntentFilter.md ├── 81.BroadcastReceiver与LocalBroadcastReceiver的区别.md ├── 83.LinearLayout和RelativeLayout性能对比.md ├── 85.Android主线程怎么给子线程发message.md ├── 89.请简述你对AsyncTask异步任务的理解.md └── 92.XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?.md ├── Android 虚拟机 ├── 101.Linux自带多种进程通信方式,为什么Android都没采用而偏偏使用Binder通信.md ├── 115.简述Android虚拟机和JAVA虚拟机的区别.md ├── 152.简单说说 dexopt 与 dex2oat 的区别.md ├── 161.管程是什么谈谈它的重要性.md └── 26.ART和Dalvik区别.md ├── Android 进阶 ├── 135.组件化如何实现,组件化与插件化的差别在哪里,该怎么选型.md ├── 168.SharedPreferences 的加载实现是在子线程,但是为什么说 getX(key) 操作可能还会阻塞主线程呢.md ├── 181.App 是如何沙箱化,为什么要这么做?.md ├── 184.如何计算一个Bitmap占用内存的大小,怎么保证加载Bitmap不产生内存溢出?.md ├── 188.什么是 IdleHandler?有什么用?怎么用?.md ├── 189.Android里的内存缓存和磁盘缓存是怎么实现的.md ├── 190.android classloader使用的双亲委托机制是什么.md ├── 48.简述app启动过程.md ├── 57.谈谈热修复的原理.md ├── 63.LruCache 算法源码解析.md ├── 70.简述apk打包过程.md ├── 76.AOT和JIT以及混合编译的区别、优劣.md ├── 78.Android WebView 的漏洞有哪几种.md ├── 87.Android推送的基本原理.md ├── 90.请描述一下View的绘制流程.md └── 91.谈谈冷启动与热启动.md ├── Android 逆向 └── 65.使用Xposed为什么需要root.md ├── JVM ├── 141.Java new一个对象的过程中发生了什么.md ├── 148.java main 函数为什么必须用 public static void 修饰.md ├── 156.一个Java对象究竟有多大(占据多少内存).md ├── 170.什么是类加载器?类加载器有哪些?.md ├── 21.谈谈Java的垃圾回收机制以及触发时机.md ├── 46.谈谈4种gc算法.md ├── 74.谈谈JVM的内存结构和内存分配.md ├── 77.Java 对象的内存分配过程是如何保证线程安全的.md └── 84.JVM加载class文件的原理机制.md ├── Java 基础 ├── 03.谈谈你对java三大特性的理解.md ├── 07.HashMap和Hashtable的区别.md ├── 09.String,StringBuilder,StringBuffer的区别.md ├── 103.谈谈对 Java 反射的理解.md ├── 104.如何停止一个正在运行的线程.md ├── 105.并发集合与普通集合的区别.md ├── 106.Java创建对象的几种方式.md ├── 107.重载(overload)和重写(override)的区别重载的方法能否根据返回类型进行区分.md ├── 108.谈谈对 java 注解的理解.md ├── 109.什么是不可变对象,它对写并发应用有什么帮助.md ├── 110.类什么时候被初始化.md ├── 112.阐述静态变量和实例变量的区别.md ├── 113.java中==和equals和hashCode的区别.md ├── 114.Wait()与Sleep()方法的区别.md ├── 118.线程和进程的区别,为什么不仅仅用进程.md ├── 119.java对象的生命周期.md ├── 12.谈谈 Java 中多线程实现的几种方式.md ├── 120.为什么等待和通知是在 Object 类而不是 Thread 中声明的.md ├── 122.Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用.md ├── 124.什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes).md ├── 125.为什么Java不支持运算符重载.md ├── 126.Java和C++的区别.md ├── 127.try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后.md ├── 128.谈谈waitnotify关键字的理解.md ├── 129.为什么wait(), notify()和notifyAll ()必须在同步方法或者同步块中被调用?.md ├── 130.有五个布尔值,当全为true时或全为false时为true,其他为false,如何判断?当有n个时呢?.md ├── 131.多线程和单线程的区别和联系.md ├── 132.Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别.md ├── 133.java 如何保证线程安全.md ├── 136.Java常用的序列化方式都有哪些.md ├── 137.运行时异常与受检异常有何异同.md ├── 138.简单谈谈对线程池的理解.md ├── 14.接口和抽象类有什么区别.md ├── 142.TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?.md ├── 146.Map接口的实现类及区别.md ├── 147.Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别.md ├── 150.内部类访问局部变量的时候,为什么变量必须加上final修饰.md ├── 150.内部类访问局部变量的时候,为什么变量必须加上final修饰?.md ├── 153.阐述ArrayList、Vector、LinkedList的存储性能和特性.md ├── 155.有了基本数据类型,为什么还需要包装类型.md ├── 157.Error和Exception有什么区别.md ├── 16.简述HashMap工作原理.md ├── 160.Retrofit中使用jdk动态代理实现,那jdk中动态代理的实现原理是.md ├── 165.Java集合类框架的最佳实践有哪些?.md ├── 166.如何控制多线程执行顺序?.md ├── 17.谈谈 ArrayList 和 LinkList 的区别.md ├── 171.为什么Android不允许在子线程操作UI.md ├── 175.单例的DCL方式下,那个单例的私有变量要不要加volatile关键字.md ├── 177.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候, 他们的执行顺序.md ├── 182.导致线程死锁的原因?怎么解除线程死锁?.md ├── 185.如何停止一个正在运行的线程.md ├── 186.你有哪些多线程开发良好的实践.md ├── 187.反射的原理,反射创建类实例的三种方式是什么.md ├── 191.并发集合和普通集合如何区别.md ├── 192.你知道有多少种方式实现单例模式.md ├── 25.谈谈对运行结果的理解.md ├── 28.谈谈 JDK8 开始的双冒号 用法及详解.md ├── 30.谈谈 static 关键字的用法.md ├── 35.谈谈数组与链表的区别.md ├── 38.谈谈你对重入锁的理解.md ├── 39.守护线程与阻塞线程的四种情况.md ├── 47.final, finally, finalize的区别.md ├── 52.break与continue的区别.md ├── 53.谈谈你对Java中Hash码的理解.md ├── 54.简述一下类加载过程.md ├── 55.什么是线程安全?保障线程安全有哪些手段?.md ├── 56.JAVA的四种引用,及应用场景.md ├── 59.Java 中堆和栈有什么区别.md ├── 79.说说你对线程池的理解.md ├── 82.并行和并发有什么区别.md ├── 86.简单谈谈 java 中 super 和 this 的区别以及应用场景.md ├── 88.深克隆与浅克隆的区别.md ├── 93.Java nio 和 io 的区别.md ├── 94.String 为什么要设计成不可变的.md ├── 95.HashMap 排序.md ├── 96.为什么 Java 中用 char 数组比 String 更适合存储密码.md ├── 97.为什么Java中不支持多重继承.md └── 99.Java中的异常处理机制的简单原理和应用.md ├── Java 进阶 ├── 100.synchronized和volatile关键字的作用.md ├── 102.Java中用到的线程调度算法是什么?并作解释说明.md ├── 134.简述内省与暴力反射.md ├── 167.描述动态代理的几种实现方式,分别说出相应的优缺点.md ├── 172.线程池不允许使用 Executors 去创建的原因?而通过 ThreadPoolExecutor 创建线程的方式及优势?.md └── 98.死锁与活锁的区别,死锁与饥饿的区别.md ├── README.md ├── 其他 ├── 10.谈谈你的职场规划.md ├── 22.推荐系统设计.md └── 29.Android平台的优势和不足.md ├── 算法,数据结构 ├── 123.设计一个有 getMin 功能的栈.md ├── 13.如何遍历一个未知深度的树.md ├── 19.快速查找1000万个数中,最大的100个(算法).md ├── 24.简述几种排序算法的区别.md ├── 27.n个台阶,每次都可以走一步,走两步,或走三步,走到顶部一共有多少种走法.md ├── 31.从扑克牌中随机抽 5 张牌,判断是不是顺子,即这 5 张牌是不是连续的。 2-10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大小王可以看成任意的 数字.md ├── 71.有了二叉查找树、平衡树为什么还需要红黑树.md └── 72.算法 #72.md ├── 网络 ├── 117.get 和 post 请求有哪些区别.md ├── 139.为什么 TCP 连接需要三次握手,两次不可以么.md ├── 144.TCP 协议如何来保证传输的可靠性.md ├── 149.客户端不断进行请求链接会怎样?DDos(Distributed Denial of Service)攻击.md ├── 154.TCP和UDP分别对应的常见应用层协议.md ├── 159.TCP 的拥塞避免机制.md ├── 162.TCPIP 如何保证可靠性,说说 TCP 头的结构.md ├── 163.简单说说 DNS 的作用及解析流程.md ├── 164.简述:Session、Cookie 与 Application.md ├── 169.OSI 网络体系结构与 TCPIP 协议模型.md ├── 174.简述IP地址的分类.md ├── 44.https 三次握手四次挥手.md ├── 67.简述 tcp 和 udp的区别.md ├── 68.Android与服务器交互的方式中的对称加密和非对称加密是什么.md └── 69.http与https的区别.md └── 设计模式 ├── 11.设计模式的基本原则.md ├── 15.谈谈对单例的理解,以及实现方式.md ├── 20.谈谈对「简单工厂模式」和「工厂方法模式」的理解.md ├── 36谈谈对「抽象工厂方法模式」的理解.md ├── 43.谈谈对责任链模式的理解.md ├── 60.谈谈对生成器模式的理解.md └── 66.谈谈对命令模式的理解.md /Android 基础/01.为什么在子线程创建Handler会抛异常?如何正确使用.md: -------------------------------------------------------------------------------- 1 | #### 为什么在子线程创建Handler会抛异常?如何正确使用 2 | 3 | ##### 参考答案 4 | 5 | Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。正确的使用方法是: 6 | 7 | ```java 8 | private final class WorkThread extends Thread { 9 | 10 | private Handler mHandler; 11 | 12 | public Handler getHandler() { 13 | return mHandler; 14 | } 15 | public void quit() { 16 | mHandler.getLooper().quit(); 17 | } 18 | @Override 19 | public void run() { 20 | super.run(); 21 | //创建该线程对应的Looper, 22 | // 内部实现 23 | // 1。new Looper() 24 | // 2。将1步中的lopper 放在ThreadLocal里,ThreadLocal是保存数据的,主要应用场景是:线程间数据互不影响的情况 25 | // 3。在1步中的Looper的构造函数中new MessageQueue(); 26 | //其实就是创建了该线程对用的Looper,Looper里创建MessageQueue来实现消息机制 27 | //对消息机制不懂得同学可以查阅资料,网上很多也讲的很不错。 28 | Looper.prepare(); 29 | mHandler = new Handler() { 30 | @Override 31 | public void handleMessage(Message msg) { 32 | super.handleMessage(msg); 33 | Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what); 34 | } 35 | }; 36 | //开启消息的死循环处理即:dispatchMessage 37 | Looper.loop(); 38 | //注意这3个的顺序不能颠倒 39 | Log.d("WorkThread", "end"); 40 | } 41 | } 42 | ``` 43 | 44 | 45 | 46 | ##### 补充(统一格式:昵称 + 补充答案) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Android 基础/02.自定义控件优化方案.md: -------------------------------------------------------------------------------- 1 | #### 自定义控件优化方案 2 | 3 | ##### 参考答案 4 | 5 | 1. 为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。 6 | 2. 你还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。 7 | 3. 另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。 8 | 如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。这个PieChart 例子展示了如何继承ViewGroup作为自定义view的一部分。PieChart 有子views,但是它从来不测量它们。而是根据他自身的layout法则,直接设置它们的大小。 9 | 10 | 11 | 12 | ##### 补充(统一格式:昵称 + 补充答案) 13 | 14 | -------------------------------------------------------------------------------- /Android 基础/04.谈谈Android的事件分发机制.md: -------------------------------------------------------------------------------- 1 | #### 谈谈Android的事件分发机制 2 | 3 | ##### 参考答案 4 | 5 | 事件的传递流程: 6 | Activity(PhoneWindow)->DecorView->ViewGroup->View。 7 | 事件分发过程中三个重要的方法: 8 | dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent(); 9 | 事件传递规则 10 | 一般一次点击会有一系列的MotionEvent,可以简单分为:down->move->….->move->up,当一次event分发到ViewGroup时,ViewGroup收到事件后调用dispatchTouchEvent,在dispatchTouchEvent中先检查是否要拦截,若拦截则ViewGroup处理事件,否则交给有处理能力的子容器处理。 11 | 12 | 13 | 14 | ##### 补充(统一格式:昵称 + 补充答案) 15 | 16 | -------------------------------------------------------------------------------- /Android 基础/05.Android动画有几种,对其理解.md: -------------------------------------------------------------------------------- 1 | #### Android动画有几种,对其理解 2 | 3 | ##### 参考答案 4 | 5 | 1. 视图动画。视图移动、view真真的位置并未移动。 6 | 2. 帧动画。就和放电影一样,一帧一帧的播 7 | 3. 属性动画。视图移动、其位置也会随着移动。 8 | 4. 触摸返回动画。发生触摸事件时有反馈效果。比如波纹效果 9 | 5. 揭露动画。从某一个点向四周展开或者从四周向某一点聚合起来。 10 | 6. 转场动画 & 共享元素。比如切换activity。共享元素一般我们使用在转换的前后两个页面有共同元素时。 11 | 7. 视图状态动画。就是 View 在状态改变时执行的动画效果 12 | 8. 矢量图动画。在图片的基础上做动画。 13 | 9. 约束布局实现的关键帧动画。就是给需要动画效果的属性,准备一组与时间相关的值。关键的几个值。 14 | 15 | 16 | 17 | ##### 补充(统一格式:昵称 + 补充答案) 18 | 19 | -------------------------------------------------------------------------------- /Android 基础/06.Android 内存泄漏的原因以及解决方案.md: -------------------------------------------------------------------------------- 1 | #### Android 内存泄漏的原因以及解决方案 2 | 3 | ##### 参考答案 4 | 5 | 1. 内存泄漏指对象不再使用,本该被回收,却因为有其他正在使用的对象持有该对象的引用,而无法被JVM回收 6 | 7 | 2. 内存泄漏的影响: 8 | 1. 应用可用内存减少,增加堆内存压力 9 | 2. 频繁触发GC,会降低了应用的性能 10 | 3. 到一定程序会导致内存溢出错误 11 | 12 | 3. Android开发中常见内存泄漏及解决办法 13 | 1. 静态变量生命周期与应用的生命周期一样,如果静态变量持有某个Activity的上下文,则对应Activity无法释放,导致内存泄漏(单例模式) 14 | 解决办法:使用Application的上下文 15 | 2. 匿名内部类与非静态内部类因为都会持有外部类引用,当执行异步操作易导致内存泄漏 16 | 解决办法:将非静态内部类转为静态内部类+WeakReferenct的方式 17 | 3. Handler消息队列存在延时消息导致内存泄漏 18 | 在onDestroy方法中调用Handler相应的方法移除回调和删除消息 19 | 4. 各种注册的监听器忘记移除导致内存泄漏 20 | 解决办法:在onDestroy方法中取消注册 21 | 5. 资源对象未关闭导致内存泄漏,如(IO,数据库,Bitmap等) 22 | 解决办法:及时关闭资源 23 | 6. 属性动画未取消导致内存泄漏(如无限轮播图效果) 24 | 解决办法:onDestroy方法中取消动画 25 | 7. 其他解决办法:使用AAC框架 26 | 27 | 4. 内存泄漏排查工具: 28 | AS Monitor,MAT,LeakCanary 29 | 30 | 5. 扩展: Java内存管理,GC 31 | 32 | 33 | 34 | ##### 补充(统一格式:昵称 + 补充答案) 35 | 36 | ###### kuky: 37 | 38 | 1. Handler引起的内存泄漏 39 | 原因:该线程持有Handler的引用,而Handler也持有Activity的引用,这就导致了Activity不再使用时,GC回收不了Activity 40 | 解决:Handler持有的引用最好使用弱引用,在Activity被释放的时候要记得清空Message,取消Handler对象的Runnable 41 | 2. 单例模式引起的内存泄漏 42 | 原因:构建该单例的一个实例时需要传入一个Context,如果此时传入的是Activity,由于Context会被创建的实例一直持有,当Activity进入后台或者开启设置里面的不保留活动时,Activity会被销毁,但是单例持有它的Context引用,Activity没法销毁 43 | 解决:对于生命周期比Activity长的对象,要避免直接引用Activity的context,可以考虑使用ApplicationContext 44 | 3. 非静态内部类创建静态实例引起的内存泄漏 45 | 原因:非静态的内部类会自动持有外部类的引用,创建的静态实例就会一直持有的引用 46 | 解决:可以考虑把内部类声明为静态的 47 | 4. 非静态匿名内部类引起的内存泄漏 48 | 原因:如果匿名内部类被异步线程使用,可能会引起内存泄漏 49 | 解决:可以考虑把内部类声明为静态的 50 | 5. 资源对象没有关闭引起的内存泄漏 51 | 原因:资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源之后没有关闭 52 | 解决:处理完资源对象的逻辑记得关闭,最好是形成习惯现写一开一关 53 | 6. 集合对象没有及时清理引起的内存泄漏 54 | 原因:如果集合是static、不断的往里面添加东西、又忘记去清理,肯定会引起内存泄漏 55 | 解决:集合里面的东西、有加入就应该对应有相应的删除 56 | 57 | 不能通过 GC 来解决内存泄漏问题 58 | 59 | -------------------------------------------------------------------------------- /Android 基础/08.ScrollView嵌套ListView的解决方案及其原理.md: -------------------------------------------------------------------------------- 1 | #### **ScrollView嵌套ListView的解决方案及其原理** 2 | 3 | ##### 参考答案 4 | 5 | 自定义ListView 6 | 解决:重写其中的onMeasure()方法 7 | 8 | 原因: 9 | ScrollView默认把Childview设置为UNSPEFEIED模式,而该模式下的ListView给自己的测量的高度就是第一个item的高度.原理: 10 | int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); 11 | 这个方法的作用是根据大小和模式来生成一个int值,这个int值封装了模式和大小信息. 12 | 首先MeasureSpec类是View的一个静态内部类,MeasureSpec类封装了从父布局到子布局传递的布局需求. 13 | 每个MeasureSpec对象代表了宽度和高度的要求. 14 | MeasureSpec用int类型表示,前2位代表模式,后30位代表大小. 15 | 第一个参数Integer.MAX_VALUE >> 2:Integer.MAX_VALUE获取到int的最大值,但是表示大小的值size是int数值的底30位,所以把这个值右移两位,留出高两位表示布局模式. 16 | 此时这个值仍旧是一个30位所能表示的最大数,用该数作为控件的size,应该足够满足控件大小的需求. 17 | 第二个参数MeasureSpec.AT_MOST:表示这个控件适配父控件的最大空间. 18 | 19 | (以下三种仅供参考,不推荐使用) 20 | 2.手动设置ListView高度 21 | 3.使用单个ListView取代ScrollView中所有内容 22 | 4.使用LinearLayout取代ListView 23 | 24 | 参考资料: 25 | https://juejin.im/entry/5979ab4d5188253e3271c953 26 | https://juejin.im/post/5a322cbf6fb9a045204c3da1 27 | 28 | 29 | ##### 补充(统一格式:昵称 + 补充答案) 30 | 31 | -------------------------------------------------------------------------------- /Android 基础/111.简述下okhttp和retrofit的使用和联系.md: -------------------------------------------------------------------------------- 1 | #### 简述下okhttp和retrofit的使用和联系 2 | 3 | OkHttp是一个关于网络请求的第三方类库,其中封装了网络请求的get、post等操作的底层实现,是Android端目前最为火热的网络请求框架之一。 4 | 5 | get同步请求: 6 | 首先使用new创建OkHttpClient对象。 7 | 然后调用OkHttpClient对象的newCall方法生成一个Call对象,该方法接收一个Request对象,Request对象存储的就是我们的请求URL和请求参数。 8 | 最后执行Call对象的execute方法,得到Response对象,这个Response对象就是返回的结果。 9 | 10 | get异步请求: 11 | 首先使用new创建OkHttpClient对象。 12 | 然后调用OkHttpClient对象的newCall方法生成一个Call对象,该方法接收一个Request对象,Request对象存储的是我们的请求URL和请求参数。 13 | 最后执行Call对象的enqueue方法,该方法接收一个Callback回调对象,在回调对象的onResponse方法中拿到Response对象,这就是返回的结果。 14 | 总结:get的同步方法和异步方法的差别在于同步方法需要手动开启一个线程执行,而异步方法不需要(其实是使用了内部的线程)。 15 | 16 | post同步请求: 17 | post方法的同步请求和get方法的同步请求几乎是一样的 18 | post方法的同步请求和get方法的同步请求的区别在于,post方法生成Request对象时多执行了post(RequestBody)方法, 19 | 而RequestBody对象的子类是FormBody类,所以可以使用FormBody对象创建键值对参数。 20 | 21 | post异步请求: 22 | 异步请求post方法的异步请求和get方法的异步请求也是非常相似的,区别也是同步请求的区别。 23 | 24 | Retrofit是一个RESTful的HTTP网络请求框架,它是基于OkHttp的。它是通过注解配置网络参数的,支持多种数据的解析和序列化。 25 | 1)添加网络请求的接口 26 | 2)创建Retrofit对象 27 | 3)创建网络请求接口实例 28 | 4)发送网络请求 29 | 30 | 总结: 31 | Retrofit的一个RESTful风格的网络请求框架,其下一层的实现也是OkHttp,所以其原理和OkHttp的一样的,只是在OkHttp的上面封装了一层,使请求接口和数据解析更加简洁明了。 32 | 33 | 参考链接: 34 | https://juejin.im/post/5d7318935188250c992d56d9 -------------------------------------------------------------------------------- /Android 基础/116.Android10新特性及适配.md: -------------------------------------------------------------------------------- 1 | #### Android10新特性及适配 2 | 3 | 新特性: 4 | 折叠屏 5 | 5G网络支持 6 | 智能答复通知 7 | 暗黑模式 8 | 手势导航 9 | 浮动设置面板 10 | 分享改进 11 | 12 | 隐私 13 | 1)前台访问权限 14 | 2)网络扫描需要精确位置权限 15 | 3)防止设备跟踪 16 | 4)保护外部存储中的用户数据 17 | 5)阻止不需要的中断 18 | 19 | 安全 20 | 1)存储加密 21 | 2)默认TLS1.3 22 | 3)平台优化 23 | 4)改进的生物识别 24 | 25 | 相机和媒体 26 | 1)照片动态深度 27 | 2)音频播放捕获 28 | 3)新的音视频编解码器 29 | 4)原生MIDI API 30 | 5)定向可缩放的麦克风 31 | 6)无处不在的Vulkan 32 | 33 | 连接优化 34 | 1)改进的点对点和互联网连接 35 | 2)WiFi高性能模式 36 | 37 | Android系统基础 38 | 1)ART优化 39 | 2)神经网络API1.2 40 | 3)热感API 41 | 4)公共API的兼容性 42 | 43 | 更快的更新速度,更新鲜的代码 -------------------------------------------------------------------------------- /Android 基础/121.getSupportFragmentManager() getFragmentManager() getChildFragmentManager()的区别.md: -------------------------------------------------------------------------------- 1 | #### getSupportFragmentManager() getFragmentManager() getChildFragmentManager()的区别 2 | 3 | getSupportFragmentManager():Activity中使用 4 | getFragmentManager():Fragment中使用 获取所在Fragment的父容器的管理器 5 | getChildFragmentManager():Fragment中使用 获取所在Fragment里面子容器的管理器 6 | 注:Fragment嵌套Fragment要用getChildFragmentManager(). -------------------------------------------------------------------------------- /Android 基础/140.组件化通信如何做到的.md: -------------------------------------------------------------------------------- 1 | #### 组件化通信如何做到的 2 | 3 | 组件化互相不直接依赖,如果组件A想调用组件B的方法是不行的。很多开发者因为组件化之间通信比较复杂 则放弃了组件化的使用 4 | 5 | 组件通信有以下几种方式: 6 | 7 | 1.本地广播 8 | ​ 本地广播,也就是LoacalBroadcastRecevier。更多是用在同一个应用内的不同系统规定的组件进行通信,好处在于:发送的广播只会在自己的APP内传播,不会泄漏给其他的APP,其他APP无法向自己的APP发送广播,不用被其他APP干扰。本地广播好比对讲通信,成本低,效率高,但有个缺点就是两者通信机制全部委托与系统负责,我们无法干预传输途中的任何步骤,不可控制,一般在组件化通信过程中采用比例不高。 9 | 10 | 2.进程间的AIDL 11 | ​ 进程间的AIDL。这个粒度在于进程,而我们组件化通信过程往往是在线程中,况且AIDL通信也是属于系统级通信,底层以Binder机制,虽说Android提供模板供我们实现,但往往使用者不好理解,交互比较复杂,往往也不适用应用于组件化通信过程中。 12 | 13 | 3.匿名的内存共享 14 | 匿名的内存共享。比如用Sharedpreferences,在处于多线程场景下,往往会线程不安全,这种更多是存储一一些变化很少的信息,比如说组件里的配置信息等等 15 | 16 | 4.Intent Bundle传递 17 | Intent Bundle传递。包括显性和隐性传递,显性传递需要明确包名路径,组件与组件往往是需要互相依赖,这背离组件化中SOP(关注点分离原则),如果走隐性的话,不仅包名路径不能重复,需要定义一套规则,只有一个包名路径出错,排查起来也稍显麻烦,这个方式往往在组件间内部传递会比较合适,组件外与其他组件打交道则使用场景不多。 -------------------------------------------------------------------------------- /Android 基础/143.简单比较 SpareArray 和 HashMap.md: -------------------------------------------------------------------------------- 1 | #### 简单比较 SpareArray 和 HashMap 2 | 3 | 1. SpareseArray 也是通过键值对存储数据,只是key为整形int , 类似于key = Interger 的HashMap,但是SpareseArray 的key 为 int 非 Interger ,更省空间。 4 | 2. SpareArray 意为稀松数组,其结构类似于数组结构,依次排开;HashMap是散列列表,根据hash值来存储;因此SpareArray 会比 HashMap节省很多空间。 5 | 3. 从查找速度 和 插入效率来看,如果是正序插入( 0 ->size插入),SpareArray 的插入效率会高于 HashMap。 6 | 4. 如果是逆序插入(size -> 0)的顺序插入,则SpareArray 的插入效率表现是最差的,会低于HashMap。 7 | 5. SpareArray 在逆序插入效率很低,是因为 每次插入 SpareArray 都会采用二分查找来定位。 8 | 6. 从查找速度来来考虑,HashMap的查找速度 会 高于 SparseArray。 9 | 7. 通过以上分析,SpareArray 相对于 HashMap的最大优势在内存空间。因此谷歌推荐使用 SpareArray 代替 HashMap 10 | 11 | -------------------------------------------------------------------------------- /Android 基础/145.说说Thread Local的原理.md: -------------------------------------------------------------------------------- 1 | #### 说说Thread Local的原理 2 | 3 | ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 4 | 5 | 每个线程中都保有一个ThreadLocalMap的成员变量,ThreadLocalMap 内部采用WeakReference数组保存,数组的key即为ThreadLocal 内部的Hash值。 6 | 7 | ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key ,如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry ,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄漏。 8 | 9 | ``` 10 | static class Entry extends WeakReference> { 11 | /** The value associated with this ThreadLocal. */ 12 | Object value; 13 | 14 | Entry(ThreadLocal k, Object v) { 15 | super(k); 16 | value = v; 17 | } 18 | } 19 | ``` 20 | 21 | 其实,ThreadLocalMap 的设计中已经考虑到这种情况,也加上了一些防护措施:在 ThreadLocal 的 get(),set(),remove()的时候都会清除线程 ThreadLocalMap 里所有 key 为 null 的 value -------------------------------------------------------------------------------- /Android 基础/151.URI和URL的区别是什么.md: -------------------------------------------------------------------------------- 1 | #### URI和URL的区别是什么 2 | 3 | URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。 4 | URL(Uniform Resource Location) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。 5 | URI的作用像身份证号一样,URL的作用更像家庭住址一样。URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。 6 | 7 | -------------------------------------------------------------------------------- /Android 基础/158.简述 kotlin 中 run, apply, let, with,also 的用法和区别.md: -------------------------------------------------------------------------------- 1 | #### 简述 kotlin 中 run, apply, let, with,also 的用法和区别 2 | 3 | 1. `run` 函数返回值为函数块最后一行,或者指定 return 表达式,如果调用某对象的run函数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定 return 表达式。例如 4 | 5 | ```kotlin 6 | val a = "run".run { 7 | println(this) 8 | return@run 3 9 | } 10 | println(a) 11 | ``` 12 | 13 | 结果为 14 | 15 | ``` 16 | run 17 | 3 18 | ``` 19 | 20 | 1. `apply` 调用某对象的 apply 函数,在函数块内可以通过 this 指代该对象。返回值为该对象自己。例如 21 | 22 | ```kotlin 23 | val a = "apply".apply { 24 | println(this) 25 | } 26 | println(a) 27 | ``` 28 | 29 | 结果为 30 | 31 | ``` 32 | apply 33 | apply 34 | ``` 35 | 36 | 1. `let` 调用某对象的 let 函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定 return 表达式,例如 37 | 38 | ```kotlin 39 | val a = "let".let { 40 | println(it) 41 | 4 42 | } 43 | println(a) 44 | ``` 45 | 46 | 结果为 47 | 48 | ``` 49 | let 50 | 4 51 | ``` 52 | 53 | 1. `also` 调用某对象的 also 函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己。例如 54 | 55 | ```kotlin 56 | val a = "also".also { 57 | println(it) 58 | } 59 | println(a) 60 | ``` 61 | 62 | 结果为 63 | 64 | ``` 65 | also 66 | also 67 | ``` 68 | 69 | 1. `with` 函数和前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定 return 表达式。例如 70 | 71 | ```kotlin 72 | val a = with("with") { 73 | println(this) 74 | 5 75 | } 76 | println(a) 77 | ``` 78 | 79 | 结果为 80 | 81 | ``` 82 | with 83 | 5 84 | ``` -------------------------------------------------------------------------------- /Android 基础/173.SharedPreferences 存储比较大或者比较多的键值对会有什么问题吗?为什么?.md: -------------------------------------------------------------------------------- 1 | #### SharedPreferences 存储比较大或者比较多的键值对会有什么问题吗?为什么? 2 | 3 | SharedPreferences 是一种轻量级的存储方式,之所以轻量级是由其设计所决定的,因为 SharedPreferences 在创建的时候会把整个文件全部加载进内存,所以如果 SharedPreferences 文件比较大就会带来如下一些性能问题: 4 | 5 | - 首次从 SharedPreferences 获取值时可能阻塞主线程从而使 UI 界面卡顿丢帧。 6 | - 解析 SharedPreferences 时会产生大量的临时对象而导致频繁 GC 使得 UI 界面卡顿丢帧。 7 | - 被解析的同一个 SharedPreferences 文件的内容会占用大量内存。 -------------------------------------------------------------------------------- /Android 基础/176.Android为什么要为应用程序签名.md: -------------------------------------------------------------------------------- 1 | #### Android为什么要为应用程序签名 2 | 3 | 一.签名的原因 4 | 1.区分Android开发者使用同样的类名以及包名 5 | 开发商可能通过使用相同的包名来混淆替换已经安装的程序,签名可以保证相同的名字,但是签名不同的包不能被替换。 6 | APK如果使用一个Key签名,发布时另一个Key签名的文件将无法安装或覆盖老的版本,这样可以防止安装的应用被恶意的第三方覆盖或替换掉。 7 | 2.Android系统要求所有的程序通过数字签名才能安装。不管是模拟器还是真机,如果没有可用的数字签名,Android系统是不会允许安装运行该程序的。 8 | 9 | 二.签名的模式 10 | Android系统要求所有的APK程序是要通过可用的数字签名才能安装与运行。无论是在模拟设备上还是在真机设备上。但是我们在平时开发的时候,也没有进行数字签名的操作,为什么我们开发的程序能在Android系统上安装与运行呢?这就是以下将要说的签名的两种模式:调试模式和发布模式。 11 | 项目bin目录下的apk也能安装,是属于调试模式的签名而不是发布模式的签名 12 | 1.调试模式 13 | ⑴.在调试模式下,ADT会自动的使用Debug钥匙为应用程序签名,因此我们可以直接安装运行程序。 14 | ⑵.Debug密钥: 一个名为debug.keystore的文件。存放位置:C:\Users\Administrator.android 15 | ⑶.Debug签名的应用程序存在两个风险: 16 | ①.Debug签名的应用程序不能在应用市场上架销售,它会强制你使用自己的签名; 17 | ②.Debug.keystore在不同的机器上所生成的可能都不一样,就意味着如果你换了机器进行apk版本升级,那么将会出现上面那种程序不能覆盖安装的问题。 不要小视这个问题,如果你开发的程序只有你自己使用,当然无所谓,卸载再安装就可以了。但要是你的软件有很多使用客户,这就是大问题了,就相当于软件不具备升级功能! 所以一定要有自己的数字证书来签名; 18 | 2.发布模式 19 | 当要发布程序时,开发者就需要使用自己的数字证书给APK签名 20 | 21 | 三.签名的好处 22 | 1.应用程序升级 23 | 如果希望使用该APP的用户无缝的升级到新的版本,就需要使用同一个数字证书进行签名。因为只有使用同一个数字证书签名,Android系统才允许安装升级的应用程。当新版程序和旧版程序的数字证书相同的时候,Android系统才会认为这两个程序是同一个程序的不同版本。如果新版程序和旧版程序的数字证书不相同的话,Android系统认为这是两个不同的程序,会产生冲突。如果想升级应用程序,需要两个条件:①数字证书签名要相同。②应用程序的报名要相同。 24 | 2.代码或者数据共享 25 | Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个相同数字签名证书的应用程序公开自己的功能。多个应用程序使用同一个数字签名证书,就可以利用签名的权限检查,在应用程序之间安全的进行数据共享。不同的应用程序之间,想共享数据或者共享代码,具备两个条件:①要让他们运行在同一个进程中。②要让他们使用相同的证书签名。 26 | 3.应用程序模块化 27 | Android系统可以允许多个应用陈谷可以使用同一个证书签名并且可以运行在同一个进程中,系统实际上是把他们当作一个单一的应用程序,此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块。 28 | 29 | 参考:https://blog.csdn.net/u014225510/article/details/50937013 -------------------------------------------------------------------------------- /Android 基础/178.如何优雅的使用 SharedPreferences 存储?你有哪些经验.md: -------------------------------------------------------------------------------- 1 | #### 如何优雅的使用 SharedPreferences 存储?你有哪些经验 2 | 3 | SharedPreferences 中不要存放大的键值对,因为会占用太多内存、引起 UI 卡顿及内存抖动等问题。在进行 SharedPreferences 数据持久时应该对持久化数据进行分类多 SharedPreferences 文件存储(譬如同一功能相关的放一个文件,或者按照读写频率及大小进行文件拆分),因为文件越大读取越慢,所以分类存储相对会好很多。对于频繁修改尽量做到批量一次性提交,尽量不要多次 edit 和 commit 或者 apply。不要直接用其进行跨进程读写操作,因为 SharedPreferences 不是进程安全的(MODE_MULTI_PROCESS 标记只是保证了在 API 11 以后如果内存中已经存在该 SharedPreference 则重性读一次文件到内存而已),如果要进行跨进程读写保证进程并发安全则建议使用 ContentProvider 对 SharedPreferences 进行包装或者采用其他 AIDL 等方式存储实现 -------------------------------------------------------------------------------- /Android 基础/179.Android有哪几种进程,是如何管理的?.md: -------------------------------------------------------------------------------- 1 | #### Android有哪几种进程,是如何管理的? 2 | 3 | Android的进程主要分为以下几种: 4 | 5 | #### 前台进程 6 | 7 | 用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程: 8 | 9 | - 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法) 10 | - 托管某个 Service,后者绑定到用户正在交互的 Activity 11 | - 托管正在“前台”运行的 Service(服务已调用 startForeground()) 12 | - 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy()) 13 | - 托管正执行其 onReceive() 方法的 BroadcastReceiver 14 | 15 | 通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。 16 | 17 | #### 可见进程 18 | 19 | 没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程: 20 | 21 | - 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。 22 | - 托管绑定到可见(或前台)Activity 的 Service。 23 | 24 | 可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。 25 | 26 | #### 服务进程 27 | 28 | 正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关 心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。 29 | 30 | #### 后台进程 31 | 32 | 包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 33 | 34 | #### 空进程 35 | 36 | 不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。 37 | 38 | ActivityManagerService负责根据各种策略算法计算进程的adj值,然后交由系统内核进行进程的管理。 -------------------------------------------------------------------------------- /Android 基础/18.既然RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线?.md: -------------------------------------------------------------------------------- 1 | #### 既然RecyclerView在很多方面能取代ListView,Google为什么没把ListView划上一条过时的横线? 2 | 3 | ##### 参考答案 4 | 5 | ListView采用的是RecyclerBin的回收机制,在一些轻量级的List显示时效率更高. 6 | 7 | 8 | 9 | ##### 补充答案 10 | 11 | ###### From [jiwenjie](https://github.com/jiwenjie) 12 | 13 | 1. ListView采用的是RecyclerBin的回收机制在一些轻量级的List时效率更高。 14 | 15 | - 在处理少量数据使用 ListView 16 | - 在处理大量数据的时候使用 RecyclerView 17 | 18 | -------------------------------------------------------------------------------- /Android 基础/180.EventBus是单例的,但是为什么默认构造函数是public的呢.md: -------------------------------------------------------------------------------- 1 | #### EventBus是单例的,但是为什么默认构造函数是public的呢 2 | 3 | EventBus可能有多条总线,订阅者注册到不同线上的 EventBus,通过不同的实例来发送数据,不同的 EventBus 是相互隔离开的,订阅者就都只会收到注册到该线上的事件 -------------------------------------------------------------------------------- /Android 基础/183.SharedPreferences 为什么存储大数据就比较占用内存?.md: -------------------------------------------------------------------------------- 1 | #### SharedPreferences 为什么存储大数据就比较占用内存? 2 | 3 | SharedPreferencesImpl 会将 File 文件在子线程全部加载解析到一个内存的 Map 中,而 SharedPreferences 对象又会被 ContextImpl 的 static Map 进程 Cache 操作,所以 SharedPreferencesImpl 就相当于是一个单例存储的,故而其 Map 在内存中就会持续存在,即便使用 Editor 进行修改后 commit 操作实质也是对 SharedPreferencesImpl 中 Map 进行对应操作。 4 | 5 | ```java 6 | //一个SharedPreferences的get操作与Editor操作对应的内存Map操作原理 7 | final class SharedPreferencesImpl implements SharedPreferences { 8 | //用来存储该SP的所有Key-Value对 9 | private Map mMap; 10 | ...... 11 | SharedPreferencesImpl(File file, int mode) { 12 | ...... 13 | //构造方法新起一个线程从磁盘加载SP文件解析XML到mMap中 14 | startLoadFromDisk(); 15 | } 16 | ...... 17 | //通过SharedPreferences对象获取指定key的值 18 | public int getInt(String key, int defValue) { 19 | ...... 20 | Integer v = (Integer)mMap.get(key); 21 | return v != null ? v : defValue; 22 | } 23 | ...... 24 | //通过SharedPreferences.edit()获取的Editor对象 25 | public final class EditorImpl implements Editor { 26 | //Editor要增删改查的操作Map记录 27 | private final Map mModified = Maps.newHashMap(); 28 | ...... 29 | public Editor putString(String key, @Nullable String value) { 30 | ...... 31 | mModified.put(key, value); 32 | } 33 | ...... 34 | public Editor remove(String key) { 35 | ...... 36 | mModified.put(key, this); 37 | } 38 | ...... 39 | //提交到内存mMap 40 | // Returns true if any changes were made 41 | private MemoryCommitResult commitToMemory() { 42 | ...... 43 | for (Map.Entry e : mModified.entrySet()) { 44 | String k = e.getKey(); 45 | Object v = e.getValue(); 46 | if (v == this || v == null) { 47 | if (!mMap.containsKey(k)) { 48 | continue; 49 | } 50 | mMap.remove(k); 51 | } else { 52 | if (mMap.containsKey(k)) { 53 | Object existingValue = mMap.get(k); 54 | if (existingValue != null && existingValue.equals(v)) { 55 | continue; 56 | } 57 | } 58 | mMap.put(k, v); 59 | } 60 | changesMade = true; 61 | if (hasListeners) { 62 | keysModified.add(k); 63 | } 64 | } 65 | //清空Editor的mModified的Map 66 | mModified.clear(); 67 | ...... 68 | } 69 | ...... 70 | //commit提交 71 | public boolean commit() { 72 | ...... 73 | //提交到内存 74 | MemoryCommitResult mcr = commitToMemory(); 75 | //提交到磁盘文件 76 | SharedPreferencesImpl.this.enqueueDiskWrite( 77 | mcr, null /* sync write on this thread okay */); 78 | try { 79 | mcr.writtenToDiskLatch.await(); 80 | } catch (InterruptedException e) { 81 | return false; 82 | } finally { 83 | ...... 84 | } 85 | notifyListeners(mcr); 86 | return mcr.writeToDiskResult; 87 | } 88 | ...... 89 | } 90 | } 91 | ``` 92 | 93 | 可以理解成一个 SpFile 对应一个单例的 SharedPreferencesImpl 对象,这个单例的 SharedPreferencesImpl 实例化时会将文件全部加载到内存中以 Map 存储,然后 Editor 操作会 new 一个新的 modifyMap,接着到了 Editor 的 commit 操作时会将 modifyMap 进行遍历增删改查到 SharedPreferencesImpl 的 Map 中,然后进行存盘操作,而 SharedPreferencesImpl 进行 getX(key) 操作时都是直接从 SharedPreferencesImpl 的 Map 中进行读取的,所以说 SharedPreferences 只适合轻量级的数据存储操作。 -------------------------------------------------------------------------------- /Android 基础/193.说说 compileSdkVersion、minSdkVersion、targetSdkVersion 的区别和作用.md: -------------------------------------------------------------------------------- 1 | #### 说说 compileSdkVersion、minSdkVersion、targetSdkVersion 的区别和作用 2 | 3 | - compileSdkVersion 告诉 Gradle 用哪个版本的 Android SDK 编译你的应用,使用任何新添加的 API 就需要使用对应 Level 的 Android SDK。修改 compileSdkVersion 不会改变运行时的行为,当修改了 compileSdkVersion 时可能会出现新的编译警告、编译错误,强烈推荐总是使用最新的 SDK 进行编译,避免弃用的 API。此外,如果使用最新发布的 Support Library 就需要使用最新的 SDK 编译。例如使用 23.1.1 版本的 Support Library,compileSdkVersion 就必需至少是 23 (大版本号要一致)。通常,新版的 Support Library 随着新的系统版本而发布,它为系统新增加的 API 和新特性提供兼容性支持。 4 | - minSdkVersion 是应用可以运行的最低 API 要求,也是 Google Play 商店用来判断用户设备是否可以安装某个应用的标志之一。在开发时 minSdkVersion 也起到一个重要角色,lint 默认会在项目中运行,它在我们使用了高于 minSdkVersion 的 API 时会警告我们,避免调用低版本不存在的 API 而导致在低版本设备上运行时的问题。如果只在较高版本的系统上才使用某些 API,通常使用运行时检查系统版本的方式解决。此外要记住,我们所使用的库(如 Support Library 或 Google Play services)可能有他们自己的 minSdkVersion,我们应用设置的 minSdkVersion 要必需大于等于这些库的 minSdkVersion。在少数情况下你可能仍然想用一个比你应用的 minSdkVersion 还高的库,这时可以使用 tools:overrideLibrary 标记配合代码调用处主动版本判断操作来实现。 5 | - targetSdkVersion 是 Android 提供向前兼容的主要依据,在应用的 targetSdkVersion 没有更新之前系统不会应用最新的行为变化,这允许你在适应新的行为变化之前就可以使用新的 API (因为你已经更新了 compileSdkVersion)。譬如 targetSdkVersion 为 19(对应为 Android4.4),应用运行时最高只能使用 API 19 的新特性,即使代码中使用了 API 23 的新特性,实际运行时也不会使用该新特性。同样譬如 AlarmManger 的 set() 和 get() 方法,在 API 19 和之前的效果是不一样的,如果 targetSdkVersion 为 18,无论运行手机是什么版本都是旧效果,如果 targetSdkVersion 为 19 则在 4.4 以上的手机上运行时就是新效果了。 6 | 7 | minSdkVersion 和 targetSdkVersion 与 compileSdkVersion 的另一个不同之处是它们会被包含进最终 APK 的 AndroidManifest.xml 文件中,他们的大小关系应该是 minSdkVersion <= targetSdkVersion <= compileSdkVersion,而推荐的大小关系是 minSdkVersion(lowest possible) <= targetSdkVersion == compileSdkVersion(latest SDK)。 -------------------------------------------------------------------------------- /Android 基础/23.Android 实现异步的几种方式,原理与各自特点.md: -------------------------------------------------------------------------------- 1 | #### Android 实现异步的几种方式,原理与各自特点 2 | 3 | ##### 参考答案 4 | 5 | 这边介绍三种:AsyncTask,HandlerThread和IntentService 6 | 7 | AsyncTask原理:内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,如果此时没有正在执行的任务,就执行它,执行完成之后继续执行队列中下一个任务,执行完成通过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,因为内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。在Android3.0开始,execute方法串行执行任务的,一个一个来,3.0之前是并行执行的。如果要在3.0上执行并行任务,可以调用executeOnExecutor方法 8 | 9 | HandlerThread原理:继承自Thread,start开启线程后,会在其run方法中会通过Looper创建消息队列并开启消息循环,这个消息队列运行在子线程中,所以可以将HandlerThread中的Looper实例传递给一个Handler,从而保证这个Handler的handleMessage方法运行在子线程中,Android中使用HandlerThread的一个场景就是IntentService 10 | 11 | IntentService原理:继承自Service,它的内部封装了HandlerThread和Handler,可以执行耗时任务,同时因为它是一个服务,优先级比普通线程高很多,所以更适合执行一些高优先级的后台任务,HandlerThread底层通过Looper消息队列实现的,所以它是顺序的执行每一个任务。可以通过Intent的方式开启IntentService,IntentService通过handler将每一个intent加入HandlerThread子线程中的消息队列,通过looper按顺序一个个的取出并执行,执行完成后自动结束自己,不需要开发者手动关闭 -------------------------------------------------------------------------------- /Android 基础/32.Android 的四大组件都需要在清单文件中注册吗?并简述四大组件.md: -------------------------------------------------------------------------------- 1 | #### Android 的四大组件都需要在清单文件中注册吗?并简述四大组件 2 | 3 | 4 | 5 | ##### 参考答案 6 | 7 | Activity、Service、ContentProvider 如 果 要 使 用 则 必 须 在AndroidManifest.xml 中 进 行 注 册 , 而BroadcastReceiver则有两种注册方式,静态注册和动态注册。其中静态注册就是指在AndroidManifest.xml中进行注册,而动态注册时通过代码注册。 8 | 9 | **Activity**:通常展现为一个用户操作的可视化界面。它为用户提供了一个完成操作指令的窗口。 10 | () (Activity的来由) 11 | 12 | **Service**:Android系统的服务(不是一个线程,是主程序的一部分),与Activity不同,它是不能与用户交互的,不能自己启动的,须要调用Context.startService()来启动,执行后台,假设我们退出应用时,Service进程并没有结束,它仍然在后台行。 13 | 14 | **BroadcastReceiver**:广播接收器是一个专注于接收广播通知信息,并做出相应处理的组件。 15 | 16 | **ContentProvider**:(内容提供者)主要用于对外共享数据,也就是通过ContentProvider把应用中的数据共享给其它应用訪问,其它应用能够通过ContentProvider对指定应用中的数据进行操作。 -------------------------------------------------------------------------------- /Android 基础/33.谈一谈对Android中Context理解.md: -------------------------------------------------------------------------------- 1 | #### 谈一谈对Android中Context理解 2 | 3 | ##### 参考答案 4 | 5 | Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。 6 | getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。 7 | Context数量 = Activity数量 + Service数量 + 1 (1为Application) 8 | 9 | -------------------------------------------------------------------------------- /Android 基础/34.Android线程间通信有几种方法?.md: -------------------------------------------------------------------------------- 1 | #### Android线程间通信有几种方法? 2 | 3 | ##### 参考答案 4 | 5 | 1. Handler机制 6 | 2. runOnUiThread(Runnable action) 7 | 3. View.post(Runnable action) 8 | 4. AsyncTask 9 | 5. 广播 10 | 6. 使用EventBus、RxJava等框架 11 | 12 | 13 | 14 | ##### 蛋友补充 15 | 16 | ###### From [Taonce](https://github.com/Taonce) 17 | 18 | 1. 通过 `Handler` 来通信 19 | 20 | ```kotlin 21 | val handler = @SuppressLint("HandlerLeak") 22 | object : Handler(){ 23 | override fun handleMessage(msg: Message?) { 24 | Log.d("taonce","msg arg1: ${msg?.arg1}") 25 | } 26 | } 27 | thread { 28 | val msg: Message = handler.obtainMessage() 29 | msg.arg1 = 1 30 | handler.sendMessage(msg) 31 | } 32 | ``` 33 | 34 | 2. 通过 `runOnUiThread()` 35 | 36 | ```kotlin 37 | thread { 38 | val text = "runOnUiThread" 39 | runOnUiThread { 40 | tv.text = text 41 | } 42 | } 43 | ``` 44 | 45 | 3. 通过 `View.post()` 46 | 47 | ```kotlin 48 | thread { 49 | val text = "post" 50 | tv.post { 51 | tv.text = text 52 | } 53 | } 54 | ``` 55 | 56 | 4. 通过 `AsyncTask` 57 | 58 | ```kotlin 59 | class MyAsyncTask(val name: String) : AsyncTask() { 60 | 61 | // 执行任务之前的准备工作,比如将进度条设置为Visible,工作在主线程 62 | override fun onPreExecute() { 63 | Log.d("async", "onPreExecute") 64 | } 65 | 66 | // 在onPreExecute()执行完之后立即在后台线程中调用 67 | override fun doInBackground(vararg params: String?): Any? { 68 | Log.d("async", "$name execute") 69 | Thread.sleep(1000) 70 | publishProgress(1) 71 | return null 72 | } 73 | 74 | // 调用了publishProgress()之后,会在主线程中被调用,用于更新整体进度 75 | override fun onProgressUpdate(vararg values: Int?) { 76 | Log.d("async", "progress is: $values") 77 | } 78 | 79 | // 后台线程执行结束后,会把结果回调到这个方法中,并在主线程中被调用 80 | override fun onPostExecute(result: Any?) { 81 | Log.d("async", "onPostExecute") 82 | } 83 | } 84 | ``` 85 | 86 | 当然少不了 `RxJava` 的线程间切换,自行了解 -------------------------------------------------------------------------------- /Android 基础/37.请介绍下Android中常用的五种布局.md: -------------------------------------------------------------------------------- 1 | #### 请介绍下Android中常用的五种布局 2 | 3 | ##### 参考答案 4 | 5 | 最常用的布局方式为Absolute Layout、Relative Layout、Linear Layout、FrameLayout、TableLayout。其中Linear Layout和Relative Layout是最常用的方式,他们可以通过在xml配置文件或者代码中进行布局。 6 | 7 | 1、Frame Layout是最简单的布局方式,放置的控件都只能罗列到左上角,控件会有重叠,不能进行复杂的布局。 8 | 9 | 2、Linear Layout可以通过orientation属性设置线性排列的方向是垂直还是纵向的,每行或每列只有一个元素,可以进行复杂的布局。 10 | 11 | 3、Absolute Layout可以让子元素指定准确的x、y坐标值,并显示在屏幕上。Absolute Layout没有页边框,允许元素之间相互重叠。它是绝对坐标,所以在实际中不提倡使用。 12 | 13 | 4、Relative Layout允许子元素制定他们相对于其他元素或父元素的位置(通过ID制定)。因此,你可以以右对齐,或上下,或置于屏幕中央的形式来排列两个元素。元素按顺序排列,因此如果第一个元素在屏幕的中央,那么相对于这个元素的其他元素将以屏幕中央的相对位置来排列。这个是相对于Absolute Layout的,采用相对坐标,所以在实际中比较常用。 14 | 15 | 5、Table Layout将以子元素的位置分配到行或列。一个Table Layout由许多的Table Row组成,每个Table Row都会定义一个row。Table Layout容器不会显示row、column或者cell的边线框。每个row拥有0个或多个的cell; 和html中的table差不多。在实际中也经常使用。 16 | 17 | 18 | 19 | ##### 蛋友补充 20 | 21 | ###### From BelieveFrank 22 | 23 | 1、线性布局 (LinearLayout):是一种非常常用的布局,次布局会将它包含的控件在线性方向上依次排列。通过android:orientation属性来确定排列的方向是vertical(垂直)还是horizontal(水平)。 24 | 2、相对布局(RelativeLayout):也是一种非常常用的布局,通过相对定位的方式让控件出现在布局的任何位置。 25 | 3、帧布局(FrameLayout):由于定位方式的欠缺,所有的控件都会默认摆放在布局的左上角,应用场景比较少。 26 | 4、绝对布局(AbsoluteLayout):用x、y坐标来确定控件的位置。 27 | 5、表格布局(TableLayout):每一个TableLayout里面有表格行TableRow,TableRow里面可以具体定义每一个控件。 -------------------------------------------------------------------------------- /Android 基础/40.Android SharedPreference频繁操作有什么后果?能存多少数据.md: -------------------------------------------------------------------------------- 1 | #### Android SharedPreference频繁操作有什么后果?能存多少数据 2 | 3 | ##### 参考答案 4 | 5 | Android中 SP 的底层是由Xml来实现的,操作SP的过程就是Xml的序列化和解析的过程。Xml是存储在磁盘上的,因此当我们频繁进行SP操作时,就是频繁进行序列化与解析,这就频繁进行I/O的操作,所以肯定会导致性能消耗。同时序列化Xml是就是将内存中的数据写到Xml文件中,由于DVM 的内存是很有限的,因此单个SP文件不建议太大,具体多大是没有一个具体的要求的,但是我们知道DVM 堆内存也就是16M,因此数据大小肯定不能超过这个数字的。其实 SP 设置的目的就是为了保存用户的偏好和配置信息的,因此不要保存太多的数据。 -------------------------------------------------------------------------------- /Android 基础/41.理解Activity,View,Window三者关系.md: -------------------------------------------------------------------------------- 1 | #### 理解Activity,View,Window三者关系 2 | 3 | ##### 参考答案 4 | 5 | Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。 6 | 1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。 7 | 2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。 8 | 3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等 9 | 4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等 10 | 11 | -------------------------------------------------------------------------------- /Android 基础/42.Android进程间通信的几种姿势.md: -------------------------------------------------------------------------------- 1 | #### Android进程间通信的几种姿势 2 | 3 | ##### 参考答案 4 | 5 | 进程间通信即IPC,英文全称Inter-Process Communication,是指进程间数据交互的过程. 6 | Android底层是基于Linux,而Linux基于安全考虑,是不允许两个进程间直接操作对方的数据,这就是进程隔离. 7 | 六种常用姿势: 8 | 9 | 1. Bundle 10 | 2. 文件共享 11 | 3. AIDL 12 | 4. Messenger 13 | 5. ContentProvider 14 | 6. Socket 15 | 16 | 参考:Android开发艺术探索 第2章 2.4节 17 | 18 | 19 | 20 | ##### 蛋友补充 21 | 22 | ###### From [lydlovexyz](https://github.com/lydlovexyz) 23 | 24 | ![ipc](https://user-images.githubusercontent.com/19246347/59193030-ee4f6480-8bb6-11e9-9350-d7499c05397b.jpg) 25 | 26 | -------------------------------------------------------------------------------- /Android 基础/45.Android的数据存储方式.md: -------------------------------------------------------------------------------- 1 | #### Android的数据存储方式 2 | 3 | ##### 参考答案 4 | 5 | Android提供了5中存储数据的方式,分别是以下几种: 6 | 7 | 1、使用Shared Preferences存储数据,用来存储key-value,pairs格式的数据,它是一个轻量级的键值存储机制,只可以存储基本数据类型。 8 | 9 | 2、使用文件存储数据,通过FileInputStream和FileOutputStream对文件进行操作。在Android中,文件是一个应用程序私有的,一个应用程序无法读写其他应用程序的文件。 10 | 11 | 3、使用SQLite数据库存储数据,Android提供的一个标准数据库,支持SQL语句。 12 | 13 | 4、使用Content Provider存储数据,是所有应用程序之间数据存储和检索的一个桥梁,它的作用就是使得各个应用程序之间实现数据共享。它是一个特殊的存储数据的类型,它提供了一套标准的接口用来获取数据,操作数据。系统也提供了音频、视频、图像和个人信息等几个常用的Content Provider。如果你想公开自己的私有数据,可以创建自己的Content Provider类,或者当你对这些数据拥有控制写入的权限时,将这些数据添加到Content Provider中实现共享。外部访问通过Content Resolver去访问并操作这些被暴露的数据。 14 | 15 | 5、使用网络存储数据 -------------------------------------------------------------------------------- /Android 基础/49.四种LaunchMode及其使用场景.md: -------------------------------------------------------------------------------- 1 | #### 四种LaunchMode及其使用场景 2 | 3 | ##### 参考答案 4 | 5 | standard 模式 6 | 这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。使用场景:大多数Activity。 7 | 8 | singleTop 模式 9 | 如果在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类App的内容页面。 10 | 11 | singleTask 模式 12 | 如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。 13 | 14 | singleInstance 模式 15 | 在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。 -------------------------------------------------------------------------------- /Android 基础/50.如何减小apk安装包体积.md: -------------------------------------------------------------------------------- 1 | #### 如何减小apk安装包体积 2 | 3 | ##### 参考答案 4 | 5 | 1.代码混淆 6 | minifyEnabled true 7 | 8 | 2.资源压缩 9 | 1)shrinkResources true 10 | 2)微信的AndResGuard 11 | 12 | 3.图片压缩 13 | 1)tinypng 14 | 2)svg 15 | 3)webp 16 | 17 | 4.so库配置 18 | 只保留两个abi平台,即armeabi和armeabi-v7a 19 | 20 | 5.dex优化 21 | Facebook的redex -------------------------------------------------------------------------------- /Android 基础/51.谈谈 RecyclerView 的性能优化.md: -------------------------------------------------------------------------------- 1 | #### 谈谈 RecyclerView 的性能优化 2 | 3 | ##### 参考答案 4 | 5 | 1. 数据处理和视图加载分离 6 | 7 | 从远端拉取数据肯定是要放在异步的,在我们拉取下来数据之后可能就匆匆把数据丢给了 VH 处理,其实,数据的处理逻辑我们也应该放在异步处理,这样 Adapter 在 notify change 后,ViewHolder 就可以简单无压力地做数据与视图的绑定逻辑,比如: 8 | 9 | ```java 10 | mTextView.setText(Html.fromHtml(data).toString()); 11 | ``` 12 | 13 | 这里的 `Html.fromHtml(data)` 方法可能就是比较耗时的,存在多个 `TextView` 的话耗时会更为严重,这样便会引发掉帧、卡顿,而如果把这一步与网络异步线程放在一起,站在用户角度,最多就是网络刷新时间稍长一点。 14 | 15 | 2. 数据优化 16 | 17 | 分页拉取远端数据,对拉取下来的远端数据进行缓存,提升二次加载速度;对于新增或者删除数据通过 `DiffUtil` 来进行局部刷新数据,而不是一味地全局刷新数据。 18 | 19 | 3. 布局优化 20 | 21 | 1. 减少过渡绘制 22 | 23 | 减少布局层级,可以考虑使用自定义 View 来减少层级,或者更合理地设置布局来减少层级,不推荐在 RecyclerView 中使用 `ConstraintLayout`,有很多开发者已经反映了使用它效果更差,相关链接有:Is ConstraintLayout that slow?、constraintlayout 1.1.1 not work well in listview。 24 | 25 | 2. 减少 xml 文件 inflate 时间 26 | 27 | 这里的 xml 文件不仅包括 layout 的 xml,还包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是通过耗时的 IO 操作,尤其当 Item 的复用几率很低的情况下,随着 Type 的增多,这种 inflate 带来的损耗是相当大的,此时我们可以用代码去生成布局,即 `new View()` 的方式,只要搞清楚 xml 中每个节点的属性对应的 API 即可。 28 | 29 | 3. 减少 View 对象的创建 30 | 31 | 一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的创建也会消耗大量时间,所以要尽可能简化 ItemView;设计 ItemType 时,对多 ViewType 能够共用的部分尽量设计成自定义 View,减少 View 的构造和嵌套。 32 | 33 | 4. 其他 34 | 35 | - 升级 `RecycleView` 版本到 25.1.0 及以上使用 Prefetch 功能,可参考 RecyclerView 数据预取。 36 | 37 | - 如果 Item 高度是固定的话,可以使用 `RecyclerView.setHasFixedSize(true);` 来避免 `requestLayout` 浪费资源; 38 | 39 | - 设置 `RecyclerView.addOnScrollListener(listener);` 来对滑动过程中停止加载的操作。 40 | 41 | - 如果不要求动画,可以通过 `((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false);` 把默认动画关闭来提神效率。 42 | 43 | - 对 `TextView` 使用 `String.toUpperCase` 来替代 `android:textAllCaps="true"`。 44 | 45 | - 对 `TextView` 使用 `StaticLayout` 或者 `DynamicLayout` 的自定义 `View` 来代替它。 46 | 47 | - 通过重写 `RecyclerView.onViewRecycled(holder)` 来回收资源。 48 | 49 | - 通过 `RecycleView.setItemViewCacheSize(size);` 来加大 `RecyclerView` 的缓存,用空间换时间来提高滚动的流畅性。 50 | 51 | - 如果多个 `RecycledView` 的 `Adapter` 是一样的,比如嵌套的 `RecyclerView` 中存在一样的 `Adapter`,可以通过设置 `RecyclerView.setRecycledViewPool(pool);` 来共用一个 `RecycledViewPool`。 52 | 53 | - 对 `ItemView` 设置监听器,不要对每个 Item 都调用 `addXxListener`,应该大家公用一个 `XxListener`,根据 `ID` 来进行不同的操作,优化了对象的频繁创建带来的资源消耗。 54 | 55 | - 通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间(显示范围之外,应该额外缓存的空间),如下所示: 56 | 57 | ``` 58 | new LinearLayoutManager(this) { 59 | @Override 60 | protected int getExtraLayoutSpace(RecyclerView.State state) { 61 | return size; 62 | } 63 | }; 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /Android 基础/58.ANR异常的产生条件及解决方案.md: -------------------------------------------------------------------------------- 1 | #### ANR异常的产生条件及解决方案 2 | 3 | ##### 参考答案 4 | 5 | ANR是什么? 6 | ANR全称:Application Not Responding,也就是应用程序无响应. 7 | 简单来说,就是应用跑着跑着,突然duang,界面卡住了,无法响应用户的操作如触摸事件等. 8 | 9 | 原因 10 | Android系统中,ActivityManagerService(简称AMS)和WindowManagerService(简称WMS)会检测App的响应时间. 11 | 如果App在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR. 12 | 13 | ANR的产生需要同时满足三个条件 14 | 15 | 1. 主线程:只有应用程序进程的主线程(UI线程)响应超时才会产生ANR 16 | 2. 超时时间:产生ANR的上下文不同,超时时间也不同,但只要超过这个时间上限没有响应就会产生ANR 17 | 3. 输入事件/特定操作:输入事件是指按键、触屏等设备输入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各个函数调用 18 | 19 | 解决方案: 20 | 总结为一句话,即不要在主线程(UI线程)里面做繁重的操作 -------------------------------------------------------------------------------- /Android 基础/61.ListView如何提高效率.md: -------------------------------------------------------------------------------- 1 | #### ListView如何提高效率 2 | 3 | ##### 参考答案 4 | 5 | 1、使用分页加载,不要一次性加载所有数据。 6 | 7 | 2、复用convertView。在getItemView中,判断converView是否为空,如果不为空,可复用。 8 | 9 | 3、异步加载图片。Item中如果包含有webimage,那么最好异步加载。 10 | 11 | 4、快速滑动时,不显示图片。当快速滑动列表(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状态(SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来 -------------------------------------------------------------------------------- /Android 基础/62.谈谈Android的安全机制.md: -------------------------------------------------------------------------------- 1 | #### 谈谈Android的安全机制 2 | 3 | ##### 参考答案 4 | 5 | 1. Android 是基于Linux内核的,因此 Linux 对文件权限的控制同样适用于 Android。在 Android 中每个应用都有自己的/data/data/包名 文件夹,该文件夹只能该应用访问,而其他应用则无权访问。 6 | 2. Android 的权限机制保护了用户的合法权益。如果我们的代码想拨打电话、发送短信、访问通信录、定位、访问、sdcard 等所有可能侵犯用于权益的行为都是必须要在 AndroidManifest.xml 中进行声明的,这样就给了用户一个知情权。 7 | 3. Android 的代码混淆保护了开发者的劳动成果。 -------------------------------------------------------------------------------- /Android 基础/64.Broadcast 注册方式与区别.md: -------------------------------------------------------------------------------- 1 | #### Broadcast 注册方式与区别 2 | 3 | ##### 参考答案 4 | 5 | Broadcast广播,注册方式主要有两种. 6 | 7 | 第一种是静态注册,也可成为常驻型广播,这种广播需要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。 8 | 9 | 第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露 10 | 广播是分为有序广播和无序广播。 11 | 12 | 13 | 14 | ##### 蛋友补充 15 | 16 | ###### From [chenwentong](https://github.com/chenwentong0) 17 | 18 | 一、Brodcast注册方式: 19 | 20 | ``` 21 | 1、静态注册: 22 | 23 | 2、动态注册: 24 | ``` 25 | 26 | 二、静态注册:在清单文件manifest中注册,当程序退出之后还可以收到该广播。不能控制具体某个时间点接收和不接收广播。 27 | 28 | 三、动态注册:通过代码的方式注册context.registerReceiver(broadcastReceiver),注册的同时注意在不需要接受的时候进行反注册context.unregisterReceiver(broadcastReceiver);避免内存泄漏, 动态注册可以很好的控制广播接受。 29 | 30 | 四、从Android 8.0(API 26)开始,对于大部分隐式广播(广播的对象不是针对你开发的APP),不能在manifest中声明receiver,如果需要使用隐式广播,需要使用context.registerReceiver 的方法。 -------------------------------------------------------------------------------- /Android 基础/73.怎样避免和解决ANR.md: -------------------------------------------------------------------------------- 1 | #### 怎样避免和解决ANR 2 | 3 | ##### 参考答案 4 | 5 | Application Not Responding,即应用无响应。避免ANR最核心的一点就是在主线程减少耗时操作。通常需要从那个以下几个方案下手: 6 | 7 | a)使用子线程处理耗时IO操作 8 | 9 | b)降低子线程优先级,使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同 10 | 11 | c)使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程 12 | 13 | d)Activity的onCreate和onResume回调中尽量避免耗时的代码 14 | 15 | e)BroadcastReceiver中onReceiver代码也要尽量减少耗时操作,建议使用intentService处理。intentService是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题 -------------------------------------------------------------------------------- /Android 基础/75.Serializable和Parcelable的区别.md: -------------------------------------------------------------------------------- 1 | #### Serializable和Parcelable的区别 2 | 3 | ##### 参考答案 4 | 5 | Serializable(Java自带): 6 | Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。 7 | 8 | Parcelable(android 专用): 9 | 除了Serializable之外,使用Parcelable也可以实现相同的效果, 10 | 不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解, 11 | 而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。 12 | 13 | 区别: 14 | 15 | 1. 在使用内存的时候,Parcelable 类比Serializable性能高,所以推荐使用Parcelable类。 16 | 2. Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。 17 | 3. Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管 Serializable 效率低点,但在这种情况下,还是建议你用Serializable 。 18 | 19 | 实现: 20 | 21 | 1. Serializable 的实现,只需要继承Serializable 即可。这只是给对象打了一个标记,系统会自动将其序列化。 22 | 2. Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这个变量需要继承Parcelable.Creator 接口,(一般利用编译器可以自动生成)。 23 | 24 | 参考自简书:https://www.jianshu.com/p/a60b609ec7e7 25 | 26 | 27 | 28 | ##### 群友补充 29 | 30 | ###### From [Noble_JIE](https://github.com/jiezongnewstar) 31 | 32 | 从出生来来说,Serializable 是java的方法,Parcelable 是android独有的序列化反序列化方法 33 | 从用法上来说,Serializable 比 Parcelable 简单,所有类实现Serializable即可,Parcelable需要对对所有属性及成员变量进行Creator 。 34 | 从性能上来说,Parcelable 性能要高于Serializable。 -------------------------------------------------------------------------------- /Android 基础/80.简单描述一下Intent 和IntentFilter.md: -------------------------------------------------------------------------------- 1 | #### 简单描述一下Intent 和IntentFilter 2 | 3 | Intent 是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组 件想要执行的动作,还可以在不同组件之间传递数据。Android 中通过 Intent 对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。通过Intent 可以实现各种系统组件的调用与激活。Intent是我们经常用的,想必都不陌生。 4 | 而IntentFilter是用于描述intent的各种属性。可以理解为邮局或者是一个信笺的分拣系统: 5 | 这个分拣系统通过3个参数来识别,匹配优先级:action>data>category 6 | Action: 动作 view 7 | Data: 数据uri uri 8 | Category : 另外的附加信息 9 | 10 | Action 匹配: 11 | Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 IntentFilter 可以包含多个Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”,例如: 12 | …… 13 | 如果我们在启动一个 Activity 时使用这样的 Intent 对象: 14 | Intent intent =new Intent(); 15 | intent.setAction("com.myself.action"); 16 | 那么所有的 Action 列表中包含了“com.myself”的 Activity 都将会匹配成功。 17 | Android 预定义了一系列的 Action 分别表示特定的系统动作。这些 Action 通过常量的方式定义在 18 | android.content. Intent中,以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。 19 | URI 数据匹配: 20 | 一个 Intent 可以通过 URI 携带外部数据给目标组件。在 节点中,通过 节点匹配外部数据。mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下: 21 | 22 | 电话的uri: tel: 12345 23 | 网址的uri:[http://www.baidu.com](http://www.baidu.com/) 24 | 自己定义的uri:content://com.myself.app/self 25 | 如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。 26 | Category 类别匹配: 27 | 节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时Category 类别匹配才会成功。 -------------------------------------------------------------------------------- /Android 基础/81.BroadcastReceiver与LocalBroadcastReceiver的区别.md: -------------------------------------------------------------------------------- 1 | #### BroadcastReceiver与LocalBroadcastReceiver的区别 2 | 3 | Android中BroadcastReceiver与LocalBroadcastReceiver的区别 4 | 即本地广播和全局广播的区别 5 | 6 | 1)通信范围的比较 7 | 1:LocalBroadcastReceiver即本地广播,而BroadcastReceiver是全局广播. 8 | 2:LocalBroadcastReceiver只能接收来自本App发送的广播,并且它只能用于应用内的通信. 9 | 所以它的安全性更好,但是通信范围比较小,仅局限于App应用内. 10 | 而BroadcastReceiver它不仅针对App内的广播有效,而且对App应用之间的广播通信、App应用和系统间的广播通信也有效,它的通信范围更大. 11 | 12 | 2)通信效率的比较 13 | 1:LocalBroadcastManager的核心实现是Handler,因此它是应用内的通信,自然安全性更好,运行效率更高. 14 | 2:BroadcastReceiver的核心实现是Binder,是全局广播,可以跨进程通信,范围更广,从而导致它的运行效率没有本地广播高效,毕竟一个是本地的通信,一个是跨进程的通信方式,效率肯定相对较低点,但对于实时性不高的应用场景我们可以忽略不计. 15 | 16 | 3)注册方式的比较 17 | 1:本地广播不能用静态注册的方式,只能采用动态注册的方式. 18 | 2:全局广播可以用静态注册的方式,也可以采用动态注册的方式. 19 | 20 | 4)注册代码的比较 21 | 1:本地广播注册代码 22 | IntentFilter filter = new IntentFilter(); 23 | filter.addAction(ACTION_LOCAL_SEND); 24 | LocalBroadcastManager.getInstance(this).registerReceiver(mLocalBroadcastReceiver, filter); 25 | 2:全局广播注册代码 26 | IntentFilter filter = new IntentFilter(); 27 | filter.addAction(ACTION_ALL_SEND); 28 | getBaseContext().registerReceiver(mBroadcastReceiver, filter); 29 | 30 | 5)取消注册代码的比较 31 | 1:本地广播取消注册代码 32 | if (mLocalBroadcastReceiver != null) { 33 | LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalBroadcastReceiver); 34 | } 35 | 2:全局广播取消注册代码 36 | if (mBroadcastReceiver != null) { 37 | getBaseContext().unregisterReceiver(mBroadcastReceiver); 38 | } 39 | 40 | 6)发送广播代码的比较 41 | 1:本地广播发送代码 42 | Intent intent = new Intent(ACTION_LOCAL_SEND); 43 | Bundle extras = new Bundle(); 44 | extras.putString(KEY_MSG, "local"); 45 | intent.putExtras(extras); 46 | LocalBroadcastManager.getInstance(this).sendBroadcast(intent); 47 | 2:全局广播发送代码 48 | Intent intent = new Intent(ACTION_ALL_SEND); 49 | Bundle extras = new Bundle(); 50 | extras.putString(KEY_MSG, "all"); 51 | intent.putExtras(extras); 52 | getBaseContext().sendBroadcast(intent); -------------------------------------------------------------------------------- /Android 基础/83.LinearLayout和RelativeLayout性能对比.md: -------------------------------------------------------------------------------- 1 | #### LinearLayout和RelativeLayout性能对比 2 | 3 | 1. RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure 4 | 2. RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。 5 | 3. 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。 6 | 7 | 最后再思考一下为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。 -------------------------------------------------------------------------------- /Android 基础/85.Android主线程怎么给子线程发message.md: -------------------------------------------------------------------------------- 1 | #### Android主线程怎么给子线程发message 2 | 3 | 一言不合就上代码: 4 | 5 | ```java 6 | /** 7 | * 演示主线程给子线程发送Message 8 | */ 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | private Handler mHandler; 12 | private Looper mLooper; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | 19 | //Activity启动的时候创建一个子线程,并启动 20 | MyThread thread = new MyThread(); 21 | thread.start(); 22 | } 23 | 24 | /** 25 | * 绑定布局中的点击按钮,点击后给子线程发送消息 26 | */ 27 | public void click(View view) { 28 | Message message = new Message(); 29 | message.obj = "来自主线程"; 30 | mHandler.sendMessage(message); 31 | } 32 | 33 | /** 34 | * 绑定布局文件中的按钮2,点击后让子线程退出,关闭子线程 35 | * 其实这里只需要让子线程的Looper对象退出即可,因为Looper.loop();是线程阻塞的. 36 | */ 37 | public void click2(View view) { 38 | if (mLooper != null) { 39 | mLooper.quit(); 40 | } 41 | } 42 | 43 | 44 | @Override 45 | protected void onDestroy() { 46 | super.onDestroy(); 47 | //在退出的时候,将子线程释放掉,不然可能会导致内存泄露 48 | if (mLooper != null) { 49 | mLooper.quit(); 50 | } 51 | } 52 | 53 | private class MyThread extends Thread { 54 | @SuppressLint("HandlerLeak") 55 | @Override 56 | public void run() { 57 | //1. 创建一个Looper对象(内部创建了MessageQueue, 58 | // 并将MessageQueue作为Looper对象的成员,然后将Looper对象绑定到ThreadLocal中 59 | // 60 | Looper.prepare(); 61 | 62 | // 创建一个Handler 63 | mHandler = new Handler() { 64 | @Override 65 | public void handleMessage(Message msg) { 66 | //处理主线程发送的消息 67 | Log.e("tag", "接收到信息" + msg); 68 | } 69 | }; 70 | 71 | //2.获取当前Looper对象 72 | mLooper = Looper.myLooper(); 73 | 74 | //3.让消息循环起来 75 | Looper.loop(); 76 | 77 | Log.e("tag-", "子线程退出"); 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Android 基础/89.请简述你对AsyncTask异步任务的理解.md: -------------------------------------------------------------------------------- 1 | #### 请简述你对AsyncTask异步任务的理解 2 | 3 | Android提供了一个AsyncTask类专门用于处理以异步问题,这个类主要是为耗时操作开辟一个新线程。AsyncTask是一个抽象类,这个类是对Thread类的一个封装并加入了一些新的方法,该类(AsyncTask)定义了3种泛型类型参数,分别是Params,Progress,Result。 4 | 5 | 1.Params:启动任务执行的输入参数,例如HTTP请求的URL,任务执行器需要的数据类型。 6 | 7 | 2.Progress:后台任务执行的百分比。 8 | 9 | 3.Result:后台执行任务最终返回的结果,如 String、Integer等。 10 | 11 | 注意:有些参数不使用时可以设置为Void,如 AsyncTask。 12 | 13 | AsyncTask类主要用到的内部回调函数有onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()。这几个回调函数构成了AsyncTask类的使用逻辑结构。 14 | 15 | 1.onPreExecute():准备运行,该回调函数在任务被执行之后立即由UI线程调用,这个步骤通常用来完成在用户UI上显示进度条等 相关操作。 16 | 17 | 2.doInBackground(Params...):正在后台运行,该回调函数由后台线程在onPreExecute()方法执行结束后立即被调用,通常在这里执行耗时的后台计算。计算的结果必须由该函数返回,并被传到到onPostExecute()中处理。在该函数内也可以使用publishProgress()发布进度值,这些进度将会在onProgressUpdate()中被接收并发布到UI线程。 18 | 19 | 3.onProgressUpdate(Params...):进度更新,该函数由UI线程在publishProgress()方法调用后被调用,一般用于动态更新一个进度条。 20 | 21 | 4.onPostExecute()完成后台任务,后台计算结果后被调用,后台计算的结果作为参数传递给这一方法。 22 | 23 | 注意:AsyncTask适用于小型的简单的异步处理,并且每个AsyncTask子类至少复写doInBackground()方法。 -------------------------------------------------------------------------------- /Android 基础/92.XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?.md: -------------------------------------------------------------------------------- 1 | #### XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式? 2 | 3 | XML文档定义分为DTD和Schema两种形式; 4 | 二者都是对XML语法的约束,其本质区别在于Schema本身也是一个XML文件,可以被XML解析器解析,而且可以为XML承载的数据定义类型,约束能力较之DTD更强大。 5 | 6 | 对XML的解析主要有:DOM(文档对象模型,Document Object Model)、SAX(Simple API for XML)和StAX(Java 6中引入的新的解析XML的方式,Streaming API for XML),其中DOM处理大型文件时其性能下降的非常厉害,这个问题是由DOM树结构占用的内存较多造成的,而且DOM解析方式必须在解析文件之前把整个文档装入内存,适合对XML的随机访问(典型的用空间换取时间的策略);SAX是事件驱动型的XML解析方式,它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过事件回调代码来处理XML文件,适合对XML的顺序访问;顾名思义,StAX把重点放在流上,实际上StAX与其他解析方式的本质区别就在于应用程序能够把XML作为一个事件流来处理。将XML作为一组事件来处理的想法并不新颖(SAX就是这样做的),但不同之处在于StAX允许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。 7 | 8 | -------------------------------------------------------------------------------- /Android 虚拟机/101.Linux自带多种进程通信方式,为什么Android都没采用而偏偏使用Binder通信.md: -------------------------------------------------------------------------------- 1 | #### Linux自带多种进程通信方式,为什么Android都没采用而偏偏使用Binder通信 2 | 3 | 进程: 4 | 进程是操作系统的概念. 5 | 每当我们执行一个程序时,对于操作系统来讲就创建了一个进程. 6 | 在这个过程中,伴随着资源的分配和释放. 7 | 可以认为进程是一个程序的一次执行过程. 8 | 9 | 进程间通信: 10 | 进程用户空间是相互独立的,一般而言是不能相互访问的. 11 | 但很多情况下进程间需要互相通信,来完成系统的某项功能. 12 | 进程通过与内核及其它进程之间的互相通信来协调它们的行为. 13 | 14 | Linux现有进程间通信: 15 | 1)管道:在创建时分配一个page大小的内存,缓存区大小比较有限. 16 | 2)消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信. 17 | 3)共享内存:无须复制,共享缓冲区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决. 18 | 4)套接字:作为更通用的接口,传输效率低,主要用于不同机器或跨网络的通信. 19 | 5)信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源.因此,主要作为进程间以及同一进程内不同线程之间的同步手段. 20 | 6)信号:不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等. 21 | 22 | 对比: 23 | 1)从性能的角度(数据拷贝次数) 24 | 0次:共享内存 25 | 1次:Binder 26 | 2次:管道 消息队列 套接字 27 | 从性能角度看,Binder性能仅次于共享内存. 28 | 29 | 2)从稳定性的角度 30 | Binder基于C/S架构,C和S相对独立,稳定性较好. 31 | 共享内存实现方式复杂,没有客户与服务端之别,需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题. 32 | 从稳定性角度看,Binder优越于共享内存. 33 | 34 | 3)从安全的角度 35 | 传统Linux的IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份. 36 | 而Android作为一个开放的开源体系,拥有非常多的开发平台,App来源甚广,因此手机的安全显得额外重要. 37 | 对于普通用户,绝不希望从App商店下载偷窥隐私数据、后台造成手机耗电等等问题,传统Linux IPC无任何保护措施,完全由上层协议来确保. 38 | 39 | Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志. 40 | 前面提到C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端. 41 | Server端会根据权限控制策略判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行. 42 | 43 | 传统IPC只能由用户在数据包里填入UID/PID. 44 | 另外,可靠的身份标记只有由IPC机制本身在内核中添加. 45 | 其次传统IPC访问接入点是开放的,无法建立私有通道. 46 | 从安全角度,Binder的安全性更高. 47 | 48 | 4)从语言层面的角度 49 | Linux基于C(面向过程),Android基于Java(面向对象). 50 | 而对于Binder恰恰也符合面向对象的思想,将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法. 51 | 而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中. 52 | 可以从一个进程传给其它进程,让大家都能访问同一Server,就像将一个对象或引用赋值给另一个引用一样. 53 | Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中. 54 | 从语言层面,Binder更适合基于面向对象语言的Android系统. 55 | 56 | 5)从公司战略的角度 57 | Linux内核是开源的系统,所开放源代码许可协议GPL保护,该协议具有“病毒式感染”的能力. 58 | Android之父Andy Rubin对于GPL显然是不能接受的. 59 | 60 | 为此,Google巧妙地将GPL协议控制在内核空间. 61 | 将用户空间的协议采用Apache-2.0协议. 62 | 同时在GPL协议与Apache-2.0之间的Lib库中采用BSD授权方法,有效隔断了GPL的传染性. 63 | 64 | 综合上述5点,可知Binder是Android系统上层进程间通信的不二选择. 65 | 66 | 参考: 67 | 1)https://www.cnblogs.com/liugh-wait/p/8533003.html 68 | 2)https://www.zhihu.com/question/39440766/answer/89210950 69 | 3)https://blog.csdn.net/universus/article/details/6211589 70 | 4)http://www.360doc.com/content/18/0414/22/11935121_745697757.shtml -------------------------------------------------------------------------------- /Android 虚拟机/115.简述Android虚拟机和JAVA虚拟机的区别.md: -------------------------------------------------------------------------------- 1 | #### 简述Android虚拟机和JAVA虚拟机的区别 2 | 3 | Android虚拟机:即DVM(Dalvik Virtual Machine),为啥不叫AVM? 4 | JAVA虚拟机:即JVM(Java Virtual Machine)。 5 | Dalvik 是 Google 公司自己设计用于 Android 平台的 Java虚拟机,每一个Android 应用程序都拥有一个独立的Dalvik 虚拟机实例,应用程序都在它自己的进程中运行。而每一个 Dalvik 都是在Linux 中的一个进程。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 6 | **dvm和jvm区别:** 7 | **1.执行的字节码不一样** 8 | dvm执行的是.dex 文件,而jvm 执行的是.class。 9 | .jar文件里面包含多个.class文件,每个.class文件里面包含了该类的头信息(如编译版本)、常量池、类信息、域、方法、属性等等,当JVM加载该.jar文件的时候,会加载里面的所有的.class文件,这样会很慢。 10 | Android 工程编译后的所有.class字节码会被dex工具抽取到一个.dex文件中。 .class文件存在很多的冗余信息,dex工具会去除冗余信息减少了整体文件尺寸,并把所有的.class文件整合到.dex文件中。减少了I/O 操作,提高了类的查找速度。 11 | 12 | ``` 13 | jvm: java->class->jar 14 | dvm: java->class->dex 15 | ``` 16 | 17 | [![image](https://user-images.githubusercontent.com/20238022/65572716-86712e00-df9b-11e9-86a6-679108d9201b.png)](https://user-images.githubusercontent.com/20238022/65572716-86712e00-df9b-11e9-86a6-679108d9201b.png) 18 | 19 | **2.基于的架构不一样** 20 | Dalvik 基于寄存器,寄存器是CPU上面的一块存储空间;而 JVM 基于栈,栈是内存上面的一段连续的存储空间。寄存器存取速度比栈快的多,dvm可以根据硬件实现最大的优化,比较适合移动设备。 21 | 22 | **3.Dalvik 和 Java 运行环境不同** 23 | Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。Dalvik虚拟机有自己的 bytecode,并非使用 Java bytecode。 24 | JVM,是运行所有Java程序的假想计算机,是Java程序的运行环境。我们用Java编写的软件可以运行在任何的操作系统上,这个特性称为Java语言的跨平台性。该特性是由JVM实现的,我们编写的程序运行在JVM上,而JVM运行在操作系统上。 25 | [![image](https://user-images.githubusercontent.com/20238022/65574030-e4534500-df9e-11e9-83f8-32fab24003bf.png)](https://user-images.githubusercontent.com/20238022/65574030-e4534500-df9e-11e9-83f8-32fab24003bf.png) 26 | JVM本身不具备跨平台功能的,每个操作系统都有不同版本的虚拟机 27 | 28 | **4.Dalvik 和 Java 的SDK不同** 29 | 30 | 其他: 31 | Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。 32 | Android有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化,库的加载,预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的数据提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域。 33 | 34 | 参考:(https://blog.csdn.net/mr_zhaojy/article/details/52776254) 35 | JVM(https://blog.csdn.net/qq_41701956/article/details/81664921) -------------------------------------------------------------------------------- /Android 虚拟机/152.简单说说 dexopt 与 dex2oat 的区别.md: -------------------------------------------------------------------------------- 1 | #### 简单说说 dexopt 与 dex2oat 的区别 2 | 3 | dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。 4 | 5 | dex2oat 是对 dex 文件的 AOT 提前编译操作,其需要一个 dex 文件,然后对其进行编译,结果是一个本地可执行的 ELF 文件,可以直接被本地处理器执行。 6 | 7 | 除此之外 Dalvik 虚拟机中有使用 JIT 编译器,也就是说其也能将程序运行的热点 java 字节码编译成本地 code 执行。所以其与 Art 虚拟机还是有区别的,Art 虚拟机的 dex2oat 是提前编译所有 dex 字节码,而 Dalvik 虚拟机只编译使用启发式检测中最频繁执行的热点字节码。 8 | 9 | -------------------------------------------------------------------------------- /Android 虚拟机/161.管程是什么谈谈它的重要性.md: -------------------------------------------------------------------------------- 1 | #### 管程是什么?谈谈它的重要性 2 | 3 | 管程的概念 4 | 1.管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。 5 | 2.进程只能互斥得使用管程,即当一个进程使用管程时,另一个进程必须等待。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程。 6 | 3.在管程入口处的等待队列称为入口等待队列,由于进程会执行唤醒操作,因此可能有多个等待使用管程的队列,这样的队列称为紧急队列,它的优先级高于等待队列。 7 | 8 | 管程的重要性 9 | 并发编程里两大核心问题——互斥和同步,都可以由管程来帮你解决。 10 | 学好管程,理论上所有的并发问题你都可以解决,并且很多并发工具类底层都是管程实现的, 11 | 所以学好管程,就是相当于掌握了一把并发编程的万能钥匙。 -------------------------------------------------------------------------------- /Android 虚拟机/26.ART和Dalvik区别.md: -------------------------------------------------------------------------------- 1 | #### ART和Dalvik区别 2 | 3 | ##### 参考答案 4 | 5 | 什么是Dalvik: 6 | Dalvik是Google公司自己设计用于Android平台的Java虚拟机。 7 | 它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行。 8 | .dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。 9 | Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。 10 | 独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 11 | 12 | 什么是ART: 13 | 与Dalvik不同,ART使用预编译(AOT,Ahead-Of-Time)。 14 | 也就是在APK运行之前,就对其包含的Dex字节码进行翻译,得到对应的本地机器指令,于是就可以在运行时直接执行了。 15 | ART应用安装的时候把dex中的字节码将被编译成本地机器码,之后每次打开应用,执行的都是本地机器码。 16 | 去除了运行时的解释执行,效率更高,启动更快。 17 | 18 | 区别: 19 | 20 | 1. Dalvik每次都要编译再运行,Art只会首次启动编译 21 | 2. Art占用空间比Dalvik大(原生代码占用的存储空间更大),就是用“空间换时间” 22 | 3. Art减少编译,减少了CPU使用频率,使用明显改善电池续航 23 | 4. Art应用启动更快、运行更快、体验更流畅、触感反馈更及时 24 | 25 | 参考官方文档: 26 | -------------------------------------------------------------------------------- /Android 进阶/135.组件化如何实现,组件化与插件化的差别在哪里,该怎么选型.md: -------------------------------------------------------------------------------- 1 | #### 组件化如何实现,组件化与插件化的差别在哪里,该怎么选型 2 | 3 | 一、组件化 4 | 组件化,就是把APP拆分成不同功能模块,形成独立组件,让宿主调用。 组件化不一定是插件化,组件化是一个更大的概念:把模块解耦,组件之间代码不依赖,宿主可以依赖组件;而插件化则具体到了技术点上,宿主通过 动态加载 来调用组件,宿主不依赖组件,达到 完全解耦 的目的(比如图片缓存就可以看成一个组件被多个 App 共用)。 5 | 6 | 适合于项目大 但是功能相对集中。比如 一个金融类的App 里面只包含金融的功能,金融功能又会有 借贷,理财,线下交易,把这些模块抽成单独的组件 7 | 8 | 二、插件化 9 | Android程序每次更新都要下载一个完整的apk,而很多时候软件只是更新了一个小功能而已,这样的话,就显得很麻烦。如果把android程序做成主程序+插件化的形式呢,这样才利于小功能的扩展(比如一般 App 的皮肤样式就可以看成一个插件)。 10 | 11 | 通过 gradle 配置的方式,将打 debug 包和 release 包分开。这样会有一个好处,开发一个模块,在 debug 的时候,可以打成一个 apk ,独立运行测试,可以完全独立于整个宿主 APP 的其他所有组件;待到要打 release 包的时候,再把这个模块作为一个 library ,打成 aar ,作为整个宿主 APP 的一部分。而 debug 和 release 的切换都是通过 gradle 配置,可以做到无缝切换。至于模块之间的跳转,可以用别名的方式,而不是用 Activity 和 Fragment 类名。这样所有的模块和宿主 APP 都是完全解耦的,彻底解决了并行开发的可能造成的交叉依赖等问题。 12 | 13 | 主要原理是:主要利用 Java ClassLoader 的原理,如 Android 的 DexClassLoader,可动态加载的内容包括 apk、dex、jar 等。如下 14 | 15 | 插件化的优势: 16 | 适应并行开发,解耦各个模块,避免模块之间的交叉依赖,加快编译速度,从而提高并行开发效率。 17 | 满足产品随时上线的需求 18 | 修复因为我们对自己要求不严格而写出来的 bug。 19 | 插件化的结果:分为稳定的 release 版本和不稳定的 snapshot 版本,每个模块都高度解耦,没有交叉依赖,不会出现一个模块依赖了另一个模块,其中一个人改了这个模块的代码,对另一个模块造成影响。 20 | 淘宝的框架是用了osgi的bundle概念,整个应用框架生命周期完整。 21 | 22 | **适合于项目超级大 但是功能相对不集中。**比如 一个支付宝App 里面即包含共享单车 也包含 电影票。这种与本业务完全不同的 可以做成插件的形式 23 | 24 | 插件化弊端: 25 | 每一个插件都是一个apk,插件多的时候管理起来也麻烦。 26 | 27 | -------------------------------------------------------------------------------- /Android 进阶/168.SharedPreferences 的加载实现是在子线程,但是为什么说 getX(key) 操作可能还会阻塞主线程呢.md: -------------------------------------------------------------------------------- 1 | #### SharedPreferences 的加载实现是在子线程,但是为什么说 getX(key) 操作可能还会阻塞主线程呢 2 | 3 | ```java 4 | //getSharedPreferences 获取 SP 的核心源码 5 | class ContextImpl extends Context { 6 | //Context会把对应 SP 缓存起来 7 | private static ArrayMap> sSharedPrefsCache; 8 | ...... 9 | @Override 10 | public SharedPreferences getSharedPreferences(File file, int mode) { 11 | ...... 12 | SharedPreferencesImpl sp; 13 | synchronized (ContextImpl.class) { 14 | final ArrayMap cache = getSharedPreferencesCacheLocked(); 15 | sp = cache.get(file); 16 | if (sp == null) { 17 | //第一次或者新进程中时缓存没有就新 new 一个对应实例 18 | sp = new SharedPreferencesImpl(file, mode); 19 | cache.put(file, sp); 20 | return sp; 21 | } 22 | } 23 | ...... 24 | return sp; 25 | } 26 | ...... 27 | } 28 | ``` 29 | 30 | 接着看看 SharedPreferencesImpl 实例化及通过 SharedPreferences 获取一个指定 key 的值的核心源码部分 31 | 32 | ```java 33 | //一个File对应的一个SP实例 34 | final class SharedPreferencesImpl implements SharedPreferences { 35 | //用来存储该SP的所有Key-Value对 36 | private Map mMap; 37 | SharedPreferencesImpl(File file, int mode) { 38 | ...... 39 | //构造方法从磁盘加载SP文件 40 | startLoadFromDisk(); 41 | } 42 | private void startLoadFromDisk() { 43 | ...... 44 | //启动一个名字为SharedPreferencesImpl-load的线程从磁盘读文件 45 | new Thread("SharedPreferencesImpl-load") { 46 | public void run() { 47 | loadFromDisk(); 48 | } 49 | }.start(); 50 | } 51 | //子线程中读取文件解析成key-value对Map 52 | private void loadFromDisk() { 53 | ...... 54 | Map map = null; 55 | StructStat stat = null; 56 | try { 57 | stat = Os.stat(mFile.getPath()); 58 | if (mFile.canRead()) { 59 | BufferedInputStream str = null; 60 | try { 61 | str = new BufferedInputStream( 62 | new FileInputStream(mFile), 16*1024); 63 | map = XmlUtils.readMapXml(str); 64 | } catch (Exception e) { 65 | Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); 66 | } finally { 67 | IoUtils.closeQuietly(str); 68 | } 69 | } 70 | } catch (ErrnoException e) { 71 | /* ignore */ 72 | } 73 | synchronized (mLock) { 74 | mLoaded = true; 75 | if (map != null) { 76 | mMap = map; 77 | mStatTimestamp = stat.st_mtime; 78 | mStatSize = stat.st_size; 79 | } else { 80 | mMap = new HashMap<>(); 81 | } 82 | //子线程读取SP文件到mMap成功后通过notify通知释放阻塞锁 83 | mLock.notifyAll(); 84 | } 85 | } 86 | //通过SharedPreferences对象获取指定key的值 87 | //(一般与SharedPreferences获取在一个线程,主线程) 88 | public int getInt(String key, int defValue) { 89 | synchronized (mLock) { 90 | //阻塞等待SP读取到内存后再get 91 | awaitLoadedLocked(); 92 | Integer v = (Integer)mMap.get(key); 93 | return v != null ? v : defValue; 94 | } 95 | } 96 | private void awaitLoadedLocked() { 97 | ...... 98 | while (!mLoaded) { 99 | try { 100 | //阻塞等待SP读取到内存完成 101 | mLock.wait(); 102 | } catch (InterruptedException unused) { 103 | } 104 | } 105 | } 106 | ...... 107 | } 108 | ``` 109 | 110 | 所以说 SharedPreferences 读取 Map 虽然在子线程,但是其 getX(key) 系列方法想要调用的前提是 SharedPreferences 子线程已经读取完成,否则就会阻塞,所以 SharedPreferences 中如果存储太大内容或者太多内容导致 XML 解析等变慢就会导致后面的 getX(key) 阻塞主线程,从而导致主线程卡顿,所以说 SharedPreferences 是轻量级的持久化工具。 -------------------------------------------------------------------------------- /Android 进阶/181.App 是如何沙箱化,为什么要这么做?.md: -------------------------------------------------------------------------------- 1 | #### App 是如何沙箱化,为什么要这么做? 2 | 3 | Android沙箱本质是为了实现不同应用程序之间的互相隔离,而这种隔离策略是通过让不同的应用程序运行于各自己的虚拟机进程中实现的。 4 | 5 | 沙箱,对使用者来说可以理解为一种安全环境,对恶意访问者来说是一种限制。 6 | 7 | 在Android系统中,应用(通常)都在一个独立的沙箱中运行,即每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。Dalvik经过优化,允许在有限的内存中同时高效地运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的Linux进程执行。Android这种基于Linux的进程“沙箱”机制,是整个安全设计的基础之一。 8 | 9 | Android从Linux继承了已经深入人心的类Unix进程隔离机制与最小权限原则,同时结合移动终端的具体应用特点,进行了许多有益的改进与提升。具体而言,进程以隔离的用户环境运行,不能相互干扰,比如发送信号或者访问其他进程的内存空间。因此,Android沙箱的核心机制基于以下几个概念: 10 | 1)标准的Linux进程隔离 11 | 2)大多数进程拥有唯一的用户ID(UID) 12 | 3)以及严格限制文件系统权限。 13 | 14 | 参考:https://blog.csdn.net/fei20121106/article/details/84023953 -------------------------------------------------------------------------------- /Android 进阶/184.如何计算一个Bitmap占用内存的大小,怎么保证加载Bitmap不产生内存溢出?.md: -------------------------------------------------------------------------------- 1 | #### 如何计算一个Bitmap占用内存的大小,怎么保证加载Bitmap不产生内存溢出? 2 | 3 | Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存 4 | 5 | 注:这里inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高 进行拉伸,进而改变Bitmap占用内存的大小。 6 | 7 | 在Bitmap里有两个获取内存占用大小的方法。 8 | 9 | - getByteCount():API12 加入,代表存储 Bitmap 的像素需要的最少内存。 10 | - getAllocationByteCount():API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。 11 | 在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大 小,getAllocationByteCount() 表示被复用 Bitmap真实占用的内存大小(即 mBuffer 的长度)。 12 | 13 | 为了保证在加载Bitmap的时候不产生内存溢出,可以受用BitmapFactory进行图片压缩,主要有以下几个参数: 14 | 15 | - BitmapFactory.Options.inPreferredConfig:将ARGB_8888改为RGB_565,改变编码方式,节约内存。 16 | - BitmapFactory.Options.inSampleSize:缩放比例,可以参考Luban那个库,根据图片宽高计算出合适的缩放比例。 17 | - BitmapFactory.Options.inPurgeable:让系统可以内存不足时回收内存。 -------------------------------------------------------------------------------- /Android 进阶/189.Android里的内存缓存和磁盘缓存是怎么实现的.md: -------------------------------------------------------------------------------- 1 | #### Android里的内存缓存和磁盘缓存是怎么实现的 2 | 3 | 内存缓存基于LruCache实现,磁盘缓存基于DiskLruCache实现。这两个类都基于Lru算法和LinkedHashMap来实现。 4 | 5 | LRU算法可以用一句话来描述,如下所示: 6 | 7 | - LRU是Least Recently Used的缩写,最近最久未使用算法,从它的名字就可以看出,它的核心原则是如果一个数据在最近一段时间没有使用到,那么它在将来被 访问到的可能性也很小,则这类数据项会被优先淘汰掉。 8 | 9 | LruCache的原理是利用LinkedHashMap持有对象的强引用,按照Lru算法进行对象淘汰。具体说来假设我们从表尾访问数据,在表头删除数据,当访问的数据项在链表中存在时,则将该数据项移动到表尾,否则在表尾新建一个数据项。当链表容量超过一定阈值,则移除表头的数据。 10 | 11 | 为什么会选择LinkedHashMap呢? 12 | 这跟LinkedHashMap的特性有关,LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。 13 | 14 | DiskLruCache与LruCache原理相似,只是多了一个journal文件来做磁盘文件的管理,如下所示: 15 | 16 | ``` 17 | libcore.io.DiskLruCache 18 | 1 19 | 1 20 | 1 21 | 22 | DIRTY 1517126350519 23 | CLEAN 1517126350519 5325928 24 | REMOVE 1517126350519 25 | ``` 26 | 27 | 注:这里的缓存目录是应用的缓存目录/data/data/pckagename/cache,未root的手机可以通过以下命令进入到该目录中或者将该目录整体拷贝出来: 28 | 29 | ``` 30 | //进入/data/data/pckagename/cache目录 31 | adb shell 32 | run-as com.your.packagename 33 | cp /data/data/com.your.packagename/ 34 | 35 | //将/data/data/pckagename目录拷贝出来 36 | adb backup -noapk com.your.packagename 37 | ``` 38 | 39 | 我们来分析下这个文件的内容: 40 | 41 | - 第一行:libcore.io.DiskLruCache,固定字符串。 42 | - 第二行:1,DiskLruCache源码版本号。 43 | - 第三行:1,App的版本号,通过open()方法传入进去的。 44 | - 第四行:1,每个key对应几个文件,一般为1. 45 | - 第五行:空行 46 | - 第六行及后续行:缓存操作记录。 47 | 48 | 第六行及后续行表示缓存操作记录,关于操作记录,我们需要了解以下三点: 49 | 50 | 1. DIRTY 表示一个entry正在被写入。写入分两种情况,如果成功会紧接着写入一行CLEAN的记录;如果失败,会增加一行REMOVE记录。注意单独只有DIRTY状态的记录是非法的。 51 | 2. 当手动调用remove(key)方法的时候也会写入一条REMOVE记录。 52 | 3. READ就是说明有一次读取的记录。 53 | 4. CLEAN的后面还记录了文件的长度,注意可能会一个key对应多个文件,那么就会有多个数字。 -------------------------------------------------------------------------------- /Android 进阶/190.android classloader使用的双亲委托机制是什么.md: -------------------------------------------------------------------------------- 1 | #### android classloader使用的双亲委托机制是什么 2 | 3 | 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。 4 | 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 -------------------------------------------------------------------------------- /Android 进阶/48.简述app启动过程.md: -------------------------------------------------------------------------------- 1 | #### 简述app启动过程 2 | 3 | ##### 参考答案 4 | 5 | App启动一般分为两种: 6 | 7 | 1. 冷启动:当应用启动的时候,后台没有当前应用的进程,这时系统会创建一个新的进程分配给应用。 8 | 2. 热启动:当前应用已经打开,但是被按下返回键或者Home键退出到桌面或者去到其他App,当再次回到应用时就是热启动。 9 | 10 | 这里主要介绍冷启动,大致分为5个步骤: 11 | 12 | 1. 当点击桌面图标,就会利用Launcher通过Binder进程间通信机制通知ActivityManagerService(AMS),它要启动一个Activity; 13 | 2. AMS得到Launcher的通知,就会新建一个Task去准备启动Activity,并通过Binder机制通知Launcher进入Paused状态; 14 | 3. Launcher得到消息,就会直接挂起,并通过Binder告诉AMS我已经Paused了;AMS知道了Launcher已经挂起之后,就可以放心的为新的Activity准备启动工作了,首先,APP肯定需要一个新的进程去进行运行,所以需要创建一个新进程,这个过程是需要Zygote参与的,AMS通过Socket去和Zygote协商,然后利用Zygote.fork()创建一个新的进程,在这个进程里启动ActivityThread类,这就是每一个应用程序都有一个ActivityThread与之对应的原因; 15 | 4. 进程创建好了,通过调用上述的ActivityThread的main方法,这是应用程序的入口,在这里开启Looper消息循环队列,这也是主线程默认绑定Looper的原因;(另外,ActivityThread通过Binder将一个ApplicationThread类型的Binder对象传递给AMS,以便以后AMS能够通过这个Binder对象和它进行通信); 16 | 5. 这时候,App还没有启动完,要永远记住,四大组件的启动都需要AMS去启动,将上述的应用进程信息注册到AMS中,所以AMS在通过BinderActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。 17 | 18 | ![]() 19 | 20 | 补充知识: 21 | **Zygote** 22 | zygote名字翻译叫受精卵,zygote进程的创建是由Linux系统中init进程创建的,Android中所有的进程都是直接或者间接的由init进程fork出来的,Zygote进程负责其他进程的创建和启动,比如创建SystemServer进程。当需要启动一个新的android应用程序的时候,ActivityManagerService就会通过Socket通知Zygote进程为这个应用创建一个新的进程。 23 | 24 | **Launcher** 25 | 我们要知道手机的桌面也是一个App我们叫它launcher,每一个手机应用都是在Launcher上显示,而Launcher的加载是在手机启动的时候加载Zygote,然后Zygote启动SystenServer,SystenServer会启动各种ManageService, 包括ActivityManagerService,并将这些ManageService注册到ServiceManage 容器中,然后ActivityManagerService就会启动Home应用程序Launcher. 26 | 27 | **ActivityManagerService** 28 | ActivityManagerService我们简称AMS,四大组件都归它管,四大组件的跨进程通信都要和它合作。 29 | 30 | **Binder** 31 | Binder是Android跨进程通信(IPC)的一种方式,也是Android系统中最重要的特性之一,android 四大组件以及不同的App都运行在不同的进程,它则是各个进程的桥梁将不同的进程粘合在一起。 32 | 33 | **ActivityThread** 34 | 首先ActivityThread并不是一个Thread,其作用就是在main方法内做消息循环。那我们常说的主线程是什么?主线程就是承载ActivityThread的Zygote fork而创建的进程。 35 | ActivityThread的调用是在ActivityManageService.startProcessLocked()方法里调用并创建,这个类主要做了这几个事: 36 | 37 | 1. 创建Looper,开启Looper循环 38 | 2. 创建内部类 H,H继承于Handler 用于跨进程通信切换线程 39 | 3. 创建ApplicationThread跨进程Binder对象mAppThread。 这里要说一点,ActivityThread通过ApplicationThread与AMS进行通信,ApplicationThread通过H与ActivityThread进行通信(handler机制),处理Activity的事务。 40 | 41 | ##### 蛋友补充 42 | 43 | ###### From [GavinCui12](https://github.com/GavinCui12) 44 | 45 | 1、点击桌面应用图标,Launcher进程将启动Activity(MainActivity)的请求以Binder的方式发送给了AMS。 46 | 47 | 2、AMS接收到启动请求后,交付ActivityStarter处理Intent和Flag等信息,然后再交给ActivityStackSupervisior/ActivityStack 处理Activity进栈相关流程。同时以Socket方式请求Zygote进程fork新进程。 48 | 49 | 3、Zygote接收到新进程创建请求后fork出新进程。 50 | 51 | 4、在新进程里创建ActivityThread对象,新创建的进程就是应用的主线程,在主线程里开启Looper消息循环,开始处理创建Activity。 52 | 53 | 5、ActivityThread利用ClassLoader去加载Activity、创建Activity实例,并回调Activity的onCreate()方法,这样便完成了Activity的启动。 -------------------------------------------------------------------------------- /Android 进阶/57.谈谈热修复的原理.md: -------------------------------------------------------------------------------- 1 | #### 谈谈热修复的原理 2 | 3 | ##### 参考答案 4 | 5 | 我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件, 6 | 而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。 7 | 8 | -------------------------------------------------------------------------------- /Android 进阶/63.LruCache 算法源码解析.md: -------------------------------------------------------------------------------- 1 | #### LruCache 算法源码解析 2 | 3 | #### 参考答案 4 | 我们先看下LruCache算法的构造方法。 5 | ```java 6 | public LruCache(int maxSize) { 7 | if (maxSize <= 0) { 8 | throw new IllegalArgumentException("maxSize <= 0"); 9 | } 10 | this.maxSize = maxSize; 11 | this.map = new LinkedHashMap(0, 0.75f, true); 12 | } 13 | ``` 14 | 从构造方法的源码我们可以看到,在这段代码中我们主要做了两件事。第一是判断下传递来的最大分配内存大小是否小于零,如果小于零则抛出异常,因为我们如果传入一个小于零的内存大小就没有意义了。之后在构造方法内存就new了一个LinkHashMap集合,从而得知LruCache内部实现原理果然是基于LinkHashMap来实现的。 15 | 16 | 之后我们再来看下存储缓存的put()方法。 17 | ```java 18 | public final V put(K key, V value) { 19 | if (key == null || value == null) { 20 | throw new NullPointerException("key == null || value == null"); 21 | } 22 | 23 | V previous; 24 | synchronized (this) { 25 | putCount++; 26 | size += safeSizeOf(key, value); 27 | previous = map.put(key, value); 28 | if (previous != null) { 29 | size -= safeSizeOf(key, previous); 30 | } 31 | } 32 | 33 | if (previous != null) { 34 | entryRemoved(false, key, previous, value); 35 | } 36 | 37 | trimToSize(maxSize); 38 | return previous; 39 | } 40 | ``` 41 | 从代码中我们可以看到,这个put方法内部其实没有做什么很特别的操作,就是对数据进行了一次插入操作。但是我们注意到最后的倒数第三行有一个trimToSize()方法,那么这个方法是做什么用的呐?我们点进去看下。 42 | ```java 43 | public void trimToSize(int maxSize) { 44 | while (true) { 45 | K key; 46 | V value; 47 | synchronized (this) { 48 | if (size < 0 || (map.isEmpty() && size != 0)) { 49 | throw new IllegalStateException(getClass().getName() 50 | + ".sizeOf() is reporting inconsistent results!"); 51 | } 52 | 53 | if (size <= maxSize) { 54 | break; 55 | } 56 | 57 | Map.Entry toEvict = map.eldest(); 58 | if (toEvict == null) { 59 | break; 60 | } 61 | 62 | key = toEvict.getKey(); 63 | value = toEvict.getValue(); 64 | map.remove(key); 65 | size -= safeSizeOf(key, value); 66 | evictionCount++; 67 | } 68 | 69 | entryRemoved(true, key, value, null); 70 | } 71 | } 72 | ``` 73 | 我们可以看到,这个方法原来就是对内存做了一次判断,如果发现内存已经满了,那么就调用map.eldest()方法获取到最后的数据,之后调用map.remove(key)方法,将这个最近最少使用的数据给剔除掉,从而达到我们内存不炸掉的目的。 74 | 75 | 我们再来看看get()方法。 76 | ```java 77 | public final V get(K key) { 78 | //key为空抛出异常 79 | if (key == null) { 80 | throw new NullPointerException("key == null"); 81 | } 82 | 83 | V mapValue; 84 | synchronized (this) { 85 | //获取对应的缓存对象 86 | //get()方法会实现将访问的元素更新到队列头部的功能 87 | mapValue = map.get(key); 88 | if (mapValue != null) { 89 | hitCount++; 90 | return mapValue; 91 | } 92 | missCount++; 93 | } 94 | ``` 95 | 96 | get方法看起来就是很常规的操作了,就是通过key来查找value的操作,我们再来看看LinkHashMap的中get方法。 97 | 98 | ```java 99 | public V get(Object key) { 100 | LinkedHashMapEntry e = (LinkedHashMapEntry)getEntry(key); 101 | if (e == null) 102 | return null; 103 | //实现排序的关键方法 104 | e.recordAccess(this); 105 | return e.value; 106 | } 107 | ``` 108 | 调用recordAccess()方法如下: 109 | 110 | ```java 111 | void recordAccess(HashMap m) { 112 | LinkedHashMap lm = (LinkedHashMap)m; 113 | if (lm.accessOrder) { 114 | lm.modCount++; 115 | remove(); 116 | addBefore(lm.header); 117 | } 118 | } 119 | ``` 120 | 由此可见LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即最近最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。 -------------------------------------------------------------------------------- /Android 进阶/70.简述apk打包过程.md: -------------------------------------------------------------------------------- 1 | #### 简述apk打包过程 2 | 3 | ##### 参考答案 4 | 5 | 1:打包资源文件,生成R.java文件 6 | 输入:res文件,Assets文件,AndroidManifest.xml文件,Android基础类库(Android.jar文件) 7 | 输出:R.java,resources.arsc 8 | 工具:aapt 9 | 工具位置:SDK\build-tools\29.0.0\aapt.exe 10 | 11 | 2:处理aidl文件,生成相应java文件 12 | 输入:源码文件,aidl文件,framework.aidl文件 13 | 输出:对应的.java文件 14 | 工具:aidl工具 15 | 工具位置:SDK\build-tools\29.0.0\aidl.exe 16 | 17 | 3:编译工程源代码,生成相应class文件 18 | 输入:源码文件(包括R.java和AIDL生成的.java文件),库文件(jar文件) 19 | 输出:.class文件 20 | 工具:javac工具 21 | 工具位置:Java\jdk1.8.0_201\bin\javac.exe 22 | 23 | 4:转换所有class文件,生成classes.dex文件 24 | 输入:.class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),库文件(.jar文件) 25 | 输出:.dex文件 26 | 工具:javac工具 27 | 工具位置:Java\jdk1.8.0_201\bin\javac.exe 28 | 29 | 5:打包生成apk文件 30 | 输入:打包后的资源文件,打包后类文件(.dex文件),libs文件(包括.so文件) 31 | 输出:未签名的.apk文件 32 | 工具:apkbuilder.bat工具已废弃,改为sdklib.jar工具 33 | 工具位置:E:\SDK\tools\lib\sdklib.jar 34 | 35 | 6:对apk文件进行签名 36 | 输入:未签名的.apk文件 37 | 输出:签名的.apk文件 38 | 工具: 39 | jarsigner工具 40 | apksigner工具 41 | 工具位置: 42 | Java\jdk1.8.0_201\bin\jarsigner.exe 43 | SDK\build-tools\29.0.0\lib\apksigner.jar 44 | 45 | 7:对签名后的apk文件进行对齐处理 46 | 输入:签名后的.apk文件 47 | 输出:对齐后的.apk文件 48 | 工具:zipalign工具 49 | 工具位置:SDK\build-tools\29.0.0\zipalign.exe 50 | 51 | 注:工具位置基于win平台. 52 | 参考连接: 53 | https://developer.android.com/studio/build/index.html?hl=zh-cn#build-process 54 | https://blog.csdn.net/jiangwei0910410003/article/details/50402000 -------------------------------------------------------------------------------- /Android 进阶/76.AOT和JIT以及混合编译的区别、优劣.md: -------------------------------------------------------------------------------- 1 | #### AOT和JIT以及混合编译的区别、优劣 2 | 3 | ##### 参考答案 4 | 5 | AOT和JIT是什么? 6 | AOT,即Ahead-of-time,指预先编译. 7 | JIT,即Just-In-Time,指即时编译. 8 | 9 | 区别: 10 | 主要区别在于是否在“运行时”进行编译. 11 | 12 | 优劣: 13 | AOT优点: 14 | 1.在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗. 15 | 2.可以在程序运行初期就达到最高性能. 16 | 3.可以显著的加快程序的启动. 17 | AOT缺点: 18 | 1.在程序运行前编译会使程序安装的时间增加. 19 | 2.牺牲Java的一致性. 20 | 3.将提前编译的内容保存会占用更多的外存. 21 | 22 | JIT优点: 23 | 1.可以根据当前硬件情况实时编译生成最优机器指令(ps:AOT也可以做到,在用户使用是使用字节码根据机器情况在做一次编译). 24 | 2.可以根据当前程序的运行情况生成最优的机器指令序列. 25 | 3.当程序需要支持动态链接时,只能使用JIT. 26 | 4.可以根据进程中内存的实际情况调整代码,使内存能够更充分的利用. 27 | JIT缺点: 28 | 1.编译需要占用运行时资源,会导致进程卡顿. 29 | 2.由于编译时间需要占用运行时间,对于某些代码的编译优化不能完全支持,需要在程序流畅和编译时间之间做权衡. 30 | 3.在编译准备和识别频繁使用的方法需要占用时间,使得初始编译不能达到最高性能. 31 | 32 | 混合编译: 33 | Android N引入了使用编译+解释+JIT的混合运行时,以获得安装时间,内存占用,电池消耗和性能之间的最佳折衷. 34 | 优点: 35 | 即使是大型应用程序的安装时间也减少到几秒钟. 36 | 系统更新安装得更快,因为它们不需要优化步骤. 37 | 应用程序的RAM占用空间较小,在某些情况下降至50%. 38 | 改善了表现. 39 | 降低电池消耗. 40 | 41 | 参考: 42 | https://source.android.com/devices/tech/dalvik 43 | https://www.infoq.com/news/2016/03/android-n-aot-jit 44 | https://player.fm/series/android-developers-backstage-1245114/episode-45-state-of-the-art -------------------------------------------------------------------------------- /Android 进阶/78.Android WebView 的漏洞有哪几种.md: -------------------------------------------------------------------------------- 1 | #### Android WebView 的漏洞有哪几种 2 | 3 | 主要三类漏洞: 4 | 5 | 1. WebView 中 addJavascriptInterface() 接口 6 | 2. WebView 内置导出的 searchBoxJavaBridge_对象 7 | 3. WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象 8 | 4. 任意代码执行漏洞 9 | 10 | 1.1 WebView 中 addJavascriptInterface()接口 11 | 12 | 原因 13 | JS调用Android的其中一个方式是通过addJavascriptInterface接口进行对象映射,当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。 14 | 解决 15 | Android 4.2以前,需要采用**拦截prompt()**的方式进行漏洞修复 16 | Android 4.2以后,则只需要对被调用的函数以 @JavascriptInterface进行注解 17 | 18 | 1.2 WebView 内置导出的 searchBoxJavaBridge_对象 19 | 20 | 原因 21 | 在Android 3.0以下,Android系统会默认通过searchBoxJavaBridge_的Js接口给 WebView 添加一个JS映射对象:searchBoxJavaBridge_对象 22 | 该接口可能被利用,实现远程任意代码。 23 | 24 | 解决 25 | 删除searchBoxJavaBridge_接口 26 | 27 | 1.3 WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象 28 | 原因和解决方法同上 29 | 30 | 1. 密码明文存储漏洞 31 | 32 | 原因 33 | WebView默认开启密码保存功能:mWebView.setSavePassword(true) 34 | 开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码; 35 | 如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险 36 | 解决 37 | 关闭密码保存提醒:WebSettings.setSavePassword(false) 38 | 39 | 1. 域控制不严格漏洞 40 | 41 | getSettings类的方法对 WebView 安全性的影响 42 | 43 | 3.1 setAllowFileAccess 44 | // 设置是否允许 WebView 使用 File 协议 45 | webView.getSettings().setAllowFileAccess(true); 46 | 如果不允许使用 file 协议,则不会存在上述的威胁; 47 | 但同时也限制了 WebView 的功能,使其不能加载本地的 html 文件 48 | 49 | 解决 50 | 对于不需要使用 file 协议的应用,禁用 file 协议; 51 | 对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript。 52 | 53 | 3.2 setAllowFileAccessFromFileURLs 54 | // 设置是否允许通过 file url 加载的 Js代码读取其他的本地文件 55 | webView.getSettings().setAllowFileAccessFromFileURLs(true); 56 | // 在Android 4.1前默认允许 // 在Android 4.1后默认禁止 57 | 58 | 解决方案 59 | 设置setAllowFileAccessFromFileURLs(false); 60 | 61 | 3.3 setAllowUniversalAccessFromFileURLs 62 | // 设置是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源) 63 | webView.getSettings().setAllowUniversalAccessFromFileURLs(true); 64 | // 在Android 4.1前默认允许(setAllowFileAccessFromFileURLs()不起作用) 65 | // 在Android 4.1后默认禁止 66 | 67 | 解决方案 68 | 设置setAllowUniversalAccessFromFileURLs(false); 69 | 70 | 参考: 71 | https://blog.csdn.net/carson_ho/article/details/64904635 -------------------------------------------------------------------------------- /Android 进阶/87.Android推送的基本原理.md: -------------------------------------------------------------------------------- 1 | #### Android推送的基本原理 2 | 3 | 1.推送是什么? 4 | 推送就是指服务器定向将信息实时发送至客户端的功能. 5 | 2.长连接和短连接 6 | 长连接是指客户端和服务器始终建立着一个通信连接,在连接没有中断之前,客户端和服务器之间可以随时进行通信.(如Socket) 7 | 短连接是指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接.(如Http) 8 | 短连接实现即时通讯叫做轮询,长连接实现即时通讯叫做推送. 9 | 3.Android推送基本原理(如下图) 10 | 11 | ![](https://user-images.githubusercontent.com/19246347/62948207-8a475780-be16-11e9-9296-06d8abd60cec.png) -------------------------------------------------------------------------------- /Android 进阶/90.请描述一下View的绘制流程.md: -------------------------------------------------------------------------------- 1 | #### 请描述一下View的绘制流程 2 | 3 | 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为:该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw),其框架过程如下: 4 | ![a](https://user-images.githubusercontent.com/20238022/63223175-2214bf00-c1e4-11e9-8536-cbcb2bade12d.png) 5 | 6 | **一、measure测三围** 7 | 主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View 的控件的实际宽高都是由父视图和本身视图决定的。 8 | 具体的调用链如下: 9 | ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup 对象的onMeasure()方法,该方法实现的功能如下: 10 | 1、设置本View 视图的最终大小,该功能的实现通过调用 setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth)。 11 | 2 、如果该View 对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure() 过 程 。 对 每 个 子 视 图 的 measure() 过 程 , 是 通 过 调 用 父 类 ViewGroup.java 类 里 的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了 View 对象的measure()方法。 12 | 13 | **二、layout摆好姿势** 14 | 主要作用:为将整个根据子视图的大小以及布局参数将 View树放到合适的位置上。 15 | 具体的调用链如下: 16 | 1、layout 方法会设置该 View 视图位于父视图的坐标轴,即 mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View 是ViewGroup对象,需要实现该方法,对每个子视图进行布局)。 17 | 2、如果该View 是个ViewGroup类型,需要遍历每个子视图 chiildView,调用该子视图的 layout()方法去设置它的坐标值。 18 | 19 | **三、draw挥洒激情** 20 | 由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View 树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View 类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。 21 | 调用流程 : 22 | 1 、绘制该View 的背景 23 | 2 、为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框) 24 | 3、调用onDraw()方法绘制视图本身(每个View 都需要重载该方法,ViewGroup不需要实现该方法) 25 | 4、调用dispatchDraw ()方法绘制子视图(如果该 View 类型不为ViewGroup,即不包含子视图,不需要重载该方法) 26 | 27 | 值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。 28 | 29 | 参考blog分享:http://blog.csdn.net/qinjuning/article/details/7110211 -------------------------------------------------------------------------------- /Android 进阶/91.谈谈冷启动与热启动.md: -------------------------------------------------------------------------------- 1 | #### 谈谈冷启动与热启动 2 | 3 | app冷启动:当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就叫做冷启动((后台不存在该应用进程) 4 | 冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上. 5 | 6 | app热启动:当应用已经被打开,但是被按下返回键、Home键等按键时回到桌面或者是其他程序的时候,再重新打开该app时,这个方式叫做热启动(后台已经存在该应用进程). 7 | 热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application. 8 | 9 | 冷启动的流程 10 | 当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上. 11 | 12 | 冷启动的生命周期简要流程: 13 | Application构造方法 –> attachBaseContext()–>onCreate –>Activity构造方法 –> onCreate() –> 配置主体中的背景等操作 –>onStart() –> onResume() –> 测量、布局、绘制显示 14 | 15 | 冷启动的优化主要是视觉上的优化,解决白屏问题,提高用户体验, 16 | 所以通过上面冷启动的过程.能做的优化如下: 17 | 1)减少 onCreate()方法的工作量 18 | 2)不要让 Application 参与业务的操作 19 | 3)不要在 Application 进行耗时操作 20 | 4)不要以静态变量的方式在 Application 保存数据 21 | 5)减少布局的复杂度和层级 22 | 6)减少主线程耗时 -------------------------------------------------------------------------------- /Android 逆向/65.使用Xposed为什么需要root.md: -------------------------------------------------------------------------------- 1 | #### 使用Xposed为什么需要root 2 | 3 | ##### 参考答案 4 | 5 | Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务,可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。是通过加载插件的形式安装的,例如微信抢红包等 6 | 7 | Xposed通过劫持Android系统的zygote进程来加载自定义功能,就像是半路截杀,在应用运行之前就已经将我们需要的自定义内容强加在了系统进程当中。 8 | 9 | Magisk类似Xposed,但是他是通过挂载一个与系统文件相隔离的文件系统来加载自定义内容,为系统分区打开了一个通往平行世界的入口,所有改动在那个世界Magisk分区里发生,在必要的时候却又可以被认为是(从系统分区的角度而言)没有发生过 10 | 11 | 因为Xposed需要胁持Zygote进程,所以必须root,不然拿不到权限 -------------------------------------------------------------------------------- /JVM/141.Java new一个对象的过程中发生了什么.md: -------------------------------------------------------------------------------- 1 | #### Java new一个对象的过程中发生了什么 2 | 3 | java new一个对象的过程中发生了什么 4 | java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载。加载并初始化类完成后,再进行对象的创建工作。 5 | 我们先假设是第一次使用该类,这样的话new一个对象就可以分为两个过程:加载并初始化类和创建对象。 6 | 一、类加载过程(第一次使用该类) 7 |   java是使用双亲委派模型来进行类的加载的,所以在描述类加载过程前,我们先看一下它的工作过程: 8 | 双亲委托模型的工作过程是:如果一个类加载器(ClassLoader)收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要加载的类)时,子加载器才会尝试自己去加载。 9 | 使用双亲委托机制的好处是:能够有效确保一个类的全局唯一性,当程序中出现多个限定名相同的类时,类加载器在执行加载时,始终只会加载其中的某一个类。 10 | 1、加载 11 |      由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例 12 | 2、验证 13 | 格式验证:验证是否符合class文件规范 14 | 语义验证:检查一个被标记为final的类型是否包含子类;检查一个类中的final方法是否被子类进行重写;确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同) 15 | 操作验证:在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证(通常在解析阶段执行,检查是否可以通过符号引用中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等) 16 | 3、准备 17 | 为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量不在此操作范围内) 18 | 被final修饰的static变量(常量),会直接赋值; 19 | 4、解析 20 | 将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。 21 | 解析需要静态绑定的内容。 // 所有不会被重写的方法和域都会被静态绑定 22 |   以上2、3、4三个阶段又合称为链接阶段,链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中。 23 | 5、初始化(先父后子) 24 | 4.1 为静态变量赋值 25 | 4.2 执行static代码块 26 | 注意:static代码块只有jvm能够调用 27 |    如果是多线程需要同时初始化一个类,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。 28 | 29 | 因为子类存在对父类的依赖,所以类的加载顺序是先加载父类后加载子类,初始化也一样。不过,父类初始化时,子类静态变量的值也有有的,是默认值。 30 | 最终,方法区会存储当前类类信息,包括类的静态变量、类初始化代码(定义静态变量时的赋值语句 和 静态初始化代码块)、实例变量定义、实例初始化代码(定义实例变量时的赋值语句实例代码块和构造方法)和实例方法,还有父类的类信息引用。 31 | 32 | 二、创建对象 33 | 1、在堆区分配对象需要的内存 34 |   分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量 35 | 2、对所有实例变量赋默认值 36 |   将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值 37 | 3、执行实例初始化代码 38 |   初始化顺序是先初始化父类再初始化子类,初始化时先执行实例代码块然后是构造方法 39 | 4、如果有类似于Child c = new Child()形式的c引用的话,在栈区定义Child类型引用变量c,然后将堆区对象的地址赋值给它 40 | 41 | 需要注意的是,每个子类对象持有父类对象的引用,可在内部通过super关键字来调用父类对象,但在外部不可访问 42 | 43 | 补充: 44 | 通过实例引用调用实例方法的时候,先从方法区中对象的实际类型信息找,找不到的话再去父类类型信息中找。 45 | 如果继承的层次比较深,要调用的方法位于比较上层的父类,则调用的效率是比较低的,因为每次调用都要经过很多次查找。这时候大多系统会采用一种称为虚方法表的方法来优化调用的效率。 46 | 所谓虚方法表,就是在类加载的时候,为每个类创建一个表,这个表包括该类的对象所有动态绑定的方法及其地址,包括父类的方法,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。当通过对象动态绑定方法的时候,只需要查找这个表就可以了,而不需要挨个查找每个父类。 -------------------------------------------------------------------------------- /JVM/148.java main 函数为什么必须用 public static void 修饰.md: -------------------------------------------------------------------------------- 1 | #### java main 函数为什么必须用 public static void 修饰 2 | 3 | 1. 为什么 main 方法是静态的(static) 4 | 正因为 main 方法是静态的,JVM 调用这个方法就不需要创建任何包含这个 main 方法的实例。 5 | 因为 C 和 C++ 同样有类似的 main 方法作为程序执行的入口。 6 | 如果 main 方法不声明为静态的,JVM 就必须创建 main 类的实例,因为构造器可以被重载,JVM 就没法确定调用哪个 main 方法。 7 | 静态方法和静态数据加载到内存就可以直接调用而不需要像实例方法一样创建实例后才能调用,如果 main 方法是静态的,那么它就会被加载到 JVM 上下文中成为可执行的方法。 8 | 2. 为什么main方法是公有的(public) 9 | Java 指定了一些可访问的修饰符如:private、protected、public,任何方法或变量都可以声明为 public,Java 可以从该类之外的地方访问。因为 main 方法是公共的,JVM 就可以轻松的访问执行它。 10 | 3. 为什么 main 方法没有返回值(Void)? 11 | 因为 main 返回任何值对程序都没任何意义,所以设计成 void,意味着 main 不会有任何值返回。 12 | 4. 总结 13 | 14 | - main 方法必须声明为 public、static、void,否则 JVM 没法运行程序 。 15 | 如果 JVM 找不到 main 方法就抛出 NoSuchMethodError:main 异常,例如:如果你运行命令:java HelloWrold,JVM 就会在 HelloWorld.class 文件中搜索 public static void main (String[] args) 方法。 16 | - main 方式是程序的入口,程序执行的开始处。 17 | - main 方法被一个特定的线程 ”main” 运行,程序会一直运行直到 main 线程结束或者 non-daemon 线程终止。 18 | - 当你看到“Exception in Thread main”如:Excpetion in Thread main:Java.lang.NullPointedException,意味着异常来自于 main 线程。 19 | - 你可以声明 main 方法使用 java1.5 的可变参数的方式如:public static void main(String... args)。 20 | - 除了 static、void、和 public,你可以使用 final,synchronized、和 strictfp 修饰符在 main 方法的签名中,如:public strictfp final synchronized static void main(String[] args)。 21 | - main 方法在 Java 可以像其他方法一样被重载,但是 JVM 只会调用上面这种签名规范的 main 方法。 22 | - 你可以使用 throws 子句在方法签名中,可以抛出任何 checked 和 unchecked 异常。 23 | - 静态初始化块在 JVM 调用 main 方法前被执行,它们在类被 JVM 加载到内存的时候就被执行了。 -------------------------------------------------------------------------------- /JVM/156.一个Java对象究竟有多大(占据多少内存).md: -------------------------------------------------------------------------------- 1 | #### 一个Java对象究竟有多大(占据多少内存) 2 | 3 | 想要精确计算一个Java对象占用的内存,首先要了解Java对象的结构表示。 4 | 一个Java对象在Heap的表示,可以分为三部分: 5 | 1)Object Header 6 | 2)Class Pointer 7 | 3)Fields 8 | 每个普通Java对象在堆(heap)中都有一个头信息(object header),头信息是必不可少的,记录着对象的状态。 9 | 32位与64位占用空间不同 10 | 32位中: 11 | hash(25)+age(4)+lock(3)=32bit 12 | 64位中: 13 | unused(25+1)+hash(31)+age(4)+lock(3)=64bit 14 | 我们知道,在Java中,一切皆对象;每个类都有一个父类, ClassPointer就是当前对象父类的一个指针,在32位系统中,这个指针为4byte; 15 | 在64位系统中,如果开启指针压缩(-XX:+UseCompressedOops)或者JVM堆的最大值小于32G,这个指针也是4byte,否则是8byte。 16 | 关于字段(Fields),这里指的是类的实例字段;也就是说不包括静态字段,因为这个字段是共享内存的,只会存在一份。 17 | 下面以32位系统为例子,计算一下 java.lang.Integer到底占用多大内存: 18 | ObjectHeader和 Pointer都是固定的,4+4=8byte;再看看字段,只有这一个,表示数值: 19 | private final int value; 20 | 一个int在java中占据4byte,所以Integer的大小为4+4+4=12byte。 21 | 这个结果对吗?不对!还有一点没有说: 在java,对象占用的heap大小是8位对齐的,上面的12byte没有对齐,所以需要补位4byte。结果是16byte! 22 | 另外,在Java中还有一种特殊的对象, 数组!没错,这个对象有点特殊,它比其他对象多了一个属性:长度(length)。 23 | 所以我们计算数组长度的时候,需要额外加上一个长度的字段,即一个int的大小。 24 | 例如:int[] arr = new int[10]; 25 | arr的占用heap大小为: 26 | 4(object header)+4(pointer)+4(length)+4*10(10个int大小)=52byte 由于需要8位对齐,所以最终大小为 56byte。 27 | 在了解了对象的内存使用情况后,我们可以简单算一笔帐。一个 java.lang.Integer占用16byte,而一个 int占用4byte,4:1的比例! 28 | 也就是说整数的类类型是基本类型内存的4倍! 29 | 30 | 由此我们得出第一个节约内存的原则: 31 | (1) 尽量使用基本类型,而不是包装类型。 32 | 数据库建表的时候字段类型需要仔细推敲,同样JavaBean中的属性字段类型也需要仔细斟酌。 33 | 不要吝啬使用short,byte,boolean,如果短类型能放下数据,尽量不要使用更长的类型。 34 | 一个long比一个int才多4byte,但是你要想,如果内存中有100W个long,那就白白浪费了约4MB空间,不要小看这一点点的空间浪费, 35 | 因为随便一个跑着在线应用的JVM中,对象都能达到上千万!内存是节省出来的。 36 | 37 | (2) 斟酌字段类型,在满足容量前提下,尽量用小字段。 38 | 你知道一个ArrayList集合,如果里面放了10个数字,占用多少内存吗?让我们算算: 39 | ArrayList中有两个字段: 40 | private transient Object[] elementData; 41 | private int size; 42 | Object Header占4byte,Pointer占4byte,一个int字段(size)占4byte,elementData数组本身占12(4+4+4),数组中10个Integer对象占10×16。 43 | 所以整个集合空间大小为4+4+4+12+160=184byte。 44 | 如果我们用int[]代替集合呢,12+4×10=52byte,对其后56byte。 45 | 集合跟数组的比例是184:56,超过3:1了! 46 | 47 | (3) 如果可能,尽量用数组,少用集合 48 | 数组中是可以使用基本类型的,但是集合中只能放包装类型! 49 | 如果实在需要使用集合,推荐一个比较节约内存的集合工具,fastutil。 50 | 这里面包含了JDK集合中绝大部分的实现,而且比较省内存。 51 | 52 | 在上面的三个原则基础上,提供两个小技巧。 53 | 1)时间用long/int表示,不用Date或者String。 54 | 2)短字符串如果能穷举或者转换成ascii表示,可以用long或者int表示。 55 | 注意:小技巧跟具体的场景数据有关系,可以根据实际情况进行激进优化节省内存。 56 | 57 | 性能和可读性向来就有些矛盾,在这里也是,为了节约内存,不得不进行取舍,代码丑陋了一些,可读性差了一些,还好能省下一些内存。 58 | 上面的原则在 确实需要节约内存的时候 ,不妨可以试试! -------------------------------------------------------------------------------- /JVM/170.什么是类加载器?类加载器有哪些?.md: -------------------------------------------------------------------------------- 1 | #### 什么是类加载器?类加载器有哪些? 2 | 3 | 通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 4 | 5 | 主要有一下四种类加载器 6 | 1.启动类加载器:这个类加载器负责放在\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库。用户无法直接使用。 7 | 8 | 2.扩展类加载器:这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。用户可以直接使用。 9 | 10 | 3.应用程序类加载器:这个类由sun.misc.Launcher$AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。 11 | 12 | 4.自定义加载器:用户自己定义的类加载器。 -------------------------------------------------------------------------------- /JVM/21.谈谈Java的垃圾回收机制以及触发时机.md: -------------------------------------------------------------------------------- 1 | #### 谈谈Java的垃圾回收机制以及触发时机 2 | 3 | ##### 参考答案 4 | 5 | 内存回收机制:就是释放掉在内存中已经没有用的对象,要判断怎样的对象是没用的,有两种方法:(1)采用标记数的方法,在给内存中的对象打上标记,对象被引用一次,计数加一,引用被释放,计数就减一,当这个计数为零时,这个对象就可以被回收,但是,此种方法,对于循环引用的对象是无法识别出来并加以回收的,(2)采用根搜索的方法,从一个根出发,搜索所有的可达对象,则剩下的对象就是可被回收的,垃圾回收是在虚拟机空闲的时候或者内存紧张的时候执行的,什么时候回收并不是由程序员控制的,可达与不可达的概念:分配对象使用new关键字,释放对象时,只需将对象的引用赋值为null,让程序不能够在访问到这个对象,则称该对象不可达。 6 | 7 | 在以下情况中垃圾回收机制会被触发: 8 | (1)所有实例都没有活动线程访问 ;(2)没有其他任何实例访问的循环引用实例;(3)Java中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。 9 | 10 | 11 | 12 | ##### 蛋友补充 13 | 14 | ###### From [safier](https://github.com/safier) 15 | 16 | ### 垃圾收集算法 17 | 18 | #### 1. Mark-Sweep(标记-清除)算法 19 | 20 | 这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示: 21 | 22 | [![image](https://user-images.githubusercontent.com/20643294/57460977-bba90800-72a8-11e9-97d7-408255106e7d.png)](https://user-images.githubusercontent.com/20643294/57460977-bba90800-72a8-11e9-97d7-408255106e7d.png) 23 | 24 | 从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。 25 | 26 | #### 2. Copying(复制)算法 27 | 28 | 为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示: 29 | 30 | [![image](https://user-images.githubusercontent.com/20643294/57461047-e1cea800-72a8-11e9-9dfb-2d0597cfef87.png)](https://user-images.githubusercontent.com/20643294/57461047-e1cea800-72a8-11e9-9dfb-2d0597cfef87.png) 31 | 32 | 这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。 33 | 34 | #### 3. Mark-Compact(标记-整理)算法 35 | 36 | 了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示: 37 | 38 | [![image](https://user-images.githubusercontent.com/20643294/57461135-0b87cf00-72a9-11e9-92e0-2b67426165e2.png)](https://user-images.githubusercontent.com/20643294/57461135-0b87cf00-72a9-11e9-92e0-2b67426165e2.png) 39 | 40 | #### 4. Generational Collection(分代收集)算法 41 | 42 | 分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。 43 | 44 |   目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。 45 | 46 |   而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。 47 | 48 |   注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。 -------------------------------------------------------------------------------- /JVM/46.谈谈4种gc算法.md: -------------------------------------------------------------------------------- 1 | #### 谈谈4种gc算法 2 | 3 | ##### 参考答案 4 | 5 | 1:标记—清除 Mark-Sweep 6 | 过程:标记可回收对象,进行清除 7 | 缺点:标记和清除效率低,清除后会产生内存碎片 8 | 9 | 2:复制算法 10 | 过程:将内存划分为相等的两块,将存活的对象复制到另一块内存,把已经使用的内存清理掉 11 | 缺点:使用的内存变为了原来的一半 12 | 进化:将一块内存按8:1的比例分为一块Eden区(80%)和两块Survivor区(10%) 13 | 每次使用Eden和一块Survivor,回收时,将存活的对象一次性复制到另一块Survivor上,如果另一块Survivor空间不足,则使用分配担保机制存入老年代 14 | 15 | 3:标记—整理 Mark—Compact 16 | 过程:所有存活的对象向一端移动,然后清除掉边界以外的内存 17 | 18 | 4:分代收集算法 19 | 过程:将堆分为新生代和老年代,根据区域特点选用不同的收集算法,如果新生代朝生夕死,则采用复制算法,老年代采用标记清除,或标记整理 20 | -------------------------------------------------------------------------------- /JVM/77.Java 对象的内存分配过程是如何保证线程安全的.md: -------------------------------------------------------------------------------- 1 | #### Java 对象的内存分配过程是如何保证线程安全的 2 | 3 | ##### 参考答案 4 | 5 | #### 内存分配 6 | 7 | 在Java中,创建一个对象的方法有很多种,如使用new、使用反射、使用Clone方法等,但是无论如何,对象在创建过程中,都需要进行内存分配。拿最常见的new关键字举例,当我们使用new创建对象后代码开始运行后,虚拟机执行到这条new指令的时候,会先检查要new的对象对应的类是否已被加载,如果没有被加载则先进行类加载。在类加载检查通过之后,就需要给对象进行内存分配了,分配的内存主要用来存放对象的实例变量。在进行内存分配时,需要根据对象中的实例变量情况等信息确定需要分配的空间大小,然后从Java堆中划分出这样一块区域(假设没有JIT优化)。根据JVM使用的垃圾回收器的类型,因其回收算法不同,会导致堆中内存分配情况不同。如标记-清楚算法回收后的内存中会有大量不连续的内存碎片,在给新的对象分配的时候,就需要通过"空闲列表"来确定一块空闲区域。无论那种方式,最终都需要确定出一块内存区域,用于给新建对象分配内存。我们知道,对象的内存分配过程中,主要是对象的引用指向这个内存区域,然后进行初始化操作。 8 | 9 | #### 内存分配过程的线程安全性 10 | 11 | 1. 对分配内存空间的动作做同步处理,采用CAS机制,配合失败重试的方式保证更新操作的线程安全性。 12 | 13 | 但是这种方案每次分配时都需要进行同步控制,这种是比较低效的。 14 | 15 | 2. 每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块"私有"内存中分配,当这部分区域用完之后,再分配新的"私有"内存。 16 | 17 | 这种方案被称之为TLAB分配,即Thread Local Allocation Buffer。这部分Buffer是从堆中划分出来的,但是是本地线程独享的。**这里值得注意的是,我们说TLAB时线程独享的,但是只是在“分配”这个动作上是线程独占的,至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别。** 18 | 19 | 另外,TLAB仅作用于新生代的Eden Space,对象被创建的时候首先放到这个区域,但是新生代分配不了内存的大对象会直接进入老年代。因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。所以,虽然对象刚开始可能通过TLAB分配内存,存放在Eden区,但是还是会被垃圾回收或者被移到Survivor Space、Old Gen等。 20 | 21 | 我们使用了TLAB之后,在TLAB上给对象分配内存时线程独享的了,这就没有冲突了,但是,TLAB这块内存自身从堆中划分出来的过程也可能存在内存安全问题啊。所以,在对于TLAB的分配过程,还是需要进行同步控制的。但是这种开销相比于每次为单个对象划分内存时候对进行同步控制的要低的多。虚拟机是否使用TLAB是可以选择的,可以通过设置-XX:+/-UseTLAB参数来指定。 22 | 23 | #### 总结 24 | 25 | 为了保证Java对象的内存分配的安全性,同时提升效率,每个线程在Java堆中可以预先分配一小块内存,这部分内存称之为TLAB(Thread Local Allocation Buffer),这块内存的分配时线程独占的,读取、使用、回收是线程共享的。 26 | 27 | 可以通过设置-XX:+/-UseTLAB参数来指定是否开启TLAB分配。 28 | 29 | 参考资料: 30 | 31 | 《深入理解Java虚拟机》 32 | 33 | https://www.cnblogs.com/straybirds/p/8529924.html 34 | 35 | https://www.zhihu.com/question/56538259 -------------------------------------------------------------------------------- /JVM/84.JVM加载class文件的原理机制.md: -------------------------------------------------------------------------------- 1 | #### JVM加载class文件的原理机制 2 | 3 | JVM加载class文件的原理机制 JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。 由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括: 4 | 5 | 1. 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类。 6 | 2. 如果类中存在初始化语句,就依次执行这些初始化语句。 7 | 3. 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。 8 | 9 | 从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。 JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明: 10 | 11 | 1. Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar)。 12 | 2. Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap。 13 | 3. System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性 java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。 -------------------------------------------------------------------------------- /Java 基础/03.谈谈你对java三大特性的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈你对java三大特性的理解 2 | 3 | ##### 参考答案 4 | 5 | ###### 封装 6 | 7 | 封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。 8 | 9 | 封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 10 | 11 | ###### 继承 12 | 13 | 面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 14 | 15 | 通过继承创建的新类称为“子类”或“派生类”。 16 | 17 | 被继承的类称为“基类”、“父类”或“超类”。 18 | 19 | 继承的过程,就是从一般到特殊的过程。 20 | 21 | ###### 多态 22 | 23 | 多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。 24 | 25 | 实现多态,有二种方式,覆盖,重载。 26 | 27 | 覆盖,是指子类重新定义父类的虚函数的做法。 28 | 29 | 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。 30 | 31 | 32 | 33 | ##### 补充(统一格式:昵称 + 补充答案) 34 | 35 | -------------------------------------------------------------------------------- /Java 基础/07.HashMap和Hashtable的区别.md: -------------------------------------------------------------------------------- 1 | #### HashMap和Hashtable的区别 2 | 3 | ##### 参考答案 4 | 5 | 1. HashMap是map接口的子类,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value,而hashtable不允许。 6 | 7 | 2. HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,由于非线程安全,效率上可能高于Hashtable。 8 | 9 | 3. HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。 10 | 11 | 4. Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 12 | 13 | 5. Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。但是如果使用Java 5或以上的话,可以用ConcurrentHashMap代替Hashtable。 14 | 15 | 6. Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差。 16 | 17 | 18 | 19 | ##### 补充(统一格式:昵称 + 补充答案) 20 | 21 | -------------------------------------------------------------------------------- /Java 基础/09.String,StringBuilder,StringBuffer的区别.md: -------------------------------------------------------------------------------- 1 | #### String,StringBuilder,StringBuffer的区别 2 | 3 | ##### 参考答案 4 | 5 | 1. 可变不可变 6 | String:字符串常量,在修改时不会改变自身;若修改,等于重新生成新的字符串对象。 7 | StringBuffer:在修改时会改变对象自身,每次操作都是对 StringBuffer 对象本身进行修改,不是生成新的对 8 | 象;使用场景:对字符串经常改变情况下,主要方法:append(),insert()等。 9 | 2. 线程是否安全 10 | String:对象定义后不可变,线程安全。 11 | StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区 12 | 大量数据。 13 | StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。 14 | 3. 共同点 15 | StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder(抽象类)。 16 | StringBuilder、StringBuffer 的方法都会调用 AbstractStringBuilder 中的公共方法,如 super.append(...)。 17 | 只是 StringBuffer 会在方法上加 synchronized 关键字,进行同步。最后,如果程序不是多线程的,那么使用 18 | StringBuilder 效率高于 StringBuffer。 19 | 20 | 21 | 22 | ##### 补充(统一格式:昵称 + 补充答案) 23 | 24 | -------------------------------------------------------------------------------- /Java 基础/103.谈谈对 Java 反射的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对 Java 反射的理解 2 | 3 | 反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。 4 | 5 | 通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。 6 | 7 | 反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。 8 | 9 | Java 反射主要提供以下功能: 10 | 11 | - 在运行时判断任意一个对象所属的类; 12 | - 在运行时构造任意一个类的对象; 13 | - 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法); 14 | - 在运行时调用任意一个对象的方法 15 | 16 | 重点:**是运行时而不是编译时** 17 | 18 | **反射最重要的用途就是开发各种通用框架。**很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。 19 | 20 | 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。 21 | 22 | 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。 23 | 24 | -------------------------------------------------------------------------------- /Java 基础/104.如何停止一个正在运行的线程.md: -------------------------------------------------------------------------------- 1 | #### 如何停止一个正在运行的线程 2 | 3 | 使用共享变量的方式 4 | 在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。 5 | 6 | 使用interrupt方法终止线程 7 | 如果一个线程由于等待某些事件的发生而被阻塞,又该怎样停止该线程呢?这种情况经常会发生,比如当一个线程由于需要等候键盘输入而被阻塞,或者调用Thread.join()方法,或者Thread.sleep()方法,在网络中调用ServerSocket.accept()方法,或者调用了DatagramSocket.receive()方法时,都有可能导致线程阻塞,使线程处于处于不可运行状态时,即使主程序中将该线程的共享变量设置为true,但该线程此时根本无法检查循环标志,当然也就无法立即中断。这里我们给出的建议是,不要使用stop()方法,而是使用Thread提供的interrupt()方法,因为该方法虽然不会中断一个正在运行的线程,但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码。 -------------------------------------------------------------------------------- /Java 基础/105.并发集合与普通集合的区别.md: -------------------------------------------------------------------------------- 1 | #### 并发集合与普通集合的区别 2 | 3 | 在java中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。 4 | 参考阅读: 5 |   ConcurrentHashMap 是线程安全的HashMap的实现,默认构造同样有initialCapacity 和loadFactor属性,不过还多了一个 concurrencyLevel属性,三属性默认值分别为16、0.75 及 16。其内部使用锁分段技术,维持这锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。 6 | 7 | put操作: 8 | 并没有在此方法上加上synchronized,首先对key.hashcode进行hash 操作,得到key的hash 值。hash 操作的算法和 map 也不同,根据此 hash 值计算并获取其对应的数组中的 Segment 对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操作。 9 | ConcurrentHashMap 基于concurrencyLevel划分出了多个Segment 来对key-value进行存储,从而避免每次put操作都得锁住整个数组。在默认的情况下,最佳情况下可允许16 个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。 10 | 11 | get(key): 12 |   首先对key.hashCode进行hash 操作,基于其值找到对应的Segment 对象,调用其get方法完成当前操作。而 Segment 的 get 操作首先通过 hash 值和对象数组大小减 1 的值进行按位与操作来获取数组上对应位置的HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的HashEntry 产生不一致性,那么ConcurrentHashMap 是如何保证的? 13 |   对象数组大小的改变只有在put操作时有可能发生,由于HashEntry对象数组对应的变量是volatile类型的,因此可以保证如HashEntry 对象数组大小发生改变,读操作可看到最新的对象数组大小。 14 |   在获取到了 HashEntry 对象后,怎么能保证它及其 next 属性构成的链表上的对象不会改变呢?这点ConcurrentHashMap 采用了一个简单的方式,即HashEntry 对象中的hash、key、next 属性都是final 的,这也就意味着没办法插入一个HashEntry对象到基于next属性构成的链表中间或末尾。这样就可以保证当获取到HashEntry对象后,其基于next 属性构建的链表是不会发生变化的。 15 |   ConcurrentHashMap 默认情况下采用将数据分为 16 个段进行存储,并且 16 个段分别持有各自不同的锁Segment,锁仅用于put 和 remove 等改变集合对象的操作,基于volatile 及 HashEntry 链表的不变性实现了读取的不加锁。这些方式使得ConcurrentHashMap 能够保持极好的并发支持,尤其是对于读远比插入和删除频繁的Map而言,而它采用的这些方法也可谓是对于Java内存模型、并发机制深刻掌握的体现。 16 | 17 | 推荐博客地址:http://m.oschina.net/blog/269037 -------------------------------------------------------------------------------- /Java 基础/106.Java创建对象的几种方式.md: -------------------------------------------------------------------------------- 1 | #### Java创建对象的几种方式 2 | 3 | 使用new关键字 4 | 使用Class类的newInstance方法 5 | 使用Constructor类的newInstance方法 6 | 使用clone方法 7 | 使用反序列化 8 | 9 | 参考: 10 | https://www.cnblogs.com/cy19/p/7684520.html -------------------------------------------------------------------------------- /Java 基础/107.重载(overload)和重写(override)的区别重载的方法能否根据返回类型进行区分.md: -------------------------------------------------------------------------------- 1 | #### 重载(overload)和重写(override)的区别?重载的方法能否根据返回类型进行区分? 2 | 3 | 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态 性。 4 | 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为 重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方 法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。 5 | 6 | 方法重载的规则: 7 | 8 | 1. 方法名一致,参数列表中参数的顺序,类型,个数不同。 9 | 2. 重载与方法的返回值无关,存在于父类和子类,同类中。 10 | 3. 可以抛出不同的异常,可以有不同修饰符。 11 | 12 | 方法重写的规则: 13 | 14 | 1. 参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致。 15 | 2. 构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次 16 | 声明。 17 | 3. 访问权限不能比父类中被重写的方法的访问权限更低。 18 | 4. 重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则 可以。 -------------------------------------------------------------------------------- /Java 基础/108.谈谈对 java 注解的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对 java 注解的理解 2 | 3 | 1. 定义:提供一种为程序元素设置元数据的方法,不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。 4 | 2. 功能: 5 | - 作为特定的标记,用于告诉编译器一些信息 6 | - 编译时动态处理,如动态生成代码 7 | - 运行时动态处理,作为额外信息的载体,如得到注解信息 8 | 3. 分类: 9 | - 标准注解:Override、Deprecated、SuppressWarnings 10 | 标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning; 11 | - 元注解:@Retention、@Target、@Inherited、@Documented 用来定义 Annotation 的 Annotation 12 | - 自定义注解:自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation 13 | 这里是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation 14 | 15 | #### 元注解 16 | 17 | 1. @Target 用于描述注解的使用范围(即:被描述的注解可以用在什么地方),说明了Annotation所修饰的对象范围,Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。 18 | 2. @Retention 定义了该Annotation被保留的时间长短,表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效) 19 | 3. @Documented 用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。 20 | 4. @Inherited 是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。 21 | 22 | ##### 自定义注解 23 | 24 | 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。 25 | 26 | 支持的格式: 27 | 28 | 1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short) 29 | 2. String类型 30 | 3. Class类型 31 | 4. enum类型 32 | 5. Annotation类型 33 | 6. 以上所有类型的数组 -------------------------------------------------------------------------------- /Java 基础/109.什么是不可变对象,它对写并发应用有什么帮助.md: -------------------------------------------------------------------------------- 1 | #### 什么是不可变对象,它对写并发应用有什么帮助 2 | 3 | 不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。 4 | 5 | 不可变对象的类即为不可变类(Immutable Class)。Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。 6 | 7 | 不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然它们的状态无法修改,这些常量永远不会变。 8 | 9 | 不可变对象永远是线程安全的。 10 | 11 | 只有满足如下状态,一个对象才是不可变的; 12 | 13 | 它的状态不能在创建后再被修改; 14 | 15 | 所有域都是final类型; 16 | 17 | 并且,它被正确创建(创建期间没有发生this引用的逸出)。 -------------------------------------------------------------------------------- /Java 基础/110.类什么时候被初始化.md: -------------------------------------------------------------------------------- 1 | #### 类什么时候被初始化 2 | 3 | 类初始化阶段是类加载过程的最后阶段。在这个阶段,java虚拟机才真正开始执行类定义中的java程序代码。那么类什么时候会被初始化呢? 4 | 1.创建类的实例,也就是new一个对象 5 | 2.访问某个类或接口的静态变量,或者对该静态变量赋值 6 | 3.调用类的静态方法 7 | 4.反射(Class.forName("com.example.load")) 8 | 5.初始化一个类的子类(会首先初始化子类的父类) 9 | 6.JVM启动时标明的启动类,即文件名和类名相同的那个类 10 | 11 | 只有这6中情况才会导致类的类的初始化。 12 | 类的初始化步骤: 13 | 1)如果这个类还没有被加载和链接,那先进行加载和链接 14 | 2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口) 15 | 3) 加入类中存在初始化语句(如static 变量和static块),那就依次执行这些初始化语句。 16 | 17 | (https://blog.csdn.net/feng_2016_07_22/article/details/82560651) -------------------------------------------------------------------------------- /Java 基础/112.阐述静态变量和实例变量的区别.md: -------------------------------------------------------------------------------- 1 | #### 阐述静态变量和实例变量的区别 2 | 3 | 静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝; 4 | 5 | 实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。 -------------------------------------------------------------------------------- /Java 基础/113.java中==和equals和hashCode的区别.md: -------------------------------------------------------------------------------- 1 | #### java中==和equals和hashCode的区别 2 | 3 | 1. 在java中"=="是用来比较变量值是否相等。如果是基本类型,直接比较值。如果是对象类型,比较的是两个对象的引用,也就是地址。对象是放在堆中的,栈中存放的是对象的引用。"==" 是对栈中的值进行比较的。 4 | 2. Object里有一个方法“equals”,这个方法是用来比较两个对象是否相等的。在Object类中有这样的代码: 5 | 6 | ``` 7 | public boolean equals(Object o) { 8 | return this == o; 9 | } 10 | ``` 11 | 12 | 1. 在Object里提供了hashcode这个方法。要说hashcode就得说java集合。java有的集合是不能重复的,所以需要用equeals判断集合中元素是否是同一个。但是如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。可以说hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 13 | 2. "=="与"equals"关系:如果类没有重写equals,那么对于该类的对象来说“==”和“equals”没有区别。都是比较对象的内存地址。 但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。(特别注意String类) 14 | 15 | ##### 总结: 16 | 17 | 1. 如果两个对象相同,那么它们的hashCode值一定要相同; 18 | 2. 如果两个对象的hashCode相同,它们并不一定相同 ,上面说的对象相同指的是用eqauls方法比较。反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。 如果重写这两个方法最好遵循以上原则。所以比较两者还要看具体是如何重写的。 -------------------------------------------------------------------------------- /Java 基础/114.Wait()与Sleep()方法的区别.md: -------------------------------------------------------------------------------- 1 | #### Wait()与Sleep()方法的区别 2 | 3 | 1. 每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。sleep()方法正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度); 4 | 2. sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用; 5 | 3. sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态; -------------------------------------------------------------------------------- /Java 基础/118.线程和进程的区别,为什么不仅仅用进程.md: -------------------------------------------------------------------------------- 1 | #### 线程和进程的区别?,为什么不仅仅用进程 2 | 3 | 进程:进程是具有独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度(若不支持线程机制,进程的系统调度的单位。否则,线程是系统调度的单位)的独立单位。特点:一、进程是程序的一次执行过程。若程序执行两次甚至多次,则需要两个甚至多个进程。二、进程是是正在运行程序的抽象。它代表运行的CPU,也称进程是对CPU的抽象。三、系统资源(如内存、文件)以进程为单位分配。四、操作系统为每个进程分配了独立的地址空间。五、操作系统通过“调度”把控制权交给进程。进程的弊端:一、进程切换的代价、开销比较大。二、在一个进程内也需要并行执行多个程序,实现不同的功能。三、进程有时候性能比较低。(线程的引入为了解决进程的弊端)。 4 | 5 | 线程:一、有标识符ID。二、有状态及状态转换,所以需要提供一些状态转换操作。三、不运行时需要保存上下文环境,所以需要程序计数器等寄存器。四、有自己的栈和栈指针。五、共享所在进程的地址空间和其它资源。 6 | 7 | 总结:一、进程是程序在某个数据集合上的一次运行活动;线程是进程中的一个执行路径。(进程可以创建多个线程)。二、在支持线程机制的系统中,进程是系统资源分配的单位,线程是CPU调度的单位。三、进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。四、进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。五、进程切换的开销较大。线程相对较小。(前面也提到过,引入线程也出于了开销的考虑)。 -------------------------------------------------------------------------------- /Java 基础/119.java对象的生命周期.md: -------------------------------------------------------------------------------- 1 | #### java对象的生命周期 2 | 3 | 1、创建阶段(Created):在创建阶段系统通过下面的几个步骤来完成对象的创建过程;一、为对象分配存储空间。二、开始构造对象。三、从超类到子类对static成员进行初始化。 四、超类成员变量按顺序初始化,递归调用超类的构造方法。五、子类成员变量按顺序初始化,子类构造方法调用。一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段。 4 | 5 | 2、应用阶段(In Use):对象至少被一个强引用持有。 6 | 7 | 3、不可见阶段(Invisible):当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。简单说就是程序的执行已经超出了该对象的作用域了。 8 | 9 | 4、不可达阶段(Unreachable):对象处于不可达阶段是指该对象不再被任何强引用所持有。与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GCroot会导致对象的内存泄露情况,无法被回收。 10 | 11 | 5、收集阶段(Collected):当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。这里要特别说明一下:不要重载finazlie()方法!原因有两点:一、会影响JVM的对象分配与回收速度 在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后才会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC。二、可能造成该对象的再次“复活”在finalize()方法中,如果有其它的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又重新变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用后续的代码管理。 12 | 13 | 6、终结阶段:当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。 14 | 15 | 7、对象空间重新分配阶段:垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。 -------------------------------------------------------------------------------- /Java 基础/12.谈谈 Java 中多线程实现的几种方式.md: -------------------------------------------------------------------------------- 1 | #### 谈谈 Java 中多线程实现的几种方式 2 | 3 | ##### 参考答案 4 | 5 | Java中多线程实现的方式主要有三种: 6 | 7 | 1. 继承Thread类 8 | 2. 实现Runnable接口 9 | 3. 使用ExecutorService、Callable、Future实现有返回结果的多线程 10 | 11 | 其中前两种方式线程执行完没有返回值,只有最后一种是带返回值的。 12 | 13 | ##### 继承Thread类实现多线程: 14 | 15 | 继承Thread类本质上也是实现Tunnable接口的一个实例,他代表一个线程的实例,并且启动线程的唯一方法是通过Thread类的start()方法,start()方法是一个native方法,他将启动一个新线程,并执行run( )方法。 16 | 17 | ##### 实现Runnable接口方式实现多线程: 18 | 19 | 实例化一个Thread对象,并传入实现的Runnable接口,当传入一个Runnable target参数给Thread后,Thraed的run()方法就会调用target.run( ); 20 | 21 | ##### 使用ExecutorService、Callable、Future实现有返回结果的多线程: 22 | 23 | 可返回值的任务必须实现Callable接口,类似的无返回值的任务必须实现Runnable接口,执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,在结合线程池接口ExecutorService就可以实现有返回结果的多线程。 24 | 25 | 26 | 27 | ##### 补充答案 28 | 29 | ###### From jiwenjie 30 | 31 | \#继承 Thread 类本身 32 | 33 | ```java 34 | public class Test { 35 | public static void main(String[] args) { 36 | MyThread mt = new MyThread(); 37 | mt.start(); 38 | } 39 | } 40 | 41 | class MyThread extends Thread { 42 | public void run() { 43 | System.out.println(Thread.currentThread().getName()); 44 | } 45 | } 46 | ``` 47 | 48 | \#实现Runnable接口 49 | *用法需要在外层包裹一层 Thread * 50 | 51 | ```java 52 | public class Test { 53 | public static void main(String[] args) { 54 | MyRunnable mr = new MyRunnable(); 55 | new Thread(mr).start(); 56 | } 57 | } 58 | 59 | class MyRunnable implements Runnable { 60 | public void run() { 61 | System.out.println(Thread.currentThread().getName()); 62 | } 63 | } 64 | ``` 65 | 66 | \#实现 Callable 接口 67 | *比较少见,不常用* 68 | *需要实现的是 call() 方法* 69 | 70 | 代码拷过来的,确实没用过 71 | 72 | ```java 73 | public class Test { 74 | public static void main(String[] args) throws Exception { 75 | ExecutorService es = Executors.newSingleThreadExecutor(); 76 | 77 | // 自动在一个新的线程上启动 MyCallable,执行 call 方法 78 | Future f = es.submit(new MyCallable()); 79 | 80 | // 当前 main 线程阻塞,直至 future 得到值 81 | System.out.println(f.get()); 82 | 83 | es.shutdown(); 84 | } 85 | } 86 | 87 | class MyCallable implements Callable { 88 | public Integer call() { 89 | System.out.println(Thread.currentThread().getName()); 90 | 91 | try { 92 | Thread.sleep(2000); 93 | } catch (InterruptedException e) { 94 | e.printStackTrace(); 95 | } 96 | 97 | return 123; 98 | } 99 | } 100 | ``` -------------------------------------------------------------------------------- /Java 基础/120.为什么等待和通知是在 Object 类而不是 Thread 中声明的.md: -------------------------------------------------------------------------------- 1 | #### 为什么等待和通知是在 Object 类而不是 Thread 中声明的 2 | 3 | 一个棘手的 Java 问题,如果 Java编程语言不是你设计的,你怎么能回答这个问题呢。Java编程的常识和深入了解有助于回答这种棘手的 Java 核心方面的面试问题。 4 | 5 | 为什么 wait,notify 和 notifyAll 是在 Object 类中定义的而不是在 Thread 类中定义 6 | 这是有名的 Java 面试问题,招2~4年经验的到高级 Java 开发人员面试都可能碰到。 7 | 8 | 这个问题的好在它能反映了面试者对等待通知机制的了解, 以及他对此主题的理解是否明确。就像为什么 Java 中不支持多继承或者为什么 String 在 Java 中是 final 的问题一样,这个问题也可能有多个答案。 9 | 10 | 为什么在 Object 类中定义 wait 和 notify 方法,每个人都能说出一些理由。从我的面试经验来看, wait 和 nofity 仍然是大多数Java 程序员最困惑的,特别是2到3年的开发人员,如果他们要求使用 wait 和 notify, 他们会很困惑。因此,如果你去参加 Java 面试,请确保对 wait 和 notify 机制有充分的了解,并且可以轻松地使用 wait 来编写代码,并通过生产者-消费者问题或实现阻塞队列等了解通知的机制。 11 | 12 | 为什么等待和通知需要从同步块或方法中调用, 以及 Java 中的 wait,sleep 和 yield 方法之间的差异,如果你还没有读过,你会觉得有趣。为何 wait,notify 和 notifyAll 属于 Object 类? 为什么它们不应该在 Thread 类中? 以下是我认为有意义的一些想法: 13 | 14 | 1. wait 和 notify 不仅仅是普通方法或同步工具,更重要的是它们是 Java 中两个线程之间的通信机制。对语言设计者而言, 如果不能通过 Java 关键字(例如 synchronized)实现通信此机制,同时又要确保这个机制对每个对象可用, 那么 Object 类则是的正确声明位置。记住同步和等待通知是两个不同的领域,不要把它们看成是相同的或相关的。同步是提供互斥并确保 Java 类的线程安全,而 wait 和 notify 是两个线程之间的通信机制。 15 | 2. 每个对象都可上锁,这是在 Object 类而不是 Thread 类中声明 wait 和 notify 的另一个原因。 16 | 3. 在 Java 中为了进入代码的临界区,线程需要锁定并等待锁定,他们不知道哪些线程持有锁,而只是知道锁被某个线程持有, 并且他们应该等待取得锁, 而不是去了解哪个线程在同步块内,并请求它们释放锁定。 17 | 4. Java 是基于 Hoare 的监视器的思想。在Java中,所有对象都有一个监视器。 18 | 线程在监视器上等待,为执行等待,我们需要2个参数: 19 | - 一个线程 20 | - 一个监视器(任何对象) 21 | 22 | 在 Java 设计中,线程不能被指定,它总是运行当前代码的线程。但是,我们可以指定监视器(这是我们称之为等待的对象)。这是一个很好的设计,因为如果我们可以让任何其他线程在所需的监视器上等待,这将导致“入侵”,导致在设计并发程序时会遇到困难。请记住,在 Java 中,所有在另一个线程的执行中侵入的操作都被弃用了(例如 stop 方法)。 -------------------------------------------------------------------------------- /Java 基础/122.Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用.md: -------------------------------------------------------------------------------- 1 | #### Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用 2 | 3 | Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。 4 | 5 | Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。 6 | - 一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理; 7 | - try用来指定一块预防所有异常的程序; 8 | - catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型; 9 | - throw语句用来明确地抛出一个异常; 10 | - throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟); 11 | - finally为确保一段代码不管发生什么异常状况都要被执行; 12 | - try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。 13 | -------------------------------------------------------------------------------- /Java 基础/124.什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes).md: -------------------------------------------------------------------------------- 1 | #### 什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes) 2 | 3 | 原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。 4 | 5 | 处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。 6 | 7 | 在Java中可以通过锁和循环CAS的方式来实现原子操作。CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作。 8 | 9 | 原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。 10 | 11 | int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。 12 | 13 | 为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的原子包装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。 14 | 15 | java.util.concurrent这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。 16 | 17 | 原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference 18 | 19 | 原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray 20 | 21 | 原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater 22 | 23 | 解决ABA问题的原子类:AtomicMarkableReference(通过引入一个boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个int来累加来反映中间有没有变过) -------------------------------------------------------------------------------- /Java 基础/125.为什么Java不支持运算符重载.md: -------------------------------------------------------------------------------- 1 | #### 为什么Java不支持运算符重载 2 | 3 | 为什么 C++ 支持运算符重载而 Java 不支持? 有人可能会说+运算符在 Java 中已被重载用于字符串连接。 4 | 与 C++ 不同,Java 不支持运算符重载。Java 不能为程序员提供自由的标准算术运算符重载,例如+, - ,*和/等。如果你以前用过 C++,那么 Java 与 C++ 相比少了很多功能,例如 Java 不支持多重继承,Java中没有指针,Java中没有引用传递。 5 | 6 | 为什么 Java 不支持运算符重载? 7 | 8 | **1)简单性和清晰性。**清晰性是Java设计者的目标之一。设计者不是只想复制语言,而是希望拥有一种清晰,真正面向对象的语言。添加运算符重载比没有它肯定会使设计更复杂,并且它可能导致更复杂的编译器, 或减慢 JVM,因为它需要做额外的工作来识别运算符的实际含义,并减少优化的机会, 以保证 Java 中运算符的行为。 9 | 10 | **2)避免编程错误。**Java 不允许用户定义的运算符重载,因为如果允许程序员进行运算符重载,将为同一运算符赋予多种含义,这将使任何开发人员的学习曲线变得陡峭,事情变得更加混乱。据观察,当语言支持运算符重载时,编程错误会增加,从而增加了开发和交付时间。由于 Java 和 JVM 已经承担了大多数开发人员的责任,如在通过提供垃圾收集器进行内存管理时,因为这个功能增加污染代码的机会, 成为编程错误之源, 因此没有多大意义。 11 | 12 | **3)JVM复杂性。**从JVM的角度来看,支持运算符重载使问题变得更加困难。通过更直观,更干净的方式使用方法重载也能实现同样的事情,因此不支持 Java 中的运算符重载是有意义的。与相对简单的 JVM 相比,复杂的 JVM 可能导致 JVM 更慢,并为保证在 Java 中运算符行为的确定性从而减少了优化代码的机会。 13 | 14 | **4)让开发工具处理更容易。**这是在 Java 中不支持运算符重载的另一个好处。省略运算符重载使语言更容易处理,这反过来又更容易开发处理语言的工具,例如 IDE 或重构工具。Java 中的重构工具远胜于 C++。 -------------------------------------------------------------------------------- /Java 基础/126.Java和C++的区别.md: -------------------------------------------------------------------------------- 1 | #### Java和C++的区别 2 | 3 | Java是纯粹的面向对象语言,所有的对象都继承自java.lang.Object, 4 | C++为了兼容C即支持面向对象也支持面向过程. 5 | 6 | Java通过虚拟机从而实现跨平台特性,但是C++依赖于特定的平台. 7 | 8 | Java没有指针,它的引用可以理解为安全指针,而C++具有和C一样的指针. 9 | 10 | Java支持自动垃圾回收,而C++需要手动回收. 11 | 12 | Java不支持多重继承,只能通过实现多个接口来达到相同目的,而C++支持多重继承. 13 | 14 | Java不支持操作符重载,虽然可以对两个String对象支持加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以. 15 | 16 | Java的goto是保留字,但是不可用,C++可以使用goto. 17 | 18 | Java不支持条件编译,C++通过#ifdef #ifndef等预处理命令从而实现条件编译. -------------------------------------------------------------------------------- /Java 基础/127.try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后.md: -------------------------------------------------------------------------------- 1 | #### try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后 2 | 3 | 会执行,在方法返回前执行。在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。 -------------------------------------------------------------------------------- /Java 基础/128.谈谈waitnotify关键字的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈wait/notify关键字的理解 2 | 3 | 概念:锁池和等待池:一、锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。二、等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中。 4 | 5 | notify和notifyAll的区别:一、如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。 6 | 7 | 二、当有线程调用了对象的notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。 8 | 9 | 三、优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。 -------------------------------------------------------------------------------- /Java 基础/129.为什么wait(), notify()和notifyAll ()必须在同步方法或者同步块中被调用?.md: -------------------------------------------------------------------------------- 1 | #### 为什么wait(), notify()和notifyAll ()必须在同步方法或者同步块中被调用? 2 | 3 | 当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。 4 | 5 | -------------------------------------------------------------------------------- /Java 基础/130.有五个布尔值,当全为true时或全为false时为true,其他为false,如何判断?当有n个时呢?.md: -------------------------------------------------------------------------------- 1 | #### 有五个布尔值,当全为true时或全为false时为true,其他为false,如何判断?当有n个时呢? 2 | 3 | 目前我的思路是通过for循环进行判断,大家有其他想法也可以提出来: 4 | 代码如下: 5 | 6 | ```java 7 | /** 8 | * @return true:全为true或全为false, 9 | * false:有一个不同时。 10 | */ 11 | public static boolean checkSameBoolean(List list) { 12 | int m = 0; 13 | for (int i = 0; i < list.size(); i++) { 14 | Boolean aBoolean = list.get(i); 15 | if (aBoolean) { 16 | m++; 17 | } else { 18 | m--; 19 | } 20 | } 21 | return Math.abs(m) == list.size(); 22 | } 23 | ``` 24 | 25 | 26 | 27 | 提供一种更方便的方法 28 | 29 | ```kotlin 30 | fun checkSameBoolean(values: MutableList): Boolean { 31 | require(values.isNotEmpty()) { "List can't be empty" } 32 | 33 | val first = values[0] 34 | 35 | values.filterNot { it == first }.forEach { _ -> return false } 36 | 37 | return true 38 | } 39 | ``` -------------------------------------------------------------------------------- /Java 基础/131.多线程和单线程的区别和联系.md: -------------------------------------------------------------------------------- 1 | #### 多线程和单线程的区别和联系 2 | 3 | 在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制。 4 | 多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。 5 | 结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间 -------------------------------------------------------------------------------- /Java 基础/132.Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别.md: -------------------------------------------------------------------------------- 1 | #### Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别? 2 | 3 | sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。 -------------------------------------------------------------------------------- /Java 基础/133.java 如何保证线程安全.md: -------------------------------------------------------------------------------- 1 | #### java 如何保证线程安全 2 | 3 | 什么是线程安全?:(当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。) 4 | 5 | 1、互斥同步:互斥同步是常见的一种并发正确性保障手段。 同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者是一些,使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区、互斥量、信号量都是主要的互斥实现方式。互斥是方法,同步是目的。[在Java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中synchronized明确制定了对象参数,那就是这个对象的reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。] 6 | 7 | 2、非阻塞同步:互斥同步最主要的问题就是进行线程阻塞和唤醒带来的性能问题,因此这种同步也成为阻塞同步。非阻塞同步是先进行操作,如果没有其他线程争用共享数据,那操作就成功;如果数据有争用,产生了冲突,那就采取其他的补偿措施。 8 | 9 | 3、自旋锁与自适应锁:共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。自旋等待不能代替阻塞,虽然避免了线程切换的开销,但是还要占用处理器时间的。如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源。自适应的自旋锁指的是由前一次在同一个锁上的自选时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进入它将允许自旋等待持续相对更长的时间。 10 | 11 | 4、锁消除:如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然无需进行。 12 | 13 | 5、锁粗化:如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部,例如上述代码,扩展第一个append()操作之前直至最后一个append()操作之后,这样只需加锁一次。 14 | 15 | 6、轻量级锁是JDK 1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就被称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。 HotSpot虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄,称为“MarkWord”。它是实现轻量级锁和偏向锁的关键。另一部分用于存储指向方法区对象类型数据的指针。 -------------------------------------------------------------------------------- /Java 基础/136.Java常用的序列化方式都有哪些.md: -------------------------------------------------------------------------------- 1 | #### Java常用的序列化方式都有哪些 2 | 3 | 1)JDK自带序列化 4 | 实现方式Serializable,JDK自带的序列化方式稳定性很高, 5 | 因为他将所有的内容都涵盖在序列化后的数组对象里, 6 | 但是因此也会导致一个问题就是占用了过多的空间去存储并不需要的那些东西. 7 | 2)JSON序列化 8 | 优点是可读性比较高,方便调试. 9 | 3)Hessian方式序列化 10 | 优点是可以跨编程语言,比Java原生的序列化和反序列化效率高. 11 | 4)protostuff序列化 12 | 基于Google protobuf. -------------------------------------------------------------------------------- /Java 基础/137.运行时异常与受检异常有何异同.md: -------------------------------------------------------------------------------- 1 | #### 运行时异常与受检异常有何异同 2 | 3 | 异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则: 4 | 5 | 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常) 6 | 对可以恢复的情况使用受检异常,对编程错误使用运行时异常 7 | 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生) 8 | 优先使用标准的异常 9 | 每个方法抛出的异常都要有文档 10 | 保持异常的原子性 11 | 不要在catch中忽略掉捕获到的异常 -------------------------------------------------------------------------------- /Java 基础/138.简单谈谈对线程池的理解.md: -------------------------------------------------------------------------------- 1 | #### 简单谈谈对线程池的理解 2 | 3 | 1. 线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。使用线程池的目的:一、减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。二、可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 4 | 2. 几个重要的类: 5 | 6 | | Class | Expalin | 7 | | --------------------------- | ------------------------------------------------------------ | 8 | | ExecutorService | 真正的线程池接口。 | 9 | | ScheduledExecutorService | 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 | 10 | | ThreadPoolExecutor | ExecutorService的默认实现。 | 11 | | ScheduledThreadPoolExecutor | 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 | 12 | 13 | - Tip:Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。 14 | 15 | 1. 使用:一、newSingleThreadExecutor[创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。];二、newFixedThreadPool[创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。];三、newCachedThreadPool[创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。];四、newScheduledThreadPool[创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求]。 16 | 2. 线程池几种工作队列:一、ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。二、LinkedBlockingQueue一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列三、SynchronousQueue一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。四、PriorityBlockingQueue一个具有优先级的无限阻塞队列。 17 | 18 | 几个重要参数说明; 19 | 20 | - corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。 21 | - maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。 22 | - keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。 23 | - workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:[ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue]ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。线程池的排队策略与BlockingQueue有关。 24 | - threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等。 25 | 26 | 有界与无界队列: 27 | 28 | - 有界队列:一、初始的poolSizeBoolean 8 | 9 | Int ——–>Integer 10 | 11 | 装箱和拆箱 12 | 13 | 装箱:把基本的数据类型转换成对应的包装类型. 14 | 15 | Integer .valueOf(1) 16 | 17 | Integer i = 1;自动装箱,实际上在编译时会调用Integer .valueOf方法来装箱 18 | 19 | 拆箱:就是把包装类型转换为基本数据类型.基本数据类型 名称 = 对应的包装类型。 20 | 21 | Integer i = 1; 22 | 23 | int j = i;//自动拆箱//int j = i=intValue();手动拆箱 24 | 25 | 自动拆箱:实际上会在编译调用intValue 26 | 27 | Java是一个面向对象的语言,而基本的数据类型,不具备面向对象的特性。 28 | 29 | null Integer—>null int—->0 用Integer和int分别表示Person这个类的ID 30 | 31 | Max 最大值 32 | 33 | min 最小值 34 | 35 | 缓存值:对象缓存,Integer i=1; integer j= 1;i ==j -------------------------------------------------------------------------------- /Java 基础/157.Error和Exception有什么区别.md: -------------------------------------------------------------------------------- 1 | #### Error和Exception有什么区别 2 | 3 | Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。 -------------------------------------------------------------------------------- /Java 基础/16.简述HashMap工作原理.md: -------------------------------------------------------------------------------- 1 | #### 简述HashMap工作原理 2 | 3 | ##### 参考答案 4 | 5 | HashMap是基于hashing算法的原理,通过put(key,value)和get(key)方法储存和获取值的。 6 | 7 | 1. 存:我们将键值对K/V 传递给put()方法,它调用K对象的hashCode()方法来计算hashCode从而得到bucket位置,之后储存Entry对象。(HashMap是在bucket中储存 键对象 和 值对象,作为Map.Entry) 8 | 2. 取:获取对象时,我们传递 键给get()方法,然后调用K的hashCode()方法从而得到hashCode进而获取到bucket位置,再调用K的equals()方法从而确定键值对,返回值对象。 9 | 3. 碰撞:当两个对象的hashcode相同时,它们的bucket位置相同,‘碰撞’就会发生。如何解决,就是利用链表结构进行存储,即HashMap使用LinkedList存储对象。但是当链表长度大于8(默认)时,就会把链表转换为红黑树,在红黑树中执行插入获取操作。 10 | 4. 扩容:如果HashMap的大小超过了负载因子定义的容量,就会进行扩容。默认负载因子为0.75。就是说,当一个map填满了75%的bucket时候,将会创建原来HashMap大小的两倍的bucket数组(jdk1.6,但不超过最大容量),来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。 11 | 12 | ![HashMap]() 13 | 14 | ##### 补充答案 15 | 16 | ###### From [midNightHz](https://github.com/midNightHz) 17 | 18 | 从前有个叫妈个蛋村,村里有个地主,有钱又有颜,有能力可以娶很多妻妾,但是怎么样才能快速找到想要的妻妾呢,于是这个地主想了一个办法; 19 | 1、先建16个房子(标上0-15),为什么是16个呢,算命的说的 20 | 2、每娶到一个妻子(put),就根据妻子的生日,每年的第几天(hash值)算出要让这个妻子住在哪个房间,具体算法是这样的 hash%16 等于0就住0号房; 21 | 3、问题来了,这个有钱富豪娶了两个妻子,两个生日不同,但是要住同一个房间怎么办(hash碰撞)这个有钱的地主想了一个办法,让这些住同一个房间的妻子根据生日排个大小(二叉树) 22 | 4、过来一段时间以后,这位有钱的地主娶了12房姨太太,他想着房子快不够住了,怎么办,又建了16个房子(hashmap扩容),然后重新安排他们的住所 23 | 24 | 25 | 26 | ###### From [吉文杰](https://github.com/jiwenjie) [jiwenjie](https://github.com/jiwenjie) 27 | 28 | HashMap的工作原理 29 | 30 | 1. 什么时候会使用HashMap?他有什么特点? 31 | 是基于Map接口的实现,存储键值对时,它可以接收null的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。 32 | 2. 你知道HashMap的工作原理吗? 33 | 通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。 34 | 3. 你知道get和put的原理吗?equals()和hashCode()的都有什么作用? 35 | 通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点 36 | 4. 你知道hash的实现吗?为什么要这样实现? 37 | 在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。 38 | 5. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办? 39 | 如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。 40 | 41 | 关于Java集合的小抄中是这样描述的: 42 | 43 | > 44 | > 以Entry[]数组实现的哈希桶数组,用Key的哈希值取模桶数组的大小可得到数组下标。 45 | > 46 | > 插入元素时,如果两条Key落在同一个桶(比如哈希值1和17取模16后都属于第一个哈希桶),我们称之为哈希冲突。 47 | > 48 | > JDK的做法是链表法,Entry用一个next属性实现多个Entry以单向链表存放。查找哈希值为17的key时,先定位到哈希桶,然后链表遍历桶里所有元素,逐个比较其Hash值然后key值。 49 | > 50 | > 在JDK8里,新增默认为8的阈值,当一个桶里的Entry超过閥值,就不以单向链表而以红黑树来存放以加快Key的查找速度。 51 | > 52 | > 当然,最好还是桶里只有一个元素,不用去比较。所以默认当Entry数量达到桶数量的75%时,哈希冲突已比较严重,就会成倍扩容桶数组,并重新分配所有原来的Entry。扩容成本不低,所以也最好有个预估值。 53 | > 54 | > 取模用与操作(hash & (arrayLength-1))会比较快,所以数组的大小永远是2的N次方, 你随便给一个初始值比如17会转为32。默认第一次放入元素时的初始值是16。 55 | > 56 | > iterator()时顺着哈希桶数组来遍历,所以看起来是个乱序 57 | > 58 | 59 | -------------------------------------------------------------------------------- /Java 基础/160.Retrofit中使用jdk动态代理实现,那jdk中动态代理的实现原理是.md: -------------------------------------------------------------------------------- 1 | #### Retrofit中使用jdk动态代理实现,那jdk中动态代理的实现原理是 2 | 3 | jdk动态代理只能代理接口,类似于编写一个接口实现类,其构造方法接收InvocationHandler参数,InvocationHandler相当于回调接口,在这个类中的接口方法实现中执行InvocationHandler的invoke方法回调出去。 4 | 与直接编写代码不同的是,这个接口的实现类是由jvm在运行期间动态生成的。在加载一个类时,类的数据是由读取class文件到内存中而来,动态代理直接就在内存中生成一份class的数据。 -------------------------------------------------------------------------------- /Java 基础/165.Java集合类框架的最佳实践有哪些?.md: -------------------------------------------------------------------------------- 1 | #### Java集合类框架的最佳实践有哪些? 2 | 3 | 根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。 4 | 5 | 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。 6 | 7 | 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。 8 | 9 | 使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。 10 | 11 | 编程的时候接口优于实现。 12 | 13 | 底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。 -------------------------------------------------------------------------------- /Java 基础/166.如何控制多线程执行顺序?.md: -------------------------------------------------------------------------------- 1 | #### 如何控制多线程执行顺序? 2 | 3 | 方法一:Join()使用。 4 | 原理:让主线程等待子线程运行结束后才能继续运行。 5 | 方法二:ExecutorService ()的使用。 6 | 原理:利用并发包里的Excutors的newSingleThreadExecutor产生一个单线程的线程池,而这个线程池的底层原理就是一个先进先出(FIFO)的队列。 7 | 代码中executor.submit依次添加了123线程,按照FIFO的特性,执行顺序也就是123的执行结果,从而保证了执行顺序。 8 | 9 | 示例代码: 10 | 11 | ```java 12 | 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | 16 | public class Test { 17 | public static void main(String[] args) throws InterruptedException { 18 | // thread1.start(); 19 | // thread1.join(); 20 | // 21 | // thread2.start(); 22 | // thread2.join(); 23 | // 24 | // thread3.start(); 25 | 26 | // executorService.submit(thread1); 27 | // executorService.submit(thread2); 28 | // executorService.submit(thread3); 29 | // 30 | // executorService.shutdown(); 31 | } 32 | 33 | static Thread thread1 = new Thread(new Runnable() { 34 | 35 | @Override 36 | public void run() { 37 | System.out.println("thread1"); 38 | } 39 | 40 | }); 41 | 42 | 43 | static Thread thread2 = new Thread(new Runnable() { 44 | 45 | @Override 46 | public void run() { 47 | System.out.println("thread2"); 48 | } 49 | 50 | }); 51 | 52 | 53 | static Thread thread3 = new Thread(new Runnable() { 54 | 55 | @Override 56 | public void run() { 57 | System.out.println("thread3"); 58 | } 59 | 60 | }); 61 | 62 | 63 | static ExecutorService executorService = Executors.newSingleThreadExecutor(); 64 | } 65 | ``` -------------------------------------------------------------------------------- /Java 基础/17.谈谈 ArrayList 和 LinkList 的区别.md: -------------------------------------------------------------------------------- 1 | #### 谈谈 ArrayList 和 LinkList 的区别 2 | 3 | ##### 参考答案 4 | 5 | 1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 6 | 2. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 7 | 3. 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 8 | 9 | 两个都是多线程不安全。 10 | 11 | 12 | 13 | ##### 补充答案 14 | 15 | ###### From [midNightHz](https://github.com/midNightHz) 16 | 17 | 1、实现原理不一样 ArrayList是基于数据的实现,而LinkedList是基于链表的实现,两者的区别就是这两种数据结构的差别 18 | 2、初始化:ArrayList初始化时会初始化一个一定容量的数组,而linkedlist只是定义了链表的头元素和尾元素 19 | 3、添加元素 在list尾端添加元素区别并不是太大,但ArrayList是基于Array来实现的,会遇到一个很蛋疼大问题就是扩容问题 20 | 4、遍历 :遍历arraylist和linkedlist区别并不是很大 21 | 5、查询指定位置的元素 arraylist的查询速度为0(1),而linkedlist则双端的元素查询快,中间的元素查询慢 22 | 6、删除元素 数组删除元素是一个很蛋疼的问题,特别是移除数组头端的元素 ,需要一定当前下标元素后面的所有元素,而linkedlist删除双端的元素则非常快 删除中间的元素会比较慢(要遍历查到为指定位置的元素) 23 | 24 | 25 | 26 | ###### From [吉文杰](https://github.com/jiwenjie) [jiwenjie](https://github.com/jiwenjie) 27 | 28 | 1. 因为 Array 是基于索引 (index) 的数据结构,它使用索引在数组中搜索和读取数据是很快的。Array 获取数据的时间复杂度是 O(1), 但是要删除数据却是开销很大的,因为这需要重排数组中的所有数据。 29 | 2. 相对于 ArrayList , LinkedList 插入是更快的。因为LinkedList不像ArrayList 一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是 ArrayList 最坏的一种情况,时间复杂度是 O(n) ,而 LinkedList 中插入或删除的时间复杂度仅为 O(1) 。ArrayList 在插入数据时还需要更新索引(除了插入数组的尾部)。 30 | 3. 类似于插入数据,删除数据时,LinkedList 也优于 ArrayList 。 31 | 4. LinkedList 需要更多的内存,因为 ArrayList 的每个索引的位置是实际的数据,而 LinkedList 中的每个节点中存储的是实际的数据和前后节点的位置( 一个LinkedList实例存储了两个值Node first 和 Node last 分别表示链表的其实节点和尾节点,每个 Node 实例存储了三个值:E item,Node next,Node pre)。 32 | 33 | 什么场景下更适宜使用LinkedList,而不用ArrayList 34 | 35 | 1. 你的应用不会随机访问数据 。因为如果你需要LinkedList中的第n个元素的时候,你需要从第一个元素顺序数到第n个数据,然后读取数据。 36 | 2. 你的应用更多的插入和删除元素,更少的读取数据。因为插入和删除元素不涉及重排数据,所以它要比ArrayList要快。 37 | 3. 以上就是关于ArrayList和LinkedList的差别。你需要一个不同步的基于索引的数据访问时,请尽量使用ArrayList。ArrayList很快,也很容易使用。但是要记得要给定一个合适的初始大小,尽可能的减少更改数组的大小。 -------------------------------------------------------------------------------- /Java 基础/171.为什么Android不允许在子线程操作UI.md: -------------------------------------------------------------------------------- 1 | #### 为什么Android不允许在子线程操作UI 2 | 3 | 如果多线程环境下,为了保障UI控件的线程安全,需要给UI的访问上锁,但是上锁会让UI控件变得复杂和低效. -------------------------------------------------------------------------------- /Java 基础/175.单例的DCL方式下,那个单例的私有变量要不要加volatile关键字.md: -------------------------------------------------------------------------------- 1 | #### 单例的DCL方式下,那个单例的私有变量要不要加volatile关键字 2 | 3 | 需要,new某个对象,可以分解为: 4 | 5 | 1 memory=allocate();// 分配内存 相当于c的malloc 2 ctorInstanc(memory) //初始化对象 3 instance=memory //设置instance指向刚分配的地址 6 | 7 | 在编译器运行时,可能会出现重排序 从1-2-3 排序为1-3-2 8 | 9 | 如此在多线程下就会出现问题例如现在有2个线程A,B 线程A在执行第5行代码时,B线程进来,而此时A执行了 1和3,没有执行2, 此时B线程判断instance不为null 直接返回一个未初始化的对象,就会出现问题 而用了volatile,上面的重排序就会在多线程环境中禁止,不会出现上述问题。 -------------------------------------------------------------------------------- /Java 基础/177.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候, 他们的执行顺序.md: -------------------------------------------------------------------------------- 1 | #### 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候, 他们的执行顺序 2 | 3 | 此题考察的是类加载器实例化时进行的操作步骤(加载–>连接->初始化)。 4 | 父类静态代变量、 5 | 父类静态代码块、 6 | 子类静态变量、 7 | 子类静态代码块、 8 | 父类非静态变量(父类实例成员变量)、 9 | 父类构造函数、 10 | 子类非静态变量(子类实例成员变量)、 11 | 子类构造函数。 12 | 示例代码: 13 | ```java 14 | public class ClassLoaderTest { 15 | public static void main(String[] args) { 16 | son sons=new son(); 17 | } 18 | } 19 | 20 | class parent{ 21 | private static int a=1; 22 | private static int b; 23 | private int c=initc(); 24 | static { 25 | b=1; 26 | System.out.println("1.父类静态代码块:赋值b成功"); 27 | System.out.println("1.父类静态代码块:a的值"+a); 28 | } 29 | 30 | int initc(){ 31 | System.out.println("3.父类成员变量赋值:---> c的值"+c); 32 | this.c=12; 33 | System.out.println("3.父类成员变量赋值:---> c的值"+c); 34 | return c; 35 | } 36 | 37 | public parent(){ 38 | System.out.println("4.父类构造方式开始执行---> a:"+a+",b:"+b); 39 | System.out.println("4.父类构造方式开始执行---> c:"+c); 40 | } 41 | } 42 | 43 | class son extends parent{ 44 | private static int sa=1; 45 | private static int sb; 46 | private int sc=initc2(); 47 | static { 48 | sb=1; 49 | System.out.println("2.子类静态代码块:赋值sb成功"); 50 | System.out.println("2.子类静态代码块:sa的值"+sa); 51 | } 52 | 53 | int initc2(){ 54 | System.out.println("5.子类成员变量赋值--->:sc的值"+sc); 55 | this.sc=12; 56 | return sc; 57 | } 58 | 59 | public son(){ 60 | System.out.println("6.子类构造方式开始执行---> sa:"+sa+",sb:"+sb); 61 | System.out.println("6.子类构造方式开始执行---> sc:"+sc); 62 | } 63 | } 64 | ``` 65 | 执行结果: 66 | 1.父类静态代码块:赋值b成功 67 | 1.父类静态代码块:a的值1 68 | 2.子类静态代码块:赋值sb成功 69 | 2.子类静态代码块:sa的值1 70 | 3.父类成员变量赋值:---> c的值0 71 | 3.父类成员变量赋值:---> c的值12 72 | 4.父类构造方式开始执行---> a:1,b:1 73 | 4.父类构造方式开始执行---> c:12 74 | 5.子类成员变量赋值--->:sc的值0 75 | 6.子类构造方式开始执行---> sa:1,sb:1 76 | 6.子类构造方式开始执行---> sc:12 -------------------------------------------------------------------------------- /Java 基础/182.导致线程死锁的原因?怎么解除线程死锁?.md: -------------------------------------------------------------------------------- 1 | #### 导致线程死锁的原因?怎么解除线程死锁? 2 | 3 | 死锁问题是多线程特有的问题,它可以被认为是线程间切换消耗系统性能的一种极端情况。在死锁时,线程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是系统任务永远无法执行完成。死锁问题是在多线程开发中应该坚决避免和杜绝的问题。 4 | 5 | 一般来说,要出现死锁问题需要满足以下条件: 6 | 互斥条件:一个资源每次只能被一个线程使用。 7 | 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 8 | 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。 9 | 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 10 | 11 | 只要破坏死锁 4 个必要条件之一中的任何一个,死锁问题就能被解决。 -------------------------------------------------------------------------------- /Java 基础/185.如何停止一个正在运行的线程.md: -------------------------------------------------------------------------------- 1 | #### 如何停止一个正在运行的线程 2 | 3 | 使用共享变量的方式 在这种方式中,之所以引入共享变量, 4 | 是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。 5 | 使用interrupt方法终止线程 6 | 如果一个线程由于等待某些事件的发生而被阻塞, 7 | 比如当一个线程调用Thread.join()方法,或者Thread.sleep()方法, 8 | 在网络中调用ServerSocket.accept()方法时,都有可能导致线程阻塞,即使主程序中将该线程的共享变量设置为true,但该线程此时根本无法检查循环标志,当然也就无法立即中断。 9 | 使用Thread提供的interrupt()方法,该方法不会中断一个正在运行的线程,但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码 -------------------------------------------------------------------------------- /Java 基础/186.你有哪些多线程开发良好的实践.md: -------------------------------------------------------------------------------- 1 | #### 你有哪些多线程开发良好的实践? 2 | 3 | 给线程起个有意义的名字,这样可以方便找 Bug。 4 | 5 | 缩小同步范围,从而减少锁争用。例如对于 synchronized,应该尽量使用同步块而不是同步方法。 6 | 7 | 多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。 8 | 9 | 使用 BlockingQueue 实现生产者消费者问题。 10 | 11 | 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。 12 | 13 | 使用本地变量和不可变类来保证线程安全。 14 | 15 | 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。 -------------------------------------------------------------------------------- /Java 基础/187.反射的原理,反射创建类实例的三种方式是什么.md: -------------------------------------------------------------------------------- 1 | #### 反射的原理,反射创建类实例的三种方式是什么 2 | 3 | 反射机制: 4 | 在通常情况下,如果有一个类,可以通过类创建对象;但是反射就是要求通过一个对象找到一个类的名称; 5 | 6 | 创建Class对象的三种方式: 7 | (1)对象.getClass() 8 | ```java 9 | Class class1 = p1.getClass(); 10 | System. out.println(p1.getClass().getName()); 11 | Class class2 = p2.getClass(); 12 | System. out.println(class1 == class2 ); 13 | ``` 14 | 15 | (2)类.class:需要输入一个明确的类 16 | ```java 17 | Class class3 = Person.class; 18 | System. out.println(class1 == class2); 19 | ``` 20 | 21 | (3)forName():传入时只需要以字符串的方式传入即可 22 | ```java 23 | Class class4 = null; 24 | try { 25 | class4 = Class. forName("cn.itcast.Person"); 26 | } catch (ClassNotFoundException e) { 27 | // TODO Auto-generated catch block 28 | e.printStackTrace(); 29 | } 30 | ``` -------------------------------------------------------------------------------- /Java 基础/191.并发集合和普通集合如何区别.md: -------------------------------------------------------------------------------- 1 | #### 并发集合和普通集合如何区别 2 | 3 | 在Java中,有普通集合、同步的集合(即线程安全的集合)、并发集合。 4 | 5 | 并发集合常见的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。并发集合位于java.util.concurrent包下,是在JDK1.5之后才有的。 6 | 7 | 普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性; 8 | 线程安全集合仅仅是给集合添加了synchronized(同步的)同步锁,严重影响了性能,而且对并发的效率就更低了; 9 | 并发集合通过复杂的策略不仅保证了多线程的安全,又提高了并发时的效率。 -------------------------------------------------------------------------------- /Java 基础/192.你知道有多少种方式实现单例模式.md: -------------------------------------------------------------------------------- 1 | #### 你知道有多少种方式实现单例模式 2 | 3 | 单例模式是应用最广的模式之一,也是最简单的模式,但越是简单的东西,就越容易忽略它的细节,在应用这个模式时,同一个进程内,单例对象的类必须保证只有一个实例存在,比如在一个应用中,应该只有一个ImagerLoader实例,因为这个ImagerLoader中含有线程池、缓存系统、网路请求等,创建一次需要消耗很多资源,因此,没有理由让它构造多个实例,这种不能自由的构造对象,确保某一个类有且只有一个对象实例的情况,就是单例模式的使用场景,那么你知道有多少种方式实现单例?具我所了解的,有六种,下面分别介绍。 4 | 1、饿汉方式 5 | 2、静态内部类形式 6 | 3、懒汉模式(线程安全) 7 | 4、Double Check Lock(DCL) 8 | 5、枚举模式 9 | 6、使用容器实现 10 | 11 | 参考: 12 | https://blog.csdn.net/Rain_9155/article/details/103318029 -------------------------------------------------------------------------------- /Java 基础/25.谈谈对运行结果的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对运行结果的理解 2 | 3 | ```java 4 | public static void main(String[] args){ 5 | int i = 0; 6 | System.out.println((i++)+(++i)+(i++)+(++i)); 7 | System.out.println(i); 8 | } 9 | ``` 10 | 11 | 12 | 13 | ##### 参考答案 14 | 15 | 主要是对前++、后++、赋值的理解 16 | int a = i++; // a= 0, i = 1 17 | int b = ++i; // b = 2, i = 2 18 | int c = i++; // c = 2, i = 3 19 | int d = ++i; // d= 4, i = 4 20 | a + b + c + d; // 0 + 2 + 2 + 4 = 8 21 | 22 | 23 | public static void main(String[] args) { 24 | int i = 0; 25 | System.out.println((i++)+(++i)+(i++)+(++i)); 26 | System.out.println(i); 27 | } 28 | //结果 8 29 | //结果 4 30 | 31 | public static void main(String[] args) { 32 | int i = 0; 33 | System.out.println("i++ = "+ i++ +"|| ++i = "+ ++i +"|| i++ = "+ i++ +"|| ++i = "+ ++i); 34 | System.out.println(i); 35 | } 36 | //结果 i++ = 0|| ++i = 2|| i++ = 2|| ++i = 4 37 | //结果 4 38 | -------------------------------------------------------------------------------- /Java 基础/28.谈谈 JDK8 开始的双冒号 用法及详解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈 JDK8 开始的双冒号 :: 用法及详解 2 | 3 | ##### 参考答案 4 | 5 | #### 概念 6 | 7 | 类名::方法名,相当于对这个方法闭包的引用,类似 js 中的一个 function。比如: 8 | 9 | ```java 10 | Function func = String::toUpperCase; 11 | ``` 12 | 13 | Function 在 java.util.function 包下,也是 jdk8 新加入的类,同级目录下有很多函数式编程模型接口,比如 Consumer/Predicate/Operator 等,func 相当于一个入参和出参都为 String 的函数,可以直接如下: 14 | 15 | ```java 16 | func.apply("abc") 17 | ``` 18 | 19 | 接收一个参数,返回一个结果("ABC")。也可以用于代替下面的 Lambda 表达式: 20 | 21 | ```java 22 | List l = Arrays.asList("a","b","c"); 23 | l.stream().map(s -> s.toUpperCase()); 24 | l.stream().map(func); 25 | ``` 26 | 27 | 下面自定义一个函数式接口: 28 | 29 | ```java 30 | public class MyConsumer implements Consumer { 31 | @Override 32 | public void accept(String s) { 33 | System.out.println(s); 34 | } 35 | } 36 | ``` 37 | 38 | 下面这俩种写法等价: 39 | 40 | ```java 41 | List l = Arrays.asList("a","b","c"); 42 | 43 | l.forEach(new MyConsumer<>()); 44 | l.forEach(s -> System.out.println(s)); 45 | ``` 46 | 47 | 但是,这种写法却不行,编译失败: 48 | 49 | ```java 50 | l.forEach(MyConsumer::accept); 51 | ``` 52 | 53 | 因为 MyConsumer 的 accept 方法不是静态的,如果想使用这个方法,需要一个实例,还需要一个入参,共俩个参数。而 List.forEach 中需要的是 consumer 类型,相当于 s -> {...},只有一个参数。 54 | 55 | ##### 下面详细分析双冒号使用的各种情况 56 | 57 | 新建一个类,里面声明四个代表各种情况的方法: 58 | 59 | ```java 60 | public class DoubleColon { 61 | 62 | public static void printStr(String str) { 63 | System.out.println("printStr : " + str); 64 | } 65 | 66 | public void toUpper(){ 67 | System.out.println("toUpper : " + this.toString()); 68 | } 69 | 70 | public void toLower(String str){ 71 | System.out.println("toLower : " + str); 72 | } 73 | 74 | public int toInt(String str){ 75 | System.out.println("toInt : " + str); 76 | return 1; 77 | } 78 | } 79 | ``` 80 | 81 | 把它们用::提取为函数,再使用: 82 | 83 | ```java 84 | Consumer printStrConsumer = DoubleColon::printStr; 85 | printStrConsumer.accept("printStrConsumer"); 86 | 87 | Consumer toUpperConsumer = DoubleColon::toUpper; 88 | toUpperConsumer.accept(new DoubleColon()); 89 | 90 | BiConsumer toLowerConsumer = DoubleColon::toLower; 91 | toLowerConsumer.accept(new DoubleColon(),"toLowerConsumer"); 92 | 93 | BiFunction toIntFunction = DoubleColon::toInt; 94 | int i = toIntFunction.apply(new DoubleColon(),"toInt"); 95 | ``` 96 | 97 | 非静态方法的第一个参数为被调用的对象,后面是入参。静态方法因为 jvm 已有对象,直接接收入参。再写一个方法使用提取出来的函数: 98 | 99 | ```java 100 | public class TestBiConsumer { 101 | public void test(BiConsumer consumer){ 102 | System.out.println("do something ..."); 103 | } 104 | } 105 | ``` 106 | 107 | 下面这俩种传入的函数是一样的: 108 | 109 | ```java 110 | TestBiConsumer obj = new TestBiConsumer(); 111 | obj.test((x,y) -> System.out.println("do something ...")); 112 | obj.test(DoubleColon::toLower); 113 | ``` 114 | 115 | #### 总结 116 | 117 | 用::提取的函数,最主要的区别在于静态与非静态方法,非静态方法比静态方法多一个参数,就是被调用的实例。 -------------------------------------------------------------------------------- /Java 基础/30.谈谈 static 关键字的用法.md: -------------------------------------------------------------------------------- 1 | #### 谈谈 static 关键字的用法 2 | 3 | ##### 参考答案 4 | 5 | static 修饰的方法/变量等资源是静态资源 在内存中存放在方法区,所有允许访问的对象都可以访问(参考变量的修饰符 public protected等) 6 | 7 | static修饰的变量只存在一份,所有可以访问的对象都允许进行修改 8 | 9 | static 修饰的变量/方法在内存中被root引用,因此不会被GC回收, 10 | 11 | static修饰的变量在类被加载的时候就会被加载 12 | 13 | 被static修饰的方法/代码块只能引用被static修饰的方法/变量 14 | 15 | static的主要用法 16 | 1. 用来修饰变量 可以不需要实例化对象就可以直接引用变量,引用方法ClassName.field; 17 | 2. 修饰方法 可以不需要实例化对象就可以直接引用方法,引用方法 ClassName.method(); 18 | 3. 静态块 用来实现需要在类加载时就需要加载的逻辑 19 | -------------------------------------------------------------------------------- /Java 基础/35.谈谈数组与链表的区别.md: -------------------------------------------------------------------------------- 1 | #### 谈谈数组与链表的区别 2 | 3 | ##### 参考答案 4 | 5 | 1. 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。 6 | 2. 链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。 7 | 8 | 9 | 10 | ##### 蛋友补充 11 | 12 | ###### From [Taonce](https://github.com/Taonce) 13 | 14 | 1. 数组是顺序的存储结构,链表是链式的存储结构。 15 | 2. 数组的查找效率高,时间复杂度为O(1),插入和删除效率低,时间复杂度为O(n);链表插入和删除效率高,时间复杂度为O(1),查找效率低,时间复杂度为O(n)。 16 | 3. 数组在内存中是一块连续的区域;而链表是分散的,它是通过指针指向下一个元素。 17 | 4. 数组的空间大小是固定的,不能动态扩展;链表空间大小不固定,可动态扩展。 -------------------------------------------------------------------------------- /Java 基础/39.守护线程与阻塞线程的四种情况.md: -------------------------------------------------------------------------------- 1 | #### 守护线程与阻塞线程的四种情况 2 | 3 | ##### 参考答案 4 | 5 | 守护线程 6 | Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 7 | 8 | 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,VM就不会退出。 9 | 10 | 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。 11 | 12 | 虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。、 13 | 14 | 另外有几点需要注意: 15 | 16 | 1、setDaemon(true)必须在调用线程的start()方法之前设置,否则会跑出IllegalThreadStateException异常。 17 | 18 | 2、在守护线程中产生的新线程也是守护线程。 19 | 3、 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。 20 | 21 | 线程阻塞 22 | 线程可以阻塞于四种状态: 23 | 24 | 1、当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断; 25 | 26 | 2、当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为止(若制定了超时值的话) 27 | 28 | 3、线程阻塞与不同I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间; 29 | 30 | 4、线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得synchronized语句必须的锁时阻塞)。 31 | 32 | 注意,并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应 33 | 34 | -------------------------------------------------------------------------------- /Java 基础/47.final, finally, finalize的区别.md: -------------------------------------------------------------------------------- 1 | #### final, finally, finalize的区别 2 | 3 | ##### 参考答案 4 | 5 | final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。 内部类要访问局部变量,局部变量必须定义成final类型. 6 | 7 | finally是异常处理语句结构的一部分,表示总是执行。 8 | 9 | finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。 JVM不保证此方法总被调用 -------------------------------------------------------------------------------- /Java 基础/52.break与continue的区别.md: -------------------------------------------------------------------------------- 1 | #### break与continue的区别 2 | 3 | ##### 参考答案 4 | 5 | break语句 (强行结束循环)作用: 6 | 7 | 1. 可以用来从循环体内跳出循环体,即提前结束循环,接着执行循环下面的语句。 8 | 2. 使流程跳出switch结构 9 | 10 | 注意:break语句不能用于循环语句和switch语句之外的任何其他语句中 11 | 12 | continue语句作用: 13 | 14 | - 结束本次循环,即忽略循环体中continue语句下面尚未执行的语句,接着进行下一次是否执行循环的判定。 15 | 16 | 注意:continue语句不能用于循环语句之外的任何其他语句中 17 | 18 | continue语句和break语句的区别: 19 | 20 | - continue语句只结束本次循环,而不是终止整个循环的执行。 21 | - break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。break语句可以用在循环语句和switch语句中。在循环语句中用来结束内部循环;在switch语句中用来跳出switch语句。 22 | 23 | 注意:循环嵌套时,break和continue只影响包含它们的最内层循环,与外层循环无关。 -------------------------------------------------------------------------------- /Java 基础/53.谈谈你对Java中Hash码的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈你对Java中Hash码的理解 2 | 3 | ##### 参考答案 4 | 5 | 在Java中,哈希码代表了对象的一种特征,例如我们判断某两个字符串是否==,如果其哈希码相等,则这两个字符串是相等的,其次,哈希码是一种数据结构的算法,常见的哈希码的算法有: 6 | 7 | Object类的HashCode,返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。 8 | 9 | String类的HashCode,根据String类包含的字符串的内容,根据一种特殊的算法返回哈希码,只要字符串的内容相同,返回的哈希码也相同。 10 | 11 | Integer类:返回的哈希码就是integer对象里所包含的那个整数的数值。 12 | 例如:Integer i1=new Integer(100) i1.hashCode的值就是100,由此可见两个一样大小的Integer对象返回的哈希码也一样。 -------------------------------------------------------------------------------- /Java 基础/54.简述一下类加载过程.md: -------------------------------------------------------------------------------- 1 | #### 简述一下类加载过程 2 | 3 | ##### 加载 4 | 加载,是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.Class对象的过程。这个过程,将类的.class文件中的二进制数据读入内存,放在运行时区域的方法区内。然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。 5 | 6 | 类加载阶段: 7 | (1)Java虚拟机将.class文件读入内存,并为之创建一个Class对象。 8 | 9 | (2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。 10 | 11 | (3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。 12 | 13 | ##### 链接 14 | 链接包括验证、准备以及解析三个阶段。 15 | (1)验证阶段。主要的目的是确保被加载的类(.class文件的字节流)满足Java虚拟机规范,不会造成安全错误。 16 | 17 | (2)准备阶段。负责为类的静态成员分配内存,并设置默认初始值。 18 | 19 | (3)解析阶段。将类的二进制数据中的符号引用替换为直接引用。 20 | 21 | 说明: 22 | 符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。 23 | 24 | 直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量。 25 | 26 | 举个例子来说,现在调用方法hello(),这个方法的地址是0xaabbccdd,那么hello就是符号引用,0xaabbccdd就是直接引用。 27 | 在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。 28 | 29 | ##### 初始化 30 | 初始化,则是为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。 31 | 32 | 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。 33 | 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。 -------------------------------------------------------------------------------- /Java 基础/55.什么是线程安全?保障线程安全有哪些手段?.md: -------------------------------------------------------------------------------- 1 | #### 什么是线程安全?保障线程安全有哪些手段? 2 | 3 | 线程安全就是当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。保证线程安全可从多线程三特性出发: 4 | 5 | 1. 原子性(Atomicity):单个或多个操作是要么全部执行,要么都不执行 6 | - Lock:保证同时只有一个线程能拿到锁,并执行申请锁和释放锁的代码 7 | - synchronized:对线程加独占锁,被它修饰的类/方法/变量只允许一个线程访问 8 | 2. 可见性(Visibility):当一个线程修改了共享变量的值,其他线程能够立即得知这个修改 9 | - volatile:保证新值能立即同步到主内存,且每次使用前立即从主内存刷新; 10 | - synchronized:在释放锁之前会将工作内存新值更新到主存中 11 | 3. 有序性(Ordering):程序代码按照指令顺序执行 12 | - volatile: 本身就包含了禁止指令重排序的语义 13 | - synchronized:保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入 -------------------------------------------------------------------------------- /Java 基础/56.JAVA的四种引用,及应用场景.md: -------------------------------------------------------------------------------- 1 | #### JAVA的四种引用,及应用场景 2 | 3 | ##### 参考答案 4 | 5 | Java的对象引用,在jdk1.2之前只有强引用,在这之后加入了其他引用,即强弱软虚。 6 | 强引用(Strong Reference):最常用的引用类型,如Object obj=new Object()。只要强引用存在GC就不会这个对象,所以这也是引起OOM的原因。 7 | 8 | 软引用(Soft Reference):用于描述还有用但非必须的对象,当堆将发生OOM时则会回收软引用所指向的内存空间,若回收后依然空间不足才会抛出OOM。一般用于实现内存敏感的高速缓存。当真正对象被标记finalizable以及的finalize()方法调用之后并且内存已经清理, 那么如果SoftReference object还存在就被加入到它的 ReferenceQueue。只有前面几步完成后,软引用和弱引用的get方法才会返回null。 9 | 10 | 弱引用(Weak Reference):发生GC时必定回收弱引用指向的内存空间。 和软引用加入队列的时机相同。 11 | 12 | 虚引用(Phantom Reference) 又称为幽灵引用或幻影引用,虚引用既不会影响对象的生命周期,也无法通过虚引用来获取对象实例,仅用于在发生GC时接收一个系统通知。当一个对象的finalize方法已经被调用了之后,这个对象的虚引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了。虚引用和软引用和弱引用都不同,它会在内存没有清理的时候被加入引用队列.虚引用的建立必须要传入引用队列,其他可以没有。 13 | 14 | 15 | 16 | ##### 蛋友补充 17 | 18 | ###### From [BelieveFrank](https://github.com/BelieveFrank) 19 | 20 | GC在收集一个对象的时候会判断是否有引用指向对象,在JAVA中的引用主要有四种: 21 | 22 | ### 强引用(Strong Reference) 23 | 24 | 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 25 | 26 | ### 软引用(Soft Reference) 27 | 28 | 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 29 | 30 | 下面举个例子,假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。 31 | 32 | 设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了内存溢出的问题。 33 | 34 | 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。 35 | 36 | ### 弱引用(Weak Reference) 37 | 38 | 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 39 | 40 | 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 41 | 42 | ### 虚引用(Phantom Reference) 43 | 44 | “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 45 | 46 | 虚引用主要用于检测对象是否已经从内存中删除,跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 47 | 48 | 虚引用的唯一目的是当对象被回收时收到一个系统通知。 -------------------------------------------------------------------------------- /Java 基础/59.Java 中堆和栈有什么区别.md: -------------------------------------------------------------------------------- 1 | #### Java 中堆和栈有什么区别 2 | 3 | ##### 参考答案 4 | 5 | 堆与栈的区别很明显: 6 | 7 | 1. 栈内存存储的是局部变量而堆内存存储的是实体; 8 | 2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短; 9 | 3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。 10 | 11 | 堆、栈说明: 12 | 13 | 栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。 14 | 15 | 堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。 -------------------------------------------------------------------------------- /Java 基础/79.说说你对线程池的理解.md: -------------------------------------------------------------------------------- 1 | #### 说说你对线程池的理解 2 | 3 | 使用线程池的原因: 4 | 5 | 1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 6 | 2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大)。 7 | 8 | 线程池的分类: 9 | 10 | 1. 线程池都是通过Executors来创建的。 11 | 2. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 12 | 3. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 13 | 4. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 14 | 5. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。 15 | 16 | 线程数解析: 17 | 18 | 1. corePoolSize: 线程池维护线程的最少数量。 19 | 2. maximumPoolSize:线程池维护线程的最大数量。 20 | 3. keepAliveTime: 线程池维护线程所允许的空闲时间。 21 | 4. workQueue: 线程池所使用的缓冲队列。 22 | 5. handler: 线程池对拒绝任务的处理策略。 23 | 24 | 创建规则: 一个任务通过execute(Runnable)方法欲添加到线程池时: 25 | 26 | 1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 27 | 2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。 28 | 3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。 29 | 4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 30 | 5. 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。 31 | 32 | 终止和关闭线程池: hreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中: 33 | 34 | 1. Shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。 35 | 2. ShutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。 -------------------------------------------------------------------------------- /Java 基础/82.并行和并发有什么区别.md: -------------------------------------------------------------------------------- 1 | #### 并行和并发有什么区别 2 | 3 | 并行:多个处理器或多核处理器同时处理多个任务。 4 | 5 | 并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。 6 | 7 | 举例: 8 | 并发 = 两个队列和一台咖啡机。 9 | 并行 = 两个队列和两台咖啡机。 -------------------------------------------------------------------------------- /Java 基础/86.简单谈谈 java 中 super 和 this 的区别以及应用场景.md: -------------------------------------------------------------------------------- 1 | #### 简单谈谈 java 中 super 和 this 的区别以及应用场景 2 | 3 | this 为当前类的引用对象,谁调用代表谁;super 为父类存储空间标识,可以理解为父类对象,谁调用代表谁的父亲。 4 | 5 | 对于 this 的应用场景主要分下面几类: 6 | 7 | - 构造方法:通过 this 调用同类中另一个满足指定参数类型的构造方法的用发是 this(参数列表); 这个仅仅在类的构造方法中,别的地方不能这么用,同时要注意 this(参数列表); 语句只能用在子类构造方法体中的第一行。 8 | - 变量:函数参数或者函数中的局部变量和成员变量同名的情况下成员变量被屏蔽,此时要访问成员变量则需要用 this.成员变量名; 的方式来引用成员变量,在没有同名的情况下可以直接用成员变量的名字而不用 this。 9 | - 函数:在函数中需要引用该函所属类的当前对象时候可以直接用 this,特别注意,this 不能用在 static 方法中,因为 static 方法是类级别的,this 是对象级别的。 10 | 11 | 对于 super 的应用场景主要分下面几类: 12 | 13 | - 构造方法:在子类构造方法中要调用父类的构造方法可以用 super(参数列表); 的方式调用,参数不是必须的,同时要注意 super(参数列表); 语句只能用在子类构造方法体中的第一行。 14 | - 变量:当子类方法中局部变量或子类成员变量与父类成员变量同名(即子类局部变量覆盖父类成员变量)时用 super.成员变量名; 来引用父类成员变量,如果父类的成员变量没有被覆盖也可以用 super.成员变量名; 来引用父类成员变量,只是多此一举。 15 | - 成员方法:当子类成员方法覆盖父类成员方法(子类和父类有完全相同的方法定义)时用 super.方法名(参数列表); 的方式访问父类方法。 16 | 17 | 注意:this 和 super 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过;此外由于 this 和 super 都指的是对象,所以均不可以在 static 中使用(包括 static 变量、static 方法、static 语句块)。 -------------------------------------------------------------------------------- /Java 基础/93.Java nio 和 io 的区别.md: -------------------------------------------------------------------------------- 1 | #### Java nio 和 io 的区别 2 | 3 | 1. Java NIO提供了与标准IO不同的IO工作方式: 4 | - Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 5 | - Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似 6 | - Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。 7 | 8 | 2. 阻塞IO和非阻塞IO 9 | 10 | - Java IO流都是阻塞的,这意味着,当一条线程执行read()或者write()方法时,这条线程会一直阻塞直到读取到了一些数据或者要写出去的数据已经全部写出,在这期间这条线程不能做任何其他的事情。 11 | 12 | - java NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存储数据外和IO基本没有区别)允许一条线程从channel中读取数据,通过返回值来判断buffer中是否有数据,如果没有数据,NIO不会阻塞,因为不阻塞这条线程就可以去做其他的事情,过一段时间再回来判断一下有没有数据。NIO的写也是一样的,一条线程将buffer中的数据写入channel,它不会等待数据全部写完才会返回,而是调用完write()方法就会继续向下执行 13 | 14 | 3. 面向流与面向缓冲 15 | 16 | Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。 -------------------------------------------------------------------------------- /Java 基础/94.String 为什么要设计成不可变的.md: -------------------------------------------------------------------------------- 1 | #### String 为什么要设计成不可变的 2 | 3 | 1、字符串池的需求字符串池是方法区(Method Area)中的一块特殊的存储区域。当一个字符串已经被创建并且该字符串在 池 中,该字符串的引用会立即返回给变量,而不是重新创建一个字符串再将引用返回给变量。如果字符串不是不可变的,那么改变一个引用(如: string2)的字符串将会导致另一个引用(如: string1)出现脏数据。 4 | 5 | 2、允许字符串缓存哈希码在 java 中常常会用到字符串的哈希码,例如: HashMap 。String 的不变性保证哈希码始终一,因此,他可以不用担心变化的出现。 这种方法意味着不必每次使用时都重新计算一次哈希码——这样,效率会高很多。 6 | 7 | 3、安全 String 广泛的用于 java 类中的参数,如:网络连接(Network connetion),打开文件(opening files )等等。如果 String 不是不可变的,网络连接、文件将会被改变——这将会导致一系列的安全威胁。操作的方法本以为连接上了一台机器,但实际上却不是。由于反射中的参数都是字符串,同样,也会引起一系列的安全问题。 -------------------------------------------------------------------------------- /Java 基础/95.HashMap 排序.md: -------------------------------------------------------------------------------- 1 | #### HashMap 排序 2 | 3 | ##### 已知一个 HashMap集合, User 有 name(String)和 age(int)属性。请写一个方法实现对HashMap的排序功能,要求对HashMap中的User的age倒序进行排序。 4 | 5 | Tips:HashMap 本身就是不可排序的,但是该道题偏偏让给HashMap排序,那我们就得想在API 中有没有这样的Map 结构是有序的,LinkedHashMap,对的,就是他,他是Map 结构,也是链表结构,有序的,更可喜的是他是HashMap 的子类,我们返回LinkedHashMap即可. 6 | 但凡是对集合的操作,我们应该保持一个原则就是能用JDK中的API就用JDK中的API,比如排序算法我们不应该 去 用 冒 泡 或 者 选 择 , 而 是 首 先 想 到 用 Collections 集 合 工 具 类 。 7 | 8 | ```java 9 | public class HashMapTest { 10 | public static void main(String[] args) { 11 | HashMap users = new HashMap<>(); 12 | users.put(1, new User("张三", 25)); 13 | users.put(3, new User("李四", 22)); 14 | users.put(2, new User("王五", 28)); 15 | System.out.println(users); 16 | 17 | HashMap sortHashMap = sortHashMap(users); 18 | System.out.println(sortHashMap); 19 | 20 | /** 21 | * 控制台输出内容 22 | * {1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四, age=22]} 23 | * {2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四, age=22]} 24 | */ 25 | } 26 | 27 | public static HashMap sortHashMap(HashMap map) { 28 | // 首先拿到 map 的键值对集合 29 | Set> entrySet = map.entrySet(); 30 | // 将 set 集合转为 List 集合,为什么,为了使用工具类的排序方法 31 | List> list = new ArrayList>(entrySet); 32 | 33 | // 使用 Collections 集合工具类对 list 进行排序,排序规则使用匿名内部类来实现 34 | Collections.sort(list, new Comparator>() { 35 | @Override 36 | public int compare(Entry o1, Entry o2) { 37 | //按照要求根据 User 的 age 的倒序进行排 38 | return o2.getValue().getAge()-o1.getValue().getAge(); 39 | } 40 | }); 41 | //创建一个新的有序的 HashMap 子类的集合 42 | LinkedHashMap linkedHashMap = new LinkedHashMap(); 43 | //将 List 中的数据存储在 LinkedHashMap 中 44 | for(Entry entry : list){ 45 | linkedHashMap.put(entry.getKey(), entry.getValue()); 46 | } 47 | 48 | return linkedHashMap; 49 | } 50 | } 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /Java 基础/96.为什么 Java 中用 char 数组比 String 更适合存储密码.md: -------------------------------------------------------------------------------- 1 | #### 为什么 Java 中用 char 数组比 String 更适合存储密码 2 | 3 | 由于字符串在 Java 中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它. 并且为了可重用性,会存在 String 在字符串池中, 它很可能会保留在内存中持续很长时间,从而构成安全威胁。由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[],你就可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。 4 | 5 | -------------------------------------------------------------------------------- /Java 基础/97.为什么Java中不支持多重继承.md: -------------------------------------------------------------------------------- 1 | #### 为什么Java中不支持多重继承 2 | 3 | 多继承虽然能使子类同时拥有多个父类的特征,但是其缺点也是很显著的,主要有两方面: 4 | (1)如果在一个子类继承的多个父类中拥有相同名字的实例变量,子类在引用该变量时将产生歧义,无法判断应该使用哪个父类的变量 5 | (2)如果在一个子类继承的多个父类中拥有相同方法,子类中有没有覆盖该方法,那么调用该方法时将产生歧义,无法判断应该调用哪个父类的方法 6 | 正因为有以上的致命缺点,所以java中禁止一个类继承多个父类。 7 | 8 | 参考文档: 9 | 1)chrome-extension://cdonnmffkdaoajfknoeeecmchibpmkmg/static/pdf/web/viewer.html?file=https%3A%2F%2Fwww.cs.dartmouth.edu%2F~mckeeman%2Fcs118%2Freferences%2FOriginalJavaWhitepaper.pdf 10 | 2)https://www.zhihu.com/question/24317891 11 | 3)https://www.breakyizhan.com/java/4226.html 12 | 13 | -------------------------------------------------------------------------------- /Java 基础/99.Java中的异常处理机制的简单原理和应用.md: -------------------------------------------------------------------------------- 1 | #### Java中的异常处理机制的简单原理和应用 2 | 3 | 异常是指java程序运行时(非编译)所发生的非正常情况或错误,与现实生活中的事件很相似,现实生活中的事件可以包含事件发生的时间、地点、人物、情节等信息,可以用一个对象来表示,Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。 4 | 5 | Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception,Error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如,说内存溢出和线程死锁等系统问题。Exception表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。 6 | 7 | java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以,编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。 -------------------------------------------------------------------------------- /Java 进阶/100.synchronized和volatile关键字的作用.md: -------------------------------------------------------------------------------- 1 | #### synchronized和volatile关键字的作用 2 | 3 | 一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile修饰之后,那么就具备了两层语义: 4 | 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 5 | 2)禁止进行指令重排序。 6 | 7 | 8 | volatile 本质是在告诉jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; 9 | synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 10 | 1.volatile 仅能使用在变量级别; 11 | synchronized则可以使用在变量、方法、和类级别的 12 | 13 | 2.volatile 仅能实现变量的修改可见性,并不能保证原子性; 14 | synchronized则可以保证变量的修改可见性和原子性 15 | 16 | 3.volatile 不会造成线程的阻塞; 17 | synchronized可能会造成线程的阻塞。 18 | 19 | 4.volatile 标记的变量不会被编译器优化; 20 | synchronized标记的变量可以被编译器优化 -------------------------------------------------------------------------------- /Java 进阶/102.Java中用到的线程调度算法是什么?并作解释说明.md: -------------------------------------------------------------------------------- 1 | #### Java中用到的线程调度算法是什么?并作解释说明 2 | 3 | 计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令.所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权. 4 | 5 | 有两种调度模型:分时调度模型和抢占式调度模型。 6 | 7 | 分时调度模型是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片这个也比较好理解。 8 | 9 | java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。 10 | 11 | 12 | 13 | ##### From [lydlovexyz](https://github.com/lydlovexyz) 14 | 15 | 一般线程调度模式分为两种——抢占式调度和协同式调度. 16 | 17 | 抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,线程的切换不由线程本身决定,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。 18 | 19 | 协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,线程的执行时间由线程本身控制,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。 20 | 21 | Java使用的是哪种线程调度模式?此问题涉及到JVM的实现,JVM规范中规定每个线程都有优先级,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。JVM的规范没有严格地给调度策略定义,一般Java使用的线程调度是抢占式调度,在JVM中体现为让可运行池中优先级高的线程拥有CPU使用权,如果可运行池中线程优先级一样则随机选择线程,但要注意的是实际上一个绝对时间点只有一个线程在运行(这里是相对于一个CPU来说),直到此线程进入非可运行状态或另一个具有更高优先级的线程进入可运行线程池,才会使之让出CPU的使用权。 22 | 23 | 参考 24 | https://juejin.im/post/5aea581ff265da0b82629c76 25 | https://juejin.im/post/5ca30c1ae51d45699466a58a -------------------------------------------------------------------------------- /Java 进阶/134.简述内省与暴力反射.md: -------------------------------------------------------------------------------- 1 | #### 简述内省与暴力反射 2 | 3 | 1、什么是JavaBean? 4 | JavaBean是一个特殊的 Java类,这个类中的方法的名称符合某种特定的规则 5 | 2、什么是内省? 6 | JDK中提供了对 JavaBean进行操作的一些 API,这套API就成为内省。 7 | 3、反射与内省的区别? 8 | 反射可以操作各种不同的 java类,内省只是通过反射来操作 JavaBean类。 JavaBean类里面操作的都是成员变量,都是通过 setXXX和getXXX方法来获取成员变量,这样的类用内省来操作会更简单。 9 | 10 | 内省在wiki上的解释: 11 | 12 | > 在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。 13 | > 不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。 14 | 15 | 内省和反射有什么区别: 16 | 反射是在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。 17 | 内省机制是通过反射来实现的,BeanInfo用来暴露一个bean的属性、方法和事件,以后我们就可以操纵该JavaBean的属性。 18 | [![image](https://user-images.githubusercontent.com/20238022/67578557-44433400-f775-11e9-990b-e3d716c0cb2f.png)](https://user-images.githubusercontent.com/20238022/67578557-44433400-f775-11e9-990b-e3d716c0cb2f.png) 19 | 在Java内省中,用到的基本上就是上述几个类。 20 | 通过BeanInfo这个类就可以获取到类中的方法和属性,具体的可以参考JDK文档 21 | [博客链接](https://www.cnblogs.com/peida/archive/2013/06/03/3090842.html) 22 | 23 | 4、什么是暴力反射 24 | 类里面的私有变量,通过普通的 getField反射无法获得,只有通过 getDeclaredField()获得,然后利用setAccessible方法访问。 这个获取和访问的过程就是暴利访问。 25 | 26 | ```java 27 | Field fieldX = ReflectPoint.class.getDeclaredField("x"); 28 | fieldX.setAccessible(true); 29 | ``` -------------------------------------------------------------------------------- /Java 进阶/167.描述动态代理的几种实现方式,分别说出相应的优缺点.md: -------------------------------------------------------------------------------- 1 | #### 描述动态代理的几种实现方式,分别说出相应的优缺点 2 | 3 | AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。 4 | jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。 5 | 6 | 总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。 -------------------------------------------------------------------------------- /Java 进阶/172.线程池不允许使用 Executors 去创建的原因?而通过 ThreadPoolExecutor 创建线程的方式及优势?.md: -------------------------------------------------------------------------------- 1 | #### 线程池不允许使用 Executors 去创建的原因?而通过 ThreadPoolExecutor 创建线程的方式及优势? 2 | 3 | Executors 返回的线程池对象的弊端如下: 4 | 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 5 | 6 | 线程池ThreadPoolExcutor的使用: 7 | 1.优势: 8 | (1)降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。 9 | (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 10 | (3)提高线程的可管理性。线程是稀缺资源,如果入限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。 11 | 12 | 2.线程池的创建 13 | new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler) 14 | (1)corePoolSize: 线程池维护线程的最少数量 (core : 核心) 15 | (2)maximumPoolSize: 线程池维护线程的最大数量 16 | (3)keepAliveTime: 线程池维护线程所允许的空闲时间 17 | (4)unit: 线程池维护线程所允许的空闲时间的单位 18 | (5)workQueue: 线程池所使用的缓冲队列 19 | (6)handler: 线程池对拒绝任务的处理策略 20 | 21 | 3.添加任务到线程池 22 | 通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。 23 | 当一个任务通过execute(Runnable)方法欲添加到线程池时: 24 | 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 25 | 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。 26 | 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。 27 | 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。 28 | 也就是:处理任务的优先级为: 29 | 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 30 | 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。 31 | unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。 32 | workQueue常用的是:java.util.concurrent.ArrayBlockingQueue 33 | handler有四个选择: 34 | ThreadPoolExecutor.AbortPolicy(): 抛出java.util.concurrent.RejectedExecutionException异常 35 | ThreadPoolExecutor.CallerRunsPolicy(): 重试添加当前的任务,他会自动重复调用execute()方法 36 | ThreadPoolExecutor.DiscardOldestPolicy(): 抛弃旧的任务 37 | ThreadPoolExecutor.DiscardPolicy(): 抛弃当前的任务 38 | 39 | 4.线程池的使用场合 40 | (1)单个任务处理的时间比较短; 41 | (2)需要处理的任务数量大。 -------------------------------------------------------------------------------- /Java 进阶/98.死锁与活锁的区别,死锁与饥饿的区别.md: -------------------------------------------------------------------------------- 1 | #### 死锁与活锁的区别,死锁与饥饿的区别 2 | 3 | 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。 4 | 5 | 产生死锁的必要条件: 6 | 7 | 互斥条件:所谓互斥就是进程在某一时间内独占资源。 8 | 9 | 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 10 | 11 | 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 12 | 13 | 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 14 | 15 | 活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。 16 | 17 | 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。 18 | 19 | 饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。 20 | 21 | Java中导致饥饿的原因: 22 | 23 | 高优先级线程吞噬所有的低优先级线程的CPU时间。 24 | 25 | 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。 26 | 27 | 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法),因为其他线程总是被持续地获得唤醒。 -------------------------------------------------------------------------------- /其他/10.谈谈你的职场规划.md: -------------------------------------------------------------------------------- 1 | #### 谈谈你的职场规划 2 | 3 | 这是一个比较开放的面试题 4 | 5 | 来自大佬的解析: 6 | 7 | > 主要看的是面试者对自己的定位,这样便于让企业去识别这个人的培养方向。问完这个问题会有第二个问题你有没有想过如何达成你的目标,这个通常看面试者的规划能力,说的越详细越好。有目标并有清晰规划的人,通常这类人自驱力比较强的人。一般没有明确目标和清晰规划的人我是不会招的 8 | > 9 | > 还有企业文化,通常我问面试者是否认同我们的企业文化,不认同技术再好也不行。 10 | 11 | -------------------------------------------------------------------------------- /其他/22.推荐系统设计.md: -------------------------------------------------------------------------------- 1 | #### 请设计一套推荐系统,不需要具体/细节的实现 你可以从一个项目经理、架构师、程序员的角度来回答,也可以从一个老板/客户的角度来谈谈想要什么,也可以从任何角度来谈谈你对推荐系统的理解 2 | 3 | 开放性面试题,无标准答案 4 | 5 | ##### 参考答案 6 | 7 | 1、数据埋点 8 | 2、数据统计/分析 9 | 3、差异化管理 10 | 4、用户行为预测 11 | 12 | ##### 蛋友补充 13 | 14 | ###### From [jiwenjie](https://github.com/jiwenjie) 15 | 16 | - 不能只是单纯的把热门的相关信息推荐过来,这样会导致时间长之后看到的都是重复的资源而看不到其他 17 | - 真的是千人千面而不是千人一面,根据用户平时的搜索习惯来推荐信息 18 | - 最好是重复性资源不要老是推荐,否则会有无趣感 19 | 20 | 21 | 22 | ###### From 陈汉鹏 23 | 24 | 推荐系统的任务就是联系用户和信息(物品),一方面帮助用户发现对自己有价值的信息,另一方面让信息能够展现在对它感兴趣的用户面前,从而实现信息消费者和信息生产者的双赢。 25 | 推荐系统很好满足了用户、平台、内容提供商三方的需求。以淘宝为例:用户及在淘宝上购物的买家,平台即淘宝网站,网站上众多的店主就是内容提供方。通过推荐系统可以更好将商品曝光给要购买的用户,提升社会资源的配置效率。 26 | 推荐系统落地到业务上需要大量的工程开发:涉及到日志打点、日志收集、ETL、分布式计算、特征工程、推荐算法建模、数据存储、提供接口服务、UI展示和交互、推荐效果评估等。 -------------------------------------------------------------------------------- /其他/29.Android平台的优势和不足.md: -------------------------------------------------------------------------------- 1 | #### Android平台的优势和不足 2 | 3 | ##### 参考答案 4 | 5 | Android平台手机 5大优势: 6 | 7 | 开放性:Android平台首先就是其开放性,开发的平台允许任何移动终端厂商加入到Android联盟中来。显著的开放性可以使其拥有更多的开发者; 8 | 9 | 挣脱运营商的束缚:在过去很长的一段时间,手机应用往往受到运营商制约,使用什么功能接入什么网络,几乎都受到运营商的控制,而Android用户可以更加方便地连接网络,运营商的制约减少; 10 | 11 | 丰富的硬件选择:由于Android的开放性,众多的厂商会推出千奇百怪,功能特色各具的多种产品。功能上的差异和特色,却不会影响到数据同步、甚至软件的兼容; 12 | 13 | 开发商不受任何限制:Android平台提供给第三方开发商一个十分宽泛、自由的环境,不会受到各种条条框框的阻扰; 14 | 15 | 无缝结合的Google应用: Android平台手机将无缝结合这些优秀的Google服务如地图、邮件、搜索等; 16 | 17 | Android平台手机几大不足: 18 | 19 | 安全和隐私:由于手机与互联网的紧密联系,个人隐私很难得到保守。除了上网过程中经意或不经意留下的个人足迹,Google这个巨人也时时站在你的身后,洞穿一切; 20 | 21 | 过分依赖开发商缺少标准配置:在Android平台中,由于其开放性,软件更多依赖第三方厂商,比如Android系统的SDK中就没有内置音乐播放器,全部依赖第三方开发,缺少了产品的统一性; 22 | 23 | 同类机型用户很少:在不少手机论坛都会有针对某一型号的子论坛,对一款手机的使用心得交流,并分享软件资源。而对于Android平台手机,由于厂商丰富,产品类型多样,这样使用同一款机型的用户越来越少,缺少统一机型的程序强化。 -------------------------------------------------------------------------------- /算法,数据结构/123.设计一个有 getMin 功能的栈.md: -------------------------------------------------------------------------------- 1 | #### 设计一个有 getMin 功能的栈 2 | 3 | 要求: 4 | pop、push、getMin操作的时间复杂度都是O(1) 5 | 设计的栈类型可以使用现成的栈结构 6 | 7 | 8 | 9 | ```java 10 | /** 11 | * 实现一个特殊的栈,在实现栈的基本功能的基础上,在实现返回栈中最小元素的操作。 要求: 1. pop、push、getMin操作的时间复杂度都是O(1) 12 | * 2. 设计的栈类型可以使用现成的栈结构 13 | */ 14 | public class Problem01_GetMinStack { 15 | 16 | public static class MyStack1 { 17 | 18 | /** 19 | * 两个栈,其中stacMin负责将最小值放在栈顶,stackData通过获取stackMin的peek()函数来获取到栈中的最小值 20 | */ 21 | private Stack stackData; 22 | private Stack stackMin; 23 | 24 | /** 25 | * 在构造函数里面初始化两个栈 26 | */ 27 | public MyStack1() { 28 | stackData = new Stack(); 29 | stackMin = new Stack(); 30 | } 31 | 32 | /** 33 | * 该函数是stackData弹出栈顶数据,如果弹出的数据恰好等于stackMin的数据,那么stackMin也弹出 34 | * @return 35 | */ 36 | public Integer pop() { 37 | Integer num = (Integer) stackData.pop(); 38 | if (num == getmin()) { 39 | return (Integer) stackMin.pop(); 40 | } 41 | return null; 42 | } 43 | 44 | /** 45 | * 该函数是先判断stackMin是否为空,如果为空,就push新的数据,如果这个数小于stackMin中的栈顶元素,那么stackMin需要push新的数,不管怎么样 46 | * stackData都需要push新的数据 47 | * @param value 48 | */ 49 | public void push(Integer value) { 50 | if (stackMin.isEmpty()) { 51 | stackMin.push(value); 52 | } 53 | 54 | else if (value < getmin()) { 55 | stackMin.push(value); 56 | } 57 | stackData.push(value); 58 | } 59 | 60 | /** 61 | * 该函数是当stackMin为空的话第一次也得push到stackMin的栈中,返回stackMin的栈顶元素 62 | * @return 63 | */ 64 | public Integer getmin() { 65 | if (stackMin == null) { 66 | throw new RuntimeException("stackMin is empty"); 67 | } 68 | return (Integer) stackMin.peek(); 69 | 70 | } 71 | } 72 | 73 | public static void main(String[] args) throws Exception { 74 | /** 75 | * 要注意要将MyStack1声明成静态的,静态内部类不持有外部类的引用 76 | */ 77 | MyStack1 stack1 = new MyStack1(); 78 | stack1.push(3); 79 | System.out.println(stack1.getmin()); 80 | stack1.push(4); 81 | System.out.println(stack1.getmin()); 82 | stack1.push(1); 83 | System.out.println(stack1.getmin()); 84 | System.out.println(stack1.pop()); 85 | System.out.println(stack1.getmin()); 86 | 87 | System.out.println("============="); 88 | } 89 | } 90 | ``` -------------------------------------------------------------------------------- /算法,数据结构/19.快速查找1000万个数中,最大的100个(算法).md: -------------------------------------------------------------------------------- 1 | #### 快速查找1000万个数中,最大的100个(算法) 2 | 3 | ##### 参考答案 4 | 5 | 可以举个例子说明,全校高一有100个班,我想找出全校前10名的同学,很傻的办法就是,把高一100个班的同学成绩都取出来,作比较,这个比较数据量太大了。应该很容易想到,班里的第11名,不可能是全校的前10名。也就是说,不是班里的前10名,就不可能是全校的前10名。因此,只需要把每个班里的前10取出来,作比较就行了,这样比较的数据量就大大地减少了。 6 | 就是说先把数分成n份,取每份中前100个,然后再汇总,取汇总后结果中的前100个,这样需要比较的数据量会大幅度减少,n 的取值越大,需要的服务器、cpu越多,主要是"分块处理"的思想 7 | 8 | 9 | 10 | ##### 蛋友补充 11 | 12 | ###### From [midNightHz](https://github.com/midNightHz) 13 | 14 | 策略1、对1000万个数字进行倒序排序,排序后取前100个 ,使用快速排序算法时间复杂度 为O(n×log(n)),最差情况为O(n^2) 15 | 策略2、i:取前100个数字,进行排序 ,从大到小排序 16 | ii:遍历101-1000万个数字 a,分别对这100个数字进行对比(如果 a 数字比100个最大的数字大,则将100个数字中最后一个剔除掉,a 添加到数字首位;如果a 比最小的数字小,则跳过;如果a比最大的数字小又比最大的数字大,则将最后一个数字剔除,将a添加到100个数字中 )可以用对中查找来查询 17 | 算法时间复制度 最差的情况 n*(log2(100)) 18 | 策略3:分组排序,排序后再进行汇总分组,直到只剩下最后100个数字为止 19 | 20 | 21 | 22 | ###### From [safier](https://github.com/safier) 23 | 24 | 1. 假设数组为 array[N] (N = 1 000万),首先利用quicksort的原理把array分成两个部分,左边部分比 array[N - 1] (array中的最后一个值,即pivot) 大, 右边部分比pivot 小。然后,可以得到array[array.length - 1] (即 pivot) 在整个数组中的位置,假设是 k. 25 | 2. 如果 k 比 99 大,原数组变成了 array [0, ... k - 1], 然后在数组里找前 100 最大值。 (继续递归) 26 | 3. 如果 k 比 99 小, 原数组变成了 array [k + 1, ..., N ], 然后在数组里找前 100 - (k + 1) 最大值。(继续递归) 27 | 4. 如果 k == 99, 那么数组的前 100 个值一定是最大的。 28 | 29 | 时间复杂度:平均时间复杂度是 O(N),但是最差是O(N^2) 30 | 31 | ```java 32 | public class QuickSort { 33 | 34 | public static void main(String[] args) { 35 | // the size of the array 36 | int number = 100000000; 37 | // the top k values 38 | int k = 100; 39 | // the range of the values in the array 40 | int range = 1000000001; 41 | 42 | 43 | int[] array = new int[number]; 44 | Random random = new Random(); 45 | for (int i = 0;i < number;i++){ 46 | array[i] = random.nextInt(range); 47 | System.out.println(array[i]); 48 | } 49 | 50 | // start time 51 | long t1 = System.currentTimeMillis(); 52 | tophundred(array,0,array.length - 1,k); 53 | // end time 54 | long t2 = System.currentTimeMillis(); 55 | 56 | System.out.println("The total execution time " + 57 | "of quicksort based method is " + (t2 - t1) +" millisecond!"); 58 | 59 | // print out the top k largest values in the top array 60 | System.out.println("The top "+ k + " largest values are:"); 61 | 62 | for (int i = 0; i < k; i++) { 63 | System.out.println(array[i]); 64 | } 65 | } 66 | 67 | private static void tophundred(int[] array,int start,int end,int k) { 68 | int switchPointer = start; 69 | int pivot = array[end];// array最后一个值作为pivot 70 | for (int i = start;i < end;i++) { 71 | if (array[i] >= pivot) { 72 | swap(array,switchPointer,i); 73 | switchPointer++; 74 | } 75 | } 76 | swap(array,end,switchPointer);//交换2后,array左边的值比pivot大,右边的值比pivot小 77 | 78 | if (switchPointer < k - 1){ 79 | tophundred(array,switchPointer+1,end,k - switchPointer - 1); // 比pivot大的部分不够99个,所以从后面再找100-(左边的部分) 80 | } else if(switchPointer == k - 1) { 81 | return; 82 | } else { 83 | tophundred(array,0,switchPointer - 1,k); 84 | } 85 | 86 | } 87 | 88 | private static void swap(int[] array,int i,int j) { 89 | int temp = array[i]; 90 | array[i] = array[j]; 91 | array[j] = temp; 92 | } 93 | } 94 | ``` -------------------------------------------------------------------------------- /算法,数据结构/27.n个台阶,每次都可以走一步,走两步,或走三步,走到顶部一共有多少种走法.md: -------------------------------------------------------------------------------- 1 | #### n个台阶,每次都可以走一步,走两步,或走三步,走到顶部一共有多少种走法 2 | 3 | ##### 参考答案 4 | 5 | 解题思路分析: 6 | 1、n=0 和 n=1 的时候 并没有其他可选择的,所以可以得出f(0)=0;f(1)=1; 7 | 2、n>=2时情况就变复杂起来,但是这个时候可以操作的步骤也就2种 8 | 也就是走1步(n-1)与走2步(n-2)。所以可以得到f(n)=f(n-1)+f(n-2); 9 | 从当前状态转为下一状态的通用算法既可。 10 | 3、 验证,使用2以上的数字验证几次。 11 | 12 | 答案: 13 | 1.递归 14 | 15 | ```java 16 | public static int f(int n){ 17 | if(n<=2) return n; 18 | int x = f(n-1) + f(n-2); 19 | return x; 20 | } 21 | ``` 22 | 2.迭代 23 | ```java 24 | public static int f(int n){ 25 | if(n<=2) return n; 26 | if first=1,second=2; 27 | int third=0; 28 | for(int i=3;i<=n;i++){ 29 | third = first+second; 30 | first = second; 31 | second = third; 32 | } 33 | return third; 34 | } 35 | ``` 36 | 3.动态规划 37 | ```java 38 | public static int[] A = new int[100]; 39 | public static int f(int n){ 40 | if(n<=2){ 41 | A[n] = n; 42 | } 43 | if(A[n]>0){ 44 | return A[n]; 45 | } else { 46 | A[n] = f(n-1)+f(n-2); 47 | return A[n]; 48 | } 49 | } 50 | ``` 51 | 52 | 53 | 54 | 55 | 56 | ##### 蛋友补充 57 | 58 | ###### From [BelieveFrank](https://github.com/BelieveFrank) 59 | 60 | 采用递归的方式 61 | 62 | ```java 63 | public static long getStepNumber(int n) { 64 | if (n == 1) { 65 | return 1; 66 | } 67 | if (n == 2) { 68 | return 2; 69 | } 70 | if (n == 3) { 71 | return 4; 72 | } 73 | if (n > 3) { 74 | return getStepNumber(n - 1) + getStepNumber(n - 2) + getStepNumber(n - 3); 75 | } 76 | return 0; 77 | } 78 | ``` 79 | 80 | 81 | 82 | ###### From 流星雨 83 | 84 | 方法一:递归,递归方程f(n)=f(n-1)+f(n-2)+f(n-3),临界点f(1)=1,f(2)=2,f(3)=3,同时可以用一个map对象缓存每次的计算结果,下次计算的时候可以先查缓存,没有再计算。 85 | 86 | ``` 87 | private static int getNumStep(int i,HashMap map) { 88 | if (i < 1) { 89 | return 0; 90 | } 91 | if (i == 1) { 92 | return 1; 93 | } 94 | if (i == 2) { 95 | return 2; 96 | } 97 | if(i==3){ 98 | return 3; 99 | } 100 | if (map.containsKey(i)) { 101 | return map.get(i); 102 | }else { 103 | int value = getNumStep(i - 1) + getNumStep(i - 2)+getNumStep(i-3); 104 | map.put(i, value); 105 | return value; 106 | } 107 | } 108 | ``` 109 | 110 | 方法二迭代:方法一是从f(n)开始算,其实也可以从f(1)开始算, 111 | 112 | ```java 113 | private static int getNumStep(int i) { 114 | if (i < 1) { 115 | return 0; 116 | } 117 | if (i == 1) { 118 | return 1; 119 | } 120 | if (i == 2) { 121 | return 2; 122 | } 123 | if(i==3){ 124 | return 3; 125 | } 126 | int a = 1; 127 | int b = 2; 128 | int c = 3; 129 | int value = 0; 130 | for (int j = 4; j <= i; j++) { 131 | value = a + b+c; 132 | a = b; 133 | b = c; 134 | c = value; 135 | } 136 | return value; 137 | } 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- /算法,数据结构/31.从扑克牌中随机抽 5 张牌,判断是不是顺子,即这 5 张牌是不是连续的。 2-10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大小王可以看成任意的 数字.md: -------------------------------------------------------------------------------- 1 | #### 从扑克牌中随机抽 5 张牌,判断是不是顺子,即这 5 张牌是不是连续的。 2-10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大小王可以看成任意的 数字 2 | 3 | #### 参考答案 4 | 5 | 解题思路:我们可以把5张牌看成是由5个数字组成的数组。大小王是特殊的数字,我们可以把它们都定义为0,这样就可以和其他的牌区分开来。 6 | 7 | 首先把数组排序,再统计数组中0的个数,最后统计排序之后的数组中相邻数字之间的空缺总数。如果空缺的总数小于或者等于0的个数,那么这个数组就是连续的,反之则不连续。如果数组中的非0数字重复出现,则该数组是不连续的。换成扑克牌的描述方式就是如果一幅牌里含有对子,则不可能是顺子。 8 | 9 | 详细代码: 10 | 11 | ```java 12 | import java.util.Arrays; 13 | 14 | public class Solution { 15 | 16 | public boolean isContinuous(int[] number){ 17 | if(number == null){ 18 | return false; 19 | } 20 | Arrays.sort(number); 21 | int numberZero = 0; 22 | int numberGap = 0; 23 | //计算数组中0的个数 24 | for(int i = 0;i < number.length && number[i] == 0; i++){ 25 | numberZero++; 26 | } 27 | //统计数组中的间隔数目 28 | int small = numberZero; 29 | int big = small + 1; 30 | 31 | while(bignumberZero)?false:true; 42 | } 43 | } 44 | ``` 45 | 46 | 47 | 48 | ##### 蛋友补充 49 | 50 | ###### From [Mononoke](https://github.com/imononoke) 51 | 52 | ```c++ 53 | bool isContinues(int *n) { 54 | uint32_t list = 0; 55 | for (int i = 0; i < 5; i++) { 56 | uint32_t v = 1u << n[i] >> 1; 57 | if (list & v) 58 | return false; 59 | list |= v; 60 | } 61 | return (list < (((list ^ (list - 1)) + 1) << 4)); 62 | } 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /算法,数据结构/72.算法 #72.md: -------------------------------------------------------------------------------- 1 | #### 算法 #72 2 | 3 | ##### 在有序数组[1,3,4,5,12,15,18,19,21,25,31]中,查找两个数,使得它们的和正好是30,时间复杂度O(n),说出解题思路? 4 | 5 | ##### 参考答案 6 | 7 | 从数组两边开始计算,一个变量从开始到结尾,另一个变量从结尾到开头,两个变量相加,大于30,第2个变量--,小于30,第一个变量++ 8 | 9 | 关键代码: 10 | 11 | ```java 12 | public class Sum { 13 | public static void main(String[] args) { 14 | Sum sum = new Sum(); 15 | int[] a = {-3,-2,0,1,2,3,6,7,9,15,17,19}; 16 | HashMap map = new HashMap(); 17 | map = sum.searchSum(a,9,map); //1)查找 18 | sum.printResult(a,map); //打印查找结果 19 | } 20 | 21 | //查找 ,下标从0开始 22 | private HashMap searchSum(int[] a, int Num, HashMap map){ 23 | int n = a.length; 24 | int i=0,j=n-1; //i和j分别保存当前较小的和交大的数据 25 | //Map map = new HashMap(); 26 | while(iNum){ 28 | j--; 29 | } 30 | if(i map){ 49 | int n = map.size(); 50 | if(n==0){ 51 | System.out.println("没有找到!"); 52 | return; 53 | } 54 | System.out.println("这是找到的所有元素:"); 55 | for(Integer key: map.keySet()){ 56 | System.out.println("下标为:("+key+","+map.get(key)+"), 值为:"+a[key]+","+a[map.get(key)]); 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | ##### 群友总结 63 | 64 | 1. 双指针法:从两端取呀,小了移动左边指针,大了移动右边指针,复杂度O(n) 65 | 可以用两个指针,一个指针指向第一个元素,一个移至最后一个元素,然后判断指针指向的两个元素和,是否小于等于30,不等于的话前移后面的指针。等于30的话输出。找到30的以后再同时移动两个指针,不等于30的时候后移前面的指针,直到找到位置,找到后继续前移后面的指针,以此类推,直到前面的指针地址不小于后面指针的地址。 66 | 67 | 2. 二分法查找;从第一个角标开始,计算差值,然后二分法查找数组,寻找是否存在有满足需求的数,没有就向右移动角标 68 | 69 | 3. 所有数字存进 map,遍历查找 map 中是否存在当前元素与 30 的差值,存在就说明两数之和为 30 ,共需遍历2次,复杂度为 n,map查找的时间复杂度是1,所以整体复杂度还是 n,一边存 map 一边查找的话,只需要遍历一次。 -------------------------------------------------------------------------------- /网络/117.get 和 post 请求有哪些区别.md: -------------------------------------------------------------------------------- 1 | #### get 和 post 请求有哪些区别 2 | 3 | 1. GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间 4 | 以&相连,如:login.action?name=zhagnsan&password=123456。POST 把提交的数据则放置在是 HTTP 包的包 体中。 5 | 2. GET 方式提交的数据最多只能是 1024 字节,理论上 POST 没有限制,可传较大量的数据。其实这样说是错误 的,不准确的: 6 | “GET 方式提交的数据最多只能是 1024 字节",因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟 URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个 限制是特定的浏览器及服务器对它的限制。IE 对 URL 长度的限制是 2083 字节(2K+35)。对于其他浏览器,如 Netscape、 FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。 7 | 3. POST 的安全性要比 GET 的安全性高。注意:这里所说的安全性和上面 GET 提到的“安全”不是同个概念。上 面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的 Security 的含义,比如:通过 GET 提交数据,用 户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别 人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。 -------------------------------------------------------------------------------- /网络/139.为什么 TCP 连接需要三次握手,两次不可以么.md: -------------------------------------------------------------------------------- 1 | #### 为什么 TCP 连接需要三次握手,两次不可以么 2 | 3 | “三次握手” 的目的是为了防止已失效的链接请求报文突然又传送到了服务端,因而产生错误。 4 | 5 | - 正常的情况:A 发出连接请求,但因连接请求报文丢失而未收到确认,于是 A 再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A 共发送了两个连接请求报文段,其中第一个丢失,第二个到达了 B。没有 “已失效的连接请求报文段”。 6 | - 现假定出现了一种异常情况:即 A 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 B。本来这是一个早已失效的报文段。但 B 收到此失效的连接请求报文段后,就误认为是 A 再次发出的一个新的连接请求。于是就向 A 发出确认报文段,同意建立连接。 7 | 8 | 假设不采用“三次握手”,那么只要 B 发出确认,新的连接就建立了。由于现在 A 并没有发出建立连接的请求,因此不会理睬 B 的确认,也不会向 B 发送数据。但 B 却以为新的运输连接已经建立,并一直等待 A 发来数据。这样,B 的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。 -------------------------------------------------------------------------------- /网络/144.TCP 协议如何来保证传输的可靠性.md: -------------------------------------------------------------------------------- 1 | #### TCP 协议如何来保证传输的可靠性 2 | 3 | TCP 提供一种面向连接的、可靠的字节流服务。其中,面向连接意味着两个使用 TCP 的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个 TCP 连接。在一个 TCP 连接中,仅有两方进行彼此通信;而字节流服务意味着两个应用程序通过 TCP 链接交换 8 bit 字节构成的字节流,TCP 不在字节流中插入记录标识符。 4 | 5 | 对于可靠性,TCP通过以下方式进行保证: 6 | 7 | - 数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时TCP发送数据端超时后会重发数据; 8 | - 对失序数据包重排序:既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层; 9 | - 丢弃重复数据:对于重复数据,能够丢弃重复数据; 10 | - 应答机制:当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒; 11 | - 超时重发:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段; 12 | - 流量控制:TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP使用的流量控制协议是可变大小的滑动窗口协议。 -------------------------------------------------------------------------------- /网络/149.客户端不断进行请求链接会怎样?DDos(Distributed Denial of Service)攻击.md: -------------------------------------------------------------------------------- 1 | #### 客户端不断进行请求链接会怎样?DDos(Distributed Denial of Service)攻击 2 | 3 | 服务器端会为每个请求创建一个链接,并向其发送确认报文,然后等待客户端进行确认 4 | (1). DDos 攻击: 5 | 客户端向服务端发送请求链接数据包 6 | 服务端向客户端发送确认数据包 7 | 客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认 8 | (2). DDos 预防:(没有彻底根治的办法,除非不使用TCP) 9 | 限制同时打开SYN半链接的数目 10 | 缩短SYN半链接的Time out 时间 11 | 关闭不必要的服务 -------------------------------------------------------------------------------- /网络/154.TCP和UDP分别对应的常见应用层协议.md: -------------------------------------------------------------------------------- 1 | #### TCP和UDP分别对应的常见应用层协议 2 | 3 | 1. TCP 对应的应用层协议: 4 | 5 | - FTP:定义了文件传输协议,使用21端口。常说某某计算机开了FTP服务便是启动了文件传输服务。下载文件,上传主页,都要用到FTP服务。 6 | - Telnet:它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,通过这种端口可以提供一种基于DOS模式下的通信服务。如以前的BBS是-纯字符界面的,支持BBS的服务器将23端口打开,对外提供服务。 7 | - SMTP:定义了简单邮件传送协议,现在很多邮件服务器都用的是这个协议,用于发送邮件。如常见的免费邮件服务中用的就是这个邮件服务端口,所以在电子邮件设置-中常看到有这么SMTP端口设置这个栏,服务器开放的是25号端口。 8 | - POP3:它是和SMTP对应,POP3用于接收邮件。通常情况下,POP3协议所用的是110端口。也是说,只要你有相应的使用POP3协议的程序(例如Fo-xmail或Outlook),就可以不以Web方式登陆进邮箱界面,直接用邮件程序就可以收到邮件(如是163邮箱就没有必要先进入网易网站,再进入自己的邮-箱来收信)。 9 | - HTTP:从Web服务器传输超文本到本地浏览器的传送协议。 10 | 11 | 2.UDP 对应的应用层协议: 12 | 13 | - DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。 14 | - SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。 15 | - TFTP(Trival File Transfer Protocal):简单文件传输协议,该协议在熟知端口69上使用UDP服务。 16 | 17 | ![](https://user-images.githubusercontent.com/20238022/69505770-89ba7300-0f66-11ea-9087-228d97dc76aa.png) 18 | 19 | ![](https://user-images.githubusercontent.com/20238022/69505778-98088f00-0f66-11ea-9e16-ec63a36d9490.png) -------------------------------------------------------------------------------- /网络/159.TCP 的拥塞避免机制.md: -------------------------------------------------------------------------------- 1 | #### TCP 的拥塞避免机制 2 | 3 | 拥塞:对资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变坏,整个网络的吞吐量随之负荷的增大而下降。 4 | 5 | 拥塞控制:防止过多的数据注入到网络中,使得网络中的路由器或链路不致过载。 6 | 7 | 拥塞控制的方法: 8 | 1.慢启动 + 拥塞避免: 9 | **慢启动:** 不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小; 10 | **拥塞避免:** 拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,这样拥塞窗口按线性规律缓慢增长。 11 | [![image](https://user-images.githubusercontent.com/20238022/69872165-abfa1b00-12ef-11ea-8d6e-b04e67fef36a.png)](https://user-images.githubusercontent.com/20238022/69872165-abfa1b00-12ef-11ea-8d6e-b04e67fef36a.png) 12 | 13 | 2.快重传 + 快恢复 14 | **快重传:** 快重传要求接收方在收到一个 失序的报文段 后就立即发出 重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 15 | [![image](https://user-images.githubusercontent.com/20238022/69872203-c2a07200-12ef-11ea-8d58-1da4b24f5163.png)](https://user-images.githubusercontent.com/20238022/69872203-c2a07200-12ef-11ea-8d58-1da4b24f5163.png) 16 | 17 | **快恢复:** 快重传配合使用的还有快恢复算法,当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半,但是接下去并不执行慢开始算法:因为如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。 18 | 19 | [![image](https://user-images.githubusercontent.com/20238022/69872247-da77f600-12ef-11ea-899c-c8b75097a5f5.png)](https://user-images.githubusercontent.com/20238022/69872247-da77f600-12ef-11ea-899c-c8b75097a5f5.png) -------------------------------------------------------------------------------- /网络/162.TCPIP 如何保证可靠性,说说 TCP 头的结构.md: -------------------------------------------------------------------------------- 1 | #### TCP/IP 如何保证可靠性,说说 TCP 头的结构 2 | 3 | 保证可靠性: 4 | 1、将数据截断为合理的长度; 5 | 2、超时重发; 6 | 3、对于收到的请求,给出确认响应; 7 | 4、 校验出包有错,丢弃报文段,不给出响应,TCP发送数据端,超时时会重发数据; 8 | 5、对失序数据进行重新排序,然后才交给应用层; 9 | 6、对于重复数据,能够丢弃重复数据; 10 | 7、TCP可以进行流量控制,防止较快主机致使较慢主机的缓冲区溢出; 11 | 12 | [![图示](https://camo.githubusercontent.com/958a2320b26f9b66d5be29c19b3acf1c6909f62c/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303230333131323735353435342e706e673f782d6f73732d70726f636573733d696d6167652f77617465726d61726b2c747970655f5a6d46755a33706f5a57356e6147567064476b2c736861646f775f31302c746578745f6148523063484d364c7939696247396e4c6d4e7a5a473475626d56304c3270315a47646c616d46745a584d3d2c73697a655f31362c636f6c6f725f4646464646462c745f3730)](https://camo.githubusercontent.com/958a2320b26f9b66d5be29c19b3acf1c6909f62c/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303230333131323735353435342e706e673f782d6f73732d70726f636573733d696d6167652f77617465726d61726b2c747970655f5a6d46755a33706f5a57356e6147567064476b2c736861646f775f31302c746578745f6148523063484d364c7939696247396e4c6d4e7a5a473475626d56304c3270315a47646c616d46745a584d3d2c73697a655f31362c636f6c6f725f4646464646462c745f3730) 13 | TCP固定头部结构: 14 | 15 | - 16位端口号:包括了源端口号和目的端口号。进行TCP通信时,客户端通常使用系统自动选择的临时端口号(一般都很大),而服务器则使用知服务端口号或服务器管理员自定义的端口号。 16 | - 32位序号:一次TCP通信过程中对一个传输方向上的字节流的每个字节的编号(从这个方向第一个报文段依次排列)。假设主机A和主机B进行TCP通信,A发送给B的第一个TCP报文段中的序号值是系统初始化的一个随机值ISN(初始序号值)。那么之后在A到B的方向上发送的TCP报文段中的序号值将会被系统设置为ISN加上该报文段所携带数据的第一个字节在整个数据字节流中的偏移。 17 | 32位确认号:用作对另一方发送来的TCP报文段做出相应。其值是收到对方的报文段的序号值加1。 18 | - 4位头部长度:标识该TCP头部有多少个32bit字(4字节)。一共有4位,所以最大能表示TCP头部大小为60字节。 19 | - 6位标志位包含如下几项: 20 | (1)URG标志,表示紧急指针是否有效。 21 | (2)ACK标志,表示确认号是否有效。一般称携带ACK标志的报文段是确认报文段。 22 | (3)PSH标志,提示接收端应用程序立即从TCP接受缓冲区读走数据。 23 | (4)RST标志,表示要求对方重新建立连接。称携带RST标志的TCP报文段为复位报文段。 24 | (5)SYN标志,表示请求建立一个连接。称携带SYN标志的TCP报文段为同步报文段。 25 | (6)FIN标志,表示通知对方本端将关闭连接。称携带FIN标志的TCP报文段为结束报文段。 26 | - 16位窗口大小:是TCP流量控制的一个手段。这里说的窗口指的是接收通告窗口(RWND)。它告诉对方本端TCP接收缓冲区还能容纳多少字节的数据,以让对方控制发送数据的速度。 27 | - 16位校验和:由发送端填充,接收端对TCP报文段执行CRC算法以效验TCP报文段在传输过程中是否损坏(包括TCP头部和数据部分)。这也是TCP可靠传输的一个重要保障。 28 | - 16位紧急指针:是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。即这个字段是紧急指针相对当前序号的偏移,为紧急偏移。以让接收端迅速接受到紧急数据。TCP的紧急指针是发送端向接收端发送紧急数据的方法。 -------------------------------------------------------------------------------- /网络/163.简单说说 DNS 的作用及解析流程.md: -------------------------------------------------------------------------------- 1 | #### 简单说说 DNS 的作用及解析流程 2 | 3 | DNS(Domain Name System) 是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于 TCP/IP 网络,它从事将主机名或域名转换为实际 IP 地址的工作。 4 | 5 | 1. 在浏览器中输入 [www.xxx.com](http://www.xxx.com/) 域名,操作系统会先检查自己本地的 hosts 文件是否有这个网址映射关系,如果有就先调用这个 IP 地址映射完成域名解析。 6 | 2. 如果 hosts 里没有这个域名的映射,则查找本地 DNS 解析器缓存是否有这个网址映射关系,如果有直接返回,完成域名解析。 7 | 3. 如果 hosts 与本地 DNS 解析器缓存都没有相应的网址映射关系,首先会找 TCP/IP 参数中设置的首选 DNS 服务器,在此我们叫它本地 DNS 服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。 8 | 4. 如果要查询的域名,不由本地 DNS 服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个 IP 地址映射,完成域名解析,此解析不具有权威性。 9 | 5. 如果本地 DNS 服务器本地区域文件与缓存解析都失效,则根据本地 DNS 服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地 DNS 就把请求发至 “根 DNS 服务器”,“根 DNS 服务器”收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个 IP。本地 DNS 服务器收到 IP 信息后,将会联系负责 .com 域的这台服务器。这台负责 .com 域的服务器收到请求后,如果自己无法解析,它就会找一个管理 .com 域的下一级 DNS 服务器地址 (xxx.com) 给本地 DNS 服务器。当本地 DNS 服务器收到这个地址后,就会找 xxx.com 域服务器,重复上面的动作,进行查询,直至找到 [www.xxx.com](http://www.xxx.com/) 主机。 10 | 6. 如果用的是转发模式,此 DNS 服务器就会把请求转发至上一级 DNS 服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根 DNS 或把转请求转至上上级,以此循环。不管是本地 DNS 服务器用是是转发,还是根提示,最后都是把结果返回给本地 DNS 服务器,由此 DNS 服务器再返回给客户机。 11 | 12 | > 递归查询过程就是“查询的递交者”更替, 而迭代查询过程则是 “查询的递交者”不变。 -------------------------------------------------------------------------------- /网络/164.简述:Session、Cookie 与 Application.md: -------------------------------------------------------------------------------- 1 | #### 简述:Session、Cookie 与 Application 2 | 3 | Cookie和Session都是客户端与服务器之间保持状态的解决方案,具体来说,cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。 4 | **(1). Cookie 及其相关 API :** 5 | Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie,而客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器,服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。 6 | [![image](https://user-images.githubusercontent.com/20238022/70369473-b8383680-18f4-11ea-905a-34ab7fdcf588.png)](https://user-images.githubusercontent.com/20238022/70369473-b8383680-18f4-11ea-905a-34ab7fdcf588.png) 7 | 8 | **(2). Session 及其相关 API:** 9 | 同样地,会话状态也可以保存在服务器端。客户端请求服务器,如果服务器记录该用户状态,就获取Session来保存状态,这时,如果服务器已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用;如果客户端请求不包含sessionid,则为此客户端创建一个session并且生成一个与此session相关联的sessionid,并将这个sessionid在本次响应中返回给客户端保存。保存这个sessionid的方式可以采用 cookie机制 ,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器;若浏览器禁用Cookie的话,可以通过 URL重写机制 将sessionid传回服务器。 10 | [![image](https://user-images.githubusercontent.com/20238022/70369506-8a072680-18f5-11ea-9ee3-e3b3fce13994.png)](https://user-images.githubusercontent.com/20238022/70369506-8a072680-18f5-11ea-9ee3-e3b3fce13994.png) 11 | 12 | (3). Session 与 Cookie 的对比: 13 | 14 | - 实现机制:Session的实现常常依赖于Cookie机制,通过Cookie机制回传SessionID; 15 | - 大小限制:Cookie有大小限制并且浏览器对每个站点也有cookie的个数限制,Session没有大小限制,理论上只与服务器的内存大小有关; 16 | - 安全性:Cookie存在安全隐患,通过拦截或本地文件找得到cookie后可以进行攻击,而Session由于保存在服务器端,相对更加安全; 17 | - 服务器资源消耗:Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加服务器的压力。 18 | 19 | **(4). Application:** 20 | Application(ServletContext):与一个Web应用程序相对应,为应用程序提供了一个全局的状态,所有客户都可以使用该状态。 -------------------------------------------------------------------------------- /网络/169.OSI 网络体系结构与 TCPIP 协议模型.md: -------------------------------------------------------------------------------- 1 | #### OSI 网络体系结构与 TCP/IP 协议模型 2 | 3 | OSI 是一个理论上的网络通信模型,而 TCP/IP 则是实际上的网络通信标准。但是,它们的初衷是一样的,都是为了使得两台计算机能够像两个知心朋友那样能够互相准确理解对方的意思并做出优雅的回应。现在,我们对 OSI 七层模型的各层进行简要的介绍: 4 | [![img](https://user-images.githubusercontent.com/20238022/70874262-5b233b80-1fec-11ea-94f8-d170a25438f6.jpg)](https://user-images.githubusercontent.com/20238022/70874262-5b233b80-1fec-11ea-94f8-d170a25438f6.jpg) 5 | 1). 物理层 6 | 7 | 参考模型的最低层,也是OSI模型的第一层,实现了相邻计算机节点之间比特流的透明传送,并尽可能地屏蔽掉具体传输介质和物理设备的差异,使其上层(数据链路层)不必关心网络的具体传输介质。 8 | 9 | 2). 数据链路层(data link layer) 10 | 11 | 接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。这一层在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路,即提供可靠的通过物理介质传输数据的方法。 12 | 13 | 3). 网络层 14 | 15 | 将网络地址翻译成对应的物理地址,并通过路由选择算法为分组通过通信子网选择最适当的路径。 16 | [![img](https://user-images.githubusercontent.com/20238022/70874327-9cb3e680-1fec-11ea-85b5-e5ec037606ef.jpg)](https://user-images.githubusercontent.com/20238022/70874327-9cb3e680-1fec-11ea-85b5-e5ec037606ef.jpg) 17 | 18 | 4). 传输层(transport layer) 19 | 20 | 在源端与目的端之间提供可靠的透明数据传输,使上层服务用户不必关系通信子网的实现细节。在协议栈中,传输层位于网络层之上,传输层协议为不同主机上运行的进程提供逻辑通信,而网络层协议为不同主机提供逻辑通信。 21 | [![img](https://user-images.githubusercontent.com/20238022/70874344-adfcf300-1fec-11ea-8143-cf0d259bf98c.jpg)](https://user-images.githubusercontent.com/20238022/70874344-adfcf300-1fec-11ea-8143-cf0d259bf98c.jpg) 22 | 实际上,网络层可以看作是传输层的一部分,其为传输层提供服务。但对于终端系统而言,网络层对它们而言是透明的,它们知道传输层的存在,也就是说,在逻辑上它们认为是传输层为它们提供了端对端的通信,这也是分层思想的妙处。 23 | 24 | 5). 会话层(Session Layer) 25 | 26 | 会话层是OSI模型的第五层,是用户应用程序和网络之间的接口,负责在网络中的两节点之间建立、维持和终止通信。 27 | 28 | 6). 表示层(Presentation Layer):数据的编码,压缩和解压缩,数据的加密和解密 29 | 30 | 表示层是OSI模型的第六层,它对来自应用层的命令和数据进行解释,以确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。 31 | 32 | 7). 应用层(Application layer):为用户的应用进程提供网络通信服务。 -------------------------------------------------------------------------------- /网络/174.简述IP地址的分类.md: -------------------------------------------------------------------------------- 1 | #### 简述IP地址的分类 2 | 3 | 整个的因特网就是一个单一的、抽象的网络。IP 地址就是给因特网上的每一个主机(或路由器)的每一个接口分配一个在全世界范围是唯一的 32 位标识符,它是一个逻辑地址,用以屏蔽掉物理地址的差异。IP地址编址方案将IP地址空间划分为A、B、C、D、E五类,其中A、B、C是基本类,D、E类作为多播和保留使用,为特殊地址。 4 | 5 | 每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。A~E类地址的特点如下: 6 | 7 | - A类地址:以0开头,第一个字节范围:0~127; 8 | - B类地址:以10开头,第一个字节范围:128~191; 9 | - C类地址:以110开头,第一个字节范围:192~223; 10 | - D类地址:以1110开头,第一个字节范围为224~239; 11 | - E类地址:以1111开头,保留地址 12 | [![img](https://user-images.githubusercontent.com/20238022/71247877-9b6c1c00-2354-11ea-83d6-8d0c0887cea5.jpg)](https://user-images.githubusercontent.com/20238022/71247877-9b6c1c00-2354-11ea-83d6-8d0c0887cea5.jpg) 13 | 14 | **1). A类地址:1字节的网络地址 + 3字节主机地址,网络地址的最高位必须是“0”** 15 | 16 | 一个A类IP地址是指, 在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码。如果用二进制表示IP地址的话,A类IP地址就由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”。A类IP地址中网络的标识长度为8位,主机标识的长度为24位,A类网络地址数量较少,有126个网络,每个网络可以容纳主机数达1600多万台。 17 | 18 | A类IP地址的地址范围1.0.0.0到127.255.255.255(二进制表示为:00000001 00000000 00000000 00000000 - 01111110 11111111 11111111 11111111),最后一个是广播地址。A类IP地址的子网掩码为255.0.0.0,每个网络支持的最大主机数为256的3次方-2=16777214台。 19 | 20 | **2). B类地址: 2字节的网络地址 + 2字节主机地址,网络地址的最高位必须是“10”** 21 | 22 | 一个B类IP地址是指,在IP地址的四段号码中,前两段号码为网络号码。如果用二进制表示IP地址的话,B类IP地址就由2字节的网络地址和2字节主机地址组成,网络地址的最高位必须是“10”。B类IP地址中网络的标识长度为16位,主机标识的长度为16位,B类网络地址适用于中等规模的网络,有16384个网络,每个网络所能容纳的计算机数为6万多台。 23 | 24 | B类IP地址地址范围128.0.0.0-191.255.255.255(二进制表示为:10000000 00000000 00000000 00000000—-10111111 11111111 11111111 11111111),最后一个是广播地址。B类IP地址的子网掩码为255.255.0.0,每个网络支持的最大主机数为256的2次方-2=65534台。 25 | 26 | **3). C类地址: 3字节的网络地址 + 1字节主机地址,网络地址的最高位必须是“110”** 27 | 28 | 一个C类IP地址是指,在IP地址的四段号码中,前三段号码为网络号码,剩下的一段号码为本地计算机的号码。如果用二进制表示IP地址的话,C类IP地址就由3字节的网络地址和1字节主机地址组成,网络地址的最高位必须是“110”。C类IP地址中网络的标识长度为24位,主机标识的长度为8位,C类网络地址数量较多,有209万余个网络。适用于小规模的局域网络,每个网络最多只能包含254台计算机。 29 | 30 | C类IP地址范围192.0.0.0-223.255.255.255(二进制表示为: 11000000 00000000 00000000 00000000 - 11011111 11111111 11111111 11111111)。C类IP地址的子网掩码为255.255.255.0,每个网络支持的最大主机数为256-2=254台。 31 | 32 | **4). D类地址:多播地址,用于1对多通信,最高位必须是“1110”** 33 | 34 |   D类IP地址在历史上被叫做多播地址(multicast address),即组播地址。在以太网中,多播地址命名了一组应该在这个网络中应用接收到一个分组的站点。多播地址的最高位必须是“1110”,范围从224.0.0.0到239.255.255.255。 35 | 36 | **5). E类地址:为保留地址,最高位必须是“1111”** -------------------------------------------------------------------------------- /网络/44.https 三次握手四次挥手.md: -------------------------------------------------------------------------------- 1 | #### https 三次握手四次挥手 2 | 3 | ##### 参考答案 4 | 5 | 三次握手: 6 | 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 7 | (1)第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 8 | (2)第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态; 9 | (3)第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据. 10 | [![图示](https://camo.githubusercontent.com/8cfd00a1f33571cd747e25d45d0fd7f42dabf552/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f3533333531382d303230616461313836336433363861352e6769663f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f3436332f666f726d61742f77656270)](https://camo.githubusercontent.com/8cfd00a1f33571cd747e25d45d0fd7f42dabf552/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f3533333531382d303230616461313836336433363861352e6769663f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f3436332f666f726d61742f77656270) 11 | 四次分手: 12 | (1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。 13 | (2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。 14 | (3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。 15 | (4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。 16 | [![图示](https://camo.githubusercontent.com/63dacfa1d0a1df900d7d5f25e9018edbb0d9c95d/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f3533333531382d326536323463663636313332623538392e6769663f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f3534392f666f726d61742f77656270)](https://camo.githubusercontent.com/63dacfa1d0a1df900d7d5f25e9018edbb0d9c95d/68747470733a2f2f75706c6f61642d696d616765732e6a69616e7368752e696f2f75706c6f61645f696d616765732f3533333531382d326536323463663636313332623538392e6769663f696d6167654d6f6772322f6175746f2d6f7269656e742f7374726970253743696d61676556696577322f322f772f3534392f666f726d61742f77656270) -------------------------------------------------------------------------------- /网络/67.简述 tcp 和 udp的区别.md: -------------------------------------------------------------------------------- 1 | #### 简述 tcp 和 udp的区别 2 | 3 | ##### 参考答案 4 | 5 | tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输,而 udp 则常被用于让广播和细节控制交给应用的通信传输。 6 | 7 | 两者的区别大致如下: 8 | 9 | - tcp 面向连接,udp 面向非连接即发送数据前不需要建立链接; 10 | - tcp 提供可靠的服务(数据传输),udp 无法保证; 11 | - tcp 面向字节流,udp 面向报文; 12 | - tcp 数据传输慢,udp 数据传输快; -------------------------------------------------------------------------------- /网络/68.Android与服务器交互的方式中的对称加密和非对称加密是什么.md: -------------------------------------------------------------------------------- 1 | #### Android与服务器交互的方式中的对称加密和非对称加密是什么 2 | 3 | 对称加密,就是加密和解密数据都是使用同一个key,这方面的算法有DES。 4 | 5 | 非对称加密,加密和解密是使用不同的key。发送数据之前要先和服务端约定生成公钥和私钥,使用公钥加密的数据可以用私钥解密,反之。这方面的算法有RSA。ssh 和 ssl都是典型的非对称加密。 -------------------------------------------------------------------------------- /网络/69.http与https的区别.md: -------------------------------------------------------------------------------- 1 | #### http与https的区别 2 | 3 | HTTP:超文本传输协议(HyperText Transfer Protocol),是目前互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。现在主要是一个客户端和服务器端请求和应答的标准(TCP),但是正在被HTTPS取代。 4 | 5 | HTTPS:安全套接字层超文本传输协议(Hyper Text Transfer Protocol over Secure Socket Layer)或超文本传输安全协议(Hypertext Transfer Protocol Secure),由网景公司(Netscape)于1994年创建的,是以安全为目标的HTTP通道。简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 6 | 7 | 主要区别: 8 | 9 | 1. URL:http 一般以http:// 开头,https以https:// 开头。 10 | 2. 证书:http不需要证书;https需要到ca申请证书,一般免费证书很少,所以需要一定费用。 11 | 3. 安全:http信息是明文传输;https 则是具有安全性的ssl加密传输协议。 12 | 4. 端口:http 默认端口是80;https是443。 13 | 5. 连接:http 的连接很简单,是无状态的;https协议是由SSL+http协议构建的可进行加密传输、身份认证的网络协议,所以首次建立连接会慢一些,但是比http协议安全。 14 | 15 | 附:https优缺点: 16 | 优点: 17 | 18 | 1. 通过证书可以更信任服务器。 19 | 2. 更安全,防篡改。 20 | 21 | 缺点: 22 | 23 | 1. https 需要证书。 24 | 2. 因为对传输进行加密,会一定程度增加cpu消耗。 25 | 3. 由于https 要还密钥和确认加密算法的需要,所以首次建立连接会慢一些。 26 | 4. 带宽消耗会增加,服务端压力大。 27 | 28 | 进阶Http:https://mp.weixin.qq.com/s/3TaonTzAsqqLLbJ-yrwNdw 29 | 进阶Https:https://mp.weixin.qq.com/s/lCr7NuQNLQh4Ake8yXorxg?scene=25#wechat_redirect -------------------------------------------------------------------------------- /设计模式/11.设计模式的基本原则.md: -------------------------------------------------------------------------------- 1 | #### 设计模式的基本原则 2 | 3 | ##### 参考答案 4 | 1. **单一职责原则(Single Responsibility Principle)** 5 | 6 | 单一职责原则表示一个模块的组成元素之间的功能相关性。从软件变化的角度来看,就一个类而言,应该仅有一个让它变化的原因;通俗地说,即一个类只负责一项职责。 7 | 8 | - SRP 是一个简单又直观的原则,但是在实际编码的过程中很难将它恰当地运用,需要结合实际情况进行运用。 9 | - 单一职责原则可以降低类的复杂度,一个类仅负责一项职责,其逻辑肯定要比负责多项职责简单。 10 | - 提高了代码的可读性,提高系统的可维护性。 11 | 12 | 2. **开放-关闭原则(Open-Closed Principle)** 13 | 14 | 开放-关闭原则表示软件实体 (类、模块、函数等等) 应该是可以被扩展的,但是不可被修改。 15 | 16 | 如果一个软件能够满足 OCP 原则,那么它将有两项优点: 17 | 18 | - 能够扩展已存在的系统,能够提供新的功能满足新的需求,因此该软件有着很强的适应性和灵活性。 19 | - 已存在的模块,特别是那些重要的抽象模块,不需要被修改,那么该软件就有很强的稳定性和持久性。 20 | 21 | 3. **里氏替换原则(Liskov Substitution Principle)** 22 | 23 | 将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。即子类可以扩展父类的功能,但不能改变父类原有的功能 24 | 25 | 4. **依赖倒转原则(Dependence Inversion Principle)** 26 | 27 | 高层模块不应该依赖低层模块,二者都应该于抽象。进一步说,抽象不应该依赖于细节,细节应该依赖于抽象。遵循依赖倒转原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。依赖倒转原则的核心就是要我们面向接口编程 28 | 29 | 5. **接口隔离原则(Interface Segregation Principle)** 30 | 31 | 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上 32 | 33 | - 接口隔离原则的思想在于建立单一接口,尽可能地去细化接口,接口中的方法尽可能少 34 | - 但是凡事都要有个度,如果接口设计过小,则会造成接口数量过多,使设计复杂化。所以一定要适度 35 | 36 | 6. **迪米特法则(Law Of Demeter)** 37 | 38 | 迪米特法则又称为 最少知道原则,它表示一个对象应该对其它对象保持最少的了解。通俗来说就是,只与直接的朋友通信。 39 | 40 | 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。 41 | 42 | 对于被依赖的类来说,无论逻辑多么复杂,都尽量的将逻辑封装在类的内部,对外提供 public 方法,不对泄漏任何信息。 43 | 44 | 7. **组合/聚合复用原则(Composite/Aggregate Reuse Principle)** 45 | 46 | 组合/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分; 新的对象通过向这些对象的委派达到复用已有功能的目的。 47 | 48 | 在面向对象的设计中,如果直接继承基类,会破坏封装,因为继承将基类的实现细节暴露给子类;如果基类的实现发生了改变,则子类的实现也不得不改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性。于是就提出了组合/聚合复用原则,也就是在实际开发设计中,尽量使用组合/聚合,不要使用类继承。 49 | 50 | - 总体说来,组合/聚合复用原则告诉我们:组合或者聚合好过于继承 51 | - 聚合组合是一种 “黑箱” 复用,因为细节对象的内容对客户端来说是不可见的 52 | 53 | 54 | 55 | ##### 补充答案 56 | 57 | ###### From jiwenjie 58 | 1、单一职责原则 59 | 60 | 核心思想:应该有且仅有一个原因引起类的变更 61 | 62 | 问题描述:假如有类Class1完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另外一个职责正常工作。 63 | 64 | 好处:类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险。 65 | 66 | 需注意:单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目和环境而异。 67 | 68 | 2、里氏替换原则 69 | 70 | 核心思想:在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类。 71 | 72 | 通俗来讲:只要父类能出现的地方子类就能出现。反之,父类则未必能胜任。 73 | 74 | 好处:增强程序的健壮性,即使增加了子类,原有的子类还可以继续运行。 75 | 76 | 需注意:如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系 采用依赖、聚合、组合等关系代替继承。 77 | 78 | 3、依赖倒置原则 79 | 80 | 核心思想:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象; 81 | 82 | 说明:高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。 83 | 84 | 通俗来讲:依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。 85 | 86 | 问题描述:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。 87 | 88 | 解决方案:将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。 89 | 90 | 好处:依赖倒置的好处在小型项目中很难体现出来。但在大中型项目中可以减少需求变化引起的工作量。使并行开发更友好。 91 | 92 | 4、接口隔离原则 93 | 94 | 核心思想:类间的依赖关系应该建立在最小的接口上 95 | 96 | 通俗来讲:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。 97 | 98 | 问题描述:类A通过接口interface依赖类B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。 99 | 需注意: 100 | 101 | 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度 102 | 103 | 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情 104 | 105 | 为依赖接口的类定制服务。只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。 106 | 107 | 5、迪米特法则 108 | 109 | 核心思想:类间解耦。 110 | 111 | 通俗来讲: 一个类对自己依赖的类知道的越少越好。自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。 112 | 113 | 6、开放封闭原则 114 | 115 | 核心思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化 116 | 117 | 通俗来讲: 一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。 118 | 119 | 总结:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。 120 | -------------------------------------------------------------------------------- /设计模式/36谈谈对「抽象工厂方法模式」的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对抽象工厂方法模式的理解 2 | 3 | 抽象工厂模式,即Abstract Factory Pattern,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。 4 | 5 | 抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个工厂可以创建多种类的产品;而工厂方法每个工厂只能创建一类 6 | 7 | 1. 主要作用 8 | 9 | 允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦。 10 | 11 | 1. 解决的问题 12 | 13 | 每个工厂只能创建一类产品 14 | 15 | 即工厂方法模式的缺点 16 | 17 | - 背景:小成有两间塑料加工厂(A厂仅生产容器类产品;B厂仅生产模具类产品);随着客户需求的变化,A厂所在地的客户需要也模具类产品,B厂所在地的客户也需要容器类产品; 18 | - 冲突:没有资源(资金+租位)在当地分别开设多一家注塑分厂 19 | - 解决方案:在原有的两家塑料厂里增设生产需求的功能,即A厂能生产容器+模具产品;B厂间能生产模具+容器产品。 20 | 21 | 即抽象工厂模式 22 | 23 | 步骤1: 创建抽象工厂类,定义具体工厂的公共接口 24 | 25 | ```java 26 | abstract class Factory { 27 | public abstract Product ManufactureContainer(); 28 | public abstract Product ManufactureMould(); 29 | } 30 | ``` 31 | 32 | 步骤2: 创建抽象产品族类 ,定义具体产品的公共接口; 33 | 34 | ```java 35 | abstract class AbstractProduct { 36 | public abstract void Show(); 37 | } 38 | ``` 39 | 40 | 步骤3: 创建抽象产品类 ,定义具体产品的公共接口; 41 | 42 | ```java 43 | //容器产品抽象类 44 | abstract class ContainerProduct extends AbstractProduct { 45 | @Override 46 | public abstract void Show(); 47 | } 48 | 49 | //模具产品抽象类 50 | abstract class MouldProduct extends AbstractProduct { 51 | @Override 52 | public abstract void Show(); 53 | } 54 | ``` 55 | 56 | 步骤4: 创建具体产品类(继承抽象产品类), 定义生产的具体产品; 57 | 58 | ```java 59 | //容器产品A类 60 | class ContainerProductA extends ContainerProduct{ 61 | @Override 62 | public void Show() { 63 | System.out.println("生产出了容器产品A"); 64 | } 65 | } 66 | 67 | //容器产品B类 68 | class ContainerProductB extends ContainerProduct{ 69 | @Override 70 | public void Show() { 71 | System.out.println("生产出了容器产品B"); 72 | } 73 | } 74 | 75 | //模具产品A类 76 | class MouldProductA extends MouldProduct{ 77 | 78 | @Override 79 | public void Show() { 80 | System.out.println("生产出了模具产品A"); 81 | } 82 | } 83 | 84 | //模具产品B类 85 | class MouldProductB extends MouldProduct{ 86 | 87 | @Override 88 | public void Show() { 89 | System.out.println("生产出了模具产品B"); 90 | } 91 | } 92 | ``` 93 | 94 | 步骤5:创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法; 95 | 96 | ```java 97 | //A厂 - 生产模具+容器产品 98 | class FactoryA extends Factory { 99 | @Override 100 | public Product ManufactureContainer() { 101 | return new ContainerProductA(); 102 | } 103 | 104 | @Override 105 | public Product ManufactureMould() { 106 | return new MouldProductA(); 107 | } 108 | } 109 | 110 | //B厂 - 生产模具+容器产品 111 | class FactoryB extends Factory { 112 | @Override 113 | public Product ManufactureContainer() { 114 | return new ContainerProductB(); 115 | } 116 | 117 | @Override 118 | public Product ManufactureMould() { 119 | return new MouldProductB(); 120 | } 121 | } 122 | ``` 123 | 124 | 步骤6:客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法创建不同具体产品类的实例 125 | 126 | ```java 127 | //生产工作流程 128 | public class AbstractFactoryPattern { 129 | public static void main(String[] args){ 130 | FactoryA mFactoryA = new FactoryA(); 131 | FactoryB mFactoryB = new FactoryB(); 132 | //A厂当地客户需要容器产品A 133 | mFactoryA.ManufactureContainer().Show(); 134 | //A厂当地客户需要模具产品A 135 | mFactoryA.ManufactureMould().Show(); 136 | 137 | //B厂当地客户需要容器产品B 138 | mFactoryB.ManufactureContainer().Show(); 139 | //B厂当地客户需要模具产品B 140 | mFactoryB.ManufactureMould().Show(); 141 | } 142 | } 143 | ``` 144 | 145 | 结果: 146 | 147 | ```java 148 | 生产出了容器产品A 149 | 生产出了容器产品A 150 | 生产出了模具产品B 151 | 生产出了模具产品B 152 | ``` 153 | 154 | 最后补充下三种工厂模式的区别 155 | 156 | - 简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。 157 | - 工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。 158 | - 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。 -------------------------------------------------------------------------------- /设计模式/43.谈谈对责任链模式的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对责任链模式的理解 2 | 3 | ##### 参考答案 4 | 5 | > 使很多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 6 | 7 | 何时使用: 8 | 9 | 1. 有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。 10 | 2. 希望用户不必明确指定接收者的情况下,想多个接受者的一个提交请求 11 | 3. 程序希望动态的指定可处理用户请求的对象集合 12 | 13 | 优点: 14 | 15 | 1. 低耦合 16 | 2. 可以动态的添加删除处理者或重新指派处理者的职责 17 | 3. 可以动态改变处理者之间的先后顺序 18 | 19 | 通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。 20 | 21 | 比如我们有一个数学公式,有一个整数输入,要求小于0时返回绝对值,其次,小于10的时候返回他的二次幂,否则,返回他本身: 22 | 23 | 首先我们要定义一个接口(**处理者**),来描述他们共有的行为: 24 | 25 | ```java 26 | public interface Handler { 27 | int handleRequest(int n); 28 | void setNextHandler(Handler next); 29 | } 30 | ``` 31 | 32 | 然后是**具体的处理者** 33 | 34 | ```java 35 | public class Handler1 implements Handler { 36 | private Handler next; 37 | @Override 38 | public int handleRequest(int n) { 39 | if(n<0) return -n; 40 | else{ 41 | if(next==null) 42 | throw new NullPointerException("next 不能为空"); 43 | return next.handleRequest(n); 44 | } 45 | } 46 | 47 | @Override 48 | public void setNextHandler(Handler next) { 49 | this.next = next; 50 | } 51 | } 52 | ``` 53 | 54 | ```java 55 | public class Handler2 implements Handler { 56 | private Handler next; 57 | @Override 58 | public int handleRequest(int n) { 59 | if(n<10) return n*n; 60 | else{ 61 | if(next==null) 62 | throw new NullPointerException("next 不能为空"); 63 | return next.handleRequest(n); 64 | } 65 | } 66 | 67 | @Override 68 | public void setNextHandler(Handler next) { 69 | this.next = next; 70 | } 71 | } 72 | ``` 73 | 74 | ```java 75 | public class Handler3 implements Handler { 76 | private Handler next; 77 | @Override 78 | public int handleRequest(int n) { 79 | if(n<=Integer.MAX_VALUE) return n; 80 | else{ 81 | if(next==null) 82 | throw new NullPointerException("next 不能为空"); 83 | return next.handleRequest(n); 84 | } 85 | } 86 | 87 | @Override 88 | public void setNextHandler(Handler next) { 89 | this.next = next; 90 | } 91 | } 92 | ``` 93 | 94 | 最后使用 95 | 96 | ```java 97 | public class TestUse { 98 | public static void main(String args[]){ 99 | Handler h1,h2,h3; 100 | h1 = new Handler1(); 101 | h2 = new Handler2(); 102 | h3 = new Handler3(); 103 | h1.setNextHandler(h2); 104 | h2.setNextHandler(h3); 105 | System.out.println(h1.handleRequest(-1)); 106 | System.out.println(h1.handleRequest(5)); 107 | System.out.println(h1.handleRequest(9999)); 108 | } 109 | } 110 | ``` 111 | 112 | 此处责任链中的具体处理者的顺序是不能重设的,否则可能会引发错误,但更多的情况是完全可以随意更改他们的位置的,就上例中,只要把if中的条件重新设置(各自独立,不相互依赖),就可以了。 113 | 114 | 我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。 115 | 116 | 117 | 118 | ##### 蛋友补充 119 | 120 | ###### From [Taonce](https://github.com/Taonce) 121 | 122 | 责任链模式就是:转移责任,比如:董事需要做一件事,找到总监,总监找到部门组长,组长再找到组员,这些人就像在一条链子上,任务在这条链子上传递。 123 | Android中用到责任链的地方有:点击事件的分发(Activity - ViewGroup - View),Okhttp中拦截器的使用 -------------------------------------------------------------------------------- /设计模式/60.谈谈对生成器模式的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对生成器模式的理解 2 | 3 | ##### 参考答案 4 | 5 | 生成器模式:将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。 6 | 7 | - 何时使用 8 | 1. 当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式老构造这样的对象。 9 | 2. 当某些系统要求对象的构造过程必须独立于创建该对象的类时。 10 | - 优点 11 | 1. 生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。 12 | 2. 生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。 13 | 3. 可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。 14 | 4. 生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。 15 | 5. 当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。 16 | 17 | 模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。 18 | 19 | 比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。 20 | 21 | 首先是这个类(产品): 22 | 23 | ```java 24 | //产品 25 | public class MyDate { 26 | String date; 27 | } 28 | ``` 29 | 30 | 然后就是抽象生成器,描述生成器的行为: 31 | 32 | ```java 33 | //抽象生成器 34 | public interface IDateBuilder { 35 | IDateBuilder buildDate(int y,int m,int d); 36 | String date(); 37 | } 38 | ``` 39 | 40 | 接下来是具体生成器,一个以“-”分割年月日,另一个使用空格: 41 | 42 | ```java 43 | /具体生成器 44 | public class DateBuilder1 implements IDateBuilder{ 45 | private MyDate myDate; 46 | public DateBuilder1(MyDate myDate){ 47 | this.myDate = myDate; 48 | } 49 | @Override 50 | public IDateBuilder buildDate(int y, int m, int d) { 51 | myDate.date = y+"-"+m+"-"+d; 52 | return this; 53 | } 54 | @Override 55 | public String date() { 56 | return myDate.date; 57 | } 58 | } 59 | ``` 60 | 61 | ```java 62 | //具体生成器 63 | public class DateBuilder2 implements IDateBuilder{ 64 | private MyDate myDate; 65 | public DateBuilder2(MyDate myDate){ 66 | this.myDate = myDate; 67 | } 68 | @Override 69 | public IDateBuilder buildDate(int y, int m, int d) { 70 | myDate.date = y+" "+m+" "+d; 71 | return this; 72 | } 73 | @Override 74 | public String date() { 75 | return myDate.date; 76 | } 77 | } 78 | ``` 79 | 80 | 接下来是指挥官,向用户提供具体的生成器: 81 | 82 | ```java 83 | //指挥者 84 | public class Derector { 85 | private IDateBuilder builder; 86 | public Derector(IDateBuilder builder){ 87 | this.builder = builder; 88 | } 89 | public String getDate(int y,int m,int d){ 90 | builder.buildDate(y, m, d); 91 | return builder.date(); 92 | } 93 | } 94 | ``` 95 | 96 | 最后使用 97 | 98 | ```java 99 | public class MainGenerate { 100 | public static void main(String args[]){ 101 | MyDate date = new MyDate(); 102 | IDateBuilder builder; 103 | builder = new DateBuilder1(date).buildDate(2066, 3, 5); 104 | System.out.println(builder.date()); 105 | builder = new DateBuilder2(date).buildDate(2066, 3, 5); 106 | System.out.println(builder.date()); 107 | } 108 | } 109 | ``` 110 | 111 | 使用不同生成器,可以使原有产品表现得有点不一样。 -------------------------------------------------------------------------------- /设计模式/66.谈谈对命令模式的理解.md: -------------------------------------------------------------------------------- 1 | #### 谈谈对命令模式的理解 2 | 3 | ##### 参考答案 4 | 5 | 将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 6 | 7 | 何时使用: 8 | - 程序需要在不同的时刻指定、排列和执行请求。 9 | - 程序需要提供撤销操作。 10 | - 程序需要支持宏操作。 11 | 12 | 优点: 13 | - 在命令模式中,请求者(Invoker)不直接与接受者(Receiver)交互,及请求者(Invoker)不包含接受者(Receiver)的引用,因此彻底消除了彼此间的耦合。 14 | - 命令模式满足“开-闭原则”。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有具体命令和接收者,新增加的调用者就可以使用已有的具体命令。 15 | - 由于请求者的请求被封装到具体的命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。 16 | - 使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按一定顺序执行这些具体命令。 17 | 18 | 一个对象有多种操作,但是我们不希望调用者(请求者)直接使用,我们就额外添加一个对象,然后让调用者通过这个对象来使用那些操作。比如,我们有一个类可以在磁盘上新建或是删除文件(接收者),但是我们不希望直接提供给别人(请求者)使用,所以我们就为它的各种操作创建对应的命令 19 | 20 | ```java 21 | //接收者 22 | public class MakeFile { 23 | //新建文件 24 | public void createFile(String name) throws IOException{ 25 | File file = new File(name); 26 | file.createNewFile(); 27 | } 28 | //删除文件 29 | public boolean deleteFile(String name){ 30 | File file = new File(name); 31 | if(file.exists()&&file.isFile()){ 32 | file.delete(); 33 | return true; 34 | } 35 | return false; 36 | } 37 | } 38 | ``` 39 | 40 | ```java 41 | //命令接口 42 | public interface Command { 43 | void execute(String name) throws Exception; 44 | } 45 | ``` 46 | 47 | ```java 48 | //新建文件命令 49 | public class CommandCreate implements Command { 50 | MakeFile makeFile; 51 | public CommandCreate(MakeFile makeFile) { 52 | this.makeFile = makeFile; 53 | } 54 | @Override 55 | public void execute(String name) throws Exception { 56 | makeFile.createFile(name); 57 | } 58 | } 59 | ``` 60 | 61 | ```java 62 | //删文件命令 63 | public class CommandDelete implements Command{ 64 | MakeFile makeFile; 65 | public CommandDelete(MakeFile makeFile) { 66 | this.makeFile = makeFile; 67 | } 68 | @Override 69 | public void execute(String name) throws Exception { 70 | makeFile.deleteFile(name); 71 | } 72 | } 73 | ``` 74 | 75 | ```java 76 | //请求者 77 | public class Client { 78 | Command command; 79 | public Client setCommand(Command command){ 80 | this.command = command; 81 | return this; 82 | } 83 | public void executeCommand(String name) throws Exception{ 84 | if(command==null) 85 | throw new Exception("命令不能为空!"); 86 | command.execute(name); 87 | } 88 | } 89 | ``` 90 | 91 | ```java 92 | public class Test { 93 | public static void main(String args[]) throws Exception{ 94 | //接收者 95 | MakeFile makeFile = new MakeFile(); 96 | //命令 97 | CommandCreate create = new CommandCreate(makeFile); 98 | CommandDelete delete = new CommandDelete(makeFile); 99 | //请求者 100 | Client client = new Client(); 101 | //执行命令 102 | client.setCommand(create).executeCommand("d://test1.txt"); 103 | client.setCommand(create).executeCommand("d://test2.txt"); 104 | client.setCommand(delete).executeCommand("d://test2.txt"); 105 | } 106 | }//执行完后在D盘会有一个test1.txt的文件,test2.txt本页创建了,不过又被删除了。。 107 | ``` 108 | 109 | 命令模式不宜滥用,比如:使用这种模式,会多出来很多对象(命令)。 --------------------------------------------------------------------------------