7.Java集合

以下是《瘋狂Java講義》中的一些知識,如有錯誤,煩請指正。


集合概述

Java集合可以分為Set、List、Map、Queue四種體系。Set是無序集合、List是有序集合、Queue是隊列實現,Map保存映射關系。Java的集合類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。

集合類:為了保存數量不確定的數據以及保存具有映射關系的數據。集合里只能保存對象,不能保存基本類型。集合類主要負責保存、盛裝其他數據,因此集合類也被稱為容器類。所有集合類都位于java.util包下。

派生接口:Collection和Map

Collection和Iterator接口

Collection是List、Set、Queue接口的父接口。

import java.util.*;
public class CollectionTest
{
    public static void main(String[] args)
    {
        Collection c = new ArrayList();
        // 添加元素
        c.add("孫悟空");
        // 雖然集合里不能放基本類型的值,但Java支持自動裝箱
        c.add(6);
        System.out.println("c集合的元素個數為:" + c.size()); // 輸出2
        // 刪除指定元素
        c.remove(6);
        System.out.println("c集合的元素個數為:" + c.size()); // 輸出1
        // 判斷是否包含指定字符串
        System.out.println("c集合的是否包含\"孫悟空\"字符串:"
            + c.contains("孫悟空")); // 輸出true
        c.add("輕量級Java EE企業應用實戰");
        System.out.println("c集合的元素:" + c);
        Collection books = new HashSet();
        books.add("輕量級Java EE企業應用實戰");
        books.add("瘋狂Java講義");
        System.out.println("c集合是否完全包含books集合?"
            + c.containsAll(books)); // 輸出false
        // 用c集合減去books集合里的元素
        c.removeAll(books);
        System.out.println("c集合的元素:" + c);
        // 刪除c集合里所有元素
        c.clear();
        System.out.println("c集合的元素:" + c);
        // 控制books集合里只剩下c集合里也包含的元素
        books.retainAll(c);
        System.out.println("books集合的元素:" + books);
    }
}
import java.util.*;
public class ForeachTest
{
    public static void main(String[] args)
    {
        // 創建集合、添加元素的代碼與前一個程序相同
        Collection books = new HashSet();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));
        for (Object obj : books)
        {
            // 此處的book變量也不是集合元素本身
            String book = (String)obj;
            System.out.println(book);
            if (book.equals("瘋狂Android講義"))
            {
                // 下面代碼會引發ConcurrentModificationException異常
                books.remove(book);     //①
            }
        }
        System.out.println(books);
    }
}

Iterator也是Java集合框架的成員,主要用于遍歷,Iterable接口是Collection接口的父接口。

import java.util.*;
public class CollectionEach
{
    public static void main(String[] args)
    {
        // 創建一個集合
        Collection books = new HashSet();
        books.add("輕量級Java EE企業應用實戰");
        books.add("瘋狂Java講義");
        books.add("瘋狂Android講義");
        // 調用forEach()方法遍歷集合,Collection集合直接調用父接口的默認方法
        // foreach 方法所需參數類型是一個函數型接口
        books.forEach(obj -> System.out.println("迭代集合元素:" + obj));
    }
}
import java.util.*;
public class IteratorTest
{
    public static void main(String[] args)
    {
        // 創建集合、添加元素的代碼與前一個程序相同
        Collection books = new HashSet();
        books.add("輕量級Java EE企業應用實戰");
        books.add("瘋狂Java講義");
        books.add("瘋狂Android講義");
        // 獲取books集合對應的迭代器
        Iterator it = books.iterator();
        while(it.hasNext())
        {
            // it.next()方法返回的數據類型是Object類型,因此需要強制類型轉換
            String book = (String)it.next();
            System.out.println(book);
            if (book.equals("瘋狂Java講義"))
            {
                // 從集合中刪除上一次next方法返回的元素
                it.remove();
            }
            // 對book變量賦值,不會改變集合元素本身
            book = "測試字符串";   
        }
        System.out.println(books);
    }
}

當使用Iterator迭代訪問Collection集合元素時,Collection集合里的元素不能被改變,只有通過Iterator的remove方法刪除上一次返回的集合元素才可以,否則引發java.util.ConcurrentModificationException異常

import java.util.*;
public class IteratorErrorTest
{
    public static void main(String[] args)
    {
        // 創建集合、添加元素的代碼與前一個程序相同
        Collection books = new HashSet();
        books.add("輕量級Java EE企業應用實戰");
        books.add("瘋狂Java講義");
        books.add("瘋狂Android講義");
        // 獲取books集合對應的迭代器
        Iterator it = books.iterator();
        while(it.hasNext())
        {
            String book = (String)it.next();
            System.out.println(book);
            if (book.equals("瘋狂Android講義"))
            {
                // 使用Iterator迭代過程中,不可修改集合元素,下面代碼引發異常
                books.remove(book);
            }
        }
    }
}

迭代時檢測到集合被修改將會引發異常,只有刪除集合的某個特定元素才不會引發異常。

lambda表達式遍歷Iterator
java8提供的forEachRemaining參數同樣也是函數式接口。

import java.util.*;
public class IteratorEach
{
    public static void main(String[] args)
    {
        // 創建集合、添加元素的代碼與前一個程序相同
        Collection books = new HashSet();
        books.add("輕量級Java EE企業應用實戰");
        books.add("瘋狂Java講義");
        books.add("瘋狂Android講義");
        // 獲取books集合對應的迭代器
        Iterator it = books.iterator();
        // 使用Lambda表達式(目標類型是Comsumer)來遍歷集合元素
        it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
    }
}

使用foreach循環遍歷集合元素
同樣當使用foreach迭代訪問集合元素時,該集合不能被改變。

import java.util.*;
public class ForeachTest
{
    public static void main(String[] args)
    {
        // 創建集合、添加元素的代碼與前一個程序相同
        Collection books = new HashSet();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));
        for (Object obj : books)
        {
            // 此處的book變量也不是集合元素本身
            String book = (String)obj;
            System.out.println(book);
            if (book.equals("瘋狂Android講義"))
            {
                // 下面代碼會引發ConcurrentModificationException異常
                books.remove(book);
            }
        }
        System.out.println(books);
    }
}

