协程Flow原理

什么是Flow

Flow直译过来就是“流”的意思,也就是将我们我们任务如同水流一样一步一步分割做处理。想象一下,现在有一个任务需要从山里取水来用你需要怎么做?

  • 扛上扁担走几十里山路把水挑回来。简单粗暴,但是有可能当你走了几十里路发现水干涸了,你就白跑一趟。
  • 架设一条从家到水源之间的水管,以后就可以在家打开水管就有水喝了。如果打开水龙头没水了,那就知道水池没水了,也不用白跑一趟。

这其实就延伸到了目前的两种主流编程思想,响应式和非响应式。

所谓的响应式就是通过订阅、监听的方式等待数据源把数据传递过来。他的优势是可以很方便的与数据源逻辑解耦,并且在数据源可以传递过来之前随意的改变数据流。比如上边例子,我想喝柠檬味的水,那我完全可以在我水龙头上接个柠檬味的管子。而像传统的方案就必须要往水源地加入柠檬精,这时候如果另一个人他想喝西瓜味的水,那就实现不了了。

为什么是Flow

在Flow之前,RXjava一直是我们常用的流式写法,那相比较RXjava,Flow的优势是什么呢?ChatGpt认为有以下几点:

  1. 更加轻量级:Flow 是 Kotlin 标准库的一部分,无需引入额外的依赖库,而 RxJava 需要引入 RxJava 核心库和相关的操作符库,增加了项目的复杂度和体积。
  2. 更加简单:Flow 基于 Kotlin 协程实现,使用协程的语法糖可以实现代码更加简单和易读,比 RxJava 更加简单。
  3. 更加灵活:Flow 支持背压处理和多个订阅者同时订阅同一个数据流,而 RxJava 对于多个订阅者需要使用 share()replay() 等操作符进行处理。
  4. 更加安全:Flow 可以在协程的上下文中运行,因此可以避免 RxJava 中常见的线程安全问题和内存泄漏问题。
  5. 更加可预测:Flow 使用 Kotlin 的类型安全特性,可以更加容易地避免类型不匹配和空指针异常等问题。

其实作为开发人员最关心的就是简洁。越简洁的用法意味着越低廉的学习成本,也更不容易使用出错出现不可以预知的问题。

更多Android开发学习笔记 请点击此处免费领取

传统方案、RXjava、Flow直观对比

我们以一个最常用的例子,子线程网络请求,然后再将数据返回主线程加载UI

传统方案

