常见面试题
1. 1元现金分10个红包 微信 算法实现
1.计算时间 微信金额是拆的时候实时算出来,不是预先分配的,采用的是纯内存计算,不需要预算空间存储。 采取实时计算金额的考虑:预算需要占存储,实时效率很高,预算才效率低。
2.分配算法 随机产生,额度在0.01-(剩余平均值 * 2)之间。 假如发1块钱,总共10个红包,均值是0.1元一个,那么发出来的额度在0.01~0.2之间波动,当前面4个包被领取0.7元时,剩下0.3,总共6个包,那么发出来的红包额度在0.01~0.1之间波动,这样算下去,会超过最开始的全部金额,因此到了后面如果不够这么算,会采取算法,保证剩余用户至少能拿到1分钱,如果前面的人手气不好,后面的余额越多,红包额度也就越多,因此实际概率一样的。
3.每一个红包的概率不是绝对均等,就是一个简单的拍脑袋算法,有可能出现金额一样的,但是手气最佳只有一个,先抢到的为手气最佳。
4.代码简易实现:
//最后一个人之前的红包总额
public class RedPacketInfo {
    //剩余红包数目
    public int remainNum;
    //剩余金额
    public double remainMoney;
    //红包总额
    public double moneySum;
    //抢红包人数
    public int redNum;
}
private double previousSum;
/**
*
* @param index 第几个人
* @param info  红包info
* @return
*/
public synchronized String getRandomMoney(int index,RedPacketInfo info){
    //保留两位小数
    DecimalFormat df = new DecimalFormat("0.00");
    //最后一个红包应该是红包总额减去前面所有红包总额
    if(info.remainNum == 1){
        previousSum = Double.valueOf(df.format(previousSum));
        System.out.println("previousSum: "+previousSum);
        info.remainNum--;
        String money = df.format(info.moneySum - previousSum);
        return Double.valueOf(money) <=0 ? "0.01" : money;
    }
    Random r = new Random();
    //红包最小金额
    double min = 0.01;
    //红包最大金额
    double max = info.remainMoney / info.remainNum * 2;
    //计算红包大小
    double money = r.nextDouble() * max;
    money = money <= min ? 0.01 : money;
    // money = Math.floor(money * 100) / 100;
    info.remainNum --;
    info.remainMoney -= money;
    if(index < info.redNum){
        previousSum += Double.valueOf(df.format(money));
    }
    return df.format(money);
}
@Test
public void test(){
    RedPacketInfo info = new RedPacketInfo();
    //初始化
    info.remainNum = 30;
    info.remainMoney = 200;
    info.moneySum = 200;
    info.redNum = 30;
    //计算后的红包总额
    double sum = 0;
    for (int i = 1; i <= info.redNum; i++) {
        String randomMoney = getRandomMoney(i,info);
        System.out.println("第 "+i + "人抢到的红包金额:"+randomMoney);
        sum+=Double.valueOf(randomMoney);
    }
    System.out.println("红包总额:"+Math.round(sum));
}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
2. handler机制
    1. handler,Looper,MessageQueue,Message知识点总结:
         1).在主线程中可以直接创建Handler对象,因为在ActivityThread类的main方法调用了Looper.prepareMainLooper()方法,
            在子线程中创建Handler对象前需要先调用Looper.preare()方法创建Looper对象,在Handler的构造方法中会调用
            Looper.myLooper()方法获取Looper对象,如果Looper对象为空,就会抛出异常;
         2).每一个线程中最多只能有一个Looper对象,否则抛异常,可以通过Looper.myLooper()获取当前线程的Looper实例,
            通过Looper.getMainLooper()获取主(UI)线程的Looper实例。
         3).一个线程中只能有一个Looper对象和一个消息队列MessageQueue对象,但是可以有多个Handler对象;
         4).Message:Handler接收和处理消息的对象。
         5).Looper:它的loop方法负责读取消息队列MessageQueue中的消息,读到消息后把消息发送给Handler进行处理。
         6).MessageQueue:消息队列,它采用先进先出的方式来管理Message,程序在调用Looper.prepare()创建Looper对象的时候,
         会在Looper的构造方法中同时创建消息队列MessageQueue对象,一个Looper只能对应一个消息队列MessageQueue对象。
         7).MessageQueue主要包含了两种操作,插入和读取,而读取操作本身也会伴随着删除操作,插入和读取对应的分别是enqueueMessage和next。
         8).Handler:它的作用有两个分别是发送消息和处理消息。
         9).Looper对象负责管理MessageQueue,而MessageQueue主要是用来存放handler发送的消息
         10).Looper.loop():启动looper中的循环线程,Handler就会从消息队列里取消息并进行对应处理。要注意的是写在Looper.loop()之后的代码不会被执行,
         这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop()才会中止,其后的代码才能得以运行。
         11).子线程中使用Handler发送消息处理消息
            1. 子线程中必须先创建Looper
            Looper.prepare();
            2. 创建Handler对象,复写handlerMessage方法处理消息10
            3. 启动looper循环
            Looper.loop();
         12).主线程中直接可以创建Handler对象,因为在ActivityThread类的main方法中调用了Looper.prepareMainLooper()方法,
         启动了looper循环Looper.loop(),这样就能很方便的进行消息发送和接受处理了。
         13)Looper的quit方法和quitSafely方法有什么区别,可以看出实际上是调用的MessageQueue的quit方法
             public void quit() {
                mQueue.quit(false);
             }
             public void quitSafely() {
                mQueue.quit(true);
             }
             无论是调用了quit方法还是quitSafely方法,MessageQueue将不再接收新的Message,此时消息循环就结束,MessageQueued的next方法将返回null,结束loop()的死循环.
             这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
     2. Handler,Looper,MessageQueue消息处理流程
        1).当我们使用handler调用sendEmptyMessage(int)发送消息时,Handler内部会去调用enqueueMessage(MessageQueue queue,Message msg)方法
        把发送的消息添加到消息队列MessageQueue中,在这个方法中同时还会设置msg.target=this此时就把当前handler对象绑定到msg.target中了,
        这样就完成了Handler向消息队列存放消息的过程
            // 最后调用此方法添加到消息队列中
            private boolean enqueueMessage(MessageQueue queue, Message msg,
            long uptimeMillis) {
                msg.target = this;// 设置发送目标对象是Handler本身
                if (mAsynchronous) {
                    msg.setAsynchronous(true);
                }
                return queue.enqueueMessage(msg, uptimeMillis);// 添加到消息队列中
            }
      2).Looper取消息和分发消息的主要逻辑在loop方法里,通过loop()方法内部源码我们可以知道,首先会通过myLoooper()去获取一个Looper对象,
      如果Looper对象为null,就会报出一个我们非常熟悉的错误提示,“No Looper;Looper.prepare() wasn't called on this thread”,
      要求我们先通过Looper.prepare()方法去创建Looper对象;如果Looper不为null,那么就会去获取消息队列MessageQueue对象,
      接着就进入一个for的死循环,不断从消息队列MessageQueue对象中获取消息,如果消息不为空,那么就会调用
      msg.target的dispatchMessage(Message)方法去处理消息,那么这个target又是什么,没错target就是我们创建的Handler对象。
            //looper中最重要的方法loop(),该方法是个死循环,
            //会不断去消息队列MessageQueue中获取消息,
            //然后调dispatchMessage(msg)方法去执行
            public static void loop() {
                final Looper me = myLooper();
                //保证当前线程必须有Looper对象,如果没有则抛出异常,调用Looper.loop()之前应该先调用Looper.prepare().
                if (me == null) {
                    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
                }
                //Looper需要不断从MessageQueue中取出消息,所以它持有MessageQueue对象
                final MessageQueue queue = me.mQueue;
                // Make sure the identity of this thread is that of the local process,
                // and keep track of what that identity token actually is.
                Binder.clearCallingIdentity();
                final long ident = Binder.clearCallingIdentity();
                for (;;) {
                    //这里开始执行死循环,queue通过调用next方法来取出下一个消息。
                    //很多人很疑惑死循环不会相当耗费性能吗,如果没有那么多消息怎么办?
                    //其实当没有消息的时候,next方法会阻塞在这里,不会往下执行了,性能问题不存在。
                    Message msg = queue.next(); // might block
                    if (msg == null) {
                        // No message indicates that the message queue is quitting.
                        //这里满足了死循环跳出的条件,即取出的消息为null
                        //没有消息next不是会阻塞吗,怎么会返回null呢?
                        //其实只有MessageQueue停止的时候(调用quit方法),才会返回null
                        //MessageQueue停止后,调用next返回null,且不再接受新消息,下面还有详细介绍。
                        return;
                    }
                    // This must be in a local variable, in case a UI event sets the logger
                    Printer logging = me.mLogging;
                    if (logging != null) {
                        logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
                    }
                    //这里的msg.target是Handler对象,分发消息到Handler去执行。
                    //有人问主线程可以创建这么多Handler,怎么保证这个Handler发送的消息不会跑到其它Handler去执行呢?
                    //那是因为在发送Message时,他会绑定发送的Handler,在此处分发消息时,也只会回调发送该条消息的Handler。
                    //那么分发消息具体在哪个线程执行呢?
                    //我觉得这个不该问,那当然是当前方法在哪个线程调用就在哪个线程执行啦。
                    msg.target.dispatchMessage(msg);
                    if (logging != null) {
                        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    }
                    // Make sure that during the course of dispatching the
                    // identity of the thread wasn't corrupted.
                    final long newIdent = Binder.clearCallingIdentity();
                    if (ident != newIdent) {
                        Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
                    }
                    //这里对Message对象进行回收,会清空所有之前Message设置的数据。
                    //正是因为Message有回收机制,我们在创建消息的时候应该优先选择Message.obtain().
                    //如果发送的消息足够多,Message缓存的Message对象不够了,obtain内部会调用new Message()创建一个新的对象。
                    msg.recycleUnchecked();
                }
            }
        3). 在dispatchMessage方法中的逻辑比较简单,具体就是如果mCallback不为空,则调用mCallback的handleMessage()方法,
        否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。
            /**
            * Handle system messages here.
            */
            public void dispatchMessage(Message msg) {
                if (msg.callback != null) {
                    handleCallback(msg);
                } else {
                    if (mCallback != null) {
                        if (mCallback.handleMessage(msg)) {
                            return;
                        }
                    }
                    handleMessage(msg);
                }
            }
        4).因为我们的Handler是在主线程创建的,也就是说Looper也是主线程的Looper,因此handleMessage内部处理最终都会在主线程上执行,
        就这样整个流程都执行完了。
        5)ActivityThread的main源码
            public final class ActivityThread {
            ...
                public static void main(String[] args) {
                    SamplingProfilerIntegration.start();
                    // CloseGuard defaults to true and can be quite spammy.  We
                    // disable it here, but selectively enable it later (via
                    // StrictMode) on debug builds, but using DropBox, not logs.
                    CloseGuard.setEnabled(false);
                    Environment.initForCurrentUser();
                    // Set the reporter for event logging in libcore
                    EventLogger.setReporter(new EventLoggingReporter());
                    Security.addProvider(new AndroidKeyStoreProvider());
                    // Make sure TrustedCertificateStore looks in the right place for CA certificates
                    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
                    TrustedCertificateStore.setDefaultUserDirectory(configDir);
                    Process.setArgV0("<pre-initialized>");
                    //注意这里,这里创建了主线程的Looper
                    Looper.prepareMainLooper();
                    ActivityThread thread = new ActivityThread();
                    thread.attach(false);
                    if (sMainThreadHandler == null) {
                        sMainThreadHandler = thread.getHandler();
                    }
                    AsyncTask.init();
                    if (false) {
                        Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
                    }
                    //开启消息循环
                    Looper.loop();
                    throw new RuntimeException("Main thread loop unexpectedly exited");
                }
            }
    3.handler内存泄漏处理
        方案一:通过程序逻辑来进行保护
            1. 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
            2. 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
        方案二:将Handler声明为静态类
            静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:
            static class TestHandler extends Handler {
                WeakReference<Activity > mActivityReference;
                TestHandler(Activity activity) {
                    mActivityReference= new WeakReference<Activity>(activity);
                }
                @Override
                public void handleMessage(Message msg) {
                    final Activity activity = mActivityReference.get();
                    if (activity != null) {
                        mImageView.setImageBitmap(mBitmap);
                    }
                }
            }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
