Paging3 (一)  入门

前言:

官方分页工具,  确实香.   但数据源不开放, 无法随意增删改操作;  只能借助 Room;  但列表数据不一定都要用 Room吧;

如果偏查询的分页数据用 Paging3 ;  其他一概用 老Adapter;  这倒也算个方案. [苦笑]

目录:

  1. 简单使用  -  数据源,Viewmodel,Adapter 等
  2. LoadResult  -  Error, Page.  Error 用法等
  3. PagingConfig
  4. 监听列表加载状态
  5. LoadStateAdapter  -  loading, 加载失败, 没有更多等
  6. Map  -  数据预处理

官方 Pagings 优势: 

  • 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
  • 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
  • 可配置的 RecyclerView 适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
  • 对 Kotlin 协程和 Flow 以及 LiveData 和 RxJava 的一流支持。
  • 内置对错误处理功能的支持,包括刷新和重试功能。

导包:

dependencies {
val paging_version = "3.0.0" //唯一必导包
implementation("androidx.paging:paging-runtime:$paging_version") // 测试用
testImplementation("androidx.paging:paging-common:$paging_version") // optional - RxJava2 support
implementation("androidx.paging:paging-rxjava2:$paging_version") // optional - RxJava3 support
implementation("androidx.paging:paging-rxjava3:$paging_version") // 适配 Guava 库 - 高效java扩展库
implementation("androidx.paging:paging-guava:$paging_version") // 适配 Jetpack Compose - 代码构建View; 干掉 layout
implementation("androidx.paging:paging-compose:1.0.0-alpha09")
}

1. 简单使用:

1.1 数据源  PagingSource 

自定义数据源, 继承 PagingSource

它有两个泛型参数,  1. 页码key,  没有特殊需求的话一般就是 Int 类型;  2.集合实体类型

重写两个方法:  1.load()  加载数据的方法;   2.getRefreshKey  初始加载的页码;  暂且返回 1 或 null

LoadResult.Page 后面再讲;

class DynamicDataSource: PagingSource<Int, DynamicTwo>() {

    //模拟最大页码
private var maxPage = 2 //模拟数据
private fun fetchItems(startPosition: Int, pageSize: Int): MutableList<DynamicTwo> {
Log.d("ppppppppppppppppppppp", "startPosition=${startPosition};;;pageSize=${pageSize}")
val list: MutableList<DynamicTwo> = ArrayList()
for (i in startPosition until startPosition + pageSize) {
val concert = DynamicTwo()
concert.title = "我是标题${i}"
concert.newsInfo = "我是内容${i}"
concert.nickName = "小王${i}"
list.add(concert)
}
return list
} override fun getRefreshKey(state: PagingState<Int, DynamicTwo>): Int? = null override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DynamicTwo> {
val nextPageNumber = params.key ?: 1
val size = params.loadSize
Log.d("ppppppppppppppppppppp", "nextPageNumber=${nextPageNumber};;;size=${size}")
val response = fetchItems((nextPageNumber-1) * size, size) return LoadResult.Page(
data = response,
prevKey = null, // Only paging forward. 只向后加载就给 null
//nextKey 下一页页码; 尾页给 null; 否则当前页码加1
nextKey = if(nextPageNumber >= maxPage) null else (nextPageNumber + 1)
)
}
}

1.2 ViewModel

代码比较简单.  内容我们一会再讲

class DynamicPagingModel(application: Application) : AndroidViewModel(application) {
val flow = Pager(
//配置
PagingConfig(pageSize = 10, prefetchDistance = 2,initialLoadSize = 10)
) {
//我们自定义的数据源
DynamicDataSource()
}.flow
.cachedIn(viewModelScope)
}

1.3 前台使用: 

初始化 Adapter 及 RecycleView

mViewModel?.flow?.collectLatest  绑定监听,  然后通过 submitData() 刷新列表;

mAdapter = SimplePagingAdapter(R.layout.item_dynamic_img_two, null)

mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
it.adapter = mAdapter
} //Activity 用 lifecycleScope
//Fragments 用 viewLifecycleOwner.lifecycleScope
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
mViewModel?.flow?.collectLatest {
mAdapter.submitData(it)
}
}

1.4 Adapter

必须继承  paging 的 PagingDataAdapter

DiffCallback() 或 handler  NewViewHolder 不了解的可以看我的 ListAdapter 封装系列

open class SimplePagingAdapter(
private val layout: Int,
protected val handler: BaseHandler? = null
) :
PagingDataAdapter<DynamicTwo, RecyclerView.ViewHolder>(DiffCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return NewViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context), layout, parent, false
), handler
)
} override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if(holder is NewViewHolder){
holder.bind(getItem(position))
}
}
}

