专门为了面试而学的 Java

1. hashmap hashtable

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。


HashMap继承关系

hashmap底层就是一个数组,然后数组里每个元素装了个链表。
这个数组元素称为bucket桶


图例

HashTable和HashMap区别
第一,继承不同。

public class Hashtable extends Dictionary implements Map
public class HashMap  extends AbstractMap implements Map

第二,Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了,对HashMap的同步处理可以使用Collections类提供的synchronizedMap静态方法,或者直接使用JDK 5.0之后提供的java.util.concurrent包里的ConcurrentHashMap类。

第三,Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。

第四,两个遍历方式的内部实现上不同。
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。

第五,哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值( indexFor )。

第六,Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

2. 为什么用枚举实现的单例是最好的方式

  • 枚举写法简单
public enum Singleton{
    INSTANCE;
}
  • 枚举自己处理序列化
    在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。

3. jvm 内存模型

内存模型架构图
内存模型架构图
  • 程序计数器
    每个线程有要有一个独立的程序计数器,记录下一条要运行的指令。线程私有的内存区域。如果执行的是JAVA方法,计数器记录正在执行的java字节码地址,如果执行的是native方法,则计数器为空。
  • 虚拟机栈
    线程私有的,与线程在同一时间创建。管理JAVA方法执行的内存模型。
  • 本地方法区
    和虚拟机栈功能相似,但管理的不是JAVA方法,是本地方法
  • 方法区
    线程共享的,用于存放被虚拟机加载的类的元数据信息:如常量、静态变量、即时编译器编译后的代码。也称为永久代。
  • JAVA 堆
    线程共享的,存放所有对象实例和数组。垃圾回收的主要区域。可以分为新生代和老年代(tenured)。

4. jdk 线程池

package cn.gaialine.threadpool;  
  
import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;  
import java.util.concurrent.TimeUnit;  
  
/** 
 * 线程池测试用例 
 * @author yangyong 
 * 
 */  
public class TestThreadPool {  
    //线程池维护线程的最少数量  
    private static final int COREPOOLSIZE = 2;  
    //线程池维护线程的最大数量  
    private static final int MAXINUMPOOLSIZE = 5;  
    //线程池维护线程所允许的空闲时间  
    private static final long KEEPALIVETIME = 4;  
    //线程池维护线程所允许的空闲时间的单位  
    private static final TimeUnit UNIT = TimeUnit.SECONDS;  
    //线程池所使用的缓冲队列,这里队列大小为3  
    private static final BlockingQueue<Runnable> WORKQUEUE = new ArrayBlockingQueue<Runnable>(3);  
    //线程池对拒绝任务的处理策略:AbortPolicy为抛出异常;CallerRunsPolicy为重试添加当前的任务,他会自动重复调用execute()方法;DiscardOldestPolicy为抛弃旧的任务,DiscardPolicy为抛弃当前的任务  
    private static final AbortPolicy HANDLER = new ThreadPoolExecutor.AbortPolicy();  
  
    public static void main(String[] args) {  
        // TODO 初始化线程池  
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(COREPOOLSIZE, MAXINUMPOOLSIZE, KEEPALIVETIME, UNIT, WORKQUEUE, HANDLER);  
        for (int i = 1; i < 11; i++) {  
            String task = "task@"+i;  
            System.out.println("put->"+task);  
            //一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法  
            //处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务  
            //设此时线程池中的数量为currentPoolSize,若currentPoolSize>corePoolSize,则创建新的线程执行被添加的任务,  
            //当corePoolSize+workQueue>currentPoolSize>=corePoolSize,新增任务被放入缓冲队列,  
            //当maximumPoolSize>currentPoolSize>=corePoolSize+workQueue,建新线程来处理被添加的任务,  
            //当currentPoolSize>=maximumPoolSize,通过 handler所指定的策略来处理新添加的任务  
            //本例中可以同时可以被处理的任务最多为maximumPoolSize+WORKQUEUE=8个,其中最多5个在线程中正在处理,3个在缓冲队列中等待被处理  
            //当currentPoolSize>corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数  
            threadPool.execute(new ThreadPoolTask(task));  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
        threadPool.shutdown();//关闭主线程,但线程池会继续运行,直到所有任务执行完才会停止。若不调用该方法线程池会一直保持下去,以便随时添加新的任务  
    }  
}  
package cn.gaialine.threadpool;  
  
import java.io.Serializable;  
  
/** 
 * 任务task 
 * @author yangyong 
 * 
 */  
public class ThreadPoolTask implements Runnable,Serializable{  
    private static final long serialVersionUID = -8568367025140842876L;  
  
    private Object threadPoolTaskData;  
    private static int produceTaskSleepTime = 10000;  
      
    public ThreadPoolTask(Object threadPoolTaskData) {  
        super();  
        this.threadPoolTaskData = threadPoolTaskData;  
    }  
  
    public void run() {  
        // TODO Auto-generated method stub  
        System.out.println("start..."+threadPoolTaskData);  
        try {  
            //模拟线程正在执行任务  
            Thread.sleep(produceTaskSleepTime);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        System.out.println("stop..."+threadPoolTaskData);  
        threadPoolTaskData = null;  
    }  
      
    public Object getTask(){  
        return this.threadPoolTaskData;  
    }  
//---------------
put->task@1  
start...task@1  
put->task@2  
start...task@2  
put->task@3  
put->task@4  
put->task@5  
put->task@6  
start...task@6  
put->task@7  
start...task@7  
put->task@8  
start...task@8  
put->task@9  
Exception in thread "main" java.util.concurrent.RejectedExecutionException  
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)  
    at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)  
    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)  
    at cn.gaialine.threadpool.TestThreadPool.main(TestThreadPool.java:42)  
