java基础知识-面试(二)

Collections和Collection有何区别?

java.util.Collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,Connection接口在java类库中有很多具体的实现,Connection的意义是为各种具体的集合提供了最大化的同一操作方式

List 、Set、Queue接口都继承Collection

这里写图片描述

AbstractConnection —实现了Collection接口,并对简单一些方法做了实现比如isEmpty();

java.util.Collections是一个包装类,它包含各种集合操作的静态方法(对集合的搜索、排序,线程安全化),大多数方法都是用来处理线性表,此类不能实例化,更像一个工具 ,服务于java的Collection

这里写图片描述

Collection是一个顶级接口,像List、Set、Queue等属于它的子类
Collections是一个工具类,提供很多静态方法来操作集合

List、Set、Map的区别?Map是否继承Conllection接口

List :可以允许重复的对象
可以插入多个null元素
是有序的,保持了每个元素的插入顺序,输出的顺序就是插入的顺序
常用的List有ArrayList 、LinkedList,ArrayList最为流行,它提供了使用索引的随意访问,而LinkedList则对于经常需要从List添加或删除元素的场合更为合适

Set:不允许重复对象,
无序容器,你无法保证每个元素的存储顺序,TreeSet通过Comparator维护了一个排序顺序
只允许一个null元素
Set最流行的几个接口是HashSet、LinkedSet以及TreeSet

List、Set、Queue继承Collection接口,Map不是

Map:
Map不是Collection的子接口或者实现类,Map是一个接口
Map的每一个Entry都是一个键值对,Map可能会有持有相同值对象但键对象不同(键对象必须是唯一的,否则会覆盖)
TreeMap也通过Comparator或者Comparable维护了一个排序顺序

Map里边可以拥有随意多个null值,但是只能有一个null键

Map最常用的几个实现类:HashMap、LinkedMap、HashTable、TreeMap (HashMap和TreeMap最为常见)

ArrayList和LinkedList的区别?
1.ArrayList是基于动态数组实现的数据结构,动态扩容(默认初始大小是10),而LinkedList是基于链表的数据结构(Node内部类,next、pre、elemnet)
2.对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针,
3.对于添加和删除操作add和remove,LinkedList要比ArrayList要快的多(数据多的情况下很明显)

ArrayList中的元素如何删除?
强烈使用Iterator,保证正确

这里写图片描述

这里写图片描述
Iterator迭代器实现
从后往前遍历

Iterator和LitsIterator区别?
LitsIterator 继承Iterator接口
Iterator 有hasNext()方法,是否有下一个元素
next:指向后面的元素
remove:删除集合中Iterator指向位置后面的元素
LitsIterator方法:(更多方法)
add(E e):将指定元素插入当前位置之前
hasPrevious:前面是否还有元素
nextIndex():返回所需位置的索引

相同点:都是迭代器,当需要对集合元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用

不同点:使用范围不同,Iterator可以应用于所有的集合,Set、List、Map和这些集合的子类型,而ListIterator只能用于List及其子类型

2.ListIterator有add方法,可以向list中添加对象,而Iterator不能

3.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious和previous方法,可以实现逆向(从后向前)遍历,Iterator不可以

4.ListIterator可以实现定位当前索引位置,NextIndex可以,而Iterator不可以

5.都可以实现删除操作,但是ListIterator可以实现对象的修改,set方法可以实现,但是Iterator仅能遍历,不能修改

Comparable和Conparator的区别?

两个都是接口
相同点:都是用来实现集合重元素的比较
不同点:Comparable接口里面的方法是public int compareTo(T o)在java.lang包下
Comparator接口里面的方法是int compare(T o1,T o2)在java.util包下

Comparable是在集合内部定义的方法实现的排序,Comparator是在集合外部实现的排序

所以要想实现排序,就需要在集合外定义的Comparator接口的方法或在集合内实现Comparable接口的方法。Comparable是一个对象本身就已经支持字比较所需要实现的接口(如String、Integer、Float、Double等),自己就可以完成比较大小操作,已经实现了Comparable接口,自定义的类要在加入容器时,实现Comparable接口,在用Conlections类的sort方法进行排序,如果不指定Comparator,那么就以自然顺序排序,这里的自然顺序就是实现Comparable接口设定的排序模式

Comparator是一个专用的比较器,在集合外部实现的排序,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较
用Comparator是策略模式 - 自定义排序,就是不改变对象自身,而用一个策略对象来改变它的行为,比如,你想对整数采用绝对值大小来怕许,Integer是不符合要求的,你不需要去修改Integer类,(实际上也不能这么做),只要使用了一个实现了Conparator的接口的对象来控制它的排序就好了

