Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 个人简历

  • 人生总结

  • 实用技巧

  • 影剧观感

  • 教育

  • 服务搭建

  • 灵感记录
  • 理财投资

  • 生活常识

  • 知识科普

  • 自媒体

  • 面试经验

    • 2017 校招面试笔试记录
      • 1.音视频不同步的原因:
        • 一般是客户端的问题。
        • 如果是服务端的问题,比较不好处理。
      • 2. synchronized 实例方法与静态方法的区别
      • 3.Android 事件分发
        • ViewGroup的dispatchTouchEvent方法分析
        • View的dispatchTouchEvent方法
        • Demo:
      • 4.内存泄漏检测工具
        • 先说下经常出现内存泄漏的几种情况及解决
        • 1.Activity的Context传给单例->改为传ApplicationContext
        • 2.在某个类(Activity)中创建了一个非静态内部类(默认会持有外部类(Activity)的引用),并且创建了该类的单例(全局静态变量),该变量在某个方法中被实例化;
        • 3.通过这样创建的Handler handler=new Handler(){...}非静态匿名内部类持有Activity的引用. 当Activity销毁 而Handler里面有消息未完全处理完时,无法正常回收Activity.
        • 4.资源未关闭造成的内存泄漏
        • 5.线程造成的内存泄漏
        • 检测工具
      • 5.View绘制过程
      • 6.Android的两种广播注册方式
      • 7.ThreadLocal 的用法
      • 8.Java的几种引用
      • 9.Android数据库升级
      • 10.HandlerThread
      • 11.onSaveInstanceState和onRestoreInstanceState
        • onSaveInstanceState触发时机:存在非用户主动销毁Activity的情况下会触发,如:按HOME,切换程序,转屏,进ActivityB,关屏幕等,系统在内存不足时会销毁Activity(转屏是一定会销毁如果没设置的话)
        • onRestoreInstanceState调用时机:Activity确实被销毁了
      • 12.Hashtable HashSet HashMap 细节
      • 13.java8 ConcurrentHashMap源码 分段锁是什么
      • 14.bitmap压缩
      • 15.Android中的几种设计模式(待补充)
        • 工厂
        • 适配器
        • 创建者模式
        • 抽象工厂
        • 原型
      • 16.[笔试]扔硬币正反概率都是0.5,直到连续的2次都为正面即结束,问结束次数的期望
        • 改为 负正 即结束 问期望
        • 改成 出现+-甲赢,--乙赢,问甲赢的概率
        • 改成 出现--+说明甲赢 出现-++说明乙赢,问甲赢的概率
        • ~~ 思路1:分别求--+和-++的期望 ~~
        • ~~ 思路2:那么分别求不出现-++情况条件下出现--+的期望和不出现--+情况条件下出现-++的期望呢,条件概率。~~
        • 思路3:直接算
    • 前端两年半面经流水账(2020)
    • 前端两年半面试准备(2020)
    • 高龄前端如何回答面试基础题
    • 面试技巧

  • 闲言碎语
  • 面试经验
gahing
2020-06-29
目录

2017 校招面试笔试记录

2017 年校招面试笔试记录,面试方向是 Android

# 1.音视频不同步的原因:

# 一般是客户端的问题。

由于音视频包都会带上时间戳,出现不同步的根本原因是客户端取出当前数据包解码放入缓冲队列,进行渲染时***(在这做同步)不能从缓冲队列中同时找到当前时间戳*的视频解码数据和音频解码数据,所以只能先取其中一个拿个渲染。

(注:拿去渲染后,如果时间戳同步,渲染一般不会出现不同步的情况,硬件处理相对成熟);

渲染不同步,可以说是缓冲队列设置太小,没有考虑网络不好数据包传输慢或者需要丢包重传的情况。 当然还有可能是解码的问题,音视频其中之一的解码效率太慢,而缓冲队列设置合适,稍微不好一点的网络情况导致解码数据包(音频or视频)来不及放入缓冲队列。

# 如果是服务端的问题,比较不好处理。

音视频线程数据采集速度不同步(和采集卡有关),封装协议时就已经出现同一时间戳的音视频包实际是不同步的情况。

# 2. synchronized 实例方法与静态方法的区别

先上结论:

