安卓面试

字节跳动客户端

一面

1、http和https有什么区别,除了安全性外还有什么别的不同?

1.1 端口号不同,http80 , https 443

1.2 协议不同,http运行在TCP之上,明文传输客户端与服务端都无法验证对方的身份

        https运行于SSL之上,SSL运行在TCP上,是添加了加密和认证机制的HTTP

1.3 加密机制 ,http无

       https,因为非对称加密计算资源消耗量太大,所以在只在第一次握手的时候使用,用于传输对称加密的秘钥。之后的传输都是使用对称加密来进行加密的。

HTTPS和HTTP协议相比提供了

· 数据完整性:内容传输经过完整性校验

· 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥

· 身份认证:第三方无法伪造服务端(客户端)身份

其中,数据完整性和隐私性由TLS Record Protocol保证,身份认证由TLS Handshaking Protocols实现。

 

 

 

 

 

https通讯过程如上图所示:

1、客户端通过URL向服务端发起请求

2、服务端使用非对称加密创建通信的公钥和私钥

3、服务端将公钥发送给客户端

4、客户端对发送过来的公钥进行CA认证,CA认证通过客户端使用对称加密的算法生成通信的共同秘钥

5、使用公钥对通信秘钥进行加密传送给服务端

6、服务端通过私钥对传送过来的通信秘钥进行解密,获得通信秘钥

7、服务端通过通信秘钥对要发送的数据进行加密,发给客户端

8、客户端通过持有的通信秘钥对发送过来的加密信息进行解密,获得服务端传送过来的数据。

 

 

 

2、对称加密和非对称加密在https中的应用

因为非对称加密计算资源消耗量太大,所以在只在第一次握手的时候使用,用于传输对称加密的秘钥。之后的传输都是使用对称加密来进行加密的。

 

3、 操作系统内置证书的作用

应该是内置的CA的证书,通过CA去认证其他网站的正当性。

当服务器将数据包发过来是,客户端需要确认该服务端的身份,服务端发过来的数据包自带一个证书,但是客户端无法验证该证书的正确性,需要向CA认证中心求证该证书是否正确,这样就组成了一个信任链。操作系统中内置的证书应该是CA的证书。

 

4、虚拟地址及其作用

 

5、进程和线程的区别

进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位

像内存,摄像头等资源操作系统是以进程为单位进行分配的,进程之间进行通信比较麻烦需要使用特定的进程间通信机制如Binder等。线程是进程中的执行单位,每个进程至少有一个线程,通常是主线程,主线程有操作系统创建的,其他的线程都是有主线程进行创建的。通常意义上的高级语言的线程如Java的线程和操作系统的线程是不一样的,Java线程无法直接操作系统资源,只有挂靠了系统线程后才能进行操作。也就是说只有系统线程才是真的线程。同一进程的线程是共享进程资源的,如内存等资源。

进程几大特性:

动态性:进程是动态运行的,也是进程的最基本特征。

并发性:

独立性:进程系统是进行资源分配、调度的基本单位

异步性:各进程以不可预估的速度向前推进,可能导致运行结果的不确定性

结构性

6、不同进程之间互相有影响吗?

是的,会相互影响。多道程序系统允许多个进程并发执行,在并发执行的过程中对争用系统资源,难免会互相产生影响。

对是否影响可以分为无关进程和相关进程。

无关进程指的是不同进程在逻辑上没有联系,不共享变量,是各自独立的在独立的数据空间进行操作。

相关进程指的是各进程之间有逻辑上的联系,存在共享变量,一个进程的执行可能依赖另一个进程的执行结果,它们之间是相互影响的。

7、不同线程之间会相互影响吗?

会的,同一进程里的线程它们共享同一份资源是会相互影响的。

不同进程之间的线程,如果有逻辑上的联系相互调用,也是会互相影响的。

8、线程退出进程会退出吗?

线程退出后进程不会退出,一般来说主线程退出后进程才会退出,系统会回收分配给进程的各种资源,同时在运行在系统中的其他线程在主线程退出后会跟着退出。

因为主线程的入口是main , main执行完了之后会调用exit().exit()会让整个进程终止,所有线程也就自然跟着退出了。