over  简单的分页模拟数据已完成;  

2. LoadResult

它是一个密封类;   它表示加载操作的结果;

2.1 LoadResult.Error

表示加载失败;  需提供 Throwable 对象.

public data class Error<Key : Any, Value : Any>(
val throwable: Throwable
) : LoadResult<Key, Value>()

可用于:

  • 异常时返回,  HTTP, IO, 数据解析等异常;
  • 服务器错误码响应
  • 没有更多数据

2.1 LoadResult.Page

表示加载成功;

参数:

data 数据集合;

prevKey 前页页码 key;   //向下一页加载 给null

nextKey 后页页码 key;    //向上一页加载 给null

public data class Page<Key : Any, Value : Any> constructor(
/**
* Loaded data
*/
val data: List<Value>,
/**
* [Key] for previous page if more data can be loaded in that direction, `null`
* otherwise.
*/
val prevKey: Key?,
/**
* [Key] for next page if more data can be loaded in that direction, `null` otherwise.
*/
val nextKey: Key?,
/**
* Optional count of items before the loaded data.
*/
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsBefore: Int = COUNT_UNDEFINED,
/**
* Optional count of items after the loaded data.
*/
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsAfter: Int = COUNT_UNDEFINED
) : LoadResult<Key, Value>() {

3.PagingConfig

分页配置

参数:

pageSize:  每页容量

prefetchDistance:  当RecycleView 滑动到底部时, 会自动加载下一页.   如果能提前预加载, 可以省去部分等待加载的时间.

        prefetchDistance 就是距离底部提前加载的距离.  默认 = pageSize;   = 0 时将不会加载更多

enablePlaceholders:  允许使用占位符.  想了解的点这里

initialLoadSize: 初始加载数量,  默认 = pageSize * 3

maxSize:   似乎意义没有那么简单.  还没看源码,不清楚;  不能 < pageSize + prefetchDistance * 2

jumpThreshold: 某阈值!  好吧我摊牌了, 我不知道. [奸笑]

4.监听加载状态:  

LoadState:  表示加载状态密封类;

LoadState.NotLoading:  加载完毕,  并且界面也已相应更新

LoadState.Error:  加载失败.

LoadState.Loading:  正在加载..

lifecycleScope.launch {
mAdapter.loadStateFlow.collectLatest { loadStates ->
when(loadStates.refresh){
is LoadState.Loading -> {
Log.d("pppppppppppppp", "加载中")
}
is LoadState.Error -> {
Log.d("pppppppppppppp", "加载失败")
}
is LoadState.NotLoading -> {
Log.d("pppppppppppppp", "完事了")
}
else -> {
Log.d("pppppppppppppp", "这是啥啊")
}
}
} //或者:
mAdapter.addLoadStateListener { ... }
}

5. 状态适配器  LoadStateAdapter

用于直接在显示的分页数据列表中呈现加载状态。 例如:  尾部显示 正在加载, 加载失败, 没有更多等;

5.1 自定义 MyLoadStateAdapter  继承 LoadStateAdapter

重写  onCreateViewHolder,  onBindViewHolder

retry:  如果加载失败, 想要重试,  则提供该高阶函数参数;  否则不需要它

class MyLoadStateAdapter(
/**
* 当下一页加载失败时, 继续尝试加载下一页;
*/
private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() { override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
) = LoadStateViewHolder(parent, retry) override fun onBindViewHolder(
holder: LoadStateViewHolder,
loadState: LoadState
) = holder.bind(loadState)
}

5.2 自定义 LoadStateViewHolder

功能: 

  • 加载中 显示 Loading;
  • 加载失败  显示 错误信息.    包括 http, IO 异常,  后台给的错误 msg 等;
  • 没有更多
class LoadStateViewHolder (
parent: ViewGroup,
retry: () -> Unit
) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.view_loading_more, parent, false)
) {
private val binding = ViewLoadingMoreBinding.bind(itemView) init {
//当点击重试按钮时, 调用 PagingDataAdapter 的 retry() 重新尝试加载
binding.btnLoadingRetry.setOnClickListener {
retry()
}
} fun bind(loadState: LoadState) {
// 当加载失败时.
if(loadState is LoadState.Error){
// 将没有更多封装成 NoMoreException; 此时显示没有更多 View
if(loadState.error is NoMoreException){
hideNoMoreUi(false) //显示 没有更多 View
hideErrUi(true) //隐藏 失败 View
}else{
hideNoMoreUi(true)
hideErrUi(false, loadState.error.message) //显示失败 View时, 填充错误 msg
}
}else{
hideNoMoreUi(true)
hideErrUi(true)
} //加载中..
binding.pbLoadingBar.visibility = if(loadState is LoadState.Loading){
View.VISIBLE
}else{
View.GONE
}
} /**
* 隐藏没有更多View;
*/
private fun hideNoMoreUi(hide: Boolean){
if(hide){
binding.tvLoadingHint.visibility = View.GONE
}else{
binding.tvLoadingHint.visibility = View.VISIBLE
}
} /**
* 隐藏 加载失败View;
*/
private fun hideErrUi(hide: Boolean, msg: String? = null){
if(hide){
binding.tvLoadingError.visibility = View.GONE
binding.btnLoadingRetry.visibility = View.GONE
}else{
binding.tvLoadingError.text = msg
binding.tvLoadingError.visibility = View.VISIBLE
binding.btnLoadingRetry.visibility = View.VISIBLE
}
}
}