3. 事件分发机制
    1、如果事件不被中断,整个事件流向是一个类U型图
    2、dispatchTouchEvent 和 onTouchEvent方法return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
    3、dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
    ViewGroup 和View的这些方法的默认实现就是会让整个事件安装U型完整走完,所以 return super.xxxxxx() 就会让事件依照U型的方向的完整走完整个事件流动路径),
    中间不做任何改动,不回溯、不终止,每个环节都走到。
    4、onInterceptTouchEvent方法中 return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。默认是不会去拦截的,
    因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,
    事件会继续往子View的dispatchTouchEvent传递。
    5、ViewGroup 的dispatchTouchEvent,之前说的return true是终结传递。return false 是回溯到父View的onTouchEvent,
    然后ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢,return true和false 都不行,那么只能通过Interceptor
    把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent,记住这一点。
    6、View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
    7、ViewGroup和View 的dispatchTouchEvent是做事件分发,那么这个事件可能分发出去的四个目标
        1)、自己消费,终结传递。------->return true ;
        2)、给自己的onTouchEvent处理------->调用super.dispatchTouchEvent()系统默认会去调用onInterceptTouchEvent,
        在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
        3)、传给子View------>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子View。
        4)、不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent------->return false;
    8、ViewGroup和View的onTouchEvent方法是做事件处理的,那么这个事件只能有两个处理方式:
        1)、自己消费掉,事件终结,不再传给谁----->return true;
        2)、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;
    9、ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:
        1)、拦截下来,给自己的onTouchEvent处理--->return true;
        2)、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;【
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
事件分发机制
4.view绘制流程
    1. Measure过程
    MeasureSpec是一个大小跟模式的组合值,MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,
    其中高两位是mode,后面30位存的是size,是为了减少对象的分配开支。
    MeasureSpec一共有三种模式
            UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大
            EXACTLY: 父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值,
            它对应于LayoutParams 中的 match_parent 和具体的数值这两种模式
            AT_MOST:父容器指定了一个可用大小即 SpecSize,View 的大小不能大于这个值,具体是什么值要看不同 View 的具体实现。
            它对应于 LayoutParams 中的 wrap_content。
