"Java8 actual combat" notes (05): use flow

tags: Java  java 8

Screening and slicing

Filtering

Filter with predicate-filter

List<Dish> vegetarianMenu = menu.stream()//
		.filter(Dish::isVegetarian)//Predicate<T> as a parameter
		.collect(toList());

vegetarianMenu.forEach(System.out::println);

Filter different elements-de-duplication-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);

Truncated stream-limit

// Truncating a stream
List<Dish> dishesLimit3 = menu.stream()//
		.filter(d -> d.getCalories() > 300)//
		.limit(3)//
		.collect(toList());

dishesLimit3.forEach(System.out::println);

Skip element-skip

List<Dish> dishesSkip2 = menu.stream()//
		.filter(d -> d.getCalories() > 300)//
		.skip(2)//
		.collect(toList());

dishesSkip2.forEach(System.out::println);

Map-map

Apply function-map to each element in the stream

// 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);

Stream flattening-flatMap

Mapping

PS. Multiple map flattening

task

Given a word list ["Hello","World"],
 Want to return to the list ["H","e","l","o","W","r","d"]

The first version

words.stream()
	.map(word -> word.split("")) //Return Stream<String[]>
	.distinct()
	.collect(toList());

The problem with this method is that the Lambda passed to the map method returns a String[] (List of Strings) for each word. Therefore, the flow returned by the map is actuallyStream<String[]> Type of . What you really want is to use Stream to represent a string stream.

The solution

1. Try to use map and Array.stream()

String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);

//What is returned is not the desired List<String>
List<Stream<String>> list = words.stream()
	.map(word -> word.split(""))// Return Stream<String[]>
	.map(Arrays::stream)// Return Stream<Stream<String>> 
	.distinct()
	.collect(toList());

2. Use flatMap

List<String> uniqueCharacters =
	words.stream()
	.map(w -> w.split(""))// Return Stream<String[]>
	.flatMap(Arrays::stream)// Return Stream<String>, compress Stream<Stream<String>> into Stream<String>
	.distinct()
	.collect(Collectors.toList());

In a nutshell, the flatmap method allows you to replace every value in one stream with another stream, and then connect all streams into one stream.

PS. flatmap can compress Stream<Stream> into Stream

//Simplified some
words.stream()
	.flatMap((String line) -> Arrays.stream(line.split("")))
	.distinct()
	.forEach(System.out::println);

More stream flat examples

