Android性能优化之ViewPagers + Fragment缓存优化

作者:Ghelper??????? 时间:2022-08-13 10:23:35 

前言

大家看标题,可能会有点儿懵,什么是ViewPagers,因为在很久之前,我们使用的都是ViewPager,但是现在更多的是在用ViewPager2,因此用ViewPagers(ViewPager、ViewPager2)来代替两者,主要介绍两者的区别。

ViewPagers嵌套Fragment架构,在我们常用的App中随处可见,抖音的首页、各大电商app首页(淘宝、京东、拼多多)等,通过左右滑动切换Tab;但因为ViewPager的预加载机制存在,

我们先看下ViewPager的源码:

public void setOffscreenPageLimit(int limit) {
   if (limit < DEFAULT_OFFSCREEN_PAGES) {
       Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
               + DEFAULT_OFFSCREEN_PAGES);
       limit = DEFAULT_OFFSCREEN_PAGES;
   }
   if (limit != mOffscreenPageLimit) {
       mOffscreenPageLimit = limit;
       populate();
   }
}

当我们设置offscreenPageLimit(离屏加载)的数值时,我们可以看到limit的值是有限制,不能小于DEFAULT_OFFSCREEN_PAGES

private static final int DEFAULT_OFFSCREEN_PAGES = 1;

那么就意味着ViewPager默认支持预加载,我们看下面这张图

Android性能优化之ViewPagers + Fragment缓存优化

如果红色区域默认为首页,根据ViewPager默认预加载的阈值,那么左右两边的页面同样也会被加载,如果有网络请求,也就是说,我们没有打开左边的页面,它已经默认进行了网络请求,这种体验是非常差的,因为会在暗地里消耗流量。

理想情况下,我们需要的是打开某个页面的时候才去加载,这里就需要通过懒加载的方式优化。

1 ViewPager懒加载优化

1.1 ViewPager的缓存机制

很多时候,我们在使用Fragment的时候,发现打开过的页面再回来,页面没有重建刷新,很多人觉得是Fragment是有缓存的,其实并不是Fragment有缓存,而是ViewPager具备缓存能力;

如果有小伙伴使用过单Activity + 多Fragment架构的时候就会发现,打开过的页面再次返回的时候,Fragment会被重建,所以两种架构都有利弊,关键看我们怎么选择,下面我们看下ViewPager的缓存机制。

public void setAdapter(@Nullable PagerAdapter adapter) {
   if (mAdapter != null) {
       ①
       mAdapter.setViewPagerObserver(null);
       mAdapter.startUpdate(this);
       for (int i = 0; i < mItems.size(); i++) {
           final ItemInfo ii = mItems.get(i);
           mAdapter.destroyItem(this, ii.position, ii.object);
       }
       mAdapter.finishUpdate(this);
       mItems.clear();
       removeNonDecorViews();
       mCurItem = 0;
       scrollTo(0, 0);
   }
   ②
   final PagerAdapter oldAdapter = mAdapter;
   mAdapter = adapter;
   mExpectedAdapterCount = 0;


   if (mAdapter != null) {
       if (mObserver == null) {
           mObserver = new PagerObserver();
       }
       mAdapter.setViewPagerObserver(mObserver);
       mPopulatePending = false;
       final boolean wasFirstLayout = mFirstLayout;
       mFirstLayout = true;
       mExpectedAdapterCount = mAdapter.getCount();
       if (mRestoredCurItem >= 0) {
           mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
           setCurrentItemInternal(mRestoredCurItem, false, true);
           mRestoredCurItem = -1;
           mRestoredAdapterState = null;
           mRestoredClassLoader = null;
       } else if (!wasFirstLayout) {
           ④
           populate();
       } else {
           ⑤
           requestLayout();
       }
   }

// Dispatch the change to any listeners
   if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
       for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
           mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
       }
   }
}

