Java 面试 -Java基础

==与equals 的区别:

== 对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址

equals:object 中默认也是采用==比较,通常也会重写。

ConcurrentHashMap的扩容机制

1.7 版本

  1. 1.7版本的ConcurrentHashMap 是基于Segment分段实现的。
  2. 每个Segment相当于小型的HashMap
  3. 每个Segment 内部会进行扩容,和HashMap的扩容逻辑类似
  4. 先生成新的数组,然后转移元素到新的数组中
  5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8 版本

  1. 1.8版本的ConcurrentHashMap 不在基于Segment 实现
  2. 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
  3. 如果某个线程put时,发现没有进行扩容,则将key-value 添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
  4. ConcurrentHashMap 是支持多个线程同时扩容的
  5. 扩容之前也先生成一个新的数组,
  6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作。

ConcurrentHashMap 原理:Jdk1.7和Jdk1.8版本的区别

jdk7:

数据结构:ReentrantLock+Segment+HashEntry ,一个Segment中包含一个HashEntry 数组,每个HashEntry又是一个链表结构

元素查询:二次hash,第一个Hash定位到Segment,第二次Hash定位到元素所在的链表头部

:Segment分段所,Segment继承了ReentrantLock,锁定操作的Segment,其他Segment不受影响,并发度为Segment的个数,可以通过构造函数中指定,数组 扩容不会影响其他的Segment

Get 方法无需枷锁,volatile 表征

Jdk8:

数据结构:synchronized+CAS+Node+红黑树。Node的val和next都用volatile修饰,保证可见性,查找、替换、赋值都使用CAS

:锁链表的Head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时 ,阻塞所有的读写操作,并发扩容

读操作无锁:

​ Node的val和Next使用volatile修饰,读写线程对该变量相互可见

​ 数组使用volatile修饰,保证扩容时被读线程感知,

CopyOnWriteArrayList的底层原理是怎样的?

CopyOnWriteArrayList 是线程安全的,写的时候会枷锁,然后串行添加数据。

  1. 首先CopyOnWriteArrayList 内部也是通过数组来实现的,在向CopyOnWriteArrayList 添加元素时,会复制一个新的数组,写操作在新的数组上进行,读操作在原数组上进行
  2. 并且,写操作会加锁,防止出现并发写入丢失数据的问题,
  3. 写操作结束之后会把原数组指向新数组
  4. CopyOnWriteArrayList 允许在写操作时来读取数据,大大提高了读操作的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList 会比较占内存,同时可能督导的数据不是实时更新的数据,所以不适合实时性要求很高的场景

简述final

最终的

  • 修饰类:表示类不可被继承
  • 修饰方法:表示方法不可被子类覆盖,但是可以重载
  • 修饰变量:表示变量一旦被赋值就不可以更改他的值

(1) 修饰成员变量

  • 如果final 修饰的是类变量,只能在静态初始化中指定初始值或者声明该类变量时指定初始值
  • 如果final修饰的时成员变量,可以在非静态初始块、声明该变量或者 构造中执行初始值

(2) 修饰局部变量

系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初始值(仅一次)

java 复制代码
public class FinalVar{
    final static int a = 0;//再声明的时候就需要赋值或者惊天代码块中赋值
    /**
    static{
    a = 0;
    }
    */
    final int b = 0;//再声明的时候就i需要赋值或者再代码块中赋值或者在构造器赋值
	/**
	{
	b = 0;
	}
	*/
    public static void main(String[] args){
        final int localA; //局部变量只声明没有初始化,不会报错,与final无关
        localA = 0;//在使用之前一定要赋值
        //localA = 1;但是不允许第二次赋值
    }
}

(3) 修饰基本类型数据和引用类型数据

  • 如果是基本类型的变量,则其数值一旦在初始化之后便不能更改
  • 如果是引用类型的变量,则在对其初始化之后不能再让其指向另一个对象。但是引用的值是可变的。