1.同一个实例,所有非静态同步方法会加锁同步,锁的对象是类实例;即先等待其中一个实例方法运行结束才能运行下一个实例方法(不管实例方法是否是同一个)

2.同步实例方法的锁对象是类实例,所以不同类实例之间可以并行的运行实例方法

3.同步静态方法的锁对象是Class类本身,所以静态方法间的运行是串行的

4.类的静态方法和类实例的实例方法同时运行,由于锁对象不同,运行是并行的

public class TestSynchronized {
	public synchronized static void synstaMethod1(){
		System.out.println("i am synstaMethod1 start");
		timeTask();
		System.out.println("i am synstaMethod1 end");
	}

	public synchronized static void synstaMethod2()  {
		System.out.println("i am synstaMethod2 start");
		timeTask();
		System.out.println("i am synstaMethod2 end");
	}

	public synchronized void synMethod1()  {
		System.out.println("i am synMethod1 start");
		timeTask();
		System.out.println("i am synMethod1 end");
	}

	public void synMethod2()  {
		synchronized (this) {
			System.out.println("i am synMethod2 start");
			timeTask();
			System.out.println("i am synMethod2 end");
		}
	}

	public static void timeTask() {
		double i = 0;
		while (true) {
			i += 1;
			if (i > 1000000000)
				break;
		}
	}
	public static void timeTask2() {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	//下面这方法不好测试 会另开线程
	public static void timeTask3(String methodName) {
		Timer timer = new Timer();
		// 3s后运行该任务
		timer.schedule(new TimerTask() {
			public void run() {
				System.out.printf("i am %s end\n",methodName);
				timer.cancel();
			}
		}, 1000);
	}
}

测试方法:

package com.france;

//话说JUnit做多线程测试有诸多不便..还是用Main来测试把..
public class Main {
	static TestSynchronized obj1 = new TestSynchronized();
	static TestSynchronized obj2 = new TestSynchronized();

	// 测试:同一个类实例的实例方法
	// 结论:同一个实例,所有非静态同步方法会加锁同步,锁的对象是类实例;即先等待其中一个实例方法运行结束才能运行下一个实例方法(不管实例方法是否是同一个)
	// output:
	// i am synMethod1 start
	// i am synMethod1 end
	// i am synMethod2 start
	// i am synMethod2 end
	// i am synMethod2 start
	// i am synMethod2 end
	@org.junit.Test
	public static void testSameObjectUnstatic() {
		// 先运行完其中的一个实例方法完再运行下一个实例方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				obj1.synMethod1();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				obj1.synMethod2();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				obj1.synMethod2();
			}
		}).start();
	}

	// 测试:不同类实例的实例方法
	// 结论:同步实例方法的锁对象是类实例,所以不同类实例之间可以并行的运行实例方法
	// output:
	// i am synMethod1 start
	// i am synMethod2 start
	// i am synMethod2 end
	// i am synMethod1 end
	@org.junit.Test
	public static void testDifferentObjectUnstatic() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				obj2.synMethod1();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				obj1.synMethod2();
			}
		}).start();
	}

	// 测试:类的静态方法
	// 结论:同步静态方法的锁对象是Class类本身,所以静态方法间的运行是串行的
	// 注意与该方法的调用者(Class或者类实例)无关
	// output:
	// i am synstaMethod1 start
	// i am synstaMethod1 end
	// i am synstaMethod2 start
	// i am synstaMethod2 end
	@org.junit.Test
	public static void testClassStatic() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				TestSynchronized.synstaMethod1();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				TestSynchronized.synstaMethod2();
			}
		}).start();
	}

	// 测试:类的静态方法和类实例的实例方法
	// 结论:类的静态方法和类实例的实例方法同时运行,由于锁对象不同,运行是并行的
	// 注意与该方法的调用者(Class或者类实例)无关
	// output:
	// i am synstaMethod1 start
	// i am synMethod2 start
	// i am synstaMethod1 end
	// i am synMethod2 end
	@org.junit.Test
	public static void testClassAndObject() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				TestSynchronized.synstaMethod1();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				obj1.synMethod2();
			}
		}).start();
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		testClassAndObject();
	}

}

# 3.Android 事件分发

