有趣的Java-J05

来源:互联网 发布:逆战网络迟 编辑:程序博客网 时间:2024/06/05 04:09

1.单例模式有几种?分别说说每一种的特性

单例模式是Java基本23种设计模式之一,也是最常用的一种设计模式,那么单例模式是怎样的一种设计模式?又会有哪些种类呢?详细看以下链接,你会从中找到答案。

http://blog.csdn.net/clandellen/article/details/77850219

2.用于注解的元注解有哪些?分别作用是什么?试着写出一个简单的用来记录方法信息的注解

首先,你对Java种的注解机制熟悉吗?如果不太熟悉,那么这个问题你压根儿就不会回答,注解是初学者常常忽略的知识点,笔者当初初学的时候也没把注解的机制搞清楚,以致于后来注解使用的非常差劲,还闹过一些笑话,但是笔者很爱学,只要不知道,就一定要去学,学了才发现其实注解机制没那么复杂,以下是笔者自己整理的一篇注解的博客,当然笔者觉得整理的还不够完整,等有时间,会好好修改一番:

http://blog.csdn.net/clandellen/article/details/77046797

下面的一些博客是当初笔者研究注解的博客:
http://blog.csdn.net/mcryeasy/article/details/52452341
http://blog.csdn.net/u010902721/article/details/52576624
http://blog.csdn.net/lxqluo/article/details/30503107

3.ArrayList内部用什么实现的?

这个问题,别看只有短短的几个字,其实里面包含了很多知识点:

3.1 ArrayList的底层数据结构?

ArrayList的底层数据结构是数组,是连续的堆内存空间组成的。这就意味着添加和删除效率是低的,而查询和修改的效率是高高的。

3.2 解释ArrayList是如何做到增删改查的?效率如何?

由于ArrayList的底层是数组实现的,一个一个连续的堆内存空间,下面来看看ArrayList如何去做增删改查的:

增加:
尾部添加add()方法:其实就是在尾部添加,加入后,会存在一个是否扩容的检测,当加入的元素后集合当前元素数量超过一定限度,就进行扩容。效率还行。

插入添加add(index,object):这是插入式添加,插入式添加效率不是太可观,因为在插入的位置之后的元素都要向后移动一位,效率能快吗?试想一下,一旦位于插入位置的后面的元素过多,那将严重影响性能。加入后,会存在一个是否扩容的检测,当加入的元素后集合当前元素数量超过一定限度,就进行扩容。

删除:
删除方法delet(index)方法是将指定位置的元素删除,删除元素的位置之后的元素都要往前移动一位, 所以效率自然也是不咋地。

修改:
由于底层是数组实现的,一个一个连续的堆内存空间,所以遍历的速度自然是非常快的,修改方法set(index,object)正好需要进行遍历,当遍历到指定的位置,那么修改元素值即可,所以效率十分可观。

查询:
跟修改一样,查询也需要进行遍历,并将目标元素与ArrayList中每一个元素进行比较,当发现元素相等时,记录位置,然后结束遍历,并且这个返回这个记录的位置值。效率跟修改一样,十分可观。

3.3 ArrayList底层是数组,都知道数组是固定大小的,那么ArrayList何以保证容量可变呢?

回答这个子问题,那得需要你对ArrayList的扩容机制十分了解,那么什么是扩容机制呢?你可以查看ArrayList的源码,你会发现有一个值为“10”的类型为int的final成员变量,不错,这个“10”就是ArrayList第一次扩容时的最大容量,源码截屏如下:

ArrayList中的部分源码

下面我们看看add()方法,来研究一下,ArrayList究竟是怎么样扩容的:

这里写图片描述

看到add()方法源码中先是执行了“ensureCapacityInternal(size + 1);”这段代码是什么意思呢?好,让我们再看看ensureCapacityInternal()方法的源码,如下所示:

这里写图片描述

elementData就是存放元素的Object数组,这里我们需要看一下ArrayList的构造方法ArrayList():

这里写图片描述