一、onMeasure测量
  1. 如果父View的MeasureSpec 是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果
  一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)
    1、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是确切,那么子View的大小肯定是确切的,
    而且大小值就是父View的size。所以子View的size=父View的size,mode=EXACTLY
    2、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content来决定的,子View的大小没有确切的值,
    子View的大小最大为父View的大小,不能超过父View的大小,子View MeasureSpec mode的应该是AT_MOST
    3、如果如果子View 的layout_xxxx是确定的值(188dp),那么就更简单了,不管你父View的mode和size是什么,我都写死了就是188dp,
    那么控件最后展示就是188dp,不管我的父View有多大,也不管我自己的content 有多大,反正我就是这么大,
    所以这种情况MeasureSpec的mode = EXACTLY,大小size=你在layout_xxxx写死的值。
 2. 如果父View的MeasureSpec是AT_MOST,说明父View的大小是不确定,最大的大小是MeasureSpec的size值,不能超过这个值。
    1、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能多大),子View的大小MATCH_PARENT(充满整个父View),
    那么子View你即使充满父容器,你的大小也是不确定的,因为父View自己都确定不了自己的大小,你MATCH_PARENT你的大小
    肯定也不能确定的,所以子View的mode=AT_MOST,size=父View的size。
    2、如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能多大),子View又是WRAP_CONTENT,
    那么在子View的content没算出大小之前,子View的大小最大就是父View的大小,所以子View MeasureSpec mode的就是AT_MOST。
    3、如果子View 的layout_xxxx是确定的值(188dp),同上,写多少就是多少,改变不了的。
 3. 如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示没有任何束缚和约束,不像AT_MOST表示最大只能多大,
 不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束
     1、如果子View 的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,
     那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,
     没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0
     2、如果子View 的layout_xxxx是确定的值(188dp),同上,写多少就是多少,改变不了的(记住,只要子View设置的是确切值,
     那么无论怎么测量,大小都是不变的,都是你写的那个值)
