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

news/2024/5/15 20:42:36/文章来源:https://blog.csdn.net/m0_70748458/article/details/130384728

大家看标题,可能会有点儿懵,什么是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性能优化文件 请点击免费领取

如果红色区域默认为首页,根据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 listenersif (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);}//初始化页面@NonNullpublic 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干了什么呢?代码太多了就不贴出来了,直接上图

在这里插入图片描述

如果是默认缓存(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 = falseoverride 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 = truelazyLoad()}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 = falseisShow = 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: VMprivate lateinit var binding: VBprivate var isShow = falseprivate var isViewCreated = falseoverride 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 = truelazyLoad()}override fun setUserVisibleHint(isVisibleToUser: Boolean) {super.setUserVisibleHint(isVisibleToUser)if (isVisibleToUser) {isShow = truelazyLoad()} else {isShow = false}}override fun onDestroy() {super.onDestroy()isShow = falseisViewCreated = false}private fun lazyLoad() {if (isShow && isViewCreated) {initData()}}open fun initData() {}open fun initView() {}abstract fun getViewModelInstance(): VMabstract 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)}
}

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

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

相关文章

Camtasia2023简体中文标准版免费更新下载

Camtasia专业的 屏幕录制和视频剪辑软件3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和Mac上进行录屏和剪辑创作专业外观的视频变得更为简单。 Camt…

一家传统制造企业的上云之旅,怎样成为了数字化转型典范?

众所周知&#xff0c;中国是一个制造业大国。在想要上云以及正在上云的企业当中&#xff0c;传统制造企业也占据了相当大的比例。 那么这类企业在实施数字化转型的时候&#xff0c;应该如何着手&#xff1f;我们不妨来看看一家传统制造企业的现身说法。 国茂股份的数字化转型诉…

mysql免安装版本(简化版)

1&#xff1a;解压mysql-5.7.26-winx64 2&#xff1a;添加data文件夹 3&#xff1a;添加my.ini文件 内容如下&#xff1a; port "3306" # 设置mysql的安装目录 basedir "D://tools\mysql-5.7.26-winx64\mysql-5.7.26-winx64\" # 设置mysql数据库的数…

软件测试面试一定要看的面试题和笔试题全套教程

1、什么是软件测试&#xff1f;2’ 【要点】 在规定条件下对程序进行操作&#xff0c;以发现错误&#xff0c;对软件质量进行评估&#xff0c;包括对软件形成过程的文档、数据以及程序进行测试。 【详解】 软件测试就是在软件投入运行前对软件需求分析、软件设计规格说明书…

【社区图书馆】PyTorch高级机器学习实战

PyTorch高级机器学习实战 作者&#xff1a;王宇龙&#xff0c;清华大学计算机博士&#xff0c;大型互联网公司算法专家&#xff0c;在国际学术会议及期刊发表过多篇论曾出版书籍《PyTorch深度学习入门与实战》&#xff0c;知乎"机器学习”话题优秀回答者。 亮点&#xf…

ssm+java企业公司产品分销商管理系统

一、 二、经营管理&#xff1a; ①分销商每月提交自己进多少货物&#xff08;从总部进购了多少“鹊巢”的商品给自己负责区的大型商超&#xff09;——对应的种类一共进多少货物&#xff1b;该种类中具体的产品又进了多少货物具体到&#xff08;参考三产品管理模块&#xff09;…

PCIE内核注册详解

代码结构 在Linux内核中&#xff0c;PCIe驱动程序的注册和处理涉及到许多文件&#xff0c;其中一些主要的文件包括&#xff1a; drivers/pci/pci.h&#xff1a;这个文件定义了PCIe驱动程序结构体和相关的函数。驱动程序需要包含这个头文件才能使用PCIe相关的函数和结构体。 d…

李宏毅 深度学习

目录 深度学习与自然语言处理 | 斯坦福CS224n 课程带学与全套笔记解读&#xff08;NLP通关指南完结&#xff09;pytorch快速入门csdn快速入门OS包PIL包Opencv包Dataset类Tensorboard的使用torchvision.transforms 的使用torchvision中数据集的使用DataLoader的使用(torch.util…

Git在工作中的使用流程