使用Predicate操作集合
Java8新增了一個removeIf(Predicate filter)方法,該方法將刪除符合filter條件的所有元素。Predicate也是函數式接口,因此可以使用Lambda表達式作為參數。

import java.util.*;
import java.util.function.*;
public class PredicateTest
{
    public static void main(String[] args)
    {
        // 創建一個集合
        Collection books = new HashSet();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂iOS講義"));
        books.add(new String("瘋狂Ajax講義"));
        books.add(new String("瘋狂Android講義"));
        // 使用Lambda表達式(目標類型是Predicate)過濾集合
        books.removeIf(ele -> ((String)ele).length() < 10);
        System.out.println(books);
    }
}

使用Predicate可以簡化集合的運算。callAll方法只統計滿足Predicate條件的圖書。


import java.util.*;
import java.util.function.*;
public class PredicateTest2
{
    public static void main(String[] args)
    {
        // 創建books集合、為books集合添加元素的代碼與前一個程序相同。
        Collection books = new HashSet();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂iOS講義"));
        books.add(new String("瘋狂Ajax講義"));
        books.add(new String("瘋狂Android講義"));
        // 統計書名包含“瘋狂”子串的圖書數量
        System.out.println(calAll(books , ele->((String)ele).contains("瘋狂")));
        // 統計書名包含“Java”子串的圖書數量
        System.out.println(calAll(books , ele->((String)ele).contains("Java")));
        // 統計書名字符串長度大于10的圖書數量
        System.out.println(calAll(books , ele->((String)ele).length() > 10));
    }
    public static int calAll(Collection books , Predicate p)
    {
        int total = 0;
        for (Object obj : books)
        {
            // 使用Predicate的test()方法判斷該對象是否滿足Predicate指定的條件
            if (p.test(obj))
            {
                total ++;
            }
        }
        return total;
    }
}

Java 8新增的Stream操作
Java 8還新增了Stream、IntStream、LongStream、DoubleStream等流式API。
獨立使用Stream的步驟如下:

  1. 使用Stream或XxxStream的builder()類方法創建該Stream對應Builder。
  2. 重復調用Builder的add()方法向該流中添加多個元素。
  3. 調用Builder的build()方法獲取對應的Stream。
  4. 調用Stream的聚集方法。
import java.util.stream.*;
public class IntStreamTest
{
    public static void main(String[] args)
    {
        IntStream is = IntStream.builder()
            .add(20)
            .add(13)
            .add(-2)
            .add(18)
            .build();
        // 下面調用聚集方法的代碼每次只能執行一個
        System.out.println("is所有元素的最大值:" + is.max().getAsInt());
        System.out.println("is所有元素的最小值:" + is.min().getAsInt());
        System.out.println("is所有元素的總和:" + is.sum());
        System.out.println("is所有元素的總數:" + is.count());
        System.out.println("is所有元素的平均值:" + is.average());
        System.out.println("is所有元素的平方是否都大于20:"
            + is.allMatch(ele -> ele * ele > 20));
        System.out.println("is是否包含任何元素的平方大于20:"
            + is.anyMatch(ele -> ele * ele > 20));
        // 將is映射成一個新Stream,新Stream的每個元素是原Stream元素的2倍+1
        IntStream newIs = is.map(ele -> ele * 2 + 1);
        // 使用方法引用的方式來遍歷集合元素
        newIs.forEach(System.out::println); // 輸出41 27 -3 37
    }
}

Collection接口提供了一個stream()默認方法,該方法可返回該集合對應的流,接下來即可通過流API來操作集合元素。由于Stream可以對集合元素進行整體的聚集操作,因此Stream極大了豐富了集合的功能。

import java.util.*;
import java.util.function.*;
public class CollectionStream
{
    public static void main(String[] args)
    {
        // 創建books集合、為books集合添加元素的代碼與8.2.5小節的程序相同。
        Collection books = new HashSet();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂iOS講義"));
        books.add(new String("瘋狂Ajax講義"));
        books.add(new String("瘋狂Android講義"));
        // 統計書名包含“瘋狂”子串的圖書數量
        System.out.println(books.stream()
            .filter(ele->((String)ele).contains("瘋狂"))
            .count()); // 輸出4
        // 統計書名包含“Java”子串的圖書數量
        System.out.println(books.stream()
            .filter(ele->((String)ele).contains("Java") )
            .count()); // 輸出2
        // 統計書名字符串長度大于10的圖書數量
        System.out.println(books.stream()
            .filter(ele->((String)ele).length() > 10)
            .count()); // 輸出2
        // 先調用Collection對象的stream()方法將集合轉換為Stream,
        // 再調用Stream的mapToInt()方法獲取原有的Stream對應的IntStream
        books.stream().mapToInt(ele -> ((String)ele).length())
            // 調用forEach()方法遍歷IntStream中每個元素
            .forEach(System.out::println);// 輸出8  11  16  7  8
    }
}

Set集合

Set集合不允許包含相同的元素,如果試圖把兩個相同的元素加入同一個Set集合中,添加操作失敗,add方法返回false,且新元素不會被加入。

HashSet類是Set接口的典型實現。HashSet按Hash算法來存儲集合中的元素,因此具有很好的存取和查找性能。當向HashSet集合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據該HashCode值來決定該對象在HashSet中存儲位置。如果有兩個元素通過equals方法比較返回true,但它們的hashCode()方法返回值不相等,HashSet將會把它們存儲在不同位置,也就可以添加成功。

特點:無序、非同步、可以包含null。
HashSet判斷元素相等:1.equals方法比較相等;2.hashCode()方法返回值相等。所以如果重寫equals方法,需要重寫hashCode方法。

