JAVA面试基础 自整理

本文主要介绍了Java面试的基础知识点,涵盖String、List、Set、Map的使用、equals与hashCode的区别、类加载过程、servlet生命周期、垃圾回收机制、设计模式、线程安全与并发控制、数据库操作、Spring框架以及面试中常见的问题,如线程池的创建、缓存策略、MySQL索引优化等。通过对这些核心概念的解析,帮助读者理解和掌握Java开发中的关键技能。
摘要由CSDN通过智能技术生成

 Object类方法:

    clone() 创建并返回此对象的一个副体

    equals(object) 比较两个对象是否相等

   finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由该对象的垃圾回收器调用此方法

    getClass() 返回一个class对象,经常用于java反射机制

    toString() 返回该对象的字符串表示

    hashCode() 获取对象的哈希值

    notify() 唤醒在此对象监视器上等待的单个线程

    notifyAll() 唤醒在此对象监视器上等待的所有线程

    wait() 暂停(释放线程锁)

Java基本数据类型与包装类型

为什么有包装类型?

为什么存在这两种类型呢?

我们都知道在Java语言中,new一个对象存储在堆里,我们通过栈中的引用来使用这些对象;但是对于经常用到的一系列类型如int,如果我们用new将其存储在堆里就不是很有效——特别是简单的小的变量。所以就出现了基本类型,同C++一样,Java采用了相似的做法,对于这些类型不是用new关键字来创建,而是直接将变量的值存储在栈中,因此更加高效。

有了基本类型为什么还要有包装类型呢?

我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。

二者的区别:

1. 声明方式不同:

基本类型不使用new关键字,而包装类型需要使用new关键字来在堆中分配存储空间;
 
2. 存储方式及位置不同:

基本类型是直接将变量值存储在栈中,而包装类型是将对象放在堆中,然后通过引用来使用;
 
3. 初始值不同:

基本类型的初始值如int为0,boolean为false,而包装类型的初始值为null;
 
4. 使用方式不同:

基本类型直接赋值直接使用就好,而包装类型在集合如Collection、Map时会使用到。

String/StringBuffer/StringBuilder

    String 字符串常量,String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法

    StringBuffer 字符串变量,线程安全

    StringBuilder 字符串变量,线程不安全

    执行效率(大多情况下):StringBuilder>StringBuffer>String

string和 new string的区别

栈区  存  引用和基本类型,不能存对象,而堆区存对象。==是比较地址,equals()比较对象内容

(1) String str1 = "abcd"的实现过程:首先栈区创建str引用,然后在String池(独立于栈和堆而存在,存储不可变量)中寻找其指向的内容为"abcd"的对象,如果String池中没有,则创建一个,然后str指向String池中的对象,如果有,则直接将str1指向"abcd"";如果后来又定义了字符串变量 str2 = "abcd",则直接将str2引用指向String池中已经存在的“abcd”,不再重新创建对象;当str1进行了赋值(str1=“abc”),则str1将不再指向"abcd",而是重新指String池中的"abc",此时如果定义String str3 = "abc",进行str1 == str3操作,返回值为true,因为他们的值一样,地址一样,但是如果内容为"abc"的str1进行了字符串的+连接str1 = str1+"d";此时str1指向的是在堆中新建的内容为"abcd"的对象,即此时进行str1==str2,返回值false,因为地址不一样。

(2) String str3 = new String("abcd")的实现过程直接在堆中创建对象。如果后来又有String str4 = new String("abcd"),str4不会指向之前的对象,而是重新创建一个对象并指向它,所以如果此时进行str3==str4返回值是false,因为两个对象的地址不一样,如果是str3.equals(str4),返回true,因为内容相同

所以堆与栈的区别很明显:

            1.栈内存存储的是局部变量而堆内存存储的是实体;

            2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

            3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
 

static/final/static final

    static 声明属性、方法、代码段和内部类,与具体对象无关,不创建对象也能调用static修饰的属性、方法等

    final 声明属性、方法和类,表示属性不可变,方法不可覆盖,类不可继承

    static final 修饰的属性表示一旦给值就不可修改,并且可以通过类名访问

List/Set/Map

    List/Set实现了Collection接口

    List 有序;允许有重复对象;可以插入多个null元素

    Set 无序;不允许有重复对象;只允许一个null元素

    Map 键值对;只允许有一个null元素

