常见面试题

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));
}

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);
                    }
                }
            }

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;【

事件分发机制

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。

5.冒泡排序和快速排序手写

参考博客Java-冒泡排序 和 博客 Java-快速排序

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单双向验证

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) 代码优化

8.图片压缩 三级缓存

 图片压缩方式:
 (1) 质量压缩
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;
}
(2) 按比例大小压缩,将options.inJustDecodeBounds设置为true,在解码过程中就不会申请内存去创建Bitmap,返回的是一个空的Bitmap,
但是可以获取图片的一些属性,例如图片宽高,图片类型等等。
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;
 }
  (3)先按比例压缩 再质量压缩
  (4) JNI终极压缩

9.常见内存泄漏问题

  参考博客“内存优化小结”

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();
     }

 }

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再封装

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中同样可以用此方法来实现生命周期绑定。

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收集器(标记整理算法)

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,如果超过,进行扩容。
    代码实现:
    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;
  }
 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)。

    代码实现:
    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;
     }
    5. hash函数的实现,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,
    这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
    在对hashCode()计算hash时具体实现是这样的:
    static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    6.resize的实现
    当put时,如果发现目前的bucket占用程度已经超过了Load Factor所希望的比例,那么就会发生resize。在resize的过程,
    简单的说就是把bucket扩充为2倍,之后重新计算index,把节点再放到新的bucket中。
    代码实现:
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;
 }
 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的冰山一角。

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时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

16.scheme知识

     参考我的博客 scheme唤醒外部APP
     //开启
     Intent intent = new Intent();
     intent.setData(Uri.parse("");
     //目标app的目标activity需要在清单文件中注册<intent-filter> 指定具体协议名称和主机类等信息。

17.数据存储和解析方式

      数据存储方式:文件,sp,数据库,网络 内容观察者
      数据解析方式:xml解析:dom解析,sax解析,pull解析;  json解析:谷歌自带json解析,Gson,fastJson ,JackJson。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值