Java 基础面试题 - 01

1、JDK 和 JRE 有什么区别?

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境

JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境

具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具
简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。

2、== 和 equals 的区别是什么?

==对于基本类型和引用类型的作用:

  • 基本类型:比较的是值是否相同;
  • 引用类型:比较的是引用是否相同;

equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较

3、两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

在Obeject类中,equals方法和hashCode区别

equals()比较的是两个对象的内存地址是否相等
hashCode()返回的是对象的内存地址

在重写这两个方法的时候,主要注意保持一下几个特性

如果两个对象的 equals()结果为true,那么这两个对象的 hashCode()一定相同;
两个对象的 hashCode() 结果相同,并不能代表两个对象的 equals()一定为true,只能够说明这两个对象在一个散列存储结构中。
如果对象的 equals()被重写,那么对象的 hashCode()也要重写。

在String类中,hashCode() 方法用于返回字符串的哈希码

字符串对象的哈希码根据以下公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) ++ s[n-1]
    
使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。空字符串的哈希值为 0

4、 六大存储区域

  • 寄存器(register)
这是最快的存储区,因为它位于处理器内部。
但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
  • 栈(stack)
1Java的堆是一个运行时数据区,类的对象从堆中分配空间。这些对象通过new等指令建立,通过垃圾回收器来销毁。
(2)堆的优势是可以动态地分配内存空间,需要多少内存空间不必事先告诉编译器,因为它是在运行时动态分配的。但缺点是,由于需要在运行时动态分配内存,所以存取速度较慢。
  • 堆(heap)
1)栈中主要存放一些基本数据类型的变量(byteshortintlongfloatdoublebooleanchar)和对象的引用。
(2)栈的优势是,存取速度比堆快,栈数据可以共享。但缺点是,存放在栈中的数据占用多少内存空间需要在编译时确定下来,缺乏灵活性。
  • 静态存储区(static storage)
静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里
  • 常量存储区(constant storage)
存放字符串常量和基本类型常量(public static final)。 常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变
  • 非RAM存储区
非RAM存储:硬盘等永久存储空间。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在

就速度来说,有如下关系:

寄存器 < 堆栈 << 其他

子类继承父类时的初始化顺序:

先初始化父类的静态代码
初始化子类的静态代码
初始化父类的非静态代码(普通成员变量)
初始化父类构造函数
初始化子类非静态代码(普通成员变量)
初始化子类构造函数

5、抽象类总结

abstract 来修饰类
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

6、接口总结

interface 来声明,一个类通过 implements 继承接口的方式,从而来继承接口的抽象方法
    
