Java8函數式編程

最近使用lambda表達式,感覺使用起來非常舒服,箭頭函數極大增強了代碼的表達能力。于是決心花點時間深入地去研究一下java8的函數式。

一、lambda表達式

先po一個最經典的例子——線程

public static void main(String[] args) {// Java7new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(i);}}}).start();// Java8new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(i);}}).start();
}
復制代碼

第一次接觸lambda表達式是在創建線程時,比較直觀的感受就是lambda表達式相當于匿名類的語法糖,emm~,真甜。不過事實上,lambda表達式并不是匿名類的語法糖,而且經過一段時間的使用,感覺恰恰相反,在使用上匿名類更像是Java中lambda表達式的載體。

使用場景

下面的一些使用場景均為個人的一些體會,可能存在不當或遺漏之處。

1. 簡化匿名類的編碼

上面的創建線程就是一個很好簡化編碼的例子,此處就不再重復。

2. 減少不必要的方法創建

在Java中,我們經常會遇到這樣一種場景,某個方法只會在某處使用且內部邏輯也很簡單,在Java8之前我們通常都會創建一個方法,但是事實上我們經常會發現這樣寫著寫著,一個類中的方法可能會變得非常龐雜,嚴重影響閱讀體驗,進而影響編碼效率。但是如果使用lambda表達式,那么這個問題就可以很容易就解決掉了。

一個簡單的例子,如果我們需要在一個函數中多次打印時間。(這個例子可能有些牽強,但是實際上還是挺常遇見的)

public class FunctionMain {public static void main(String[] args) {TimeDemo timeDemo = new TimeDemo();timeDemo.createTime = System.currentTimeMillis();timeDemo.updateTime = System.currentTimeMillis() + 10000;outputTimeDemo(timeDemo);}private static void outputTimeDemo(TimeDemo timeDemo) {Function timestampToDate = timestamp -> {DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return df.format(new Date(timestamp));};System.out.println(timestampToDate.apply(timeDemo.createTime));System.out.println(timestampToDate.apply(timeDemo.updateTime));}interface Function {String apply(long timestamp);}
}class TimeDemo {long createTime;long updateTime;
}
復制代碼

在這段代碼的outputTimeDemo中我們可以看到,對于時間戳轉換的內容,我們并沒有額外創建一個方法,而是類似于創建了一個變量來表達。不過,這個時候出現了另一個問題,雖然我們少創建了一個方法,但是我們卻多創建了一個接口Function,總有種因小失大的感覺, 不過這個問題,我們在后面的java.util.function包部分可以找到答案。

3. 事件處理

一個比較常見的例子就是回調。

public static void main(String[] args) {execute("hello world", () -> System.out.println("callback"));
}private static void execute(String s, Callback callback) {System.out.println(s);callback.callback();
}@FunctionalInterface
interface Callback {void callback();
}
復制代碼

在這里,可以發現一點小不同,就是Callback多了一個注解@FunctionalInterface,這個注解主要用于編譯期檢查,如果我們的接口不符合函數式接口的要求,那編譯的時候就會報錯。不加也是可以正常執行的。

4. stream中使用

這個在后面的stream中詳解。

java.util.function包

在之前的例子中,我們發現使用lambda表達式的時候,經常需要定義一些接口用來輔助我們的編碼,這樣就會使得本應輕量級的lambda表達式又變得重量級。那是否存在解決方案呢?其實Java8本身已經為我們提供了一些常見的函數式接口,就在java.util.function包下面。

接口描述
Function<T,R>接受一個輸入參數,返回一個結果
Supplier<T>無參數,返回一個結果
Consumer<T>接受一個輸入參數,并且不返回任何結果
BiFunction<T,U,R>接受兩個輸入參數的方法,并且返回一個結果
BiConsumer<T,U>接受兩個輸入參數的操作,并且不返回任何結果

此處列出最基本的幾個,其他的都是在這些的基礎上做了一些簡單的封裝,例如IntFunction<R>就是對Function<T,R>的封裝。上面的這些函數式接口已經可以幫助我們處理絕大多數場景了,如果有更復雜的情況,那就得我們自己定義接口了。不過遺憾的是在java.util.function下沒找到無參數無返回結果的接口,目前我找到的方案就是自己定義一個接口或者直接使用Runnable接口。

