Java 8 中的流操作-基本使用&性能測試

2022-02-07 好好學java

  點擊上方 好好學java ,選擇 星標 公眾號

重磅資訊、乾貨,第一時間送達

一、流(Stream)簡介

流是 Java8 中 API 的新成員,它允許你以聲明式的方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。這有點兒像是我們操作資料庫一樣,例如我想要查詢出熱量較低的菜品名字我就可以像下面這樣:

SELECT name FROM dishes WHERE calorie < 400;

您看,我們並沒有對菜品的什麼屬性進行篩選(比如像之前使用迭代器一樣每個做判斷),我們只是表達了我們想要什麼。那麼為什麼到了 Java 的集合中,這樣做就不行了呢?

另外一點,如果我們想要處理大量的數據又該怎麼辦?是否是考慮使用多線程進行並發處理呢?如果是,那麼可能編寫的關於並發的代碼比使用迭代器本身更加的複雜,而且調試起來也會變得麻煩。

基於以上的幾點考慮,Java 設計者在 Java 8 版本中,引入了流的概念,來幫助您節約時間!並且有了 lambda 的參與,流操作的使用將更加順暢!

特點一:內部迭代

就現在來說,您可以把它簡單的當成一種高級的迭代器(Iterator),或者是高級的 for 循環,區別在於,前面兩者都是屬於外部迭代,而流採用內部迭代。

上圖簡要說明了內部迭代與外部迭代的差異,我們再舉一個生活中實際的例子(引自《Java 8 實戰》),比如您想讓您兩歲的孩子索菲亞把她的玩具都收到盒子裡面去,你們之間可能會產生如下的對話:

這正是你每天都要對 Java 集合做的事情。你外部迭代了一個集合,顯式地取出每個項目再加以處理,但是如果你只是跟索菲亞說:「把地上所有玩具都放進盒子裡」,那麼索菲亞就可以選擇一手拿娃娃一手拿球,或是選擇先拿離盒子最近的那個東西,再拿其他的東西。

採用內部迭代,項目可以透明地並行處理,或者用優化的順序進行處理,要是使用 Java 過去的外部迭代方法,這些優化都是很困難的。

這或許有點雞蛋裡挑骨頭,但這差不多就是 Java 8 引入流的原因了——Streams 庫的內部迭代可以自動選擇一種是和你硬體的數據表示和並行實現。

特點二:只能遍歷一次

請注意,和迭代器一樣,流只能遍歷一次。當流遍歷完之後,我們就說這個流已經被消費掉了,你可以從原始數據那裡重新獲得一條新的流,但是卻不允許消費已消費掉的流。例如下面代碼就會拋出一個異常,說流已被消費掉了:

List<String> title = Arrays.asList("Wmyskxz", "Is", "Learning", "Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);
// 運行上面程序會報以下錯誤
/*
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Test1.main(Tester.java:17)
*/

特點三:方便的並行處理

Java 8 中不僅提供了方便的一些流操作(比如過濾、排序之類的),更重要的是對於並行處理有很好的支持,只需要加上 .parallel() 就行了!例如我們使用下面程序來說明一下多線程流操作的方便和快捷,並且與單線程做了一下對比:

public class StreamParallelDemo {

    /** 總數 */
    private static int total = 100_000_000;

    public static void main(String[] args) {
        System.out.println(String.format("本計算機的核數:%d", Runtime.getRuntime().availableProcessors()));

        // 產生1000w個隨機數(1 ~ 100),組成列表
        Random random = new Random();
        List<Integer> list = new ArrayList<>(total);

        for (int i = 0; i < total; i++) {
            list.add(random.nextInt(100));
        }

        long prevTime = getCurrentTime();
        list.stream().reduce((a, b) -> a + b).ifPresent(System.out::println);
        System.out.println(String.format("單線程計算耗時:%d", getCurrentTime() - prevTime));

        prevTime = getCurrentTime();
        // 只需要加上 .parallel() 就行了
        list.stream().parallel().reduce((a, b) -> a + b).ifPresent(System.out::println);
        System.out.println(String.format("多線程計算耗時:%d", getCurrentTime() - prevTime));

    }

    private static long getCurrentTime() {
        return System.currentTimeMillis();
    }
}

以上程序分別使用了單線程流和多線程流計算了一千萬個隨機數的和,輸出如下:

本計算機的核數:8
655028378
單線程計算耗時:4159
655028378
多線程計算耗時:540

並行流的內部使用了默認的 ForkJoinPool 分支/合併框架,它的默認線程數量就是你的處理器數量,這個值是由 Runtime.getRuntime().availableProcessors() 得到的(當然我們也可以全局設置這個值)。我們也不再去過度的操心加鎖線程安全等一系列問題。

二、流基本操作

至少我們從上面了解到了,流操作似乎是一種很強大的工具,能夠幫助我們節約我們時間的同時讓我們程序可讀性更高,下面我們就具體的來了解一下 Java 8 帶來的新 API Stream,能給我們帶來哪些操作。

1、篩選和切片filter

Stream 接口支持 filter 方法,該操作會接受一個返回 boolean 的函數作為參數,並返回一個包含所有符合該條件的流。例如,你可以這樣選出所有以字母 w 開頭的單詞並列印:

List<String> words = Arrays.asList("wmyskxz", "say", "wow", "to", "everybody");
words.stream()
     .filter(word -> word.startsWith("w"))
     .forEach(System.out::println);
// ==============輸出:===============
// wmyskxz
// wow

這個過程類似下圖:

當然如果您不是想要輸出而是想要返回一個集合,那麼可以使用 .collect(toList()),就像下面這樣:

List<String> words = Arrays.asList("wmyskxz", "say", "wow", "to", "everybody");
List<String> filteredWords = words.stream()
                                  .filter(word -> word.startsWith("w"))
                                  .collect(Collectors.toList());

distinct

流還支持一個叫做 distinct 的方法,它會返回一個元素各異(根據流所生成的元素的 hashCode 和 equals 方法實現)的流。例如,以下代碼會篩選出列表中所有的偶數,並確保沒有重複:

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 2, 1, 3, 4);
numbers.stream()
       .filter(integer -> integer % 2 == 0)
       .distinct()
       .forEach(System.out::println);
// ==============輸出:===============
// 2
// 4

limit

流支持 limit(n) 方法,該方法會返回一個不超過給定長度的流,所需長度需要作為參數傳遞給 limit。如果流是有序的,則最多會返回前 n 個元素。比如,你可以建立一個 List,選出前 3 個元素:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
numbers.stream()
       .filter(integer -> integer % 2 == 0)
       .limit(3)
       .forEach(System.out::println);
// ==============輸出:===============
// 2
// 4
// 6

請注意雖然上述的集合是有序的,但 limit 本身並不會做任何排序的操作。

skip

流還支持 skip(n) 方法,返回一個扔掉了前 n 個元素的流。如果流中元素不足 n 個,則返回一個空流。請注意 litmit 和 skip 是互補的!例如,下面這段程序,選出了所有的偶數並跳過了前兩個輸出:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
numbers.stream()
       .filter(integer -> integer % 2 == 0)
       .skip(2)
       .forEach(System.out::println);
// ==============輸出:===============
// 6
// 8

2、映射

一個非常常見的數據處理套路就是從某些對象中選擇信息。比如在 SQL 裡,你可以從表中選擇一列,Stream API 也通過 map 和 flatMap 方法提供了類似的工具。

map

流支持 map 方法,他會接受一個函數作為參數。這個函數會被應用到每個元素身上嗎,並將其映射成一個新的函數。例如,下面的代碼把方法引用 Words::getContent 傳給了 map 方法,來提取流中 Words 的具體內容:

public static void main(String[] args) {
    List<Words> numbers = Arrays.asList(new Words("我沒有三顆心臟"),
            new Words("公眾號"), new Words("wmyskxz"));
    numbers.stream()
           .map(Words::getContent)
           .forEach(System.out::println);
}

@Data
@AllArgsConstructor
private static class Words {
    private String content;
}
// ==============輸出:===============
// 我沒有三顆心臟
// 公眾號
// wmyskxz

但是如果你現在只想要找出每個 Words 具體內容的長度又該怎麼辦呢?我們可以再進行一次映射:

public static void main(String[] args) {
    List<Words> numbers = Arrays.asList(new Words("我沒有三顆心臟"),
            new Words("公眾號"), new Words("wmyskxz"));
    numbers.stream()
           .map(Words::getWords)
           .map(String::length)
           .forEach(System.out::println);
}

@Data
@AllArgsConstructor
private static class Words {
    private String words;
}
// ==============輸出:===============
// 7
// 3
// 7

flatMap:流的扁平化

你已經看到我們是如何使用 map 方法來返回每個 Words 的具體長度了,現在讓我們來擴展一下:對於一個 Words 集合,我需要知道這個集合裡一共有多少個不相同的字符呢?例如,給定單詞列表為:["Hello", "World"],則需要返回的列表是:["H", "e", "l", "o", "W", "r", "d"]。

您可能會覺得簡單,而後寫下下列錯誤的第一版本:

List<String> words = Arrays.asList("Hello", "World");
words.stream()
     .map(s -> s.split(""))
     .distinct()
     .collect(Collectors.toList())
     .forEach(System.out::println);
// ==============輸出:===============
// [Ljava.lang.String;@238e0d81
// [Ljava.lang.String;@31221be2

為什麼會這樣呢?這個方法的問題在於,傳遞給 map 方法的 lambda 表達式為每個單詞返回了一個 String[],所以經過 map 方法之後返回的流就不是我們預想的 Stream<String>,而是 Stream<String[]>,下圖就說明了這個問題:

幸好我們可以使用 flatMap 來解決這個問題:

List<String> words = Arrays.asList("Hello", "World");
words.stream()
     .map(s -> s.split(""))
     .flatMap(Arrays::stream)
     .distinct()
     .collect(Collectors.toList())
     .forEach(System.out::println);
// ==============輸出:===============
// H
// e
// l
// o
// W
// r
// d

使用 flatMap 方法的效果是,各個數組並不是分別映射成一個流,而是映射成流的內容。一言蔽之就是 flatMap 讓你一個流中的每個值都轉換成另一個六,然後把所有的流連接起來成為一個流,具體過程如下圖:

3、查找和匹配

另一個常見的數據處理套路是看看數據集中的某些元素是否匹配一個給定的屬性,Stream API 通過 allMatchanyMatchnoneMatchfindFirstfindAny 方法提供了這樣的工具(其實到這裡看名字就會大概能夠知道怎麼使用了)。

我們簡單的舉兩個例子就好。

比如,你可以用它來看看集合裡面是否有偶數:

List<Integer> numbers = Arrays.asList(1, 2, 3);
if (numbers.stream().anyMatch(i -> i % 2 == 0)) {
    System.out.println("集合裡有偶數!");
}

再比如,你可以用來它來檢驗是否集合裡都為偶數:

List<Integer> numbers = Arrays.asList(2, 2, 4);
if (numbers.stream().allMatch(i -> i % 2 == 0)) {
    System.out.println("集合裡全是偶數!");
}

再或者,給定一個數字列表,找出第一個平方能被 3 整除的數:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Optional<Integer> firstSquareDivisibledByThree =
        numbers.stream()
               .map(x -> x * x)
               .filter(x -> x % 3 == 0)
               .findFirst();
System.out.println(firstSquareDivisibledByThree.get());
// ==============輸出:===============
// 9

