基础知识点-数据结构

1、基础类型(Primitives)与封装类型(Wrappers)的区别?
1)传递方式不同
基本类型都是按值传递;
封装类型是按引用传递的(其实“引用也是按值传递的”,传递的是对象的地址)。
2)封装类可以有方法和属性
基本数据类型都是final修饰的,不能继承扩展新的类、新的方法。
封装类可以有方法和属性,利用这些方法和属性来处理数据。
3)默认值不同
基本类型跟封装类型的默认值是不一样的。如int i,i的预设为0;Integer j,j的预设为null,因为封装类产生的是对象,对象默认值为null。
4)存储位置不同
基本类型是存储在栈中;
引用类型的引用(值的地址)存储在栈中,而实际的对象(值)是存在堆中。
基本数据类型的好处是速度快(不涉及到对象的构造和回收),封装类的目的主要是更好的处理数据之间的转换。

2、简述九种基本数据类型的大小,以及他们的封装类
基本类型 大小(字节) 默认值 封装类
byte 1 (byte)0 Byte
short 2 (short)0 Short
int 4 0 Integer
long 8 0L Long
float 4 0.0f Float
double 8 0.0d Double
boolean - boolean的大小JVM规范并没有指定, 取决于jvm的实现。1byte的可能性多。 false Boolean
char 2 \u0000(null) Character
void - - Void

3、int和Integer哪个会占用更多的内存?int和Integer有什么区别?
Integer对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。
int是一个原始类型的数据,所以占用的空间更少。
其他区别:
Integer变量是对一个Integer对象的引用。当new一个Integer时,实际上是生成一个指针指向此对象,两次new Integer生成的是两个对象,其内存地址不同,所以两个new出来的Integer变量不等。
非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同。
两个非new生成的Integer对象进行比较,如果两个变量的值在区间[-128,127]之间,比较结果为true;否则,结果为false。
java会将[-128,127]之间的数进行缓存。Integer i1 = 127时,会将127缓存,Integer j2 = 127时,就直接从缓存中取,不会new。
Integer变量(无论是否是new生成的)与int变量比较,只要两个变量的值是相等的,结果都为true。包装类Integer变量在与基本数据类型int变量比较时,Integer会自动拆包装为int,然后进行比较,实际上就是两个int变量进行比较,值相等,所以为true。

4、如何取小数四舍五入保留小数点后两位?
double f = 3.1415926;
BigDecimal bg = new BigDecimal(f);
double f1 = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
或者
Double get_double = (double) (Math.round(d * 100) / 100.0);

5、char型变量中能否存贮一个中文汉字?
可以,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么这个char型变量中就不能存储这个特殊汉字。
1)char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字。unicode编码占用两个字节,所以char类型的变量也是占用两个字节。
2)在Java中,char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),放一个中文是没问题的。

6、如何将数值型字符转换为数字?
int类型就是 Integer.parseInt(str);
byte类型就是 Byte.parseByte(str)
short类型就是 Short.parseShort(str)
float类型就是 Float.parseFloat(str)
char 类型就是 Character.parseChar(str)

7、能将int强制转换为byte类型吗?如果值大于byte类型的范围,将会出现什么现象?
int类型的占4个字节,而byte占1个字节,所以int类型转化为byte类型时会出现位丢失情况,将int的低8位作为byte类型的值。

8、能在不进行强制转换的情况下将一个double值赋值给long类型的变量吗?
不行,double类型的范围比long类型更广,必须要进行强制转换。

9、类型向下转换是什么?
子类可以自动向上转换成父类型,只是部分内存空间无法访问,只能访问父类型在子类中的方法和变量;
父类型向下转换成子类型,必须满足父类型变量是子类型实例化对象,就是原本一个子类实例;

10、如何权衡是使用无序的数组还是有序的数组?
查找的时间复杂度:有序数组O(log(n)),无序数组O(n)。
插入的时间复杂度:有序数组O(n),无序数组O(1)。

11、怎么判断数组是null还是为空?
数组为null:是创建了数组的引用,但在堆中并没有数组中的元素
数组为空:数组是空其实就是数组的长度为0,数组是真正的对象,只是对象中没有元素.
判断数组为空:array.length==0
判断数组为null:直接使用变量名==null

12、怎么打印数组?
1)for循环方式
2)for each循环
3)利用Array类中的toString方法