核心方法就是setAdapter,像RecyclerView一样,因为会有缓存,所以当页面滑动的时候,如果缓存中存在页面,那么就会从缓存中取,如果没有,就需要去创建新的页面,所以我们先来关注一下PagerAdapter

public abstract class PagerAdapter {
   private final DataSetObservable mObservable = new DataSetObservable();
   private DataSetObserver mViewPagerObserver;

public static final int POSITION_UNCHANGED = -1;
   public static final int POSITION_NONE = -2;

public abstract int getCount();
   //开始更新
   public void startUpdate(@NonNull ViewGroup container) {
       startUpdate((View) container);
   }
   //初始化页面
   @NonNull
   public Object instantiateItem(@NonNull ViewGroup container, int position) {
       return instantiateItem((View) container, position);
   }
   //销毁页面
   public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
       destroyItem((View) container, position, object);
   }
   //结束刷新
   public void finishUpdate(@NonNull ViewGroup container) {
       finishUpdate((View) container);
   }
}

PagerAdapter是一个抽象类,那么这些方法肯定是具体实现类实现,如果我们在使用ViewPager嵌套Fragment的时候,使用的是FragmentPageAdapter

接着回到setAdapter方法中:

  • ①:有一个全局变量 mAdapter,如果是第一个加载进来,那么mAdapter是空的,走到②

  • ②:这里就是将我们传入的adapter给mAdapter赋值

  • ③:这个时候mAdapter不为空,这里需要关注几个参数:

wasFirstLayout = true
mRestoredCurItem = -1

所以这里直接走到⑤,调用requestLayout方法,会执行到onMeasure,在这个方法中,会执行populate方法(这个大家自己去爬楼)

populate干了什么呢?代码太多了就不贴出来了,直接上图:

Android性能优化之ViewPagers + Fragment缓存优化

如果是默认缓存(mOffscreenPageLimit = 1),那么在mItems就会缓存3个Fragment

private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();

当页面滑动时,page2成为了当前页,那么ViewPager的populate做了什么操作呢?

  • (1)首先page3会被预加载,这个时候调用了PagerAdapter的instantiateItem方法新建页面,并放在mItems集合中,并且设置为不可见的状态(setUserVisibleHint(false)),所有缓存中不可见的页面同理(2)page1就会从缓存中移除,调用了PagerAdapter的destroyItem方法,curPage会成为mItems中第一个缓存对象;

  • (3)将page2设置为当前展示的Fragment

因此populate干的主要工作就是,随着页面的滑动,将Page从缓存中移除销毁,或者将新页面新建加入缓存中。

1.2 ViewPager懒加载方案

如上所述,ViewPager默认就是开启预加载的,而且默认最多能够缓存3个Fragment页面,那么为了避免流量的消耗,需要我们针对预加载这种情况进行页面懒加载,只有当页面可见的时候,才能加载数据。

class MainLazyLoadAdapter(
   fragmentManager: FragmentManager,
   val fragments:MutableList<Fragment>
) : FragmentPagerAdapter(fragmentManager) {
   override fun getCount(): Int {
       return fragments.size
   }

override fun getItem(position: Int): Fragment {
       return fragments[position]
   }
}
class LazyFragment(val index:Int) : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       Log.e("TAG","LazyFragment $index onCreate")
   }

override fun onCreateView(
       inflater: LayoutInflater, container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View? {
       Log.e("TAG","LazyFragment $index onCreateView")
       return inflater.inflate(R.layout.fragment_lazy, container, false)
   }

}
val fragments = mutableListOf<Fragment>()
for (index in 0..5) {
   fragments.add(LazyFragment(index))
}
vp_lazy_load.adapter = MainLazyLoadAdapter(supportFragmentManager, fragments)

首先我们先看默认预加载状态,验证之前源码中的原理:

//第一次进来
2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView

我们看到第一次进来,第二个Fragment被加载进来,然后右滑,第三个Fragment被加载

2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 13:48:45.248 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 13:48:45.250 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreateView

当我们滑到第4个Fragment的时候,左滑回到第3个Fragment,发现并没有重建是因为缓存的原因,因为滑到第4个Fragment的时候,第2个Fragment已经被销毁了,再回到第3个Fragment的时候,第2个Fragment被重建,走了onCreateView方法

2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 13:48:45.248 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 13:48:45.250 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreateView
2022-08-28 13:50:00.439 12677-12677/com.lay.image_process E/TAG: LazyFragment 3 onCreate
2022-08-28 13:50:00.440 12677-12677/com.lay.image_process E/TAG: LazyFragment 3 onCreateView
2022-08-28 13:50:01.344 12677-12677/com.lay.image_process E/TAG: LazyFragment 4 onCreate
2022-08-28 13:50:01.345 12677-12677/com.lay.image_process E/TAG: LazyFragment 4 onCreateView
2022-08-28 13:50:03.315 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView

首先我们先看下,Adapter重建Fragment的时候的核心代码

public Object instantiateItem(@NonNull ViewGroup container, int position) {
   if (mCurTransaction == null) {
       mCurTransaction = mFragmentManager.beginTransaction();
   }

final long itemId = getItemId(position);

// Do we already have this fragment?
   String name = makeFragmentName(container.getId(), itemId);
   Fragment fragment = mFragmentManager.findFragmentByTag(name);
   if (fragment != null) {
       if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
       mCurTransaction.attach(fragment);
   } else {
       fragment = getItem(position);
       if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
       mCurTransaction.add(container.getId(), fragment,
               makeFragmentName(container.getId(), itemId));
   }
   if (fragment != mCurrentPrimaryItem) {
       fragment.setMenuVisibility(false);
       if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
           mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
       } else {
           //关键代码
           fragment.setUserVisibleHint(false);
       }
   }

return fragment;
}

我们可以看到,当前Fragment如果被创建但是没有在当前页面展示的时候,调用了fragment.setUserVisibleHint(false),也就是说setUserVisibleHint能够监听当前Fragment是否可见

所以我们对Fragment进行改造:

class LazyFragment(val index:Int) : Fragment() {

//判断当前页面是否可见
   private var isShow = false
   //判断页面是否创建成功
   private var isViewCreated = false

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       Log.e("TAG","LazyFragment $index onCreate")
   }
   override fun onCreateView(
       inflater: LayoutInflater, container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View? {
       Log.e("TAG","LazyFragment $index onCreateView")
       return inflater.inflate(R.layout.fragment_lazy, container, false)
   }
   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)
       isViewCreated = true
       lazyLoad()
   }
   override fun setUserVisibleHint(isVisibleToUser: Boolean) {
       super.setUserVisibleHint(isVisibleToUser)
       Log.e("TAG","LazyFragment $index isVisibleToUser $isVisibleToUser")
       if(isVisibleToUser){
           isShow = true
           //才有资格去懒加载
           lazyLoad()
       }else{
           isShow = false
       }
   }
   private fun lazyLoad() {
       if(isViewCreated && isShow){
           Log.e("TAG","Fragment $index loadData")
       }
   }
   override fun onDestroy() {
       super.onDestroy()
       isViewCreated = false
       isShow = false
   }
}

如果按照之前的方式,当调用onViewCreated方法的时候,我们就会加载数据;做了懒加载处理之后,重写了setUserVisibleHint方法,当前页面可见的时候,才有资格去加载数据,这样即便创建了Fragment,但是如果不可见就不会加载数据

2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser false
2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 isVisibleToUser false
2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser true
2022-08-28 14:06:29.782 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 14:06:29.783 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 14:06:29.796 25904-25904/com.lay.image_process E/TAG: Fragment 0 loadData
2022-08-28 14:06:29.805 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 14:06:29.805 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 14:06:59.395 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 isVisibleToUser false
2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser false
2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 isVisibleToUser true
2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: Fragment 1 loadData
2022-08-28 14:06:59.399 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 14:06:59.400 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 onCreateView