很惊奇发现“ this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;”这段代码,那么也就是说ensureCapacityInternal()方法把elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行比较,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object数组,这是确认存放元素的Object数组elementData有没有指向新的实例对象,也就是elementData是否为空,为什么elementData会指向新的实例呢?我们接下来往后看,从前面的分析可知,在elementData没有指向新的实例时,也就是为空的时候,“ minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);”会被执行,也就是 minCapacity(最小容量)会等于DEFAULT_CAPACITY的值为10,当elementData指向新的实例时,也就是elementData不为空的时候,minCapacity依然还是Size + 1,也就是最小容量等于当前集合的元素个数加1,紧接着,又调用了方法ensureExplicitCapacity()并把minCapacity的值传递给了它,ensureExplicitCapacity()的源码如下:

这里写图片描述

看到ensureExplicitCapacity()方法中先执行了代码“modCount++;”,这个modCount指的是集合修改的次数,这个参数跟扩容没有关系,这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。紧接着,进行了一个if判断。就是把minCapacity和当前存放元素的Object数组elementData的大小进行比较,当minCapacity的值大于当前存放元素的Object数组elementData的大小,就执行grow()方法,否则,结束add()方法中的“ ensureCapacityInternal(size + 1); // Increments modCount!!”这段代码的执行,然后执行“elementData[size++] = e; return ture;”,这就不需要笔者解释吧,size是ArrayList的元素个数!接下来我们着重看grow()方法,源码如下:

这里写图片描述

先解释里面的一些布局变量的意思,oldCapacity是旧的容量意思,等于当前存放元素的Object数组elementData的大小,newCapacity是新的容量的意思,看到代码,执行了代码“ int newCapacity = oldCapacity + (oldCapacity >> 1);”,新的容量为原来容量的1.5倍,紧接着进行了两次判断“newCapacity - minCapacity < 0”和“newCapacity - MAX_ARRAY_SIZE > 0”,第一个判断是把newCapacity和最小容量minCapacity进行比较,我们前面已经分析过,这个最小容量minCapacity要么等于10,要么就是当前元素的个数加1,当newCapacity小于minCapacity时,代码“ newCapacity = minCapacity;”就会执行,newCapacity的值等于
minCapacity的值,这里通常是第一次扩容的时候执行;第二个判断是把newCapacity的值和MAX_ARRAY_SIZE的值进行比较,而MAX_ARRAY_SIZE的值等于Integer.MAX_VALUE - 8,当newCapacity大于MAX_ARRAY_SIZE时就执行hugeCapacity()方法,hugeCapacity()方法源码如下:

这里写图片描述

奇怪,为什么这里会出现一个“minCapacity < 0”的判断啊,不科学啊,在一路的代码分析当中,minCapacity也没参与运算啊,值是不可能为负值啊,其实很简单,由于minCapacity是int类型数据,当minCapacity的值范围超过int类型表示的范围,那么minCapacity肯定是为负值的,这是在学Java基础的时候就应该知道的,那么什么操作会使minCapacity变负值呢?回到开始add()方法的第一段执行的代码:“ensureCapacityInternal(size + 1); // Increments modCount!!”,这个size + 1就是答案,当size为int表示的最大的整数值时,再加1就会导致溢出,从而使整个(size + 1)为负值,接着看,当minCapacity < 0时,说明当前的元素个数已经到达最大限度,那么久抛出OutOfMemoryError,这是内存溢出,当抛出内存溢出的时候,整个add()方法结束,如果minCapacity大于0,说明当前的元素个数没有达到最大限度,就会执行一个三元运算符,可以看图中源码,当minCapacity等于MAX_ARRAY_SIZE时,hugeCapacity()方法返回Integer.Max_Value,否则,hugeCapacity()方法返回MAX_ARRAY_SIZE的值,也就是Integer.MAX_VALUE - 8,再回到grow()方法,通过前面的分析就可以知道“newCapacity = hugeCapacity(minCapacity);”这段代码的意思就是检测内存溢出,当没有内存溢出时,newCapacity的大小是跟minCapacity和MAX_ARRAY_SIZE有关的,这就不需要笔者详细进行解释了吧!应该很简单,在grow()方法的最后执行了代码“elementData = Arrays.copyOf(elementData, newCapacity);”,这就是前面提到的,将elementData指向新的实例对象,Arrays.copyOf()方法大家应该很熟悉吧!通过Arrays的copyOf(elementData, newCapacity)方法把当前存放元素的Object数组elementData和计算出的新的集合容量newCapacity传递给它,Arrays的copyOf(elementData, newCapacity)方法源码如下所示:

这里写图片描述

可以看到copyOf(elementData, newCapacity)中又调用了方法copyOf ( U[] original, int newLength, Class < ? extends T[]> newType),源码如下:

这里写图片描述

当你看到copyOf ( U[] original, int newLength, Class < ? extends T[]> newType)源码中的new关键字,你就应该知道怎么回事了,so 这个扩容过程就讲解完了,下面总结一下:

0.刚开始new出来的ArrayList实例对象的底层存放元素的数组的容量为0,只有第一次执行add()方法的时候,才进行第一次扩容,第一次扩容后,存放元素的数组的容量为10。

1.扩容的条件就是执行add()方法的时候,当size+1大于当前存放元素的数组的容量时,newCapacity=oldCapacity*1.5,oldCapacity为当前存放元素的数组的容量,此时再进行两次检测,第一次检测把当前新的容量newCapacity与size+1进行比较,新的容量newCapacity将会取二者之间大的值,这里通常是第一次扩容的时候执行,接下来进行第二次检测,把当前新的容量newCapacity与Integer.MAX_VALUE - 8进行比较,当新的容量newCapacity>Integer.MAX_VALUE - 8时,接下来判断是否内存溢出,内存溢出时size+1是小于0的,当内存溢出的时候,结束整个加入元素的操作,也就是结束add()方法的执行,并提示OutMemoryError,当没有内存溢出的时候,再进行判断当size+1大于Integer.MAX_VALUE - 8时,当前的新的容量newCapacity就会变成Integer.Max_Value,否则,当前的新的容量newCapacity就会变成Integer.MAX_VALUE - 8。然后new出一个容量为newCapacity的Object[]数组作为集合新的存放元素的数组。

2.每一次扩容,底层存放元素的数组的引用elementData都会指向新的Object[]实例对象,new出来的Object[]数组的容量要么是以前的1.5倍,要么是Integer.Max_VALUE-8,要么是Integer.Max_VALUE。

3.内存溢出OutMemoryError的时候,是size + 1<0的时候,也就是size等于Integer.Max_Value的时候。

4.String,StringBuffer,StringBuilder区别与特性

这个题目面试的时候时常被问到,由于ArrayList的扩容,笔者整理浪费了很多精力,这里笔者暂时贴一个链接,后期补上:

http://blog.csdn.net/kingzone_2008/article/details/9220691

5.String str1 =”a”; String str2 = str1+”b”;String str3 = new String(str2);这段代码共生成几个对象?

这个题目也是面试题高概率问到的题目,解答这个题目需要一定的JVM知识,首先看第一句代码“String str1 =”a”;”这句代码只会生成1个对象,因为”a”是一个常量String对象,不同的是,常量String对象被放于常量池中,然后再看第二句代码“String str2 = str1+”b”;”,这句代码先在常量池中生成了常量String对象”b”,这时生成的第2个对象,然后再把str和“b”进行相加,又会生成一个String对象”ab”,这个”ab”也同样被放于常量池中,这是生成的第3个对象,接着最后一句代码“String str3 = new String(str2);”,这个代码中又new关键字,那么就会在堆内存中生成一个String对象,并且这个String对象的值指向常量池中的”ab”,所以最后,一共生成了4个对象。把图画出来更加容易懂,图笔者有时间就会更新到这里,谢谢关注。

下篇问题预览:

1.如何使用Java23中设计模式的Bulider模式

2.IO流中的File类提供了哪些重要的方法

3.说说IO流的字符流和字节流

4.Lock锁和synchronized锁的区别与特性

5.详细说说线程的生命周期

原创粉丝点击