jetpack之ViewModel

2023-05-16

ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。

摘自官方文档

Android 框架可以管理界面控制器(如 Activity 和 Fragment)的生命周期。Android 框架可能会决定销毁或重新创建界面控制器,以响应完全不受您控制的某些用户操作或设备事件。

如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。因配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState()方法中保存从 onCreate()中恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

另一个问题是,界面控制器经常需要进行可能需要一些时间才能返回的异步调用。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。此项管理需要大量的维护工作,并且在为配置更改重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。

诸如 Activity 和 Fragment 之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。 Activity 和 Fragment 太多的逻辑控制代码降低了代码阅读性和可维护性。

从界面控制器逻辑中分离出视图数据所有权的操作更容易且更高效。

ViewModel 的生命周期

ViewModel对象存在的时间范围是获取 ViewModel时传递给 ViewModelProvider的Lifecycle。ViewModel将一直留在内存中,直到限定其存在时间范围的 Lifecycle永久消失:对于 activity,是在 activity finished的时候;而对于 fragment,是在 fragment detach时候

我们通常在Activity 的onCreate()方法中创建ViewModel(避免重复创建).系统可能会在Activity的整个生命周期内多次调用onCreate()方法,例如旋转手机屏幕的时候。ViewModel存在的时间范围是从首次创建ViewModel直到activity finished 并销毁

ViewModel的创建

ViewModel使用很简单,我们只需要继承ViewModel类就行了

class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityViewModelBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val model  = ViewModelProvider(this).get(MyViewModel::class.java)
        model.name.observe(this){
            //update UI 的操作
        }
    }
}

在Activity中,我们可以使用ViewModelProvider来得到 ViewMode的实例。

如果在ViewModel中,我们需要使用到上下文Context对象(toast 或者获取系统服务等等),我们可以继承AndroidViewModel来构建ViewModel

class AndroidModel(app:Application): AndroidViewModel(app) {
    
}

此时这个AndroidModel的创建和上面的类似,也是用ViewModelProvider来获得。

ViewModel常常结合LiveData使用,然后在我们的Activity或者Fragemnt中去监听LiveData的改变

然后去在Activity中做UI的更新逻辑,例如,我们需要根据网络请求来决定是否弹出一个DialogFragment,我们的网络请求放在ViewModel中,DialogFragment必须在Activity或者Fragment中弹出来(因为DialogFragment的弹出不能使用Application作为Context),所以此时我们必须使用LiveData。当网络请求完成之后,我们改变LiveData的值,并且在Activity或者Fragment监听LiveData的变化,然后作出弹出DailogFragment的操作

在Fragment之间共享数据

现在的App中使用Fragment是很常见的,之前我们从Activity中向Fragment中传递数据,我们使用Bundle来传递(在创建Fragment的时候),但是在Activityt中如果需要动态传递(随时传递)数据给Fragment,我们平常的做法可能是在Activity中持有Fragment的应用,然后在Activity中去调用对于Fragment的某些方法传递数据,或者利用通知系统(EventBus),但是我们如果在BFragment中促使Activity中的数据改变,要通知到CFragment的Ui修改的话,目前的场景只能使用EventBus。当然也可以使用我们这里的ViewModel了

class ShareViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class ListFragment : Fragment() {

    private lateinit var itemSelector: Selector

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: ShareViewModel?=null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model = ViewModelProvider(requireActivity()).get(ShareViewModel::class.java)
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: ShareViewModel?=null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        model = ViewModelProvider(requireActivity()).get(ShareViewModel::class.java)
        model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
            // Update the UI
        })
    }
}

在上面的两个Fragment 在获取ViewModel的时候 传递的都是requireActivity(),那么获取到的ViewModel的实例其实就是同一个SharedViewModel,所以当Activity 或者任何一个Fragment中 改变了SharedViewModel中LiveData的数据,都会及时的通知到。这种方法有以下好处:

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  • 除了 SharedViewModel之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

ViewModel的源码解析