通过日志我们可以看到,当首次进入的时候,虽然Fragment 1 被创建了,但是并没有加载数据。

这里有个问题,既然可见之后就能加载数据,那么我只在setUserVisibleHint的时候,判断是否可见来去加载数据?

其实是不可以的,通过日志我们能够发现,setUserVisibleHint是早于onCreate方法调用的,也就是说在页面还没有创建时,去加载数据有可能导致页面元素找不到发生空指针异常。

2 ViewPager2与ViewPager的区别

上一小节,我们介绍了ViewPager的加载机制和缓存机制,那么我们把整套页面搬过来,唯一发生变化的就是将ViewPager转换为ViewPager2

class MainLazyLoadAdapter2(
   activity: FragmentActivity,
   val fragments: MutableList<Fragment>
) : FragmentStateAdapter(activity) {
   override fun getItemCount(): Int {
       return fragments.size
   }
   override fun createFragment(position: Int): Fragment {
       return fragments[position]
   }

}

ViewPager2的适配器使用的是FragmentStateAdapter,因为FragmentStateAdapter继承了RecyclerView.Adapter,因此支持了横向滑动和竖向滑动

val fragments = mutableListOf<Fragment>()
for (index in 0..5) {
   fragments.add(LazyFragment(index))
}
vp_lazy_load = findViewById(R.id.vp_lazy_load)
vp_lazy_load.adapter = MainLazyLoadAdapter2(this, fragments)

用同样的方式设置了适配器,我们看下日志输出,就会发现,咦?怎么跟ViewPager不一样了

2022-08-28 14:47:11.790 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 14:47:11.792 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView

刚进来的时候,只有Fragment 1 加载了页面,并没有新建缓存页面,当我滑动到下一页的时候,也只有下一页的页面进行了重建

2022-08-28 14:47:11.790 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 14:47:11.792 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 14:47:13.948 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 14:47:13.948 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreateView

ViewPager2没有预加载机制吗?这里我们就需要看源码了,直接奔向setOffscreenPageLimit方法,我们看到跟ViewPager的setOffscreenPageLimit方法是不一样的

public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
   if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
       throw new IllegalArgumentException(
               "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
   }
   mOffscreenPageLimit = limit;
   // Trigger layout so prefetch happens through getExtraLayoutSize()
   mRecyclerView.requestLayout();
}
public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;

这里的判断条件 limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT,有一个数值能够通过,就是-1,这就意味着,ViewPager2默认是不支持预加载的

但是ViewPager2的缓存策略还是存在,因为继承了RecyclerView的Adapter,所以缓存复用机制是跟RecyclerView一致的,默认mViewCaches缓存池的大小是3

2022-08-28 15:30:00.579 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 15:30:00.579 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
2022-08-28 15:30:03.883 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreate
2022-08-28 15:30:03.884 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
2022-08-28 15:30:05.064 15514-15514/com.lay.image_process E/TAG: LazyFragment 2 onCreate
2022-08-28 15:30:05.064 15514-15514/com.lay.image_process E/TAG: LazyFragment 2 onCreateView
2022-08-28 15:30:08.997 15514-15514/com.lay.image_process E/TAG: LazyFragment 3 onCreate
2022-08-28 15:30:08.997 15514-15514/com.lay.image_process E/TAG: LazyFragment 3 onCreateView
2022-08-28 15:30:20.005 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate
2022-08-28 15:30:20.005 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView

当我们滑动到第4个Fragment的时候,注意这里跟ViewPager不一样的是,ViewPager的缓存是缓存当前页的左右两边,但是ViewPager2就是RecyclerView的缓存机制,顺序缓存;

当滑动到第4个Fragment的时候,因为缓存池大小为3,因此LazyFragment 0 就会从缓存池中移除,当再次滑动到LazyFragment 0的时候,就会重建!

