Android阿里、京东、美团等大厂面试java篇

数据结构篇

一、JAVA数据类型

1.JAVA基本数据类型

前四种都属于整形。

2.引用数据类型

二、ArrayList、LinkedList、Vector的区别 

1.ArrayList:--基于数组,采用懒加载策略、允许对元素的快速随机访问以及在链表尾部进行插入或删除元素操作ArrayList适合随机访问,而不适合插入和删除。采用异步处理,线程不安全,性能较高。

2. Vector:--基于数组实例化对象时就初始化内部数组、支持线程同步,正是由于Vector保证了线程同步(锁的粒度太粗,将当前集合对象锁住,读读都互斥),所以导致了其性能较ArrayList低很多。

3. LinkedList:--基于双向链表实现支持对数据的随机插入和删除访问元素的话就比较慢,它必须从头遍历。

三、HashMap与Hashtable区别及HashMap实现原理

1、HashMap和Hashtable区别

(1)Hashtable线程安全的,基于老的Diactionary类实现,Hashtable put空的key、value则会报错,Hashtable则保留了contains,containsValue和containsKey三个方法,使用Enumeration的方式遍历。HashTable直接使用对象的hashCode ,Hashtable扩容时,将容量变为原来的2倍加1。

(2)HashMap非线程安全的,性能优于Hashtable。允许有一个空的key,和任意个空的value, HashMap把Hashtable的contains方法改成containsValue和containsKey使用Iterator遍历。HashMap需重新计算hash值。HashMap扩容时,将容量变为原来的2倍

2、HashMap的实现原理:底层主要是基于数组和链表来实现的, HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当发生碰撞时,对象将会储存在链表的下一个节点中

当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

四、List、Set、Map是否都继承自Collection接口? 他们的区别 

1、List:允许重复、可以插入多个null元素、有序。

2、Set:不允许重复无序、只允许一个 null 元素。

List, Set是否继承自Collection接口,Map不是。

五、String、StringBuffer与StringBuilder

1、String不可变类每一次改变都会创建新的String对象,创建、回收这样导致效率比较低

2、StringBuffer线程安全的

3、StringBuilder不是线程安全的,可变的变量,不会有频繁的创建、回收,性能要好,StringBuffer与StringBuilder底层都是通过一个char的数组来存储数据,而StringBuffer就是在StringBuilder的基础上一些方法加了synchronized关键字

4、equals和 == 区别

== 操作比较的是两个变量的值是否相等,比较的是栈。

Equals比较的是两个变量是否是对同一个对象的引用,即堆中的内容是否相同

 六、java中的数据存储机制   

(一)java的六种存储地址及解释

(1)寄存器(register)最快的存储区,位于处理器内部。但数量极其有限,所以寄存器由编译器根据需求进行分配

(2)堆栈(stack):位于通用RAM中,但通过它的堆栈指针可以从处理器哪里获得支持堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。仅次于寄存器。

(3)堆(heap):通用性的内存池(也存在于RAM中)用于存放所有的JAVA对象。堆的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。

(4)静态存储(static storage):“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。

(5)常量存储(constant storage):常量值通常直接存放在程序代码内部,安全的,永远不会被改变。

(6)非RAM存储:如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

(二)栈、堆、方法区存储的内容

1.堆区:

(1)存储的全部是对象,对象持有堆栈的引用

(2)jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 。

(3)它是JVM的一个内存模型

从上面的颜色可以看出:新生代和老年代实际是存放在我们的堆里面。

新生代:又分eden、S0、S1三个区,我们可以设定每个区的大小值,当生成一个对象的时候会判断某区域的大小,来选择存放的区域。提高内存的利用率。

永久代:1.8以前在我们的方法区。

1.8以后的内存模型:

Meta Space 是为了解决内存溢出的生。它可以扩容,

分代分区的原因:空间利用率和垃圾回收。

2.栈区:

(1)每个线程包含一个栈区,栈中只保存基础数据类型的值和对象以及基础数据的引用

(2)每个栈中的数据都是私有的,其他栈不能访问