import java.util.*;
// 類A的equals方法總是返回true,但沒有重寫其hashCode()方法
class A
{
    public boolean equals(Object obj)
    {
        return true;
    }
}
// 類B的hashCode()方法總是返回1,但沒有重寫其equals()方法
class B
{
    public int hashCode()
    {
        return 1;
    }
}
// 類C的hashCode()方法總是返回2,且重寫其equals()方法總是返回true
class C
{
    public int hashCode()
    {
        return 2;
    }
    public boolean equals(Object obj)
    {
        return true;
    }
}
public class HashSetTest
{
    public static void main(String[] args)
    {
        HashSet books = new HashSet();
        // 分別向books集合中添加兩個A對象,兩個B對象,兩個C對象
        books.add(new A());
        books.add(new A());
        books.add(new B());
        books.add(new B());
        books.add(new C());
        books.add(new C());
        System.out.println(books);//輸出2A,2B,1C
    }
}

重寫hashCode的基本規則:

  • 同一個對象多次調用hashCode方法應該返回相同的值
  • 兩個對象equal方法返回true。則hashCode應返回相等的值
  • 用作equal方法比較標準的實例變量,都應該用于計算hashCode值。

程序把可變對象添加到HashSet中之后,盡量不要修改參與運算的hashCode()、equals()的實例變量,否則會導致HashSet無法正確操作這些集合元素。因為對象的Hashcode改變了,實際存儲的位置并沒有變化,無法對其進行準確的索引。

import java.util.*;
class R
{
    int count;
    public R(int count)
    {
        this.count = count;
    }
    public String toString()
    {
        return "R[count:" + count + "]";
    }
    public boolean equals(Object obj)
    {
        if(this == obj)
            return true;
        if (obj != null && obj.getClass() == R.class)
        {
            R r = (R)obj;
            return this.count == r.count;
        }
        return false;
    }
    public int hashCode()
    {
        return this.count;
    }
}
public class HashSetTest2
{
    public static void main(String[] args)
    {
        HashSet hs = new HashSet();
        hs.add(new R(5));
        hs.add(new R(-3));
        hs.add(new R(9));
        hs.add(new R(-2));
        // 打印HashSet集合,集合元素沒有重復
        System.out.println(hs);
        // 取出第一個元素
        Iterator it = hs.iterator();
        R first = (R)it.next();
        // 為第一個元素的count實例變量賦值
        first.count = -3;     
        // 再次輸出HashSet集合,集合元素有重復元素
        System.out.println(hs);
        // 刪除count為-3的R對象
        hs.remove(new R(-3));    
        // 可以看到被刪除了一個R元素
        System.out.println(hs);
        System.out.println("hs是否包含count為-3的R對象?"
            + hs.contains(new R(-3))); // 輸出false
        System.out.println("hs是否包含count為-2的R對象?"
            + hs.contains(new R(-2))); // 輸出false
    }
}

LinkedHashSet
LinkedHashSet根據元素的hashCode來決定元素的存儲位置,但同時使用鏈表維護元素的次序。當遍歷LinkedHashSet集合里的元素時,將會按照元素的添加順序來訪問集合里的元素。因為需要維護元素的插入順序,性能略低于HashSet的性能,但在迭代訪問Set里的全部元素時有很好的性能。

import java.util.*;
public class LinkedHashSetTest
{
    public static void main(String[] args)
    {
        LinkedHashSet books = new LinkedHashSet();
        books.add("瘋狂Java講義");
        books.add("輕量級Java EE企業應用實戰");
        System.out.println(books);//與添加順序一致
        // 刪除 瘋狂Java講義
        books.remove("瘋狂Java講義");
        // 重新添加 瘋狂Java講義
        books.add("瘋狂Java講義");
        System.out.println(books);
    }
}

TreeSet
TreeSet是SortedSet接口的實現類,可以確保集合元素處于排序狀態。

import java.util.*;
public class TreeSetTest
{
    public static void main(String[] args)
    {
        TreeSet nums = new TreeSet();
        // 向TreeSet中添加四個Integer對象
        nums.add(5);
        nums.add(2);
        nums.add(10);
        nums.add(-9);
        // 輸出集合元素,看到集合元素已經處于排序狀態
        System.out.println(nums);
        // 輸出集合里的第一個元素
        System.out.println(nums.first()); // 輸出-9
        // 輸出集合里的最后一個元素
        System.out.println(nums.last());  // 輸出10
        // 返回小于4的子集,不包含4
        System.out.println(nums.headSet(4)); // 輸出[-9, 2]
        // 返回大于5的子集,如果Set中包含5,子集中還包含5
        System.out.println(nums.tailSet(5)); // 輸出 [5, 10]
        // 返回大于等于-3,小于4的子集。
        System.out.println(nums.subSet(-3 , 4)); // 輸出[2]
    }
}

TreeSet采用紅黑樹的數據結構對元素進行排序。TreeSet支持兩種排序方法:自然排序和定制排序。
1. 自然排序
TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間的大小關系,然后將集合元素按照升序排列。如果試圖把一個對象添加到TreeSet時,則該對象必須實現Compareable接口的compareTo(Object obj)方法,否則拋出異常。

import java.util.*;
class Err{}
public class TreeSetErrorTest
{
    public static void main(String[] args)
    {
        TreeSet ts = new TreeSet();
        // 向TreeSet集合中添加兩個Err對象
        ts.add(new Err());
        ts.add(new Err());  //Err沒有實現compareTo方法,引發ClassCastException
    }
}

大部分類在實現CompareTo方法時,需要將被比較對象強制轉換成相同類型。因此向TreeSet中添加的應該是同一個類的對象,否則引發ClassCastException。

import java.util.*;
public class TreeSetErrorTest2
{
    public static void main(String[] args)
    {
        TreeSet ts = new TreeSet();
        // 向TreeSet集合中添加兩個對象
        ts.add(new String("瘋狂Java講義"));
        ts.add(new Date());   // 會引發異常
    }
}

如果希望TreeSet能正常運作,TreeSet只能添加同一種類型的對象。
TreeSet判斷對象相等:通過compareTo方法比較是否返回0

