Java基础面试题(下)

1.Collection下的常用子类集合有那一些?

有List,Set。

List:有序(按添加顺序显示),可重复,set:无序(按大小排序显示),不可重复,map:以键值对方式存储

List分别有:

ArrayList:是List的主要实现类,ArrayList的底层是数组。线程不安全的。查找快,增删慢(通过首地址+下标获取元素位置,增删要移动某一些元素)”

LinkedList:底层是一个双向链表(是一个带头/尾指针的双向链表,)。插入,删除快(增删快因为直接就在对应的位置设置新的元素指向前后指针或删除)。查找慢(因为链表都是next指向下一个next.只能从首元素开始,依次获得下一个元素的地址。)

Vector:底层是一个object数组,效率低。它是线程安全的

 

2.ArrayList1.7和1.8的底层扩容实现原理说一下

通过一个空参的构造器创建对象时1.7底层创建了长度是10的数组。当我们向集合中添加第11个元素时,底层会进行扩容,扩容为原来数组长度的1.5倍。同时将原数组中的内容复制新的数组中。 元素要求所在的类必须重写equals方法(饿汉式)

1.8中并没有直接就创建长度为10的数组,调用add的时候才创建。(懒汉式),可以自己测试。

   public static void main(String[] args)throws  Exception {
        ArrayList list=new ArrayList();
        Integer integer = getCapacity(list);
        System.out.println("初始容量为"+integer);
        list.add("1");
        Integer integer2 = getCapacity(list);
        System.out.println("初始容量为"+integer2);

   }

   public static Integer getCapacity(ArrayList list) {
        Integer length = null;
        Class clazz = list.getClass();
        Field field;
        try {
            field = clazz.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] object = (Object[]) field.get(list);
            length = object.length;
            return length;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return length;
     }

扩容机制也可以看源码

第一次添加元素的时候this.size+1 的值是1,所以第一次添加的时候会将当前elementData数组的长度变为10。

将修改次数(modCount)自增1,判断是否需要扩充数组长度,判断条件就是用当前所需的数组最小长度与数组的长度对比,如果大于0,则增长数组长度。

我们来打个断点测试

当前数组还没有创建长度为0,所以就到下面的扩容方法中去创建数组

第二次进来的时候就有数组的长度了所以不会扩容,mincapacity达到11的时候就会继续触发扩容机制

public boolean add(E e) {
		//确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将元素e放在size的位置上,并且size++
        elementData[size++] = e;
        return true;
    }
	//数组容量检查,不够时则进行扩容,只供类内部使用 
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
		// 若elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则取minCapacity为DEFAULT_CAPACITY和参数minCapacity之间的最大值。DEFAULT_CAPACITY在此之前已经定义为默认的初始化容量是10。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	//数组容量检查,不够时则进行扩容,只供类内部使用 
	// minCapacity 想要的最小容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
		//最小容量>数组缓冲区当前长度
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//扩容
    }

3.Set的常用子集合有哪些以及hashset的底层实现原理?

HashSet:是set的主实现类,线程不安全,可以存null值。底层是hashmap集合。创始容量和加载因子和hashmap一样。会根据数字或者字母来进行排序。

HashSet的底层原理:向set中添加对象时,会调用改对象的hashcod方法来计算存储的位置,如果该位置上没有其他元素,则直接存放,已经存在其他元素就调用该对象的equals方法进行内容比较,如果是true则不能存放,是false以链表的形式存放该位置上。

LinkedHashSet:是按照添加元素的顺序进行遍历,因为底层维护了一张链表用来记录添加的顺序。底层实现原理和HashSet一样,是hashset的子类

TreeSet:可以按照添加的对象的指定属性进行排序。AbstractSet子类。AbstractSet是set的子类。

什么是链表?:链表是一种根据元素节点逻辑关系排列起来的一种数据结构。它的主要目的是依靠引用关系来实现多个数据的保存

 

4.说一下Map接口中常用的子类,以及他的底层实现原理

Map分为hashmap和linkedhashmap,hashtable,treemap,

key是无序的不可重复的,使用Set存储方式的,Key所在的类想要重写equals方法和Hashcod方法,value无序可重复的,vaule所在的类要重写equals方法。

HashMap:是线程不安全的,可以存放null的key和vaule,如果是null默认从第0个查找。

