2018年4月17日,电话面试

首先,面试官很nice,非常感谢能够有这样一次面试经历,非常感谢面试官耐心的面试,让我得到锻炼和提升的机会。

下面就面试中的一些问题作出总结:

1.重视数据结构和常用算法,2.学习要有明确的路线,明确的目的,3.不能简单地仅仅调用SDK,而且还要了解代码的实现原理。

下面是面试的问题:

自我介绍(讲自己做的一些事情)

数据库(MySQL),如何设计数据库?

Web框架(Spring框架,IOC)?

    控制反转,对组件对象控制权的转移,从程序代码本身转移到了外部容器,通过容器来实现对象组件的装配和管理。IOC实现原理:Spring中的IoC的实现原理就是工厂模式加反射机制。工厂模式为创建对象提供接口,可分为简单工厂模式、工厂方法模式和抽象工厂模式;反射机制就是从一个对象找到一个类的名称,从实例化对象,调用getClass()方法,得到完整的“包”“类”名称。
hashMap与hashCode, hashCode发生碰撞该怎么处理?

    hashMap,无序存放,key不允许重复;hashCode方法给对象返回一个hash code值,被用于hash tables,如hashMapHashMap可以接受null键值和值,HashMap是非synchronized;HashMap很快,以及HashMap储存的是键值对;Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突,这是可以采取的方法有:开放地址法、再哈希法、链地址法(拉链法)以及建立一个公共溢出区

①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用拉链法构造的散列表中,删除结点的操作易于实现。

CurrentHashMap?

    3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap, ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以及如何锁,Hashtable的实现方式是锁整个hash表;而ConcurrentHashMap的实现方式是锁桶(或段),ConcurrentHashMap的读取并发,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,比起之前又更加快速

只有在求size等操作时才需要锁定整个表。而在迭代时,ConcurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,称为弱一致迭代器。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,而是在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。ConcurrentHashMap中主要实体类就是三个:ConcurrentHashMap(整个Hash表),Segment(桶),HashEntry(节点)。

Java内存模型?

    Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。

    在并发编程领域,有两个关键问题:线程之间的通信同步线程的通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种共享内存消息传递共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()notify()同步是指程序用于控制不同线程之间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。必须显式指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。Java线程之间的通信总是隐式进行.

    JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。在JVM内部,Java内存模型把内存分成了两部分:线程栈区和堆区.当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronization关键字,一个线程对共享对象的更新有可能导致其它线程不可见。在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。 编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。

关于Hadoop,是否使用过?

Python的闭包、装饰器和生成器?

    python中的闭包从表现形式上定义为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).闭包=函数块+定义函数时的环境,一个闭包就是调用了一个函数A,这个函数A返回了一个函数B。这个返回的函数B就叫做闭包。在调用函数A的时候传递的参数就是自由变量。

    装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数,装饰器就是一种的闭包的应用,只不过其传递的是函数。

    闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在,这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。

    生成器是一种用普通的函数语法定义的迭代器。任何包含yield语句的函数称为生成器,每次产生多个值,每次产生一个值,函数就会被冻结:即函数停在那点等待被重新唤醒。函数被重新唤醒后就从停止的那点开始执行。生成器是由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分。生成器的方法有:send()、yield()、throw()、close()等。

快速排序算法?

      快速排序采用了一种分治的策略,通常称其为分治法。将原问题分解为若干个规模的更小但结构与原问题相似的子问题,递归地解决这些子问题,然后将这些子问题的解组合为原问题的解。

    设当前待排序的无序区为R[low...high],利用分治法将快速排序的基本思想描述为:

    ①分解。在R[low...high]中任选一个记录作为基准(Pivot),以此基准将当前的无序区划分为左、右两个较小的子区间R[low...pivotpos-1]和R[PIVOTPOS+1...high],并使左边子区间中所有记录的关键字均小于或等于基准记录(pivot)的关键字pivot.key,右边子区间的所有记录的关键字均大于或等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无需参加后续的排序。

   划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(pivot=R[pivotpos]):

    R[low...pivotpos-1].keys≤R[PIVOTPOS].key≤R[pivotpos+1...high].keys,其中low≤pivotpos≤high

   ②求解:通过递归调用快速排序对左、右子区间R[low...pivotpos-1]和R[pivotpos+1...high]快速排序

   ③组合。因为当“求解”步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,“组合”步骤无须做什么,可看作是空操作。

   划分算法Partition

   第一步:(初始化)设置两个指针i和j,它们的初值分别为区间的下届和上界,即i=low,j=high。选取无序区的第一个记录R[i](即R[low])作为基准记录,并将它保存在变量pivot中。

   第二步:令j自high起向左描述,直到找到第一个关键字小于pivot.key的记录R[j]。将R[j]移至i所在的位置上,这相当于R[j]和基准[i](即pivot)进行了交换,使关键字小于基准关键字pivot.key的记录移到了基准的左边,交换后,R[j]中相当于pivot;然后令i指针自i+1位置开始向右扫描,直至找到第一个关键字大于pivot.key的记录R[i],将R[i]移动i所指的位置上,这相当于交换了R[i]和基准R[j],使关键字大于基准关键字的记录移到了基准的右边,交换后,R[i]中又相当于存放了pivot;接着令指针j自位置j-1开始向左描述,如此交替改变扫描方向,从两端各自往中间靠拢,直至i=j时,i便是基准pivot最终的位置,将pivot放在此位置上就完成了一次划分。


猜你喜欢

转载自blog.csdn.net/arpospf/article/details/79979587
今日推荐