0. Given a list of numbers, how to return a list consisting of the square of each number? For example, given [1, 2, 3, 4, 5], it should return [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. Given two lists of numbers, how to return all the number pairs? For example, given list [1, 2, 3] and list [3, 4], it should return [[1, 3], [1, 4], [2, 3], [2, 4], [3, 3], [3, 4]]. For simplicity, you can use an array with two elements to represent pairs of numbers.

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. How to extend the previous example to only return the number pairs whose sum is divisible by 3? For example, [2, 4] and [3, 3] are possible.

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 and match-find-match

Finding

Check if the predicate matches at least one element-anyMatch

private static boolean isVegetarianFriendlyMenu() {
	return Dish.menu.stream().anyMatch(Dish::isVegetarian);
}

Check if the predicate matches all elements-allMatch

private static boolean isHealthyMenu() {
	return Dish.menu.stream().allMatch(d -> d.getCalories() < 1000);
}

Check if the predicate does not match all elements-noneMatch

private static boolean isHealthyMenu2() {
	return Dish.menu.stream().noneMatch(d -> d.getCalories() >= 1000);
}

The three operations anyMatch, allMatch and noneMatch all use the so-calledShort circuit, This is the familiar version of the && and || operators in Java that short-circuit in the stream

Find element-findAny

Optional<Dish> dish =
	menu.stream()
		.filter(Dish::isVegetarian)
		.findAny();

Optional at a glance

The Optional<T> class (java.util.Optional) is a container class that represents the presence or absence of a value. In the above code, findAny may not find any elements. The library designers of Java 8 introduced Optional<T> so that there is no need to return null, which is known to be problematic.

There are several ways in Optional that can force you to explicitly check for the existence of a value or handle situations where the value does not exist.

  • isPresent() will return true if the Optional contains a value, otherwise it will return false.
  • ifPresent(Consumer<T> block) will execute the given block of code when the value exists. We are in Chapter 3
    introduces the Consumer functional interface; it allows you to pass a Lambda expression that accepts a T type parameter and returns void.
  • T get() will return the value if it exists, otherwise it will throw a NoSuchElement exception.
  • T orElse(T other) will return a value if the value exists, otherwise it will return a default value.

	menu.stream()
		.filter(Dish::isVegetarian)
		.findAny()
		.ifPresent(d -> System.out.println(d.getName());

Find the first element-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();

When to use findFirst and findAny

You may wonder, why are there both findFirst and findAny? the answer isparallel. Finding the first element is more restrictive in parallelism. If you don't care which element is returned, please use findAny, because it is less restrictive when using parallel streams.

Reduce-reduce

Reducing

Element summation

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

//or

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

reduce accepts two parameters:

  • An initial value, here is 0;
  • A BinaryOperator to combine two elements to produce a new value, here we use lambda (a, b) -> a + b


No initial value

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

Why does it return an Optional? Consider the case where there are no elements in the stream. The reduce operation cannot return its sum because it has no initial value. This is why the result is wrapped in an Optional object to indicate that the sum may not exist.

Element quadrature

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

Maximum and minimum

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

//Of course it can also be written as Lambda (x, y) -> x <y? X: y instead of Integer::min, but the latter is easier to read

total

int count = menu.stream()
	.map(d -> 1)
	.reduce(0, (a, b) -> a + b);

long count = menu.stream().count();

Advantages and parallelization of reduction methods

Compared with the gradual iterative summation previously written, the advantage of using reduce is that the iterations here are abstracted away by internal iterations, which allows internal implementations to choose to execute reduce operations in parallel. The iterative summation example updates the shared variable sum, which is not so easy to parallelize. If you join the synchronization,It is possible to find that thread competition offsets the performance improvement that parallelism should bring! The parallelization of this kind of calculation requires another method: divide the input into blocks, sum the blocks, and finally merge them. But in this case, the code looks completely different.

When using a stream to sum all the elements in parallel, the code hardly needs to be modified: stream() is replaced by parallelStream().

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

Stream operations: stateless and stateful

Operations such as map or filter will get every element from the input stream and get 0 or 1 result in the output stream. These operations are generallyno status: They have no internal state (assuming that the Lambda or method reference provided by the user has no internal mutable state). But operations such as reduce, sum, and max require internal state to accumulate results. In the above case, the internal state is very small. In our case it is an int or double. No matter how many elements in the stream need to be processed, the internal state is bounded.

On the contrary, operations such as sort or distinct are similar to filter and map at the beginning-both accept a stream and generate a stream (intermediate operation), but there is a key difference. You need to know the previous history when sorting and removing duplicates from the stream. For example, sorting requires all elements to be put into the buffer before adding an item to the output stream. The storage requirement for this operation isUnboundedof. If the stream is relatively large or infinite, there may be problems (what does it do if you reverse the stream of prime numbers? It should return the largest prime number, but mathematics tells us that it does not exist). We call these operationsStatefuloperating.

Summary of intermediate operations and terminal operations

operating Types of Return type Type/Functional Interface Used Function descriptor
filter intermediate Stream<T> Predicate<T> T->boolean
distinct intermediate
(Stateful-Unbounded)
Stream<T> - -
skip intermediate
(Stateful-Bounded)
Stream<T> long -
limit intermediate
(Stateful-Bounded)
Stream<T> long -
map intermediate Stream<R> Function<T,R> T->R
flatMap intermediate Stream<R> Function<T,Stream<R>> T->Stream<R>
sorted intermediate
(Stateful-Unbounded)
Stream<T> Comparator<T> (T,T)->int
anyMatch terminal boolean Predicate<T> T->boolean
noneMatch terminal boolean Predicate<T> T->boolean
allMatch terminal boolean Predicate<T> T->boolean
findAny terminal Optional<T> - -
findFirst terminal Optional<T> - -
forEach terminal void Consumer<T> T->void
collect terminal R Collector<T,A,R> -
reduce terminal
(Stateful-Bounded)
Optional<T> BinaryOperator<T> (T,T)->T
count terminal long - -

Put into practice

Trader executing the transaction

  1. Find all transactions that occurred in 2011 and sort them by transaction amount (lowest to high).
  2. Which different cities have traders worked in?
  3. Find all traders from Cambridge, sorted by name.
  4. Returns the name strings of all traders, sorted alphabetically.
  5. Are there any traders working in Milan?
  6. Print all transactions of traders living in Cambridge.
  7. What is the highest transaction amount among all transactions?
  8. Find the transaction with the smallest transaction amount.

Field: Traders and Trading

Trader

Transaction

answer

PuttingIntoPractice

1. Find all transactions in 2011 and sort them by transaction amount (lowest to high)

List<Transaction> tr2011 = transactions.stream()
		.filter(transaction -> transaction.getYear() == 2011)
		.sorted(comparing(Transaction::getValue))
		.collect(toList());

2. Which different cities have traders worked in?

List<String> cities = transactions.stream()
	.map(transaction -> transaction.getTrader().getCity())
	.distinct()
	.collect(toList());

//or

Set<String> cities =
	transactions.stream()
	.map(transaction -> transaction.getTrader().getCity())
	.collect(toSet());

3. Find all traders from Cambridge and sort them by name

List<Trader> traders = transactions.stream()
		.map(Transaction::getTrader)
		.filter(trader -> trader.getCity().equals("Cambridge"))
		.distinct()
		.sorted(comparing(Trader::getName))
		.collect(toList());

4. Return the name strings of all traders, sorted alphabetically

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. Are there any traders working in Milan?

boolean milanBased = transactions.stream()
			.anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println(milanBased);

6. Print all transactions of traders living in Cambridge

transactions.stream()
	.filter(t -> "Cambridge".equals(t.getTrader().getCity()))
	.map(Transaction::getValue)
	.forEach(System.out::println);

7. What is the highest transaction amount among all transactions?

Optional<Integer> highestValue =
	transactions.stream()
	.map(Transaction::getValue)
	.reduce(Integer::max);

8. Find the smallest transaction

Optional<Transaction> smallestTransaction =
	transactions.stream()
	.reduce((t1, t2) ->
	t1.getValue() < t2.getValue() ? t1 : t2);

Optional<Transaction> smallestTransaction =
	transactions.stream()
	.min(comparing(Transaction::getValue));

Numerical flow

NumericStreams

You can use the reduce method to calculate the sum of the elements in the stream.

For example, you can calculate the calories of the menu like this:

int calories = menu.stream()
		.map(Dish::getCalories)
		.reduce(0, Integer::sum);

The problem with this code is that it has an implicit boxing cost. Each Integer must be unboxed into a primitive type, and then summed. Wouldn't it be better if the sum method could be called directly like the following?

int calories = menu.stream()
		.map(Dish::getCalories)
		.sum();//Cannot compile here, the Streams interface does not define the sum method

But this is impossible. The problem is that the map method generates a Stream. Although the elements in the stream are of type Integer, the Streams interface does not define the sum method.

Why not? For example, you only have a Stream like menu, and adding up various dishes is meaningless.

But don't worry, the Stream API also provides primitive type stream specialization, specifically supporting methods for processing numeric streams.

Primitive type stream specialization

Java 8 introduced three primitive type specialization stream interfaces to solve this problem: IntStream, DoubleStream and LongStream, respectively specializing the elements in the stream into int, long and double, thus avoiding the implicit boxing cost. Each interface brings new methods for commonly used numerical reductions, such as sum of numerical streams to find the max of the largest element. In addition, there are methods to convert them back to object streams when necessary.

The thing to remember is, The reason for these specializations is not the complexity of the stream, but the complexity caused by boxing-that is, the efficiency difference between int and Integer.

Map to numeric stream-mapToXXX

Common methods for converting streams to specialized versions are mapToInt, mapToDouble, and mapToLong.

int calories = menu.stream()
	.mapToInt(Dish::getCalories)//Return an IntStream, not Stream<Integer>
	.sum();

Please note that if the stream is empty, sum returns 0 by default. IntStream also supports other convenient methods, such as max, min, average, etc.

Convert back to object stream-boxed

Similarly, once you have a numerical stream, you may want to convert it back to a non-specialized stream.

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

Default value-OptionalInt

If you want to calculate the largest element in the IntStream, you have to change the way, because 0 is the wrong result. How to distinguish a stream with no elements from a stream with a maximum value of 0?

Optional can be parameterized with reference types such as Integer and String. For the three primitive stream specializations, there is also an Optional primitive type specialization version: OptionalInt, OptionalDouble, and OptionalLong.

For example, to find the largest element in the IntStream, you can call the max method, which will return an OptionalInt:

OptionalInt maxCalories = menu.stream()
		.mapToInt(Dish::getCalories)
		.max();

Now, if there is no maximum value, you can explicitly process OptionalInt to define a default value:

int max = maxCalories.orElse(1);

Range

Java 8 introduced two static methods that can be used for IntStream and LongStream to help generate this range: range and rangeClosed. In both methods, the first parameter accepts the start value, and the second parameter accepts the end value. But range does not contain the end value, and rangeClosed contains the end value.

IntStream evenNumbers = IntStream.rangeClosed(1, 100)//Range [1,100], IntStream.range(1, 100) range is [1,100)
	.filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());

Numerical flow application: Pythagorean

Pythagorean

Pythagorean shares

a^2+b^2=c^2
a b c
3 4 5
5 12 13
6 8 10
7 24 25

Ternary number

new int[]{3, 4, 5};//To indicate the number of Pythagorean shares (3, 4, 5)

Filter established combinations

How do you know whether it can form a set of Pythagorean shares? You need to test whether the square root of a * a + b * b is an integer, which means it has no decimal part-it can be represented by expr% 1 in Java. If it is not an integer, then c is not an integer.

filter(b -> Math.sqrt(a*a + b*b) % 1 == 0);

Generate triples

stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
		.map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});