LinkedHashMap:和Hashmap一样,只是可以按照元素添加的顺序进行遍历。底层用双向链表来记录元素添加顺序。

HashTable:是线程安全的。不可能存null。但是Hashtable是表级锁底层方法都是使用synchronized来保证线程安全,而ConcurrentHashMap是段级锁

TreeMap:添加的key-vaule对进行排序,可以考虑订制排序和自然排序,实现Comparator和 Comparable接口。

 

5.HashMap的底层实现原理,也就是Put方法底层是怎么实现的1.8

1.new HashMap() : 底层默认创建了一个长度为16的数组为Node[16]类型(1.7中为Entry[]数组),加载因子为0.75。

当我们添加元素的数量一旦达到 (16 * 0.75)12时就会进行扩容。扩容为原来的2倍,,  初始容量与负载因子影响性能。

为什么是扩容2倍?根据计算机二进制的算法,在范围内的所索引值都可以得到  

2.put添加数据的时候(k1,v1),先调用k1的hashCode方法算出哈希值,再根据哈希值算出该对象在数组中存放的位置。如果该位置上没其它元素,如果没其它元素则直接将该对象存放到数组中。如果该位置上已经存在其它元素(K2,V2)。则调用k1的equals方法和k2进行比较。如果结果为true,则说明key是相同的。则v1覆盖v2。

 如果结果为false,以链表的形式将该对象存在该  位置上。如果链表上的元素的个数达到8时,

将链表改成红黑树(jdk1.8。    

代码测试通过反射获取HashMap容量

public static void main(String[] args) throws Exception {
        //指定初始容量15来创建一个HashMap
		HashMap m = new HashMap(15);
        //获取HashMap整个类
		Class mapType = m.getClass();
        //获取指定属性,也可以调用getDeclaredFields()方法获取属性数组
		Field threshold =  mapType.getDeclaredField("threshold");
        //将目标属性设置为可以访问
		threshold.setAccessible(true);
        //获取指定方法,因为HashMap没有容量这个属性,但是capacity方法会返回容量值
		Method capacity = mapType.getDeclaredMethod("capacity");
        //设置目标方法为可访问
		capacity.setAccessible(true);
        //打印刚初始化的HashMap的容量、阈值和元素数量
		System.out.println("容量:"+capacity.invoke(m)+"    阈值:"+threshold.get(m)+"    元素数量:"+m.size());
		for (int i = 0;i<17;i++){
			m.put(i,i);
            //动态监测HashMap的容量、阈值和元素数量
			System.out.println("容量:"+capacity.invoke(m)+"    阈值:"+threshold.get(m)+"    元素数量:"+m.size());
		}
	}

6.Hashmap为什么使用红黑树,红黑树是什么?

因为链表长了就会查询慢的问题,红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。

红黑树是一种平衡二叉查找树,他的节点是红色或者黑色,根节点是黑色,每个叶子节点都是黑色的空节点,红色的节点他的子节点都是黑色,他可以用变色和旋转来调整平衡。

 

7.Hashmap的加载因子为什么是0.75为什么不是别的?

提高空间利用率,冲突少0.75比较合适。(我这里简单说了)

8.Hashmap如何解决hash冲突问题?1.7扩容死循环问题?

使用链表存放hash值相等且内容不等,存放到同一个链表中。

因为采用了头插法,在多线程的情况下操作hashmap的时候,导致死循环的问题。

9.HashMap1.8和1.7改进了那些, 红黑树和链表的时间复杂度

(一)JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,因为1.7用单链表就行纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

(二)扩容后数据存储位置的计算方式也不一样

(三)JDK1.7的时候使用的是数组+ 单链表的数据结构。但是在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)

Ologn  和 o(n)

10.Iterator和ListIterator有什么区别?

ListIterator 继承 Iterator,ListIterator中操作list集合的方法多,比如遍历的时候Iterator遍历做添加操作报并发修改异常错误,这个时候可以使用ListIterator中的add方法做添加,还有set方法做修改等。

 

11.你了解多线程吗?说一下多线程。

多线程原理:同一时间内,CPU只能处理1条线程,只有1条线程在工作(执行),而多线程…

为了充分的利用资源,让程序响应更快,使用了多线程。线程越多,cpu开销就越大。