[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

接口特性

接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法

接口与类的区别

接口不能用于实例化对象
接口没有构造方法
接口中所有的方法必须是抽象方法
接口不能包含成员变量,除了 staticfinal 变量
接口不是被类继承了,而是要被类实现
接口支持多继承

接口的多继承

Java中,类的多继承是不合法,但接口允许多继承。
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
public interface Hockey extends Sports, Event

7、Java 集合框架

图片

总共有两大接口:Collection 和 Map ,一个元素集合,一个是键值对集合;

其中ListSet接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合;ArrayListLinkedList 实现了List接口,HashSet实现了Set接口;
HashMapHashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;

List 和 Set 区别

是否允许重复元素的存在,在List中允许插入重复的元素,而在Set中不允许重复元素存在,即使插入相同元素也会进行替换
List是有序集合,而Set是无序集合;List会保留元素插入时的顺序,而Set不会保留插入时的顺序
List可以通过下标来访问,而Set不能

Set 和 hashCode以及equals方法的联系

Java编程使用HashSet添加对象时,由于要符合Set的特点(没顺序,不重复)所以必须重写equals方法和hashCode方法
程序向HashSet中添加一个对象时,先用hashCode方法计算出该对象的哈希码
比较规则:
(1)如果该对象哈希码与集合已存在对象的哈希码不一致,则该对象没有与其他对象重复,添加到集合中!
(2)如果存在于该对象相同的哈希码,那么通过equals方法判断两个哈希码相同的对象是否为同一对象(判断的标准是:属性是否相同),若是相同对象则不添加,不是相同对象则添加

Arraylist 与 LinkedList 区别

ArrayList底层数据结构是数组,支持随机访问
LinkedList底层数据结构是双向循环链表,不支持随机访问
对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;
LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用

ArrayList 与 Vector 区别

ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问
Vector也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。

HashMap 和 Hashtable 的区别

HashMap 不是线程安全的
HashTable 是线程安全 
HashMap,其中键和值都是对象,并且不能包含重复键,但可以包含重复值
HashMap 允许 null key 和 null value,而 HashTable 不允许
//测试如下
Map<Object, Integer> data = new HashMap<>();
data.put(null,null);
//HashMap是可以运行的

Hashtable<Object, String> table = new Hashtable<>();
table.put(null,null);
//Hashtable抛出NullPointerException
HashMap允许将 null 作为一个 元素 的 key 或者 value,而 Hashtable 不允许。
HashMapHashtable的 contains 方法去掉了,改成 containsValue 和 containsKey(包含)
HashMap实现Map接口,而Hashtable继承Dictionary类(字典),实现Map接口
HashTable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步

HashSet和HashMap的区别

HashMapHashSet
HashMap实现了Map接口HashSet实现了Set接口
HashMap储存键值对HashSet仅仅存储对象
使用put()方法将元素放入map中使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值HashSet使用成员对象来计算hashcode值
HashMap比较快,因为是使用唯一的键来获取对象HashSet较HashMap来说比较慢

HashMap底层实现原理解析

img

HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的
如果定位到的数组位置不含链表(当前entry的next指向null,那么对于查找,添加等操作很快,仅需一次寻址即可;
如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,急需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过key对象的equals方法逐一比对查找
当我们往HashMap中put元素
第一步:将key,value封装到Node对象中(节点)
第二步:根据key的hashCode重新计算hash值,通过哈希表函数/哈希算法,将hash值转换成数组的下标
第三步:如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)O(logn)
当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。
当我们从HashMap中get元素
第一步:调用key的hashCode方法得出哈希值,并通过哈希算法转换成数组的下标
第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上
第三步:如果这个位置上什么都没有,则返回null。
如果这个位置上有单向链表,那么它就会拿着参数key和单向链表上的每一个节点的key进行equals
如果所有equals方法都返回false,则get方法返回null
如果其中一个节点的key和参数key进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value

哈希冲突

如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞
哈希冲突的解决方案有多种:
再散列函数法
开放定址法(发生冲突,继续寻找下一块未被占用的存储地址)
链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式,

HashSet 的实现原理

  • HashSet底层由HashMap实现
  • HashSet的值存放于HashMap的key上
  • HashMap的value统一为PRESENT
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
//HashSet构造函数
public HashSet() {
    map = new HashMap<>();
}
// add方法,向hashmap中存放,key存放值,value存放PRESENT(new 的Object类)
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

如何实现数组和 List 之间的转换

  • List转换成为数组:调用ArrayList的toArray方法。

  • 数组转换成为List:调用Arrays的asList方法。

Array数组

ArrayJava中的数组,声明数组有三种方式
int[] a=new int[10];
int a[]=new int[10];
int a[]={1,2,3,4};

Array 和 ArrayList 有何区别

Array可以容纳基本类型和对象,而ArrayList只能容纳对象。 
Array在定义的时候,必须指定这个数组的数据类型及数组的大小,也就是说数组中存放的元素个数固定并且类型一样。
ArrayList是动态数组,它可以动态的添加和删除元素;在不使用泛型时,可以添加不同类型的元素。
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。

在 Queue 中 poll()和 remove()有什么区别?

poll()remove() 都是从队列中取出一个元素
但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常

哪些集合类是线程安全的?

  • Vector:比ArrayList多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用
  • Stack:堆栈类,先进后出,继承Vector类
  • Hashtable:比HashMap多了个线程安全
  • enumeration:枚举,相当于迭代器

迭代器 Iterator 是什么?

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小
iterator()方法是java.lang.Iterable接口;  Collection接口继承Iterable接口
第一次调用Iteratornext()方法时,它返回序列的第一个元素
使用next()获得序列中的下一个元素
使用hasNext()检查序列中是否还有元素
使用remove()将迭代器新返回的元素删除

Iterator 和 ListIterator 有什么区别?

Iterator可用来遍历SetList集合,但是ListIterator只能用来遍历ListIterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。 
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值