Optional 簡介:
Optional<T> 類是 java.util.Optional 包裡的一個容器類,代表一個值存在或者不存在。在上面的代碼中,findFirst() 可能什么元素都找不到,Java 8 的設計人員引入了 Optional<T>,這樣就不用返回眾所周知容易出問題的 null 了。我們在這裡不對 Optional 做細緻的討論。

4、歸約:reduce

到目前為止,你見到過的終端操作(下面我們會說到這些操作其實分為中間操作和終端操作)都是返回一個 boolean(allMatch 之類的)、void(forEach)或 Optional 對象(findFirst 等)。你也見到過了使用 collect 來將流中的所有元素合併成一個 List。

接下來我們來接觸更加複雜的一些操作,比如 「挑出單詞中長度最長的的單詞」 或是 「計算所有單詞的總長度」。此類查詢需要將流中的元素反覆結合起來,得到一個值。這樣的查詢可以被歸類為歸約操作(將流歸約成一個值)。

數組求和

在研究 reduce 之前,我們先來回顧一下我們在之前是如何對一個數字數組進行求和的:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = 0;
for (int x : numbers) {
    sum += x;
}
System.out.println(sum);
// ==============輸出:===============
// 15

numbers 中的每個元素都用加法運算符反覆迭代來得到結果。通過反覆使用加法,我們最終把一個數字列表歸約成了一個數字。在這段代碼中,我們一共使用了兩個參數:

要是還能把所有的數字相乘,而不用複製粘貼這段代碼,豈不是很好?這正是 reduce 操作的用武之地,它對這種重複應用的模式做了抽象。你可以像下面這樣對流中所有的元素求和:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println(sum);
// ==============輸出:===============
// 15

其中 reduce 接受了兩個參數:

你也可以很容易改造成所有元素相乘的形式,只需要將另一個 Lambda:(a, b) -> a * b 傳遞給 reduce 就可以了:

int product = numbers.stream().reduce(0, (a, b) -> a * b);

我們先來深入研究一下 reduce 是如何對一個數字流進行求和的:

如上圖所示一樣的,reduce 每一次都把結果返回並與下一次的元素進行操作,比如第一次當遍歷到元素 1 時,此時返回初始值 0 + 1 = 1,然後再用此時的返回值 1 與第二個元素進行疊加操作,如此往復,便完成了對數字列表的求和運算。

當然你也可以使用方法引用讓這段代碼更加簡潔:

int sum = numbers.stream().reduce(0, Integer::sum);

無初始值

reduce 還有一個重載的變體,它不接受初始值,但是會返回一個 Optional 對象(考慮到流中沒有任何元素的情況):

Optional<Integer> sum = numbers.stream().reduce(Integer::sum);

最大值和最小值

有點類似於上面的操作,我們可以使用下面這樣的 reduce 來計算流中的最大值or最小值:

// 最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 最小值
Optional<Integer> max = numbers.stream().reduce(Integer::min);

5、中間操作和結束操作(終端操作)

Stream API 上的所有操作分為兩類:中間操作和結束操作。中間操作只是一種標記,只有結束操作才會觸發實際計算。

中間操作又可以分為無狀態的(Stateless)和有狀態的(Stateful),無狀態中間操作是指元素的處理不受前面元素的影響,而有狀態的中間操作必須等到所有元素處理之後才知道最終結果,比如排序是有狀態操作,在讀取所有元素之前並不能確定排序結果;

結束操作又可以分為短路操作和非短路操作,短路操作是指不用處理全部元素就可以返回結果,比如找到第一個滿足條件的元素。之所以要進行如此精細的劃分,是因為底層對每一種情況的處理方式不同。為了更好的理解流的中間操作和終端操作,可以通過下面的兩段代碼來看他們的執行過程:

IntStream.range(1, 10)
   .peek(x -> System.out.print("\nA" + x))
   .limit(3)
   .peek(x -> System.out.print("B" + x))
   .forEach(x -> System.out.print("C" + x));
// ==============輸出:===============
// A1B1C1
// A2B2C2
// A3B3C3

