关于响应式编程ReactiveX,RxGo

ReactiveX,简称为 Rx,是一个异步编程的 API。与 callback(回调)、promise(JS 提供这种方式)和 deferred(Python 的 twisted 网络编程库就是使用这种方式)这些异步编程方式有所不同,Rx 是基于事件流的。这里的事件可以是系统中产生或变化的任何东西,在代码中我们一般用对象表示。在 Rx 中,事件流被称为 Observable(可观察的,被观察者)。事件流需要被 Observer(观察者)处理才有意义。

ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。它是一套API,针对不同的编程语言会有不同的实现,比如 RxJS, RxJava, RxGo

ReactiveX官网:https://reactivex.io/

ReactiveX仓库:https://github.com/ReactiveX

RxGo 是 Rx 的 Go 语言实现。借助于 Go 语言简洁的语法和强大的并发支持(goroutine、channel),Rx 与 Go 语言的结合非常完美。

pipelines (官方博客:https://blog.golang.org/pipelines)是 Go 基础的并发编程模型。其中包含,fan-in——多个 goroutine 产生数据,一个goroutine 处理数据,fan-out——一个 goroutine 产生数据,多个 goroutine 处理数据,fan-inout——多个 goroutine 产生数据,多个 goroutine 处理数据。它们都是通过 channel 连接。RxGo 的实现就是基于 pipelines 的理念,并且提供了方便易用的包装和强大的扩展。

通常来说,Go写异步程序很容易,完全可以自己封装实现,而RxGo的封装更加标准化,遵循了 ReactiveX 规范,易于理解。

在这里插入图片描述

RxGo: https://github.com/ReactiveX/RxGo

go get -u github.com/reactivex/rxgo/v2

简单使用
func t1() {
	observable := rxgo.Just(1, 2, 3, 4, 5)()
	ch := observable.Observe()
	for item := range ch {
		fmt.Println(item.V)
	}
}

使用 RxGo 的一般流程如下:

  • 使用相关的 Operator 创建 ObservableOperator 就是用来创建 Observable 的。这些术语都比较难贴切地翻译,而且英文也很好懂,就不强行翻译了;
  • 中间各个阶段可以使用过滤操作筛选出我们想要的数据,使用转换操作对数据进行转换;
  • 调用 ObservableObserve()方法,该方法返回一个<- chan rxgo.Item。然后for range遍历即可。

实际上rxgo.Item还可以包含错误。所以在使用时,我们应该做一层判断

func t2() {
	observable := rxgo.Just(1, 2, errors.New("unknown"), 4, 5)()
	ch := observable.Observe()
	for item := range ch {
		if item.Error() {
			fmt.Println("Error:", item.E)
		} else {
			fmt.Println(item.V)
		}
	}
}

除了使用for range之外,我们还可以调用 ObservableForEach()方法来实现遍历。ForEach()接受 3 个回调函数:

  • NextFunc:类型为func (v interface {}),处理数据;
  • ErrFunc:类型为func (err error),处理错误;
  • CompletedFunc:类型为func ()Observable 完成时调用。
func t3() {
	observable := rxgo.Just(1, 2, 3, 4, 5)()
	<-observable.ForEach(func(v interface{}) {
		fmt.Println("onNext:", v)
	}, func(err error) {
		fmt.Println("onError:", err)
	}, func() {
		fmt.Println("onComplete")
	})
}
onNext: 1
onNext: 2
onNext: 3
onNext: 4
onNext: 5
onComplete

ForEach()实际上是在 goroutine 里执行的,它返回一个接收通知的 channel。当 Observable 数据发送完毕时,该 channel 会关闭。所以如果要等待ForEach()执行完成,我们需要使用<-。上面的示例中如果去掉<-,可能就没有输出了,因为主 goroutine 结束了,整个程序就退出了。

创建 Observable

上面使用最简单的方式创建 Observable:直接调用Just()方法传入一系列数据。下面再介绍几种创建 Observable 的方式。

Create

传入一个[]rxgo.Producer的切片,其中rxgo.Producer的类型为func(ctx context.Context, next chan<- Item)。我们可以在代码中调用rxgo.Of(value)生成数据,rxgo.Error(err)生成错误,然后发送到next通道中:

func t4() {
	observable := rxgo.Create([]rxgo.Producer{
		func(ctx context.Context, next chan<- rxgo.Item) {
			next <- rxgo.Of(1)
		},
		func(ctx context.Context, next chan<- rxgo.Item) {
			next <- rxgo.Of(2)
			next <- rxgo.Error(errors.New("unknown"))
			next <- rxgo.Of(4)
			next <- rxgo.Of(5)
		},
	})
	<-observable.ForEach(func(v interface{}) {
		fmt.Println("onNext:", v)
	}, func(err error) {
		fmt.Println("onError:", err)
	}, func() {
		fmt.Println("onComplete")
	})
}
FromChannel

直接从一个已存在的<-chan rxgo.Item对象中创建 Observable

func t5() {
	ch := make(chan rxgo.Item) // no buffer
	go func() {
		for i := 1; i <= 5; i++ {
			ch <- rxgo.Of(i)
		}
		close(ch)
	}()

	observable := rxgo.FromChannel(ch)
	<-observable.ForEach(func(v interface{}) {
		fmt.Println("onNext:", v)
	}, func(err error) {
		fmt.Println("onError:", err)
	}, func() {
		fmt.Println("onComplete")
	})
}

注意:通道需要手动调用close()关闭,上面Create()方法内部rxgo自动帮我们执行了这个步骤。

Interval

以传入的时间间隔生成一个无穷的数字序列,从 0 开始

func t6() {
	observable := rxgo.Interval(rxgo.WithDuration(3 * time.Second))
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Range

生成一个范围内的数字,达到最大值就结束,不包含右值

func t7() {
	observable := rxgo.Range(0, 5)
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Repeat

每隔指定时间,重复一次该序列,一共重复指定次数:

func t8() {
	observable := rxgo.Just(1, 2, 3)()
	// 每隔指定时间,重复一次该序列,一共重复指定次数
	observable = observable.Repeat(5, rxgo.WithDuration(5*time.Second))
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Start

可以给Start方法传入[]rxgo.Supplier作为参数,它可以包含任意数量的rxgo.Supplier类型。rxgo.Supplier的底层类型为 func(ctx context.Context) Item

func t9() {
	observable := rxgo.Start([]rxgo.Supplier{
		func(ctx context.Context) rxgo.Item {
			return rxgo.Of(1)
		},
		func(ctx context.Context) rxgo.Item {
			return rxgo.Of(2)
		},
		func(ctx context.Context) rxgo.Item {
			return rxgo.Of(3)
		},
	})
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Observable 分类

根据数据在何处生成,Observable 被分为 HotCold 两种类型(类比热启动和冷启动)。数据在其它地方生成的被成为 Hot Observable。相反,在 Observable 内部生成数据的就是 Cold Observable

使用上面介绍的方法创建的实际上都是 Hot Observable

ch := make(chan rxgo.Item)
go func() {
    for i := 0; i < 3; i++ {
        ch <- rxgo.Of(i)
    }
    close(ch)
}()

observable := rxgo.FromChannel(ch)

for item := range observable.Observe() {
    fmt.Println(item.V)
}

for item := range observable.Observe() {
    fmt.Println(item.V)
}

上面创建的是 Hot Observable。但是有个问题,第一次Observe()消耗了所有的数据,第二个就没有数据输出了。

Cold Observable 就不会有这个问题,因为它创建的流是独立于每个观察者的。即每次调用Observe()都创建一个新的 channel。我们使用Defer()方法创建 Cold Observable,它的参数与Create()方法一样。

Defer
func t10() {
	// Defer does not create the Observable until the observer subscribes,
	// and creates a fresh Observable for each observer.
	//
	// Cold Observable: 也就是在 subscribe 的时候才去生产数据流;
	// 与之相反的是 Hot Observable,也就是在创建 Observable 的时候就同时创建了数据流,
	// 这样第一次 subscribe 的时候就把数据消耗完了,再次 subscribe 是没有数据的,前面的
	// 例子都是 Hot Observable.
	observable := rxgo.Defer([]rxgo.Producer{func(_ context.Context, ch chan<- rxgo.Item) {
		for i := 0; i < 3; i++ {
			ch <- rxgo.Of(i)
		}
	}})

	// 有数据
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
	// 有数据
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
0
1
2
0
1
2
可连接的 Observable

可连接的(Connectable)Observable 对普通的 Observable 进行了一层组装。调用它的Observe()方法时并不会立刻产生数据。使用它,我们可以等所有的观察者都准备就绪了(即调用了Observe()方法)之后,再调用其Connect()方法开始生成数据。我们通过两个示例比较使用普通的 Observable 和可连接的 Observable 有何不同。

普通的:

func t11() {
	ch := make(chan rxgo.Item)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- rxgo.Of(i)
		}
		close(ch)
	}()

	// 普通的 Observable,只要有一个观察者注册成功就会释放数据
	observable := rxgo.FromChannel(ch)

	// 注册观察者1
	observable.DoOnNext(func(i interface{}) {
		fmt.Printf("First observer: %d\n", i)
	})

	time.Sleep(3 * time.Second)
	fmt.Println("before subscribe second observer")

	// 注册观察者2
	observable.DoOnNext(func(i interface{}) {
		fmt.Printf("Second observer: %d\n", i)
	})

	time.Sleep(3 * time.Second)
}

上例中我们使用DoOnNext()方法来注册观察者。由于DoOnNext()方法是异步执行的,所以为了等待结果输出,在最后增加了一行time.Sleep。运行:

First observer: 1
First observer: 2
First observer: 3
before subscribe second observer

由输出可以看出,注册第一个观察者之后就开始产生数据了。

我们通过在创建 Observable 的方法中指定rxgo.WithPublishStrategy()选项就可以创建可连接的 Observable

func t12() {
	ch := make(chan rxgo.Item)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- rxgo.Of(i)
		}
		close(ch)
	}()

	// 可连接的 Observable
	observable := rxgo.FromChannel(ch, rxgo.WithPublishStrategy())

	// 注册观察者1
	observable.DoOnNext(func(i interface{}) {
		fmt.Printf("First observer: %d\n", i)
	})

	time.Sleep(3 * time.Second)
	fmt.Println("before subscribe second observer")

	// 注册观察者2
	observable.DoOnNext(func(i interface{}) {
		fmt.Printf("Second observer: %d\n", i)
	})

	// 通知 Observable,意味着所有的观察者已经注册完毕,才开始释放数据
	// 另外,可连接的 Observable 是 Cold Observable,即每个观察者都会收到一份相同的拷贝。
	observable.Connect(context.Background())

	time.Sleep(5 * time.Second)

	fmt.Println("over.")
}
before subscribe second observer
Second observer: 1
First observer: 1
First observer: 2
First observer: 3
Second observer: 2
Second observer: 3
over.

上面是等两个观察者都注册之后,并且手动调用了 Observable 的Connect()方法才产生数据。而且可连接的 Observable 有一个特性:它是 Cold Observable !!!,即每个观察者都会收到一份相同的拷贝。

其他常用操作符
Map

转换操作符,Map()方法简单修改它收到的rxgo.Item然后发送到下一个阶段(转换或过滤)。Map()接受一个类型为func (context.Context, interface{}) (interface{}, error)的函数。第二个参数就是rxgo.Item中的数据,返回转换后的数据。如果出错,则返回错误。

func t13() {
	observable := rxgo.Just(1, 2, 3)()
	// Map 转换或者过滤
	// 如果出现一个 error,整个数据流都是无效的
	observable = observable.Map(func(ctx context.Context, v interface{}) (interface{}, error) 	  {
		// vv := v.(int)
		// if vv%2 == 0 {
		// 	return vv * 2, nil
		// } else {
		// 	return vv, errors.New("Error")
		// }

		return v.(int) * 2, nil
	}).Map(func(ctx context.Context, v interface{}) (interface{}, error) {
		return v.(int) + 1, nil
	})

	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Marshal

Marshal对经过它的数据进行一次Marshal。这个Marshal可以是json.Marshal/proto.Marshal,甚至我们自己写的Marshal函数。它接受一个类型为func(interface{}) ([]byte, error)的函数用于对数据进行处理。

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func t14() {
	observable := rxgo.Just(
		User{
			Name: "dj",
			Age:  18,
		},
		User{
			Name: "jw",
			Age:  20,
		},
	)()

	observable = observable.Marshal(json.Marshal)

	for item := range observable.Observe() {
		fmt.Println(string(item.V.([]byte)))
	}
}
Unmarshal
func t15() {
	observable := rxgo.Just(
		`{"name":"dj","age":18}`,
		`{"name":"jw","age":20}`,
	)()

	observable = observable.Map(func(_ context.Context, i interface{}) (interface{}, error) {
		return []byte(i.(string)), nil
	}).Unmarshal(json.Unmarshal, func() interface{} {
		return &User{}
	})

	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Buffer

Buffer按照一定的规则收集接收到的数据,然后一次性发送出去(作为切片),而不是收到一个发送一个。有 3 种类型的Buffer

  • BufferWithCount(n):每收到n个数据发送一次,最后一次可能少于n个;
  • BufferWithTime(n):发送在一个时间间隔n内收到的数据;
  • BufferWithTimeOrCount(d, n):收到n个数据,或经过d时间间隔,发送当前收到的数据。
func t16() {
	observable := rxgo.Just(1, 2, 3, 4)().BufferWithCount(3)
	for item := range observable.Observe() {
		fmt.Println(item.V) // item.V 此时是切片类型
	}
}
[1 2 3]
[4]
GroupBy

GroupBy根据传入一个 Hash 函数,为每个不同的结果分别创建新的 Observable。换句话说,GroupBy生成一个数据类型为 ObservableObservable

func t17() {
	count := 3

	observable := rxgo.Range(0, 10).GroupBy(count, func(item rxgo.Item) int {
		return item.V.(int) % count
	}, rxgo.WithBufferedChannel(10))

	for subObservable := range observable.Observe() {
		fmt.Println("New observable:")

		for item := range subObservable.V.(rxgo.Observable).Observe() {
			fmt.Printf("item: %v\n", item.V)
		}
	}
}
New observable:
item: 0
item: 3
item: 6
item: 9
New observable:
item: 1
item: 4
item: 7
New observable:
item: 2
item: 5
item: 8

注意rxgo.WithBufferedChannel(10)的使用,由于我们的数字是连续生成的,依次为 0->1->2->…->9->10。而 Observable 默认是惰性的,即由Observe()驱动。内层的Observe()在返回一个 0 之后就等待下一个数,但是下一个数 1 不在此 Observable 中。所以会陷入死锁。使用rxgo.WithBufferedChannel(10),设置它们之间的连接 channel 缓冲区大小为 10,这样即使我们未取出 channel 里面的数字,上游还是能发送数字进来。

并行操作

默认情况下,这些转换操作都是串行的,即只有一个 goroutine 负责执行转换函数。我们也可以使用rxgo.WithPool(n)选项设置运行n个 goroutine,或者rxgo.WitCPUPool()选项设置运行与逻辑 CPU 数量相等的 goroutine。

func t18() {
	observable := rxgo.Range(1, 20)

	observable = observable.Map(func(_ context.Context, i interface{}) (interface{}, error) {
		time.Sleep(time.Duration(rand.Int31()))
		return i.(int)*2 + 1, nil
	}, rxgo.WithCPUPool())

	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}

由于是并行,所以输出顺序就不确定了。为了让不确定性更明显一点,我在代码中加了一行time.Sleep

Filter

Filter()接受一个类型为func (i interface{}) bool的参数,通过的数据使用这个函数断言,返回true的将发送给下一个阶段。否则,丢弃。

func t19() {
	observable := rxgo.Range(1, 10)

	observable = observable.Filter(func(i interface{}) bool {
		return i.(int)%2 == 0
	})

	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
ElementAt

ElementAt()只发送指定索引的数据,如ElementAt(2)只发送索引为 2 的数据,即第 3 个数据。

func t20() {
	observable := rxgo.Just(0, 1, 2, 3, 4)().ElementAt(2)

	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Debounce

Debounce()比较有意思,它收到数据后还会等待指定的时间间隔,后续间隔内没有收到其他数据才会发送刚开始的数据。

func t21() {
	ch := make(chan rxgo.Item)

	go func() {
		ch <- rxgo.Of(1)
		time.Sleep(2 * time.Second)
		ch <- rxgo.Of(2)
		ch <- rxgo.Of(3)
		time.Sleep(2 * time.Second)
		close(ch)
	}()

	observable := rxgo.FromChannel(ch).Debounce(rxgo.WithDuration(1 * time.Second))
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}

上面示例,先收到 1,然后 2s 内没收到数据,所以发送 1。接着收到了数据 2,由于马上又收到了 3,所以 2 不会发送。收到 3 之后 2s 内没有收到数据,发送了 3。所以最后输出为 1,3。

Distinct

Distinct()会记录它发送的所有数据,它不会发送重复的数据。由于数据格式多样,Distinct()要求我们提供一个函数,根据原数据返回一个唯一标识码(有点类似哈希值)。基于这个标识码去重。

func t22() {
	observable := rxgo.Just(1, 2, 2, 3, 3, 4, 4)().
		Distinct(func(_ context.Context, i interface{}) (interface{}, error) {
			return i, nil
		})
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Skip

跳过前面若干个数据

func t23() {
	observable := rxgo.Just(1, 2, 3, 4, 5)().Skip(2)
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
Take

只取前面若干个数据

func t24() {
	observable := rxgo.Just(1, 2, 3, 4, 5)().Take(2)
	for item := range observable.Observe() {
		fmt.Println(item.V)
	}
}
选项 rxgo.Option

rxgo 提供的大部分方法的最后一个参数是一个可变长的选项类型。这是 Go 中特有的、经典的选项设计模式。我们前面已经使用了:

  • rxgo.WithBufferedChannel(10):设置 channel 的缓存大小;
  • rxgo.WithPool(n) / rxgo.WithCpuPool():使用多个 goroutine 执行转换操作;
  • rxgo.WithPublishStrategy():使用发布策略,即创建可连接的 Observable

除此之外,rxgo 还提供了很多其他选项。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/155339.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSDN每日一题学习训练——Python版(简化路径,不同的二叉搜索树)

版本说明 当前版本号[20231116]。 版本修改说明20231116初版 目录 文章目录 版本说明目录简化路径题目解题思路代码思路参考代码 不同的二叉搜索树题目解题思路代码思路参考代码 简化路径 题目 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路…

数据结构刷题

空间复杂度&#xff1a;临时开辟的空间、空间是可以重复利用的 递归为O(n) 时间复杂度&#xff1a;程序执行次数 消失的数字 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 思路1&#xff1a;利用连续的特点求等差和然后减去所有元素得到的就是消…

【AI视野·今日Robot 机器人论文速览 第六十三期】Thu, 26 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Fri, 27 Oct 2023 Totally 27 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers 6-DoF Stability Field via Diffusion Models Authors Takuma Yoneda, Tianchong Jiang, Gregory Shakhnarovich, Matthew R. …

JVM虚拟机:垃圾回收器ZGC和Shenandoah算法

随着计算机技术的不断发展,内存管理成为了一个重要的话题。垃圾回收是一种自动内存管理技术,它可以自动地回收不再使用的内存,从而减少内存泄漏和程序崩溃的风险。在Java等高级编程语言中,垃圾回收器是必不可少的组件。近年来,ZGC和Shenandoah算法作为新一代的垃圾回收器,…

C++ 基础二

文章目录 四、流程控制语句4.1 选择结构4.1.1 if语句 4.1.2 三目运算符4.1.3 switch语句注意事项 4.1.4 if和switch的区别【CHAT】4.2 循环结构4.2.1 while循环语句4.2.2 do...while循环语句 4.2.3 for循环语句九九乘法表 4.3 跳转语句4.3.1 break语句4.3.2 continue语句4.3.3 …

扩散模型实战(九):使用CLIP模型引导和控制扩散模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 扩散模型实战&#xff08;四&#xff…

Dubbo协议详解

前言特点应用场景Dubbo协议示例Dubbo协议的不足拓展 前言 Dubbo协议是一种高性能、轻量级的开源RPC框架&#xff0c;主要设计目的是解决分布式系统中服务调用的一些常见问题&#xff0c;例如服务负载均衡、服务注册中心、服务的远程调用等。它支持多种语言&#xff0c;例如Jav…

LeetCode(26)判断子序列【双指针】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 判断子序列 1.题目 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;…

centos搭建docker镜像Harbor仓库的简明方法

在kubernetes集群中如果要部署springcloud这样的应用&#xff0c;就必须有一个自建的docker镜像中心仓库。 它的目的有两点&#xff1a; 1. 镜像拉取速度快 2. 开发好维护 而Harbor是一个非常好用的docker本地仓库 所以本篇文章来讲讲如何在部署Harbor仓库 首先系统版本最…

此芯科技加入绿色计算产业联盟,参编绿色计算产业发展白皮书

近日&#xff0c;此芯科技正式加入绿色计算产业联盟&#xff08;Green Computing Consortium&#xff0c;简称GCC&#xff09;&#xff0c;以Arm架构通用智能CPU芯片及高能效的Arm PC计算解决方案加速构建软硬协同的绿色计算生态体系&#xff0c;推动绿色计算产业加速发展。 继…

Linux_系统信息_uname查看内核版本、内核建立时间、处理器类型、顺便得到操作系统位数等

1、uname --help 使用uname --help查看uname命令的帮助信息 2、uname -a 通过上面的help就知道-a选项显示全部内容时的含义了。 内核名是Linux主机名是lubancat&#xff0c;如果想看主机名可以使用命令hostname&#xff1b;内核版本是Linux 4.19.232&#xff0c;建立时间为2…

全栈工程师必须要掌握的前端Html技能

作为一名全栈工程师&#xff0c;在日常的工作中&#xff0c;可能更侧重于后端开发&#xff0c;如&#xff1a;C#&#xff0c;Java&#xff0c;SQL &#xff0c;Python等&#xff0c;对前端的知识则不太精通。在一些比较完善的公司或者项目中&#xff0c;一般会搭配前端工程师&a…

【二分法】

二分法可以在有序排列中&#xff0c;通过不断对半切割数据&#xff0c;提高数据查找效率。 lst [1,4,6,7,45,66,345,767,788,999] n 66 left 0 right len(lst)-1 while left < right: #边界&#xff0c;当右边比左边还小的时候退出循环 mid (left right)//2 …

JVM虚拟机-虚拟机执行子系统-第6章 类文件结构

各种不同平台的Java虚拟机&#xff0c;以及所有平台都统一支持的程序存储格式——字节码&#xff08;Byte Code&#xff09;是构成平台无关性的基石 Class类文件的结构 字节码指令&#xff1a;操作码 操作数 任何一个Class文件都对应着唯一的一个类或接口的定义信息 Class文件…

C/C++输出整数部分 2021年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C输出整数部分 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C输出整数部分 2021年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 输入一个双精度浮点数f&#xff0c; 输出其整…

C++打怪升级(十一)- STL之list

~~~~ 前言1. list是什么2. list接口函数的使用1. 构造相关默认构造n个val构造迭代器范围构造拷贝构造 2 赋值运算符重载函数2 析构函数3 迭代器相关begin 和 endrbegin 和rend 4 容量相关emptysize 5 元素访问相关frontback 6 修改相关push_backpop_backpush_frontpop_frontins…

【OJ比赛日历】快周末了,不来一场比赛吗? #11.18-11.24 #15场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-11-18&#xff08;周六&#xff09; #5场比赛2023-11-19…

受电诱骗快充取电芯片XSP08:PD+QC+华为+三星多种协议9V12V15V20V

目前市面上很多家的快充充电器&#xff0c;都有自己的私有快充协议&#xff0c;如PD协议、QC协议、华为快充协议、三星快充协议、OPPO快充协议等待&#xff0c;为了让它们都能输出快充电压&#xff0c;就需要在受电端也增加快充协议取电芯片XSP08&#xff0c;它可以和充电器通讯…

我记不住的getopt_long的那些参数和返回值

前言&#xff1a;最近在学习面向Linux系统进行C语言的编程&#xff0c;通过查询man手册和查看网络上的各种文章来获取一点点的知识&#xff0c;重点是看完手册还是一脸懵逼&#xff0c;搞不懂手册里面再说啥&#xff0c;而本篇文章将记录一下学习getopt_long的那些参数和返回值…

IDEA无法查看源码是.class,而不是.java解决方案?

问题&#xff1a;在idea中&#xff0c;ctrl鼠标左键进入源码&#xff0c;但是有时候会出现无法查看反编译的源码&#xff0c;如图&#xff01; 而我们需要的是方法1: mvn dependency:resolve -Dclassifiersources 注意&#xff1a;需要该模块的目录下&#xff0c;不是该文件目…