参考:(荐)http://blog.csdn.net/duo2005duo/article/details/51604119 http://www.jianshu.com/p/6ebdb78f579e

1.一次Touch有UP,MOVE(>=0次),UP/Cancel,事件至上而下传递

# ViewGroup的dispatchTouchEvent方法分析

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;

    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

    if (action == MotionEvent.ACTION_DOWN) {
        //1.只有在非拦截的情况的下寻找target
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            // 防止onInterceptTouchEvent()的时候改变Action
            ev.setAction(MotionEvent.ACTION_DOWN);
            // 遍历子View,第一个消费这个事件的子View的为Target
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                //当然只遍历可见的,并且没有在进行动画的。
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                    child.getHitRect(frame);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        // offset the event to the view's coordinate system
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        if (child.dispatchTouchEvent(ev))  {
                            // Event handled, we have a target now.
                            mMotionTarget = child;
                            return true;
                        }
                                            }
                }
            }
        }
    }

    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    //up或者cancel的时候清空DisallowIntercept
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // 如果没有target,则把自己当成View,向自己派发事件
    final View target = mMotionTarget;
    if (target == null) {
        // We don't have a target, this means we're handling the
        // event as a regular view.
        ev.setLocation(xf, yf);
        return super.dispatchTouchEvent(ev);
    }

    // 如果有Target,拦截了,则对Target发送Cancel,并且清空Target
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
            // target didn't handle ACTION_CANCEL. not much we can do
            // but they should have.
        }
        // clear the target
        mMotionTarget = null;
        // Don't dispatch this event to our own view, because we already
        // saw it when intercepting; we just want to give the following
        // event to the normal onTouchEvent().
        return true;
    }
    //up 或者 cancel清空Target
    if (isUpOrCancel) {
        mMotionTarget = null;
    }

    //如果有Target,并且没有拦截,则向Target派发事件,这个事件会转化成Target的坐标系
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);

    return target.dispatchTouchEvent(ev);
}

2.点击某个View控件,系统计算位置然后找到根ViewGroup; ACTION_DOWN且ViewGroup非拦截时向下分发:如果ViewGroup的某个子View消费掉(View的dispatchTouchEvent返回true),则不再分发并且进行向上传递:ViewGroup遍历子View,将第一个消费这个事件的子View置为Target 3.如果ViewGroup没有target,则把自己当成View,向自己派发事件:向下分发时没有子View消费 4.如果ViewGroup有Target,并且拦截了,则对Target发送Cancel,并且清空Target。需要分析onInterceptTouchEvent方法 5.如果ViewGroup有Target,并且没有拦截,则向Target派发事件。比如DOWN时设置子View为Target 然后现在ACTION_MOVE,则向该子VIEW派发该事件

# View的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    //1.停止嵌套滑动
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }
    //2.安全监测
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;

        //3.如果当前View使能(setEnabled(true)),则调用Touch监听器    
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //4.如果Touch监听器返回false或者没有调用Touch监听器,则返回调用onTouchEvent()
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    //停止嵌套滑动
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

分析3. 第一个if语句要同时满足li != null 、 li.mOnTouchListener != null 、 (mViewFlags & ENABLED_MASK) == ENABLED 、 li.mOnTouchListener.onTouch(this, event); 第一个一般不会为null,所以走第二个条件,刚刚上面说到mOnTouchListener是我们在setOnTouchListener的时候传递过来的,所以只要设置了TouchListener,这个条件也会成立; 第三个条件是判断控件是否是enable的,一般控件默认都是enable的,除非你明确的设置成disable; 接着判断第四个条件,就是回调mOnTouchListener的onTouch方法,这个方法是我们自己重写的,如果我们返回false,这个条件就不成立,否则我们返回true,该条件就成立,然后执行result=true。

如果第一个if不满足就会执行onTouchEvent方法