总结:
    1.当子View 采用固定宽高时,不管父容器的测量模式是什么,子View的测量模式都是EXACTLY(精确模式),并且大小是写死的宽高。
    2.当子View 的宽高是match_parent时,如果父容器的模式是EXACTLY(精确模式),那么子View也是EXACTLY,并且大小就是父容器的宽高;
    如果父容器是AT_MOST(最大模式),那么子View也是AT_MOST,并且大小是不会超过父容器的大小。
    3.当子View 的宽高是wrap_content时,不管父容器的模式是EXACTLY还是AT_MOST模式,子View的模式总是AT_MOST模式,并且大小不超过父容器的大小。
二、onLayout布局,目的就是安排其children在父视图的具体位置
    onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,
    然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,
    重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。
三、onDraw视图绘制,View的绘制是借助onDraw方法传入的Canvas类来进行的。
    1. 绘制背景:background.draw(canvas);
    2. 对View的内容进行绘制:onDraw();
    3. 绘制子View:dispatchDraw();
    4. 绘制滚动条:onDrawScrollBars。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
5.冒泡排序和快速排序手写
参考博客Java-冒泡排序 和 博客 Java-快速排序
    1
6.http协议 https单双向验证 算法
http协议(超文本传输协议):是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议.
HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统
主要特点:
    1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST
    2、灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记
    3. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    4. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,
    这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
    5、 支持B/S及C/S模式。
HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息
URL,全称是UniformResourceLocator, 中文叫统一资源定位符,是互联网上用来标识某一处资源的地址
URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
URI一般由三部组成:
    ①访问资源的命名机制
    ②存放资源的主机名
    ③资源自身的名称,由路径表示,着重强调于资源。
URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
URL一般由三部组成:
    ①协议(或称为服务方式)
    ②存有该资源的主机IP地址(有时也包括端口号)
    ③主机资源的具体地址。如目录和文件名等
HTTP之请求消息Request
    请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
HTTP之响应消息Response
    HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
HTTP之状态码
    200 OK                        //客户端请求成功
    400 Bad Request               //客户端请求有语法错误,不能被服务器所理解
    401 Unauthorized              //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
    403 Forbidden                 //服务器收到请求,但是拒绝提供服务
    404 Not Found                 //请求资源不存在,eg:输入了错误的URL
    500 Internal Server Error     //服务器发生不可预期的错误
    503 Server Unavailable        //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
   GET和POST请求的区别
       1 GET提交,请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;
                    POST提交:把提交的数据放置在是HTTP包的包体中。因此,GET提交的数据会在地址栏中显示出来,而POST提交,地址栏不会改变
       2.传输数据大小
                    因此对于GET提交时,传输数据就会受到URL长度的 限制。
                    POST:由于不是通过URL传值,理论上数据不受限。
       3.安全性
                    POST的安全性要比GET的安全性高。比如:通过GET提交数据,用户名和密码将明文出现在URL上,
                    使用GET提交数据还可能会造成Cross-site request forgery攻击
       4.GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。
   Https单双向验证参考博客HTTPS单双向验证
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
7.安装包瘦身
    (1)在app的build.gradle中开启minifyEnabled混淆代码,在app/proguard-rules.pro编写混淆规则,根据自己项目依赖的库一一混淆
    (2)开启shrinkResources去除无用资源,shrinkResources依赖于minifyEnabled,必须和minifyEnabled一起用,
    就是打开shrinkResources也必须打开minifyEnabled。
    android {
        buildTypes {
             release {
             minifyEnabled true
             shrinkResources true
             }
        }
    }
    (3)删除未使用到xml和图片,使用Android Studio的Lint,步骤:
    Android Studio -> Menu -> Refactor -> Remove Unused Resources
    选择 Refactor 一键删除
    选择 Perview 预览未使用到的资源
    (4)删除未使用到代码,点击菜单栏 Analyze -> Run Inspection by Name -> unused declaration -> Moudule ‘app’ -> OK
    (5)png图片格式转成jpg,缩小大图
    (6) 使用vector
    (7) 使用shape作为背景,很多点击效果可能会使用到图片,可以换成shape实现
    (8) 使用微信Android资源混淆工具
    (9) 使用webp格式
    (10) 一个APK尽量只用一套图片,从内存占用和适配的角度考虑,取720p的资源,放到xhdpi目录。
    (11) 删除无用的语言资源
    (12) 使用tinypng有损压缩
    (13) 删除多余的so文件,
    比如armable-v7包下的so,基本上armable的so也是兼容armable-v7的,armable-v7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。
    比如删除x86包下的so,x86包下的so在x86型号的手机是需要的,如果产品没用这方面的要求也可以精简。
    建议实际工作的配置是只保留armable、armable-x86下的so文件,算是一个折中的方案。
    (14)避免重复库,使用更小的库。
    (15) 代码优化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