ViewModel的代码很简单

这里基本只需要知道onCleared()方法就行了,自定义ViewModel并重写这个方法,讲释放资源的逻辑放在这个方法中就行

ViewModelProvider类

获取ViewModel的实例时,我们是使用ViewModelProvider来获取的

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

创建ViewModelProvider的时候需要传递一个ViewModelStore 和一个Factory,而我们构建的时候只传递了一个this(Activity),其实就是一个ViewModelStoreOwner,

AppCompatActivity -->FragmentActivity -->ComponentActivity --> ViewModelStoreOwner

有上面这样一个继承实现关系,我们的AppCompatActivity其实可以说是实现了ViewModelStoreOwner的,最终返回的是ComponentActivity中的mViewModelStore 关于Factory后面再讲


@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}

@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

这里就是获取ViewModelStore的方法,可以看到在ensureViewModelStore方法中,我们会首先判断mViewModelStore 是否为null,然后通过getLastNonConfigurationInstance() 得到一个NonConfigurationInstances 实例,这里其实就是当Activity旋转的时候ViewModel中的数据还会存在的奥秘,通过nc 可以获取重建之前的mViewModelStore,然后从ViewModelStore里面根据类名获取ViewModel的实例,所以获取到的ViewModel在旋转前后其实是同一个实例,在我们的App系统configuration发生改变的时候 就会回调onRetainNonConfigurationInstance()这个方法


@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
    // Maintain backward compatibility.
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

//Activity中 的方法
@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

这里就是将ViewModelStore进行保存。 这里getLastNonConfigurationInstance 方法 最后其实是返回Activity中的mLastNonConfigurationInstances 变量的activity对象,我们看看这mLastNonConfigurationInstances 在哪里赋值,我们知道,我们的Activity的启动其实最终都会走到ActivityThread类中,当我们启动一个Activity的时候会执行其中的 performLaunchActivity 方法最终会调用到Activity的attach方法,lastNonConfigurationInstances是存在 ActivityClientRecord中的一个组件信息 

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    if (r != null) {
        activityClass = r.activity.getClass();
        r.activity.mConfigChangeFlags |= configChanges;
        if (finishing) {
            r.activity.mFinished = true;
        }

        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to retain activity "
                            + r.intent.getComponent().toShortString()
                            + ": " + e.toString(), e);
                }
            }
        }
        try {
            r.activity.mCalled = false;
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + safeToComponentShortString(r.intent) +
                    " did not call through to super.onDestroy()");
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException(
                        "Unable to destroy activity " + safeToComponentShortString(r.intent)
                        + ": " + e.toString(), e);
            }
        }
        r.setState(ON_DESTROY);
    }
    schedulePurgeIdler();
    // updatePendingActivityConfiguration() reads from mActivities to update
    // ActivityClientRecord which runs in a different thread. Protect modifications to
    // mActivities to avoid race.
    synchronized (mResourcesManager) {
        mActivities.remove(token);
    }
    StrictMode.decrementExpectedActivityCount(activityClass);
    return r;
}

在屏幕旋转造成的的Activity重建的时候 就会给lastNonConfigurationInstances 这个变量赋值,这样就能够在Activity重建的时候 获取到之前的ViewModel了。而我们的ViewModel中onClear方法什么时候执行呢,在我们的ComponetActivity构方法中


public ComponentActivity() {
    // ......省略部分代码
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
    }

上面注册了一个Lifecycle的监听,在我们的Activity在onDestory之后 并且isChangingConfigurations()为false的时候,才会去执行getViewModelStore().clear(); 的操作间接调用到ViewModel的onCleared()方法

get()获取ViewModel

在这里通过get方法创建ViewModel 传入的参数是 所需要创建的ViewModel的Class对象

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}


@SuppressWarnings("unchecked")
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        viewModel = mFactory.create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

最终通过下面的get方法获取ViewModel,当我们从ViewModelStore 根据key值去获取ViewModel为null的时候,如果为null 就是用Factory进行创建。所以后面我们也可以定义自己的Factory 来创建ViewModel