Git中的分支 master分支&#xff1a;所有用户可见的正式版本&#xff0c;都从master发布&#xff08;也是用于部署生产环境的分支&#xff0c;确保master分支稳定性&#xff09;。主分支作为稳定的唯一代码库&#xff0c;不做任何开发使用。master 分支一般由develop以及hotfi…

从“恰当”的项目管理工具中,了解自己的缺点

项目管理工具是为了帮助管理者&#xff0c;但管理者需要了解自己在特定情况下的“缺点”&#xff0c;才能从“恰当”的工具中获得“恰当”的帮助。如果你不知道在某个特定项目中自己&#xff08;作为项目经理&#xff09;的缺点&#xff0c;也不知道自己需要利用哪些好用的项目…

记录-因为写不出拖拽移动效果,我恶补了一下Dom中的各种距离

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 背景 最近在项目中要实现一个拖拽头像的移动效果&#xff0c;一直对JS Dom拖拽这一块不太熟悉&#xff0c;甚至在网上找一个示例&#xff0c;都看得云里雾里的&#xff0c;发现遇到最大的拦路虎就是JS…

2022年NOC大赛创客智慧编程赛道Python初赛题,包含答案

目录 一、单选题 二、多选题 三、判断题 下载文档打印做题: NOC Python 初赛考题 一、单选题 <

【大数据之Hadoop】十八、MapReduce之压缩

1 概述 优点&#xff1a;减少磁盘IO、减少磁盘存储空间。 缺点&#xff1a;因为压缩解压缩都需要cpu处理&#xff0c;所以增加CPU开销。 原则&#xff1a;运算密集型的Job&#xff0c;少用压缩&#xff1b;IO密集型的Job&#xff0c;多用压缩。 2 压缩算法对比 压缩方式选择时…

IDEA 新版安装教程

目录 一、安装IDEA 1、双击安装&#xff0c;然后下一步 2、修改默认安装路径&#xff0c;自定义目录。(建议所有开发工具都放在同一个盘符) 3、改为自定义安装路径&#xff0c;下一步。&#xff08;不用使用中文或空格&#xff09; 4、创建桌面图标等 5、点击安装&#x…

面板数据进行熵值法

面板数据熵值法分析流程如下&#xff1a; 一、案例背景 当前有9家公司连续5年&#xff08;2018-2022年&#xff09;的财务指标数据&#xff0c;想要通过这份数据&#xff0c;确定各个财务指标的权重。熵值法根据指标离散程度确定赋权大小&#xff0c;客观公正准确度高。本次收…

DJ4-5 路由和选路

目录 一、路由与转发的相互作用 二、路由的基本概念 1. 默认路由器 2. 路由算法 三、网络的抽象模型 1. 节点图 2. 费用 Cost 四、路由算法分类 1. 静态路由算法 2. 动态路由算法 3. 全局路由算法 4. 分布式路由算法 一、路由与转发的相互作用 二、路由的基本概念 …

【1G-6G】移动通信技术发展

移动通信技术发展 1G 早在1947年&#xff0c;贝尔实验室的科学家就提出了蜂窝通信的概念&#xff0c;在20世纪60年代对此进行了系统的实验。20世纪60年代末、70年代初开始出现了第一个蜂窝&#xff08;Cellular&#xff09;系统。蜂窝的意思是将一个大区域划分为若干个相邻的…

通过使用生成对抗市场模型改进基于强化学习的交易的泛化

Improving Generalization in Reinforcement Learning–Based Trading by Using a Generative Adversarial Market Model | IEEE Journals & Magazine | IEEE Xplore Improving Generalization in Reinforcement Learning–Based Trading by Using a Generative Adversaria…

使用Docker部署wikitten个人知识库

使用Docker部署wikitten个人知识库 一、wikitten介绍1.wikitten简介2.wikitten特点 二、本地实践环境介绍三、本地环境检查1.检查Docker服务状态2.检查Docker版本 四、部署wikitten个人知识库1.创建数据目录2.下载wikitten镜像3.创建wikitten容器4.查看wikitten容器状态5.检查w…

内网渗透的一些tips

声明&#xff1a;文中涉及到的技术和工具&#xff0c;仅供学习使用&#xff0c;禁止从事任何非法活动&#xff0c;如因此造成的直接或间接损失&#xff0c;均由使用者自行承担责任。 每周不定时持续分享各种干货。 众亦信安&#xff0c;中意你啊&#xff01; 一.密码抓取 平…