public boolean onTouchEvent(MotionEvent event) {    

...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP://抬起事件
                //巴拉巴拉巴拉....
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
                    performClick();//去调用onClick方法
                }
                ......
                break;
            case MotionEvent.ACTION_DOWN://按下事件
                //巴拉巴拉巴拉....
                break;
            case MotionEvent.ACTION_CANCEL://取消事件
                //巴拉巴拉巴拉....
                break;
            case MotionEvent.ACTION_MOVE://移动事件
                //巴拉巴拉巴拉....
                break;
        }//switch语句执行完毕

    return true;//如果控件可以点击,也就是说可以进入到这个if语句里面,那么总会返回true
}//if语句结束
return false;//如果控件不可以点击,就进不到上面的if语句里面,那么总会返回false

} 这个条件是判断控件是否可以支持点击事件,如果支持点击事件进入语句体,开始执行switch语句,可以看到switch执行完毕之后总是会返回true onTouchEvent方法总结: 1.不管View使能与否,只要clickable或者longclickable,就一定消费事件(返回true) 2.如果View不使能,并且clickable或者longclick,就只会消费事件但不做其他任何操作 3.如果View使能,先看看TouchDelegate消费与否,如果不消费再给自己消费 4.处理包括focus,press,click,longclick

# Demo:

tv.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Log.i(TAG, "onTouch: ACTION_DOWN");
                    break;

                case MotionEvent.ACTION_MOVE:
                    Log.i(TAG, "onTouch: ACTION_MOVE");
                    break;

                case MotionEvent.ACTION_UP:
                    Log.i(TAG, "onTouch: ACTION_UP");
                    break;
            }
            return false;
        }
    });

点击TextView,log如下:

06-04 04:38:46.835 15690-15690/cn.wang.permissiondemo I/ThirdActivity: onTouch: ACTION_DOWN

执行流程(TextView的CLICKABLE=false): 1.ACTION_DOWN:ViewGroup向下分发,找到某个View尝试进行消费 2.该View执行TouchListener.onTouch 处理了ACTION_DOWN,但是返回false,继续运行该View的onTouchEvent发现CLICKABLE是false然后方法返回false. 3.遍历子View后,此时ViewGroup没有找到子View可以消费事件,将自己当成View进行派发事件(super.dispatchTouchEvent),该dispatchTouchEvent默认返回false,就这样不断向上传递 4.ACTION_MOVE:ViewGroup没有Target,故还是自己当成View进行派发事件,并返回false 总的说,打印了ACTION_DOWN只是一开始View进行尝试消费时运行的..

# 4.内存泄漏检测工具

# 先说下经常出现内存泄漏的几种情况及解决

# 1.Activity的Context传给单例->改为传ApplicationContext

# 2.在某个类(Activity)中创建了一个非静态内部类(默认会持有外部类(Activity)的引用),并且创建了该类的单例(全局静态变量),该变量在某个方法中被实例化;

注:该变量生命周期与应用一致.也就是说,在应用生命周期中,该变量会持有Activity的引用导致不能进行回收
改进->将该非静态内部类该为静态内部类或单独提出封装成一个单例

# 3.通过这样创建的Handler handler=new Handler(){...}非静态匿名内部类持有Activity的引用. 当Activity销毁 而Handler里面有消息未完全处理完时,无法正常回收Activity.

改进->覆盖Handler类被静态实现被弱引用Context,被注意移除消息队列中消息(否则虽避免Activity内存泄漏但是Handler)

public class MainActivity extends AppCompatActivity {
  private MyHandler mHandler = new MyHandler(this);
  private TextView mTextView ;
  private static class MyHandler extends Handler {
    private WeakReference<Context> reference;
    public MyHandler(Context context) {
      reference = new WeakReference<>(context);
    }
    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = (MainActivity) reference.get();
      if(activity != null){
        activity.mTextView.setText("");
      }
    }
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mTextView = (TextView)findViewById(R.id.textview);
    loadData();
  }
  private void loadData() {
    //...request
    Message message = Message.obtain();
    mHandler.sendMessage(message);
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
  }
}

# 4.资源未关闭造成的内存泄漏

BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap,注册的系统监听器等资源的使用,应该在Activity销毁时及时关闭或者注销

使用完Bitmap后先调用recycle(),再赋为null

# 5.线程造成的内存泄漏

将异步任务和Runnable写成非静态匿名内部类,那么他们持有Activity的引用 如果Activity销毁时任务还没完成将导致Activity的资源无法正常回收

正确的做法还是使用静态内部类的方式

# 检测工具

