当前位置:首页 > 后端开发 > 正文内容

【Kotlin】协程

邻居的猫1个月前 (12-09)后端开发1943

1 前语

​ 相较于 C# 中的协程(详见 → 【Unity3D】协同程序),Kotlin 中协程更灵敏,难度更大。

​ 协程是一种并发规划形式,用于简化异步编程,它答应以次序化的方法表达异步操作,防止回调阴间等问题。运用协程,能够将异步操作的代码像同步代码相同写,而无需显式地办理线程。

​ 在 Kotlin 中,协程由 kotlinx.coroutines 库供给支撑。它运用 suspend 修饰符来符号挂起函数(即可暂停履行并稍后康复履行的函数),这使得编写异步代码愈加直观和简略。

​ 协程和线程具有以下异同点。

1)并发模型

  • 线程:线程是操作系统供给的履行单位,一个进程能够具有多个线程,线程之间相对独立,数据同享需求经过特别手法(如锁)保证安全。
  • 协程:协程是一种用户态的轻量级线程,由开发者操控其运转与暂停,能够在同一线程上并发履行,经过挂起和康复的方法,完结非堵塞的并发。

2)资源耗费

  • 线程:每个线程都需求分配必定的内存和系统资源,线程切换时会有必定的开支。
  • 协程:协程是用户级的,由协程调度器(Coroutine Dispatcher)调度,通常会复用较少的系统资源,因而更轻量级。

3)编程模型

  • 线程:多线程编程通常以同享状况和锁为根底,编写并发代码较为杂乱。
  • 协程:协程供给了一种结构化并发编程的方法,经过挂起函数的调用完结代码的暂停和康复,使得异步编程更易于了解和保护。

4)过错处理

  • 线程:多线程编程中,过错处理相对困难,需求开发者手动处理反常和线程间的通讯。
  • 协程:协程供给了愈加简略和共同的过错处理方法,经过结构化的反常处理机制,能够轻松处理协程中的反常。

5)功能

  • 线程:创立和办理线程或许会带来较大的开支,尤其是在很多线程一起运转时,线程切换的开支也会比较高。
  • 协程:协程由所以轻量级的用户级线程,资源耗费较少,因而在大规模并发场景下或许体现更优。

​ 总的来说,协程比较于传统的线程模型,愈加灵敏、轻量级,而且供给了愈加简略和结构化的并发编程方法,使得异步编程愈加简单和高雅。

2 协程相关类图

img

3 协程源码

3.1 协程效果域源码(CoroutinueScope)

​ 协程的效果域界说了协程的效果域规模,当该效果域被毁掉时,其间的协程也会被撤销。协程的效果阈首要有 CoroutineScope、MainScope、GlobalScope、lifecycleScope 、viewModelScope,首要差异如下。

  • CoroutineScope:CoroutineScope 是通用的协程效果域,用于界说协程的效果域规模,当该效果域被毁掉时,其间的协程也会被撤销。
  • MainScope:MainScope 是 Kotlin 中供给的特定于 Android 的协程效果域,用于在 Android 主线程上发动协程,通常在 Android 的 Activity 或 Fragment 中运用 MainScope,以保证在主线程上运转协程,并在相关生命周期完毕时撤销协程。
  • GlobalScope:GlobalScope 是 Kotlin 中供给的一个大局协程效果域,它是一个顶层目标,用户能够在任何地方运用 GlobalScope 发动协程,但不引荐在 Android 中运用它,由于它的生命周期很长,而且不受办理,或许导致内存走漏等问题。
  • lifecycleScope:lifecycleScope 是 Android Jetpack 中的 Lifecycle 模块供给的一个扩展特点,它的生命周期与相关的组件(如 Activity 或 Fragment)的生命周期绑定,然后防止内存走漏等问题。
  • viewModelScope:viewModelScope 是 Android Jetpack 中 Lifecycle 模块供给的一个扩展特点,它的生命周期与 ViewModel 的生命周期绑定,然后防止内存走漏等问题。

3.1.1 CoroutineScope

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

​ 阐明:CoroutineScope 是通用的协程效果域,用于界说协程的效果域规模,当该效果域被毁掉时,其间的协程也会被撤销。

3.1.2 MainScope

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

​ 阐明:MainScope 是 Kotlin 中供给的特定于 Android 的协程效果域,用于在 Android 主线程上发动协程,通常在 Android 的 Activity 或 Fragment 中运用 MainScope,以保证在主线程上运转协程,并在相关生命周期完毕时撤销协程。

3.1.3 GlobalScope

public object GlobalScope : CoroutineScope