Generate b value

	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)});

Generated value

//Conforms to form a right triangle
Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100)
	.boxed()flatMap uses generics, so basic types cannot be used
	.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)})
	);

Run code

pythagoreanTriples.limit(5)
	.forEach(t ->System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

Take it to the next level

The current solution is not optimal, because you require two square roots. One possible way to make the code more compact is to first generate all the ternary numbers (a*a, b*b, a*a+b*b), and then filter those that meet the conditions

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));

Build flow

BuildingStreams

Create flow from value

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

//You can use empty to get an empty stream, as shown below:
Stream<String> emptyStream = Stream.empty();

Create stream from array

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

Generate stream from file

long uniqueWords = 0;

//The stream will automatically close
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){
}

Generate a stream from a function: create an infinite stream

The Stream API provides two static methods to generate streams from functions: Stream.iterate and Stream.generate.

These two operations can create so-called infinite streams: unlike streams created from a fixed collection, there are no fixed-size streams. The stream generated by iterate and generate will use the given function to create values ​​on demand, so it can be calculated endlessly!

In general, limit(n) should be used to limit this stream to avoid printing infinitely many values.

Iterate

Stream.iterate(0, n -> n + 2)
	.limit(10)
	.forEach(System.out::println);

The iterate method accepts an initial value (here 0), and a Lambda (UnaryOperator<T> type) that is applied to each new value generated in turn.