顺便补一下  NoMoreException;  用法? 在下面 PagingSource 喽.

class NoMoreException: RuntimeException()

5.3 layout  view_loading_more.xml

包含:   TextView: 没有更多;    ProgressBar: 加载中;   TextView: 错误信息;   Button: 重试按钮

<layout>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingHorizontal="16dp"
android:layout_width="match_parent"
android:layout_height="54dp">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#e5e5e5"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tv_loading_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#798080"
android:text="已经到底了"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<ProgressBar
android:id="@+id/pb_loading_bar"
android:layout_width="32dp"
android:layout_height="32dp"
android:visibility="gone"
android:indeterminateTint="#7671F8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/tv_loading_error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/shape_red"
android:text="错误信息"
android:layout_marginEnd="8dp"
android:maxLines="2"
android:ellipsize="end"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_loading_retry"
app:layout_constraintStart_toStartOf="parent"/>
<Button
android:id="@+id/btn_loading_retry"
android:layout_width="60dp"
android:layout_height="38dp"
android:textColor="@color/white"
android:text="重试"
android:visibility="gone"
android:background="@drawable/shape_blue_7671f8_r8"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

5.4 PagingSource 需要根据情况  返回不同的  LoadResult

代码如下,  直接看注释就可以了;   

class DynamicDataSource: PagingSource<Int, DynamicTwo>() {

    private var maxPage = 1

    override fun getRefreshKey(state: PagingState<Int, DynamicTwo>): Int? = null

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DynamicTwo> {
try {
val nextPageNumber = params.key ?: 1 //超过页码时, 返回没有更多状态 NoMoreException
if(nextPageNumber > maxPage){
return LoadResult.Error(NoMoreException())
} //这是 Retrofit 网络请求
val map = mapOf("page" to nextPageNumber, "pageSize" to params.loadSize)
val param = ApiManager.INSTANCE.getJsonBody(map)
val response = ApiManager.INSTANCE.mApi.getDynamicList(param) //后台 响应错误码时; 用 RuntimeException 返回错误信息
if(response.code != 200){
return LoadResult.Error(RuntimeException(response.msg))
} //解析响应数据
val jo = response.data
val list = jo?.getAsJsonArray("newsList")?.toString()?.toBeanList<DynamicTwo>() ?: mutableListOf()
maxPage = jo?.get("totalPage").toString().toInt() //返回正常数据
return LoadResult.Page(
data = list,
prevKey = null, // Only paging forward. 只向后加载就给null
// nextKey 下一页页码; 尾页给 null; 否则当前页码加1
nextKey = nextPageNumber + 1
)
} catch (e: IOException) {
// IOException for network failures.
return LoadResult.Error(e)
} catch (e: HttpException) {
// HttpException for any non-2xx HTTP status codes.
return LoadResult.Error(e)
} catch (e: Exception) {
// IOException for network failures.
return LoadResult.Error(e)
}
}
}

代码中 请求参数只给了 page 和 pageSize;  其他参数怎么给?  

  1. DynamicDataSource 的构造方法传入;
  2. 动态参数怎么办?  写回调, 从ViewModel 中组装请求数据
  3. 麻烦怎么办?  创建 BaseDataSource.  将相似代码封装.  请求参数通过高阶函数从ViewModel组装;

5.5 前台使用: 

首先正常初始化 Adapter,  RecycleView,  并调用  mViewModel?.flow?.collectLatest

其次  RecycleView 的 adaper 不要给 主数据Adapter;  而是给 withLoadStateFooter() 返回的 ConcatAdapter