​ 阐明:GlobalScope 是 Kotlin 中供给的一个大局协程效果域,它是一个顶层目标,用户能够在任何地方运用 GlobalScope 发动协程,但不引荐在 Android 中运用它,由于它的生命周期很长,而且不受办理,或许导致内存走漏等问题。GlobalScope 是一个单例,其效果域的生命周期跟从运用程序的生命周期,中心不能撤销(cancel)。

3.1.4 lifecycleScope

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

// -----------------------------------------------------------
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }

// -----------------------------------------------------------
internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver

// -----------------------------------------------------------
public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope

​ 阐明:lifecycleScope 是 Android Jetpack 中的 Lifecycle 模块供给的一个扩展特点,它的生命周期与相关的组件(如 Activity 或 Fragment)的生命周期绑定,然后防止内存走漏等问题。

​ 运用 lifecycleScope 时,需求在 build.gradle 中引进以下依靠。

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"

​ 并导入包名。

import androidx.lifecycle.lifecycleScope

​ AppCompatActivity、FragmentActivity 与 LifecycleOwner 存在以下承继联系。因而能够在 AppCompatActivity 和 FragmentActivity 中直接拜访 lifecycleScope。

AppCompatActivity → FragmentActivity → ComponentActivity → LifecycleOwner

3.1.5 viewModelScope

public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

// --------------------------------------------------------------------------
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}

​ 阐明:viewModelScope 是 Android Jetpack 中 Lifecycle 模块供给的一个扩展特点,它的生命周期与 ViewModel 的生命周期绑定,然后防止内存走漏等问题。

​ 运用 viewModelScope 时,需求在 build.gradle 中引进以下依靠。

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'

​ 并导入包名。

import androidx.lifecycle.viewModelScope

3.2 协程调度器源码(Dispatchers)

public actual object Dispatchers {
    // 线程池, 合适履行CPU密集型使命(很多占用量CPU的使命)
    public actual val Default: CoroutineDispatcher = DefaultScheduler
    // Android中是UI线程, Swing中是invokerLater线程
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
    // 在当时线程上履行
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
    // 线程池, 合适履行磁盘读写、网络IO、数据库操作等使命
    public val IO: CoroutineDispatcher = DefaultIoScheduler
    // ...
}

3.3 协程发动方法源码

​ 协程的发动方法首要有 launch、async、runBlocking、withContext,它们的差异如下。

  • launch:launch 用于发动一个新的协程,并回来一个 Job 目标,该目标代表了这个新协程;发动的协程在后台运转,不会堵塞当时线程的履行,而且不会回来协程的履行成果。
  • async:async 用于发动一个新的协程,并回来一个 Deferred 目标,它是 Job 的子类,能够经过 await 函数获取协程的履行成果;发动的协程在后台运转,不会堵塞当时线程的履行。
  • runBlocking:runBlocking 是一个顶层函数,用于发动一个新的协程并堵塞当时线程,直到协程履行完结; runBlocking 本质上是为了在顶层(如 main 函数)运用协程,以及在测验中运用协程;在出产代码中不引荐运用 runBlocking,由于它会堵塞当时线程,或许导致功能问题。
  • withContext:withContext 用于切换协程的上下文,它会创立一个新的协程并在指定的上下文中履行,它会挂起本来的协程,待新协程履行完毕后才康复履行。

3.3.1 launch

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

​ 阐明:launch 用于发动一个新的协程,并回来一个 Job 目标,该目标代表了这个新协程;发动的协程在后台运转,不会堵塞当时线程的履行,而且不会回来协程的履行成果。

3.3.2 async

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

​ 阐明:async 用于发动一个新的协程,并回来一个 Deferred 目标,它是 Job 的子类,能够经过 await 函数获取协程的履行成果;发动的协程在后台运转,不会堵塞当时线程的履行。

3.3.3 runBlocking

​ runBlocking 官方介绍见 → runBlocking

public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
    ...
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        // 假如没有指定调度器(dispatcher), 则创立或运用私有事情循环(eventLoop)
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
            ?: ThreadLocalEventLoop.currentOrNull()
        newContext = GlobalScope.newCoroutineContext(context)
    }
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}

​ 阐明:runBlocking 是一个顶层函数,用于发动一个新的协程并堵塞当时线程,直到协程履行完结; runBlocking 本质上是为了在顶层(如 main 函数)运用协程,以及在测验中运用协程;在出产代码中不引荐运用 runBlocking,由于它会堵塞当时线程,或许导致功能问题。

3.3.4 withContext

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {
    // ...
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        val oldContext = uCont.context
        val newContext = oldContext.newCoroutineContext(context)
        newContext.ensureActive()
        if (newContext === oldContext) {
            val coroutine = ScopeCoroutine(newContext, uCont)
            return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
        }
        if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
            val coroutine = UndispatchedCoroutine(newContext, uCont)
            withCoroutineContext(newContext, null) {
                return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
            }
        }
        val coroutine = DispatchedCoroutine(newContext, uCont)
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

