Android岗Java常问面试题

自己用来记录,不是很完整,有需要可参考

Android岗位常问java知识点

  • 一、知识点
    • 1、面向对象理解,特点
      • 封装、继承、多态
      • 接口与抽象类的区别
    • 2、Final
    • 3、线程同步的方法
    • 4、volatile
    • 5、ArrayList内部结构,与LinkList区别
    • 6、类对象、实例对象
    • 7、静态变量和实例变量
    • 8、浅拷贝与深拷贝
    • 9、HashMap
    • 10、多线程相关
    • 11、JVM内存模型和类加载机制。
    • 12、OOM异常相关
    • 13、设计模式
    • 14、Java堆、栈和队列的区别
    • 15、线程的5个状态
    • 16、String、StringBuffer和StringBuilder的区别
    • 17、数组和链表的区别?Java 中有哪些数据结构是用它们实现的?
    • 18、重载和重写
    • 19、数据库索引结构
    • 20、String为什么是final修饰的
    • 21、Int与Integer
    • 22、synchronized修饰静态方法和普通方法的区别
    • 22、进程和线程的区别
    • 23、SharePreference原理及跨进程数据共享的问题
  • 二、算法
    • 1、翻转单链表
    • 2、判断数组中是否有重复元素
    • 3、判断单链表是否有环
    • 4、找二叉树中任意两个节点的最近的公共父节点

一、知识点

1、面向对象理解,特点

封装、继承、多态

万物皆对象
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

继承:一个类继承一个类时候,它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

多态:所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。

不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。例如将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。

接口与抽象类的区别

相同点