(3)栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

3、方法区:

(1)又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

(2)方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

图示:

https://images2015.cnblogs.com/blog/1141604/201704/1141604-20170417172238024-19391955.png七、char类型的存储

1、char在内存占2个字节,与int类型进行运算时会查编码表得到对应的整数,进行运算,是无符号的数据类型,char的取值范围是0-65535。Char储存汉字,会查Unicode编码表。

2、java中的char是用2个字节进行存的,UTF-8可能是1~3个字节。例如:
char test = '庆',是用2 个字节进行存储的。
而它的UTF-U字节是e5ba86,是3个字节。

3、char是什么?UTF-8是什么?占几个字节?和Unicode什么关系?

char里面存的是什么?
System.out.println(Integer.toHexString(test));
打印出来的0x5e,0x86就是Unicode的码点Unicode是字符集,ASCII码也是字符集。

如何存储字符:
字符(人类认知) -> char(字符集) -> byte(计算机存储)
编码:UTF-8

4、UTF-8与UTF-16的区别:
UTF-8最小的单位是1个字节,UTF-16最小的单位是2个字节。在UTF-16里面也是需要2个字节的。Java char中存的是UTF-16。

5、理论上 String 变量长度限制为 int 类型的最大值,即 Integer.MAX_VALUE = 2^31 - 1 = 2147483647,约等于 2G。

 

                 Java系统知识点相关篇

一、JAVA虚拟机(JVM)的内存结构,以及它们的作用

(一)概述:保证java的跨平台性,不同的平台可以下载对应JVM来运行java文件。

(二)、JVM的运行过程

首先Java源文件经过前端编译器(javac或ECJ)将.java文件编译为Java字节码文件,然后JRE加载Java字节码文件,载入系统分配给JVM的内存区,然后执行引擎解释或编译类文件,再由即时编译器将字节码转化为机器码。主要介绍下图中的类加载器和运行时数据区两个部分。

 (三)、jvm的GC回收算法

1.标记 -清除算法:分为“标记清除”两个阶段:先标记、再统一回收掉所有被标记的对象。

主要缺点有两个:效率不高空间问题,会产生大量不连续的内存碎片,频繁触发GC。

2.复制算法:将内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

3.标记-整理算法:标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

4.分代收集算法:Java堆分为新生代和老年代,在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理标记-整理”算法来进行回收。

(四)JVM、DVM、ART的区别

1.执行的码不同

(1)JVM运行的是JAVA字节码:JAVA程序经过编译,生成JAVA字节码保存在class文件中,JVM通过解码class文件中的内容来运行程序。DVM虚拟机运行的是dex字节码,Dalvik字节码由JAVA字节码转换而来,并被打包到一个DEX(Dalvik Executable)可执行文件中,DVM通过解释DEX文件来执行这些字节码。ART虚拟机执行的是本地机器码,程序源码编译成目标机器码。

2、可执行文件的体积不同

JVM在把描述类的数据从class文件加载到内存时,需要对数据进行校验、转换解析和初始化,会产生大量的冗余信息。Dalvik可执行文件体积更小,

3、基于的载体不一样

JVM基于栈结构,程序在运行时虚拟机需要频繁的从栈上读取写入数据,这个过程需要更多的指令分派与内存访问次数,会耗费很多CPU时间。DVM基于寄存器,Dalvik虚拟机基于寄存器架构,数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快很多。

                   Java特征篇

(一)封装的概念

通过定义接口来访问类的私有属性—即 set/get方法,示例:

外部设置使用类里面的私有属性

接口就是:给外面提供方法来访问自己。

(二)面向对象-继承

1.继承的概念

用例演示:

如果子类继承父类,子类就拥有父类的一些属性和方法。

(三)面向对象-多态

  1. 概念

用例演示;

每个车都有自己的骑法,上面new一个car的子类,来实现摩托车的骑法。就是多态的用法。

(四)方法重载

  1. 概念

代码用例演示

重载中传入不定量参数的方法

不定量参数方法的变种