但是也有办法让主线程退出后,其他子线程继续执行。在main方法中调用pthread_exit()函数,来终止主线程的运行,在调用了

pthread_exit()后,后面的exit()函数不会执行,也就暂时不会回收系统资源,其他子线程能够继续运行直至结束。

9、线程池中的线程是如何执行任务的?

为什么要有线程池?

线程是稀缺的资源,它的创建和销毁是一个相对偏重且消耗资源的操作,且java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为了避免资源过度消耗需要设法重用线程执行多个任务。线程池就是一个线程缓存,负责对线程进行统一分配、调优和监控。

什么场景下使用线程池?

*当单个任务处理时间比较短的时候

*需要处理的任务数量很大的时候

 

线程池的优势

*重用存在的线程,减少线程创建,消亡的开销,提高性能。

*提高响应速度。当任务到达时,任务可以不需要等到线程的创建就能立刻执行。

*提高线程的可管理性,可统一分配,调优和监控。

 

 

线程池中的线程如何执行任务?

线程池刚开创建的时候,会规定核心线程的个数,辅助线程的个数,阻塞队列的容量

线程池中刚来时是没有线程的,当有任务过来进入到线程池,此时线程池就会创建线程去执行任务。直到核心线程的个数已经达到上限,此时再有新的任务过来是就往阻塞队列进行添加,线程池中的核心线程在执行完当前任务后会从阻塞队列中取出任务进行执行。当阻塞队列也满了的时候,此时再有新的任务过来会分配给辅助线程,当辅助线程也满了的时候,线程池会拒绝新的线程再添加进来。

 

 

 

 

 

 

二面

1、说一下Java里面的几种内部类

Java中可以在类中创建内部类

主要有四种:静态内部类、成员内部类、局部内部类、匿名内部类

1.1静态内部类

静态内部类使用static关键字修饰的定义在类内部的类

public class Outer {

    private static int radius = 1;

    static class StaticInner {
        public void visit() {
            System.out.println("visit outer static  variable:" + radius);
        }
    }
}

静态内部类可以访问外部类的所有静态变量,但是不能访问外部类的非静态变量。

静态内部类的创建方式为:Outer.StaticInner  inner = new Outer.StaticInner() ;

 

1.2成员内部类

定义在类内部,成员位置上的非静态类,也叫成员内部类

public class Outer {

    private static  int radius = 1;
    private int count =2;
    
     class Inner {
        public void visit() {
            System.out.println("visit outer static  variable:" + radius);
            System.out.println("visit outer   variable:" + count);
        }
    }
}

成员内部类可以访问外部类的所有方法和变量。

成员内部类的创建方式 Outer outer = new Outer();    outer.Inner inner = outer.new Inner();

需要先创建外部类,再用外部类的实例调用内部类来进行创建。

 

1.3 局部内部类

局部内部类是定义在外部类方法中的类,定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的内部类只能访问外部类的静态变量和静态方法。且局部内部类的创建方式只能在所定义的方法中 使用new关键字来进行创建。

public class Outer {

    private  int out_a = 1;
    private static int STATIC_b = 2;