使用示例

public static void main(String[] args) {Function<Integer, Integer> f = x -> x + 1;System.out.println(f.apply(1));BiFunction<Integer, Integer, Integer> g = (x, y) -> x + y;System.out.println(g.apply(1, 2));
}
復制代碼

lambda表達式和匿名類的區別

lambda表達式雖然使用時和匿名類很相似,但是還是存在那么一些區別。

1. this指向不同

lambda表達式中使用this指向的是外部的類,而匿名類中使用this則指向的是匿名類本身。

public class FunctionMain {private String test = "test-main";public static void main(String[] args) {new FunctionMain().output();}private void output() {Function f = () -> {System.out.println("1:-----------------");System.out.println(this);System.out.println(this.test);};f.outputThis();new Function() {@Overridepublic void outputThis() {System.out.println("2:-----------------");System.out.println(this);System.out.println(this.test);}}.outputThis();}interface Function {String test = "test-function";void outputThis();}
}
復制代碼

如上面這段代碼,輸出結果如下

所以如果想使用lambda表達式的同時去訪問原類中的變量、方法的是做不到的。

2. 底層實現不同

編譯

從編譯結果來看,兩者的編譯結果完全不同。

首先是匿名類的方式,代碼如下:

import java.util.function.Function;public class ClassMain {public static void main(String[] args) {Function<Integer, Integer> f = new Function<Integer, Integer>() {@Overridepublic Integer apply(Integer integer) {return integer + 1;}};System.out.println(f.apply(1));}
}
復制代碼

編譯后的結果如下:

可以看到ClassMain在編譯后生成了兩個class,其中ClassMain$1.class就是匿名類生成的class。


那么接下來,我們再來編譯一下lambda版本的。代碼和編譯結果如下:

import java.util.function.Function;public class FunctionMain {public static void main(String[] args) {Function<Integer, Integer> f = x -> x + 1;System.out.println(f.apply(1));}
}
復制代碼

在這里我們可以看到FunctionMain并沒有生成第二個class文件。

字節碼

更進一步,我們打開他們的字節碼來尋找更多的細節。首先依然是匿名類的方式

在Code-0這一行,我們可以看到匿名類的方式是通過new一個類來實現的。


接下來是lambda表達式生成的字節碼,

在lambda表達式的字節碼中,我們可以看到我們的lambda表達式被編譯成了一個叫做lambda$main$0的靜態方法,接著通過invokedynamic的方式進行了調用。

3. lambda表達式只能替代部分匿名類

lambda表達式想要替代匿名類是有條件的,即這個匿名類實現的接口必須是函數式接口,即只能有一個抽象方法的接口。

性能

由于沒有實際測試過lambda表達式的性能,且我使用lambda更多是基于編碼簡潔度的考慮,因此本文就不探討性能相關問題。

關于lambda表達式和匿名類的性能對比可以參考官方ppt www.oracle.com/technetwork…

二、Stream API

Stream API是Java8對集合類的補充與增強。它主要用來對集合進行各種便利的聚合操作或者批量數據操作。

1. 創建流

在進行流操作的第一步是創建一個流,下面介紹幾種常見的流的創建方式

從集合類創建流

如果已經我們已經有一個集合對象,那么我們可以直接通過調用其stream()方法得到對應的流。如下

List<String> list = Arrays.asList("hello", "world", "la");
list.stream();
復制代碼

利用數組創建流

String[] strArray = new String[]{"hello", "world", "la"};
Stream.of(strArray);
復制代碼

利用可變參數創建流

Stream.of("hello", "world", "la");
復制代碼

根據范圍創建數值流

IntStream.range(0, 100);         // 不包含最后一個數
IntStream.rangeClosed(0, 99);    // 包含最后一個數
復制代碼

BufferReader.lines()

對于BufferReader而言,它的lines方法也同樣可以創建一個流

File file = new File("/Users/cayun/.m2/settings.xml");
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
br.lines().forEach(System.out::println);
br.close();
復制代碼

2. 流操作

在Stream API中,流的操作有兩種:Intermediate和Terminal