java 复制代码
public class FinalReferenceTest{
    public static void main(){
        final int[] iArr={1,2,3,4};
        iArr[2] = 3;//合法
        iArr = null;//非法,对iArr不能重新赋值
        final Person p = new Person(25);
        p.setAge(24);//合法
        p = null; //非法
    }
}

为什么局部的内部类和匿名内部类只能访问局部final 变量?

编译之后会生成两个class 文件,Test.clas \ Test1.class

java 复制代码
public class Test{
    public static void main(String[] args){
        
    }
    public void test(final int b){
        final int a = 10;
        new Thread(){
            public void run(){
                System.out.println(a);
                System.out.println(b);
            }
        }.start();
    }
}
class OutClass{
    private int age = 12;
    public void outPrint(final int x){
        class InClass{
            public void InPrint(){
                System.out.println(x);
                System.out.println(age);
            }
        }
        new InClass().InPrint();
    }
}

首先需要我们知道的一点是:内部类和外部类是处于同一级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。

这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了 ,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾,内部类对象访问量一个不存在的变量,为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的就是局部变量的"copy"这样就好像延长了局部变量的生命周期。

将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们再内部类中修改了成员变量 ,方法中的局部变量也跟着改变。怎么解决这个问题呢?

九江局部变量设置为final,对他初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性,这实际上也是一种妥协,使得局部变量与内部类建立的拷贝保持一致。

HashCode 与Equals

hashCode 介绍

hashcode()的作用是获取哈希码 ,也称为散列码,它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在Jdk的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(Key-value)它的特点是,能根据"键"快速的检索出对应的"值",这其中就利用到了散列码。

为什么要有HashCode()

以"HashSet如何检查重复"为例来说明为什么要有HashCode:

对象加入HashSet时,HashSet会先计算对象的Hashcode值来判断加入对应的位置。看该位置是否有值,如果没有,HashSet会假设对象没有重复出现,但如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同。如果两个相同,HashSet就不会让其加入操作成功,如果不同的话,就会重新散列到其他位置,这样就大大减少了equeals的次数,响应就大大提高了执行速度。

  • 如果两个对象相等,则hashCode一定也是相同的
  • 两个对象相等,对两个对象分别调用equals方法都返回true
  • 两个对象有相同的hashCode值,他们也不一定是相等的,
  • 因此,equals 方法被覆盖过,则hashCode方法也必须被覆盖
  • hashCode()是默认行为是对堆上的对象产生独特的值,如果没有重写hashCode(),则该class的对象无论如何都不会相等(即使这两个对象指向相同的数据)

HashMap的扩容机制原理

1.7 版本

  1. 先生成新数组
  2. 遍历老数组中每个位置上的链表上的每个元素
  3. 取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下表
  4. 将元素添加到新数组中去
  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8 版本

  1. 先生成新数组
  2. 遍历老数组中的每个位置上的链表或红黑树
  3. 如果是链表,则直接将链表中的每个元素重新计算下表,并添加到新的数组中去
  4. 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下表位置
    1. 统计每个下标位置的元素个数
    2. 如果该位置下的元素个数超过了8个,则生成一个新的红黑树 ,并将根节点添加到新数组的对应位置
    3. 如果该位置下的元素个数没有超过8个,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置
  5. 所有元素转移完成了之后,将新数组赋值给HashMap对象的table 属性。

HashMap和HashTable的区别及底层实现是什么?

区别:

  • HashMap 方法没有synchronized修饰,线程非安全,HashTable线程安全
  • HashMap 允许Key和value为null,而HashTable 是不允许的

底层实现:数组+链表实现 。

Jdk8开始链表高度到8,数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在、

  • 计算Key的Hash值,二次Hash然后对数组长度去摸,对应数组下表
  • 如果没有产生Hash冲突(下标位置没有元素),则直接创建Node存入数组
  • 如果产生Hash冲突,先进行equals比较,相同则取代该元素,不同,则判断链表高度插入聊表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表,
  • key为null,存在下标为0的位置

数组扩容 (每次扩容两倍)

Java异常体系

java 中所有的 异常都来自顶级父类Throwable

Throwable下有两个子类Execption和Error

Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行

Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常