import java.util.*;
class Z implements Comparable
{
    int age;
    public Z(int age)
    {
        this.age = age;
    }
    // 重寫equals()方法,總是返回true
    public boolean equals(Object obj)
    {
        return true;
    }
    // 重寫了compareTo(Object obj)方法,總是返回1,總是認為不相等
    public int compareTo(Object obj)
    {
        return 1;
    }
}
public class TreeSetTest2
{
    public static void main(String[] args)
    {
        TreeSet set = new TreeSet();
        Z z1 = new Z(6);
        set.add(z1);
        // 第二次添加同一個對象,輸出true,表明添加成功
        System.out.println(set.add(z1));    //true
        // 下面輸出set集合,將看到有兩個元素
        System.out.println(set);
        // 修改set集合的第一個元素的age變量
         ((Z)(set.first())).age = 9;
        // 輸出set集合的最后一個元素的age變量,將看到也變成了9
        System.out.println(((Z)(set.last())).age);
    }
}

注意equals方法應該和compareTo方法有一致的結果。

一旦改變了TreeSet集合里可變元素的實例變量,導致它的與其他對象的大小順序發生改變,但TreeSet不會調整他們的 順序,可能導致compareTo返回0。當試圖刪除該對象時,刪除會失敗,可以刪除沒有被修改的實例變量、切不與被修改的實例變量重復的變量。執行一次成功刪除之后TreeSet會對集合中的元素重新索引,接下來就可以刪除TreeSet中的所有元素了。

import java.util.*;
class R implements Comparable
{
    int count;
    public R(int count)
    {
        this.count = count;
    }
    public String toString()
    {
        return "R[count:" + count + "]";
    }
    // 重寫equals方法,根據count來判斷是否相等
    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if(obj != null && obj.getClass() == R.class)
        {
            R r = (R)obj;
            return r.count == this.count;
        }
        return false;
    }
    // 重寫compareTo方法,根據count來比較大小
    public int compareTo(Object obj)
    {
        R r = (R)obj;
        return count > r.count ? 1 :
            count < r.count ? -1 : 0;
    }
}
public class TreeSetTest3
{
    public static void main(String[] args)
    {
        TreeSet ts = new TreeSet();
        ts.add(new R(5));
        ts.add(new R(-3));
        ts.add(new R(9));
        ts.add(new R(-2));
        // 打印TreeSet集合,集合元素是有序排列的
        System.out.println(ts);    // -3 -2 5 9
        // 取出第一個元素
        R first = (R)ts.first();
        // 對第一個元素的count賦值
        first.count = 20;
        // 取出最后一個元素
        R last = (R)ts.last();
        // 對最后一個元素的count賦值,與第二個元素的count相同
        last.count = -2;
        // 再次輸出將看到TreeSet里的元素處于無序狀態,且有重復元素
        System.out.println(ts);   // 20 -2 5 -2
        // 刪除實例變量被改變的元素,刪除失敗
        System.out.println(ts.remove(new R(-2)));   // false
        System.out.println(ts);//20 -2 5 -2
        // 刪除實例變量沒有被改變的元素,刪除成功
        System.out.println(ts.remove(new R(5)));    // true,之后會重新索引,可以刪除所有元素了
        System.out.println(ts);// 20 -2 -2
    }
}

2. 定制排序
如果需要實現定制排序,例如降序,需要在創建TreeSet集合對象時,提供一個Comparator對象與該TreeSet集合關聯,由該Comparator對象負責集合元素的排序邏輯。可以用Lambda表達式代替Comparator對象,這時候無需實現Comparable接口。

import java.util.*;
class M
{
    int age;
    public M(int age)
    {
        this.age = age;
    }
    public String toString()
    {
        return "M[age:" + age + "]";
    }
}
public class TreeSetTest4
{
    public static void main(String[] args)
    {
        // 此處Lambda表達式的目標類型是Comparator
        TreeSet ts = new TreeSet((o1 , o2) ->
        {
            M m1 = (M)o1;
            M m2 = (M)o2;
            // 根據M對象的age屬性來決定大小,age越大,M對象反而越小
            return m1.age > m2.age ? -1
                : m1.age < m2.age ? 1 : 0;
        });
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        System.out.println(ts);
    }
}

List集合

List集合代表一個元素有序可重復的集合,集合中每個元素都有其對應的順序索引。List作為Collection接口的子接口,與Set集合相比多了根據索引插入、替換、刪除集合元素的方法。

import java.util.*;
public class ListTest
{
    public static void main(String[] args)
    {
        List books = new ArrayList();
        // 向books集合中添加三個元素
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));
        System.out.println(books);
        // 將新字符串對象插入在第二個位置
        books.add(1 , new String("瘋狂Ajax講義"));
        for (int i = 0 ; i < books.size() ; i++ )
        {
            System.out.println(books.get(i));
        }
        // 刪除第三個元素
        books.remove(2);
        System.out.println(books);
        // 判斷指定元素在List集合中位置:輸出1,表明位于第二位
        System.out.println(books.indexOf(new String("瘋狂Ajax講義"))); //①
        //將第二個元素替換成新的字符串對象
        books.set(1, new String("瘋狂Java講義"));
        System.out.println(books);
        //將books集合的第二個元素(包括)
        //到第三個元素(不包括)截取成子集合
        System.out.println(books.subList(1 , 2));
    }
}

List判斷對象相等:equals方法返回true即可。

import java.util.*;
class A
{
    public boolean equals(Object obj)
    {
        return true;
    }
}
public class ListTest2
{
    public static void main(String[] args)
    {
        List books = new ArrayList();
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));
        System.out.println(books);
        // 刪除集合中A對象,將導致第一個元素被刪除
        books.remove(new A());     // ①
        System.out.println(books);
        // 刪除集合中A對象,再次刪除集合中第一個元素
        books.remove(new A());     // ②
        System.out.println(books);
    }
}

List常用的默認方法sort()需要一個Comparator對象控制秩序,可使用Lambda表達式作為參數;默認方法replaceAll()需要一個函數式接口的UnaryOperator來替換所有集合元素,也可用Lambda表達式作為參數。