(五)方法重写

代码演示:

父类写一个方法,子类继承后重写,重写里可以做子类想干的事。

Java 的设计模式篇

一、单例模式的两种写法以及优缺点:

1、饿汉式:静态常量写法:

public class Singleton {

    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){

        return INSTANCE;

    }

}

静态代码块写法:

public class Singleton {

    private static Singleton instance;

    static {

        instance = new Singleton();

    }

    private Singleton() {}

    public static Singleton getInstance() {

        return instance;

    }

}

优点:

(1)空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

(2)饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。

2.懒汉模式:双重检查写法

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {

        if (singleton == null) {

            synchronized (Singleton.class) {

                if (singleton == null) {

                    singleton = new Singleton();

                }

            }

        }

        return singleton;

    }

}

优点

懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

(二)观察者模式

1.观察者模式的定义

2.使用过程

3.使用场景:

(三)工厂模式

                  Java中的系统异常体系篇

一、异常的来源

二、简介:

1、Throwable 类是 Java 语言中所有错误或异常的超类

2、Error:表示由 JVM 所侦测到的无法预期的错误,由于这是属于 JVM 层次的严重错误 ,导致 JVM 无法继续执行,不可捕捉到的。

3、Exception:表示可捕捉到的,Java 提供了两类主要的异常 :

(1)runtime exception 运行时异常:类型转换异常和数组越界,当出现这样的异常时,总是由虚拟机接管。系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run() 抛出 ,如果是单线程就被 main() 抛出 。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。

(2)IOException(输入输出异常):文件找不到异常和EOFException(当输入过程中意外到达文件或流的末尾时抛出的异常)。 

三、异常的分类:编译时错误和运行时错误

四、实现APP的全局异常捕获

1、新建一个类CrashHandler implements Thread.UncaughtExceptionHandler,以单例的方式初始化CrashHandler对象,并在application中初始化和获取。
2、重写uncaughtException()在此方法中添加处理逻辑

四、爱奇艺的xCrash框架行捕获Native异常

1、原理:信号机制,所有的崩溃都是编程错误或者硬件错误相关的,系统遇到不可恢复的错误时会触发崩溃机制让程序退出,如除零、段地址错误等。异常发生时,CPU通过异常中断的方式,触发异常处理流程linux把这些中断处理,统一为信号量,可以注册信号量向量进行处理。实际上xCrash在c底层捕获到native信号,收到native信号之后,在c层的信号处理函数,通过反射,调用java中的方法。

2、信号机制

https://pic1.zhimg.com/80/v2-792e0559464e45a92efa9e8ebb7ac5a5_hd.jpg

3、常见的信号量

https://pic2.zhimg.com/80/v2-fd171770eac4c565e8f6df3621c00647_hd.jpg

 四、分析native crash流程

1、集成和初始化。

2、指定目录中生成一个 tombstone文件

3、使用addr2line工具(原理:确定函数的在内存中的指令地址和源代码的对应关系)把backtrace报错日志定位到具体到源代码位置。

Android中的so文件是动态链,是二制文件

java中几个关键字

一、this、super关键字的使用

二、final

(1)final类:不能被继承,
(2)final方法:可以把方法锁定,防止任何继承类修改它的意义和实现,高效。编译器在遇到调用final方法时候会转入内嵌机制。

(3)final变量:表示常量,值一旦给定就无法改变!

三、static
 

(1)static变量: 静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便)

(2)静态方法:静态方法可以直接通过类名调用,任何的实例也都可以调用, static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。

(3)static代码块:也叫静态代码块,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

四、static和final一块用表示什么

static+final:静态常量,编译期常量,编译时就确定值。放于方法区中的静态常量池。可简单理解为“全局常量”。

                   Java的注解原理

  • 注解的简介

 注解是标记,也可以理解成是一种应用在类、方法、参数、属性、构造器上的特殊修饰符

二、注解作用有以下三种:

 第一种:生成文档,常用的有@param@return等。

 第二种:替代配置文件的作用,尤其是在spring等一些框架中,使用注解可以大量的减少配置文件的数量。

