根据牛客网的面试经验的题进行的汇总,里面的内容是看别人的博客或者其东西进行汇总的,将这些的知识总结一下,方便自己查看和复习用
牛客网
Java基础(二)
JAVA notation
请你谈谈大O符号(big-O notation)并给出不同数据结构的例子
表示随着数据结果元素的增加,在最坏场景下的最好情况
Big O notation大零符号一般用于描述算法的复杂程度,比如执行的时间或占用内存(磁盘)的空间等,特指最坏时的情形。
补充【例子】
- 常量阶(O(1)):代码片段执行时间不随数据规模n的增加而增加,即使执行次数非常大,只要与数据规模无关,都算作常量阶【n=100,此时只执行1次】
- O(n):成线性增加【n=100,此时执行100次】
- 按量级递增排序:常量阶O(1)< 对数阶O(logn) < 线性阶O(n) < 线性对数阶O(nlogn) < 平方阶O(n²)…立方阶O(n³)…k方阶 < 指数阶O(2n次方) < 阶乘阶O(n!)
时间复杂度
- 加法原则【找最大的复杂度】【2n=+2,其中的两个2都可以省略,复杂度为f(n)】
- 乘法原则【两个复杂度的乘积】
空间复杂度
- 计算开辟的空间大小
Array
请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
可以将 ArrayList想象成一种“会自动扩增容量的Array”
Array数组
Array数组可以存放基本数据类型和对象,效率高,但长度固定
Array名称本身实际上是个reference,指向heap之内得某个实际对象
【代码测试】可以利用Arrays工具类进行操作【没有删除】sort()、parallelSort()、swap()、binarySearch()、fill()、copyOf()、hsshCode()…
ArrayList
ArrayList只能存放对象,指向堆内存的引用时Object类型,长度可以变化,但效率低
数组扩容是对ArrayList效率影响比较大的一个因素
【代码测试】add()、remove()、toArray()、replaceAll()、sort()、indexOf()…
Java值传递和引用传递
请你解释什么是值传递和引用传递?
一般认为,java内的传递都是值传递【代码测试】
值传递
在操作时,是将原对象进行拷贝,对拷贝以后的对象操作,不会改变原对象的值
引用传递
是将原对象的引用进行拷贝,改变拷贝对象的值,会改变原对象的值
JAVA数据类型
请你讲讲Java支持的数据类型有哪些?什么是自动拆装箱?
基本数据类型:byte、char、short、int、long、float、double、boolean
引用数据类型:String、对象…
拆箱:包装类->基本数据类型 intValue、自动拆箱
装箱:基本数据类型->包装类 包装类的构造器、valueOf、自动装箱
Java中除了float和double的其他基本数据类型,都有常量池
基本数据类型 | 对应的包装类 | 父类 |
---|---|---|
byte | Byte | java.lang.Number |
short | Short | java.lang.Number |
int | Integer | java.lang.Number |
long | Long | java.lang.Number |
char | Character | java.lang.Object |
floar | Float | java.lang.Number |
double | Double | java.lang.Number |
boolean | Boolean | java.lang.Object |
基本数据类型 到 包装类 | 包装类 到 基本数据类型 |
---|---|
使用包装类的构造方法 | 调用包装类类的xxxValue()方法 |
使用包装类内部的valueOf( )方法 | 自动拆箱 |
自动装箱 |
补充
包装
- 所有包装类都是final类型,因此不能创建他们的子类
- 包装类是不可变类,一个包装类的对象自创建后,他所包含的基本类型数据就不能被改变
- 在进行判断时,**[-128—127]**在常量池中
Integer i1 = new Integer(12);
Integer i2 = new Integer(12);
System.out.println(i1 == i2);//指向不同堆内存
Integer i3 = new Integer(128);
Integer i4 = new Integer(128);
System.out.println(i3 == i4);//指向不同堆内存
Integer i5 = 12;
Integer i6 = 12;
System.out.println(i5 == i6);//在常量池中[-128—127]
Integer i7 = -129;
Integer i8 = -129;
System.out.println(i7 == i8);//不在常量池中
Integer i9 = new Integer(128);//会自动拆箱
int a = 128;
System.out.println(i9 == a);//两个基本数据类型比较
计算机基础
请你解释为什么会出现4.0-3.6=0.40000001这种现象?
因为计算机无法计算十进制,在进行计算时,会先将十进制转化为二进制进行计算,这个过程出现了误差
请你讲讲一个十进制的数在内存中是怎么存的?
用补码的形式
补充
正数的原反补一样
负数的反码是原码的符号位不变,其余求反;补码是反码+1
Java基础
请你说说Lamda表达式的优缺点
优点
- 使表达更加简介
- 可以执行并行计算
缺点
- 对于新手来说不好理解
- 若不用并行计算,很多时候计算速度没有比传统的 for 循环快(并行计算有时需要预热才显示出效率优势)
- 不容易调试
代码测试
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
普通方法增加
public class TestCommon {
public static List<User> users() {
List<User> userList = Arrays.asList(
new User(1, "aaa", 24),
new User(2, "bbb", 34),
new User(3, "ccc", 66),
new User(4, "ddd", 19)
);
return userList;
}
//判断年纪大于30岁
public static List<User> filterAge(List<User> users) {
users = users();
ArrayList<User> arrayList = new ArrayList<User>();//用此方法生成ArrayList有那些方法,因为自己都重写了
for (User user: users) {
if (user.getAge() >= 30) {
arrayList.add(user);//用Arrays工具类生成的ArrayList【匿名内部类】没有add()、remove()...
}
}
System.out.println(arrayList);
return users;
}
//增加方法时的代码太多
public static void main(String[] args) {
//查询年纪大于30岁的员工
filterAge(users());
}
}
策略模式实现增加
//策略模式
public interface Design<T> {
//此接口定义一个过滤方法,为true过滤,false阻止
public boolean filter(T t);
}
public class DesignImpl implements Design<User> {
public boolean filter(User user) {
return user.getAge() >= 30;
}
//只用在此处增加方法代码
}
public class DesignFilter {
public List<User> filterUser(List<User> list, Design<User> design) {
ArrayList<User> arrayList = new ArrayList<User>();
for (User user: list) {
if (design.filter(user)) {
arrayList.add(user);
}
}
return arrayList;
}
}
public class TestDesign {
public static void main(String[] args) {
List<User> userList = Arrays.asList(
new User(1, "aaa", 24),
new User(2, "bbb", 34),
new User(3, "ccc", 66),
new User(4, "ddd", 19)
);
DesignFilter filter = new DesignFilter();
List<User> users = filter.filterUser(userList, new DesignImpl());
for (User user : users) {
System.out.println(user);
}
}
}
lambda表达式实现
DesignFilter(this.userList, (user) -> user.getAge() >= 30).forEach(System.out::println);
请你说明符号“==”比较的是什么?
基本数据类型
比较的是值
引用类型
比较的是地址,看是否指向同一个对象
补充
基本数据类型,包装类(除了Float、Double)以及声明式的String都放在常量池中
public boolean equals(Object var1) {
return this == var1;
}
本质上equals也是实现“==”,但是许多类会重写equals(),所以才会有值的比较,如果没有重写equals()那么比较的还是地址
请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?
如果不重写就是使用Object类中的,是由c/c++编写出来的
public native int hashCode();//由c/c++编写
说白了就是判断在集合中是否有相同的对象,用equals实现的代价太大,无论何时,对同一个对象调用hashcode()都应该产生同样的值
逻辑上,如果两个对象的equals方法返回是相等的,那么它们的hashcode必须相等;反之不一定成立c。
请你解释为什么重写equals还要重写hashcode?
重写hashcode是为了提高效率
重写后的equals是判断属性值是否一样,但其本质是判断地址,想达到的效果是判断属性值是否一样的前提先判断hashcode是否一样
public boolean equals(Object var1) {
return this == var1;
}
如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的
如果只重写equals,那么就会出现,不是同一对象但是因为有相同的个别属性(重写个别属性)【就是因为我们重写的那几个属性相同】,equals就会返回true
如果只重写hashcode,就会出现是不同对象【因为经过哈希算法导致hashcode可能相同】,但其特征相同,equals也会返回true,此时只是单纯的比较了地址
hashCode是所有java对象的固有方法,如果不重写的话,返回的实际上是该对象在jvm的堆上的内存地址,而不同对象的内存地址肯定不同,所以这个hashCode也就肯定不同了。如果重写了的话,由于采用的算法的问题,有可能导致两个不同对象的hashCode相同
- hashcode相等equals不一定相等
- hashcode不相等equals一定不相等
- equals相等hashcode一定相等
请你介绍一下map的分类和常见的情况
Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖),但允许值重复。
HashMap
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {}
HashMap最多只允许有一个记录的键为null,可以允许多个记录的值为null
HashMap 是一个最常用的Map,它根据键的Hashcode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的
HashMap非线程安全,不支持线程同步,如果同时有多个线程操作HashMap,可能会导致数据不一致
如果想要线程安全可以通过Collections.synchronizedMap(…){…}获得
补充
JDK1.8之后有原来的“链表+数组”变成了“链表+数组+红黑树”
当链表的节点数大于8时,会生成红黑树,红黑树又前后两个节点(prev、next)链表只用一个节点(next)
源码中对红黑树的查找,当查找的目标节点的hash值小于p节点,则向p的左边遍历,否则向p的右边遍历;key值的原来一样
Hashtable
public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, Serializable {}
不允许记录的键或者值为空
持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢
TreeMap
public class TreeMap<K, V> extends AbstractMap<K, V> implements NavigableMap<K, V>, Cloneable, Serializable {}
线程不安全
能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的
LinkedHashMap
public class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V> {}
保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时用带参数,按照应用次数排序
LinkedHashMap的遍历速度只和实际数据有关,和容量无关;而HashMap的遍历速度和他的容量有关
Java8
你知道Java8的新特性吗,请简单介绍一下
- lambda表达式:
- 方法引用:
- stream API:
- 默认方法:允许在接口中实现一个类
- Optional 类:
- Date Time API