Et Voilà | Java 数据结构(一): 我的百宝箱Collections

说到Java数据结构,Collection下衍生出的各种list, set, 以及Map下的子类可以说是种类繁多,数不胜数。如何挑选如何使用就好比妹子们逛街买配饰,要跟鞋搭,跟衣服搭,跟妆容搭,各种搭。在详细归纳,比较这些结构之前,大黄对另一个类更感兴趣,就是Collections。和Collection好像啊是吧,又是亲兄弟吗?No No No, 名字像,可实际上却是完全不同的东西,就像谁一生没遇见过几个同名同姓的朋友呀,但即使名字一模一样,也是不同的个体。

Collection 与 Collections的区别

光看名字没有用,我们直接打开jdk源码来看看这两个类的java doc里的关键描述。

Collection

The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements.

Collections

This class consists exclusively of static methods that operate on or return collections. It contains polymorphic algorithms that operate on collections, "wrappers", which return a new collection backed by a specified collection, and a few other odds and ends.

很清楚有木有, Collection是一个基础的接口,包含一组对象,也即它的元素。而Collections是包含静态方法,操作或返回集合类。也就是说这个类实际上是一个"工具类"。类似于Collection和Collections这样关系的组合常见的还有Array和Arrays类,是不是很眼熟啊,那个大明湖畔的Arrays.asList方法。这里就不再扩大篇幅分析这一对了。明白了区别以后就来看看Collections到底能干什么吧,看看它到底是不是一把swiss knife。

百宝箱Collections——"网红"方法介绍举例

Collections里有一些被频繁使用,几乎天天露脸的方法,大大方便了对于集合类一些特殊要求的操作。

我要排序

对于顺序的要求是Collection的基础需求,有时需要排序,有时需要打乱,有时还需要指定交换某两个元素的位置等等,大黄用例子介绍下列最常用的几个方法。

  • sort(List list):对List中的所有元素进行自然升序排序
  • sort(List list, Comparator c):自定义比较器进行排序
  • shuffle(List list):对List中的元素进行随机打乱,类似于洗牌
  • swap(List list, int i, int j):将List中i处元素和j处元素进行交换
  • rotate(List list, int distance):将所有元素向右移位指定长度
  • reverse(List list):对List集合中元素进行一次倒序排序
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class collectionTest {
    public static void main(String[] args) {
    String[]  testData={ "London", "Shanghai", "Paris", "Tokyo", "Cannes", "Berlin" };
    ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(testData));
    System.out.println("Initial Order:" + dataList);
    //排序
    Collections.sort(dataList);
    System.out.println("Order after sort:" + dataList);
    //倒序
    Collections.reverse(dataList);
    System.out.println("Order after reverse:" + dataList);  
    //打乱
    Collections.shuffle(dataList);
    System.out.println("Order after shuffle:" + dataList);
    //交换
    Collections.swap(dataList, 1,2);
    System.out.println("Order after swap:" + dataList);
    //移位
    Collections.rotate(dataList, 1);
    System.out.println("Order after rotate:" + dataList);
  }

}

看一下每一个方法后产生的变化:

Initial Order:[London, Shanghai, Paris, Tokyo, Cannes, Berlin]
Order after sort:[Berlin, Cannes, London, Paris, Shanghai, Tokyo]
Order after reverse:[Tokyo, Shanghai, Paris, London, Cannes, Berlin]
Order after shuffle:[Paris, Tokyo, Berlin, London, Cannes, Shanghai]
Order after swap:[Paris, Berlin, Tokyo, London, Cannes, Shanghai]
Order after rotate:[Shanghai, Paris, Berlin, Tokyo, London, Cannes]

我要查找替换

查找和替换同样也是集合类中的重要概念,如果不想生硬地使用for循环这样的结构,熟练地运用下面这些常见方法会大大提升效率。我们向原始list多添加一个Shanghai,然后开始验证下列方法:
binarySearch(List list, Object key):使用二分搜索法,以获得指定对象在List中的索引,前提是集合已经排序

  • max(Collection coll):获取最大元素
  • min(Collection coll):获取最小元素
  • frequency(Collection Object o):获取集合中指定元素出现的次数
  • replaceAll(List list, Object old, Object new):替换指定的元素
  • fill(List list, Object obj):使用指定元素填充集合
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class collectionTest {
    public static void main(String[] args) {
    String[]  testData={ "London", "Shanghai", "Paris", "Tokyo", "Cannes", "Berlin","Shanghai" };
    ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(testData));
    System.out.println("Initial Order:" + dataList);
    //取极值
    System.out.println("max:" + Collections.max(dataList));
    System.out.println("min:" + Collections.min(dataList));
    //指定元素的出现次数
    System.out.println("Frequency of Shanghai:" + Collections.frequency(dataList, "Shanghai"));
    //替换指定元素
    Collections.replaceAll(dataList, "Shanghai", "Dubai");
    System.out.println("After replaceAll:" + dataList);
    
    // binarySearch
    System.out.println("binarySearch before sort:" + Collections.binarySearch(dataList, "London"));
    Collections.sort(dataList);
    System.out.println("Order after sort:" + dataList);
    System.out.println("binarySearch after sort:" + Collections.binarySearch(dataList, "London"));

    Collections.fill(dataList, "Beijing");
    System.out.println("After fill:" + dataList);
    
   }
}