Intermediate:一個流可以后面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然后返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,并沒有真正開始流的遍歷。 Terminal:一個流只能有一個 terminal 操作,當這個操作執行后,流就被使用“光”了,無法再被操作。所以這必定是流的最后一個操作。Terminal 操作的執行,才會真正開始流的遍歷,并且會生成一個結果,或者一個 side effect。

除此以外,還有一種叫做short-circuiting的操作

對于一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。 對于一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。

常見的流操作可以如下歸類:

Intermediate map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

Terminal forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

Short-circuiting anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

常見的流操作詳解

1. forEach

forEach可以說是最常見的操作了,甚至對于List等實現了Collection接口的類可以不創建stream而直接使用forEach。簡單地說,forEach就是遍歷并執行某個操作。

Stream.of("hello", "world", "a", "b").forEach(System.out::println);
復制代碼

2. map

map也同樣是一個非常高頻的流操作,用來將一個集合映射為另一個集合。下面代碼展示了將[1,2,3,4]映射為[1,4,9,16]

IntStream.rangeClosed(1, 4).map(x -> x * x).forEach(System.out::println);
復制代碼

除此之外,還有一個叫做flatMap的操作,這個操作在映射的基礎上又做了一層扁平化處理。這個概念可能比較難理解,那舉個例子,我們需要將["hello", "world"]轉換成[h,e,l,l,o,w,o,r,l,d],可以嘗試一下使用map,那你會驚訝地發現,可能結果不是你想象中的那樣。如果不信可以執行下面這段代碼,就會發現map與flatMap之間的區別了,

Stream.of("hello", "world").map(s -> s.split("")).forEach(System.out::println);
System.out.println("--------------");
Stream.of("hello", "world").flatMap(s -> Stream.of(s.split(""))).forEach(System.out::println);
復制代碼

3. filter

filter則實現了過濾的功能,如果只需要[1,2,3,4,5]中的奇數,可以如下,

IntStream.rangeClosed(1, 5).filter(x -> x % 2 == 1).forEach(System.out::println);
復制代碼

4. sorted和distinct

其中sorted表示排序,distinct表示去重,簡單的示例如下:

Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4};    // 千萬不要用int
Stream.of(arr).sorted().forEach(System.out::println);
Stream.of(arr).distinct().forEach(System.out::println);
Stream.of(arr).distinct().sorted().forEach(System.out::println);
復制代碼

5. collect

在流操作中,我們往往需求是從一個List得到另一個List,而不是直接通過forEach來打印。那么這個時候就需要使用到collect了。依然是之前的例子,將[1,2,3,4]轉換成[1,4,9,16]。

List<Integer> list1= Stream.of(1, 2, 3, 4).map(x -> x * x).collect(Collectors.toList());// 對于IntStream生成的流需要使用mapToObj而不是map
List<Integer> list2 = IntStream.rangeClosed(1, 4).mapToObj(x -> x * x).collect(Collectors.toList());
復制代碼

3. 補充

并行流

除了普通的stream之外還有parallelStream,區別比較直觀,就是stream是單線程執行,parallelStream為多線程執行。parallelStream的創建及使用基本與stream類似,

List<Integer> list = Arrays.asList(1, 2, 3, 4);
// 直接創建一個并行流
list.parallelStream().map(x -> x * x).forEach(System.out::println);
// 或者將一個普通流轉換成并行流
list.stream().parallel().map(x -> x * x).forEach(System.out::println);
復制代碼

不過由于是并行執行,parallelStream并不保證結果順序,同樣由于這個特性,如果能使用findAny就盡量不要使用findFirst。

使用parallelStream時需要注意的一點是,多個parallelStream之間默認使用的是同一個線程池,所以IO操作盡量不要放進parallelStream中,否則會阻塞其他parallelStream。

三、Optional

Optional的引入是為了解決空指針異常的問題,事實上在Java8之前,Optional在很多地方已經較為廣泛使用了,例如scala、谷歌的Guava庫等。

在實際生產中我們經常會遇到如下這種情況,

public class FunctionMain {public static void main(String[] args) {Person person = new Person();String result = null;if (person != null) {Address address = person.address;if (address != null) {Country country = address.country;if (country != null) {result = country.name;}}}System.out.println(result);}
}class Person {Address address;
}class Address {Country country;
}class Country {String name;
}
復制代碼

