Java+Android笔试知识点(三)

ARP协议

在IP以太网中,当一个上层协议要发包时,有了该节点的IP地址,ARP就能提供该节点的MAC地址。

HTTP、HTTPS的区别

  1. HTTP所封装的信息时明文的;HTTPS使用TLS(SSL)进行加密
  2. HTTP缺省工作在TCP协议80端口;TTPS缺省工作在TCP协议443端口
  3. HTTPS的工作流程一般如以下方式:
    • 完成TCP三次同步握手
    • 客户端验证服务器数字证书,通过则进入步骤3
    • DH算法协商对称加密算法的密钥、hash算法的密钥
    • SSL安全加密隧道协商完成
    • 网页以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的hash算法进行数据完整性保护,保证数据不被篡改。

http VS https

TCP协议

三次握手

定义:防止了服务器端的一直等待而浪费资源

Java+Android笔试知识点(三)_第1张图片

四次挥手:TCP是全双工模式

Java+Android笔试知识点(三)_第2张图片

滑动窗口

停止等待、后退N、选择重传

拥塞控制

慢启动、拥塞避免、加速递减、快重传快恢复

Android的IPC通信方式

IPC通信方式:Binder、Content Provider、Socket

Android的跨线程通信

Handler、Content Provider、SharePreferences、共享文件、网络

Android中数据存储的方式

SharePreferences、Content Provider、SQLite数据库、文件(XML、JSON或其他文件形式)、网络存储

操作系统进程和线程的区别

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程的划分尺度小于进程,使得多线程程序的并发性高。
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  • 多进程的意义在于一个应用程序中,有多个执行部分可以同时执行。

操作系统中调度的最小单位元是线程,也叫轻量级进程,在一个进程里可以创建多个进程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。因此我们可以这样理解:

  • 进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
  • 线程:是进程中的单个顺序控制流,是一条执行路径。一个进程如果只有一条执行路径,则称为单线程程序。如果有多条执行路径,则称为多线程程序。

如何在UDP上层保证UDP的可靠性传输

要使用UDP来构建可靠的面向连接的数据传输,就要实现类似于TCP协议的超时重传,有序接收,应答确认,滑动窗口、流量控制等机制。实际上就是在传输层的上一层(应用层)实现TCP协议的可靠数据传输机制。

比如使用UDP数据包+序列号,UDP数据报+时间戳等方法,在服务器端进行应答确认机制,这样就会保证不可靠的UDP协议进行可靠的数据传输

基于UDP的可靠传输协议:RUDP、RTP、UDT

过度绘制优化

  1. 移除Window默认的Background:getWidow.setBackgroundDrawable(null);
  2. 移除XML布局文件中非必需的Background
  3. 减少布局嵌套(扁平化的一个体现,减少View数的深度,也就减少了View树的遍历时间,渲染的时候,前后期的工作,总是按View树结点来)
  4. 在引入布局文件里面,最外层可以用merge替代LinearLayout,RelativeLayout,这样把子UI元素直接衔接在include位置
  5. 工具:HierarchyViewer 查看视图层级

apk瘦身

  1. classes.dex:通过代码混淆、删掉不必要的jar包和代码复用等方式实现文件的优化
  2. 资源文件:通过Lint工具扫描代码中没有使用到的资源文件
  3. 图片资源:使用tingpng和webP,使用矢量图。
  4. 将不需要的.so文件去掉,目前主流app一般只放一个arm的so包

Serializable和Parcelable的区别

  1. P 消耗内存小
  2. 网络传输用S 程序内使用P
  3. S 方便进行数据持久化
  4. S 使用了反射,容易触发垃圾回收,效率比较慢

操作系统如何管理内存

  1. 使用寄存器对进程地址和物理地址进行映射
  2. 虚拟内存进行内存映射到硬盘上增大内存
  3. 虚拟内存是进行内存分页管理
  4. 页表实现分页,就是 页+地址偏移
  5. 如果程序的内存在硬盘上,那么就需要用页置换算法来将其调入内存中:先进先出、最近未(最少)使用等等

浏览器输入地址到返回结果发生了什么

  1. DNS解析
  2. TCP连接
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析渲染页面
  6. 连接结束

    Java类加载过程

    类加载的过程有三步,装载、连接、初始化。

    类从被加载到虚拟机内存开始,到被卸载出内存为止,它的生命周期包括:加载(Loading)、验证(Vertification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading) 7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)

  7. 加载时机:创建实例、访问静态变量或方法、反射、加载子类之前

  8. 加载:根据全类名获取文件字节流,将字节流转化为静态储存结构放入方法区、生成class对象
  9. 验证:验证文件格式、元数据、字节码、符号引用的正确性
  10. 准备:在堆上为静态变量划分内存
  11. 解析:将常量池中的符号引用转换为直接引用
  12. 初始化:初始化静态变量

线程同步

synchronized:保证了原子性、可见性、有序性