8.图片压缩 三级缓存
 图片压缩方式:
 (1) 质量压缩
    1
    2
public static Bitmap compressImageByQuality(Bitmap bitmap, int limitMaxSize){
    //进行有损压缩
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int options = 100; //0-100 100表示不压缩
    bitmap.compress(Bitmap.CompressFormat.JPEG,options,baos);
    //循环判断如果压缩后图片是否大于limitMaxSize,大于继续压缩
    int baosLength = baos.toByteArray().length;
    while ((baosLength / 1024) > limitMaxSize){
        //重置baos即让下一次的写入覆盖之前的内容
        baos.reset();
        options = options - 10 > 0 ? options - 10 : 0;
        bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
        if(options == 0){
        break;
        }
    }
    Log.e("compressImage options",options+"");
    ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中
    Bitmap outBitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片
    return outBitmap;
}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
(2) 按比例大小压缩,将options.inJustDecodeBounds设置为true,在解码过程中就不会申请内存去创建Bitmap,返回的是一个空的Bitmap,
但是可以获取图片的一些属性,例如图片宽高,图片类型等等。
    1
    2
public static Bitmap compressImageByScale(Context context,int resId,int limitMaxSize){
     float outWidth = 720f;
     float outHeight = 1280f;
     return compressImageByScale(context,resId,outWidth,outHeight,limitMaxSize);
 }
 public static Bitmap compressImageByScale(Context context,int resId, float outWidth, float outHeight,int limitMaxSize){
     BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;   // 设置为true,不将图片解码到内存中
     options.inPreferredConfig = Bitmap.Config.RGB_565;
     BitmapFactory.decodeResource(context.getResources(), resId, options);
     options.inSampleSize = countInSampleSize(options,outWidth,outHeight);
     options.inJustDecodeBounds = false;
     Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
     return bitmap;
 }
 public static int countInSampleSize(BitmapFactory.Options options,float outWidth,float outHeight){
     //获取原始图片宽高
     int srcWidth = options.outWidth;
     int srcHeight = options.outHeight;
     int inSampleSize = 1;
     // 如果宽度大的话根据宽度固定大小缩放  否则根据高度固定大小缩放
     if(srcWidth > outWidth && srcWidth > srcHeight){
         inSampleSize = (int) (srcWidth /  outWidth);
     }else if(srcHeight > outHeight && srcHeight > srcWidth){
         inSampleSize = (int) (srcHeight /  outHeight);
     }
     return inSampleSize < 0 ? 1 : inSampleSize;
 }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  (3)先按比例压缩 再质量压缩
  (4) JNI终极压缩
    1
    2
9.常见内存泄漏问题
  参考博客“内存优化小结”
    1
10.设计模式 单例模式 手写最好的实
写法一:线程安全加锁
 public class Single {
     private Single(){}
     public static Single single;
     public static Single getSingle(){
         if(single == null){
             synchronized (Single.class){
                 if(single == null){
                  single = new Single();
                 }
             }
         }
     return single;
     }
 }
 写法二:内部类形式
 public class Single {
     private Single(){}
     public static Single getSingle(){
        return SingleHolder.single;
     }
     private static class SingleHolder{
        private final static Single single = new Single();
     }
 }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