每每寫到這樣的代碼,作為編碼者一定都會頭皮發麻,滿心地不想寫,但是卻不得不寫。這個問題如果使用Optional,或許你就能找到你想要的答案了。

Optional的基本操作

1. 創建Optional

Optional.empty();          // 創建一個空Optional
Optional.of(T value);      // 不接受null,會報NullPointerException異常
Optional.ofNullable(T value);     // 可以接受null
復制代碼

2. 獲取結果

get();                                   // 返回里面的值,如果值為null,則拋異常
orElse(T other);                         // 有值則返回值,null則返回other
orElseGet(Supplier other);               // 有值則返回值,null則由提供的lambda表達式生成值
orElseThrow(Supplier exceptionSupplier); // 有值則返回值,null則拋出異常
復制代碼

3. 判斷是否為空

isPresent();       // 判斷是否為空
復制代碼

到這里,我們可能會開始考慮怎么用Optional解決引言中的問題了,于是思考半天,寫出了這樣一段代碼,

public static void main(String[] args) {Person person = new Person();String result = null;Optional<Person> per = Optional.ofNullable(person);if (per.isPresent()) {Optional<Address> address = Optional.ofNullable(per.get().address);if (address.isPresent()) {Optional<Country> country = Optional.ofNullable(address.get().country);if (country.isPresent()) {result = Optional.ofNullable(country.get().name).orElse(null);}}}System.out.println(result);
}
復制代碼

啊嘞嘞,感覺不僅沒有使得代碼變得簡單,反而變得更加復雜了。那么很顯然這并不是Optional的正確使用方法。接下來的部分才是Optional的正確使用方式。

4. 鏈式方法

在Optional中也有類似于Stream API中的鏈式方法map、flatMap、filter、ifPresent。這些方法才是Optional的精髓。此處以最典型的map作為例子,可以看看map的源碼

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {Objects.requireNonNull(mapper);if (!isPresent())return empty();else {return Optional.ofNullable(mapper.apply(value));}
}
復制代碼

源碼很簡單,可以看到對于null情況仍然返回null,否則返回處理結果。那么此再來思考一下引言的問題,那就可以很簡單地改寫成如下的寫法,

public static void main(String[] args) {Person person = new Person();String result = Optional.ofNullable(person).map(per -> per.address).map(address -> address.country).map(country -> country.name).orElse(null);System.out.println(result);
}
復制代碼

哇哇哇,相比原先的null寫法真真是舒服太多了。

map與flatMap的區別

這兩者的區別,同樣使用一個簡單的例子來解釋一下吧,

public class FunctionMain {public static void main(String[] args) {Person person = new Person();String name = Optional.ofNullable(person).flatMap(p -> p.name).orElse(null);System.out.println(name);}
}class Person {Optional<String> name;
}
復制代碼

在這里使用的不是map而是flatMap,稍微觀察一下,可以發現Person中的name不再是String類型,而是Optional<String>類型了,如果使用map的話,那map的結果就是Optional<Optional<String>>了,很顯然不是我們想要的,flatMap就是用來將最終的結果扁平化(簡單地描述,就是消除嵌套)的。

至于filter和ifPresent用法類似,就不再敘述了。

四、其他一些函數式概念在Java中的實現

由于個人目前為止也只是初探函數式階段,很多地方了解也不多,此處只列舉兩個。(注意:下面的部分應用函數與柯里化對應的是scala中的概念,其他語言中可能略有偏差)

部分應用函數(偏應用函數)

部分應用函數指的是對于一個有n個參數的函數f,但是我們只提供m個參數給它(m < n),那么我們就可以得到一個部分應用函數,簡單地描述一下,如下

f(x,y,z) = x + y + z
g(x,y) = f(x,y,3)

在這里g就是f的一個部分應用函數。

BiFunction<Integer, Integer, Integer> f = (x, y) -> x + y;
Function<Integer, Integer> g = x -> f.apply(1, x);
System.out.println(g.apply(2));
復制代碼

柯里化

柯里化就是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數而且返回結果的新函數的技術。換個描述,如下

f(x, y, z)  \rightarrow  f(x)(y)(z)

Java中對柯里化的實現如下,