import java.util.*;
public class ListTest3
{
    public static void main(String[] args)
    {
        List books = new ArrayList();
        // 向books集合中添加4個元素
        books.add(new String("輕量級Java EE企業應用實戰"));
        books.add(new String("瘋狂Java講義"));
        books.add(new String("瘋狂Android講義"));
        books.add(new String("瘋狂iOS講義"));
        // 使用目標類型為Comparator的Lambda表達式對List集合排序
        books.sort((o1, o2)->((String)o1).length() - ((String)o2).length());
        System.out.println(books);
        // 使用目標類型為UnaryOperator的Lambda表達式來替換集合中所有元素
        // 該Lambda表達式控制使用每個字符串的長度作為新的集合元素
        books.replaceAll(ele->((String)ele).length());
        System.out.println(books); // 輸出[7, 8, 11, 16]

    }
}

Set只提供一個iterator()方法,List提供了listIterator()方法,該方法返回一個ListIterator對象,ListIterator接口繼承了Iterator接口。

import java.util.*;
public class ListIteratorTest
{
    public static void main(String[] args)
    {
        String[] books = {
            "瘋狂Java講義", "瘋狂iOS講義",
            "輕量級Java EE企業應用實戰"
        };
        List bookList = new ArrayList();
        for (int i = 0; i < books.length ; i++ )
        {
            bookList.add(books[i]);
        }
        ListIterator lit = bookList.listIterator();
        while (lit.hasNext())
        {
            System.out.println(lit.next());
            lit.add("-------分隔符-------");
        }
        System.out.println("=======下面開始反向迭代=======");
        while(lit.hasPrevious())
        {
            System.out.println(lit.previous());
        }
    }
}

ArrayList和Vector
兩個類都是基于數組實現的List類,所以都封裝了動態的可以再分配的Object[]數組。ArrayList對象和Vector對象使用initialCapacity參數來設置數組的長度。添加元素超出數組長度時,initialCapacity會自動增加。
Vector提供了一個子類Stack,這也是一個古老的集合,線程安全,性能較差,盡量少用。
區別在于:ArrayList時線程不安全的,Vector是線程安全的。所以Vector性能比ArrayList性能要低。
Vector提供了Stack子類,用于模擬后進先出(LIFO)的“棧”這種數據結構。同樣線程安全、性能較差。如果需要實現“棧”,可以使用ArrayDeque。

固定長度的List
介紹數組時的Arrays類提供了asList(Object a)方法,該方法可以把一個數組或者指定個數對象轉換成一個List集合,這個List集合既不是ArrayList實現類的實例,也不是Vector實現類的實例,而是Arrays的內部類ArrayList的實例。Arrays.ArrayList是一個固定長度的List集合,程序只能遍歷訪問該集合里的元素,不可增加、刪除該集合里的元素。

import java.util.*;
public class FixedSizeList
{
    public static void main(String[] args)
    {
        List fixedList = Arrays.asList("瘋狂Java講義"
            , "輕量級Java EE企業應用實戰");
        // 獲取fixedList的實現類,將輸出Arrays$ArrayList
        System.out.println(fixedList.getClass());
        // 使用方法引用遍歷集合元素
        fixedList.forEach(System.out::println);
        // 試圖增加、刪除元素都會引發UnsupportedOperationException異常
        fixedList.add("瘋狂Android講義");
        fixedList.remove("瘋狂Java講義");
    }
}

Queue集合

Queue用于模擬“先進先出”(FIFO)隊列這種數據結構,通常隊列不允許隨機訪問隊列中的元素。Queue接口有一個PriorityQueue實現類和一個Deque接口,后者既可以當成隊列使用,也可以當棧使用。

PriorityQueue實現類
比較標準的隊列實現類--保存隊列元素的順序按照隊列元素的大小重新排列。這是違反隊列的先進先出的原則的

import java.util.*;
public class PriorityQueueTest
{
    public static void main(String[] args)
    {
        PriorityQueue pq = new PriorityQueue();
        // 下面代碼依次向pq中加入四個元素
        pq.offer(6);
        pq.offer(-3);
        pq.offer(20);
        pq.offer(18);
        // 輸出pq隊列,并不是按元素的加入順序排列
        System.out.println(pq); // 輸出[-3, 6, 20, 18]
        // 訪問隊列第一個元素,其實就是隊列中最小的元素:-3
        System.out.println(pq.poll());
    }
}

沒有從小到大排序是因為受了toString方法返回值的影響。如果是使用poll()方法,則嚴格從小到大。
PriorityQueue兩種排序方式:自然排序、定制排序,詳細參考TreeSet。

Deque接口和ArrayDeque實現類
Deque接口代表一個雙端隊列,還可以當成棧使用。Deque接口提供了典型的實現類ArrayDeque,這是基于數組實現的雙端隊列。
ArrayDeque可作為“棧”,也可作為“隊列”
注意:ArrayList和ArrayDeque兩個集合類的實現機制基本相似,底層都采用一個動態的、可重分配的Object[]數組來存儲集合元素,當集合元素超出了數組的容量時,系統會在底層重新分配一個Object[]數組來存儲元素。
ArrayDeque作為棧:

import java.util.*;
public class ArrayDequeStack
{
    public static void main(String[] args)
    {
        ArrayDeque stack = new ArrayDeque();
        // 依次將三個元素push入"棧"
        stack.push("瘋狂Java講義");
        stack.push("輕量級Java EE企業應用實戰");
        stack.push("瘋狂Android講義");
        // 輸出:[瘋狂Android講義, 輕量級Java EE企業應用實戰, 瘋狂Java講義]
        System.out.println(stack);
        // 訪問第一個元素,但并不將其pop出"棧",輸出:瘋狂Android講義
        System.out.println(stack.peek());
        // 依然輸出:[瘋狂Android講義, 瘋狂Java講義, 輕量級Java EE企業應用實戰]
        System.out.println(stack);
        // pop出第一個元素,輸出:瘋狂Android講義
        System.out.println(stack.pop());
        // 輸出:[輕量級Java EE企業應用實戰, 瘋狂Java講義]
        System.out.println(stack);
    }
}

ArrayDeque作為隊列