所以当我们还在思考如何针对ViewPager的预加载机制做懒加载操作时,请将项目中的ViewPager迁移至ViewPager2

附录:

当你的项目中还在使用ViewPager时,建议使用当前这个懒加载框架

abstract class BaseLazyFragment<VM : ViewModel, VB : ViewBinding> : Fragment() {
   private lateinit var viewModel: VM
   private lateinit var binding: VB

private var isShow = false
   private var isViewCreated = false
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       viewModel = getViewModelInstance()
       binding = getLayoutInflate(layoutInflater)
   }

override fun onCreateView(
       inflater: LayoutInflater,
       container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View? {
       return binding.root
   }
   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)
       initView()
       isViewCreated = true
       lazyLoad()
   }
   override fun setUserVisibleHint(isVisibleToUser: Boolean) {
       super.setUserVisibleHint(isVisibleToUser)
       if (isVisibleToUser) {
           isShow = true
           lazyLoad()
       } else {
           isShow = false
       }
   }
   override fun onDestroy() {
       super.onDestroy()
       isShow = false
       isViewCreated = false
   }
   private fun lazyLoad() {
       if (isShow && isViewCreated) {
           initData()
       }
   }
   open fun initData() {}
   open fun initView() {}
   abstract fun getViewModelInstance(): VM
   abstract fun getLayoutInflate(layoutInflater: LayoutInflater): VB
}

使用方式:

class LazyFragment(val index:Int) : BaseLazyFragment<MainVM,FragmentLazy2Binding>() {
   override fun initData() {
       super.initData()
       Log.e("TAG","LazyFragment $index initData -- ")
   }
   override fun getViewModelInstance(): MainVM {
       return MainVM()
   }
   override fun getLayoutInflate(layoutInflater: LayoutInflater): FragmentLazy2Binding {
       return FragmentLazy2Binding.inflate(layoutInflater)
   }
}

来源:https://juejin.cn/post/7136832116318273543

标签:Android,ViewPagers,Fragment,缓存,优化
0
投稿

猜你喜欢

  • 解析C++哈夫曼树编码和译码的实现

    2021-11-19 05:26:25
  • Java编程中的检查型异常与非检查型异常分析

    2023-11-04 13:08:38
  • java实现压缩字符串和java字符串过滤

    2023-01-06 10:35:19
  • android音频编辑之音频裁剪的示例代码

    2021-08-13 00:30:20
  • 使用mybatis插件PageHelper实现分页效果

    2023-03-29 15:12:03
  • 基于Spring-Security自定义登陆错误提示信息

    2021-09-20 17:33:40
  • Go返回int64类型字段超出javascript Number范围的解决方法

    2023-08-27 05:15:01
  • Java中的Random()函数及两种构造方法

    2023-05-12 03:00:46
  • dotnet core链接mongodb代码实例

    2023-07-20 14:26:09
  • JAVA中 Spring定时器的两种实现方式

    2022-10-11 02:33:45
  • Android中标签容器控件的实例详解

    2023-05-09 00:45:53
  • 向一个数组中插入一个1~100的随机数

    2022-03-29 16:12:22
  • JAVA中ListIterator和Iterator详解与辨析(推荐)

    2021-06-11 16:47:19
  • java之函数式接口解读

    2022-07-16 16:32:46
  • java开发之Jdbc分页源码详解

    2021-10-28 16:06:48
  • 探究实现Aware接口的原理及使用

    2022-10-14 14:29:39
  • Android实现ListView数据动态加载的方法

    2021-11-02 23:01:02
  • MyBatis中使用foreach循环的坑及解决

    2023-11-02 12:47:51
  • c# 单例模式的实现方法

    2022-12-21 14:39:46
  • Java实现查找算法的示例代码(二分查找、插值查找、斐波那契查找)

    2022-05-28 04:17:38
  • asp之家 软件编程 m.aspxhome.com