《编写高质量Java》(八)

七十、子列表只是原列表的一个视图

List接口提供了subList方法,其作用是返回一个列表的子列表。来看下subList的源码:

public List<E> subList(int fromIndex, int toIndex){
    return ( this instanceof RandomAccess ?
    new RandomAccessSubList<E>(this, fromIndex, toIndex) :
    new SubList<E>(this, fromIndex, toIndex));
}

subList方法是由AbstractList来实现的,它会根据是不是可以随机存取来提供不同的SubList实现方式,不过随机存储的使用频率比较高,而且RandomAccessSubList也是SubList的子类,所以所有的操作都是由SubList来实现的。我们来看看SubList的源码:

class SubList<E> extends AbstractList<E>{
    private AbstractList<E> l;
    private int offset;
    
    SubList(AbstractList<E> list, int fromIndex, int toIndex){
        l = list;
        offset = fromIndex;
        size = toIndex - fromIndex;
    }

    public E get(int index){
        return l.get(index + offset);
    }

    public void add(int index, E element){
        l.add(index + offset, element);
    }
    /*其他代码省略*/
}

通过阅读这段代码, 我们就非常清楚subList的实现原理:它返回的SubList类也是AbstractList子类,其所有的方法get,set,add,remove等都是在原始列表上的操作,它自身并没有生成一个数组或者链表,也就是子列表只是原列表的一个视图(View),所有的修改动作都反映在了原列表上。

七十一、推荐使用subList处理局部列表

例如,一个列表有100个元素,要删除索引位置为20~30的元素。可以直接使用下面一行代码即可实现:

list.subList(20, 30).clear();

七十二、生成子列表后不要再操作原列表

生成子列表后,修改原列表,则子列表的size方法会出现并发修改异常,这是因为:subList取出的列表是原始列表的一个视图,原数据集修改了,但是subList取出的子列表不会重新生成一个新列表(这点与数据库视图是不相同的),后面在对子列表进行操作时,就会检测到修改计数器与预期的不相同,于是就抛出了并发修改异常。
出现这个问题的最终原因还是在子列表提供的size方法的检查上,来看看size源码:

public int size(){
    checkForComodification();
    return size;
}

private void checkForComodification(){
    if(l.modCount != expectedModCount){
        throw new ConcurrentModificationException();
    }
}

expectedModCount是从什么地方来的呢?它是在SubList子列表的构造函数中赋值的,其值等于生成子列表时的修改次数(modCount 变量)。因此在生成子列表后再去修改原列表,l.modCount就必然比expectedModCount大1,不再保持相等了,于是就抛出了异常。

对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的办法就是通过Collections.unmodifiableList();设置列表为只读状态。

七十三、使用Comparator进行排序

在Java中要实现对数据排序有两种方式,一种是实现Comparable接口,一种是实现Comparator接口,两者的不同:

  • 实现了Comparable接口的类表明自身是可比较的,有了比较才能进行排序。
  • 而Comparator接口是一个工具类接口,它的名字也已经表明了他的作用:用作比较,它与原有类的逻辑没有关系,只是实现两个类的比价逻辑。

所以,一个类可以有很多的比较器,只要有业务需求就可以产生比较器,有比较器就可以产生N多种排序,而Comparable接口的排序只能说是实现类的默认排序算法。

七十四、不推荐使用binarySearch对列表进行检索

二分查找的一个前提是:数据集已经实现升序排列,否则二分法查找到的值是不准确的。使用binarySearch的二分法查找比indexOf的遍历算法性能上高很多,特别是在大数据集而且目标又快接近尾部时,binarySearch方法与indexOf相比,性能上会提升几十倍。从性能的角度考虑时可以选择binarySearch。

七十五、集合中的元素必须做到compareTo和equals同步

  • indexOf依赖equals方法查找,binarySearch则依赖compareTo方法查找。
  • equals是判断元素是否相等,compareTo是判断元素在排序中的位置是否相同
    实现了compareTo方法,就应该覆写equals方法,确保两者同步。

七十六、集合运算时使用更优雅的方式

  • 并集(合集):list1.addAll(list2)
  • 交集:list1.retainAll(list2)
  • 差集:list1.removeAll(list2)
  • 无重复的并集:list2.removeAll(list1);list1.addAll(list2)

七十七、使用shuffle打乱列表

Collections.shuffle(**)可以打乱一个列表的顺序。shuffle一般很少用到,使用场景如下:

  • 可以用在程序的“伪装”上
  • 可以用在抽奖程序中
  • 可以用在安全传输方面

七十八、减少HashMap中元素的数量

用HashMap作为数据集容器,偶尔会出现内存溢出的问题,这经常与HashMap有关。

  • HashMap比ArrayList多了一次封装,把String类型的键值对转换成Entry对象后再放入数组。
  • HashMap的size大于数组容量的0.75倍时,就开始扩容。

七十九、集合中的哈希码不要重复

在一个列表中查找某值是非常耗费资源的,随机存取的列表是遍历查找,顺序存储列表是链表查找,或者是Collections的二分法查找,但这都不够快,毕竟都是遍历。最快的还是以Hash开头的集合(如HashMap、HashSet等类)。
HashMap中的hashCode应避免冲突

八十、多线程使用Vector或HashTable

Vector是ArrayList的多线程版本,HashTable是HashMap的多线程版本。
基本上所有的集合都有一个叫做快速失败的校验值机,当一个集合在被多个线程修改和访问时,就可能出现ConcurrentModificationException异常,这是为了确保集合方法一致而设置的保护措施,它的实现原理就是modCount修改计数器:在读列表时,如果modCount发生变化(也就是有其他线程修改)则会抛出ConcurrentModificationException异常。这与线程同步是两码事,线程同步是为了保护集合中的数据不被脏读、脏写而设置的。

八十一、非稳定排序推荐使用List

Set和List最大的区别在于Set中的元素不可以重复。在Set的实现类中有一个比较常用的类需要了解一下:TreeSet。该类实现了类默认排序为升序的Set集合,如果插入一个元素,默认会按照升序排序。
SortedSet接口(TreeSet实现了该接口)只是定义了在给集合加入元素时将其进行排序,并不能保证元素修改后的排序结果,因此TreeSet适用于不变量的集合排序,但不适用于可变量的排序,特别是不确定何时元素会发生变化的数据集合。

八十二、由点及面,一叶知秋——集合大家族

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • Collection ├List │├LinkedList │├ArrayList │└Vector │└Stac...
    AndyZX阅读 873评论 0 1
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,257评论 0 16
  • Java源码研究之容器(1) 如何看源码 很多时候我们看源码, 看完了以后经常也没啥收获, 有些地方看得懂, 有些...
    骆驼骑士阅读 993评论 0 22
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,612评论 18 399
  • 在经过一次没有准备的面试后,发现自己虽然写了两年的android代码,基础知识却忘的差不多了。这是程序员的大忌,没...
    猿来如痴阅读 2,838评论 3 10