Set ----HashSet:以哈希表的形式存放元素,插入删除速度很快

List ----ArrayList :数组      ----LinkedList:链表,队列,堆栈

ArrayList(泛型)/LinkedList

    都实现了List接口;线程不安全

    ArrayList是基于数组的数据结构;LinkedList是基于链表的数据结构

    当获取特定元素时,ArrayList效率比较快,它通过数组下标即可获取;LinkedList需要移动指针

    当存储元素与删除元素时,LinkedList效率较快,只需要将指针移动到指定位置增加或删除即可;ArrayList需要移动数据。

线程安全的List:Collections.synchronizedList与CopyOnWriteArrayList

Collections.synchronizedList:内部给synchronize加了个锁

CopyOnWriteArrayList:使用的是Lock,每增加一个元素都要copy一遍旧数组中的元素,生成一个新数组

List删除元素

List中的对象按某个字段进行排序

Java实现对List中的对象按某个字段进行排序 - 采国文 - 博客园

Set中的元素为什么不允许重复?

    它实际执行的是map方法,因为map中的key是不允许重复的

HashMap/HashTable

    HashMap工作原理:HashMap基于hashing原理,通过put()和get()方法存储和获取对象,当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashCode,然后找到bucket位置来存储对象,当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

    HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中,HashMap在每个链表节点中存储键值对象

    HashMap 实现了Map接口;线程不安全;可以接受null;无序的;

    HashTable 实现了Map接口;线程安全,多个线程可以共享一个HashTable;不接受null;

hashMap是有序还是无序的?怎么变为有序的?linkedHashMap呢?

    (1)hashMap是无序的;linkedHashMap是有序的;

    (2)

    

不同的键对象的hashCode相同时会发生什么?

    他们会存储在同一个bucket位置的链表中,键对象的equals()方法用来找到键值对

Map的哪些实现类可以能实现FIFO(First IN First Out 先进先出)?

    LinkedHashMap

Collection/Collections

    Collection是集合类的上级接口,继承与他有关的接口主要有LIst/Set

    Collections是针对集合类的一个帮助类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作

Collections.sort()    

equals和hashCode

    equals:比较对象的内存地址

   hashCode:用于散列数据的快速存取,如利用HashSet/HashMap/HashTable类来存储数据时,都是根据存储对象的hashCode值来进行判断是否相同的。

    重写equals()方法时尽量重写hashCode()方法

    为什么重写equals方法?

        如果不重写equals方法,那么比较的将是对象的引用是否指向同一块内存地址,重写之后的目的是为了比较两个对象的value值是否相等。特别指出利用equals比较八大包装对象(如int、float等)和String类(因为该类已重写了equals和hashCode方法)对象时,默认比较的是值,在比较其它自定义对象时都是比较的引用地址。

    为什么重写hashCode方法?

        就是为了保证同一个对象,保证在equals相同的情况下hashCode值必定相同。如果重写了equals而未重写hashCode方法,可能出现两个没有关系的对象的equals相同的(因为equals都是根据对象的特征进行重写的),但hashCode确实不相同的。

Java常用注解

    @Override 重写, 标识覆盖它的父类的方法

    @Controller 控制层

    @Service 用于标注业务层组件

    @Repository 用于标注数据访问组件,即DAO组件

    @RequestMapping 请求映射

    @ResponseBody 返回数据

    @Autowired 自动装配,将bean容器里的值自动注入到bean

    @Transactional 声明这service所有方法都需要事务管理。每一个业务方法开始时都会打开一个事务

    @Target 表示该注解用于什么地方

    @Method 常用的HTTP动词有下面五个(GET/POST/PUT/PATCH/DELETE)

自定义注解的实现

    使用自定义注解时,自动继承了java.lang.annotation接口,注解中声明的方法实际上是声明了一个注解参数,方法的名称就是      参数的名称,返回值类型就是参数的类型,可以通过default来声明参数的默认值

    自定义注解格式:

        public @interface 注解名{

            定义体

        }

    示例:

        (1)创建FieldTypeAnnotation类:

            import java.lang.annotation.ElementType;

            import java.lang.annotation.Retention;

            import java.lang.annotation.RetentionPolicy;

            import java.lang.annotation.Target;

            @Retention(RetentionPolicy.RUNTIME)

            @Target({ElementType.TYPE, ElementType.FIELD})

            public @interface FieldTypeAnnotation{

                Sting[] description();//没有指定default的,需要在注解的时候显式指明

            }

        (2)使用(在其他的实体属性上添加此注解)

            @FieldTypeAnnotation(description="名称")

            public String name;