11.网络框架怎么封装的(我自己封装的网络框架 Rxjava+Retrofit+MVP)
    1.添加相应架包
      compile 'com.squareup.retrofit2:retrofit:2.3.0'
      compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
      compile 'io.reactivex.rxjava2:rxjava:2.0.1'
      compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
      compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
      compile 'com.squareup.retrofit2:converter-gson:2.3.0'
     2. 创建RetrofitHelper帮助类,这个类是单例的,里面主要是配置缓存文件和目录,配置OkHttpClient,设置网络请求连接,读取和写入等的超时时间,
    添加日志过滤器,然后通过Retrofit.Builder()这个类,通过build模式,添加Gson支持,添加RxJava支持,关联okhttp,返回Retrofit对象,
    通过Retrofit这个对象的create方法创建一个HttpService接口对象,这个接口里面主要定义的是一些网络请求接口信息,
    这个帮助类的主要目的是提供一个方法返回这个HttpService接口对象,用于后面的网络请求。
     3.创建HttpService接口对象,用于定义app数据请求接口信息。
     4.创建一个HttpUtils工具类,这个类主要是用于所有网络请求的,里面主要是提供post和get请求,以及检测版本更新等方法
        (1)创建一个内部接口,定义一个网络请求成功和请求失败的方法,请求成功方法参数直接传入Gson解析之后的对象,请求失败方法参数传入错误信息。
         (2)定义一个订阅事件的方法,因为rx进行网络请求都是链式结构,通过Observable设置网络请求在子线程,设置ui更新在主线程,然后通过subscribe方法把被观察者和观察者连接起来。
         //订阅事件
         public static<T> void setSubscriber(Observable<T> observable, Observer<T> observer){
             observable.subscribeOn(Schedulers.io())  //开启新线程
             .observeOn(AndroidSchedulers.mainThread()) //ui操作放主线程
             .subscribe(observer);
         }
        (3)在post方法里面调用这个方法,传入被观察者Observable对象和观察者Observer对象,因为我是和MVP结合使用的,所以我把Observer又封装了一个类BaseObserver
         (4) 创建BaseObserver实现Observer接口,构造函数中传入自定义MVP的View接口BaseIView,实现Observer的几个方法,同时定义一个请求成功和请求失败的抽象方法,
         请求成功方法参数直接传入Gson解析之后的对象,请求失败方法参数传入错误信息,在onNext方法里面调用网络请求成功的方法,
         在onError方法里面调用请求失败的方法,同时调用BaseIView接口的请求失败错误处理方法,在onComplete方法里面调用BaseIView接口的请求完成关闭Loading等的方法。
        (5)在post方法里面通过setSubscriber方法,传入被观察者Observable对象和BaseObserver对象,实现刚刚定义的请求成功和失败的抽象方法,
        在请求成功的方法里面调用HttpUtils里面定义的内部接口的请求成功方法,在请求失败方法里面调用内部接口的请求失败方法,处理各种错误码。
     5. 创建BaseIView接口,(所有的View接口都会实现这个接口)定义数据请求失败,数据请求成功关闭Loading的方法。
     6. 创建BaseInfo,里面主要定义接口通用的字段,所有定义的bean类都会继承这个父类,同时也是为了多态的方便
     7. 创建BasePresenter基类,构造方法中传BaseIView接口,同时通过RetrofitHelper提供的静态方法获取HttpService接口对象,
    在这个类里面主要定义一个抽象方法public abstract void initData()用于子类在这个方法进行网络请求操作,
    同时还定义一个onDestoryView用于在Activity或者Fragment生命周期结束时,把View接口置为null,切断数据联系。
     8.详细封装代码可以参考我的博文:Rxjava2 Retrofit2 和 mvp再封装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
12.图片框架glide底层实现 (源码太复杂)
    整个库分为 RequestManager(请求管理器),Engine(数据获取引擎)、 Fetcher(数据获取器)、MemoryCache(内存缓存)、
    DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标) 等模块。
    简单的讲就是 Glide 收到加载及显示资源的任务,创建 Request 并将它交给RequestManager,Request 启动
     Engine 去数据源获取资源(通过 Fetcher ),获取到后 Transformation 处理后交给 Target。
    Glide 依赖于 DiskLRUCache、GifDecoder 等开源库去完成本地缓存和 Gif 图片解码工作。
    glide优点:
        1)图片request跟随Activity或Fragment生命周期。Activity onStart()时,request得到恢复;Activity onStop()时,request会被暂停;
        Activity onDestroy()时,request会被清除。这个功能对用户完全透明,故而用户完全不用担心Activity退出而request仍然存在等内存泄露问题。
        2)采用LruCache和DiskLruCache两级缓存,分别对应内存缓存和磁盘缓存
        3)支持丰富的图片格式,支持GIF,webp等格式
        4)保存到磁盘上的图片是经过裁剪和压缩的,故存储数据小
        5)采用BitmapPool对象池管理BitmapResource的创建和回收,采用线程池管理执行耗时request的子线程。
        可以减少对象的创建和垃圾回收,并控制对象个数以保持足够的空余内存。
        6)简单易用,Glide.with(context).load(url).into(imageView),采用builder模式,这是常见的使用方式。
        用户只需要知道如何利用builder传入参数即可。Glide的很多特性对用户来说完全透明,用户不需要去操心。
    1. 第一步:with()方法
        with()方法是Glide类中的一组静态方法,有如下几个with()方法的方法重载:
        //with方法的方法重载
        public static RequestManager with(Context context) {
            //得到一个RequestManagerRetriever对象
            RequestManagerRetriever retriever = RequestManagerRetriever.get();
            return retriever.get(context);
        }
        public static RequestManager with(Activity activity) {
            RequestManagerRetriever retriever = RequestManagerRetriever.get();
            return retriever.get(activity);
        }
        public static RequestManager with(FragmentActivity activity) {
            RequestManagerRetriever retriever = RequestManagerRetriever.get();
            return retriever.get(activity);
        }
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        public static RequestManager with(android.app.Fragment fragment) {
            RequestManagerRetriever retriever = RequestManagerRetriever.get();
            return retriever.get(fragment);
        }
        public static RequestManager with(Fragment fragment) {
            RequestManagerRetriever retriever = RequestManagerRetriever.get();
            return retriever.get(fragment);
        }
        retriever.get()是这部分的关键点,获取了RequestManager对象。RequestManager主要作用为实现
        request和Activity生命周期相关联。在本文讲述的Glide这部分内容中关系不大。
    2. 第二步: load()方法
        创建DrawableTypeRequest并利用它的load方法设置相关参数,这部分关键点是new DrawableTypeRequest(),
        DrawableTypeRequest是GenericRequestBuilder的子类,故load()调用后builder就创建好了。之后就可以通过builder模式来配置参数了
    3. 第三步:into()方法
        a) 创建Request对象并将builder中设入的参数,设置到Request数据类中
        b)调用 requestTracker中的runRequest(),发送request请求。创建好request只是into()方法的第一阶段,还需要将request请求发送出去。
        into()方法是比较关键也比较麻烦的一个方法。Glide将View封装为了ViewTarget。into()方法分为request创建和request发送两大部分。
        创建采用Builder模式生成GenericRequest对象,发送则调用GenericRequest的begin方法,最终回调到底层Engine部分的相关方法。
        可以看出,经过Glide的层层封装,复杂的逻辑变得相对简单了很多。
    4.绑定生命周期
    Glide中一个重要特性是Request可以随Activity或Fragment的onStart而resume,onStop而pause,onDestroy而clear,
    从而节约流量和内存,并且防止内存泄露,这一切都由Glide在内部实现了。用户唯一要注意的是,Glide.with()方法中
    尽量传入Activity或Fragment,而不是Application,不然没办法进行生命周期管理。后面分绑定流程和生命周期调用流程
    来分析整个实现原理,实现这一切的核心类如下。
        1)RequestManager,实现了LifeCycleListener,主要作用为结合Activity或Fragment生命周期,对Request进行管理,
        如pauseRequests(), resumeRequests(), clearRequests()
        2) RequestManagerRetriever,获取RequestManager,和SupportRequestManagerFragment,并将二者绑定,
        从而在fragment的生命周期方法中可回调到RequestManager对request进行生命周期管理的相应方法。
        3) SupportRequestManagerFragment, 空白Fragment,与RequestManager进行了绑定,
        作用为提供Fragment生命周期管理方法入口,如onStart(), onStop(), onDestroy()。
        4)ActivityFragmentLifecycle, 管理LifecycleListener, 空白Fragment会回调它的onStart(), onStop(), onDestroy()
        5)LifecycleListener,接口,定义生命周期管理方法,onStart(), onStop(), onDestroy(). RequestManager实现了它。
    Glide中巧妙的用一个空白Fragment来实现了生命周期调用,并使用LifeCycleListener来回调相应的request管理方法。
    设计模式上也很规范,是一个典型的MVC,SupportRequestManagerFragment用来接入生命周期方法,RequestManager用来实现生命周期中处理request的方法,
    RequestManagerRetriever用来绑定二者,作为桥梁。我们在网络请求request中同样可以用此方法来实现生命周期绑定。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