结果是:

Initial Order:[London, Shanghai, Paris, Tokyo, Cannes, Berlin, Shanghai]
max:Tokyo
min:Berlin
Frequency of Shanghai:2
After replaceAll:[London, Dubai, Paris, Tokyo, Cannes, Berlin, Dubai]
binarySearch before sort:-3
Order after sort:[Berlin, Cannes, Dubai, Dubai, London, Paris, Tokyo]
binarySearch after sort:4
After fill:[Beijing, Beijing, Beijing, Beijing, Beijing, Beijing, Beijing]

这里要提一下binarySearch, 二分法排序查找,上面在排序前输出的答案很离谱,但排序后是正确的,为什么呢?看一下Jdk源码:

 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);
    }

    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
    }

    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
    }

用排序前大黄的例子来看它是怎么运转的:

  1. low=0; high=6; mid=3, value=Tokyo
  2. low=0; high=2; mid=1, value=Dubai
  3. low=2; high=2; mid=2, value=Paris
  4. low=2; high=1; return -3

这就是为啥出现-3的原因,由于没有排序,二分的思路从一开始就走向了歧途。所以这个方法的使用还是要看具体情况,你的数据是否是按序排列的,你是否可以接受先排序一次再进行查找,这些都是需要考虑的。

Wrapper的魔力

如果你仔细看了大黄贴出来的java doc,你肯定注意到了Collections里还有一些"wrappers"。我们一般译为封装器。主要作用就是将我们原始的一些Collection类封装转换成具有特定属性的Collection. 其中比较重要的一种是synch wrappers:synchronizedXxx(),如synchronizedList,synchronizedSet。这些方法会返回指定集合对象对应的同步对象,从而解决多线程并发访问集合时线程的安全问题。这也提供了除使用concurrent包以外另一种多线程的数据结构构造方式。
另一种常见的wrapper种类我们统一称之为不可变集合,即转换成这类对象后集合将被禁止相关的操作。这类wrapper包含

  • emptyXxx():获取一个空的不可变集合对象
  • singletonXxx():获取一个只包含指定对象的,不可变集合对象。
  • unmodifiableXxx():获取指定集合对象的不可变视图

当你有一些特殊需求时,比如你的集合对外必须是只读的,那么就可以运用这些封装器。简单的一个例子

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


public class collectionTest {
    public static void main(String[] args) {
    String[]  testData={ "London", "Shanghai", "Paris", "Tokyo", "Cannes", "Berlin","Shanghai" };
    ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(testData));
    
    List<String> unModifyList = (List) Collections.unmodifiableList(dataList);
    unModifyList.add("Madrid"); //此处应有exception
    System.out.println("unModifyList:" + unModifyList);
    }
}

可以看到,在add时,抛出了java.lang.UnsupportedOperationException,印证了我们的想法:集合对象不可变。

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055)
    at jianshu.collectionTest.main(collectionTest.java:15)

后话

Collections里还有许多其他的方法,wrappers,这里就不一一介绍了,有兴趣的可以翻看jdk源码。工欲善其事,必先利其器,熟悉并挑选正确的工具类方法可以大大提升我们对集合类对象的操作效率,对我们深入了解Java数据结构的核心部分打下基础,这也是这个类优秀之处。

Et voilà!

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

推荐阅读更多精彩内容

  • Java 语言支持的类型分为两类:基本类型和引用类型。整型(byte 1, short 2, int 4, lon...
    xiaogmail阅读 1,342评论 0 10
  • 第十天 权限修饰符 public protected default private 同一类 true true ...
    炙冰阅读 524评论 0 1
  • 1.Java集合框架是什么?说出一些集合框架的优点? 每种编程语言中都有集合,最初的Java版本包含几种集合类:V...
    独念白阅读 747评论 0 2
  • 3.3 集合 一方面, 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另...
    闫子扬阅读 715评论 0 1
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,483评论 0 3