Java类加载的过程

    五个步骤 加载 验证 准备 解析 初始化

    类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程

    JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。

servlet生命周期:

       1,初始化阶段  调用init()方法

  2,响应客户请求阶段  调用service()方

  3,终止阶段  调用destroy()方法

堆溢出和栈溢出的区别?

    堆溢出:不断的new一个对象,一直创建新的对象;

    栈溢出:死循环或者递归太深,递归的原因可能太大,也可能没有终止

    栈的实现是先入后出的(类似集装箱的那种货舱)

垃圾回收机制?

    (1)由于有垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域";

    (2)有效的防止内存泄漏,有效的使用可以使用的内存。

    GC(Gabage  Collection)工作原理:当创建对象时,GC就开始监视这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理heap(堆)中的素有对象。通过这种方式确定哪些对象是“可达的”,哪些是“不可以达的”。
    垃圾回收机制通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清理,我们虽然可以调用System.gc()让垃圾回收器运行,但依旧无法保证GC一定会执行。

栈与队列的区别?

    栈(Stack)和队列(Queue)是两种操作受限的线性表。

    (线性表:线性表是一种线性结构,它是一个含有n≥0个结点的有限序列,同一个线性表中的数据元素数据类型相同并且满           足“一对一”的逻辑关系。

“一对一”的逻辑关系指的是对于其中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。)

这种受限表现在:栈的插入和删除操作只允许在表的尾端进行(在栈中成为“栈顶”),满足“FIFO:First In Last Out”;队列只允许在表尾插入数据元素,在表头删除数据元素,满足“First In First Out”。

    相同点: 

        1.都是线性结构。

        2.插入操作都是限定在表尾进行。

        3.都可以通过顺序结构和链式结构实现。、

        4.插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。

        5.多链栈和多链队列的管理模式可以相同。

    不同点:      

        1.删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。

        2.应用场景不同;常见栈的应用场景包括括号问题的求解,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等;常见的队列的应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。

        3.顺序栈能够实现多栈空间共享,而顺序队列不能。

设计模式

    (1)工厂模式:主要解决接口选择的问题;定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

        使用场景:数据库访问,当用户不知道最后系统采用哪一类数据库以及数据库可能有变化时(如:hibernate换数据库只需换方言和驱动即可)

    (2)单例模式:保证系统中应用该模式的类一个类只有一个实例,即一个类只有一个对象实例

              使用场景:1、要求生产唯一序列号

                                2、web中的计数器,不用每次刷新都在数据库里面加一次,用单例先缓存起来

                                3、创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等

        (3)代理模式:提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

什么是线程安全?

    线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

    线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

什么是多线程?

    (1)一个进程是一个独立的运行环境,可以看做是一个程序,而线程可以看做是进程的一个任务,比如QQ是一个进程,而    一 个QQ窗口是一个线程

    (2)在多线程程序中,多线程并发可以提高程序的效率,CPU不会因为某个线程等待资源而进入空闲状态,它会把资源让给  其他的线程

    (3)用户线程就是我们开发程序创建的线程,而守护线程为系统线程,如JVM虚拟机中的GC

    (4)线程的优先级别:每一个线程都有优先级别,优先级别高的可以获取CPU资源使该线程从就绪状态转为运行状态,也可以自定义线程的优先级别

    (5)死锁:至少两个以上线程争取两个以上CPU资源,避免死锁就避免使用嵌套锁,只需要在他们需要同步的地方加锁和避 免无限等待

线程的状态

    (1)新建:new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。

    (2)等待:当线程在new之后,并且在调用start方法前,线程处于等待状态。

    (3)就绪:当一个线程创建对象后,其它线程调用它的start()方法,该线程就进入就绪状态。处于这个状态的线程位于java虚拟机的可运行池中,等待cpu的使用权。

    (4)运行状态:处于运行状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。

    (5)阻塞状态:阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。

        阻塞状态分为三种:

            1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

            2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。

            3、其他阻塞:运行的线程执行sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。

    (6)死亡状态:当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。