    public void testFunctionClass(){
        int inner_c =3;
        class Inner {
            private void fun(){
                System.out.println(out_a);
                System.out.println(STATIC_b);
                System.out.println(inner_c);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
    public static void testStaticFunctionClass(){
        int d =3;
        class Inner {
            private void fun(){
                // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
                System.out.println(STATIC_b);
                System.out.println(d);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
}

创建方式如下:

 public static void testStaticFunctionClass(){
    class Inner {
    }
    Inner  inner = new Inner();
 }

 

1.4 匿名内部类

匿名内部类

public class Outer {

    private void test(final int i) {
        new Service() {
            public void method() {
                for (int j = 0; j < i; j++) {
                    System.out.println("匿名内部类" );
                }
            }
        }.method();
    }
 }
 //匿名内部类必须继承或实现一个已有的接口 
 interface Service{
    void method();
}

匿名内部类就是没有名字的内部类

匿名内部类有以下几个特点:

*/匿名内部类必须继承一个抽象类或者实现一个接口

*/匿名内部类不能定义任何的静态成员变量和静态方法

*/当所在方法的形参需要被匿名内部类使用时,必须声明为final

*/匿名内部类不能是抽象的,他必须要实现继承的类或者实现接口的所有抽象方法

匿名内部类的创建形式 new 类/接口{

}

 

为什么局部内部类和匿名内部类访问局部变量的时候,局部变量必须加上final进行修饰 ?

因为内部类和方法的生命周期不一样,当不用final修饰只是使用局部变量,当方法执行结束后,非final的局部变量就会被回收,但是此时内部类中可能还有对该局部变量的引用,如果不定义为final就会发生问题。

 

 

 

 

2 你刚刚说到实例内部类可以访问外部类的成员,请问这是为什么?

1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;

2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;

3 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。

 

简单来说,内部类在实例化时编译器会自动创建一个外部类类型的引用,该引用指向实例化的外部类。编译器会将该引用通过构造器传给内部类,这样内部类的对象就持有了外部类对象的引用,就可以通过该引用来访问外部类的成员变量及方法。

 

为什么局部内部类可以访问方法中定义的局部变量?

1.当方法中的局部变量是可确定的字面常量时

字面常量, 是因为他们被final修饰, 运行时不可改变, 当编译器在编译源文件时, 可以确定他们的值, 也可以确定他们在运行时不会被修改, 所以可以实现类似C语言宏替换的功能。也就是说虽然在编写源代码时, 在另一个类中访问的是当前类定义的这个变量, 但是在编译成字节码时, 却把这个变量的值放入了访问这个变量的另一个类的常量池中, 或直接将这个变量的值嵌入另一个类的字节码指令中。 运行时这两个类各不相干, 各自访问各自的常量池, 各自执行各自的字节码指令。在编译方法中定义的内部类时, 编译器的行为就是这样的。 
2、当方法中的局部变量不是可确定的字面常量时

当方法中定义的内部类访问的方法局部变量的值, 不是在编译时能确定的字面常量时, 编译器会为内部类增加一个成员变量, 在运行时, 将对外部类方法中局部变量的访问。 转换成对这个内部类成员变量的方法。 这就要求内部类中的这个新增的成员变量和外部类方法中的局部变量具有相同的值。 编译器通过为内部类的构造方法增加参数, 并在调用构造器初始化内部类对象时传入这个参数, 来初始化内部类中的这个成员变量的值。 所以, 虽然在源文件中看起来是访问的外部类方法的局部变量, 其实运行时访问的是内部类对象自己的成员变量。 

 

 

 

为什么被方法内的内部类访问的局部变量必须是final的
上面我们讲解了, 方法中的内部类访问方法局部变量是怎么实现的。 那么为什么这个局部变量必须是final的呢? 我认为有以下两个原因:

1 当局部变量的值为编译时可确定的字面常量时( 如字符串“abc”或整数1 ), 通过final修饰, 可以实现类似C语言的编译时宏替换功能。 这样的话, 外部类和内部类各自访问自己的常量池, 各自执行各自的字节码指令, 看起来就像共同访问外部类方法中的局部变量。 这样就可以达到语义上的一致性。 由于存在内部类和外部类中的常量值是一样的, 并且是不可改变的,这样就可以达到数值访问的一致性。

2 当局部变量的值不是可在编译时确定的字面常量时(比如通过方法调用为它赋值), 这种情况下, 编译器给内部类增加相同类型的成员变量, 并通过构造函数将外部类方法中的局部变量的值赋给这个新增的内部类成员变量。

 

参考文献:https://blog.csdn.net/zhangjg_blog/article/details/19996629

 

 

3、java的引用,强引用、软引用、弱引用、虚引用

强引用,代表有对象正在使用这个引用,JVM不会回收它就算抛出异常也不会回收强引用,不管内存是否充足都不会回收强引用

软引用,当内存充足是不会回收它,内存不足时会回收软引用

弱引用,不管内存是否充足只要JVM的GC扫描到弱引用都会回收它

虚引用,虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

 

 

具体见参考文献:https://blog.csdn.net/baidu_22254181/article/details/82555485

 

 

 

4、

 

posted @ 2021-05-08 21:12  蚂蚁上树025  阅读(104)  评论(0)    收藏  举报