ViewModelStore类

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

这里其实就是一个 HashMap存放了ViewModel,主要是ViewModel的存取

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

jetpack之ViewModel 的相关文章

  • ASP.net MVC - 用于 POST 操作的单独 ViewModel

    在我的 MVC 应用程序中 我有一个与此类似的视图模型 public class ComplexViewModel public ComplexDetailsViewModel Details1 get set public Complex
  • ASP.NET MVC ViewModel 和 DropDownList

    我的 ViewModel 有 2 个属性 class ViewModel1 Dictonary
  • 如何使用 MVC 3 和 Entity Framework Code First 建立多对多关系的 ModelBind?

    我在 MVC 3 应用程序中遇到了同样的问题 我有一个创建新产品的视图 并且该产品可以分配给一个或多个类别 这是我的 EF Code First 模型类 public class Product public int ProductID g
  • MVVM:绑定一个 ViewModel,它将构造函数参数传递给 UserControl

    我的 WPF 应用程序有一个 MainWindow 其中包含一个名为 Tvshow GridView 的用户控件 主窗口
  • MVC3 使用 CheckBox 和复杂的视图模型

    对了 伙计们 我需要你的智慧 因为我找不到正确的方法 我有一个视图模型 public class EditUserViewModel public User User public IQueryable
  • Asp.net MVC Razor 页面上有多个表单

    Yo 我的网站上有一个注册页面 页面顶部是现有用户的登录表单 主区域有登记表 登录区域是部分视图 model ViewModels LoginViewModel注册区域也是部分的 model ViewModels RegViewModel
  • 从 Mvc 3 视图填充列表

    我有一个基于 Nominees 的 Viewmodel 我可以为视图模型指定多个提名者 我想从视图中填充 Ilist 这是我的视图模型 public class DebitViewModel IValidatableObject publi
  • 具有 ViewModel 和行为的 Xamarin 视图

    我有一个视图 其中包含当前用户的用户配置文件 该视图包含一个视图模型 该模型具有与用户名或电子邮件地址等单个属性的双向绑定 该视图还有一个行为 它会验证输入并显示输入无效的错误 由于双向绑定 即使行为表明输入错误 视图模型也会更新值 我需要
  • MVC 映射到模型中可为 null 的 bool

    使用包含该字段的视图模型 public bool IsDefault get set 尝试在视图中映射时出现错误 无法隐式转换类型 bool 布尔 存在显式转换 您是否缺少强制转换 我尝试过铸造并使用 Value但都不起作用 请注意 我想要
  • 如何测试启动 viewModelScope 协程的 ViewModel 函数?安卓科特林

    我试图找出在函数成员上测试这种类型的最简单方法 我见过更复杂的情况 例如协程 单元测试 viewModelScope launch 方法 https stackoverflow com questions 55765190 coroutin
  • 如何观察数据库的变化以更新LiveData

    我正在从以下位置迁移应用程序LoaderManager with Callbacks到一个实现使用ViewModel and LiveData 我想继续使用现有的SQLiteDatabase 主要实现工作正常 这Activity实例化Vie
  • ASP.NET MVC 模型/视图模型验证

    我在 Linq to Sql 中有模型类 其中部分类标记有数据注释属性和对 xVal 的引用 当我将视图直接绑定到模型时 一切都很好 xVal 生成的 JS 和服务器端都进行了双重检查 我的许多视图不接受某一特定模型的输入 因此我正在设置视
  • 通用视图模型?

    我想知道尝试创建一个采用通用视图模型的视图是否是一种好的做法 我想知道这一点 因为有人提到他预计必须执行大量重复代码 除非他开始制作通用视图和通用视图模型 所以基本上视图就像一组控件 一个视图可能有 2 个控件 例如文本框和单选按钮 另一个
  • 显示一个子虚拟机,然后在第一个关闭后显示另一个子虚拟机

    我有一位家长指挥 我想显示其中的第一个视图模型 然后在第一个关闭后 即完成一些操作 我想显示一个不同的视图模型 我在用着Caliburn Micro Contrib https github com kmees CMContrib 其中一个
  • 为什么 Android 中需要视图模型工厂?

    我们一直在讨论这个问题 但不知道为什么要创建viewmodel工厂来创建viewmodel而不是直接实例化viewmodel 创建一个只创建视图模型的工厂有什么好处 我只是举了一个简单的例子来说明我如何在没有工厂的情况下做到这一点 这是 k
  • asp.net mvc 模型与实体框架模型

    在 asp net mvc model 文件夹中创建模型是最佳实践吗 将这些模型与您的视图结合使用 并使用服务层将我的模型 调整 为 EF 模型 或者您是否使用过其他方法 这种方法的问题在于 大多数时候我的 自制 模型是 EF 模型的副本
  • 在 ASP.NET MVC ViewModel 中存储模型 ID,安全问题

    在我的 MVC 应用程序中 我有一个页面供用户编辑其帐户详细信息 例如电子邮件地址 密码等 在我的数据库中 用户表保存此数据 主键是 UserId 在我创建的 ChangeAccountDetails 视图上 我传递了一个 ViewMode
  • “by viewModels()”Kotlin 属性委托未解析的引用

    我正在尝试用 kotlin 实现 viewmodel 首先我添加了所需的依赖项 implementation androidx appcompat appcompat 1 1 0 implementation androidx core c
  • MVVMCross 在 MvxBindableListView 中更改 ViewModel

    我的 Android 应用程序出现了一些小问题 我不知道如何使用 MVVM Cross 来解决它 这是我的模型 public class Article string Label get set string Remark get set
  • 错误:此 PlotModel 已被其他 PlotView 控件使用

    我有两个选项卡绑定到一个viewmodel其中包含一个PlotModel of oxyplot并查看通过选择的模型DataTemplate 当单击第一个选项卡时viewmodel已正确绑定 但当切换到标题中定义的上面第二个选项卡时 会抛出异