13、Array 和 ArrayList有什么区别?什么时候应该使用Array而不是ArrayList?
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等。
适用场景:
Array:保存不变的数据;
ArrayList:只想以数组的形式保存数据,只是方便查找,不对数据进行增加等操作。
LinkedList:对元素进行频繁的移动或删除,或处理量很大。

14、数组和链表数据结构描述,各自的时间复杂度?
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可通过下标迅速访问数组中任何元素。但在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
链表中的元素在内存中不是顺序存储的,而是通过元素中的指针联到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素只要修改元素中的指针就可以了。如果经常插入和删除元素就需要用链表数据结构。
● (静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
● 链表从堆中分配空间, 自由度大但申请管理比较麻烦。
● 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
● 数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。

15、数组有没有length()这个方法? String有没有length()这个方法?
数组只有length属性,array.length返回数组长度。
String有length()方法,str.length()返回字符串长度。

16、队列和栈是什么,它们的区别?
队列(Queue):只能在表的一端插入和在另一端进行删除操作的线性表;
栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表;
队列先进先出(FIFO),栈先进后出(FILO);

17、BlockingQueue是什么?
通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
常用的队列主要有以下两种:
先进先出(FIFO):先插入的队列的元素最先出队列,类似于排队。
后进先出(LIFO):后插入队列的元素最先出队列。
当队列中没有数据时,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
当队列中填满数据时,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
BlockingQueue的核心方法:
1)放入数据
offer(anObject):将anObject加到BlockingQueue里,如果可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程);      
offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果不能往队列中加入,则返回失败。
put(anObject):把anObject加到BlockingQueue里,如果没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
2)获取数据
poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则直到时间超时还没有数据,返回失败。
take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数);不需要多次分批加锁或释放锁。

18、简述 ConcurrentLinkedQueue LinkedBlockingQueue 的用处和不同之处?
线程安全的Queue可以分为阻塞队列BlockingQueue和非阻塞队列ConcurrentLinkedQueue;
LinkedBlockingQueue:是一个线程安全的阻塞队列,实现了BlockingQueue接口,增加了take和put方法;可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
ConcurrentLinkedQueue:是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。

19、ArrayList、Vector、LinkedList的存储性能和特性?
ArrayList和Vector底层的实现都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接按序号索引元素,但插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入慢。
Vector中的方法添加了synchronized修饰,是线程安全的容器,性能上较ArrayList差。
LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录本项的前后项,插入速度较快。

20、ByteBuffer与StringBuffer有什么区别?
ByteBuffer是NIO中的buffer。
StringBuffer是字符串连接用的buffer类。

21、HashMap的工作原理是什么,内部的数据结构是什么?
HashMap和Hashtable的底层都是数组+链表结构实现;通过put(key,value)和get(key)方法存储和获取对象元素,将key值传递给put()时,会自动调用对象的hashcode()计算hashcode,然后根据hashcode确定对象存储位置;获取对象时,根据键对象的equals()找到具体的键值对Entry,返回值对象;
HashMap使用链表方式解决hash冲突,发生hash冲突时,会将对象存储在链表的下一个节点上;
HashMap线程不安全,CourrentHashMap线程安全(利用分段锁锁住map的一部分);
HashMap初始化时会创建默认容量为16的Entry数组,默认加载因子为0.75,临界值为16*0.75;

22、HashMap的table的容量如何确定?loadFactor是什么?该容量如何变化?这种变化会带来什么问题?
initialCapacity = (需要存储的元素个数 / 负载因子) + 1。
HashMap默认的加载因子是0.75,最大容量是16,默认容量是:0.75*16=12; 如果无法确定初始值大小,请设置为16(即默认值)。
loadFactor加载因子表示Hsah表中元素的填满程度。
加载因子越大,空间利用率越高,冲突机会越大;
加载因子越小,空间利用率越少,冲突机会越小;

23、HashMap和HashTable、ConcurrentHashMap的区别?
HashTable
1)底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低
2)初始size为11,扩容:newsize = oldsize*2+1
3)计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap
1)底层数组+链表实现,可以存储null键和null值,线程不安全
2)初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
3)扩容针对整个Map,每次扩容时,原数组中的元素依次重新计算存放位置,并重新插入
4)插入元素后才判断该不该扩容,有可能无效扩容
5)当Map中元素总数超过Entry数组的75%,触发扩容操作,为减少链表长度,元素分配更均匀
6)计算index方法:index = hash & (tab.length – 1)

