电话面--通联数据
面试我的是女开发--嗯,╰(*°▽°*)╯惊讶又惊喜,因为历经大小20多次面试,没有一个是女的呀?
1.list/set/map
我有说map和set底层的数据结构是一样的,她问我是吗?给我问懵了?
List和set集合,Map集合的区别以及它们的实现类有哪些?有什么区别?
List 是可重复集合,Set 是不可重复集合,这两个接口都实现了 Collection 父接口。
Map 未继承 Collection,而是独立的接口,Map 是一种把键对象和值对象进行映射的集合,它的每一个元素都包含了一对键对象和值对象,Map 中存储的数据是没有顺序的, 其 key 是不能重复的,它的值是可以有重复的。
List 的实现类有 ArrayList,Vector 和 LinkedList:
ArrayList 和 Vector 内部是线性动态数组结构,在查询效率上会高很多,Vector 是线程安全的,相比 ArrayList 线程不安全的,性能会稍慢一些。
LinkedList:是双向链表的数据结构存储数据,在做查询时会按照序号索引数据进行前向或后向遍历,查询效率偏低,但插入数据时只需要记录本项的前后项即可,所以插入速度较快。
Set 的实现类有 HashSet 和 TreeSet;
HashSet的实现原理其实是HashMap
增:HashSet调用add方法增加元素,那HashSet是如何找到插入的位置呢?根据对象的hashCode方法和存储的数据的长度的与运算找到插入的位置(HashMap的底层实现是一个数组和链表实现的)。当调用的hashCode生成的值一样的时候就产生了冲突,这就需要在链表上增加元素。在这里有个非常重要的概念需要明白,什么是对象的相等性?在HashSet插入的对象都需要实现hashCode和equals方法,只有当hashcode相同和equal返回为true的时候才认为对象是相等的。HashSet也是用hashcode和equal来判断元素的重复性的。当两个对象的hashcode相同并且equals为false的时候就产生了冲突,需在链表上增加元素,和LinkedList的一样时间复杂度为O(n),如没有冲突时间复杂度为O(1)。所以在实现hashCode和equals需保证,尽量保证hashcode分散性一般是元素的属性乘以素数,并且equals相等的对象,hashcode也必须相同,不然程序会出现意想不到的情况。
删:更加对象的hashcode找到元素的位置,如果该hashCode桶产生了冲突,查找的原理和LinkedList一样,时间复杂度为O(n),如果没有产生冲突,时间复杂度为O(1)。
改:查了java的文档,貌似HashSet没有提供直接的修改操作。间接的做法是先删后增。
1、HashSet类:HashSet是采用hash表算法来实现的,其中的元素没有按顺序排列
2、TreeSet类:TreeSet是采用树结构实现(称为红黑树算法),元素是按顺序进行排列
3、LinkedHashSet类:LinkedHashSet正好介于HashSet和TreeSet之间,它也是一个hash表,但它同时维护了一个双链表来记录插入的顺序,
HashSet:内部是由哈希表(实际上是一个 HashMap 实例)支持的。它不保证 set 元素的迭代顺序。
TreeSet:TreeSet 使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序。
Map 接口有三个实现类:Hashtable,HashMap,TreeMap,LinkedHashMap;
Hashtable:内部存储的键值对是无序的是按照哈希算法进行排序,与 HashMap 最大的区别就是线程安全。键或者值不能为 null,为 null 就会抛出空指针异常。
TreeMap:基于红黑树 (red-black tree) 数据结构实现,按 key 排序,默认的排序方式是升序。
LinkedHashMap:有序的 Map 集合实现类,相当于一个栈,先 put 进去的最后出来,先进后出。
List 和 Map 区别?
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其 key 是不能重复的,它的值是可以有重复的。
2问我为什么在hashmap中插入和删除的时间复杂度是o(1),那向hashmap中存数据的时候怎么存的?
因为hashmap本身的数据结构是hash表+链表,因此,插入和删除如果涉及到链表插入和链表删除的话就是o(n),不涉及的话就是o(1),,,,所以在hashmap或者是hashset中插入或者删除本身如果都是在没有冲突时走的就是hash表,有冲突时走的就是就是链表,
总结:在hashset/hashmap上增加/删除,有冲突时是o(n),没有冲突时是o(1),有冲突的o(n)的时间是浪费在链表查找上,
链表查找o(n),增删o(1),,,hash表从增删查都是O(1)
3面向过程和面向对象的区别
面向过程是以事件流程为思想,面向对象是以参与事件的角色为思想进行编程
4模板方法设计模式你给我举个例子?
我没想到好例子,做饮料。。。。
模式的定义与特点
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下。
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
5单例模式懒汉饿汉的区别?其他单例实现方式?
https://www.cnblogs.com/zhaoyan001/p/6365064.html
恶汉:线程安全,因为在类初始化的时候就完成了单例的创建,从而不会出现线程安全问题
懒汉:存在线程安全问题
饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期 都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题,实现如下。
上边的两种是最常见的,顾名思义懒汉式和饿汉式,一个是拿时间换空间,一个是拿空间换时间,懒汉式只有我需要他的时候才去加载它,懒加载机制,饿汉式不管需不需要我先加载了再说,先在内存中开辟一块空间,占用一块地方,等用到了直接就拿来用.这两种是最基本的单例模式。
双重检查单例(DCL实现单例)
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
优点:资源利用率高,第一次执行方法是单例对象才会被实例化。
缺点:第一次加载时会稍慢,jdk1.5之之前有可能会加载会失败。
这种写法估计是我们在开发中最常用的,这次代码的亮点是是在getInstance()方法中进行了双重的判断,第一层判断的主要避免了不必要的同步,第二层判断是为了在null的情况下再去创建实例;举个简单的列子:假如现在有多个线程同时触发这个方法: 线程A执行到nstance = new Singleton(),它大致的做了三件事:
(1)、给Singleton实例分配内存,将函数压栈,并且申明变量类型。
(2)、初始化构造函数以及里面的字段,在堆内存开辟空间。
(3)、将instance对象指向分配的内存空间。