import java.util.*;
public class ArrayDequeQueue
{
    public static void main(String[] args)
    {
        ArrayDeque queue = new ArrayDeque();
        // 依次將三個元素加入隊列
        queue.offer("瘋狂Java講義");
        queue.offer("輕量級Java EE企業應用實戰");
        queue.offer("瘋狂Android講義");
        // 輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰, 瘋狂Android講義]
        System.out.println(queue);
        // 訪問隊列頭部的元素,但并不將其poll出隊列"棧",輸出:瘋狂Java講義
        System.out.println(queue.peek());
        // 依然輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰, 瘋狂Android講義]
        System.out.println(queue);
        // poll出第一個元素,輸出:瘋狂Java講義
        System.out.println(queue.poll());
        // 輸出:[輕量級Java EE企業應用實戰, 瘋狂Android講義]
        System.out.println(queue);
    }
}

LinkedList實現類
這是一個List接口的實現類,所以可以根據索引來隨機訪問集合中的元素。除此之外還實現了Deque接口,可作為雙端隊列。雙端隊列棧的頭部和尾部分別對應隊列的頭部和尾部。

import java.util.*;
public class LinkedListTest
{
    public static void main(String[] args)
    {
        LinkedList books = new LinkedList();
        // 將字符串元素加入隊列的尾部
        books.offer("book1");
        books.offer("boook2");
        // 將一個字符串元素加入棧的頂部
        books.push("book3");
        books.push("book4");
        // 將字符串元素添加到隊列的頭部(相當于棧的頂部)
        books.offerFirst("book5");
        books.offerFirst("book6");
        // 以List的方式(按索引訪問的方式)來遍歷集合元素
        for (int i = 0; i < books.size() ; i++ )
        {
            System.out.println("遍歷中:" + books.get(i));
        }
        // 訪問、并不刪除棧頂的元素 book6
        System.out.println(books.peekFirst());
        // 訪問、并不刪除隊列的最后一個元素 book2
        System.out.println(books.peekLast());
        // 將棧頂的元素彈出“棧”  book6
        System.out.println(books.pop());
        // 下面輸出將看到隊列中第一個元素被刪除
        System.out.println(books);
        // 訪問、并刪除隊列的最后一個元素book2
        System.out.println(books.pollLast());
        
        System.out.println(books);
    }
}

LinkedList與ArrayList、ArrayDeque的實現機制完全不同。后兩者內部以數組形式來保存集合中的元素,因此隨機訪問集合元素時性能較好;LinkedList內部以鏈表的形式來保存集合中的元素,因此隨機訪問集合中的元素時性能較差,但在插入、刪除元素時性能較好。Vector和后兩者都是以數組形式存儲元素,,而Vector實現了線程安全,所以性能較差。

各種線性表的性能分析
數組一塊連續內存區來保存所有的數組元素,所以在隨機訪問時性能最好,所有的內部以數組作為底層實現的集合隨機訪問時性能都比較好。而內部以鏈表作為底層實現的集合在執行插入、刪除操作時性能較好。
List集合使用的幾點建議

  • 需要遍歷List集合元素時,對于ArrayList、Vector集合,應該使用隨機訪問方法來遍歷集合元素;對于LinkedList應該采用迭代器來遍歷。
  • 需要經常執行插入、刪除來改變包含大量數據的List集合的大小,可考慮使用LinkedList集合。使用ArrayList需要重新分配內部數組的大小,效果比較差。
  • 如果有多個線程需要同時訪問List集合中的元素,開發者可考慮使用Collection將集合包裝成線性安全的集合。

Map集合

Map用于保存具有映射關系的數據,包含兩組值,一組是key,一組是value。key不允許重復。Java源碼中先實現了Map,然后通過包裝一個所有value都為null的Map實現了Set集合。Map中包括一個內部類Entry,該類封裝了一個key-value對。

import java.util.*;
public class MapTest
{
    public static void main(String[] args)
    {
        Map map = new HashMap();
        // 成對放入多個key-value對
        map.put("瘋狂Java講義" , 109);
        map.put("瘋狂iOS講義" , 10);
        map.put("瘋狂Ajax講義" , 79);
        // 多次放入的key-value對中value可以重復
        map.put("輕量級Java EE企業應用實戰" , 99);
        // 放入重復的key時,新的value會覆蓋原有的value
        // 如果新的value覆蓋了原有的value,該方法返回被覆蓋的value
        System.out.println(map.put("瘋狂iOS講義" , 99)); // 輸出10
        System.out.println(map); // 輸出的Map集合包含4個key-value對
        // 判斷是否包含指定key
        System.out.println("是否包含值為 瘋狂iOS講義 key:"
            + map.containsKey("瘋狂iOS講義")); // 輸出true
        // 判斷是否包含指定value
        System.out.println("是否包含值為 99 value:"
            + map.containsValue(99)); // 輸出true
        // 獲取Map集合的所有key組成的集合,通過遍歷key來實現遍歷所有key-value對
        for (Object key : map.keySet() )
        {
            // map.get(key)方法獲取指定key對應的value
            System.out.println(key + "-->" + map.get(key));
        }
        map.remove("瘋狂Ajax講義"); // 根據key來刪除key-value對。
        System.out.println(map); // 輸出結果不再包含 瘋狂Ajax講義=79 的key-value對
    }
}
import java.util.*;
public class MapTest2
{
    public static void main(String[] args)
    {
        Map map = new HashMap();
        // 成對放入多個key-value對
        map.put("瘋狂Java講義" , 109);
        map.put("瘋狂iOS講義" , 99);
        map.put("瘋狂Ajax講義" , 79);
        // 嘗試替換key為"瘋狂XML講義"的value,由于原Map中沒有對應的key,
        // 因此對Map沒有改變,不會添加新的key-value對
        map.replace("瘋狂XML講義" , 66);
        System.out.println(map);
        // 使用原value與參數計算出來的結果覆蓋原有的value
        map.merge("瘋狂iOS講義" , 10 ,
            (oldVal , param) -> (Integer)oldVal + (Integer)param);
        System.out.println(map); // "瘋狂iOS講義"的value增大了10
        // 當key為"Java"對應的value為null(或不存在時),使用計算的結果作為新value
        map.computeIfAbsent("Java" , (key)->((String)key).length());
        System.out.println(map); // map中添加了 Java=4 這組key-value對
        // 當key為"Java"對應的value存在時,使用計算的結果作為新value
        map.computeIfPresent("Java",
            (key , value) -> (Integer)value * (Integer)value);
        System.out.println(map); // map中 Java=4 變成 Java=16
    }
}