ConcurrentHashMap
1)底层采用分段的数组+链表实现,线程安全
2)把整个Map分为N个Segment,可以提供相同的线程安全,效率默认提升16倍。(读操作不加锁,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。)
3)Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
4)有些方法需要跨段,比如size()和containsValue(),可能需要锁定整个表,这需要按顺序锁定所有段,操作完毕后,按顺序释放所有段的锁
5)扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容

24、HashMap的遍历方式及效率?
1)显式调用map.entrySet()集合迭代器
Iterator iter1 = map.entrySet().iterator();
2)foreach增强for循环实现entrySet
for (Entry str : map.entrySet())
3)显式调用map.keySet()集合迭代器
Iterator iter2 = map.keySet().iterator();
4)foreach增强for循环实现keySet
for (Integer str : map.keySet())
效率:
1)当需要key也需要value时,选择entrySet
2)当只是遍历key而无需取value的话,使用keySet。
3)foreach更简洁,推荐使用。

25、HashMap、LinkedMap、TreeMap的区别?
HashMap:根据键的HashCode值存储数据,根据键可以直接获取它的值,访问速度快。最多只允许一条记录的键为Null;允许多条记录的值为Null;不支持线程同步。
LinkedHashMap:保存记录的插入顺序,也可以在构造时带参数,按照应用次数排序。
当HashMap容量很大,实际数据较少时,遍历速度比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和容量有关。
TreeMap实现SortMap接口,能够把保存的记录根据键排序,默认按键的升序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。

26、如何决定选用HashMap还是TreeMap?
TreeMap的Key值是要求实现java.lang.Comparable,迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现也是基于红黑树结构。
HashMap的Key值实现散列hashCode(),分布是散列均匀的,不支持排序;数据结构主要是桶(数组),链表或红黑树。
HashMap有更好的性能,不要排序时用HashMap。

27、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认负载因子大小为0.75,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

28、HashMap是线程安全的吗?并发下使用的Map是什么,它们内部原理分别是什么,比如存储方式、hashcode、扩容、默认容量等?
HashMap非线程安全
并发下使用ConcurrentHashMap,其工作机制是通过把整个Map分为N个Segment,可以提供相同的线程安全,效率提升N倍,默认提升16倍。根据key.hashCode()来决定把key放到哪个分段中。
ConcurrentHashMap是一个Segment数组,Segment通过继承ReentrantLock进行加锁,每次锁住的是一个segment,只要保证每个Segment是线程安全的,也就实现了全局线程安全。
● Segment数组长度为16,不可以扩容
● Segment[i]的默认大小为2,负载因子是0.75,得出初始阈值为1.5,也就是以后插入第一个元素不会触发扩容,插入第二个会进行第一次扩容;

29、HashSet和TreeSet有什么区别?
1)HashSet数据是无序的,TreeSet数据是有序的。
2)TreeSet保存自定义类对象时,自定义所在的类一定要实现Comparable接口,否则无法区分大小关系,而且在TreeSet中如果要进行排序,就要将所有的字段都进行比较,在TreeSet中是依靠comparato()方法返回结果是否为0来判断重复元素。
3)HashSet子类,其判断重复数据的方式是Object类之中的两个方法:(1)取得对象的哈希码hashCode();(2)对象比较:equals();

30、HashSet内部是如何工作的?
HashSet内部采用HashMap来实现。由于Map需要key和value,所以HashSet中所有key的都有一个默认value。HashSet不允许重复的key,只允许有一个null key。

31、WeakHashMap是怎么工作的?
WeakHashMap:只有自身有对key的引用,那么WeakHashMap会在下次进行增删改查操作时丢弃该键值对,节约内存使用,此特性使得WeakHashMap非常适合构建缓存系统。
WeakHashMap主要通过expungeStaleEntries函数来实现移除其内部不用的entry达到自动释放内存的目的。基本上只要对WeakHashMap的内容进行访问就会调用expungeStaleEntries函数,从而达到清除不再被外部引用的key对应的entry键值对。

32、Set里的元素是不能重复的,用什么方法来区分重复与否呢?用==还是 equals(), 有何区别?
Set是Collection容器的一个子接口,不允许出现重复元素,只允许有一个null对象。
使用equals()区分元素是否重复;