随机推荐

  • C语言基础----流程控制

    流程控制是C语言中比较基础的 它分为三种状态 xff1a 1是顺序结构 2是选择结构 3是循环结构 我要说明后两种结构 xff0c 选择机构和循环结构 首先先说 xff1a 选择结构 选择结构是指 xff1a 当一个条件成立则执 xff08
  • 复杂数据类型——数组

    复杂数据类型是C语言基础的重点 1 数组 xff1a 存储一组数据 2 特点 xff1a 只能存放一种类型的数据 如int类型 xff0c float类型的数据 数组的元素个数只可以放常量 int ages 5 61 1 2 3 格式 xf
  • OC语言——基本语法和思想

    今天学习了OC语言基础语法 1 oc语言完全兼容C语言 xff0c 后缀为 m类型 被广泛应运与开发苹果mac os x平台和ios开发平台 2 oc语言关键字基本上以 64 开头 xff0c oc字符串也是以 64 开头 3 基本类型新加
  • OC语言——三大特性-继承与多态

    继承是oc中比较常见的 1 继承 xff1a 就是当两个类拥有相同的属性和方法时 xff0c 就可以将相同的东西抽取到一个父类中 子类可以拥有父类中所有的成员变量和方法 2 继承的好处 xff1a 可以抽取重复代码 xff0c 节省时间 建
  • OC语言——点语法和成员变量的4种作用域及property和synthesize的使用

    点语法 xff1a 点语法的本质还是方法调用 Person p 61 Person new 点语法的本质还是方法调用 p age 61 10 p setAge 10 一 点语法注意点 xff1a 64 implementation Pers
  • 树排序的理解

    参考文献与详细资料 xff1a https blog csdn net weixin 64067830 article details 124443430 视频 https www bilibili com video BV1iU4y1B7
  • OC语言——构造方法和分类的使用

    一 构造方法 1调用 43 alloc分配存储空间 Person p 61 Person alloc 2初始化 init Person p1 61 p init 可以整合为一句 Person p2 61 Person alloc init
  • 使用CSDN-markdown

    欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来 xff0c 用它写博客 xff0c 将会带来全新的体验哦 xff1a Markdown和扩展Markdown简洁的语法代码块高亮图片链接和图片上传
  • 【笔试&面试】关于动态链接库

    动态链接库英文为DLL xff0c 是Dynamic Link Library 的缩写形式 xff0c DLL 是一个包含可由多个程序同时使用的代码和数据的库 xff0c DLL 不是可执行文件 动态链接提供了一种方法 xff0c 使进程可
  • 虚函数表的实现细节

    1 虚函数 虚表是怎么实现的 xff1f 虚表存放在哪里 xff1f 虚表中的数据是在什么时候确定的 xff1f 对象中的虚表指针又在什么时候赋值的 xff1f 我们很难通过 C 43 43 语言本身来找到答案 C 43 43 标准给编译器
  • 三种工厂模式区别总结

    工厂模式分为三种 xff1a 简单工厂 工厂模式和抽象工厂模式 三者之间存在哪些异同呢 xff1f 先分别看看各个模式的特点 一 简单工厂模式 xff1a 实现了算法和界面的分离 xff0c 也就是将业务逻辑和界面逻辑分开了 xff0c 降
  • 快速排序 改进快排的方法

    快速排序法事应用最广泛的排序算法之一 xff0c 最佳情况下时间复杂度是 O nlogn 但是 最坏情况下可能达到 O n 2 说明快速排序达到最坏情况的原因 并提出改善方案并实现 之 答 xff1a 改进方案 xff1a 改进选取枢轴的方
  • linux select函数详解

    在 Linux 中 xff0c 我们可以使用 select 函数实现 I O 端口的复用 xff0c 传递给 select 函数的参数会告诉内核 xff1a 我们所关心的文件描述符 对每个描述符 xff0c 我们所关心的状态 我们是要想从一
  • Linux epoll详解

    Linux epoll详解 一 什么是epoll epoll是什么 xff1f 按照man手册的说法 xff1a 是为处理大批量句柄而作了改进的poll 当然 xff0c 这不是2 6内核才有的 xff0c 它是在2 5 44内核中被引进的
  • 转折后的总结--2014年找工作

    转折后的总结 找工作 好吧 xff0c 还是忍不住做个总结 xff0c 毕竟还是我人生中一次比较大的事件了 非常感谢华科 xff0c 我的第二个母校能提供给我一个优秀的平台 非常感谢信息安全与保密实验室607室的老师们 xff0c 给我诸多
  • 2014找工作总结-机会往往留给有准备的人

    好基友的文章必须转 xff0c 大神一枚 xff1a 出处 xff1a http blog csdn net xiajun07061225 article details 12844801 其实我的求职过程在十一之前就已经结束了 xff0c
  • 1020 Tree Traversals

    Suppose that all the keys in a binary tree are distinct positive integers Given the postorder and inorder traversal sequ
  • 入职后的书单

    程序员一生的命运就是不停的学习 xff0c 淬炼自己的技术 xff0c 转化为自己的经验 作为新手 xff0c 首先要读的应该是 xff1a 代码整洁之道 1 JAVA核心技术 xff08 卷1 xff09 作者 Cay S Horstma
  • 国内第一本详解云GIS技术的参考书籍《云GIS技术与实践》

    书籍封面 内容简介 云计算技术从概念提出到项目落地已经经历了十余年了 xff0c GIS技术也紧跟IT主流技术大潮 xff0c 通过日趋成熟的云计算技术来解决GIS面临的诸多问题 一转眼 xff0c 云GIS技术也发展了五个年头了 xff0
  • jetpack之ViewModel

    ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据 ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存 摘自官方文档 Android 框架可以管理界面控制器 xff08 如 Activity 和 Fragmen