HashMap和Hashtable實現類
HashMap和Hashtable類似于ArrayList和Vector的關系,Hashtable是一個古老的Map類。類似Vector盡量少用Hashtable,可以通過Collections工具類把HashMap變成線程安全的。
區別:

  • Hashtable線程安全,HashMap線程不安全。HashMap性能更高。多個線程訪問同一個Map對象時使用Hashtable
  • Hashtable不允許使用null作為key或者value,HashMap可以使用null作為key或者value
import java.util.*;
public class NullInHashMap
{
    public static void main(String[] args)
    {
        HashMap hm = new HashMap();
        // 試圖將兩個key為null的key-value對放入HashMap中
        hm.put(null , null);
        hm.put(null , null);    // 無法放入
        // 將一個value為null的key-value對放入HashMap中
        hm.put("a" , null);    // ②
        // 輸出Map對象
        System.out.println(hm);
    }
}

HashMap、Hashtable判斷key相等:兩個key通過equal方法返回true;兩個key的hashCode值相等。
HashMap、Hashtable判斷value相等:兩個對象通過equal方法返回true。

import java.util.*;
class A
{
    int count;
    public A(int count)
    {
        this.count = count;
    }
    // 根據count的值來判斷兩個對象是否相等。
    public boolean equals(Object obj)
    {
        if (obj == this)
            return true;
        if (obj != null && obj.getClass() == A.class)
        {
            A a = (A)obj;
            return this.count == a.count;
        }
        return false;
    }
    // 根據count來計算hashCode值。
    public int hashCode()
    {
        return this.count;
    }
}
class B
{
    // 重寫equals()方法,B對象與任何對象通過equals()方法比較都返回true
    public boolean equals(Object obj)
    {
        return true;
    }
}
public class HashtableTest
{
    public static void main(String[] args)
    {
        Hashtable ht = new Hashtable();
        ht.put(new A(60000) , "瘋狂Java講義");
        ht.put(new A(87563) , "輕量級Java EE企業應用實戰");
        ht.put(new A(1232) , new B());
        System.out.println(ht);
        // 只要兩個對象通過equals比較返回true,
        // Hashtable就認為它們是相等的value。
        // 由于Hashtable中有一個B對象,
        // 它與任何對象通過equals比較都相等,所以下面輸出true。
        System.out.println(ht.containsValue("測試字符串")); // 輸出true
        // 只要兩個A對象的count相等,它們通過equals比較返回true,且hashCode相等
        // Hashtable即認為它們是相同的key,所以下面輸出true。
        System.out.println(ht.containsKey(new A(87563)));   // 輸出true
        // 下面語句可以刪除最后一個key-value對
        ht.remove(new A(1232));
        System.out.println(ht);
    }
}

盡量不要使用可變對象作為HashMap、Hashtable的key,如果確實需要,盡量不要在程序中修改作為key的可變對象。

LinkedHashMap實現類
LinkedHashMap也是用雙向鏈表來維護key-value對的次序,該鏈表負責維護Map的迭代順序,其余key-value對的插入順序保持一致。因為需要維護元素的插入順序,性能不如HashMap,但在迭代訪問Map里的全部元素時將有較好的性能。

import java.util.*;
public class HashMapErrorTest
{
    public static void main(String[] args)
    {
        HashMap ht = new HashMap();
        // 此處的A類與前一個程序的A類是同一個類
        ht.put(new A(60000) , "瘋狂Java講義");
        ht.put(new A(87563) , "輕量級Java EE企業應用實戰");
        // 獲得Hashtable的key Set集合對應的Iterator迭代器
        Iterator it = ht.keySet().iterator();
        // 取出Map中第一個key,并修改它的count值
        A first = (A)it.next();
        first.count = 87563;   
        // 輸出{A@1560b=瘋狂Java講義, A@1560b=輕量級Java EE企業應用實戰}
        System.out.println(ht);
        // 只能刪除沒有被修改過的key所對應的key-value對
        ht.remove(new A(87563));
        System.out.println(ht);
        // 無法獲取剩下的value,下面兩行代碼都將輸出null。
        System.out.println(ht.get(new A(87563)));   //輸出null
        System.out.println(ht.get(new A(60000)));   //輸出null
    }
}

LinkedHashMap實現類
使用雙向鏈表維護key-value對。因為需要維護元素的插入順序,性能低于HaspMap。

import java.util.*;
public class LinkedHashMapTest
{
    public static void main(String[] args)
    {
        LinkedHashMap scores = new LinkedHashMap();
        scores.put("語文" , 80);
        scores.put("英文" , 82);
        scores.put("數學" , 76);
        // 調用forEach方法遍歷scores里的所有key-value對
        scores.forEach((key, value) -> System.out.println(key + "-->" + value));
    }
}

使用Properties讀寫屬性文件
Properties類是Hashtable類的子類,該對象在處理屬性文件時特別方便。Properties類可以把Map對象和屬性文件關聯起來,從而可以把Map對象中的key-value對寫入屬性文件,也可以把屬性文件中的屬性名=屬性值加載到Map對象中。由于屬性文件里的屬性名、屬性值只能是字符串類型,所以Properties里的key、value都是字符串類型

import java.util.*;
import java.io.*;
public class PropertiesTest
{
    public static void main(String[] args)
        throws Exception
    {
        Properties props = new Properties();
        // 向Properties中增加屬性
        props.setProperty("username" , "yeeku");
        props.setProperty("password" , "123456");
        // 將Properties中的key-value對保存到a.ini文件中
        props.store(new FileOutputStream("a.ini")
            , "comment line");   //寫入文件
        // 新建一個Properties對象
        Properties props2 = new Properties();
        // 向Properties中增加屬性
        props2.setProperty("gender" , "male");
        // 將a.ini文件中的key-value對追加到props2中
        props2.load(new FileInputStream("a.ini") );   //讀取
        System.out.println(props2);
    }
}

