美团面经
byte 1;boolean 1;char 2;short 2; int 4;float 4; double 8;long 8
Integer的缓冲池大小是-128-127,因此在此范围内使用的数据并不创建新的空间,每个包装类都有自己的缓冲值,Long的缓冲范围也是-128-127
hashmap结合数组结构和链表结构的优点,从而实现了查询和修改效率高,插入和删除效率也高的一种数据结构,在java8以前,其底层结构是数组+链表,java8以后是数组+链表+红黑树;当链表数量超过8的时候,会装换成红黑树,在红黑树节点低于6的时候会转换成链表,之所以不设置成相同的数字,是为了避免频繁转换结构带来的内存开销
HashMap的线程不安全,在put操作的时候,导致多线程数据不一致,而链表的死循环是由于resize引起的,不仅会引起死循环还会引起元素丢失;
其Put操作的具体步骤是:
首先将kv封装到节点对象中,然后它的底层会调用k的hashCode()方法得到hash值,通过哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把节点添加到这个位置上,如果说下标对应的位置有链表,此时就会拿着k与链表上每个节点的k调用equals方法进行比较,如果所有eqauls返回false那么这个节点就会被添加到链表的末尾,如果其中一个返回了true就会将节点的value值覆盖
其Get操作的实现原理是:
调用k的hashCode方法得到hash值,通过hash算法转换成数组的下标,如果这个位置什么都没有返回null,如果有单向链表,就会遍历该链表,让k进行equals操作,如果所有的equals方法都返回false,那也返回null,只要一个返回了true就应该返回value
比如现在有两个线程a和b,首先a希望插入一个kv到表中,首先计算记录要落到的hash桶的索引坐标,然后获取到桶里的链表头节点,并且遍历到了链表的最后一个元素,此时线程a的时间片用完了,线程b被调度得以执行,b成功的将记录插在了桶里,线程a再次被调度运行的时候,它依然认为当前元素是最后一个元素,继续了插入操作,这将导致b的数据被覆盖,这就导致了数据不一致行为;
一个线程在扩容的时候还没有完全分配完所有的元素,第二个线程就强行插入元素;其主要原因在于在移动到新的bucket的时候,HashMap不会将元素放在链表的尾部而是放在头部
hashtable,synchronizedMap,concurrentHashMap
其数据结构基本和HashMap雷同
在Java8以前,数组,链表+reentrantLock+segment,使用的是分段锁的概念,将数据一段一段的存储,当一个线程占用锁访问一个数据段的时候,其他数据段依然能被正常访问;它需要使用两次hash,第一次hash到对应segment第二次hash到对应的链表;put方法在指定的segment加锁,get方法使用volatile关键字保证可见性,不加锁,size方法是对其进行两次计算,如果结果不一致再加锁计算一次,一致就直接返回
在java8以后,采用的是数组,红黑树,链表+CAS+Synchronized的方法,以前是对segment加锁,现在是对数组元素加锁,锁的粒度更加细致了;put方法,如果putKey的位置已经存在链表了,采用的是synchronized加锁的方法,,锁的是当前链表否则采用的是CAS;get操作使用volatile保证可见性,不加锁.size方法会在更新链表的时候实时更新,所以也不加锁
继承父类不同:前者是AbstractMap后者是Dictionary(已经废弃),但是两者都实现了map接口
线程安全:前者不安全,后者安全
允许null值:前者允许kv为null,后者不允许
计算hash值的方式不同:前者先调用hashCode方法计算出来一个hash值,再将hash与右移16位后相异或,从而得到新的hash值;后者直接使用hashCode
扩容方式不同:hashmap要求每次扩容是原来的两倍,并且要求是2的幂次,后者扩容为两倍+1
解决hash冲突方式不同:冲突数大于8红黑树,小于6链表,hashtable只有链表
封装区别:java多了一个default关键字,表示同一包下可以访问
继承区别:c++可以继承多个类,并且可以指定public,protected,private继承,java中只能继承一个类,方式和public相似
一个对象实例化时 先去看伊甸园有没有足够的空间
如果有就不进行垃圾回收 ,对象直接在伊甸园存储.
如果伊甸园内存已满,会进行一次minor gc,然后再进行判断伊甸园中的内存是否足够
如果不足,则去看幸存者区的内存是否足够.
如果内存足够,把伊甸园部分活跃对象保存在幸存者区,然后把对象保存在伊甸园.
如果内存不足,向老年代发送请求,查询老年代的内存是否足够
如果老年代内存足够,将部分幸存者区的活跃对象存入老年代.然后把伊甸园的活跃对象放入存活区,对象依旧保存在伊甸园.
如果老年代内存不足,会进行一次full gc,之后老年代会再进行判断 内存是否足够,如果足够,如果不足 会抛OutOfMemoryError.
幸存者区的空间有两块,他们是相对的,分为from和to,发生一次minor gc之后两块的区域会互换
伊甸园区丢给幸存者区的对象,或是幸存者区丢给老年代的对象都是to区来的
引用计数(java中没有应用,Python中有使用),被引用+1,失去引用-1,为0的时候清除,存在循环引用问题
标记清除:标记是把所有活动的对象打上标记,清除是清除没有打上标记的对象,存在内存碎片问题
复制算法:一块内存分成两块,如果一边用完了就把所有的存活对象丢到另一块上面,然后对其进行整个清空,缺点是递归调用函数
标记整理:与标记清除类似,只是后续有整理内存的步骤,会把存活的内存移动到一端,然后清除掉边界外的内存,缺点是压缩需要时间成本
7种,新生代使用复制算法进行垃圾回收,老年代使用的是标记整理,特殊的是CMS-标记清除,G1是复制+标记整理算法
Serial收集器(新生代)-默认,以串行的方式执行,单线程收集器,它工作的时候所有线程都要停止工作,收集新生代垃圾,使用参数-XX:+Use SerialGC,它拥有最高的单线程收集效率
ParNew收集器(新生代),Serial的多线程版本,单核效率不如Serial,只有它可以和CMS收集器配合使用,-XX +UseParNewGC
Parallel Scavenge(新生代):多线程,吞吐量大,-XX +useParallelGC
Serial Old(老年代)-默认:Serial的老年代版本,-XX +useSerialOldGC
Parallel Old(老年代):关注吞吐量 --XX:+UseParallelOldGC
CMS(老年代):垃圾回收线程与用户线程同时工作:缺点在于:吞吐量低,无法处理浮动垃圾(标记之后在此次gc无法清理的垃圾),内存碎片
-XX:+UseConcMarkSweepGC
G1垃圾收集器:面向服务端的垃圾收集器,G1把堆划分成多个大小相等的独立区域,新生代老年代不再物理隔离,它只有并发标记的时候才不会stop the word其他时候都会停下来-XX:+UseG1GC
其相比于CMS的特点在于:
如何选择MyISAM和Innodb?多读少写:MyISAM;事务,多读多写:Innodb
MyISAM的优点:count算的快,表锁批量插入速度快,可以使用压缩表减少空间占用
一组操作要么同时成功,要么一起失败,他们是不可分割的工作单元,拥有ACID的特点
原子性:不可分割;一致性:a给b转账1000,a少1000,b多1000,这个操作是整体;隔离性:事务之间互不影响,不可以被其他事务干扰;持续性:事故提交之后,发生的变化是永久的
Mysql的默认隔离级别--可重复读
where和having都可以使用的场景
select goods_price,goods_name from sw_goods where goods_price > 100 select goods_price,goods_name from sw_goods having goods_price > 100
只能用where,不能用having
select goods_name,goods_number from sw_goods where goods_price > 100 select goods_name,goods_number from sw_goods having goods_price > 100 //报错!!!因为前面并没有筛选出goods_price 字
只能用having不能用where
select goods_category_id , avg(goods_price) as ag from sw_goods group by goods_category having ag > 1000 select goods_category_id , avg(goods_price) as ag from sw_goods where ag>1000 group by goods_category //报错!!因为from sw_goods 这张数据表里面没有ag这个字段
select distinct <select_list> from <left_table><join_type> join <right_table> on <join_condition> where <where_condition> group by <group_by_list> having <having_condition> order by <order_by_condition> limit <limit number>
1、from <left_table><join_type> 2、on <join_condition> 3、<join_type> join <right_table> 4、where <where_condition> 5、group by <group_by_list> 6、having <having_condition> 7、select 8、distinct <select_list> 9、order by <order_by_condition> 10、limit <limit_number>
关注type属性:all<index<range<ref<eq_ref<const<system,一般保证查询等级至少达到range,至少要是ref
关注extra属性中的:
设置手机号phone为主键(因为不设置为主键Innodb会自动生成一个,浪费空间),用户肯触发插入和删除操作,需要使用Innodb引擎,手机号不更新,不同国家的手机号位数不同,使用varchar类型,,可以按照范围进行分区,对于查询某个手机号是否存在可以加上布隆过滤器(bitmap)提高效率
//num=1就是线程1执行,num=2就是线程2执行,num=3就是线程3执行 private int num = 1; private Lock lock = new ReentrantLock();//创建锁对象 private Condition condition1 = lock.newCondition();//条件1 private Condition condition2 = lock.newCondition();//条件2 private Condition condition3 = lock.newCondition();//条件3 public void sub1() { try { lock.lock();//开启锁 while (num != 1) {//这里jdk源码里推荐用while,因为有可能出现虚假唤醒,所以要再次确认 try { condition1.await();//条件1线程等待,并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } //运行到这里,说明num=1 num = 2; Thread.sleep(1000); System.out.println("线程1运行完毕"); condition2.signal();//唤醒条件2线程 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock();//释放锁 } } public void sub2() { try { lock.lock();//开启锁 while (num != 2) {//这里jdk源码里推荐用while,因为有可能出现虚假唤醒,所以要再次确认 try { condition2.await();//条件2线程等待,并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } //如果线程执行到这里,说明num=2 num = 3; Thread.sleep(1000); System.out.println("线程2运行完毕"); condition3.signal();//唤醒条件3线程 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock();//释放锁 } } public void sub3() { try { lock.lock();//开启锁 while (num != 3) {//这里jdk源码里推荐用while,因为有可能出现虚假唤醒,所以要再次确认 try { condition3.await();//条件3线程等待,并释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } //如果线程执行到这里,说明num=3 num = 1; Thread.sleep(1000); System.out.println("线程3运行完毕"); condition1.signal();//唤醒条件1线程 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock();//释放锁 } } public static void main(String[] args) { Parallel parallel = new Parallel(); new Thread(parallel::sub1).start(); new Thread(parallel::sub2).start(); new Thread(parallel::sub3).start(); }