33、TreeMap是采用什么树实现的?TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
TreeMap是红黑树算法的实现;
TreeSet要求存放的对象所属类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
Collections工具类的sort方法有两种重载的形式,第一种要求待排序容器中存放的对象必须实现Comparable接口以实现元素的比较;第二种不强制性要求容器中的元素必须可比较,但要求传入第二个参数,参数是Comparator接口的子类型(需重写compare()实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法。

34、一个已经构建好的TreeSet,怎么完成倒排序?
通过TreeSet构造函数传入一个比较器,指定比较器进行排序为原排序的倒叙。
TreeSet自然排序是根据集合元素的大小,以升序排列。如需实现定制排序,如降序,则需在创建TreeSet时,提供一个Comparator对象与该TreeSet关联,由该Comparator对象负责集合元素的排序。Comparator接口包含一个int compare(T o1,T o2)方法,用于比较o1和o2的大小。

35、EnumSet是什么?
是与枚举类型一起使用的专用Set实现。枚举set中所有键都必须来自单个枚举类型,该枚举类型在创建set时显式或隐式指定。枚举set在内部表示为位向量,此表示形式非常紧凑且高效。此类的空间和时间性能很好,具有高品质、类型安全的优势。

36、Hashcode的作用?
1)用来在散列存储结构中快速确定对象的存储地址,如Hashtable,HashMap等;
2)如果两个对象相同则hashCode一定相同,适用于equals(java.lang.Object)方法;
3)如果对象的equals()被重写,则对象的hashCode()也需重写;
4)两个对象的hashCode相同,两个对象未必相同,不一定适用于equals(java.lang.Object)方法,只能说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

37、简述一致性Hash算法?
在分布式缓存系统中,需要将数据均匀的分布到缓存服务器集群的不同机器上,就需要对缓存数据的key做hash计算, 再将hash值除以服务器节点的数量取模计算出数据需要落到哪台服务器节点上。这种算法简单,也可以实现数据的均匀分布, 但增加或减少数据节点时会导致所有缓存数据失效。
一致性Hash最关键的区别:对节点和数据,都做一次哈希运算,然后比较节点和数据的哈希值,数据取和节点最相近的节点作为存放节点。 保证节点增加或减少时,影响数据最少。

38、有没有可能两个不相等的对象有相同的hashcode?当两个对象hashcode相同怎么办?
判断规则:
1)如果两个对象equals,hashcode一定相等。
2)如果两个对象不equals,hashcode有可能相等。
3)如果两个对象hashcode相等,他们不一定equals。
4)如果两个对象hashcode不相等,他们一定不equals。
hashcode相同,它们的bucket位置相同,碰撞会发生。HashMap使用链表存储对象,Entry存储在链表中。如果数组坐标相同,则进入链表中,一般的添加都在最前面,也就是和数组下标直接相连的地方,链表长度到达8的时候,jdk1.8上升为红黑树。

39、为什么在重写equals方法的时候需要重写hashCode方法?
为保证同一个对象,在equals相同时hashcode值也相同,如果重写equals未重写hashcode方法,可能会出现两个没有关系的对象equals相同,hashcode不相同。

40、a.hashCode()有什么用?与a.equals(b)有什么关系?
hashCode()方法是对象整型的hash值。两个使用equal()方法判断相等的对象,必须具有相同的hashcode。将对象放入集合中时,首先判断对象的hashcode在集合中是否存在,不存在则放入。
如果hashcode相等,通过equal()方法判断对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入。

41、hashCode()和equals()方法的重要性体现在什么地方?
同一个对象无论何时调用hashCode(),返回值必须一样。
hashCode()返回值相等,对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象。重写equals(),必须重写hashCode()。hashCode()生成哈希值的依据应是equals()中用来比较是否相等的字段。

42、Object有哪些公用方法?Object类hashcode,equals设计原则?
clone、getClass、toString、finalize、equals、hashCode、wait、notify、notifyAll
如果两个对象相等equals(),必须有相同的哈希码hashCode(),即使两个对象有相同的哈希值,他们不一定相等。

43、如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣?
同时复写hashcode和equals方法,优势可以添加自定义逻辑,且不必调用超类的实现。

44、可以在hashcode()中使用随机数字吗?
不行,因为同一对象的hashcode值必须相同。

45、LinkedHashMap和PriorityQueue的区别?
PriorityQueue:保证最高或最低优先级的元素总是在队列头部,遍历时没有任何顺序保证;
LinkedHashMap:维持元素插入的顺序,遍历顺序是元素插入的顺序。

