美文网首页
01.面试题

01.面试题

作者: Jsonzhang | 来源:发表于2017-02-17 11:37 被阅读35次

    目录

    • 算法
    • 冒泡排序
    • 网络通信协议TCP和UDP的区别
    • 注册广播有几种方式,他们的区别是什么?
    • Get请求和Post请求的区别
    • Android中创建线程的两种方式及优缺点?
    • 活动的启动模式
    • ArrayList与LinkedList和Vector的区别
    • 简述Handler机制
    • 在项目中怎样对ListView优化
    • 说说线程池管理机制
    • Fragment的生命周期
    • Service和IntentService的区别
    • 写下单例模式

    算法

       /**
         * 猫扑素数:
         * 猫扑数:以2开头,后面拼接任意多个3的十进制整数
         * 素数:大于1的自然数中,除了1和他本身,没有其他的因数
         */
    
        public static boolean isMopNumber(int num) {
            if (num < 10) return num == 2;
            else {
                return (num % 10 == 3) && isMopNumber(num / 10);
            }
        }
    
    //如果n不是素数,2=< d <= Math.sqrt(n) ,则这个区间中一定有是它的因数
        public static boolean isPrimeNumber(int num) {
            if (num < 2) return false;
            else {
                for (int i = 2; i<= Math.sqrt(num); i++) {
                    if (num % i==0){
                        return false;
                    }
                }
                return true;
            }
        }
    
       //测试
      for (int i = 0; i < 1000000; i++) {
           if (Utils.isMopNumber(i) && Utils.isPrimeNumber(i)) {
               Log.e("猫扑素数", "=" + i);
           }
       }
    01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =23
    01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =233
    01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =2333
    01-17 17:37:08.359 5032-5032/com.zsl.aa E/猫扑素数: =23333
    

    1.冒泡排序

    private int[] arr = {6,3,8,2,9,1};
    private int temp;
    
    private void bubbleSort(int[] arr){
    
        for(int i=0;i<arr.length-1;i++){  //外层控制排序趟数
            for(int j=0;j<arr.length-1-i;j++){  //内层控制每趟排多少次
                if(arr[j]>arr[j+1]){
                    temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        
        }
    }
    最后的排序结果:1,2,3,6,8,9
    
    //二分法查找
    public static int search(int[] arr, int key) {
           int start = 0;
           int end = arr.length - 1;
           while (start <= end) {
               int middle = (start + end) / 2;
               if (key < arr[middle]) {
                   end = middle - 1;
               } else if (key > arr[middle]) {
                   start = middle + 1;
               } else {
                   return middle;
               }
           }
           return -1;
       }
    

    2.网络通信协议TCP和UDP的区别

    TCP:Transmission Control Protocol 传输控制协议
    UDP:User Dataprogram Protocol 用户数据协议

    TCP协议是面向连接的通信协议,即在数据传输前现在发送端和接收端建立逻辑联系,然后再传输数据,它提供了两台计算机间可靠无差错的数据传输。在tcp协议中必须明确客户端和服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。第一次握手,客户端向服务器端发出连接请求,等待服务器确认;第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求;第三次握手,客户端再次向服务器端发送确认信息,确认连接。

    UDP协议是无连接的通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说就是,当一台计算机向另一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用udp协议消耗的资源小,通信效率高,所以通常会用于音频、视频和普通数据的传输,例如开视频会议时都使用udp协议,因为这种情况即是偶尔丢失一两个数据包,也不会对接收的结果产生太大的影响。但是在使用udp协议传输数据时,由于udp面向无连接性,不能保证数据的完整性,因此在传输重要的数据时不建议使用udp协议。

    TCP的三次握手过程?为什么会采用三次握手,若采用二次握手可以吗?

    答:建立连接的过程是利用客户服务器模式,假设主机A为客户端,主机B为服务器端。
    (1)TCP的三次握手过程:主机A向B发送连接请求;主机B对收到的主机A的报文段进行确认;主机A再次对主机B的确认进行确认。
    (2)采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。失效的连接请求报文段是指:主机A发出的连接请求没有收到主机B的确认,于是经过一段时间后,主机A又重新向主机B发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机A第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机B,主机B以为是主机A又发起的新连接,于是主机B同意连接,并向主机A发回确认,但是此时主机A根本不会理会,主机B就一直在等待主机A发送数据,导致主机B的资源浪费。
    (3)采用两次握手不行,原因就是上面说的实效的连接请求的特殊情况。

    3. 注册广播有几种方式,他们的区别是什么?

    有两种方式,分别为在代码中的动态注册和在AndroidManifest.xml中的静态注册,动态注册的方式缺点是程序必须启动才能收到广播,而在静态注册的方式不需要程序启动就能就能接受广播,比如监听开机的广播。注册广播是注意要添加相应的权限。

    动态注册的核心代码:

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("....");  //系统级别的广播或是自定义的广播
    MyReciver reciver = new MyReciver();
    registerReciver(reciver,intentFilter);
    
    
    
    public class MyReciver extend BroadcastReciver{
        @override
        public void onRecive(Context context,Intent intent){
            //接受到广播后的处理逻辑
        }
    }
    

    静态注册的核心代码:

    public class MyReciver extend BroadcastReciver{
        @override
        public void onRecive(Context context,Intent intent){
            //接受到广播后的处理逻辑
        }
    }
    
    <reciver andorid:name=".MyReciver">
        <intent-filter>
            <action android:name="andorid.intent.action.BOOT_COMPLETED"
        </intent-filter>
    </reciver>     
    

    4. Get请求和Post请求的区别

    • 1.get请求是从服务器获取信息,它并不能修改服务器的信息,所以它是安全的,一般不会产生副作用。而post一般是将数据发给服务器,它会更改服务器的信息。
    • 2.get使用url或cookie传参,而post是将数据放在请求体(request body)中。
    • 3.get的url会有长度的限制,而post的数据可以非常大。
    • 4.post比get安全,因为数据在地址栏不可见。

    上面是网上流传最多的说法,但实际并不完全正确:
    1.get和post请求是http协议定义的,http没有要求如果是get请求,请求参数就必须放在url中,而不能放在body中;也没有规定如果是post请求,请求参数就必须放在请求体中,那么网上的答案是怎么来的呢,这只是我们的一种习惯用法,它并不是两者的区别。

    5.Android中创建线程的两种方式及优缺点?

    方法一:继承java.lang.Thread类,重写run()方法,调用start()方法开启
    方法二:实现java.lang.Runnable接口,并实现run()方法

    public class MyThread extends Thread{
            @Override
            public void run() {
                super.run();
                //线程内的逻辑处理
            }
     }
    
     MyThread thread = new MyThread();
     thread.start();
    
     public class MyThread implements Runnable{
          @Override
          public void run() {
            //线程内部处理逻辑  
          }
     }
    
    MyThread thread = new MyThread();
    new Thread(thread).start();
    

    优缺点:
    1.java中类仅支持单继承, 如果自定义线程类继承了Thread就不能继承其他类了,拥有其他类的方法,降低了扩展性。而如果你用实现Runnable的方式就可以避免这种情况。
    2.还有最重要的一点是,使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源共享。

    比如:一个火车站的售票系统,比如有100张车票,允许所有的窗口卖这100张票,那么每一个窗口相当于一个线程,但是处理的资源是同一个资源,即这100张车票。 那么方式一和方式二实现买票的逻辑有什么不同呢?

    public class Example {
        public static void main(String[] args){
            new TicketWindow().start();
            new TicketWindow().start();
            new TicketWindow().start();
            new TicketWindow().start();
        }
    }
    
    class TicketWindow extends  Thread{
            private int tickets = 100;
            @Override
            public void run() 
                while (true){
                    if (tickets>0){
                        Thread th = Thread.currentThread();
                        String name = th.getName();
                        System.out.print(name+"正在发售第"+tickets--+"张票");
                    }
                }
            }
     }
    

    运行结果是每张票都被打印了4次,其实这种写法是开启了4个卖票的程序,各自都卖100张票。这肯定是不符合现实的逻辑的,所以我们来看下另一种方式。

    public class Example {
        public static void main(String[] args){
            TicketWindow tw = new TicketWindow();
            new Thread(tw,"窗口1").start();
            new Thread(tw,"窗口2").start();
            new Thread(tw,"窗口3").start();
            new Thread(tw,"窗口4").start();
        }
    }
    
    class TicketWindow implements Runnable{
            private int tickets = 100;
            @Override
            public void run() {
                while (true){
                    if (tickets>0){
                        Thread th = Thread.currentThread();
                        String name = th.getName();
                        System.out.print(name+"正在发售第"+tickets--+"张票");
                    }
                } 
            }
    }
    

    这种方式只创建了一个TicketWindow对象,然后开启了4个线程,在每个线程都去调用TickeWindow对象中的run()方法,这样就确保了四个线程访问的是同一个tickets变量,共享100张车票。

    6.活动的启动模式

    怎样指定活动的启动模式: android:launchMode="singleTop" ,栈是遵从后进先出的原则
    四种启动模式,分别是:
    1、standard:标准模式,默认情况下活动都使用标准模式,在该模式下每当启动一个活动,它都会进入栈中,并处于栈定。这种模式下,无论栈中是否有需要启动的活动实例,系统都会创建一个活动实例进栈。
    2、singleTop:栈顶模式,该种模式下要启动一个活动如果发现已经已经是该活动,可以直接使用它,不需要再创建活动实例。
    3、singleTask:该种模式下,启动一个活动时如果发现栈中存在该活动的实例,也同样可以直接使用这个活动,不在重新创建实例。栈中的变化就是,这个活动上面的活动都退出栈,使这个活动处于栈顶。
    4、singleInstance:单实例模式,该种模式下活动会启用一个新的活动栈来管理这个活动。

    Paste_Image.png
    如上图所示,栈中有四个活动,想问怎样使A跳转到D,然后按返回键直接回到主界面,不回到A?
    答案:A,B,C都是默认模式,D设置singleTask模式
    注意:A,B,C都是默认模式,D设置singleInstance模式,这种方法是不行的

    7.ArrayList与LinkedList和Vector的区别

    ArrayList:基于数组结构,有角标索引,所以通过索引查询比较快,因为增删元素相当于底层重新创建数组,所以增删慢,线程不安全
    LinkedList:基于双向循环链表结构,增删元素只是改变了该元素节点的引用,所以增删比较快,查询慢,线程不安全
    Vector:基于数组结构,查询快,增删慢,线程安全

    LinkedList增删元素.png

    8.简述Handler机制

    Android系统规定主线程中不能做耗时的操作,只能在子线程进行,但是子线程不能更新ui,为了解决类似的问题,Android提供了handler机制,它是一个异步消息处理机制,我们可以在子线程做些耗时的操作,比如网络请求数据,得到数据后通过handler发送到主线程,进行相应ui的更新。
    Message:携带数据的消息
    MessageQueue:消息队列,由looper来创建它
    Looper:每个主线程都默认持有一个looper对象,它负责轮循messagequeue中的所有消息并回调消息处理函数

    当我们通过handler.send一个消息后,消息进入消息队列,looper没轮循一次,都会从中去除一个消息,交给handle.message方法处理,当messagequeue中没有消息了,线程则会阻塞停止等待新的消息进来。

    9.在项目中怎样对ListView优化

    1.在Adapter的getView方法中使用ConvertView,即ConvertView的复用,不需要每次执行getView方法时都inflate一个View,这样既浪费时间也消耗内存。
    2.使用ViewHolder,既不要不再getView方法中频繁的findViewById,这样更加消耗系统的开销。
    3.使用分页加载
    4.如果ListView中要加载图片需要进行缓存处理,我们可以借助开源的框架,自带缓存机制。
    5.当我们滑动ListView时不要加载图片,当我们停止滑动时再加载图片。

    10.说说线程池管理机制

    首先,线程池有一下优点:

    • 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
    • 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占资源而导致的阻塞现象。
    • 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

    线程池主要包括线程池和任务队列,如下图所示:

    线程池.png
    ThreadPoolExecutor的构造方法:
    public ThreadPoolExecutor(
       int corePoolSize,    //核心线程数
       int maxPoolSize,    //最大线程数
       long keepAliveTime,  //非核心线程闲置时的超时时长
       TimeUnit unit,    //制定keepAliveTime参数的时间单位,它是一个枚举
       BlockingDeque<Runnable> workQueue,   //线程池中的任务队列
       ThreadFactory threadFactory  //线程工厂,为线程池创建新线程
    ){ }
    
    

    ThreadPoolExecutor执行任务时遵循的原则:

    • ①如果线程池中的线程数未达到核心线程数,那么会直接启动一个核心线程来执行任务。
    • ②如果线程池中的线程数量已经达到或已经超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
    • ③ 如果在步骤2中无法将任务插入到任务队列中,可能是任务队列已满,这时如果线程数未达到线程池规定的最大值,就会立刻启动一个非核心线程来执行任务。
    • ④如果步骤3中的线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

    11.Fragment的生命周期

    onAttach()—onCreate()—onCreateView()—onActivityCreated()
    —onStart()—onResume()—onPause()—onStop()—onDestroyView()
    —onDestroy()—onDetach(),共11个生命周期。
    比如有A,B两个Fragmeng,可以切换显示,如下图所以。

    Paste_Image.png
    最开始A显示,A执行的方法有:
    onAttach()—onCreate()—onCreateView()—onActivityCreated()
    —onStart()—onResume()
    点击B,B Fragment显示,A隐藏,A要执行的方法:
    如果不执行addToBackStack()方法,A将会执行:
    onPause()—onStop()—onDestroyView()—onDestroy()—onDetach()
    如果添加了addToBackStack()方法,则会执行下面的方法:
    onPause()—onStop()—onDestroyView()
    这时按Back返回键,A重新显示,执行下面的方法:
    onActivityCreated()—onStart()—onResume()

    12.Service和IntentService的区别

    Android中的Service是用于后台服务的,当应用程序被挂到后台时,为了保证某些组件仍然可以工作,引入了Service。但是,需要强调的是Service不是独立的进程,也不是独立的线程,它是依赖于应用的主线程。因此不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。

    当我们编写耗时逻辑,不得不被Service管理的时候,就需要引进IntentService,IntentService是继承Service,包含了Service的全部特性,当然也包含Service的生命周期。那么与Service不同的是,IntentService在执行onCreate()方法的时候,内部开启了一个线程,去执行你的耗时操作。

    IntentService内部有个关键的方法onHandleIntent,它是什么时候被调用的呢?当IntentService执行onCreat()方法时会内部开启一个线程ThreadHandler,并获得了当前线程队列管理的looper,并且在onStart的时候,把消息置入了消息队列。

    @Override  
    public void handleMessage(Message msg) {  
        onHandleIntent((Intent)msg.obj);  
        stopSelf(msg.arg1);  
    }  
    
    @Override  
    public void onCreate() {  
        super.onCreate();  
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
        thread.start();  
      
        mServiceLooper = thread.getLooper();  
        mServiceHandler = new ServiceHandler(mServiceLooper);  
    }  
      
    @Override  
    public void onStart(Intent intent, int startId) {  
        Message msg = mServiceHandler.obtainMessage();  
        msg.arg1 = startId;  
        msg.obj = intent;  
        mServiceHandler.sendMessage(msg);  
    }  
    

    在消息被handler接受并且回调的时候,执行了onHandlerIntent方法,该方法的实现是子类去做的。

    13.写下单例模式

    http://www.jianshu.com/p/bfc62d9bddb0

    14、Android中常用的五种布局

    LinearLayout 线性布局
    RelativeLayout 相对布局
    FrameLayout 帧布局
    TableLayout 表格布局
    AbsoluteLayout 绝对布局

    15、Android里的动画有几种,它们的区别是什么?

    Android3.0后有3中动画,分别是帧动画(FrameAnimation)、补间动画(TweenAnimation)、属性动画(PropertyAnimation)。

    区别:

    1. 帧动画类似于幻灯片,由多张图片顺序播放而成
    2. 补间动画望文生义,就是在两点之间插入渐变值来平滑过渡。它包括透明动画、平移动画、缩放动画、旋转动画。补间动画只是在视觉上View在改变,实际位置并没有改变
    3. 属性动画和补间动画类似,区别是属性动画是真实改变位移。

    垃圾回收资料:https://juejin.im/post/59f929135188254897330937

    16、简述Android垃圾回收机制,垃圾回收算法有几种?

    垃圾回收意义:
    它使java程序员不需要时时刻刻关注内存管理分配的工作,垃圾回收机制会自动管理jvm的内存空间,将那些不再会用到的“垃圾对象”清理掉,释放出更多的内存空间。

    17、说说Android 对象四大引用?

    1. 强引用:java中最普遍的,只要强引用还在,垃圾回收器就永远不会回收掉被引用的对象。
    2. 软引用:描述一些非必须的对象,在系统内存不足时,这类对象会被垃圾回收器回收掉。
    3. 弱引用:描述一些非必须对象,如果GC发生了,无论系统内存是否足够,都会回收掉这类对象。
    4. 虚引用:持有虚引用的对象,就像没有任何引用一样,时刻都有可能被垃圾回收器回收掉。

    18、在Activity的onCreate方法中,开启一个线程并更新UI元素,如下代码,会报错吗?为什么,如果报错报什么错误?

    参考:https://juejin.im/entry/58133f59c4c97100553f1056

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
            tv = findViewById(R.id.tv_text);
            btn = findViewById(R.id.btn_button);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    tv.setText("哈哈");
                }
            }).start();
       }
    

    当你运行这段代码后发现并没有报错,可以正常修改UI,如果我们把代码稍微做下修改,看下结果:

    new Thread(new Runnable() {
             @Override
             public void run() {
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  tv.setText("哈哈");
              }
       }).start();
    

    运行后,你会发现程序崩溃了,我们看下崩溃日志:

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581) 
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
    

    CalledFromWrongThreadException :only the original thread that created a view hierarchy can touch its views.
    ViewRootImpl.checkThread()
    ViewRootImpl.requestLayout()
    以上是报错的三点重要信息,首先我们来查看下checkThread()的源码:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    

    到这里我们还有些不太清楚,但是大体知道了是这样的:
    Android系统在更新UI的时候会调用ViewRootImpl.checkThread()方法来检查当前线程是否是主线程。
    那我们开始时运行为啥没有报错,因为第一种写法,ViewRootImpl还没有创建,所以不会执行checkThread()方法。那我们要来看下什么时候创建ViewRootImpl呢?
    答案:ViewRootImpl是在WindownManagerGloable.addView()方法中创建的,在回调onResume方法后,ViewRootImpl就创建好了。

    相关文章

      网友评论

          本文标题:01.面试题

          本文链接:https://www.haomeiwen.com/subject/cixcwttx.html