(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点

(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。

(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

(3)接口强调特定功能的实现,而抽象类强调所属关系。

(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

2、Final

(1)修饰类
当用final修饰一个类时,表明这个类不能被继承。
 在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

(2)修饰方法
final修饰一个方法,不能被重写
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。
  注:类的private方法会隐式地被指定为final方法。

(3)修饰变量
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改(必须初始化);如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

3、线程同步的方法

对于多线程的程序来说,同步指的是在一定的时间内只允许某一个线程访问某个资源

(1)互斥锁
互斥锁 是最常见的线程同步方式,它是一种特殊的变量,它有 lock 和 unlock 两种状态,一旦获取,就会上锁,且只能由该线程解锁,期间,其他线程无法获取。
在使用同一个资源前加锁,使用后解锁,即可实现线程同步,需要注意的是,如果加锁后不解锁,会造成死锁。

(2)读写锁
读写锁处于写锁定的状态,则在解锁之前,所有试图加锁的线程都会阻塞。
读写锁处于读锁定的状态,则所有试图以读模式加锁的线程都可得到访问权,但是以写模式加锁的线程则会阻塞;

(3)信号量
它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目

(4)条件变量
当线程在等待某些满足条件时使线程进入睡眠状态,一旦条件满足,就唤醒,这样不会占用宝贵的互斥对象锁,实现高效

4、volatile

用volatile修饰的变量对所有线程的可见性。

5、ArrayList内部结构,与LinkList区别

(1).ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
(2).对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。(参考数组与链表来思考)
(3).对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。

6、类对象、实例对象

(1)类对象:就是类本身
(2)实例对象: 由类实例化出来的对象

7、静态变量和实例变量

(1)静态变量:静态变量也叫做类变量,独立于方法之外的变量,有static修饰。
静态变量不属于某个实例对象,而是属于整个类。只要程序加载了类的字节码,不用创建任何实例对象,静态变量就回被分配空间,静态变量就可以被使用了。

(2)实例变量:实例变量同样独立也是独立于方法之外 的变量,但没有static修饰。
实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。

8、浅拷贝与深拷贝

(1)浅拷贝:增加了一个指针指向已存在的内存地址
(2)深拷贝:增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存

9、HashMap

10、多线程相关

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

11、JVM内存模型和类加载机制。

12、OOM异常相关

内存溢出:当前占用的内存+申请的内存资源超过Dalvik虚拟机的最大内存
内存抖动:短时间创建大量对象,然后再次释放,触发GC
内存泄露:进程中对象未被引用,但间接引用到其他对象,使GC无法对其起作用

(1)堆内存溢出

堆内存用来存储对象实例,只要不停的创建对象,且GC Roots和对象之间有可达路径避免垃圾回收,那么对象数量在超过最大堆的大小限制后很快会出现。

(2)方法区和元空间溢出

(3)直接内存溢出
直接内存并不是虚拟机运行时数据区域的一部分,并且不受堆内存的限制,但是受到机器内存大小的限制。常见的比如在NIO中可以使用native函数直接分配堆外内存就容易导致OOM的问题。

(4)栈内存溢出
栈是线程私有,它的生命周期和线程相同。每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,方法调用的过程就是栈帧入栈和出栈的过程。

在java虚拟机规范中,对虚拟机栈定义了两种异常:

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
如果虚拟机栈可以动态扩展,并且扩展时无法申请到足够的内存,抛出OutOfMemoryError异常

13、设计模式

单例模式、构造者模式、责任链模式

14、Java堆、栈和队列的区别

栈:先进后出,具有记忆能力,使用于括号求解
队列:先进先出,以进行有顺序的处理,如计算机系统中各种资源的管理

系统一般在内存中划分出两种不同的内存空间,一种是Stack(栈),一种是heap(堆)

它们的主要区别是:

stack是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小;heap是没有结构的,数据可以任意存放。因此,stack的寻址速度要快于heap。

每个线程分配一个stack,每个进程分配一个heap,也就是说,stack是线程独占的,heap是线程共用的。

15、线程的5个状态

(1)新建:new Thread()

(2)就绪:“可执行状态”,thread.start()

(3)运行:线程获取CPU权限进行执行,线程只能从就绪转到运行

(4)阻塞:线程因为某种原因放弃CPU使用权,暂时停止运行,分为3种情况:
(01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

(5)死亡:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

16、String、StringBuffer和StringBuilder的区别

String:不可变字符串
StringBuffer:可变字符串、效率低、线程安全
StringBuilder:可变字符序列、效率高、线程不安全

String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者能够被多次的修改,并且不产生新的未使用对象。

常见的:s=s+“aa”;
这种实际上是JVM新建了一个叫s的对象,将原来的s加上“aa”赋值给新的s,s实际上并没有被更改,所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

StringBuilder 类和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,多数情况下建议使用 StringBuilder类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

17、数组和链表的区别?Java 中有哪些数据结构是用它们实现的?

(1)数组的内存空间是连续的,创建数组的时候就会指定数组大小,且不能动态更改数组的大小,是因为创建时候已经分配了连续的固定内存空间,每个元素占用两个字节。

数组的随机访问速度快,增加和删除效率低。
链表的增删效率高,而且链表拓展性强

18、重载和重写

(1)重载:方法名不变,返回值和参数变
(2)重写:子类继承父类,重写继承父类的方法内容

19、数据库索引结构

20、String为什么是final修饰的

String类是用final关键字修饰,这说明String不可继承
为了线程安全

21、Int与Integer

Integer是int的包装类;int是基本数据类型;
Integer变量必须实例化后才能使用;int变量不需要;
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
Integer的默认值是null;int的默认值是0。

Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况

22、synchronized修饰静态方法和普通方法的区别

synchronized具有同步功能,是一种互斥锁,锁的是对象,synchronized修饰普通方法时,锁对象是this对象。修饰静态方法时,锁对象是字节码文件对象。

synchronized的缺点:
1、synchronized底层是由jvm实现,因此不能手动控制锁的释放,不如lock锁灵活,synchronized修饰的方法一旦出现异常,jvm保证锁会被释放(lock锁需要在finally中释放)。
2、synchronized是非公平锁,不保证公平性。

关键字 synchronized 可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作)。
synchronized 还可以保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代 volatile 功能,但是 volatile 更轻量,还是要分场景使用)

synchronized 包括三种用法:
修饰实例方法
修饰静态方法
修饰代码块

22、进程和线程的区别

地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。

资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。

健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

执行过程:
每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。

可并发性:
两者均可并发执行。

切换时:
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

23、SharePreference原理及跨进程数据共享的问题

SharedPreferences是Android提供的数据持久化的一种手段,适合单进程、小批量的数据存储与访问。因为SharedPreferences的实现是基于单个xml文件实现的,并且,所有持久化数据都是一次性加载到内存,如果数据过大,是不合适采用SharedPreferences存放的。而适用的场景是单进程的原因同样如此,由于Android原生的文件访问并不支持多进程互斥,所以SharePreferences也不支持,如果多个进程更新同一个xml文件,就可能存在同不互斥问题。

二、算法

1、翻转单链表

public class Main {

    public static void main(String[] args) {
        Node head=new Node(0);
        Node n1=new Node(1);
        Node n2=new Node(2);
        Node n3=new Node(3);

        head.setNext(n1);n1.setNext(n2);n2.setNext(n3);

        //打印反转前的
        System.out.println("反转前的链表:");
        Node h=head;
        while (h!=null){
            System.out.println(h.getData()+" ");
            h=h.getNext();
        }
        
//        System.out.println("递归(从后向前)反转后的链表:");
//        head=Reversel(head);
//        while (head!=null){
//            System.out.println(head.getData()+" ");
//            head=head.getNext();
//        }

        System.out.println("从前向后反转后的链表:");
        head=Reversel2(head);
        while (head!=null){
            System.out.println(head.getData()+" ");
            head=head.getNext();
        }

    }

    /**
     * 递归方式反转链表
     * @param head
     * @return
     * 从后向前依次反转
     */
    public static Node Reversel(Node head){
        if (head==null||head.getNext()==null){
            return head;
        }
        Node reHead=Reversel(head.getNext());//先反转后面的
        head.getNext().setNext(head);//将当前结点的指针域指向前一结点
        head.setNext(null);//前一结点的指针域为null
        return reHead;//反转后新链表的头结点
    }

    /**
     * 遍历方式反转链表
     * @param head
     * @return
     * 从前往后一次调换指针方向
     */
    public static Node Reversel2(Node head){
        if (head==null){
            return head;
        }
        Node pre=head;
        Node cur=head.getNext();
        Node temp;
        while (cur!=null){
            temp=cur.getNext();
            cur.setNext(pre);//反转结点的指针域指向上一个结点
            //指针向下移动
            pre=cur;
            cur=temp;
        }
        head.setNext(null);
        return pre;
    }

}
class Node{
    private int data;
    private Node next;
    public Node(int data){
        this.data=data;
    }
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
}

2、判断数组中是否有重复元素

//先选择排序,再判断相邻元素是否有重叠
public static boolean judge(int[] a){
    for (int j=0;j

3、判断单链表是否有环

import java.util.Scanner;

public class Main {

    public static void main(String[] args){
        //建了一个循环链表
        Node n1=new Node(5);Node n2=new Node(2);Node n3=new Node(10);Node n4=new Node(7);
        n1.next=n2;n2.next=n3;n3.next=n4;n4.next=n2;
        System.out.println(isCircle(n1));
    }
    //从头结点出发 判断快慢指针所指的数据是否环
    public static boolean isCircle(Node head) {
        Node p1=head;
        Node p2=head;
        while (p2.next!=null&&p2.next.next!=null){
            p1=p1.next;
            p2=p2.next.next;
            if (p1==p2){
                return true;
            }
        }
        return false;
    }
}
class Node{
    int data;
    Node next;
    Node(int data){
        this.data=data;
    }
}

4、找二叉树中任意两个节点的最近的公共父节点

你可能感兴趣的:(java,android,面试)