简单来说Comparable是自己完成比较,Comparator是外部程序实现比较(集合外部实现的排序)

工作中如何使用?
1.一个类既可以实现Comparable接口也可以实现Comparator接口
如果希望一个类的比较方式有很多种,而且比较方式具有外部扩展性,那么可以实现Comparator接口,如果一个类默认的实现了Comparable接口,而你又希望再定义一个比较规则,那么你不可能去修改原类,因为这样会破坏封闭开放原则,最好的方法就是写一个实现了Comparator接口的类,总的来说,Comparator接口比Comparable更灵活
2.如果一个集合类中存放着一些对象,如果希望对这个集合进行排序,可以使用Collections类里面的sort方法,或者Arrays.sort方法
sort方法有两种重载方式:
sort(Collection c):这种方法是根据类T里面默认的规则来进行排序的,即类里面有compareTo方法
sort(Collections c ,Comparator com):这种方法是根据类外面穿进来的Comparator实现类排序的,可以自定义排序规则

有的类已经实现了Comparable方法(String/Integer/double、float等List)可直接调用sort方法,默认升序排序

成绩属性如何排序
实现Comparable接口,实现compareTo方法,
Comparator接口,实现compare方法

java中的ArrayList和HashMap默认容量?

在java7中,ArrayList默认大小是10个元素
HashMap的默认容量是16(必须是2的幂),负载因子0.75
动态扩容,达到负载因子,就会进行扩容

jdk1,8中
ArrayList包含了两个重要的对象
elementData和size
elementData是Object数组,他保存了添加到ArrayList中的元素,实际上elementData是一个动态数组,我们可以通过构造函数ArrayList(int initialCapacity)来执行它的初始容量为InitialCapacity,如果通过不含参数的构造函数ArrayList来创建ArrayList,则在第一次添加元素的时候初始化elementData的默认容量是10,elementData数组大小会根据ArrayList数组的增长而动态增长,具体的增长方式,请参考源码分析中的ensureCapacity()函数
size则是动态数组的实际大小
jdk1.7中是调用默认构造函数this(10),初始化大小为10

HashMap和ArrayList一样,也是第一次添加元素的时候出初始化默认容量

hashCode()和equals方法有什么重要性?
hashCode()和equals()方法的作用其实一样,在java里面都是用来对比两个对象是否相等一致

在java中任何一个对象都具备equals和hashCode方法,因为他们是在Object类中定义的

equals方法是用来判断两个对象是否相同,如相同则返回true,否则返回false
hashCode方法返回一个int数,在Object中默认的实现是“将对象的内存地址转换成一个整数返回”
native方法

equals方法的最用:
1.默认情况(没有覆盖equals)都是调用了Object类的equals方法,而Object的equals方法主要用于判断两个对象的内存地址是否是同一个地址(是不是同一个对象)
2.要是类中覆盖了equals方法,那么就要根据具体的代码来确定equals方法的作用了,覆盖后一般都是通过对象的内容是否相等来判断对象是否相等

hashCode方法的作用:
hashCode方法是为了提高在散列结构存储中查找的效率(定位在hash表中的位置),在线性表中没有作用,hashCode方法只在集合中用到,以Hash开头的集合类

哈希码
哈希码的产生依据,哈希码并不是完全唯一的,它是一种算法,让同一类的对象按照自己不同的特征尽量有不同的哈希码,但不表示不同的对象hash码完全不同,也有相同情况,看程序员如何看待哈希算法
在java中,哈希码代表对象的特征:
String str1 = “aa”; str1.hashCode =3014
String str2 = “bb”; str1.hashCode =3016
String str3 = “aa”; str1.hashCode =3014
根据hashcode由此可得出:str1!=str2 ; str1 == str3
几个常见的哈希码的算法:
1.Object类的hashCode,返回对象的内存地址经过处理后的机构,由于每个对象的内存地址都不一样,所以HashCode也不一样
2.String类(重写了hashCode)的HashCode,根据String中包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同
3.Integer类,返回的HashCode 就是Integer对象里所包含的那个整数的值,例如Integer i1 = new Integer(100),i1的hashcode值就是100
由此可见,2个大小一样的Integer对象,返回的哈希码也一样

那么equals既然已经实现对比的功能了,为什么还要hashCode呢?
因为重写的equals里一般比较的 比较全面、比较复杂,这样效率就比较低,而利用hashCode方法进行对比则只要生成一个hash值进行比较就可以了,效率很高