SortedMap和TreeMap實現類
Set接口派生出SortedSet子接口,TreeSet接口也有一個TreeSet實現類。
Map接口派生出SortedMap子接口,SortedMap接口也有一個TreeMap實現類。
兩種排序方式:自然排序、定制排序,同TreeSet。
TreeMap判斷key相等:兩個key通過compareTo方法返回0。但是自定義類時需要equals方法和compareTo方法返回的結果一致。

import java.util.*;
class R implements Comparable
{
    int count;
    public R(int count)
    {
        this.count = count;
    }
    public String toString()
    {
        return "R[count:" + count + "]";
    }
    // 根據count來判斷兩個對象是否相等。
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj != null && obj.getClass() == R.class)
        {
            R r = (R)obj;
            return r.count == this.count;
        }
        return false;
    }
    // 根據count屬性值來判斷兩個對象的大小。
    public int compareTo(Object obj)
    {
        R r = (R)obj;
        return count > r.count ? 1 :
            count < r.count ? -1 : 0;
    }
}
public class TreeMapTest
{
    public static void main(String[] args)
    {
        TreeMap tm = new TreeMap();
        tm.put(new R(3) , "輕量級Java EE企業應用實戰");
        tm.put(new R(-5) , "瘋狂Java講義");
        tm.put(new R(9) , "瘋狂Android講義");
        System.out.println(tm);
        // 返回該TreeMap的第一個Entry對象
        System.out.println(tm.firstEntry());
        // 返回該TreeMap的最后一個key值
        System.out.println(tm.lastKey());
        // 返回該TreeMap的比new R(2)大的最小key值。
        System.out.println(tm.higherKey(new R(2)));
        // 返回該TreeMap的比new R(2)小的最大的key-value對。
        System.out.println(tm.lowerEntry(new R(2)));
        // 返回該TreeMap的子TreeMap 介于-1--4之間的key
        System.out.println(tm.subMap(new R(-1) , new R(4)));
    }
}

Map實現類的性能分析
HashMap和Hashtable實現機制幾乎一樣,但是后者是線程安全的,HashMap通常比Hashtable快。
TreeMap比HashMap和Hashtable慢,因為其底層采用紅黑樹管理key-value對。優勢在于:key-value對總是處于有序狀態,無須專門進行排序操作。
以上應該多考慮使用HashMap。
LinkedHashMap比HashMap慢一點,因為需要維護鏈表維持添加順序 。

操作集合的工具類

Collections提供了大量方法對Set、List、Map進行排序、查詢、修改操作。

排序

public class SortTest
{
    public static void main(String[] args)
    {
        ArrayList nums = new ArrayList();
        nums.add(2);
        nums.add(-5);
        nums.add(3);
        nums.add(0);
        System.out.println(nums); // 輸出:[2, -5, 3, 0]
        Collections.reverse(nums); // 將List集合元素的次序反轉
        System.out.println(nums); // 輸出:[0, 3, -5, 2]
        Collections.sort(nums); // 將List集合元素的按自然順序排序
        System.out.println(nums); // 輸出:[-5, 0, 2, 3]
        Collections.shuffle(nums); // 將List集合元素的按隨機順序排序
        System.out.println(nums); // 每次輸出的次序不固定
    }
}

查找替換

import java.util.*;
public class SearchTest
{
    public static void main(String[] args)
    {
        ArrayList nums = new ArrayList();
        nums.add(2);
        nums.add(-5);
        nums.add(3);
        nums.add(0);
        System.out.println(nums); // 輸出:[2, -5, 3, 0]
        System.out.println(Collections.max(nums)); // 輸出最大元素,將輸出3
        System.out.println(Collections.min(nums)); // 輸出最小元素,將輸出-5
        Collections.replaceAll(nums , 0 , 1); // 將nums中的0使用1來代替
        System.out.println(nums); // 輸出:[2, -5, 3, 1]
        // 判斷-5在List集合中出現的次數,返回1
        System.out.println(Collections.frequency(nums , -5));
        Collections.sort(nums); // 對nums集合排序
        System.out.println(nums); // 輸出:[-5, 1, 2, 3]
        //只有排序后的List集合才可用二分法查詢,輸出3
        System.out.println(Collections.binarySearch(nums , 3));
    }
}

同步控制
HashSet、TreeSet、ArrayList、ArrayDeque、linkedlist、HashMap、TreeMap都是線程不安全的。Collections提供了許多類方法把他們包裝成線程同步集合。

import java.util.*;
public class SynchronizedTest
{
    public static void main(String[] args)
    {
        // 下面程序創建了四個線程安全的集合對象
        Collection c = Collections
            .synchronizedCollection(new ArrayList());
        List list = Collections.synchronizedList(new ArrayList());
        Set s = Collections.synchronizedSet(new HashSet());
        Map m = Collections.synchronizedMap(new HashMap());
    }
}

設置不可變集合

import java.util.*;
public class UnmodifiableTest
{
    public static void main(String[] args)
    {
        // 創建一個空的、不可改變的List對象
        List unmodifiableList = Collections.emptyList();
        // 創建一個只有一個元素,且不可改變的Set對象
        Set unmodifiableSet = Collections.singleton("瘋狂Java講義");
        // 創建一個普通Map對象
        Map scores = new HashMap();
        scores.put("語文" , 80);
        scores.put("Java" , 82);
        // 返回普通Map對象對應的不可變版本
        Map unmodifiableMap = Collections.unmodifiableMap(scores);
        // 下面任意一行代碼都將引發UnsupportedOperationException異常
        unmodifiableList.add("測試元素");   //異常
        unmodifiableSet.add("測試元素");    //異常
        unmodifiableMap.put("語文" , 90);   //異常
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,710評論 18 399
  • Collection ├List │├LinkedList │├ArrayList │└Vector │└Stac...
    AndyZX閱讀 886評論 0 1
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,127評論 0 62
  • 3.3 集合 一方面, 面向對象語言對事物的體現都是以對象的形式,為了方便對多個對象的操作,就要對對象進行存儲。另...
    閆子揚閱讀 740評論 0 1
  • 查找一個表結構 desc是describe的縮寫 添加列 刪除列 修改列 修改列名 修改表名
    吐痰高手閱讀 151評論 0 0