13.Java内存回收机制
垃圾回收的意义:
    在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,
    该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。
    当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。
    由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。
    碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。
    垃圾回收能自动释放内存空间,减轻编程的负担。这使Java 虚拟机具有一些优点。首先,它能使编程效率提高。
    在没有垃圾回收机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾回收机制可大大缩短时间。
    其次是它保护程序的完整性, 垃圾回收是Java语言安全性策略的一个重要部份。
    垃圾回收的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。
    这一个过程需要花费处理器的时间。其次垃圾回收算法的不完备性,早先采用的某些垃圾回收算法就不能保证100%收集到所有的废弃内存。
    当然随着垃圾回收算法的不断改进以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。
垃圾收集算法:
    1.引用计数法
    引用计数法是唯一没有使用根集的垃圾回收的法,该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。
    当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),
    引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。
    2. 标记 -清除算法
            “标记-清除”算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
    3.复制算法
            “复制”算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,
            就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    4.标记-整理算法
            “标记-整理”算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,
            而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
    5.分代收集算法
            “分代收集”算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,
            每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
            而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
减少GC开销的措施
       (1)不要显式调用System.gc()
       (2)尽量减少临时对象的使用
       (3)对象不用时最好显式置为Null
       (4)尽量使用StringBuffer,而不用String来累加字符串
       (5)能用基本类型如Int,long,就不用Integer,Long对象
       (6)尽量少用静态对象变量
       (7)分散对象创建或删除的时间
垃圾收集器分类
    1、Serial收集器(复制算法)
    2、ParNew收集器(停止-复制算法)
    3、Parallel收集器(停止-复制算法)
    4、Parallel Old 收集器(停止-复制算法)
    5、CMS收集器(标记-清理算法)
    6、G1收集器(标记整理算法)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