OkHttpUtils.sendGetRequest("http://example.com", new OkHttpUtils.CallbackListener() {@Overridepublic void onSuccess(String response) {// 在主线程中处理请求成功的结果textView.setText(response);}@Overridepublic void onFailure(IOException e) {// 在主线程中处理请求失败的结果e.printStackTrace();Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();}
});

发起一个网络请求并注册一个监听,当请求结果回来的时候把结果callback回来。如果此时我们需要网络请求成功之后再去请求第二个接口回来,那我们就需要再嵌套一个callback,就像这样:

OkHttpUtils.sendGetRequest("http://example.com", new OkHttpUtils.CallbackListener() {@Overridepublic void onSuccess(String response) {// 在主线程中处理请求成功的结果OkHttpUtils.sendGetRequest("http://example2.com", new OkHttpUtils.CallbackListener() {@Overridepublic void onSuccess(String response) {// 在主线程中处理请求成功的结果textView.setText(response);}@Overridepublic void onFailure(IOException e) {// 在主线程中处理请求失败的结果e.printStackTrace();Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();}});}@Overridepublic void onFailure(IOException e) {// 在主线程中处理请求失败的结果e.printStackTrace();Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();}
});

可以看到,当两层的时候我们就完全陷入了“回调地狱”中,逻辑越来越难直观看清。更为致命的是这个方法也无法扩展了。比如说另一个使用者要在第一次请求成功之后弹出一个气泡,第二个使用者说我不需要。这样就只能加参数写if-else判断了。长此以往这个方法就会迅速膨胀再也无法复用,紧接着这个方法的CV1.0、2.0版本就开始“闪亮登场”,最后这个类也无法继续维护了。

RXjava方案

RxJavaUtils.sendGetRequest("http://example.com").subscribe(new Observer<String>() {@Overridepublic void onSubscribe(Disposable d) {// 在这里可以做一些准备工作,比如显示进度条等}@Overridepublic void onNext(String response) {// 在主线程中处理请求成功的结果textView.setText(response);}@Overridepublic void onError(Throwable e) {// 在主线程中处理请求失败的结果e.printStackTrace();Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT});

这样的写法相比较传统写法确实优雅了一些,当有多个请求操作的时候可以使用 flatMapconcatMap 操作符来实现在一个请求成功之后继续发起另一个请求的功能,就像这样:

RxJavaUtils.sendGetRequest("http://example.com").flatMap(response -> RxJavaUtils.sendGetRequest("http://example2.com")).subscribe(new Observer<String>() {@Overridepublic void onSubscribe(Disposable d) {// 在这里可以做一些准备工作,比如显示进度条等}@Overridepublic void onNext(String response) {// 在主线程中处理请求成功的结果textView.setText(response);}@Overridepublic void onError(Throwable e) {// 在主线程中处理请求失败的结果e.printStackTrace();Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();}@Overridepublic void onComplete() {// 在请求完成时进行一些收尾工作,比如隐藏进度条等}});

这样看起来我们很好的规避了‘回调地狱’的问题,逻辑看起来也非常的清晰。当其他使用者需要扩展这个方法的时候,只需要拿着原始的sendGetRequest方法自己通过rx操作符拼接自己的业务逻辑就可以了。

Flow方案

lifecycleScope.launch {FlowUtils.sendGetRequest("http://example.com").collect { response ->// 在主线程中处理请求结果textView.text = response}
}

就是这么简单,对于Flow的创建也是比较简单的,使用flow函数一包装,我们就得到了任务流。

import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOExceptionobject FlowUtils {private const val TAG = "FlowUtils"private val client = OkHttpClient()fun sendGetRequest(url: String): Flow<String> = flow {val request = Request.Builder().url(url).build()val call = client.newCall(request)call.enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {Log.e(TAG, "Request failed: ${e.message}")// 使用 Flow 的 emit 方法将异常抛出emit(e.message ?: "Unknown error")}override fun onResponse(call: Call, response: Response) {val responseBody = response.body()?.string()if (response.isSuccessful && responseBody != null) {// 使用 Flow 的 emit 方法将请求成功的结果抛出emit(responseBody)} else {val errorMessage = response.message()Log.e(TAG, "Request failed: $errorMessage")// 使用 Flow 的 emit 方法将异常抛出emit(errorMessage)}}})}.flowOn(Dispatchers.IO)
}

而将两个flow任务拼接起来也是极为简单的

lifecycleScope.launch {FlowUtils.sendGetRequest("http://example.com").flatMapConcat { response ->// 在请求1的结果基础上发起请求2FlowUtils.sendGetRequest("http://example2.com")}.collect { response ->// 在主线程中处理请求2的结果textView.text = response}
}

可以看到除去我们所说的操作符的学习成本,flow在kotlin语法糖的加持下写法更简洁直观。

Flow学习

与RXJava一样,Flow也可以分为三大块部分。

  1. Flow的创建,即创建我们的数据源,我们可以通过flow函数或者其他语法糖开启一个Flow流。
  2. 中间操作符,像flatMapConcatmap这些操作符,中间改变数据流向。
  3. 终止操作符,用来最终接收数据。当执行最终操作符的时候,Flow流就不能再被改变,只能得到结果。

Flow创建

  1. 通过 Flow 构造器创建

    kotlinCopy code
    val flow = flow {// 在这里定义 Flow 的发射过程emit(1)emit(2)emit(3)
    }
    
  2. 通过集合转换成 Flow

    kotlinCopy code
    val flow = listOf(1, 2, 3).asFlow()
    
  3. 通过数组转换成 Flow

    kotlinCopy code
    val flow = arrayOf(1, 2, 3).asFlow()
    
  4. 通过流转换成 Flow

    kotlinCopy code
    val flow = inputStream.bufferedReader().lineSequence().asFlow()
    
  5. 通过 channelFlow 构建

    kotlinCopy code
    val flow = channelFlow {// 在这里定义 Flow 的发射过程send(1)send(2)send(3)
    }
    
  6. 通过 transform 操作符变换一个 Flow

    kotlinCopy code
    val flow = flowOf(1, 2, 3).transform { value ->emit(value * 2)}
    
  7. 通过其他操作符变换一个 Flow

    kotlinCopy code
    val flow = flowOf(1, 2, 3).map { it * 2 }
    
  8. 通过 suspendCancellableCoroutine 构建

    kotlinCopy code
    val flow = flow {val data = suspendCancellableCoroutine<String> { continuation ->// 这里是协程挂起等待数据的逻辑}emit(data)
    }
    

中间操作符

  1. map: 将 Flow 中的每个元素转换为另一个元素。
  2. filter: 根据指定的谓词过滤 Flow 中的元素。
  3. take: 从 Flow 中获取指定数量的元素。
  4. drop: 从 Flow 中删除指定数量的元素。
  5. flatMap: 将 Flow 中的每个元素转换为另一个 Flow,并将所有结果合并为一个 Flow。
  6. zip: 将两个 Flow 中的元素按顺序配对并生成新元素。
  7. reduce: 将 Flow 中的所有元素缩减为一个单独的元素。
  8. scan: 将 Flow 中的所有元素累积为一个可变状态,并发出每个中间状态。
  9. distinctUntilChanged: 仅在 Flow 中出现与上一个不同的元素时才发出元素。
  10. buffer: 将 Flow 的元素缓存在内存中,以便在上游产生元素速度很快时缓解下游的压力。
  11. onEach: 对 Flow 中的每个元素执行指定操作,而不会修改元素。
  12. catch: 处理 Flow 中的错误并将其替换为另一个值或另一个 Flow。

终止操作符

  1. collect():收集Flow流发射的值,并将其处理,直到Flow结束或被取消。
  2. toList():将Flow流发射的所有值存储在List集合中,然后返回该集合。
  3. toSet():将Flow流发射的所有值存储在Set集合中,然后返回该集合。
  4. reduce():将Flow流发射的值进行累加或其他操作,返回一个最终的结果。
  5. fold():与reduce()类似,但是可以提供一个初始值,用于指定操作的起始值。
  6. first():返回Flow流发射的第一个值。
  7. last():返回Flow流发射的最后一个值。
  8. single():返回Flow流发射的单个值,如果Flow流中没有值或有多个值则会抛出异常。
  9. firstOrNull():返回Flow流发射的第一个值,如果没有值则返回null。
  10. lastOrNull():返回Flow流发射的最后一个值,如果没有值则返回null。

Flow背压处理

与RXJava一样,Flow也会遇到背压问题(任何生产者消费者模型都会有这个问题)。Flow提供了专门处理背压的操作符:

  1. buffer 操作符:可以设置一个缓冲区,使生产者和消费者的处理速度可以有一定的错开,从而避免数据积压。例如:flow.buffer().collect{...}
  2. conflate 操作符:可以丢弃掉生产者发送的部分数据,只保留最新的数据,这样可以保证消费者始终处理最新的数据,避免处理过多的数据。例如:flow.conflate().collect{...}

buffer操作符可以设置缓存的大小,比如buffer(2)标识最多缓存两个元素。

我们还可以设置BufferOverflow来约定超过缓存区后的处理逻辑。比如: val channel = producer.buffer(Channel.BUFFERED, onBufferOverflow = BufferOverflow.SUSPEND)

BufferOverflow 背压策略

  • SUSPEND,默认策略,填满后再来的数据流,发送会被挂起,若 bufferSize <= 0,此策略不可更改。
  • DROP_OLDEST,丢弃最旧的值。
  • DROP_LATEST,丢弃最新的值。

Flow原理

我们从一个简单的demo开始

fun test() {  runBlocking(Dispatchers.Main) {  //生产者flow {  println("flow block")  //发射数据emit(1)  emit(2) }.flowOn(Dispatchers.IO).collect {  //消费者println("collect block $it")  }  }  
}

以上代码涉及到三个关键函数(flow、emit、collect),两个闭包(flow闭包、collect闭包。
从上面的调用图可知,以上五者的调用关系:

flow–>collect–>flow闭包–>emit–>collect闭包

接下来我们逐步分析。

flow()

flow函数实现:

public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)

别看这个函数只有一行,其实他所声明的东西还不少。

  • 首先flow()接受一个block参数,这个参数就是我们demo里的flow闭包。
  • 其次这个参数的类型是FlowCollector<T>.() -> Unit,表明他是FlowCollector的一个扩展。
  • 同时这个函数又声明了suspend,也就是说flow闭包是一个挂起函数。
  • 最后这个函数的实际逻辑是返回了一个SafeFlow(),我们的flow闭包作为参数。返回类型是Flow。

我们再看SafeFlow的实现:

private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {  override suspend fun collectSafely(collector: FlowCollector<T>) {  collector.block()  }  
}

SafeFlow的父类就是AbstractFlow,这个block参数就是我们上边所说的flow闭包。他重写了collectSafely()方法,这个方法里调用了collector.block()

collector.block()实际在调用谁?实际调用的是参数里的block,也就是flow闭包的执行。

可以看到flow函数主要作用就是构建flow流,并没有实际启动这个流任务。而实际启动任务流是在调用collect()方法的时候,也就是实际消费的时候。这也就是所谓的冷流

在这里插入图片描述

collect()

我们还是先看源码定义

public interface Flow<out T> {  public suspend fun collect(collector: FlowCollector<T>)  
}

点击demo的collect()方法就进入这里,可以看到Flow其实是一个接口,只有一个方法collect,接收FlowCollector参数。这里的collector就是我们外部传来的collect闭包。

上边我们讲到fllow()方法最终返回的是SafeFlow对象,而SafeFlow的父类就是AbstractFlow。所以我们接着看AbstractFlow的collect方法。

public abstract class AbstractFlow<T> : Flow<T>, CancellableFlow<T> {  public final override suspend fun collect(collector: FlowCollector<T>) {  val safeCollector = SafeCollector(collector, coroutineContext)  try {  collectSafely(safeCollector)  } finally {  safeCollector.releaseIntercepted()  }  }  public abstract suspend fun collectSafely(collector: FlowCollector<T>)  }
}

可以看到我们传递的collector闭包最终又被包装成了SafeCollector,然后调用抽象函数collectSafely。而collectSafely的具体实现则是由子类实现。而AbstractFlow子类又是谁?是SafeFlow。所以最终又走到了SafeFlow的collectSafely方法,而collectSafely方法又触发了flow闭包的执行。此时,消费者通过collect函数已经调用到生产者的闭包里

emit()

上边我们提到Flow在调用collect方法的时候就触发了flow闭包的执行,而在flow闭包怎么把数据传递给collect闭包的呢?对就是通过emit()方法。我们看下emit()方法的实现。

public fun interface FlowCollector<in T> {  public suspend fun emit(value: T)  
}

可以看到又是一个接口FlowCollector。在上边我们分析flow方法的时候看到,flow方法将flow闭包转换为FlowCollector的扩展方法。所以FlowCollector可以理解为我们flow闭包。他有一个emit方法,这也就是为什么我们在flow闭包里可以直接调用emit的原因。

那这个接口具体实现是谁呢?我们上边看AbstractFlow源码的时候提到,flow闭包被包装成了SafeCollector,然后调用collectSafely。在collectSafely最终触发了flow闭包的执行。而flow闭包实现了emit接口。所以最终触发emit的就是SafeCollector。

override suspend fun emit(value: T) {  return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->  try {  emit(uCont, value)  } catch (e: Throwable) {  lastEmissionContext = DownstreamExceptionContext(e, uCont.context) throw e  }  } 
}private fun emit(uCont: Continuation<Unit>, value: T): Any? {  val currentContext = uCont.context  currentContext.ensureActive() val previousContext = lastEmissionContext  if (previousContext !== currentContext) {  checkContext(currentContext, previousContext, value)  lastEmissionContext = currentContext  }  completion = uCont  val result = emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)  if (result != COROUTINE_SUSPENDED) {  completion = null  }  return result  
}private val emitFun = FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>

这样在flow闭包里调用emit函数后,将会调用到collect的闭包里,此时数据从flow的上游流转到下游。

有同学就问了private val emitFun = FlowCollector<Any?>::emit as Function3<FlowCollector<Any?>, Any?, Continuation<Unit>, Any?>就这么一行,怎么理解说调到collect的闭包里的?

说实话刚开始我也不懂,不得不说kotlin的高阶语法真是太难理解了。不过我们换种思路来,通过kotlin转java看下这行代码转换后的样子。

private static final Function3 emitFun = (Function3)TypeIntrinsics.beforeCheckcastToFunctionOfArity(new Function3() {  
// $FF: synthetic method  
// $FF: bridge method  
public Object invoke(Object var1, Object var2, Object var3) {  return this.invoke((FlowCollector)var1, var2, (Continuation)var3);  
}  @Nullable  
public final Object invoke(@NotNull FlowCollector p1, @Nullable Object p2, @NotNull Continuation continuation) {  return p1.emit(p2, continuation);  
}  
}, 3);

这下就简单明了了吧?p1对应的是就是我们的collector,那这个collector又是什么鬼?他是SafeCollector类的参数。

internal actual class SafeCollector<T> actual constructor(@JvmField internal actual val collector: FlowCollector<T>,@JvmField internal actual val collectContext: CoroutineContext  ) : FlowCollector<T>, ContinuationImpl(NoOpContinuation, EmptyCoroutineContext), CoroutineStackFrame{
····
}

而我们往上翻SafeCollector的collector参数不就是AbstractFlow类中collect方法传递进去的collect闭包吗?

中间操作符

最后我们来研究下Flow中间操作符是怎么运作的,我们还是以最关心的flowOn线程切换操作符来说明吧。点进flowOn的源码看下:

public fun <T> Flow<T>.flowOn(context: CoroutineContext): Flow<T> {  checkFlowContext(context)  return when {  context == EmptyCoroutineContext -> this  this is FusibleFlow -> fuse(context = context)  else -> ChannelFlowOperatorImpl(this, context = context)  }  
}

看方法签名,又是给Flow类扩展了一个flowOn方法,最终又返回了一个Flow类。所以可以猜想这个方法其实是做了一层包装。在这里我们的demo逻辑走ChannelFlowOperatorImpl这个分支(为啥?打断点啊!)。

internal class ChannelFlowOperatorImpl<T>(flow: Flow<T>,context: CoroutineContext = EmptyCoroutineContext,capacity: Int =Channel.OPTIONAL_CHANNEL,onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND  ) : ChannelFlowOperator<T, T>(flow, context, capacity, onBufferOverflow) {  override fun create(context: CoroutineContext, capacity: Int,onBufferOverflow: BufferOverflow): ChannelFlow<T> =  ChannelFlowOperatorImpl(flow, context, capacity, onBufferOverflow)  override fun dropChannelOperators(): Flow<T>? = flow  override suspend fun flowCollect(collector: FlowCollector<T>) =  flow.collect(collector)  
}
  • 首先ChannelFlowOperatorImpl的父类实现了Flow接口,所以我们可以认为这里就是flow闭包。
  • 其次create方法创建了ChannelFlowOperatorImpl实体对象。flowCollect方法里最终调用了flow.collect(collector)。也就是我们上边分析的触发消费者执行。
  • 所以我们得出结论,中间操作符其实也是一个小的Flow流,有生产者、消费者。生产者就是上一级传来的流,消费者就是自己。将流改变之后再调用原来的消费者,就是拦截器的原理。

那flowOn的实际处理逻辑在哪呢?上边我们分析了Flow类只有一个collect函数。所以我们找父类看collect函数的实现就行了。

private suspend fun collectWithContextUndispatched(collector: FlowCollector<T>, newContext: CoroutineContext) {  val originalContextCollector = collector.withUndispatchedContextCollector(coroutineContext)  // invoke flowCollect(originalContextCollector) in the newContext  return withContextUndispatched(newContext, block = { flowCollect(it) }, value = originalContextCollector)  
}  // Slow path when output channel is required  
protected override suspend fun collectTo(scope: ProducerScope<T>) =  flowCollect(SendingCollector(scope))  // Optimizations for fast-path when channel creation is optional  
override suspend fun collect(collector: FlowCollector<T>) {  // Fast-path: When channel creation is optional (flowOn/flowWith operators without buffer)  if (capacity == Channel.OPTIONAL_CHANNEL) {  val collectContext = coroutineContext  val newContext = collectContext + context // compute resulting collect context  // #1: 上下文相同 直接调用flowCollect,就是上边看到的子类,也就是执行下一步流转。if (newContext == collectContext)  return flowCollect(collector)  // #2: 检验上下文拦截器是否相同,如果相同就走不用分发的逻辑。if (newContext[ContinuationInterceptor] == collectContext[ContinuationInterceptor])  return collectWithContextUndispatched(collector, newContext)  }  // 最终都不相同走了父类处理。 super.collect(collector)  
}

我们这里没有设置拦截器,只是修改了线程。所以肯定走collectWithContextUndispatched逻辑。

internal suspend fun <T, V> withContextUndispatched(  
newContext: CoroutineContext, value: V, countOrElement: Any = threadContextElements(newContext), block: suspend (V) -> T ): T =  
suspendCoroutineUninterceptedOrReturn { uCont ->  //瞅这个,是不是很亲切?withCoroutineContext(newContext, countOrElement) {  block.startCoroutineUninterceptedOrReturn(value,StackFrameContinuation(uCont,newContext))  }  
}

看到withCoroutineContext这个方法我们就明白了

总结

  • 什么是冷流?什么是热流?
    • 冷流:没有消费者,生产者不会生产数据 没有观察者,被观察者不会发送数据
    • 热流:没有消费者,生产者也会生产数据 没有观察者,被观察者也会发送数据
  • Flow是怎么流转的? 在这里插入图片描述
    直接搬大佬的图。
  • Flow中间操作符是怎么做转换的?
    • 本质也是一个小的Flow流,在collect逻辑中终止上一步的流转,插入自己的逻辑,然后调用下一步流转
  • Flow相比较RXJava有什么优势?
    • 更加轻量级:Flow 是 Kotlin 标准库的一部分,无需引入额外的依赖库,而 RxJava 需要引入 RxJava 核心库和相关的操作符库,增加了项目的复杂度和体积。
    • 更加简单:Flow 基于 Kotlin 协程实现,使用协程的语法糖可以实现代码更加简单和易读,比 RxJava 更加简单。
    • 更加灵活:Flow 支持背压处理和多个订阅者同时订阅同一个数据流,而 RxJava 对于多个订阅者需要使用 share()replay() 等操作符进行处理。
    • 更加安全:Flow 可以在协程的上下文中运行,因此可以避免 RxJava 中常见的线程安全问题和内存泄漏问题。
    • 更加可预测:Flow 使用 Kotlin 的类型安全特性,可以更加容易地避免类型不匹配和空指针异常等问题。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为2023年一季度收入增长0.8%;微软将推私有版ChatGPT;2022年中国自动驾驶市场增速达106%丨每日大事件...

‍ ‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 企业动态 OpenAI完成新一轮融资&#xff0c;估值接近300亿美元 据报道&#xff0c;OpenAI完成3亿美元融资&#xff0c;估值达到270亿-290亿美元。本轮融资参与的风投公司包括老虎全球、红杉资本、加州Andreessen Horowit…

最全ChatGPT创业方向!谁是下个字节跳动?

图片&#xff5c;Photo by Jonathan Kemper on Unsplash ©自象限原创 作者&#xff5c;程心 编辑&#xff5c;云天明 排版&#xff5c;李帛锦 3月24日&#xff0c;OpenAI轻描淡写的宣布了两件大事&#xff1a; 一是ChatGPT联网了。 二是OpenAI开放了第三方插件&…

Flutter ChatGPT | 代码生成器

theme: cyanosis highlight: mono-blue ChatGPT 作为一个自然语言处理工具&#xff0c;已经火了一段时间。对待 ChatGPT 不同人有着不同的看法&#xff0c;新事物的出现必然如此。利益相关者形成 抵制 和 狂热 两极&#xff1b;哗众取宠者蹭蹭热度&#xff0c;问些花活&#xf…

汽车+ChatGPT 车内生活体验再升级

这两年&#xff0c;人工智能工具ChatGPT爆火&#xff0c;在全球掀起了大模型之战。如今&#xff0c;最前沿的自然语言处理大模型应用到了人类的出行工具上&#xff0c;梅赛德斯-奔驰和微软官宣正在合作测试车载ChatGPT人工智能&#xff0c;并将面向约90万车主开启测试&#xff…

chatGPT对SAP各模块顾问需要掌握的技术分析,看看chatGPT对SAP顾问有哪些建议

序言 OpenAI 使用监督学习和强化学习的组合来调优 ChatGPT&#xff0c;其中的强化学习组件使 ChatGPT 独一无二。OpenAI 使用了「人类反馈强化学习」&#xff08;RLHF&#xff09;的训练方法&#xff0c;该方法在训练中使用人类反馈&#xff0c;以最小化无益、失真或偏见的输出…

ChatGPT的强劲对手Claude来了。免费!国内可以丝滑用

大家好&#xff0c;我是雷慢慢&#xff0c;欢迎大家来到AI训练营&#xff0c;那么今天我想给大家演示的是一个ChatGPT的强劲对手&#xff0c;它的名字叫Claude。 这个产品跟ChatGPT极为类似&#xff0c;因为Claude的创始团队成员之前也在OpenAI工作&#xff0c;他们是两兄妹&am…

ChatGPT的前世今生(2023)

本篇分享的是464页幻灯片《ChatGPT 的前世今生》的PDF版本。ChatGPT的历史可以追溯到2018年&#xff0c;当时OpenAI推出了第一个GPT模型。随着技术的不断进步&#xff0c;GPT-2在2019年推出&#xff0c;它拥有更强的语言生成能力和多任务学习能力。随后&#xff0c;OpenAI推出了…

自己实现 ChatGpt ?先学习 Pytorch 吧

最近 ChatGpt 的爆火&#xff0c;让人非常震撼&#xff0c;无论是知识问答、对话还是代码撰写&#xff0c;都非常符合人们的预期&#xff0c;让人不得不感慨机器学习的强大。不信&#xff1f;看下面&#xff1a; 图1 语言分析处理 图2 知识问答 图3 写故事 图4 写代码 体…

怎样让ChatGPT在其内部训练神经网络?

怎样让ChatGPT在其内部训练神经网络&#xff1f;这个话题有点超乎大多数人的理解。 步骤是这样的: 1. 先让它伪装成 Ubuntu 18.04&#xff0c;给它说你安装了 Python 3.9, Pytorch 1.8, CUDA 11.3 和其他训练一个 pytorch 模型所需要的库。 让 ChatGPT 伪装成 Linux 终端&#…

【饭谈】大佬们已经联名叫停了ChatGpt的继续进化,据说已经出现不可理解逻辑。

前几天我刚刚发文章&#xff0c;劝大家不要因为gpt的出现太过焦虑&#xff1a; 【饭谈-缓解焦虑】浅谈下目前AI【ChatGpt】现状和测试行业未来预测 说世界顶级大佬肯定会出手制裁这个野蛮成长的新神&#xff0c;这不&#xff0c;马上就有新闻验证了这个猜测&#xff1a; 昨天的…

CHATGPT+WECHAT 国内环境 学习体验版

1.注意事项 本文描述为学习学术研究&#xff0c;不商用&#xff0c;魔法工具请自行解决。不提供任何魔法工具的说明与教程&#xff1b; 教程说明的为学习环境&#xff0c;不建议部署至国内VPS&#xff0c;会有封号风险&#xff0c;有条件的可在国外vps 进行部署&#xff1b; …

ChatGPT的跳跃式技术突破!

ChatGPT自从发布以来&#xff0c;一直热度不减&#xff0c;围绕它的话题也是持续不断。 而ChatGPT 之所以引起这么大的社会反响&#xff0c;不仅仅是因为它作为一个对话机器人&#xff0c;效果比之前的对话机器人好了一大截&#xff0c;还因为它从技术的角度来看&#xff0c;出…

ChatGPT , 王炸!!!

昨天我们的ChatGPT星球 实在是太火爆了&#xff0c; 原本以为加入的兄弟不会太多&#xff0c; 我就只设置了50张优惠券&#xff0c; 没想到不到1个小时又被抢完了。 欢迎你加入我们的「ChatGPT编程圈」&#xff0c;带着大家一起探索ChatGPT和新的AI时代。 ChatGPT 编程圈&#…

ChatGPT的语言艺术

介绍 本文用于了解和利用各种提示技术&#xff0c;从ChatGPT生成高质量的答案。 我们将探讨如何利用不同的提示工程技术来实现不同的目标。ChatGPT是一种最先进的语言模型&#xff0c;能够生成类似人类的文本。然而&#xff0c;了解向ChatGPT提问的正确方式&#xff0c;以获得我…

浅尝基于 GPT3 模型的 IDE: Cursor 用法全解读

目录 一、安装介绍二、测试功能2.1 常规类编程任务2.2 爬虫 三、更多推荐 一、安装介绍 Cursor 是一个基于人工智能技术的代码生成器&#xff0c;它可以根据程序员输入的代码上下文和要实现的功能&#xff0c;自动生成相应的代码。支持多平台 Mac / Windows / Linux&#xff0…

向ChatGPT询问的艺术

向ChatGPT询问的艺术 本文是对 Ibrahim John 的书籍《THE ART OF ASKING CHATGPT FOR HIGH QUALITY ANSWERS》的中文翻译&#xff0c;主要目的是练习我的英文阅读能力顺便学习一下 chatgpt 相关的一些提问技巧。 在翻译的过程中&#xff0c;原文没有给出具体的示例&#xff0c;…

GitHub-3KStar吴恩达ChatGPT课程最新中文版Prompt+ChatGPT API+LangChain——面向开发者的 LLM 入门课程开源,小白也可学

目录 导言面向开发者的 LLM 入门课程项目简介项目意义项目受众项目亮点内容大纲一、面向开发者的 Prompt Engineering二、搭建基于 ChatGPT 的问答系统三、使用 LangChain 开发应用程序四、Prompt 高级技巧&#xff08;暂未完成&#xff09;配套视频 致谢 参考资料其它资料下载…

吴恩达教你如何玩转ChatGPT,限时免费!

克雷西 发自 凹非寺量子位 | 公众号 QbitAI ChatGPT催生新职业提示工程师&#xff0c;年薪可高达几十万美元。‍‍ 但是&#xff0c;该怎么入门&#xff1f; 吴恩达面向广大开发者推出ChatGPT提示工程课程&#xff0c;与OpenAI合作出品。 限时免费&#xff0c;而且对新手友好&a…

微软计划在未来几周内使用更快版本的 ChatGPT 更新 Bing

根据 Semafor 的一份新报告,微软正在努力在未来几周内将 OpenAI 的 ChatGPT 的更快版本(称为 GPT-4)整合到 Bing 中,此举将使搜索引擎与谷歌更具竞争力。集成将使必应使用 GPT-4 来回答搜索查询。 知情人士告诉 Semafor,ChatGPT 和 GPT-4 的主要区别在于速度。尽管 ChatG…