锁:保证了原子性、可见性、有序性

  1. 自旋锁:可以使线程在没有取得锁的时候,不会被挂起,而转去执行一个while/空循环

    • 优点:线程被挂起的几率减少,线程执行的连贯性加强。适用于对锁竞争不激烈,锁占用事件很短的并发线程
    • 缺点:浪费CPU时间,如果一个线程连续两次尝试获取自旋锁会引起死锁
  2. 阻塞锁:没得到锁的线程等待或者挂起,sync、Lock

  3. 可重入锁:一个线程可多次获取该锁,sync、Lock
  4. 悲观锁:每次去拿数据的时候都认为别人会修改,所以会阻塞全部其他线程,sync、Lock
  5. 乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断在此期间别人有没有更新这个数据,可以使用版本号判断等机制,CAS
  6. 显示锁和内置锁:显示锁用Lock,内置锁用synchronized
  7. 读-写锁:为了提高性能而提出的

volatile:保证了可见性、有序性,不能保证原子性

CAS

CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,Doug lea大神在java同步器中大量使用了CAS技术,鬼斧神工的实现了多线程执行的安全性。

CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。

存在的缺点:CAS存在一个很明显的问题,即ABA问题。

static关键字

同时被static修饰的成员变量和成员方法是独立于该类的,它不依赖于某个特定的实例变量,也就是说它被该类的所有实例共享。所有实例的引用都指向同一个地方,任何一个实例对其的修改都会导致其他实例的变化。

static变量

static修饰的变量我们称之为静态变量,没有用static修饰的变量称之为实例变量,它们两者的区别是:

  • 静态变量是随着类加载时完成初始化工作的,它在内存中仅有一个,且JVM只会为它分配一次内存,同时所有的类实例都共享同一个静态变量,可以通过所在的类名来访问它。

  • 实例变量则不同,它是伴随着实例的,每创建一个实例就会产生一个实例变量,它与该实例同生共死。

static方法

static修饰的方法我们称之为静态方法,我们通过类名对其进行直接调用。由于它在类加载的时候就存在了,它不依赖于任何实例,所以static方法必须实现,也就是说它不能是抽象方法(abstract)。

Static方法是类中的一种特殊方法,我们只有在真正需要他们的时候才会将方法声明为static。如Math类的所有方法都是静态static的。

static代码块

被static修饰的代码块,我们称之为静态代码块,静态代码块会随着类的加载一块执行。和静态变量同时进行初始化,根据书写顺序决定初始化的执行顺序。

static的局限性

Static确实是存在诸多的作用,但是它也存在一些缺陷。

  1. 它只能调用static变量。
  2. 它只能调用static方法。
  3. 不能以任何形式引用this、super。
  4. static变量在定义时必须要进行初始化,且初始化时间要早于非静态变量。

java提高篇(六)—–关键字static

内部类

将一个类的定义放在另一个类的内部,这就是内部类。

为什么使用内部类?

为什么使用内部类?在《Think in Java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地实现一个接口,所以无论外部类是否已经实现了某个接口,对于内部类都没有影响。

在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类(设计多个内部类)的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

内部类的特性

  1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立
  2. 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类
  3. 创建内部类对象的时刻并不依赖于外部类对象的创建
  4. 内部类并没有令人迷惑的“is-a”关系,它就是一个独立的实体
  5. 内部类提供了更好的封装,除了该外部类,其他类都不能访问
  6. *创建内部类对象时,此时内部类对象必定会捕获一个指向其外部类对象的引用,并以成员变量的方式记录下来*,通过OutClassName.this的方式获取外部类的引用。

内部类的种类

在Java中内部类主要分为成员内部类、静态内部类、局部内部类、匿名内部类

  1. 成员内部类
    成员内部类是最普通的内部类,它是外部类的一个成员,所以它可以访问外部类中的所有成员属性和方法;反过来,外部类要访问内部类的成员变量和方法,需要通过内部类实例来访问。

在成员内部类中要注意:
- 成员内部类中不能声明任何static变量和方法
- 成员内部类是依附于外部类的,所以只有先创建了外部类才能创建内部类
- 内部类间可以互相调用

public class Outer{
    private int size;
    public class Inner {
        public void dostuff() {
            size++;
        }
    }
    public void testTheInner() {
        Inner in = new Inner();
        in.dostuff();
    }
}
  1. 静态内部类
    静态内部类相当于其外部类的static成员,它的对象与外部类对象间不存在依赖关系,因此可以直接创建,不需要先创建外部类对象。也因为这样,它不能直接使用任何外部类的非static成员变量和方法。
public class Demo{
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();
        inner.dostuff();
    }
}
class Outer{
    private static int size;
    static class Inner {
        public void dostuff() {
            size++;
            System.out.println("size=" + size);
        }
    }
}
  1. 局部内部类
    局部内部类是定义在代码块中的类,它只在定义它的代码块中是可见的。

局部类有几个重要特性:
1. 仅在定义它的代码块中是可见的
2. 可以使用定义它的代码块中的任何局部final变量
3. 局部类不可以是static的,也不能定义static成员
4. 局部类不可以使用public、protected、private修饰,只能使用缺省(default)的
5. 局部类可以使abstract的