java实现线程两种方式:

    继承Thread类、实现Runnable接口

    同步:

        synchronized

        wait

        notify

wait()/sleep(long millis)

    wait() 暂停(释放线程锁)必须在synchronized内部使用;是Object类里的方法;wait()必须放在synchronized block中,否则会在program runtime时扔出"java.lang.IllegalMonitorStateException"异常;wait()使用notify()或者notifyAll()或者指定睡眠时间来唤醒当前等待池中的线程。

    sleep() 暂停(不释放线程锁,不安全会成死锁);是Thread类的static方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他线程对象的运行。但在sleep的过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入terminated状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

1、sleep是线程中的方法,但是wait是Object中的方法。

2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。

start()/run()

    start():启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。

    run():run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程。

synchronize的实现原理

    同步代码块是使用monitorenter和monitorexit指令实现的,任何的java对象都有一个monitor与之关联,当一个monitor被持有后,对象就处于锁定状态。

线程的join()方法

    join()等待线程执行完后才执行其他方法

    Thread t1 = new Thread(new Runnabled() {

        @Overried

        public void run(){

            System.out.println("t1");

        }

    });

    Thread t2 = new Thread(new Runnabled() {

        @Overried

        public void run(){

           System.out.println("t2");

        }

    });

    t1.start();

    t2.start();

    结果:t1和t2的打印顺序不固定

    t1.start();

    t1.join();//等待t1执行完,此时t2还启动

    t2.start();

synchronized/java.util.concurrent.locks.Lock

    Lock能完成synchronized所实现的所有功能

    Lock的性能更好

    Lock一定要求手工释放,并且必须在finally从句中释放

Spring的理解

    Spring基于IOC(控制反转)和AOP(面向切面编程)构架多层j2ee系统的框架,采用IOC可以容易实现bean的装配,提供了简洁的AOP并实现事物管理

    IOC:

        IOC优点:把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的)只需要修改XML就好了

        IOC管理和生产bean的容器,解决了代码的耦合性问题

    AOP:

        在AOP中正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增大,AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为将其封装为一个可重用的模块,

        这个模块就被命名为“切面”(aspect),切面将那些与业务无关却被业务模块共同调用的逻辑提取并封装了起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

        如:性能监控、日志记录、权限控制等

        AOP配置都必须定义在<aop:config>元素内部,在<aop:config>中每个切面都需要创建一个<aop:aspect>元素

        业务处理的主要流程是核心关注点,如日志记录、权限控制等为横切关注点

动态代理:JDK动态代理和CGLIB代理

        (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类

        (2)CGLIB是针对类实现代理,主要是指定的类生成一个子类,覆盖其中的方法(继承),目标类和方法不能声明为final类型

         从执行效率看,CGLIB代理效率较高

自动代理:

        (1)采用自动代理IOC容器一定要使用applicationContext

        (2)自动代理默认采用JDK动态代理机制

         创建代理对象需指定的三要素:

            (1)target 设定目标对象

            (2)proxyInterfaces 设定代理接口

            (3)interceptorNames 设定拦截器的名字

Spring管理bean的三种创建方式:

        (1)调用构造器创建bean

        (2)调用静态工厂方法创建bean

        (3)调用实例工厂方法创建bean

SpringMVC的执行流程:

        SpringMVC是由dispatchServlet为核心的分层控制框架,首先客户端发出一个请求,web服务器解析请求并去匹配dispatchServlet的映射url,如果匹配上就将这个请求放入到dispatchServlet,dispatchServlet根据mapping映射配置去寻找相对应的handler,然后把处理权交给找到handler,handler封装了处理业务逻辑的代码,当handler处理完后会返回一个逻辑视图ModelAndView给dispatchServlet,此时的ModelAndView是逻辑视图,所以dispatchServlet会通过ViewResource视图资源解析ModelAndView,然后将解析后的参数放到View中返回给客户端。

spring自动注入是单例还是多例?单例如何注入多例?

    (1)默认是单例(Singleton);

    (2)要实现多例,有两种实现方式:

        1)添加注解@Scope("prototype")

        2)配置文件中添加<bean scope="prototype">

1. 什么是单例、多例:

     所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;

     单例模式和多例模式说明:

     1

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值