《Java8实战》笔记(05):使用流
筛选和切片
Filtering
用谓词Predicate筛选-filter
List<Dish> vegetarianMenu = menu.stream()//.filter(Dish::isVegetarian)//Predicate<T>做参数.collect(toList());vegetarianMenu.forEach(System.out::println);
筛选各异的元素-去重-distinct
// Filtering unique elements
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()//.filter(i -> i % 2 == 0)//.distinct()//.forEach(System.out::println);
截短流-limit
// Truncating a stream
List<Dish> dishesLimit3 = menu.stream()//.filter(d -> d.getCalories() > 300)//.limit(3)//.collect(toList());dishesLimit3.forEach(System.out::println);
跳过元素-skip
List<Dish> dishesSkip2 = menu.stream()//.filter(d -> d.getCalories() > 300)//.skip(2)//.collect(toList());dishesSkip2.forEach(System.out::println);
映射-map
对流中每一个元素应用函数-map
// map
List<String> dishNames = Dish.menu.stream().map(Dish::getName).collect(toList());
System.out.println(dishNames);// map
List<String> words = Arrays.asList("Hello", "World");
List<Integer> wordLengths = words.stream().map(String::length).collect(toList());
System.out.println(wordLengths);
流的扁平化-flatMap
Mapping
PS.多重map压扁
任务
给定单词列表["Hello","World"],
想要返回列表["H","e","l","o","W","r","d"]
第一版本
words.stream().map(word -> word.split("")) //返回Stream<String[]>.distinct().collect(toList());
这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String列 表 )。 因 此 , map 返 回 的 流 实 际 上 是 Stream<String[]> 类 型 的 。 你 真 正 想 要 的 是 用Stream来表示一个字符串流。
解决之道
1.尝试使用map和Array.stream()
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);//返回的并不是想要的List<String>
List<Stream<String>> list = words.stream().map(word -> word.split(""))// 返回Stream<String[]>.map(Arrays::stream)// 返回Stream<Stream<String>> .distinct().collect(toList());
2.使用flatMap
List<String> uniqueCharacters =words.stream().map(w -> w.split(""))// 返回Stream<String[]>.flatMap(Arrays::stream)// 返回Stream<String>,把Stream<Stream<String>> 压成 Stream<String>.distinct().collect(Collectors.toList());
一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
PS. flatmap 能把Stream<Stream> 压成 Stream
//简化了一些
words.stream().flatMap((String line) -> Arrays.stream(line.split(""))).distinct().forEach(System.out::println);
更多流的扁平化例子
0.给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4, 5],应该返回[1, 4, 9, 16, 25]
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream().map(n -> n * n).collect(toList());
1.给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[[1, 3], [1, 4], [2, 3], [2, 4], [3, 3], [3, 4]]。为简单起见,你可以用有两个元素的数组来代表数对。
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs = numbers1.stream().flatMap(i -> numbers2.stream().map(j -> new int[]{i, j})).collect(toList());
2.如何扩展前一个例子,只返回总和能被3整除的数对呢?例如[2, 4]和[3, 3]是可以的。
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =numbers1.stream().flatMap(i ->numbers2.stream().filter(j -> (i + j) % 3 == 0).map(j -> new int[]{i, j})).collect(toList());
查找和匹配-find-match
Finding
检查谓词是否至少匹配一个元素-anyMatch
private static boolean isVegetarianFriendlyMenu() {return Dish.menu.stream().anyMatch(Dish::isVegetarian);
}
检查谓词是否匹配所有元素-allMatch
private static boolean isHealthyMenu() {return Dish.menu.stream().allMatch(d -> d.getCalories() < 1000);
}
检查谓词是否不匹配所有元素-noneMatch
private static boolean isHealthyMenu2() {return Dish.menu.stream().noneMatch(d -> d.getCalories() >= 1000);
}
anyMatch、allMatch和noneMatch这三个操作都用到了所谓的短路,这就是大家熟悉的Java中&&和||运算符短路在流中的版本
查找元素-findAny
Optional<Dish> dish =menu.stream().filter(Dish::isVegetarian).findAny();
Optional一览
Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中, findAny可能什么元素都没找到。 Java 8的库设计人员引入了Optional<T>,这样就不用返回众所周知容易出问题的null了。
Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法也不错。
- isPresent()将在Optional包含值的时候返回true, 否则返回false。
- ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块。我们在第3章
介绍了Consumer函数式接口;它让你传递一个接收T类型参数,并返回void的Lambda表达式。 - T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
- T orElse(T other)会在值存在时返回值,否则返回一个默认值。
menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d -> System.out.println(d.getName());
查找第一个元素-findFirst
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst();
何时使用findFirst和findAny
你可能会想,为什么会同时有findFirst和findAny呢?答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。
归约-reduce
Reducing
元素求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);//orint sum = numbers.stream().reduce(0, Integer::sum);
reduce接受两个参数:
- 一个初始值,这里是0;
- 一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a + b
无初始值
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
为什么它返回一个Optional呢?考虑流中没有任何元素的情况。reduce操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和可能不存在。
元素求积
int product = numbers.stream().reduce(1, (a, b) -> a * b);
最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);//当然也可以写成Lambda (x, y) -> x < y ? x : y而不是Integer::min,不过后者比较易读
总数
int count = menu.stream().map(d -> 1).reduce(0, (a, b) -> a + b);long count = menu.stream().count();
归约方法的优势与并行化
相比于前面写的逐步迭代求和,使用reduce的好处在于,这里的迭代被内部迭代抽象掉了,这让内部实现得以选择并行执行reduce操作。而迭代式求和例子要更新共享变量sum,这不是那么容易并行化的。如果你加入了同步,很可能会发现线程竞争抵消了并行本应带来的性能提升!这种计算的并行化需要另一种办法:将输入分块,分块求和,最后再合并起来。但这样的话代码看起来就完全不一样了。
使用流来对所有的元素并行求和时,代码几乎不用修改:stream()换成了parallelStream()。
int sum = numbers.parallelStream().reduce(0, Integer::sum);
流操作:无状态和有状态
诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态(假设用户提供的Lambda或方法引用没有内部可变状态)。但诸如reduce、sum、max等操作需要内部状态来累积结果。在上面的情况下,内部状态很小。在我们的例子里就是一个int或double。不管流中有多少元素要处理,内部状态都是有界的。
相反,诸如sort或distinct等操作一开始都和filter和map差不多——都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题(把质数流倒序会做什么呢?它应当返回最大的质数,但数学告诉我们它不存在)。我们把这些操作叫作有状态操作。
中间操作和终端操作小结
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T->boolean |
distinct |
中间 (有状态-无界) |
Stream<T> | - | - |
skip |
中间 (有状态-有界) |
Stream<T> | long | - |
limit |
中间 (有状态-有界) |
Stream<T> | long | - |
map | 中间 | Stream<R> | Function<T,R> | T->R |
flatMap | 中间 | Stream<R> | Function<T,Stream<R>> | T->Stream<R> |
sorted |
中间 (有状态-无界) |
Stream<T> | Comparator<T> | (T,T)->int |
anyMatch | 终端 | boolean | Predicate<T> | T->boolean |
noneMatch | 终端 | boolean | Predicate<T> | T->boolean |
allMatch | 终端 | boolean | Predicate<T> | T->boolean |
findAny | 终端 | Optional<T> | - | - |
findFirst | 终端 | Optional<T> | - | - |
forEach | 终端 | void | Consumer<T> | T->void |
collect | 终端 | R | Collector<T,A,R> | - |
reduce |
终端 (有状态-有界) |
Optional<T> | BinaryOperator<T> | (T,T)->T |
count | 终端 | long | - | - |
付诸实践
执行交易的交易员
- 找出2011年发生的所有交易,并按交易额排序(从低到高)。
- 交易员都在哪些不同的城市工作过?
- 查找所有来自于剑桥的交易员,并按姓名排序。
- 返回所有交易员的姓名字符串,按字母顺序排序。
- 有没有交易员是在米兰工作的?
- 打印生活在剑桥的交易员的所有交易额。
- 所有交易中,最高的交易额是多少?
- 找到交易额最小的交易。
领域:交易员和交易
Trader
Transaction
解答
PuttingIntoPractice
1.找出2011年的所有交易并按交易额排序(从低到高)
List<Transaction> tr2011 = transactions.stream().filter(transaction -> transaction.getYear() == 2011).sorted(comparing(Transaction::getValue)).collect(toList());
2.交易员都在哪些不同的城市工作过
List<String> cities = transactions.stream().map(transaction -> transaction.getTrader().getCity()).distinct().collect(toList());//orSet<String> cities =transactions.stream().map(transaction -> transaction.getTrader().getCity()).collect(toSet());
3.查找所有来自于剑桥的交易员,并按姓名排序
List<Trader> traders = transactions.stream().map(Transaction::getTrader).filter(trader -> trader.getCity().equals("Cambridge")).distinct().sorted(comparing(Trader::getName)).collect(toList());
4.返回所有交易员的姓名字符串,按字母顺序排序
String traderStr = transactions.stream().map(transaction -> transaction.getTrader().getName()).distinct().sorted().reduce("", (n1, n2) -> n1 + n2);String traderStr =transactions.stream().map(transaction -> transaction.getTrader().getName()).distinct().sorted().collect(joining());
5.有没有交易员是在米兰工作的
boolean milanBased = transactions.stream().anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println(milanBased);
6.打印生活在剑桥的交易员的所有交易额
transactions.stream().filter(t -> "Cambridge".equals(t.getTrader().getCity())).map(Transaction::getValue).forEach(System.out::println);
7.所有交易中,最高的交易额是多少
Optional<Integer> highestValue =transactions.stream().map(Transaction::getValue).reduce(Integer::max);
8.找到交易额最小的交易
Optional<Transaction> smallestTransaction =transactions.stream().reduce((t1, t2) ->t1.getValue() < t2.getValue() ? t1 : t2);Optional<Transaction> smallestTransaction =transactions.stream().min(comparing(Transaction::getValue));
数值流
NumericStreams
可以使用reduce方法计算流中元素的总和.
例如,你可以像下面这样计算菜单的热量:
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
这段代码的问题是,它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型,再进行求和。要是可以直接像下面这样调用sum方法,岂不是更好?
int calories = menu.stream().map(Dish::getCalories).sum();//这里不能编译,Streams接口没有定义sum方法
但这是不可能的。问题在于map方法会生成一个Stream。虽然流中的元素是Integer类型,但Streams接口没有定义sum方法。
为什么没有呢?比方说,你只有一个像menu那样的Stream,把各种菜加起来是没有任何意义的。
但不要担心,Stream API还提供了原始类型流特化,专门支持处理数值流的方法。
原始类型流特化
Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。
要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
映射到数值流-mapToXXX
将流转换为特化版本的常用方法是mapToInt、 mapToDouble和mapToLong。
int calories = menu.stream().mapToInt(Dish::getCalories)//返回一个IntStream,不是Stream<Integer>.sum();
请注意,如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如max、min、average等。
转换回对象流-boxed
同样,一旦有了数值流,你可能会想把它转换回非特化流。
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
默认值-OptionalInt
如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。如何区分没有元素的流和最大值真的是0的流呢?
Optional可以用Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
int max = maxCalories.orElse(1);
数值范围-range
Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
IntStream evenNumbers = IntStream.rangeClosed(1, 100)//范围[1,100],IntStream.range(1, 100)范围为[1,100).filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());
数值流应用:勾股数
Pythagorean
勾股数
a^2+b^2=c^2
a | b | c |
---|---|---|
3 | 4 | 5 |
5 | 12 | 13 |
6 | 8 | 10 |
7 | 24 | 25 |
表示三元数
new int[]{3, 4, 5};//来表示勾股数(3, 4, 5)
筛选成立的组合
怎么知道它是否能形成一组勾股数呢?你需要测试a * a + b * b的平方根是不是整数,也就是说它没有小数部分——在Java里可以使用expr % 1表示。如果它不是整数,那就是说c不是整数。
filter(b -> Math.sqrt(a*a + b*b) % 1 == 0);
生成三元组
stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
生成b值
IntStream.rangeClosed(1, 100).filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
生成值
//符合形成直角三角形
Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100).boxed()flatMap用到泛型,所以不能使用基本类型.flatMap(a ->IntStream.rangeClosed(a, 100).filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).mapToObj(b ->new int[]{a, b, (int)Math.sqrt(a * a + b * b)}));
运行代码
pythagoreanTriples.limit(5).forEach(t ->System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
更上一层楼
目前的解决办法并不是最优的,因为你要求两次平方根。让代码更为紧凑的一种可能的方法是,先生成所有的三元数(a*a, b*b, a*a+b*b),然后再筛选符合条件的
Stream<double[]> pythagoreanTriples2 =IntStream.rangeClosed(1, 100).boxed().flatMap(a ->IntStream.rangeClosed(a, 100).mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}).filter(t -> t[2] % 1 == 0));
构建流
BuildingStreams
由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);//你可以使用empty得到一个空流,如下所示:
Stream<String> emptyStream = Stream.empty();
由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
由文件生成流
long uniqueWords = 0;//流会自动关闭
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();
}
catch(IOException e){}
由函数生成流:创建无限流
Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。
这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!
一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。
迭代-iterate
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator<T>类型)。
迭代-斐波纳契数列
//序列(0, 1), (1, 1), (1, 2), (2, 3), (3, 5), (5, 8), (8, 13), (13, 21)...
Stream.iterate(new int[]{0, 1},t -> new int[]{t[1], t[0]+t[1]}).limit(20).forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));//只想打印正常的斐波纳契数列
Stream.iterate(new int[]{0, 1},t -> new int[]{t[1],t[0] + t[1]}).limit(10).map(t -> t[0]).forEach(System.out::println);
生成
Stream.generate(Math::random).limit(5).forEach(System.out::println);
我们使用的供应源(指向Math.random的方法引用)是无状态的:它不会在任何地方记录任何值,以备以后计算使用。但供应源不一定是无状态的。
你可以创建存储状态的供应源,它可以修改状态,并在为流生成下一个值时使用。
举个例子,接下来将展示如何利用generate创建斐波纳契数列,这样你就可以和用iterate方法的办法比较一下。
但很重要的一点是,在并行代码中使用有状态的供应源是不安全的。因此下面的代码仅仅是为了内容完整,应尽量避免使用
IntStream.generate(() -> 1).limit(5).forEach(System.out::println);IntStream twos = IntStream.generate(new IntSupplier(){public int getAsInt(){return 2;}
});
生成-斐波纳契数列
IntSupplier fib = new IntSupplier(){private int previous = 0;private int current = 1;public int getAsInt(){int oldPrevious = this.previous;int nextValue = this.previous + this.current;this.previous = this.current;this.current = nextValue;return oldPrevious;}
};IntStream.generate(fib).limit(10).forEach(System.out::println);
前面的代码创建了一个IntSupplier的实例。此对象有可变的状态:它在两个实例变量中记录了前一个斐波纳契项和当前的斐波纳契项。getAsInt在调用时会改变对象的状态,由此在每次调用时产生新的值。
相比之下,使用iterate的方法则是纯粹不变的:它没有修改现有状态,但在每次迭代时会创建新的元组。
请注意,因为你处理的是一个无限流,所以必须使用limit操作来显式限制它的大小;否则,终端操作(这里是forEach)将永远计算下去。
同样,你不能对无限流做排序或归约,因为所有元素都需要处理,而这永远也完不成!
小结
- Streams API可以表达复杂的数据处理查询。常用的流操作总结在表5-1中。
- 使用filter、distinct、skip和limit对流做筛选和切片。
- 使用map和flatMap提取或转换流中的元素。
- 使用findFirst 和findAny 方法查找流中的元素。你可以用allMatch 、
noneMatch和anyMatch方法让流匹配给定的谓词。这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。 - 你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大
元素。 - filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
- 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操作也有相应的特化。
- 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
- 无限流是没有固定大小的流。
《Java8实战》笔记(05):使用流相关推荐
- Java8实战笔记--组合异步编程
一.Future 初衷是对将来某个时刻会发生的结果进行建模. 想象成这样的场景:你拿了一袋子衣 服到你中意的干洗店去洗.干洗店的员工会给你张发票,告诉你什么时候你的衣服会洗好(这就 是一个Future ...
- 《Java8实战》读书笔记06:Parallel Stream 并行流
<Java8实战>读书笔记06:Parallel Stream 并行流 第7章 并行数据处理与性能 7.1 并行流 7.1.1 将顺序流转换为并行流 7.1.2 测量流性能 7.1.3 正 ...
- 《Java8实战》-第六章读书笔记(用流收集数据-01)
用流收集数据 我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合.你可以把Java 8的流看作花哨又懒惰的数据集迭代器.它们支持两种类型的操作:中间操作(如 filter 或 map )和终 ...
- 《Java8实战》笔记汇总
<Java8实战>笔记(01):为什么要关心Java8 <Java8实战>笔记(02):通过行为参数传递代码 <Java8实战>笔记(03):Lambda表达式 & ...
- 《Java8实战》读书笔记10:组合式异步编程 CompletableFuture
<Java8实战>读书笔记10:组合式异步编程 CompletableFuture 第11章 CompletableFuture:组合式异步编程 11.1 Future 接口 (只是个引子 ...
- Java8实战学习笔记(三)——函数式数据处理
一.引入流 (一).引言 1.流是什么 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现).可以看成遍历数据集的高级迭代器. 流可以透明地并行 ...
- lambda 两个list获取交集_《Java8 实战》笔记 - Lambda 表达式
Lambda 表达式介绍 可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它 有参数列表.函数主体.返回类型,可能还有一个可以抛出的异常列表.这个定义够大的,让我 ...
- 阿里巴巴中台战略思想与架构实战笔记
阿里巴巴中台战略思想与架构实战笔记 序言一 序言二 第一部分 引子 第1章 阿⾥巴巴集团中台战略引发的思考 1.1 阿⾥巴巴共享业务事业部的发展史 1.2 企业信息中心发展的症结 "烟囱式& ...
- JAVA8实战 -- Lamdba表达式
JAVA8实战 文章目录 JAVA8实战 前言 一.JAVA8的核心和优势是什么 二.Lambda表达式 1.函数式编程 -- 将代码传递给方法(将方法作为参数传递给另外一个代码) 2.Lambda表 ...
- Wireshark网络分析实战笔记(二)显示过滤器
转自:彬彬在线 显示过滤表达式中操作符: == eq 等于 != ne 不等于 > gt 高于 < lt 低于 >= ge 不高于 <= le 不低于 contains 包 ...
最新文章
- 眼图在通信系统中有什么意义_悟空CRM:施行CRM系统对汽车行业有什么意义
- 如何使用我的博客电子书
- 华为畅享8plus停产了吗_华为畅享8Plus
- Python数据清理之数据质量
- PAT乙级(1034 有理数四则运算)
- final cut pro x怎样导入3D LUT调色预设?
- JDK API 1.60 中文版(Java 1.6 中文帮助文档) 中文版
- cydia 未能连接服务器,iOS 越狱全过程,解决cydia或sileo无法联网问题
- H3C交换机堆叠配置
- 企业微信oauth认证_企业微信登陆
- Bypass open_basedir
- 美赛BOOM数学建模4-3马尔科夫预测
- Java实现Sunday算法
- 三、队列:优先队列+循环队列(击鼓传花算法)
- 当有人知道你的愿望想帮你实现你会是怎样
- 测绘用计算机吗,探讨测绘工程中计算机制图的运用问题原稿(网络分享版)
- 高数 | 多元函数求极限 使用极坐标代换的条件与细节
- Linux OS 学习笔记(1)
- python .net web开发对比_Python与C#/.NET——在使用Python开发大型web应用程序时,需要考虑哪些关键区别?...
- 转载:解决wps linux中字体缺失以及文字体名字全是英文的问题
热门文章
- 用计算机MR,计算机上的【MC、MR、M
- python爬虫抓取文本_Python实现可获取网易页面所有文本信息的网易网络爬虫功能示例...
- 【转】C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁
- [你必须知道的.NET] 第八回:品味类型---值类型与引用类型(上)-内存有理
- java 不识别enum_Java enum关键字不识别的快速解决办法
- typeahead有什么作用_typeahead使用配置参数。
- 【Chrome浏览器】常用快捷键整理
- 【牛客 - 318M】被打脸的潇洒哥(几何问题,水题,结论,知识点)
- mysql use index用法_MySQL中USE INDEX 和 FORCE INDEX
- java中将字符串顺序反传转_如何在Java中将字符串序列化的Erlang术语反序列化为JInterface对象?...