那么hashCode既然这么高效,为什么还要equals?
因为hashCode(并不是完全可靠,有的时候不同的对象生成的hashcode也会一样(生成的hash值公式可能存在问题)所以hashCode()只能说是大部分时间可靠,并不是绝对可靠,所以我们在使用的时候,按照下面的规范:

1.若是重写了equals方法,则有必要重写hashCode方法
得出结论:
equals相等的两个对象他们的hashCode一定相等,也就是equals对比是绝对可靠的
hashCode相等的两个对象他们的equals不一定相等,也就是hashCode不是绝对可靠的

详细的说明:
若是重写了equals方法,则有必要重写hashCode方法
若两个对象的equals方法返回true,则hashCode有必要返回相同的int数
若两个对象的equals方法返回false,则hashCode有不一定返回相同的int数
若两个对象hashcode返回相同的int数,则两个对象的equals方法不一定返回true
若两个对象hashcode返回不同的int数,则两个对象的equals方法一定返回false
同一对象在执行期间若已存储在集合中,则不能修改影响hashCode的相关信息,否则会导致内存泄漏等问题

最佳实践:
所有对于大量并且快速的对比的话都要用equals去做显然效率太低,所以解决方法是每当需要对比的时候,首先用hashCode去对比,如果hashCode不一样,则表示这两个对象肯定不相同(没必要再去用equals对比),如果hashCode相等,此时再对比它们的equals,如果equals相等,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性

这种大量的并且快速的对象对比一般使用在Hash容器中,比如HashSet、HashMap、HashTable等等,比如hashSet要求元素不能重复,则它的内部必须要对添加进去的元素对象进行对比,而它的比对规则就是象上面说的那样,先hashCode,如果hashCode相同,在比对equals ,如果hashCode都不同,则肯定不同,这样对比的效率就会很高

必须是在hash容器中先hashCode,如果hashCode相同才会比较equals,如果hashCode不同,则认为不同对象,不会比较equals

如果不在容器中,就是看对应的equals方法是否相等,于HashCode没有关系、

修改学生的年龄,hashcode的值会改变
当我们将某个对象存储到set中,如果该对象的属性参与了hashcode计算,那么以后就不能修改该对象参与hashcode计算的那些属性了,否则会引起意想不到问题

这里写图片描述

会删除失败,因为删除的时候也要根据hash码判断,此时找不到hashcode的值,所以无法删除。

HashSet和TreeSet有什么区别?

HashSet:
不能保证元素的排序顺序,顺序有可能发生变化,不允许一样重复的对象—无序的
集合元素可以是null,但是只能放一个null
HashSet底层是采用HashMap实现的
HashSet底层是一个哈希表

只利用hashMap中的key键来完成 ,value不用,将值存储到key中即可

TreeSet
TreeSet中的数据是排好序的,不允许放入null值,与插入顺序无关,比如字符串,按首字母大小写排序自然排序
TreeSet是通过TreeMap来实现的,只不过set用的只是map的key值
TreeSet底层实现的是采用二叉树(红黑树)的数据结构

TreeSet对象里面放字符串:
这里写图片描述

TreeSet对象里面放入对象
这里写图片描述

Comparable–实现接口,实现compareTo方法
Comparator–实现接口,实现compare方法、定义在类外部,比较专用的比较器
TreeSet tsc = new TreeSet(new MyCompare());

这里写图片描述

这里写图片描述

这里写图片描述

HashSet:
当向HashSet集合中存入一个元素时,HashSet会调用对象的hashCode方法来得到该对象的hashCode值,然后根据hashcode值来决定对象在HashSet中的存储位置,如果相等,那就不能插入,如果不等,才回去调用equals方法,如果equals方法为true,说明已经存在,就不能插入,如果为false,则可以插入

简单来说,HashSet集合判断两个元素是否相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashcode方法返回值相等

简单来说,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也因该重写其hashCode方法,其规则是如果两个对象equals比较相同返回true时,其HashCode也应该相同,对象中用作比较equals的属性,都应该参与计算hashCode的值

TreeSet:
TreeSet是怎么实现有序的,它是按照什么规则排序的

TreeSet的底层是采用红黑树的数据结构,采用这种结构可以从Set中获取有序的序列,但是前提条件是:元素必须实现Comparable接口,该接口中只有一个方法comparaTo方法,当往Set集合中插入一个新的元素的时候,首先会遍历Set中已经存在的元素,并调用compareTo方法,根据返回的结果决定插入的位置,这样也就保证了元素的顺序

TreeSet是SortedSet接口唯一的实现类,TreeSet可以确保集合元素处于排序状态,TreeSet支持两种排序方式:自然排序和定制排序,其中自然排序为默认的排序方式
字符串默认 -----字母大小写排序

用Student类做Map的key值需要重写哪些方法?

使用HashMap等,如果是自定义的类,就必须重写equals和hashcode

HashMap的工作原理介绍下,是线程安全的吗?
1.底层数据结构
HashMap底层用的是哈希表,哈希表是由数组和链表组成

哈希表的优势:
数组是存储空间连续的,占用内存严重,故空间复杂度很大,但数组的二分查找时间复杂度小,为o(1),数组的特点:寻址容易,插入和删除数据难
链表:链表存储空间离散,占用内存较为宽松,故空间复杂度很小,但时间复杂度很大,达到O(n),链表的特点:寻址困难,但是插入和删除元素较为简单

哈希表:
那我们能不能综合两者的特性,做出一种寻址容易,插入删除也方便的数据解耦,答案是肯定的,这就是我们提到的哈希表
HashTable既满足了数据的查找方便,同时也不用占用多态的内容空间,使用也十分方便
哈希表有多种不同的实现方法:最常用的就是拉链法可以理解为链表的数组,数组和链表的结合体

数组中每一个元素产生链表–发生碰撞的时候,就会在hashcode后面存储链表

另外一个说法:大家遇到的时候要知道:HashMap采用位桶(bucket)+链表实现
对于HashMap以及子类而言,它们呢采用Hash算法来决定集合中元素的存储位置,当系统开始初始化HashMap时,系统会自动创建长度为capacity长度的Entry数组,这个数组里可以存储元素的位置被称为桶(Bucket),每一个bucket都有其指定索引,系统可以根据索引快速访问该bucket里存储的元素。
无论何时,HashMap的每个桶只能存储一个元素,也就是一个Entry,由于Entry对象可以包含一个引用变量,就是 Entry构造器的最后一个参数,用于指向下一个Entry数组,因此可能出现的情况是:HashMap的bucket中只有一个Entry,但这个Entry指向另一个Entry,这就形成了一个Entry链

如何实现快速的存取?
HashMap的get实现
当HashMap的每个bucket里存储的Entry只是单个Entry,也就是没有通过指针产生Entry链时,此时的HashMap具有最好的性能,当程序通过key值取出对应的value值时,系统 只要先计算出该key的hashcode的返回码,在根据hashCode返回值找出该key在table数组中的索引,然后取出该索引处的Entry,最后返回该key值对应的value值即可。

这里写图片描述

HashMap的put操作:
对key的hashCode()做hash,然后在计算index,
如果没有碰撞则直接放到bucket,
如果碰撞了,以链表的形式存在buckets之后,
如果节点已经存在就替换old value(保证key的唯一性)
不存在时如果buckets满了,(超过load factor * current capacity)就要resize(扩容)
说明:当key值出现hash冲突的时候,链表中的第一个元素都是最后面添加进来的元素,之前的责备next变量引用着

这里写图片描述

这里写图片描述

Entry是HashMap中的静态内部类
初始化容量16 负载因子0.75
扩容算法

源码核心:
jdk1.7和jdk.18是不同的
HashMap里面实现一个静态内部类,其重要的属性有key/value/next,从属性key、value我们就能够很面先看出来Entry就是HashMap里面键值对实现的一个基础bean,HashMap的基础就是一个线性数组,这个数组的就是Entry,Map里面保存的内容都保存在Entry里面,Entry是一个单向链表

HashMap不是线程安全的(方法中很多有synchronize策略关键字),ConcurrentHashMap是线程安全的,核心Set内部类

集合框架中的泛型介绍下?
java泛型(gengerics)是jdk1.5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序在编译时检测到非法的类型
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

思考?假定我们有这样一个需求,写一个排序方法,能够针对整数型数组、字符串数组什么其他任何类型的数组进行排序,该如何实现?
当然是可以使用java泛型

泛型方法
可以写一个泛型方法,该方法调用时可以接受不同类型的参数,根据传递给泛型方法的参数类型,编译器适当的处理每一个方法调用

这里写图片描述

2.有界的类型参数:
可能有时候,你会想限制哪些被允许传递到一个类型参数的类型种类范围,例如:一个操作数字的方法可能只希望接受Number或者Number子类的实例,这就是有界参数地目的

要声明一个有界的参数类型,首先列出类型参数的名称,后跟上参数的名称,后跟extends关键字,最后紧跟它的上限

这里写图片描述

3.泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面加了类型参数声明部分
和泛型方法一样,泛型类的参数声明部分也包含一个或者多个类型参数,参数用逗号隔开,一个泛型参数也可被称为一个类型变量,用于指定一个泛型类型的名称标识符,因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值