基础面试题
JAVA基础
1. Java跨平台开发的原理
实现跨平台:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。(注意不是能在所有的平台上运行,关键是该平台是否能安装相应的虚拟机)。
Java跨平台原理:由源文件(.java)—>字节码文件(.class)(二进制文件)-----> 解释---->Unix,Win,Linux等机器。
1.一次编译,到处运行
2.编译器的作用:将源文件编译成class文件
3.虚拟机的作用:将字节码文件解释成对应平台机器码并执行。
4.java可以实现跨所有的平台?只有提供并且安装了相对应的虚拟机就可以跨该平台。
5.虚拟机和解释器的关系:解释器是虚拟机的一个重要的组成部分。
6.Java语言的执行要经过编译和解释两个阶段。
2.8大基本数据类型所占的字节数及其默认值
基本数据类型的字节数及其默认值
3.int与Integer类型的区别
1、int是基本数据类型。integer是int的包装类。
2、Integer变量必须实例化后才能使用,而int变量不需要 。
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 。
4、Integer的默认值是null,int的默认值是0。
5、一般接收参数是基本数据类型应该使用包裹类进行接收。基本类型只能按值传递,而每个基本类型对应的封装类是按引用传递的。
从性能上说java中的基本类型是在堆栈上创建的,而所有的对象类型都是在堆上创建的,(对象的引用在堆栈上创建)。比如Integer i=new Integer(10); 其中new Integer()是在堆上创建的,而他的引用Integer i是在堆栈上。 封装类的出现,是为了更方便的使用一些基本类型不具备的方法,比如valueOf(),toString()等等。还有你如果想传递一个int对象的引用,而不是值,那只能用封装类。在堆栈上分配内存的调用效率和在堆上分配内存的效率差太多了。虽然在堆栈上分配内存效率高,不过在堆栈上分配内存有内存泄露的问题。
4.==与equals区别 equals和hashcode的区别与联系
1."=="运算符对与基本类型(int long float double boolean byte char short),比较的是他们的值。但是对于复合数据类型,比较的是他们的内存地址。
2、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。
当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性。
3、hashCode方法只有在set或map集合中用到
4、将对象放入到集合中时,首先判断要放入对象的hashCode值与集合中的任意一个元素的hashCode值是否相等,
如果不相等直接将该对象放入集合中。如果hashCode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
5、将元素放入集合的流程图
5.简述集合体系
List(列表)
List的元素以线性方式存储,可以存放重复对象,List主要有以下两个实现类:
ArrayList : 长度可变的数组,可以对元素进行随机的访问,向ArrayList中插入与删除元素的速度慢。 JDK8 中ArrayList扩容的实现是通过grow()方法里使用语句newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍扩容)计算容量,然后调用Arrays.copyof()方法进行对原数组进行复制。
LinkedList: 采用链表数据结构,插入和删除速度快,但访问速度慢。
Set(集合)
Set中的对象不按特定(HashCode)的方式排序,并且没有重复对象,Set主要有以下两个实现类:
HashSet: HashSet按照哈希算法来存取集合中的对象,存取速度比较快。当HashSet中的元素个数超过数组大小*loadFactor(默认值为0.75)时,就会进行近似两倍扩容(newCapacity = (oldCapacity << 1) + 1)。
TreeSet :TreeSet实现了SortedSet接口,能够对集合中的对象进行排序。
Map(映射)
Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一个键对象和值对象。 Map主要有以下两个实现类:
HashMap:HashMap基于散列表实现,其插入和查询<K,V>的开销是固定的,可以通过构造器设置容量和负载因子来调整容器的性能。
LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得<K,V>的顺序是其插入次序,或者是最近最少使用(LRU)的次序。
TreeMap:TreeMap基于红黑树实现。查看<K,V>时,它们会被排序。TreeMap是唯一的带有subMap()方法的Map,subMap()可以返回一个子树。
HashMap
底层实现:HashMap底层整体结构是一个数组,数组中的每个元素又是一个链表。每次添加一个对象(put)时会产生一个链表对象(Object类型),Map中的每个Entry就是数组中的一个元素(Map.Entry就是一个<Key,Value>),它具有由当前元素指向下一个元素的引用,这就构成了链表。
存储原理:当向HsahMap中添加元素的时候,先根据HashCode重新计算Key的Hash值,得到数组下标,如果数组该位置已经存在其他元素,那么这个位置的元素将会以链表的形式存放,新加入的放在链头,最先加入的放在链尾,如果数组该位置元素不存在,那么就直接将该元素放到此数组中的该位置。
去重原理:不同的Key算到数组下标相同的几率很小,新建一个<K,V>放入到HashMap的时候,首先会计算Key的数组下标,如果数组该位置已经存在其他元素,则比较两个Key,若相同则覆盖写入,若不同则形成链表。
读取原理:从HashMap中读取(get)元素时,首先计算Key的HashCode,找到数组下标,然后在对应位置的链表中找到需要的元素。
扩容机制:当HashMap中的元素个数超过数组大小*loadFactor(默认值为0.75)时,就会进行2倍扩容(oldThr << 1)。
6.hashMap和hashTable区别
一、HashMap简介
HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。
HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。
HashMap存数据的过程是:
HashMap内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上是一个单向链表。当准备添加一个key-value对时,首先通过hash(key)方法计算hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,计算方法是先用hash&0x7FFFFFFF后,再对length取模,这就保证每一个key-value对都能存入HashMap中,当计算出的位置相同时,由于存入位置是一个链表,则把这个key-value对插入链表头。
HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。
HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了。所以HasnMap内部有自己的扩容机制。HashMap内部有:
变量size,它记录HashMap的底层数组中已用槽的数量;
变量threshold,它是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
变量DEFAULT_LOAD_FACTOR = 0.75f,默认加载因子为0.75
HashMap扩容的条件是:当size大于threshold时,对HashMap进行扩容
扩容是是新建了一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时&#