Java核心技术·卷二·第一章笔记
Java核心技術·卷二·筆記
第一章:Java8的流庫
Java8引入的用來以“做什么而非怎么做”的方式的處理集合
1.1 從迭代到流的操作
package com.package1;import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List;public class Test {public static void main(String[] args) throws IOException {var contents = new String(Files.readAllBytes(Paths.get("alice.txt")),StandardCharsets.UTF_8);List<String> words = List.of(contents.split("\\PL+"));//里面是一個regex正則表達式,表示的是非字母,split的意思是分裂,這個方法會更具后面的regex返回一個String數組int count = 0;for(String w:words){if(w.length() > 12){count++;}}//使用流long count2 = words.stream().filter(w -> w.length() > 12).count();//將串行的stream修改成parallelStream就可以讓流庫以并行的方式來執行過濾和計數long count3 = words.parallelStream().filter(w -> w.length() > 12).count();} }流與集合是有顯著的差異的:
流遵循了“做什么而非怎么做的原則”,描述了需要做什么,而交給Java本身去實現,給了Java的優化空間,相對于自行指定操作流程,更方便也更快捷
- 創建流
- 轉換流
- 應用終止操作,從而產生結果
1.2 流的創建
可以用Collection接口的的stream方法將任何的集合轉換成流。也可以使用靜態的Stream.of方法
Stream<String> words= Stream.of(contains.split("\\PL+")); //of具有可變長度,可以構建具有任意數量引元的流 Stream<String> song = Stream.of("gently","down","the","stream"); //使用Array.stream(array,from,to)可以使用數組的一部分元素來創建一個流 //創建一個不包含任何元素的流 Stream<String> silence = Stream.empty();在操作流的時候,并沒有對流背后的集合進行操作.因此對流的引元進行刪除等操作是錯誤而且毫無意義的.
package com.package1;import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Iterator; import java.util.List; import java.util.Spliterator; import java.util.Spliterators; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport;public class Test {public static void main(String[] args) throws IOException {//下面會展示大部分的創建流的操作Path path = Paths.get("../gutenberg/alice30.txt");var contents = new String(Files.readAllBytes(path),StandardCharsets.UTF_8);Stream<String> words = Stream.of(contents.split("\\PL+"));//of方法會產生一個元素為給定值的流//static <T> Stream<T> of(T... values);show("words",words);Stream<String> song = Stream.of("gently","down","the","stream");show("song",song);Stream<String> silence = Stream.empty();show("silence",silence);Stream<String> echos= Stream.generate(()->"Echo");//generate方法產生一個無限流,它是通過不斷調用函數s來構建的//static <T> Stream<T> generate(Supplier<T> s)show("echos",echos);Stream<Double> randoms = Stream.generate(Math::random);//也可以直接用Java給定的閉包的方法引用show("randoms",randoms);Stream<BigInteger> integers = Stream.iterate(BigInteger.ONE,n-> n.add(BigInteger.ONE));//產生一個無限流,它的原始包括seed,在seed上調用f產生的值,在前一個元素上調用f產生的值//可以加一個hasNext謂語來指定終止// static <T> Stream<T> iterate(T seed,UnaryOPerator<T> f)// static <T> Stream<T> iterate(T seed,Predicate<? super T> hasNext,UnaryOPerator<T> f)show("integers",integers);//Java API中大量的方法都可以產生流,例如下面這個方法會將contents里面的字符串分割為一個個單詞Stream<String> wordAnotherWay = Pattern.compile("\\PL+").splitAsStream(contents);show("wordAnotherWay",wordAnotherWay);//Files.lines方法會返回一個包含了文件中所有行的Streamtry(Stream<String> lines = Files.lines(path,StandardCharsets.UTF_8)){show("lines",lines);}Iterable<Path> iterable = FileSystems.getDefault().getRootDirectories();//如果所持有的iterable對象不是集合可以通過下面的調用將其轉換成一個流Stream<Path> rootDirectories = StreamSupport.stream(iterable.spliterator(),false);show("rootDirections",rootDirectories);Iterator<Path> iterator = Paths.get("/usr/share/dict/words").iterator();//如果是Iterator對象,可以使用下面的語句將其轉換成流Stream<Path> pathComponents = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED),false);show("pathComponents",pathComponents);}public static <T> void show(String title, Stream<T> stream){final int SIZE= 10;List<T> firstElements = stream.limit(SIZE+1).collect(Collectors.toList());System.out.print(title+": ");for(int i = 0; i < firstElements.size(); i++){if(i > 0){System.out.print(", ");}if(i < SIZE){System.out.print(firstElements.get(i));}else{System.out.print("...");}}System.out.println();} }1.3 filter,map和flatMap方法
Stream<T> filter(Predicate<? super T> predicate); //產生一個流,它包含當前流中所有滿足謂語條件的元素 <R> Stream<R> map(Function<? super T,? extends R> mapper); //產生一個流,它包含將mapper應用于當前流中所有元素所產生的結果 <R> Stream<R> flatmap(Function<? super T, ? extends Stream<? extends R>> mapper); //產生一個流,它不光實現map的功能,還會將結果連接到一起(注意,這里的每一個結果的都是一個流,即將流連接)1.4 抽取子流和集合流
Stream<T> limit(long maxSize); //產生一個流,其中包含了當前流中最初的maxSize個元素 Stream<T> skip(long n); //舍棄前n個 Stream<T> takeWhile(Predicate<? super T> predicate); //產生滿足條件predicate的流 Stream<T> dropWhile(Predicete<? super T> predicate); //產生一個流,元素為不滿足predicate條件的元素之外的元素 static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b); //連接1.5 其他的流轉換
Stream<T> distinct(); //去重 Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator); //排序,可以加入自己的排序規格 Stream<T> peek(Consumer<? super T> action); //產生一個流,與原流相同,獲取其中每個元素都會將其傳遞給action; package com.package1;import java.util.Comparator; import java.util.stream.Stream;public class Test {public static void main(String[] args){Stream<String> uniqueWords = Stream.of("Job","Job","Tom","Job").distinct();uniqueWords.peek(e -> System.out.println(" "+e));//注意,這是沒有任何輸出的,因為peek是一個中間操作,而沒有終止操作,就只是“流過去”罷了,加一個例如toArray這種終止操作,就會有輸出了Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());Object[] powers = Stream.iterate(1.0,p -> p*2).peek(e -> System.out.println(" "+e)).limit(20).toArray();} }1.6 簡單約簡
約簡是一種終結操作(terminal operation)
它們返回的并非Stream而是Optional類型。Optional類型是一種缺少返回值的更好的方式
以下都是終結操作: Optional<T> max(Comparator<? super T> comparator); Optional<T> min(Comparator<? super T> comparator); 返回max,min, Optional<T> findFirst(); Optional<T> findAny(); 返回第一個或者任意一個元素,如果流為空,返回空的Optional; boolean anyMacth(Predicate<? super T> predicate); boolean allMacth(Predicate<? super T> predicate); boolean nonMacth(Predicate<? super T> predicate); 任意,所有,沒有 元素滿足謂語返回true1.7 Optional 類型
1.7.1 獲取Optional的值
可以再沒有任何匹配的時候使用默認值
String result = optionalString.orElse("");也可以從配置文件里面加載默認值
String result = optionalString.orElseGet(() -> System.getProperty("myapp.default"));也可以拋出異常
String result = optionalString.orElseThrow(IllegalStateException);1.7.2 消費Optional的值
opionalValue.ifPresent(v->Process v);例如如果該值存在的情況下將其添加到某個集當中,就可以調用
optionalValue.ifPresent(v -> result.add(v)); //or: optinalValue.ifPresent(result::add);也可以實現在滿足的時候執行一種動作,在不滿足的時候執行另一種動作
optionalValue.ifPresentOrElse( v->System.out.println("Found"+v), ()->logger.warning("No Match") );1.7.3 管道化Optional值
可以使用map來轉化Optional內部的值
Optional<String> transformed = optionalString.map(String::toUpperCase);可以直接用map來進行相關操作
optionalValue.map(result::add); //還可以添加filter Optional<String> transformed = optionalString.filter(s -> s.length() >= 8).map(String::toUpperCase);可以使用or方法將空的Optional替換成一個可替代的Optional。它將以惰性計算
Optional<String> result = optionalString.or(() -> alternatives.stream().findFirst());1.7.4 不適合使用Optional值的方式
對于Optional的使用,應該遵循以下原則:
- Optional類型的變量永遠都不應該為null
- 不要使用Optional類型域,這只會額外多出來一個對象(所謂域,其實是“field”的翻譯, 也就是我們常說的字段,或者說是屬性)
- 不要在集合中放置Optional對象,而且不要將他作為map的鍵。應該直接收集其中的值
調用Optional的isPresent等方法反而會將問題處理變得復雜
1.7.5 創建Optional的值
static <T> Optional<T> of(T value); static <T> Optional<T> ofNullable(T value); //兩個方法都會用value的值產生一個Optional,第一個方法在value為空時會拋出NullPointerException異常,第二個會自動調用empty方法產生一個空的Optional static <T> Optional<T> empty();1.7.6 用flatMap創建Optional值的函數
<U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper); 將mapper應用于當前Optional值所產生的結果,或者在當前Optional為空時,返回一個空Optional1.7.7 將Optional轉換為流
stream將Optional方法轉化成一個或者兩個的Stream對象
package com.package1;import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional;public class Test {public static void main(String[] args) throws IOException {var contents = new String(Files.readAllBytes(Paths.get("src/com/package1/demo.txt")), StandardCharsets.UTF_8);List<String> wordlist = List.of(contents.split("\\PL+"));Optional<String> optionalValue = wordlist.stream().filter(s -> s.contains("fred")).findFirst();System.out.println(optionalValue.orElse("No Word") +" contains fred");Optional<String> optionalString = Optional.empty();String result = optionalString.orElse("N/A");System.out.println("result: "+result);result = optionalString.orElseGet(() -> Locale.getDefault().getDisplayName());System.out.println("result: "+result);try{result = optionalString.orElseThrow(IllegalStateException::new);System.out.println("result: "+result);}catch (Throwable t){t.printStackTrace();}optionalValue = wordlist.stream().filter(s -> s.contains("red")).findFirst();optionalValue.ifPresent(s -> System.out.println(s+"contains red"));var results = new HashSet<String>();optionalValue.ifPresent(results::add);Optional<Boolean> added = optionalValue.map(results::add);System.out.println(added);System.out.println(inverse(4.0).flatMap(Test::squareRoot));System.out.println(inverse(-1.0).flatMap(Test::squareRoot));System.out.println(inverse(0.0).flatMap(Test::squareRoot));Optional<Double> result2 = Optional.of(-4.0).flatMap(Test::inverse).flatMap(Test::squareRoot);System.out.println(result2);}public static Optional<Double> inverse(Double x){return x == 0 ? Optional.empty() : Optional.of(1 / x);}public static Optional<Double> squareRoot(Double x){return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));} }1.8 收集結果
package com.package1;import com.sun.source.tree.Tree;import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream;public class Test {public static void main(String[] args) throws IOException {Iterator<Integer> iter = Stream.iterate(0, n -> n+1).limit(10).iterator();while(iter.hasNext()){System.out.println(iter.next());}Object[] numbers = Stream.iterate(0, n -> n+1).limit(10).toArray();System.out.println("Object array:" + numbers+Arrays.toString(numbers));//注意,由于無法在運行的時候創建泛型數組,所以表達式stream.toArray會返回一個Object數組try{var number = (Integer) numbers[0];System.out.println("number:" + number);System.out.println("The following state will throw an Exception");var number2 = (Integer[]) numbers;//Object[]強制類型轉換會由于無法識別轉換前后是否屬于子類或者同一類而報錯}catch (ClassCastException e){System.err.println(e);}Integer[] number3 = Stream.iterate(0, n -> n+1).limit(10).toArray(Integer[]::new);//傳入構造方法就可以指定類型System.out.println("Integer Array: "+ number3);Set<String> noVowelSet = noVowels().collect(Collectors.toSet());show("noVowelSet",noVowelSet);TreeSet<String> noVowelTreeSet = noVowels().collect(Collectors.toCollection(TreeSet::new));show("noVowelTreeSet",noVowelTreeSet);String result = noVowels().limit(10).collect(Collectors.joining());System.out.println("Joining: "+result);result = noVowels().limit(10).collect(Collectors.joining(", "));System.out.println("Joining with commas "+result);IntSummaryStatistics summary = noVowels().collect(Collectors.summarizingInt(String::length));double averageWorldLength = summary.getAverage();double maxWorldLength = summary.getMax();System.out.println("averageWorldLength: "+averageWorldLength);System.out.println("maxWorldLength: "+maxWorldLength);System.out.println("Foreach: ");noVowels().limit(10).forEach(System.out::println);}public static Stream<String> noVowels() throws IOException {var contains = new String(Files.readAllBytes(Paths.get("src/com/package1/demo.txt")), StandardCharsets.UTF_8);List<String> wordList = List.of(contains.split("\\PL+"));Stream<String> words = wordList.stream();return words.map(s -> s.replaceAll("[aeiouAEIOU]", ""));}public static <T > void show (String label, Set< T > set){System.out.print(label+": "+set.getClass().getName());System.out.println("["+set.stream().limit(10).map(Object::toString).collect(Collectors.joining(", ")));} }1.9 收集到映射表中
package com.package1;import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream;public class Test {public static void main(String[] args) {Map<Integer, String> idToName = people().collect(Collectors.toMap(Person::getId,Person::getName));System.out.println("idToName:"+idToName);Map<Integer, Person> idToPerson = people().collect(Collectors.toMap(Person::getId, Function.identity()));System.out.println("idToPerson: "+idToPerson.getClass().getName()+idToPerson);idToPerson = people().collect(Collectors.toMap(Person::getId,Function.identity(),(existingValue, newValue) -> {throw new IllegalStateException();}, TreeMap::new));//第三個引元闡述了如果原有的鍵值和新的鍵值產生沖突的解決方法(實際上默認的解決就是拋出異常),第四個引元使得本該為Map的本方法生產了TreeMap.System.out.println("idToPerson: "+idToPerson.getClass().getName()+idToPerson);Stream<Locale> locals = Stream.of(Locale.getAvailableLocales());Map<String,String> languageNames = locals.collect(Collectors.toMap(Locale::getDisplayLanguage, l -> l.getDisplayLanguage(l),(existingValue, newValue) -> existingValue,TreeMap::new));System.out.println("languageNames: "+ languageNames);locals = Stream.of(Locale.getAvailableLocales());Map<String, Set<String>> countryLanguageSets = locals.collect(Collectors.toMap(Locale::getDisplayCountry, l -> Set.of(l.getDisplayLanguage()), (a,b) ->{Set<String> union = new HashSet<>(a);union.addAll(b);return union;}));System.out.println("countryLanguageSets"+countryLanguageSets);}public static class Person {private int id;private String name;public Person(int id, String name) {this.id = id;this.name = name;}public int getId() {return id;}public String getName() {return name;}public String toString() {return getClass().getName() + "[id=" + id + "name=" + name + "]";}}public static Stream<Person> people() {return Stream.of(new Person(100, "Peter"), new Person(1002, "Paul"), new Person(1003, "Mary"));} } result: ================================================================================= D:\Devlop\Environment\JDK-15.0.2\bin\java.exe "-javaagent:C:\Program Files\Devlop\IDE\JetBrains\IntelliJ IDEA 2021.1.2\lib\idea_rt.jar=50157:C:\Program Files\Devlop\IDE\JetBrains\IntelliJ IDEA 2021.1.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\MuRuo\Desktop\Dev\IDEA\JavaRelearn\out\production\JavaRelearn com.package1.Test idToName:{100=Peter, 1002=Paul, 1003=Mary} idToPerson: java.util.HashMap{100=com.package1.Test$Person[id=100name=Peter], 1002=com.package1.Test$Person[id=1002name=Paul], 1003=com.package1.Test$Person[id=1003name=Mary]} idToPerson: java.util.TreeMap{100=com.package1.Test$Person[id=100name=Peter], 1002=com.package1.Test$Person[id=1002name=Paul], 1003=com.package1.Test$Person[id=1003name=Mary]} languageNames: {=, 上索布語=hornjoserb??ina, 下索布語=dolnoserb??ina, 世界語=esperanto, 東桑海語=Koyraboro senni, 中庫爾德語=?????? ???????, 中文=中文, 丹麥語=dansk, 烏克蘭語=укра?нська, 烏茲別克語=o‘zbek, 烏爾都語=????, =========此處略去很多字============馬庫阿語=Makua, 馬恩語=Gaelg, 馬拉加斯語=Malagasy, 馬拉地語=?????, 馬拉雅拉姆語=??????, 馬來語=Melayu, 馬耳他語=Malti, 馬賽語=Maa, 馬贊德蘭語=???????, 高棉語=?????, 魯巴加丹加語=Tshiluba} countryLanguageSets{巴西=[西班牙語, 葡萄牙語], 泰國=[泰語], =[, 旺杜語, 烏茲別克語, 桑布魯語, 爪哇語, 宿務語, 隆迪語, 索加語, 克羅地亞語, =========此處略去很多字============科威特=[阿拉伯語], 牙買加=[英語], 世界=[阿拉伯語, 依地文, 普魯士語, 世界語, 沃拉普克語, 國際語, 英語]}Process finished with exit code 01.10 群組和分區
將相同的值群組成組的方法
Map<String,List<Locale>> countryToLocales = locales.collect(Collector.groupingBy(Locale::getCountry)); //稱Locale::getCountry為分類函數當分類函數是斷言函數(即返回值為Boolean的函數時,使用partitioningBy方法更高效
Map<Boolean, List<Locale>> englishAndOtherLocales = locales.collect(Collectors.partitingBy(l -> l.getLanguage().equals("en"))); List<Locale> englishLocales = englishAndOtherLocales.get(true);1.11 下游收集器
groupingBy方法產生的映射表的每個值都是一個列表,下游收集器用于處理這些列表
package com.package1;import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream;import static java.util.stream.Collectors.*; //靜態引入public class Test {public static void main(String[] args) throws IOException{Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());locales = Stream.of(Locale.getAvailableLocales());Map<String,Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry,toSet()));System.out.println("countryToLocaleSet: "+countryToLocaleSet);locales = Stream.of(Locale.getAvailableLocales());Map<String,Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry,counting()));System.out.println("countryToLocaleCounts: "+countryToLocaleCounts);Stream<City> cities = readCities("cities.txt");Map<String,Integer> stateToCityPopulation = cities.collect(groupingBy(City::getState,summingInt(City::getPopulation)));System.out.println("stateToCityPopulation: "+ stateToCityPopulation);cities = readCities("cities.txt");Map<String,Optional<String>> stateToLongestCityName = cities.collect(groupingBy(City::getState,mapping(City::getName,maxBy(Comparator.comparing(String::length)))));locales = Stream.of(Locale.getAvailableLocales());Map<String,Set<String>> countryToLanguages = locales.collect(groupingBy(Locale::getDisplayCountry,mapping(Locale::getDisplayLanguage,toSet())));cities = readCities("cities.txt");Map<String,IntSummaryStatistics> stateToCityPopulationSummary = cities.collect(groupingBy(City::getState,summarizingInt(City::getPopulation)));System.out.println(stateToCityPopulationSummary.get("NY"));cities = readCities("cities.txt");Map<String,String> stateToCityNames = cities.collect(groupingBy(City::getState,reducing("",City::getName,(s,t) -> s.length() == 0 ? t : s + "," +t)));cities = readCities("cities.txt");stateToCityNames = cities.collect(groupingBy(City::getState,mapping(City::getName,joining(","))));}public static class City{private String name;private String state;private int population;public City(String name, String state, int population) {this.name = name;this.state = state;this.population = population;}public String getName() {return name;}public String getState() {return state;}public int getPopulation() {return population;}}public static Stream<City> readCities(String filename) throws IOException{return Files.lines(Paths.get(filename)).map(l -> l.split(", ")).map(a -> new City(a[0],a[1],Integer.parseInt(a[2])));} }1.12 約簡操作
對于v1 op v2 , v2 op v3, v3 op v4, …, vn op vn+1…,稱op為約簡操作,在實際中,例如求和,成績,字符串連接,求最大值和最小值,求集合的并與交等等(但是需要op操作是可結合的,例如減法操作不滿足置換性,就不能使用約簡操作)
通常會有一個幺元e使得 e op x = x ,例如在加法操作中幺元是0
如果流為空,則會返回幺元的值,而非Optional類
List<Integer> values = ...; Optional<Integer> sum = values.stream().reduce((x,y) -> x+y); //也可以直接寫成 reduce(Integer::sum); List<Integer> values = ...; Integer sum = values.stream().reduce(0,(x,y) -> x+y); int result = words.reduce(0,(total,word) -> total + world.length,(total1,total2) -> total1+total2); //第一個lambda表達式表示將每個單詞的個數相加,第二個lambda表達式指出如果多線程操作,將每個線程的結果再相加形成最終結果1.13 基本類型流
IntStream,LongStream,DoubleStream .... 用來直接存儲基本類型值,而無需使用包裝器 package com.package1;import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream;public class Test {public static void main(String[] args) throws IOException{IntStream is1 = IntStream.generate(() ->(int)(Math.random()*100));show("is1",is1);IntStream is2 = IntStream.range(5,10);show("is2",is2);IntStream is3 = IntStream.rangeClosed(5,10);//10 is includedshow("is3",is3);Path path = Paths.get("./src/com/package1/demo.txt");var contents = new String((Files.readAllBytes(path)), StandardCharsets.UTF_8);Stream<String> words = Stream.of(contents.split("\\PL+"));IntStream is4 = words.mapToInt(String::length);//mapTo...方法按照傳入的lambda表達式對每一個元素進行映射操作,轉化成基本類型流show("is4",is4);var sentences = "\uD835\uDD46 is the set of octonions";System.out.println(sentences);IntStream codes =sentences.codePoints();//CharSequence接口的codePoints方法,生成由字符的UnionCode碼或者UTF-16編碼機制的碼元構成的IntStreamSystem.out.println(codes.mapToObj(c -> String.format("%X",c)).collect(Collectors.joining()));Stream<Integer> integers = IntStream.range(0,100).boxed();//boxed方法將基本類型流轉換為對象流IntStream is5 = integers.mapToInt(Integer::intValue);show("is5",is5);}public static void show(String title, IntStream stream){final int SIZE = 10;int[] firstElements = stream.limit(SIZE+1).toArray();System.out.print(title+": ");for(int i = 0; i < firstElements.length; i++){if(i > 0){System.out.print(", ");}if(i < SIZE){System.out.print(firstElements[i]);}else{System.out.print("...");}}System.out.println();}}1.14 并行流
看不懂,以后再來細啃
總結
以上是生活随笔為你收集整理的Java核心技术·卷二·第一章笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开启dhcp服务
- 下一篇: Maple希腊字母按键查表