第三种:检查代码的格式,如@Override,标识某一个方法是否覆盖了它的父类的方法。

三、注解的底层实现原理:

使用反射实现的,注解和接口有点类似,不过申明注解类需要加上@interface,注解类里面,只支持基本类型、String及枚举类型,里面所有属性被定义成方法,并允许提供默认值。

四、我们自定义一个注解来感受下:

(一)运行时的注解:通过反射获取类和属性的注解。

定义注解类:

  @Target({ElementType.TYPE})

  @Retention(RetentionPolicy.RUNTIME)

  @Documented

  public @interface User {

     String name() default "张三";

  }

由于我们的注解是类注解,所以我们创建一个类

@User

 public class test {

}

我们需要通过反向代理去读取类中定义的注解

创建测试类,来读取:

public class testamin {

public static void main(String[] args) throws ClassNotFoundException{

        Class<?> classTest=Class.forName("com.test.test");

        Annotation[] ann=classTest.getAnnotations();

        for(Annotation aa:ann){

            User u=(User)aa;

            System.out.println(u.name());

        }

     }

}

(二)编译时的注解通过注解处理器来实现。底层还是通过反射机制。

1、注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。你可以注册你自己的注解处理器。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。

2、注解器的实现:

(1)自定义自己的注解器继承于AbstractProcessor,如下所示:

package com.example;

public class MyProcessor extends AbstractProcessor {

    @Override

    public synchronized void init(ProcessingEnvironment env){ }

 

    @Override

    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

    @Override

    public Set<String> getSupportedAnnotationTypes() { }

    @Override

    public SourceVersion getSupportedSourceVersion() { }

}

init(ProcessingEnvironment env): init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。

getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。你在这里定义你的注解处理器注册到哪些注解上。

getSupportedSourceVersion(): 用来指定你使用的Java版本。

在自定义的注解器,在回调方法中可以添加自己想要实现的逻辑。

(2)注册你的处理器

提供一个.jar文件,打包你的注解处理器到此文件中。并且,在你的jar中,你需要打包一个特定的文件javax.annotation.processing.Processor到META-INF/services路径下。把MyProcessor.jar放到你的builpath中,javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册MyProcessor作为注解处理器。

 

Java中的反射机制

一、反射:

在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。

二、获取方式

先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。

获取字节码文件对象的三种方式:

1、Class clazz1 = Class.forName("全限定类名");  

//通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。

2、Class clazz2  = Person.class;    

//当类被加载成.class文件时,此时Person类变成了.class,在获取该字节码文件对象,也就是获取自己,该类处于字节码阶段。

3、Class clazz3 = p.getClass();    

//通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段。

三.反射的原理:

java类的执行需要经历

(1)编译:.java文件编译后生成.class字节码文件
(2)加载:类加载器根据类名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例。
(3)连接:细分三步
  验证:格式(class文件规范) 语义(final类是否有子类) 操作
  准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。
  解析:符号引用转化为直接引用,分配地址

Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。

Java中的泛型

一、概念
泛型,即参数化类型。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

二、泛型有三种使用方式:泛型类、泛型接口、泛型方法

1、泛型类:

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型

//在实例化泛型类时,必须指定T的具体类型

public class Generic<T>{

    //key这个成员变量的类型为T,T的类型由外部指定 

    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定

        this.key = key;

    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定

        return key;

    }

}

2、泛型接口

//定义一个泛型接口

public interface Generator<T> {

    public T next();

}

当实现泛型接口的类,未传入泛型实参时:

/**

 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中

 * 即:class FruitGenerator<T> implements Generator<T>{

 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"

 */

class FruitGenerator<T> implements Generator<T>{

    @Override

    public T next() {

        return null;

    }

}

3、泛型方法

/**

* @param tClass 传入的泛型实参

 * @return T 返回值为T类型

 * 说明:

 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。

 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。

 */

public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,

  IllegalAccessException{

        T instance = tClass.newInstance();

        return instance;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhwadezh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值