RunTimeException常常发生在程序运行过程中,会导致当前线程执行失败,CheckedException常常发生在程序编译过程中,会导致编译不通过

JDK、JRE、JVM三者的区别和联系

JDK Java开发工具 JRE Java运行时环境 JVM Java虚拟机

JDK包含了JRE,JRE 包含了JVM

List和Set的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator提取出所有元素,再逐一遍历使get(int index)获取指定下标的元素
  • Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iteratror接口获取所有元素,在逐一遍历各个元素

String、StringBuilder、StringBuffer的区别

  • String 时不可变的, 如果尝试去修改,会生成一个新的字符串对象,StringBuffer和StringBuilder时可变的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下,StringBuilder效率会更高

String、StringBuilder、StringBuffer的区别及使用场景

  • String 是final 修饰的,不可变,每次操作都会产生新的String 对象,
  • StringBuffer 和StringBuilder都是在原对象上操作
  • StringBuffer 是线程安全的,StringBuilder是线程不安全的
  • StringBuffer 方法都是synchronizied修饰的
  • 性能:StringBuilder>StringBuffer>String

场景:

优先使用StringBuilder,多线程共享变量时使用StringBuffer

阿里二面 :Jdk1.7到JDK1.8 HashMap发生了什么变化(底层)

  • 1.7 底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率
  • 1.7链表插入使用的是头插法,1.8链表插入使用的是尾插法。因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用了尾插法
  • 1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为负载的哈思算法的目的就是为了提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU 资源

阿里二面:Jdk1.7到JDK1.8 Java虚拟机发生了什么变化

1.7中存在永久代,1.8中没有永久代,替换它的是元空间,元空间所占的内存不是在虚拟机内部,而是本地内存空间。这么做的原因是,不管是永久代还是元空间,他们都是方法区的具体实现,之所以元空间所占的内存改为本地内存,官方的说法是为了和JRockit同意,不过额外还有一些原因,比如方法区所存储的类信息通常比较难确定,所以对于方法方法区的大小也是比较难指定的,太小了容易出现方法溢出,太大了又会占用太多的虚拟机的内存空间,而转移到本地内存后则不会应i想虚拟机所占用的内存

阿里一面:说一下ArrayList和LinkedList的区别

  • 首先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList是基于链表实现的
  • 由于底层数据结构的不同,他们所是用的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同。
  • 另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当作双端队列(队列)来使用

阿里一面:说下HashMap的Put方法

Jdk1.8中:

  • 首先put方法接收到Key和Value时,会先利用Key进行哈希算法得到这个key对应的Hash值
  • 在通过这个Hash值与数组的长度-1及逆行操作得到一个数组下标
  • 再判断数组下标位置是不是空值,如果空着,则直接把key和value封装为一个Node对象并存入此数组位置
  • 如果次下标位置上非空,表示此位置上存在Node对象,那么则判断该Node对象是不是一个红黑树,如果是,则将key和Value封装为一个红黑树节点,并添加到红黑树中去,再这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
  • 如果此位置上的Node对象是链表节点,则将key和value封装为一个链表Node并插入到链表中去
  • 插入到链表后,会判断链表的节点个数是不是超过8个,如果查过则把当前位置的链表转化为红黑树
  • 插入链表使用的是尾插法,所以需要遍历链表,而在这个过程中也会去判断key是否存在,如果存在则更新value

接口和抽象类

  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final 类型的
  • 抽象类只能继承一个,而接口可以实现多个

接口的设计目的,是对类的行为进行约束(更准确的说是一种"有"约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以轻质要求实现不同的类具有相同的行为,它只约束了行为的有无,但不对如何实现行为进行限制。

而抽象类设计的目的,是代码服用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B ,这就达到了代码复用的目的,而A减B的部分,留给各个子类自己实现,正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时无法执行)

抽象类是对类本质的抽象,表达的是is a的关系,比如BMW is a car 抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现