Iteration-Fibonacci Sequence

//Sequence (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] +")"));

//Just want to print the normal Fibonacci sequence
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);

generate

Stream.generate(Math::random)
	.limit(5)
	.forEach(System.out::println);

The source of supply we use (a method reference to Math.random) isStateless: It will not record any value anywhere for future calculations. But the source of supplyNot necessarily stateless。
You can create a supply source that stores state, it can modify the state, and use it when generating the next value for the flow.

For example, the following will show how to use generate to create a Fibonacci sequence, so you can compare it with the iterate method.

But the important point is that inIt is not safe to use a stateful supply source in parallel code. Therefore, the following code is just for completeness and should beTry to avoid using

IntStream.generate(() -> 1)
	.limit(5)
	.forEach(System.out::println);

IntStream twos = IntStream.generate(new IntSupplier(){
	public int getAsInt(){
		return 2;
	}
});

Generating-Fibonacci Sequence

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);

The preceding code creates an instance of IntSupplier. This object hasVariableStatus: It records the previous Fibonacci term and the current Fibonacci term in two instance variables. getAsInt will change the state of the object when it is called, thereby generating a new value each time it is called.

In contrast, the iterate method is pureChangeless: It does not modify the existing state, but it willCreate a new tuple。

Please note that because you are dealing with an infinite stream, you must use the limit operation to explicitly limit its size; otherwise, the terminal operation (here, forEach) will be calculated forever.