public class Parcel5 {  
    //定义在方法中
    public Destionation destionation(String str){  
        class PDestionation implements Destionation{  
            private String label;  
            private PDestionation(String whereTo){  
                label = whereTo;  
            }  
            public String readLabel(){  
                return label;  
            }  
        }  
        return new PDestionation(str);  
    } 
    //定义在作用域中
    private void internalTracking(boolean b){  
        if(b){  
            class TrackingSlip{  
                private String id;  
                TrackingSlip(String s) {  
                    id = s;  
                }  
                String getSlip(){  
                    return id;  
                }  
            }  
            TrackingSlip ts = new TrackingSlip("chenssy");  
            String string = ts.getSlip();  
        }  
    }  

    public void track(){  
        internalTracking(true);  
    }  

    public static void main(String[] args) {  
        Parcel5 parcel5 = new Parcel5();  
        Destionation d = parcel5.destionation("chenssy");  
    }  
}  
  1. 匿名内部类
    匿名内部类是局部内部类的一种特殊形式,不同的就是没有创建变量指向这个类的实例,而且具体的类实现也会写在这个内部类里面

注意:匿名类必须继承一个父类或实现一个接口。由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。

匿名内部类是没有构造方法的。

abstract class Person {
    public abstract void eat();
}
public class Demo {
    public static void main(String[] args){

        // 继承 Person 类
        new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        }.eat();
    }
}

java提高篇(七)—–详解内部类

为什么匿名内部类的形参必须是final的

在内部类中的属性和外部方法的参数(形参)两者看似是同一个参数,但实际上并不是,因为Java中是传值不传对象,在匿名内部类中对变量的改变并不会影响到外部的形参。然而站在程序的角度来看这两个根本就是同一个,所以为了保持参数的一致性,就规定使用final来保证形参不被改变。

另一种说法是,Java编译器支持了闭包,但支持地不完整,编译器编译的时候把外部环境方法的形参,拷贝了一份到匿名内部类里。在内部类中修改形参的值,也只是对拷贝的值进行修改,而不会影响到形参的值。为了避免数据不同步的问题,做出了匿名内部类只可以访问final的局部变量的限制。

最关键的原因就是:Java编译器实现的只是capture-by-value,并没有实现capture-by-reference

java为什么匿名内部类的参数引用时final? - 胖胖的回答 - 知乎
java提高篇(九)—–详解匿名内部类

实现多重继承的方式

  1. 接口
  2. 内部类

String字符串

        String s1 = "I" + "Love";
        String s2 = "I" + new String("Love");
        String s3 = "ILove";
        String I = "I";
        String s4 = I + "Love";
        String s5 = new String("ILove");
        String s55 = new String("ILove");
        String s6 = new String("I") + "Love";
        String Love = "Love";
        String s7 = I + Love;

        LogUtils.e("s1 == s2" + (s1 == s2));
        LogUtils.e("s1 == s4" + (s1 == s4));
        LogUtils.e("s1 == s3" + (s1 == s3));   //true
        LogUtils.e("s2 == s3" + (s2 == s3));
        LogUtils.e("s3 == s4" + (s3 == s4));
        LogUtils.e("s3 == s5" + (s3 == s5));
        LogUtils.e("s5 == s55" + (s5 == s55));
        LogUtils.e("s3 == s6" + (s3 == s6));
        LogUtils.e("s5 == s6" + (s5 == s6));
        LogUtils.e("s5 == s6" + (s5 == s6));
        LogUtils.e("s3 == s7" + (s3 == s7));
        LogUtils.e("s4 == s7" + (s4 == s7));

        LogUtils.e("str" + 1 + 2);              //str12
        LogUtils.e("str" + (1 + 2));            //str3

GC线程是否为守护线程?

是。

线程分为守护线程和非守护线程(即用户线程)。

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

守护线程最典型的应用就是 GC (垃圾回收器)

存在使得 i + 1 < i 的数吗?

存在。

假设 i 为整型int,那么当i为int能表示的最大整数时,i+1就溢出变成负数了,此时不就

Java创建对象的几种方式

  1. 使用new关键字 调用了构造函数
  2. 使用Class类的newInstance方法 调用了构造函数
  3. 使用Constructor类的newInstance方法 调用了构造函数
  4. 使用clone方法 没有调用构造函数
  5. 使用反序列化 没有调用构造函数

第2和3种方法使用了反射,具体实现如下:

  • 使用Class类的newInstance方法
Employee emp = (Employee) Class.forName("com.tin.chigua.Employee").newInstance();

//或者
Employee emp2 = Employee.class.newInstance();
  • 使用Constructor类的newInstance方法
Constructor constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();

事实上,使用Class类的newInstance()方法内部调用了Constructor的newInstance()方法,所以后者的效率更高,被更多框架所使用。

  • 使用反序列化
    当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象。在反序列化时,JVM创建对象并不会调用任何构造函数。

为了反序列化一个对象,我们需要让我们的类实现Serializeable接口

ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp4 = (Employee) in.readObject();

总结

除了第一个方法,其他四个方法全都转变为invokevirtual(创建对象的直接方法),第一个方法转变为了两个调用,new和invokespecial(构造函数调用)。

Android大厂面试题锦集(BAT TMD JD 小米)
Java三十个面试题总结

你可能感兴趣的:(笔试+面试)