46、List, Set, Map存取元素时各有什么特点?
Set不允许有重复元素
存元素:add方法有一个boolean返回值,当集合中没有某个元素,add方法可加入该元素返回true;当集合含有与某个元素equals相等的元素时,add方法无法加入,返回false。
取元素:只能以Iterator接口取得所有元素,再遍历各元素。

List有顺序的集合
存元素:多次调用add(Object)方法时,按先来后到排序,也可插队,调用add(int index,Object),指定当前对象在集合中的存放位置。
取元素:方法1:Iterator接口取得所有,遍历各元素
方法2:调用get(index i)明确取第几个,精确控制元素插入位置。用户能使用索引来访问List中的元素,类似于Java的数组。

Map是双列的集合
存元素:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。
取元素:用get(Object key)根据key获得相应的value。可以获得所有key和value的集合,还可以获得key和value组合成的Map.Entry对象的集合。

List以特定次序来持有元素,可有重复元素。
Set无法拥有重复元素,内部排序。
Map保存key-value值,value可多值。

46、List、Set、Map是否继承自Collection接口?
List与Set都是单列元素的集合,它们有一个共同的父接口Collection。
Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应值对象。

47、遍历一个List有哪些不同的方式?
for each、Iterator、loop with size

48、LinkedList是单向链表还是双向链表?
Linkedlist是双向链表
优点:增删快;没有索引,对索引的操作,只能循环遍历,每次循环都会先判断一下,这个索引位于链表的前部分还是后部分,每次都会遍历链表的一半,而不是全部遍历。
双向链表都有一个previous和next,链表最开始都有一个first和last指向第一个和最后一个元素。增删时,只需更改一个previous和next,就可以实现增加和删除。

49、LinkedList与ArrayList的区别?
1)ArrayList基于动态数组的数据结构,LinkedList基于链表的数据结构。
2)随机访问get和set,ArrayList优于LinkedList,LinkedList要移动指针。
3)增删操作,LinedList占优势,ArrayList要移动数据。

50、插入数据时,ArrayList、LinkedList、Vector谁的速度较快?
ArrayList:数组结构,查询快,增删慢,线程不同步
LinkdeList:链表结构,查询慢,增删快
Vector:数组结构,线程同步
插入速度:LinkdeList>ArrayList>Vector

51、ArrayList和HashMap的默认大小?
ArrayList默认大小是10个元素,HashMap默认大小是16个元素(必须是2的幂);

52、ArrayList、LinkedList、Set、Vector的区别?
ArrayList
优点:基于索引的数据结构,适合随机读取数据,读取速度快,可一步get(index)。
缺点:增删值慢,添加数据在array中间时,需要移动后面的数;当长度大于初始长度时,每添加一个数都会扩容。

LinkedList:双向链表
优点:增删值快,不需改变数组大小,也不需在数组装满时将所有数据重新装入一个新数组,添加在list中间也只需更改指针;长度不固定,优势只存在于数据插入表头,如果一直往后插入,就没有优势了。实现栈和队列方面,LinkedList要优于ArrayList。
缺点:LinkedList需要更多内存,ArrayList每个索引位置是实际的数据,而LinkedList中每个节点中存储的是实际数据和前后节点位置。

Set不允许有重复,ArrayList是动态数组实现的列表,有序可重复。
Vector类似ArrayList,但Vector是同步的。

53、ArrayList如何实现扩容?
arraylist扩充机制:newCapacity=oldCapacity+(oldCapacity>>1)(注:>>1:右移1位,相当于除以2,如10>>1=5),所以size就是初始最大容量或上一次扩容后达到的最大容量,才会扩容。扩容后的大小是原来1.5倍+1;
ArrayList在没指定initialCapacity时会延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,在第一次将ArrayList容量变为10;之后扩容按照1.5倍增长。当添加第11个数据时,Arraylist扩容为101.5=15;当添加第16个数据时,扩容为15 1.5=22个。
每次扩容都是通过Arrays.copyOf(elementData, newCapacity) 这样的方式实现的;
如果通过无参构造的话,初始数组容量为0,当对数组进行添加时,才真正分配容量。每次按1.5倍(位运算)的比率通过copeOf的方式扩容。

54、Array和ArrayList有何区别?什么时候更适合用Array?
Array:最高效,容量固定且无法动态改变;Array实际是个reference,指向heap内某个实际对象。
ArrayList:容量可动态增长,牺牲效率;
基于效率和类型检验,尽可能使用Array,无法确定数组大小时用ArrayList;