​ 阐明:withContext 用于切换协程的上下文,它会创立一个新的协程并在指定的上下文中履行,它会挂起本来的协程,待新协程履行完毕后才康复履行。

3.4 协程发动形式源码(CoroutineStart)

public enum class CoroutineStart {
	// 当即履行协程体
    DEFAULT,
    // 只要在需求的情况下运转, 需求调用job.start()函数才发动协程
    LAZY,
    // 当即履行协程体, 但在开端运转前无法撤销
    ATOMIC,
    // 当即在当时线程履行协程体, 直到第一个suspend函数调用(发动较快)
    UNDISPATCHED;
    // ...
}

4 协程运用

4.1 协程效果域运用

4.1.1 CoroutineScope

fun main() {
    println("main-start")
    CoroutineScope(Dispatchers.Default).launch {
        for (i in 1..2) {
            println("CoroutineScope-A-$i")
            delay(100)
        }
    }
    CoroutineScope(Dispatchers.IO).launch {
        for (i in 1..2) {
            println("CoroutineScope-B-$i")
            delay(100)
        }
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

​ 打印如下。

main-start
main-end
CoroutineScope-A-1
CoroutineScope-B-1
CoroutineScope-A-2
CoroutineScope-B-2

​ 阐明:成果表明 main、CoroutineScope-A、CoroutineScope-B 并行。

4.1.2 MainScope

fun main() {
    println("main-start")
    MainScope().launch(Dispatchers.Default) {
        test("MainScope-A")
    }
    MainScope().launch(Dispatchers.IO) {
        test("MainScope-B")
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
main-end
MainScope-B-1
MainScope-A-1
MainScope-A-2
MainScope-B-2

​ 阐明:成果表明 main、MainScope-A、MainScope-B 并行。

4.1.3 GlobalScope

fun main() {
    println("main-start")
    GlobalScope.launch(Dispatchers.Default, CoroutineStart.DEFAULT) {
        test("GlobalScope-A")
        test("GlobalScope-B")
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
main-end
GlobalScope-A-1
GlobalScope-A-2
GlobalScope-B-1
GlobalScope-B-2

​ 阐明:成果表明 main 与 GlobalScope 并行。

4.1.4 lifecycleScope

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

class MyActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycleScope.launch {
            println("lifecycleScope")
        }
    }
}

​ 阐明:运用 lifecycleScope 时,需求在 build.gradle 中引进以下依靠。

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"

4.1.5 viewModelScope

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            println("viewModelScope")
        }
    }
}

​ 阐明:运用 viewModelScope 时,需求在 build.gradle 中引进以下依靠。

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'

4.1.6 子协程

fun main() {
    println("main-start")
    CoroutineScope(Dispatchers.Default).launch {
        test("CoroutineScope-A")
        launch(Dispatchers.Default) { // 也能够经过async发动子协程
            test("CoroutineScope-B")
        }
        launch(Dispatchers.Default) { // 也能够经过async发动子协程
            test("CoroutineScope-C")
        }
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
main-end
CoroutineScope-A-1
CoroutineScope-A-2
CoroutineScope-B-1
CoroutineScope-C-1
CoroutineScope-B-2
CoroutineScope-C-2

​ 阐明:成果表明 main 与 CoroutineScope-A 并行,CoroutineScope-A 运转完毕后,又发动了 GlobalScope-B、CoroutineScope-C 两个子协程,它们又并行。

4.2 协程发动方法运用

4.2.1 launch

fun main() {
    println("main-start")
    MainScope().launch(Dispatchers.Default, CoroutineStart.DEFAULT) {
        test("MainScope")
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
main-end
MainScope-1
MainScope-2

4.2.2 async

fun main() {
    println("main-start")
    MainScope().launch(Dispatchers.Default) {
        var deferred = async { // 发动子协程
            test("MainScope")
            "async return value"
        }
        println("MainScope-xxx")
        var res = deferred.await() // 获取子协程的回来值, 此处会挂起当时协程, 直到子协程履行完结
        println(res)
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
main-end
MainScope-xxx
MainScope-1
MainScope-2
async return value

​ 阐明:成果表明 deferred.await() 会挂起当时协程(MainScope),直到子协程(async)履行完结。

4.2.3 runBlocking

fun main() {
    println("main-start")
    runBlocking {
        var deferred = async { // 发动子协程
            test("runBlocking")
            "async return value"
        }
        launch { // 发动子协程
            var res = deferred.await() // 获取子协程的回来值, 此处会挂起当时协程, 直到子协程履行完结
            println(res)
        }
        println("runBlocking-xxx")
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
runBlocking-xxx
runBlocking-1
runBlocking-2
async return value
main-end

​ 阐明:成果表明 runBlocking 发动了一个新的协程(runBlocking),并堵塞了当时线程(main),直到协程履行完结;deferred.await() 会挂起当时子协程(async),直到子协程(launch)履行完结。

4.2.4 withContext

1)不运用 withContext 回来值

@OptIn(ExperimentalStdlibApi::class)
fun main() {
    println("main-start")
    runBlocking(Dispatchers.IO) {
        println("context1=${coroutineContext[CoroutineDispatcher]}")
        withContext(Dispatchers.Default) { // 发动子协程, 并挂起当时协程
            println("context2=${coroutineContext[CoroutineDispatcher]}")
            test("withContext")
        }
        println("runBlocking-xxx")
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

suspend fun test(tag: String) {
    for (i in 1..2) {
        println("$tag-$i")
        delay(100)
    }
}

​ 打印如下。

main-start
context1=Dispatchers.IO
context2=Dispatchers.Default
withContext-1
withContext-2
runBlocking-xxx
main-end

​ 阐明:成果表明 withContext 创立了子协程,并挂起了 runBlocking 协程,直到 withContext 协程履行完毕才康复履行。

2)运用 withContext 回来值

@OptIn(ExperimentalStdlibApi::class)
fun main() {
    println("main-start")
    runBlocking(Dispatchers.IO) {
        println("context1=${coroutineContext[CoroutineDispatcher]}")
        var res = withContext(Dispatchers.Default) { // 发动子协程, 并挂起当时协程
            println("context2=${coroutineContext[CoroutineDispatcher]}")
            "withContext return value"
        }
        println("res=$res")
    }
    println("main-end")
    Thread.sleep(1000) // 堵塞当时线程, 防止程序过早完毕, 协程提早撤销
}

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=138

标签: Kotlin
分享给朋友:

“【Kotlin】协程” 的相关文章

python快速注释,提升代码可读性与维护性

在Python中,快速注释代码的常见方法有以下几种:1. 单行注释:使用 `` 符号在代码行前添加注释。2. 多行注释:可以使用三个连续的单引号 `'''` 或三个连续的双引号 `` 来创建多行注释。3. 使用文本编辑器或IDE的快捷键:大多数文本编辑器和IDE都提供了快捷键来快速注释或取消注释代码...

php一句话,php官网

请提供具体的上下文或问题,以便我能提供相关的PHP代码示例。深入解析PHP一句话木马:原理、构造与免杀技巧一、PHP一句话木马原理PHP一句话木马,顾名思义,就是只需要一行代码就能实现攻击目的的木马。其核心原理是利用PHP中的eval()函数。eval()函数可以将字符串当作PHP代码执行,从而实现...

python机器学习,从基础到实践

python机器学习,从基础到实践

当然可以,机器学习是Python编程中的一个重要领域,它涉及到使用算法从数据中学习,以便做出预测或决策。Python有许多流行的库和框架,如scikitlearn、TensorFlow和PyTorch,可以用于机器学习。如果你对机器学习感兴趣,我可以帮助你学习基础知识,包括数据预处理、特征工程、模型...

php中文乱码, PHP中文乱码的原因

php中文乱码, PHP中文乱码的原因

1. 设置字符编码: 在PHP文件的开头,使用 `` 来设置输出内容的字符编码为UTF8。 确保你的PHP文件本身也是保存为UTF8编码。2. 数据库连接: 如果你在使用数据库,确保数据库、数据库表和数据库列都使用UTF8编码。 在连接数据库时,设置字符集为UTF8,例如使用...

rust木门怎么拆,Rust游戏中的木门拆除方法详解

rust木门怎么拆,Rust游戏中的木门拆除方法详解

拆装木门是一项需要谨慎操作的任务,尤其是对于初学者来说。下面是一些基本的步骤,可以帮助你安全地拆下Rust木门:1. 准备工具:在开始之前,确保你拥有必要的工具,如螺丝刀、锤子、凿子、钳子等。2. 断电:如果门附近有电源插座或开关,请先关闭电源,以避免触电风险。3. 拆卸门把手和锁:首先,卸下门把手...

rust地图,探索无限可能

rust地图,探索无限可能

在Rust游戏中,地图是一个非常重要的元素,玩家可以通过不同的途径获取和定制地图。以下是几种主要的获取和定制Rust地图的方法:1. RustMaps.com: 网站地址: 功能:这个网站提供了大量由其他玩家创建的地图,你可以通过过滤条件(如生物群系、纪念碑、地标等)来浏览这些地图。此外...