开启线程的4种方式,继承Thred,实现Runnable接口,实现Callble接口和使用线程池。

生命周期->新建->就绪->阻塞->运行->死亡 ,阻塞到运行过程是循环的主要看线程

线程的同步机制:同步方法和同步代码块。

 

12.sleep和wait的区别?手写一下买票交替的线程实现。

1.sleep不会释放锁。Wait会释放锁

2.sleep会自动唤醒。wait需要其它线程唤醒。

3.sleep是Thread中的方法。wait是Object中的方法。

线程通信的wait,notfiy,notfiyall只能在同步方法和代码块中使用

 

13.为什么使用多线程?你对锁有了解吗?什么时候会出现线程安全问题?

因为可以提高程序效率,重入锁(Synchronized )、(数据库锁)悲观锁和乐观锁,分段锁(ConcurrentHashMap),读写锁(ReadWriteLock)。,分布式锁    在有共享数据,当多个线程访问全局变量的时候。

 

14.TCP的3次握手和四次挥手?为什么是四次挥手,而不是三次或是五次、六次?
 

三次握手:要建立连接时,1客户端发送一个SYN报文给服务器,2服务器收到请求后,向客户端响应一个SYN+ACK连接数据报文,3然后客户端收到报文,回应ACK报文,服务器收的到ACK完成3次握手。

四次挥手:双方关闭连接要经过双方都同意。所以,1首先是客服端给服务器发送FIN,要求关闭连接,2服务器收到后会发送一个ACK进行确认。3服务器关闭连接然后再发送一个FIN,4客户端收到FIN发送ACK确认,并进入TIME_WAIT状态。等待2MSL后自动关闭。服务器收到ACK就关闭连接了。

为什么是3次握手:因为没有收到服务器返回确认报文,这时会放弃连接,重新启动一条连接请求,因为他们之间要相互确认是否收到ACK。

 

15.为什么ArrayList集合不是安全的?并发(修改)异常是什么

就是在用迭代器Iterator的遍历的时候,同时对集合元素进行操作,但是迭代器并不知道集合发生变化,会产生并发修改异常。

1.(删除操作)迭代的过程中可以使用Iterator的删除方法删除。2.(新增操作)使用listIterator 中有add方法,因为在迭代的过程对数组的修改。只有List有这特性,因为List有索引。2.使用for循环集合的siez去删除(遍历删除数据异常) 使用迭代器iterator遍历删除,不使用while(遍历删除数据异常)

16.TCP与UDP的区别:

1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;

5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

 

17.什么是浅拷贝和深拷贝?

浅拷贝(Shallow Copy):指向被复制的内存地址,如果地址发生改变,浅拷贝出来的对象也会相应改变

深拷贝为对象都开辟了新的内存空间地址,使这个增加的指针指向新的内存 

总结实则浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。

 

18.说一下BIO和NIO和AIO?

Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

 

19.什么是序列化与反序列化?

作用:在网络上传输对象字节序列,把对象字节保存到硬盘上。减少内存消耗。或者在远程rpc调用对象的时候需要。

序列化就是把对象存储到硬盘上,反序列化就是获取对象文件中的数据,转成后台对象类的数据。

20. 快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?

java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

21.ConcurrentHashMap分段锁的实现原理

ConcurrentHashMap是同步容器类jdk5增加的一个线程安全的哈希表。在hashmap和hashtable之间,内部采用锁分段机制代替hashtable的独占锁。提高性能。默认把表分成16个段(hashtable),每段中分出一个数组加链表。内部采用大量了CAS操作,他的数据结构很接近hashmap,只是增加了同步操作来控制并发,保证了线程的安全。(分段锁)

22.CAS算法是怎么操作的? ABA问题?

比较并替换。cas是一种基于锁的操作,而且是乐观锁。CAS包含了三个操作值,内存值,预估值,更新值,当内存值==预估值,才将更新值赋值给 内存值==更新值,否则将不做任何操作,也可以再次尝试次操作直至修改成功。基于这样的算法,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

ABA问题是: 可能发生内存值原来是A,中间被改成B,后来又被改成A,此时再使用CAS进行检查时发现没有变化,但是实际上发生了变化,这就是ABA问题。 解决方式: Compartmentalization

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值