号外!RandomAccess它是个空接口
关于RamdomAccess接口,比较正规的解释为:
用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的list中表现的更加高效。
那么,接下来,Ted就敲起键盘,带着你撸一撸它,感受一下它的触感。
!它是一个空接口 标记接口
查看源码,发现是这样的
public interface RandomAccess {
}
什么都没有啊,那它的后代们(实现类)继承它的衣钵(方法)的机会都没有,那它老人家,用来干什么呢? 这种接口称为标记接口,如果某个类implements它,只是标记这个类和它有关系,仅此。
! 标记接口的具体使用
比较经常会用到的就是判断一个list集合,它是否是RandomAccess的一个实现,如果是,do somting 如果不是,do someting else.
此刻我们用到Collections类(内部有很多对集合操作的方法)举例 ,看代码
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法
此时,我们就可以看出,RandomAccess接口的作用,就是为了标记,用作实例判断 。
但此时,引申出2个问题
-
为什么传List 为(List<? extends Comparable<? super T>> list ) 这样奇怪的泛型结构 ?
-
哪些list 属于randomAccess ,哪些不是呢 ?
于是,我带着问题,遨游在度娘的怀抱中。。。。。
第1个问题,也不难理解,我们一般申明一个list集合时会这样,比如:List <Dog>
第1个?号,它就是指代这个具体的类型(如Dog),而且这个具体类型要extends Comparable,说明这个具体类型一定要实现Comparable接口(java APi规定 重写Comparable接口后才能对自定义对象进行比较,可具体查看本人博文https://blog.csdn.net/ted_cs/article/details/82713706有详细介绍)
Comparable<? super T >这句话,说明Compareable接口,也要传入规定类型,而?我们之前已经清楚 ,是指代List<Dog >中的Dog对象,那么Dog对象的super T ,T应该指的是Dog对象的父类了。
那为什么还要通过super,引出一个父类对象呢,因为父子类间也可以通过CompareTo()方法来比较,比的一般是它们公有的属性的自然顺序。
但因为我自定义的Dog类,我没有自定义父类,但Comparable后面如果不加泛型,又会报错,那么我也可以这样写:
public class Dog implements Comparable<Dog>{
重写CompareTo()方法
}
至于,第2个问题嘛,其实我们可以翻看java IPA就知道哪些list实现了RandmoAccess接口,
如ArraysList 实现了, 而LinkedList 没有 。
哎,讲到这俩个类,感觉我又要扯出来数据结构才能讲清楚,算了吧,我就简单过一下,然后我们看代码再来感悟一下。
ArraysList实现基础是Array数组,是有序且占用连续的内存;LinkedList实现是Link链表结构,是有序占用非连序内存;ArraysList是数组集合,有索引,可以根据索引快速get到对应数值,而LinkedList,虽然也有索引的概念,但可以理解为隐式的,只是说可以通过索引可以达到正序或倒序的查找结果,有一定优化作用,但本质上,还是一个一个Entry根据双向“指针”的去查找下一个Entry.
估计我讲到这里,有人会蒙圈,没关系,送上救命药给你,请查看 https://blog.csdn.net/ted_cs/article/details/82848350
最终得到的结果是,随着LinkedList的size增加,get方法的有很复杂,远不如AraysList的get的效率。
那回到我们问题,既然实现RandmoAccess接口,可以支持什么快速访问,什么高效算法什么的,那给人家LinkedList也实现好了啊,这们好的东西,怎么可以不分享?
那是因为本身实现快速访问,高效算法什么的,其实是针对数组结构的,因为有索引,让其有了实现的可能 ,而LinkedList不是数组实现的,你丫,就算给你实现这样接口,你丫也没有数组的功能啊,所以数据结构才是问题的根本!!!
indexedBinarySearch 方法:
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
- 请注意,这是一个根据值来查索引的方法
- 实现二分法查询,list中的对象一定要实现Comparable接口
通过此代码可以看出,其实这就是一个遍历数组的过程,通过索引来获得对应的对象值,因为是数组结构,get方法的效率高
indexedBinarySearch 方法:
private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
通过上面的的代码,我们发现link结构的list,调用get方法,传入了ListInterator对象i,和索引mid,
但我们知道,对于Link结构的集合的话,通过iterator遍历是要比普通for循环遍历高效的,所以,判断list没有实现RamdomAccess接口后,其实也相当于知道它不是数组结构,那么就会调用迭代器来进行遍历,提高性能。
讲到这里了,我们应该深刻的认识到
- linkedList集合,遍历时,由其是huge size时,千万不要用普通for循环,太low效了!!
End!