Function<Integer, Function<Integer, Integer>> f = x -> y -> x + y;
System.out.println(f.apply(1).apply(2));
復制代碼

因為Java限制,我們不得不寫成f.apply(1).apply(2)的形式,不過視覺上的體驗與直接寫成f(1)(2)相差就很大了。

柯里化與部分應用函數感覺很相像,不過因為個人幾乎未使用過這兩者,因此此處就不發表更多見解。

參考

[1] java.util.stream 庫簡介
[2] Java 8 中的 Streams API 詳解
[3] 了解、接受和利用Java中的Optional(類)
[4] 維基百科-柯里化
[5] 維基百科-λ演算

轉載于:https://juejin.im/post/5d005fb6e51d4577555508ab

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/448643.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/448643.shtml
英文地址,請注明出處:http://en.pswp.cn/news/448643.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

電腦如何獲得管理員權限

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 我只是記錄下&#xff0c;方便以后查看。 參見&#xff1a; https://jingyan.baidu.com/article/ab69b270ff426e2ca6189f54.html

.NET混淆器 Dotfuscator如何保護應用程序?控制流了解一下!

Dotfuscator是一個.NET的Obfuscator。它提供企業級的應用程序保護&#xff0c;大大降低了盜版、知識產權盜竊和篡改的風險。Dotfuscator的分層混淆、加密、水印、自動失效、防調試、防篡改、報警和防御技術&#xff0c;為世界各地成千上萬的應用程序提供保護。 Dotfuscator提供…

到底什么才是人生最大的投資

不是房子&#xff0c;不是股票&#xff0c; 是人&#xff0c;跟什么人交往&#xff0c;跟隨什么人&#xff0c; 交什么樣的朋友&#xff0c;其實就是你投資什么人&#xff0c; 而這&#xff0c;是對人生影響最大的。 錢不會給人機會&#xff0c;房子也不會&#xff0c; 只有人會…

tcpdump抓包命令

目錄&#xff1a; 命令格式選項expression表達式示例【命令格式】 man手冊顯示如下 1 tcpdump [ -AbdDefhHIJKlLnNOpqStuUvxX# ] [ -B buffer_size ]2 [ -c count ]3 [ -C file_size ] [ -G rotate_seconds ] [ -F file ]4 [ -i …

百度Ueditor編輯器wordimage踩坑

背景 改造公司老項目后臺編輯器&#xff0c;使用百度的Ueditor做替換。 發現問題 1、ue編輯器初始化后部分參數無法覆蓋ueditor.config.js中的選項。2、wordimage&#xff08;word圖片轉存&#xff09;始終是灰色&#xff0c;無法使用。解決辦法 1、將ueditor.config.js中的inp…

IntelliJ IDEA 配置JDK

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 IDEA配置JDK 1、點擊File -->Project Structure&#xff1b; 2、點擊左側標簽頁SDKs選項&#xff0c;再點擊左上角“”&#xff0c;…

get和post 兩種基本請求方式的區別

GET和POST是HTTP請求的兩種基本方法&#xff0c;要說它們的區別&#xff0c;接觸過WEB開發的人都能說出一二。 最直觀的區別就是GET把參數包含在URL中&#xff0c;POST通過request body傳遞參數。 你可能自己寫過無數個GET和POST請求&#xff0c;或者已經看過很多權威網站總結出…

無論是工作還是生活都要記住這些話

1.如果你不喜歡現在的工作&#xff0c;要么辭職不干&#xff0c;要么就閉嘴不言。初出茅廬&#xff0c;往往眼高手低&#xff0c;心高氣傲&#xff0c;大事做不了&#xff0c;小事不愿做。不要養成挑三揀四的習慣。不要雨天煩打傘&#xff0c;不帶傘又怕淋雨&#xff0c;處處表…

蘇嵌第一天,shell中一些基礎知識

一、常用環境變量 1、HOME變量 Linux系統中的每個用戶都有一個相關的稱作HOME的目錄。 2、PATH變量 包含一列用冒號定界的目錄的路徑名字&#xff0c;便于可執行程序的搜索。 3、PS1變量 PS1變量包含了shell提示符&#xff0c;$符號 4、LOGNAME變量 包含用戶的注冊名字…

Java異常處理001:Maven clean package時Failed to clean project: Failed to delete