val stateAdapter = mAdapter.withLoadStateFooter(MyLoadStateAdapter(mAdapter::retry))
mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
// **** 这里不要给 mAdapter(主数据 Adapter); 而是给 stateAdapter ***
it.adapter = stateAdapter
}

PagingDataAdapter 的 withLoadStateFooter 方法会返回一个新的 ConcatAdapter 对象; 请将这个 ConcatAdapter 设置给 RecycleView
withLoadStateFooter 的参数 就是我们自定义的 MyLoadStateAdapter;  retry -> mAdapter.retry()

5.6 看一下  LoadStateAdapter 的源码;  

可以发现,  这是个单条目 Adapter.   

并且  只有当  LoadState.Loading, LoadState.Error 时才会出现;   当然也可以重写  displayLoadStateAsItem(), 让它所有状态都出现;

当 列表状态变化时,  会设置 loadState 参数;  动态增删改 Item;

abstract class LoadStateAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {

    var loadState: LoadState = LoadState.NotLoading(endOfPaginationReached = false)
set(loadState) {
if (field != loadState) {
val oldItem = displayLoadStateAsItem(field)
val newItem = displayLoadStateAsItem(loadState) if (oldItem && !newItem) {
notifyItemRemoved(0)
} else if (newItem && !oldItem) {
notifyItemInserted(0)
} else if (oldItem && newItem) {
notifyItemChanged(0)
}
field = loadState
}
} final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
return onCreateViewHolder(parent, loadState)
} final override fun onBindViewHolder(holder: VH, position: Int) {
onBindViewHolder(holder, loadState)
} final override fun getItemViewType(position: Int): Int = getStateViewType(loadState)

  //条目数量, final 不可重写;
final override fun getItemCount(): Int = if (displayLoadStateAsItem(loadState)) 1 else 0 abstract fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): VH abstract fun onBindViewHolder(holder: VH, loadState: LoadState) open fun getStateViewType(loadState: LoadState): Int = 0
  
  //只有当 Loading, Error 时, 才显示
open fun displayLoadStateAsItem(loadState: LoadState): Boolean {
return loadState is LoadState.Loading || loadState is LoadState.Error
}
}

5.7 LoadStateAdapter  改建头尾

如果我们把它强行改造成 Header footer: 

  1. 重写 displayLoadStateAsItem() 不管什么状态, 都返回true
  2. loadState 不能重写,  所以 notifyItemChanged(0) 必被调用;
  3. 暴力一点, 直接重写 notifyItemChanged() 让它什么都不做?   好吧  它也是 final, 不能重写
  4. 既然要调刷新, 那就调吧 [破涕为笑];  那怎么办 尽量少执行无用代码呗,   那就 onBindViewHolder() 啥也不干;
  5. 头尾由前端控制,  Adapter 只需要把这个 固定View显示就 ok 了
  6. 如果能阻止 notifyItemChanged(0) 那就更好了.  聪明的你有没有办法呢. [666]

最终 Adapter: 

class EndViewAdapter(val v: View) : LoadStateAdapter<EndHolder>() {

    override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
) = EndHolder(v) override fun onBindViewHolder(holder: EndHolder, loadState: LoadState){
//啥也不干
} override fun displayLoadStateAsItem(loadState: LoadState) = true
} class EndHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

好吧,  一运行, 崩了 [捂脸];   called attach on a child which is not detached

怎么办, 取消 RecycleView 的刷新闪烁动画:

(mDataBind.rvRecycle.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false;

整个 RecycleView 的条目刷新动画都没了,  这不是个事啊!  但博主已经没办法了 [捂脸]

没办法了怎么办? 不用 Header 了?   当然不是,  我们只是不用 LoadStateAdapter 做头尾了;  我们用 ConcatAdapter 做头尾;

就是在 withLoadState...  之后,  再自己组装  ConcatAdapter

6. MAP:  数据转换;  有的时候, 我们需要对响应数据 进行预先处理; 

例如: 根据条件,预先改变实体内容;

val flow: Flow<PagingData<DynamicTwo>> = Pager(
PagingConfig(pageSize = 10, prefetchDistance = 2,initialLoadSize = 10)
) {
DynamicDataSource()
}.flow
.cachedIn(viewModelScope)
.map {
it.map { entity ->
// 这里根据条件, 预先处理数据
if(entity.isLike == 1){
entity.nickName = "变变变, 我是百变小魔女"
}else{
entity.nickName = "呜哈哈哈"
}
entity
}
}

例如:  组合实体; 根据条件产生不同实体;

val flow: Flow<PagingData<GroupEntity>> = Pager(
PagingConfig(pageSize = 10, prefetchDistance = 2,initialLoadSize = 10)
) {
DynamicDataSource()
}.flow
.cachedIn(viewModelScope)
.map {
it.map { entity ->
// 这里根据条件, 预先处理数据
if(entity.isLike == 1){
GroupEntity.DynamicTwoItem(entity)
}else{
GroupEntity.DynamicItem(DynamicEntity())
}
}
} sealed class GroupEntity{
class DynamicTwoItem (val entity: DynamicTwo): GroupEntity()
class DynamicItem (val entity: DynamicEntity): GroupEntity()
}

又例如: 插入实体分隔符等

Over

回到顶部

孟老板 Paging3 (一) 入门的更多相关文章

  1. 孟老板 Paging3 (二) 结合Room

    BaseAdapter系列 ListAdapter系列 Paging3 (一) 入门 Paging3 (二) 结合 Room Paging3 (二)  结合Room Paging 数据源不开放, 无法 ...

  2. 孟老板 ListAdapter封装, 告别Adapter代码 (三)

    BaseAdapter系列 ListAdapter封装, 告别Adapter代码 (一) ListAdapter封装, 告别Adapter代码 (二) ListAdapter封装, 告别Adapter ...

  3. 孟老板 ListAdapter封装, 告别Adapter代码 (四)

    BaseAdapter系列 ListAdapter封装, 告别Adapter代码 (一) ListAdapter封装, 告别Adapter代码 (二) ListAdapter封装, 告别Adapter ...

  4. 孟老板 BaseAdapter封装(五) ListAdapter

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

  5. 孟老板 BaseAdapter封装 (一) 简单封装

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

  6. 孟老板 BaseAdapter封装 (二) Healer,footer

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

  7. 孟老板 BaseAdapter封装 (三) 空数据占位图

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

  8. 孟老板 BaseAdapter封装(四) PageHelper

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

  9. 孟老板 ListAdapter封装, 告别Adapter代码 (上)

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

随机推荐

  1. 2021/5/11 docker的应用

    很快一天过去了,今天虽然没有加班,但是依旧感觉疲惫,弄了一天的docker镜像的问题.作为一名前端开发工程师从以前从未听说过docker到现在懂得了如何运用,也是不容易啊.之前也说过,我们项目结构是前 ...

  2. JavaScript中DOM与BOM的区别

    1.BOM BOM全称为Brower Object Model,中文翻译为浏览器对象模型,提供了独立于内容而与浏览器窗口进行交互的对象.描述了与浏览器进行交互的方法和接口.通过BOM可以用来获取或设置 ...

  3. Linux 中如何使用 IP 命令

    老版本的 Linux 中都是使用 ifconfig 命令检查和配置网络接口,但是该命令目前已经没有维护了,取而代之的是 ip 命令 ip 命令和 ifconfig 命令很相似,但是 相比起来,ip命令 ...

  4. 墙裂推荐一波mysql学习资源

    在日常工作与学习中,无论是开发.运维.测试,还是架构师,数据库是一门必不可少的"必修课", 也是必备的涨薪神器.在互联网公司中,开源数据库用得比较多的当属 MySQL 了. 但my ...

  5. 开机自动挂载本地yum源-20200402-V0.1

    开机自动挂载本地yum源-20200402-V0.1 已下载本地iso /home/Kylin-Server-10-mips64-Release-Build04.08-lic-20200313.iso ...

  6. gitlab使用URL导入远程仓库报错

    gitlab使用URL导入远程仓库报错Import url is blocked: Only allowed ports are 80,443, and any over 1024 报错内容为Impo ...

  7. Windows 10 版本 21H1 发布,百度网盘下载

    请访问原文链接:https://sysin.org/article/windows-10/,查看最新版.原创作品,转载请保留出处. Windows 10, version 21H1, all edit ...

  8. 『动善时』JMeter基础 — 33、JMeter察看结果树的显示模式详解

    目录 1.CSS Selector Tester视图 2.HTML查看器 (1)HTML视图 (2)HTML(download resources)视图 (3)HTML Source Formatte ...

  9. [leetcode] 72. 编辑距离(二维动态规划)

    72. 编辑距离 再次验证leetcode的评判机有问题啊!同样的代码,第一次提交超时,第二次提交就通过了! 此题用动态规划解决. 这题一开始还真难到我了,琢磨半天没有思路.于是乎去了网上喵了下题解看 ...

  10. Step By Step(Lua函数)

    Step By Step(Lua函数) 一.函数:    在Lua中函数的调用方式和C语言基本相同,如:print("Hello World")和a = add(x, y).唯一的 ...