stop...task@1  
start...task@3  
stop...task@2  
start...task@4  
stop...task@6  
start...task@5  
stop...task@7  
stop...task@8  
stop...task@3  
stop...task@4  
stop...task@5  

从中可以看出task1和task2依次最先执行,这时候currentPoolSize=2达到了corePoolSize,task3、task4、task5被送入缓冲队列,达到了workQueue最大值3,task6、task7、task8开启新的线程开始执行,此时currentPoolSize=5达到了maximumPoolSize,task9、task10根据AbortPolicy策略抛出异常,不再执行task9和task10。10秒钟后task1、task2....依次执行完毕释放线程,开始执行队列里的task3、task4、task5,最后task3、4、5执行完毕,所有任务完成。

JDK根据ThreadPoolExecutor配置好的线程池

// 固定工作线程数量的线程池  
ExecutorService executorService1 = Executors.newFixedThreadPool(3);  
  
// 一个可缓存的线程池  
ExecutorService executorService2 = Executors.newCachedThreadPool();  
  
// 单线程化的Executor  
ExecutorService executorService3 = Executors.newSingleThreadExecutor();  
  
// 支持定时的以及周期性的任务执行  
ExecutorService executorService4 = Executors.newScheduledThreadPool(3);  

5. Java 内存模型

内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节。
JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个线程又存在自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
JMM的最初目的,就是为了能够支持多线程程序设计的,每个线程可以认为是和其他线程不同的CPU上运行,或者对于多处理器的机器而言,该模型需要实现的就是使得每一个线程就像运行在不同的机器、不同的CPU或者本身就不同的线程上一样。

6. volatile 与 synchronized

在Java中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:

  • 同步
    如用synchronized关键字,或者使用锁对象.
  • volatile
    使用volatile关键字
    用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道.

volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.

7. 面试中遇到的问题

求一个无序数组的中位数,白板写code

因为时间有限,没有多想,直接使用冒泡排序冒一半的数据,另一半保持无序。如果有更好的方法请告诉我谢谢!

数据库表的行转列

使用case when就可以实现了,但是要注意需要对每个case when做max,以及最后的group by,这样可以去除null值。

使用map = new hashmap 比 hashmap = new hashmap 的好处

一时间没想明白,因为觉得不会有人写成hashmap = new hashmap,就答了一下实现依赖抽象,方便复用和修改,减少栈存储之类乱七八糟的东西。。。如果有更好的答案也请告诉我谢谢😢

== 和 equals 的区别

Object类中的equals方法和“==”是一样的,没有区别,而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==不同”,所以,当自己创建类时,自动继承了Object的equals方法,要想实现不同的等于比较,必须重写equals方法。
"=="比"equal"运行速度快,因为"=="只是比较引用.

hashcode 和 equals 的具体实现方式

这真是个高频问题,从大四找实习面试到研究生找工作面试到两次跳槽一共几十场技术面试几乎全都问到了。唯一一次没问到的一次是因为我面的是算法工程师的职位,那次都在面机器学习的算法和实现……

默认 equals 方法直接调用了 ==

public boolean equals(Object obj) {
    return (this == obj);
}

String 改写了 equals

public boolean equals(Object anObject) {
         if (this == anObject) {
             return true;
         }
         if (anObject instanceof String) {
             String anotherString = (String) anObject;
             int n = value.length;
             if (n == anotherString.value.length) {
                 char v1[] = value;
                 char v2[] = anotherString.value;
                 int i = 0;
                 while (n-- != 0) {
                     if (v1[i] != v2[i])
                             return false;
                     i++;
                 }
                 return true;
             }
         }
         return false;
     }

hashCode是根类Obeject中的方法。默认情况下,Object中的hashCode() 返回对象的32位jvm内存地址。也就是说如果对象不重写该方法,则返回相应对象的32为JVM内存地址。

String类源码中重写的hashCode方法如下:

public int hashCode() {
    int h = hash;    //Default to 0 ### String类中的私有变量,
    if (h == 0 && value.length > 0) {    //private final char value[]; ### Sting类中保存的字符串内容的的数组
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

总结:

(1)绑定。当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

(2)绑定原因。Hashtable实现一个哈希表,为了成功地在哈希表中存储和检索对象,用作键的对象必须实现 hashCode 方法和 equals 方法。同(1),必须保证equals相等的对象,hashCode 也相等。因为哈希表通过hashCode检索对象。

(3)默认。
  ==默认比较对象在JVM中的地址。
  hashCode 默认返回对象在JVM中的存储地址。
  equal比较对象,默认也是比较对象在JVM中的地址,同==

reference:
http://blog.csdn.net/sdyy321/article/details/7407685
http://blog.csdn.net/fanaticism1/article/details/9966163
http://www.cnblogs.com/dingyingsi/p/3760447.html
http://www.cnblogs.com/xudong-bupt/p/3960177.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,478评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,825评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,482评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,726评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,633评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,018评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,168评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,320评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,264评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,288评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,995评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,587评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,909评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,284评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,862评论 2 339

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,157评论 11 349
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,681评论 0 11
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • Java SE 基础: 封装、继承、多态 封装: 概念:就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽...
    Jayden_Cao阅读 2,083评论 0 8
  • 1小强升职记 2把时间当做朋友 3白夜行 4活着 5百年孤独 6围城 7杜拉拉升职记 8暗时间 9拆掉思维的墙 1...
    若谷如是阅读 236评论 0 0