Similarly, you cannot sort or reduce an infinite stream, because all elements need to be processed, and this will never be done!

summary

  • Streams API can express complex data processing queries. Commonly used stream operations are summarized in Table 5-1.
  • Use filter, distinct, skip, and limit to filter and slice streams.
  • Use map and flatMap to extract or transform elements in the stream.
  • Use findFirst and findAny methods to find elements in the stream. You can use allMatch,
    The noneMatch and anyMatch methods let the stream match the given predicate. These methods all take advantage of short circuits: the calculation stops as soon as the result is found; there is no need to process the entire flow.
  • You can use the reduce method to iteratively merge all the elements in the stream into one result, such as summing or finding the largest
    element.
  • Operations such as filter and map are stateless, and they do not store any state. Operations such as reduce need to store the state to calculate a value. Operations such as sorted and distinct also need to store state, because they need to cache all elements in the stream to return a new stream. This kind of operation is called stateful operation.
  • There are three basic primitive type specializations for streams: IntStream, DoubleStream, and LongStream. Their operations also have corresponding specializations.
  • Streams can be created not only from collections, but also from specific methods such as values, arrays, files, and iterate and generate.
  • An infinite stream is a stream with no fixed size.

Intelligent Recommendation

Bean quot

Bean's reference statement First of all, this series of blog refers to the oil pipeSpring Expression LanguageTeaching videoReferencing BeansAnd write. start This episode is a simple look at how to use...

"Java8 actual combat" study notes

table of Contents 1. Function 1.1 Functional interface 1.2 Lambda expression-anonymous function 1.2.1 Description 1.2.2 Lambda expression has three parts 1.2.3 Predicate 1.2.4 Use cases 1.2.5 Commonly...

Java8 actual combat reading notes

lambda composition Example Commonly used functional interface Examples of Lambdas and functional interfaces flow Drawing 2. Intermediate operation 3. Construction stream Create flow by value Stream.of...

Java8 stream use and actual combat

1. Quotes Before understanding Stream, let's take a look at a demand: Known the employee information of a company to obtain employees who are older than 30 years old in the current company. First, we ...

No configuration files have been created. To create a new profile, use the &amp;quot;Mail&amp;quot; icon in the &amp;quot;Control Panel&amp;quot;

Right click on the desktop_Send to _Mail recipient appears "No configuration file created. To create a new configuration file, please use the "Mail" icon in the "Control Panel"...

More Recommendation

Actual combat: use NGINX limited flow

Nginx can not only do web servers, do reverse proxy, load balancing, but also a current limiting system. Here we have an example for Nginx, introduce how to configure a current limiting system. The cu...

Unable to open debugger port (127.0.0.1:13249): java.net.BindException &amp;quot;Address already in use: JVM_Bind&amp;quot;

This problem is a bit simpler. The port of Tomcat is occupied. I used a hot deployment plugin JReble in IDEA. After updating IDEA, I found that the port is occupied. Maybe my computer has not been res...

Java8 actual combat reading note - Chapter 4 introduces flow

4.1 What is the flow? The stream is a new member of the Java API, which allows you to process data sets in a declarative manner (expressed by query statements instead of temporarily writing an impleme...

Geek Time Kafka core technology and actual combat study notes 05

30 | How to reset the consumer group displacement? We know that Kafka and traditional message engines are very different in design. One of the more significant differences is that Kafka consumers can ...

Copyright  DMCA © 2018-2026 - All Rights Reserved - www.programmersought.com  User Notice

Top