MAT(比较复杂,面试的时候就提到有安装该插件 但是具体没用过) LeakCanary(onCreate里加一句代码,然后出现泄漏会发通知,点通知跳转到具体原因的页面,面试就讲这个) ndk内存泄漏检测工具?Valgrind, 有点复杂

# 5.View绘制过程

  1. 自上而下,先设置父视图,再循环设置子视图;
  2. 先通过measure计算视图大小,再通过layout计算视图位置,再调用draw绘制;
  3. 子视图的measure由父子视图共同决定,layout方法是传入四个位置的坐标,draw时先绘制background,然后绘制当前view的内容,再遍历view; 总过程:measure(viewgroup->childview)->layout(viewgroup->childview)->draw(viewgroup->childview);

# 6.Android的两种广播注册方式

代码中注册,与程序生命周期有关 Manifest.XML中注册,在系统中常驻:始终处于活动状态 耗电

# 7.ThreadLocal 的用法

提供线程内部的局部变量,本线程随时可取,隔离其他线程

# 8.Java的几种引用

软引用对象在内存不足会被回收;
弱引用只要进行GC就会被回收;
虚引用对象任何时候都会被回收(不仅是GC);
强引用对象不会被回收,一般不用的时候需要代码显示将其置null,不然宁愿OOM也不会回收;

另外讲下GC分主GC和次GC,次GC比较平滑,主GC满足一定条件才会调用 System.gc():调用该方法,JVM底层建议进行主GC,大部分情况会进行非一定,增加了间接性停顿的次数..

# 9.Android数据库升级

传入的数据库版本比之前的大,会调用SQLiteOpenHelper#onUpgrade()方法,我们在该方法做如下操作:

1.将要修改的表A重命名为A_temp
2.先建表A,并把A_temp的数据传入A
3.删除A_temp

# 10.HandlerThread

另开线程,封装了创建Looper,Handle去处理MessageQueue的操作

# 11.onSaveInstanceState和onRestoreInstanceState

# onSaveInstanceState触发时机:存在非用户主动销毁Activity的情况下会触发,如:按HOME,切换程序,转屏,进ActivityB,关屏幕等,系统在内存不足时会销毁Activity(转屏是一定会销毁如果没设置的话)

# onRestoreInstanceState调用时机:Activity确实被销毁了

# 12.Hashtable HashSet HashMap 细节

HashSet里面维护了一个HashMap map变量,增删改查都是对map对象的key进行操作,map的key即set的value HashMap里面定义了静态内部类Node<K,V>和transient Node<K,V>[] table,K由key和hash(key)确定;非并发操作 Hashtable 维护了private transient Entry<?,?>[] table,其上的操作synchronized->并发,允许null值

# 13.java8 ConcurrentHashMap源码 分段锁是什么

insert链表头是采用CAS汇编指令集判断数据一致性,每个链表头加锁 链表>8才则tree化,桶数量>64也tree化

# 14.bitmap压缩

inJustDecodeBounds设置为true 不会分配bitmap内存 但是可以获取宽高 然后设置inSampleSize为我们想要的压缩比(和获取的宽高有关,取小的比例?)

源300,200 目标100,100 一般是压缩2 变为 150,100 而不是100,67..不过这也要看应用

再设置 inJustDecodeBounds为false 然后return BitmapFactory.decodeResource(res, resId, options);

# 15.Android中的几种设计模式(待补充)

# 工厂

BitmapFactory.decodeResource.. 生产出bitmap

# 适配器

不同的数据提供者通过同一接口提供给同一客户:Adapter

# 创建者模式

复杂对象的构建和表现分离

# 抽象工厂

MediaPlayerFactory:ijk项目的player有好几种实现,后面具体生成哪个Player就看用户选择了

# 原型

Bitmap.copy(Config.RGB_xxx)方法修改部分数据然后进行对象的clone AlertDialog.Builder

# 16.[笔试]扔硬币正反概率都是0.5,直到连续的2次都为正面即结束,问结束次数的期望

解:这题如果一直穷举,列数列,找规律 难度很大,会在其中陷入循环.... 所以我们设法得到一个等式。设次数的期望为E.我们现在开始抛第一枚硬币,有以下情况:

  1. 第一枚硬币为负,那么需要重新扔,所占比例的期望为0.5*(1+E)
  2. 第一枚硬币为正,第二枚为负,那么需要重新扔,所占比例的期望为0.25*(2+E)
  3. 一正二正,结束,0.25*2