而接口是对行为的抽象,表达的是like a的关系,比如Brid like a Aricraft(像飞行器一样可以飞,)但其本质上是is a Bird。接口的核心行为是定义行为,即实现类可以做什么,至于实现类的主体是谁,是如何实现的,接口并不关心。

使用场景

当你关注一个事务的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

抽象类的功能要远远超过接口,但是,定义抽象类的代价狗,因为高级语言来说(从实际设计上来说也是),每个类只能继承一个类,在这个类中,你必须继承或编写出其子类所有共性,虽然接口在功能上会弱化狠毒,但是它只是针对一个动作的描述,而且你可以在一个类中同时实现多个接口。在这集阶段会降低难度。

京东二面:泛型中extends和super的区别

  1. <? extends T> 表示包含T在内的任何T的子类
  2. <? super T> 表示包含T在内的任何T的父类

京东一面:深拷贝和浅拷贝

深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是示例对象的引用

  • 浅拷贝,只会拷贝基本数据类型,以及实例对象的引用地址,并不会复制一份用用地址所指向的对象,也就i是浅拷贝出来的对象,内部的类属性执行的是同对象
  • 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象

面向对象

什么是面向对象

对比面向过程,是两种不同的处理问题的角度

面向过程更注重事情的每一步骤及顺序,面向对象更注重事情有哪些参与者(对象),及各自需要做什么?

比如:洗衣机洗衣服

面向过程会将任务拆解成一系列的步骤(函数),1. 打开洗衣机,2放衣服,3,放洗衣粉,4 ,清洗,5烘干

面向对象会拆出人和洗衣机两个对象

人:打开洗衣机、放衣服、放洗衣粉

洗衣机:清洗、烘干

从以上例子能看出,面向过程比较直接搞笑,而面向对象更容易复用、扩展和维护

面向对象

封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项

内部细节对外部调用透明,外部调用无需修改或者关心内部实现

  1. JavaBean 的属性私有,提供getset对外访问,因为属性的赋值或者获取逻辑只能有JavaBean本身决定,而不能由外部胡乱修改。

    java 复制代码
    private String name;
    public void setName(String name){
        this.name = "moran_"+name; //name有自己的命名规则,不能由外部直接赋值
    }
  2. Orm框架

    操作数据库,我们不需要关心连接是如何建立的,sql是如何执行的,只需要引入Mybatis调用方即可

继承:继承基类的方法,并做出自己的改变或扩展

子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性的

多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同

继承、方法重写、父类引用指向之类的对象

txt 复制代码
父类类型 变量名 = new 子类对象;
变量名.方法名()

弊端:无法调用子类特有的功能

重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时

重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的一场访问小于等于父类,访问修饰符范围大于等于父类,如果父类访问修饰符为private则子类就不能重写该方法

java 复制代码
public int add(int a,String b);
public String add(int a,String b);
//编译报错
相关推荐
菠菠萝宝3 小时前
【Java八股文】13-中间件面试篇
java·docker·kafka·rabbitmq·canal·rocketmq·es
瓯雅爱分享5 小时前
基于Java后端与Vue前端的MES生产管理系统,涵盖生产调度、资源管控及数据分析,提供全流程可视化支持,包含完整可运行源码,助力企业提升生产效率与管理水平
java·mysql·vue·软件工程·源代码管理
不枯石5 小时前
Matlab通过GUI实现点云的最远点下采样(Farthest point sampling)
开发语言·图像处理·算法·计算机视觉·matlab
轩情吖6 小时前
Qt常用控件之QLabel(一)
开发语言·数据库·c++·qt·小程序·qlabel·桌面开发
hello_2507 小时前
k8s安全机制解析:RBAC、Service Account与安全上下文
java·安全·kubernetes
望获linux8 小时前
【实时Linux实战系列】实时安全 C++ 模式:无异常、预分配与自定义分配器
java·linux·服务器·开发语言·数据库·chrome·tomcat
码猩8 小时前
wordVSTO插件实现自动填充序号
开发语言·c#
多多*8 小时前
linux安装hbase(完)
java·分布式·算法·c#·wpf
野木香8 小时前
tdengine笔记
开发语言·前端·javascript