中間操作是懶惰的,也就是不會對數據做任何操作,直到遇到了結束操作。而結束操作都是比較熱情的,他們會回溯之前所有的中間操作。

拿上面的例子來說,當執行到 forEach() 的時候,它會回溯到上一步中間操作,再到上一步中間操作,再上一步..直到第一步,也就是這裡的 .peek(x -> System.out.println("\nA" + x),然後開始自上而下的依次執行,輸出第一行的 A1B1C1,然而第二次執行 forEach() 操作的時候等同,以此類推..

我們再來看第二段代碼:

IntStream.range(1, 10)
   .peek(x -> System.out.print("\nA" + x))
   .skip(6)
   .peek(x -> System.out.print("B" + x))
   .forEach(x -> System.out.print("C" + x));
// ==============輸出:===============
// A1
// A2
// A3
// A4
// A5
// A6
// A7B7C7
// A8B8C8
// A9B9C9

根據上面介紹的規則,同樣的當第一次執行 .forEach() 的時候,會回溯到第一個 peek 操作,列印出 A1,然後執行 skip,這個操作的意思就是跳過,也就是相當於 for 循環裡面的 continue,所以前六次的 forEach() 操作都只會列印 A。

而第七次開始,skip 失效之後,就會開始分別執行 .peek() 和 forEach() 裡面的列印語句了,就會看到輸出的是:A7B7C7。

OK,到這裡也算是對 Stream API 有了一定的認識,下面我們對中間操作和結束操作做一個總結:

三、Stream 性能測試

引用自:下方參考文檔第 4 條。

已經對 Stream API 的用法鼓吹夠多了,用起簡潔直觀,但性能到底怎麼樣呢?會不會有很高的性能損失?本節我們對 Stream API 的性能一探究竟。

為保證測試結果真實可信,我們將 JVM 運行在 -server 模式下,測試數據在 GB 量級,測試機器採用常見的商用伺服器,配置如下:

指標數值OSCentOS 6.7 x86_64CPUIntel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads內存96GBJDKjava version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM

測試所用代碼在這裡,測試結果匯總.

測試方法和測試數據

性能測試並不是容易的事,Java性能測試更費勁,因為虛擬機對性能的影響很大,JVM對性能的影響有兩方面:

GC的影響。GC的行為是Java中很不好控制的一塊,為增加確定性,我們手動指定使用CMS收集器,並使用10GB固定大小的堆內存。具體到JVM參數就是-XX:+UseConcMarkSweepGC -Xms10G -Xmx10G

JIT(Just-In-Time)即時編譯技術。即時編譯技術會將熱點代碼在JVM運行的過程中編譯成本地代碼,測試時我們會先對程序預熱,觸發對測試函數的即時編譯。相關的JVM參數是-XX:CompileThreshold=10000。

Stream並行執行時用到ForkJoinPool.commonPool()得到的線程池,為控制並行度我們使用Linux的taskset命令指定JVM可用的核數。

測試數據由程序隨機生成。為防止一次測試帶來的抖動,測試4次求出平均時間作為運行時間。

實驗一 基本類型迭代

測試內容:找出整型數組中的最小值。對比for循環外部迭代和Stream API內部迭代性能。

測試程序IntTest,測試結果如下圖:

圖中展示的是for循環外部迭代耗時為基準的時間比值。分析如下:

對於基本類型Stream串行迭代的性能開銷明顯高於外部迭代開銷(兩倍);
Stream並行迭代的性能比串行迭代和外部迭代都好。

並行迭代性能跟可利用的核數有關,上圖中的並行迭代使用了全部 12 個核,為考察使用核數對性能的影響,我們專門測試了不同核數下的Stream並行迭代效果:

分析,對於基本類型:

使用Stream並行API在單核情況下性能很差,比Stream串行API的性能還差;

隨著使用核數的增加,Stream並行效果逐漸變好,比使用for循環外部迭代的性能還好。

以上兩個測試說明,對於基本類型的簡單迭代,Stream串行迭代性能更差,但多核情況下Stream迭代時性能較好。

實驗二 對象迭代

再來看對象的迭代效果。

測試內容:找出字符串列表中最小的元素(自然順序),對比for循環外部迭代和Stream API內部迭代性能。

測試程序StringTest,測試結果如下圖:

結果分析如下:

對於對象類型Stream串行迭代的性能開銷仍然高於外部迭代開銷(1.5倍),但差距沒有基本類型那麼大。

Stream並行迭代的性能比串行迭代和外部迭代都好。

再來單獨考察Stream並行迭代效果:

分析,對於對象類型:

使用Stream並行API在單核情況下性能比for循環外部迭代差;

隨著使用核數的增加,Stream並行效果逐漸變好,多核帶來的效果明顯。

以上兩個測試說明,對於對象類型的簡單迭代,Stream串行迭代性能更差,但多核情況下Stream迭代時性能較好。

實驗三 複雜對象歸約

從實驗一、二的結果來看,Stream串行執行的效果都比外部迭代差(很多),是不是說明Stream真的不行了?先別下結論,我們再來考察一下更複雜的操作。

測試內容:給定訂單列表,統計每個用戶的總交易額。對比使用外部迭代手動實現和Stream API之間的性能。

我們將訂單簡化為<userName, price, timeStamp>構成的元組,並用Order對象來表示。測試程序ReductionTest,測試結果如下圖:

分析,對於複雜的歸約操作:

Stream API的性能普遍好於外部手動迭代,並行Stream效果更佳;

再來考察並行度對並行效果的影響,測試結果如下:

分析,對於複雜的歸約操作:

使用Stream並行歸約在單核情況下性能比串行歸約以及手動歸約都要差,簡單說就是最差的;

隨著使用核數的增加,Stream並行效果逐漸變好,多核帶來的效果明顯。

以上兩個實驗說明,對於複雜的歸約操作,Stream串行歸約效果好於手動歸約,在多核情況下,並行歸約效果更佳。我們有理由相信,對於其他複雜的操作,Stream API也能表現出相似的性能表現。

結論

上述三個實驗的結果可以總結如下:

對於簡單操作,比如最簡單的遍歷,Stream串行API性能明顯差於顯示迭代,但並行的Stream API能夠發揮多核特性。

對於複雜操作,Stream串行API性能可以和手動實現的效果匹敵,在並行執行時Stream API效果遠超手動實現。

所以,如果出於性能考慮,1. 對於簡單操作推薦使用外部迭代手動實現,2. 對於複雜操作,推薦使用Stream API, 3. 在多核情況下,推薦使用並行Stream API來發揮多核優勢,4.單核情況下不建議使用並行Stream API。

如果出於代碼簡潔性考慮,使用Stream API能夠寫出更短的代碼。即使是從性能方面說,儘可能的使用Stream API也另外一個優勢,那就是只要Java Stream類庫做了升級優化,代碼不用做任何修改就能享受到升級帶來的好處。

參考文檔

https://redspider.gitbook.io/concurrent/di-san-pian-jdk-gong-ju-pian/19 - Java 8 Stream並行計算原理

http://hack.xingren.com/index.php/2018/10/17/java-stream/ - 原來你是這樣的 Stream —— 淺析 Java Stream 實現原理

https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/6-Stream%20Pipelines.md - Stream Pipelines

https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/8-Stream%20Performance.md - Stream Performance

《Java 8 實戰》

相關焦點

  • Java8 中用法優雅的 Stream,性能也「優雅」嗎?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫ava8的Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的代碼。那麼,Stream API的性能到底如何呢,代碼整潔的背後是否意味著性能的損耗呢?
  • 手機 App 電量分析:Monkey 性能測試工具的基本操作
    大致操作流程:通過名為「monkey」的Shell腳本去啟動Monkey.jar程序(shell腳本在Android文件系統中 的存放路徑是:/system/bin/monkey),在你指定的APP應用上模擬用戶點擊,滑動,輸入等操作以極快的速度來對設備程序進行壓力測試,檢測程序是否會發生異常,然後通過日誌進行排錯。主要目的:測試app 是否會是否會Crash。
  • java中資料庫:JDBC的使用
    包中)。通過JDBC,我們可以用java編寫程序,實現與特定的資料庫進行連接,向資料庫發送SQL語句,實現對資料庫的特定操作,並對資料庫返回的結果進行處理。JDBC編程步驟:步驟一:根據應用程式所用的資料庫,選擇JDBC驅動程序類型;步驟二:連接到資料庫,得到Connection對象;步驟三:通過Connection創建Statement對象;步驟四:使用Statement對象提交SQL語句;步驟五:操作結果集;步驟六:回收資料庫資源。
  • 使用 Java 8 Stream 優雅的找出重複數據
    最近經常遇到問題:要獲取到集合中某一屬性值重複的數據,除了for 循環,還有更簡單的處理方式?先來引入 Stream 流的概念。Stream 闡述Stream API(java.util.stream.*) 是 Java 8 中新增重要特性。
  • Java 8 的Stream流那麼強大,你知道它的原理嗎
    Java 的並行 API 演變歷程基本如下:「1.0-1.4 中的 java.lang.Thread5.0 中的 java.util.concurrent6.0 中的 Phasers 等其實很好理解,我們看一下接口中對S的使用就知道了:如sequential()、parallel()這兩個方法,它們都返回了S實例,也就是說它們分別支持對當前流進行串行或者並行的操作,並返回「改變」後的流對象。
  • 夯實Java基礎系列16:一文讀懂Java IO流和常見面試題
    然而你只能把有限的數據推回流中,並且你不能像操作數組那樣隨意讀取數據。流中的數據只能夠順序訪問。Java IO流通常是基於字節或者基於字符的。字節流通常以「stream」命名,比如InputStream和OutputStream。
  • Java微基準性能測試:數字轉字符串方式哪家強?JMH來幫忙
    目前各大網際網路公司都開始注重代碼質量,在京東單元測試已經在進行全面推廣和覆蓋中,這次,我們通過一起實際的例子,聊一聊另一種非常重要的測試,也就是微基準性能測試。Java中數字轉字符串相信大家都有做過,四種常用的轉換方式,究竟用哪種最優呢?
  • java編程中'為了性能'一些儘量做到的地方
    - 3.儘量避免過多過常的創建java對象 儘量避免在經常調用的方法,循環中new對象,由於系統不僅要花費時間來創建對象,而且還要花時間對這些對象進行垃圾回收和處理,在我們可以控制的範圍內,最 大限度的重用對象,最好能用基本的數據類型或數組來替代對象。
  • 簡潔方便的集合處理——Java 8 stream流
    java 8已經發行好幾年了,前段時間java 12也已經問世,但平時的工作中,很多項目的環境還停留在java1.7中。而且java8的很多新特性都是革命性的,比如各種集合的優化、lambda表達式等,所以我們還是要去了解java8的魅力。今天我們來學習java8的Stream,並不需要理論基礎,直接可以上手去用。我接觸stream的原因,是我要搞一個用戶收入消費的數據分析。起初的統計篩選分組都是打算用sql語言直接從mysql裡得到結果來展現的。
  • 提升java編程性能優化知識 程式設計師必看這幾點
    對於學習java的學子也是如此,那麼java程式設計師如何提高編程性能呢,有哪些小知識或者技巧呢,怎麼樣才能在編程性能優化方面有所提升呢?  1.儘量在合適的場合使用單例  使用單例可以減輕加載的負擔,縮短加載的時間,提高加載的效率,但並不是所有地方都適用於單例,簡單來說,單例主要適用於以下三個方面:
  • Java冷門小技巧:如何使用Steam.map()轉換流?
    map()方法是一個中間操作。它返回一個流,該流包括將給定函數應用於流中每個元素的結果。以下代碼返回一個整數流,這是應用String.length()方法的結果。importjava.util.ArrayList;import java.util.List;import java.util.stream.Stream;publicclassJava8Map
  • CUBRID和MySQL使用SSD前後性能測試對比
    使用CUBRID的公司可以得到更好的性能,高可靠性,靈活性,擴展性和高可用性,為其重要客戶提供7*24小時的持續服務」  CUBRID被韓國IT業的領頭企業NHN公司大量的使用,該公司部署了超過一萬臺CUBRID伺服器,看來它的確和MySQL有得一比,本文主要測試兩者的性能,我們同時在HDD硬碟和SSD硬碟上完成了測試,為了保證公平,每個資料庫(CUBRID和MySQL)都安裝在兩臺伺服器上
  • Java學習進階-Stream流
    Stream流的操作思想類似工廠流水線的操作思想:1.
  • 「014期」JavaSE面試題(十四):基本IO流
    ,主要總結了Java中的IO流的問題,IO流分為兩篇來講,這篇是第一篇,主要是基本IO流,第二篇主要為網絡IO流,在後續,會沿著第一篇開篇的知識線路一直總結下去,做到日更!輸入流從文件中讀取數據存儲到進程(process)中,輸出流從進程中讀取數據然後寫入到目標文件。Q:Java中有幾種類型的流?
  • Java 性能優化的 50 個細節(珍藏版)
    java編譯器會尋找機會內聯(inline)所有的final方法(這和具體的編譯器實現有關),此舉能夠使性能平均提高50%。儘量處理好包裝類型和基本類型兩者的使用場所雖然包裝類型和基本類型在使用過程中是可以相互轉換,但它們兩者所產生的內存區域是完全不同的基本類型數據產生和處理都在棧中處理
  • Java不同壓縮算法的性能比較
    文中進行比較的算有:●JDK GZIP ——這是一個壓縮比高的慢速算法,壓縮後的數據適合長期使用。JDK中的java.util.zip.GZIPInputStream / GZIPOutputStream便是這個算法的實現。●JDK deflate ——這是JDK中的又一個算法(zip文件用的就是這一算法)。
  • Java 性能優化的那些事兒
    在 Java 核心 API 中,有許多應用 final 的例子,例如 java、lang、String,為 String 類指定 final 防止了使用者覆蓋 length() 方法。另外,如果一個類是 final 的,則該類所有方法都是 final 的。java 編譯器會尋找機會內聯(inline)所有的 final 方法(這和具體的編譯器實現有關),此舉能夠使性能平均提高 50%。
  • Java性能優化的50個細節(珍藏版)
    在Java程序中,性能問題的大部分原因並不在於Java語言,而是程序本身。養成良好的編碼習慣非常重要,能夠顯著地提升程序性能。6、儘量處理好包裝類型和基本類型兩者的使用場所雖然包裝類型和基本類型在使用過程中是可以相互轉換,但它們兩者所產生的內存區域是完全不同的,基本類型數據產生和處理都在棧中處理,包裝類型是對象,是在堆中產生實例。在集合類對象,有對象方面需要的處理適用包裝類型,其他的處理提倡使用基本類型。
  • JAVA-新手入門:JAVA資料庫基本操作指南
    java 資料庫基本操作  1、java資料庫操作基本流程  2、幾個常用的重要技巧:  ·可滾動、更新的記錄集  ·批量更新  ·事務處理java資料庫操作基本流程:取得資料庫連接 - 執行sql語句 - 處理執行結果 - 釋放資料庫連接基礎視頻有挺多的你是看你的學習欲望!!!!
  • Java 不同壓縮算法的性能比較
    文中進行比較的算有:JDK GZIP ——這是一個壓縮比高的慢速算法,壓縮後的數據適合長期使用。JDK中的java.util.zip.GZIPInputStream / GZIPOutputStream便是這個算法的實現。JDK deflate ——這是JDK中的又一個算法(zip文件用的就是這一算法)。