得到 E= 0.5*(1+E)+ 0.25*(2+E)+ 0.25*2 解得E=6

也可以写代码跑下,随着样例越大答案会趋近于6

# 改为 负正 即结束 问期望

情况为以下,注:当出现连续的-时我们只考虑下次是否为+即可,否则说明为-我们继续判断下下次

  1. +:0.5*(E+1)

  2. -+:0.25*2

  3. --+:0.125*3 ...

  4. (-...-)[n个]+:(0.5)^(n+1)*(n+1)

    E=0.5E+0.5+0.252+...+(0.5)^(n+1)(n+1) 高中知识:错位相减法 0.5E=0.51+0.252+...+(0.5)^(n+1)(n+1) 0.25E=0.251+0.1252+...+(0.5)^(n+2)(n+1) 两式相减: 0.25E=0.5+0.25+0.125+...+0.5^(n+1)-(0.5)^(n+2)(n+1) 等比数列求和:0.5+0.25+0.125+...+0.5^(n+1) a1=0.5 q=0.5 等比数列前n项和(其实不用算也能得到是趋于1的无穷...E=4): Sn=0.5(1-0.5^n)/(1-0.5)=1-0.5^n 故0.25E=1-0.5^n-0.5^(n+2)(n+1) E=4(1-0.5^n-0.5^(n+2)*(n+1))

n→∞ 得E=4

# 改成 出现+-甲赢,--乙赢,问甲赢的概率

只要投硬币出现+说明后面甲一定赢了,乙赢的情况必须是一开始就是-- 很容易求得甲赢的概率为0.5+0.5*0.5=0.75

# 改成 出现--+说明甲赢 出现-++说明乙赢,问甲赢的概率

# ~~ 思路1:分别求--+和-++的期望 ~~

[不可行 例:-+和--期望分别是4 6 但是两人赢的概率是一样的] 说明两个事件是相关的,不能分别求。

# ~~ 思路2:那么分别求不出现-++情况条件下出现--+的期望和不出现--+情况条件下出现-++的期望呢,条件概率。~~

# 思路3:直接算

顺便复习**可计算理论** 求 表示不含-++且以--+结尾的-+字符串的`正则表达式 先画DFA

1

DFA转正则 2.png

最后的正则表达式如下 \+*-(\+-|--)*-\+

所以不含-++且以--+结尾的概率为 +*- :概率为1 (\+-|--)*-\+ :以下概率的和

先找几项
-+:0.5^2
---+:0.5^2*0.5^2
+--+:0.5^2*0.5^2
-----+:0.5^4*0.5^2
--+--+: 0.5^3 以--+-开头的不用再往后搜索了
+-+--+:0.5^4*0.5^2
+----+:0.5^4*0.5^2
...

把图画出来就好做了: 3.png

a:--(--)*-+:0.5^2*(0.5^2+0.5^4+...+0.5^2n) =0.25*(0.25(1-0.25^n)/0.75)=
b:--(--)*+-:0.5*(0.5^2+0.5^4+...+0.5^2n)=0.5*(0.25(1-0.25^n)/0.75)
s=a+b 表示以--开头 =0.75*(0.25(1-0.25^n)/0.75)
c:+-(+-)*-+:0.5^2*(0.5^2+0.5^4+...+0.5^2n)=0.25*(0.25(1-0.25^n)/0.75)
d:+-(+-)*s:(0.5^2+0.5^4+...+0.5^2n)*s=(0.25(1-0.25^n)/0.75)* 0.75*(0.25(1-0.25^n)/0.75)
e:-+:0.5^2=0.25

所得概率=a+b+c+d+e=0.25+(0.25(1-0.25^n)/0.75)*(1+ 0.75*(0.25(1-0.25^n)/0.75)) =0.25+1/3*(1+3/4*1/3)=2/3

故甲赢概率是2/3 乙赢概率是1/3

编辑 (opens new window)
上次更新: 2024/09/01, 23:56:56
生活服务直播间通用话术
前端两年半面经流水账(2020)

← 生活服务直播间通用话术 前端两年半面经流水账(2020)→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式