Java異常處理001&#xff1a;Maven打包時Failed to clean project: Failed to delete 異常日志&#xff1a; [ERROR] Failed to execute goal org.apache.maven.plugins:maven-clean-plugin:2.6.1:clean (default-clean) on project fmk-web: Failed to clean project: Failed …

Weekly Contest 141

做了第一道后&#xff0c;看了下中間兩道題目&#xff0c;沒怎么看懂就先放著&#xff0c;做完最后一道&#xff0c;然后就沒時間了。 1089. Duplicate Zeros Given a fixed length array arr of integers, duplicate each occurrence of zero, shifting the remaining element…

IntelliJ IDEA 中配置、使用 SVN

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1.配置svn 如下圖&#xff1a; file -- setting -- version control -- subversion -- 選擇 SVN安裝路徑 -- apply -- OK 2.直接檢出…

切記!職場郵件需注意的細節

電子郵件是如今工作場所重要的通信工具之一&#xff0c;但不是每個人都知道如何很好地使用這個工具。工作郵件也是人際溝通的一種方式&#xff0c;和打電話、面談一樣&#xff0c;有很多學問講究&#xff0c;所以在發送郵件之前一定要深思熟慮。 【發送&#xff0c;抄送&…

李洋瘋狂C語言之初

1.sizeof 是看數據類型所占空間大小&#xff0c;這個大小是以 字節&#xff08;B&#xff09;為單位 char 是C語言的字符數據類型 %d 用在printf 中表示往屏幕打印一個數字 printf ("char&#xff1a; %d\n", sizeof(char)); 數據類型之間的關系&#xff0c;shor…

時時流量查看工具-ifsta,nload,iftop

為什么80%的碼農都做不了架構師&#xff1f;>>> 1、ifstat 是一個網絡流量監測程序。能查看網卡的流出和流入的字節. 概要&#xff1a;ifstat就像iostat/vmstat描述其它的系統狀況一樣&#xff0c;是一個統計網絡接口活動狀態的工具。 參數&#xff1a; -l 監測環路…

10大清宿便排毒方法及簡單排毒瘦小腹運動

早上空腹喝水法&#xff1a;每日起床后空腹喝下500C.C.加鹽的冷開水。只要是冷的飲料或水分&#xff0c;在腸胃空腹時都有刺激腸胃蠕動的效果&#xff0c;而且越冰刺激效果越好&#xff0c;建議不需加鹽&#xff0c;以免高血壓患者因鹽分中的金屬離子造成腎的負擔。 優酪乳加綠…

破解 IntelliJ IDEA 、免費注冊方法、注冊碼

1. 找到hosts文件&#xff0c;在此路徑下 C:\Windows\System32\drivers\etc 2. 修改hosts 文件&#xff0c;在最后 加一行配置&#xff1a; &#xff08;此操作需要電腦管理員權限&#xff09; 0.0.0.0 account.jetbrains.com 3. 從idea 注冊碼生成網站生成一組注冊碼。網…

李洋瘋狂C語言之冒泡排序法

今天的課后任務是2種排序方式&#xff08;冒泡排序和選擇排序&#xff09; 冒泡排序法1 原理&#xff1a;從a[0]開始&#xff0c;依次將其和后面的元素比較&#xff0c;若a[0]>a[i]&#xff0c;則交換他們&#xff0c;一直比較到a[n]。同理對a[1], a[2], ……a[n-1]處理&a…

MySQL水平分區代理Spock Proxy(一)

為什么80%的碼農都做不了架構師&#xff1f;>>> MySQL水平分區代理Spock Proxy 水平分區(sharding)將同一數據表中的數據通過特定的算法進行分離&#xff0c;分別保存在不同的數據表中&#xff0c;從而部署到不同的數據庫服務器上。 水平分區后&#xff0c;數據拆分…

OO第四單元作業

1.作業的架構設計 &#xff08;1&#xff09;對于第一次作業中&#xff0c;要求我們實現關于類圖的查詢指令。 在這次作業中&#xff0c;主要采用的儲存方法是哈希表。 在查詢方法上&#xff0c;大多數要求諸如共有多少類等&#xff0c;利用哈希表進行查詢即可。 比較困難的一些…