14.Hashmap底层实现
    1. 在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,
    即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,
    当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
    2. 两个重要的参数:容量(Capacity)和负载因子(Load factor),HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
    3. put函数的实现
        1).判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
        2).根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
        3).判断table[i]的首个元素是否和key一样,如果相同用新的value直接覆盖旧的value,否则转向④,这里的相同指的是hashCode以及equals;
        4).判断table[i] 是否为红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
        5).遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;
        遍历过程中若发现key已经存在,用新的value直接覆盖旧的value即可;
        6).插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
    代码实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public V put(K key, V value) {
         // 对key的hashCode()做hash
         return putVal(hash(key), key, value, false, true);
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
      boolean evict) {
          Node<K,V>[] tab; Node<K,V> p; int n, i;
          // 步骤①:tab为空则创建
          if ((tab = table) == null || (n = tab.length) == 0)
              n = (tab = resize()).length;
          // 步骤②:计算index,并对null做处理
          if ((p = tab[i = (n - 1) & hash]) == null)
              tab[i] = newNode(hash, key, value, null);
          else {
                  Node<K,V> e; K k;
                   // 步骤③:节点key存在,直接覆盖value
                  if (p.hash == hash &&
                  ((k = p.key) == key || (key != null && key.equals(k))))
                      e = p;
                  // 步骤④:判断该链为红黑树
                  else if (p instanceof TreeNode)
                      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                  // 步骤⑤:该链为链表
                  else {
                      for (int binCount = 0; ; ++binCount) {
                          if ((e = p.next) == null) {
                              p.next = newNode(hash, key, value, null);
                               //链表长度大于8转换为红黑树进行处理
                              if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                  treeifyBin(tab, hash);
                              break;
                          }
                          // key已经存在直接覆盖value
                          if (e.hash == hash &&
                          ((k = e.key) == key || (key != null && key.equals(k))))
                              break;
                          p = e;
                      }
                  }
                  // 写入
                  if (e != null) { // existing mapping for key
                          V oldValue = e.value;
                      if (!onlyIfAbsent || oldValue == null)
                          e.value = value;
                      afterNodeAccess(e);
                      return oldValue;
                  }
          }
          ++modCount;
           // 步骤⑥:超过最大容量 就扩容
          if (++size > threshold)
          resize();
          afterNodeInsertion(evict);
          return null;
  }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
 4.get函数的实现
    大致思路如下:
    1. 计算hash&(n-1)得到在链表数组中的位置first=tab[hash&(n-1)],先判断first的key是否与参数key相等,如果相等(命中),直接返回该key对应的value值;
    2. 如果不等,则通过key.equals(k)去查找对应的entry,
    若为红黑树,则在树中通过key.equals(k)查找,O(logn);
    若为链表,则在链表中通过key.equals(k)查找,O(n)。
    代码实现:
    1
    2
    3
    4
    5
    6
    7
    8
    public V get(Object key) {
         Node<K,V> e;
         return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    final Node<K,V> getNode(int hash, Object key) {
         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
         if ((tab = table) != null && (n = tab.length) > 0 &&
             (first = tab[(n - 1) & hash]) != null) {
             // 直接命中
             if (first.hash == hash && // always check first node
             ((k = first.key) == key || (key != null && key.equals(k))))
                 return first;
             // 未命中
             if ((e = first.next) != null) {
                 // 在树中get
                 if (first instanceof TreeNode)
                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                 // 在链表中get
                 do {
                     if (e.hash == hash &&
                     ((k = e.key) == key || (key != null && key.equals(k))))
                         return e;
                 } while ((e = e.next) != null);
             }
         }
         return null;
     }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    5. hash函数的实现,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,
    这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
    在对hashCode()计算hash时具体实现是这样的:
    1
    2
    3
    static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    1
    2
    3
    4
    6.resize的实现
    当put时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,那么就会发生resize。在resize的过程,
    简单的说就是把bucket扩充为2倍,之后重新计算index,把节点再放到新的bucket中。
    代码实现:
    1
    2
    3
    4
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
        // 超过最大值就不再扩充了,就只好随你碰撞去吧
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 没超过最大值,就扩充为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
        oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 计算新的resize上限
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
            (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            // 把每个bucket都移动到新的buckets中
            for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 原索引
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                        // 原索引+oldCap
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 原索引放到bucket里
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 原索引+oldCap放到bucket里
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
 }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
 7. HashMap工作原理
     首先有一个数组,数组的每个元素都是链表,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,
     但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,
     同一个链表上的Hash值是相同的,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
 8. 小结
    (1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
    (2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
    (3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。
    (4) JDK1.8引入红黑树大程度优化了HashMap的性能。
    (5) 还没升级JDK1.8的,现在开始升级吧。HashMap的性能提升仅仅是JDK1.8的冰山一角。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
15.activity启动模式
    (1)standard标准模式: 这个模式是默认的启动模式,即标准模式,在不指定启动模式的前提下,系统默认使用该模式启动Activity,
    每次启动一个Activity都会重写创建一个新的实例,不管这个实例存不存在。
    (2)singleTop-栈顶复用模式:这个模式下,如果新的activity已经位于栈顶,那么这个Activity不会被重写创建,同时它的onNewIntent方法会被调用,
    通过此方法的参数我们可以去除当前请求的信息。如果栈顶不存在该Activity的实例,则情况与standard模式相同。
         singleTop模式分3种情况
         当前栈中已有该Activity的实例并且该实例位于栈顶时,不会新建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewIntent方法
         当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例
         当前栈中不存在该Activity的实例时,其行为同standard启动模式
    (3)singleTask-栈内复用模式:在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,
    会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,
    会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。
    (4)singleInstance-全局唯一模式:该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,
    具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。
以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
16.scheme知识
     参考我的博客 scheme唤醒外部APP
     //开启
     Intent intent = new Intent();
     intent.setData(Uri.parse("");
     //目标app的目标activity需要在清单文件中注册<intent-filter> 指定具体协议名称和主机类等信息。
    1
    2
    3
    4
    5
17.数据存储和解析方式
      数据存储方式:文件,sp,数据库,网络 内容观察者
      数据解析方式:xml解析:dom解析,sax解析,pull解析;  json解析:谷歌自带json解析,Gson,fastJson ,J
--------------------- 
作者:尽人事看天意 
来源:CSDN 
原文:https://blog.csdn.net/rjgcszlc/article/details/79851435 
版权声明:本文为博主原创文章,转载请附上博文链接!
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号