55、Map, Set, List, Queue, Stack
Map是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,可形成一个多级映射。使用哈希算法,以便快速查找一个键,TreeMap则是对键按序存放;Map没有继承Collection接口,Map提供key到value的映射。Map中不能包含相同的key,每个key只能映射一个value。Map有两种比较常用的实现:HashMap和TreeMap。

List是有序的Collection,能够精确的控制每个元素插入的位置。使用索引访问List中的元素,允许有相同的元素,查询快,插入慢;实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

Set接口也是Collection的一种扩展,Set中的对象元素不能重复,最多有一个null元素;常用具体实现有HashSet和TreeSet类。

Stack继承自Vector,同步的,实现一个后进先出的堆栈。Stack刚创建后是空栈。

Queue是种特殊的线性表,只允许在表的前端(front)删除,在表的后端(rear)插入。进行插入的端称为队尾,进行删除的端称为队头。队列中没有元素时,称为空队列。在队列结构中,最先插入的元素最先被删除,最后插入的元素最后被删除,队列又称为“先进先出”(FIFO—first in first out)的线性表。

56、Map接口提供了哪些不同的集合视图?
keyset,entryset和collection

57、为什么Map接口不继承Collection接口?
Map不是集合,集合也不是Map。因此Map继承Collection毫无意义,Map包含key-value,提供抽取key或value集合的方法,但它不适合“一组对象”规范。

58、介绍Java中的Collection FrameWork,集合类框架的基本接口有哪些?
collection是java工具包,包含数据结构:集合、链表、队列、栈、数组、映射等。
Java集合主要分为4个部分:List列表、Set集合、Map映射、工具类(Iterator、Arrays和Collections)。Collection有List和Set两大分支。
基本接口:Collection和Map
Collection:单列集合的根接口
List:元素有序,可重复
ArrayList:类似一个长度可变的数组 。适合查询,不适合增删
LinkedList:底层是双向循环链表。适合增删,不适合查询。
Set:元素无序,不可重复
HashSet:根据对象的哈希值确定元素在集合中的位置
TreeSet: 以二叉树的方式存储元素,实现了对集合中的元素排序
Map:双列集合的根接口,用于存储具有键(key)、值(value)映射关系的元素。
HashMap:用于存储键值映射关系,不能出现重复key
TreeMap:用来存储键值映射关系,不能出现重复key,键按二叉树方式排列;
具体结构如下所示:


结构图.jpg

59、Collection和Collections的区别?
1)Collection是集合接口。它提供了对集合对象进行基本操作的通用接口方法。
2)Collections是包装类。包含各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

60、为什么Collection不从Cloneable和Serializable接口继承?
克隆或序列化的含义是跟具体实现相关的,应该由集合类的具体实现类来决定如何被克隆或序列化;

61、Comparator与Comparable接口是干什么的?它们的区别?
Comparable可以认为是内比较器,实现了Comparable接口的类可以和自己比较,和另一个实现了Comparable接口的类如何比较,依赖compareTo方法的实现,compareTo方法也称为自然比较方法。如果想要Collections的sort方法自动进行排序,那么这个对象必须实现Comparable接口。
compareTo方法的返回值是int,有三种情况:
1)比较者大于被比较者(也就是compareTo方法里面的对象),返回正整数
2)比较者等于被比较者,返回0
3)比较者小于被比较者,返回负整数

Comparator可以认为是外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:
1)一个对象不支持自己和自己比较(没有实现Comparable接口),但又想对两个对象进行比较;
2)一个对象实现了Comparable接口,但开发者认为compareTo方法中的比较方式不是想要的方式;

Comparator接口的compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:
1)o1大于o2,返回正整数
2)o1等于o2,返回0
3)o1小于o3,返回负整数

62、什么是B+树,B-树,列出实际的使用场景?
B树:平衡多叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;
B-树:即B树
B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3

使用场景:
红黑树: 平衡二叉树,广泛用在C++的标准模板库(STL)中。如map和set都是用红黑树实现的。
B/B+树: 用在磁盘文件组织、数据索引和数据库索引。

63、fail-fast 与 fail-safe 机制有什么区别?
Iterator的安全失败是基于对底层集合做拷贝,因此它不受源集合上修改的影响。
java.util包下面所有的集合类都是快速失败的,而java.util.concurrent包下面所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,安全失败的迭代器不会抛出此异常。

如何解决:
fail-fast是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。

你可能感兴趣的:(基础知识点-数据结构)