LayoutInflater源码解析及常见相关报错分析

在日常Android开发中,最经常使用的RecyclerView控件是大家都绕不开的,而编写其Adapter时更离不开LayoutInflater的调用。当然,如果你做这一行有些时日了,相信你对其使用一定是炉火纯青了。即使如此,我觉得LayoutInflater仍旧有值得分析的地方,相信你看完之后有更多的认识。Android系统中有许多包括ActivityManagerService在内的系统级服务,我们平时在使用时可通过Context调用,这些服务会在安卓系统初始化时以单例的形式注册,其中LayoutInflater服务就是其中之一。

我们常在RecyclerView.Adapter的onCreateViewHolder()中通过LayoutInflater将xml编写的布局文件转换成Android中的一个View对象:

@NonNull
@NotNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_recycler_item, parent,false);ViewHolder viewHolder = new ViewHolder(inflate);return viewHolder;
}

其实不仅仅是onCreateViewHolder(),在Activity中的setContentView()中内部也是通过LayoutInflater将xml布局转换成View。

@Override
public void setContentView(int layoutResID) {...if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {mLayoutInflater.inflate(layoutResID, mContentParent);}...
}  //SDK里PhoneWindow.java中

而我们最常使用的关于LayoutInflater的方法莫过于下面几个:

1、View.inflate(mContext,R.layout.layout_sence_recycler_item,null);
2、LayoutInflater.from(mContext).inflate(R.layout.layout_sence_recycler_item,parent);
3、LayoutInflater.from(mContext).inflate(R.layout.layout_sence_recycler_item,parent,false);
4、LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);View view = inflater.inflate(R.layout.main, null);

.from(Context)方法原理暂时先不做详细讲解,其内部调用的是ContextImpl类中的getSystemService(),因此其实要说的是getSystemService(),这方法代码流程过多且与本文主旨无关不做赘述。

我们现在进去看看inflate()相关源码(在相关方法上按住Ctrl+鼠标左键点击),会发现其中总共四种重载方法:

image.png
第一种方法:

/*** Inflate a new view hierarchy from the specified xml resource. Throws* {@link InflateException} if there is an error.** @param resource ID for an XML layout resource to load (e.g.,*        <code>R.layout.main_page</code>)* @param root Optional view to be the parent of the generated hierarchy.* @return The root View of the inflated hierarchy. If root was supplied,*         this is the root View; otherwise it is the root of the inflated*         XML file.*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);
}

第二种方法:

/*** Inflate a new view hierarchy from the specified xml node. Throws* {@link InflateException} if there is an error. ** <p>* <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance* reasons, view inflation relies heavily on pre-processing of XML files* that is done at build time. Therefore, it is not currently possible to* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.** @param parser XML dom node containing the description of the view*        hierarchy.* @param root Optional view to be the parent of the generated hierarchy.* @return The root View of the inflated hierarchy. If root was supplied,*         this is the root View; otherwise it is the root of the inflated*         XML file.*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {return inflate(parser, root, root != null);
}

第三种方法:

/*** Inflate a new view hierarchy from the specified xml resource. Throws* {@link InflateException} if there is an error.** @param resource ID for an XML layout resource to load (e.g.,*        <code>R.layout.main_page</code>)* @param root Optional view to be the parent of the generated hierarchy (if*        <em>attachToRoot</em> is true), or else simply an object that*        provides a set of LayoutParams values for root of the returned*        hierarchy (if <em>attachToRoot</em> is false.)* @param attachToRoot Whether the inflated hierarchy should be attached to*        the root parameter? If false, root is only used to create the*        correct subclass of LayoutParams for the root view in the XML.* @return The root View of the inflated hierarchy. If root was supplied and*         attachToRoot is true, this is root; otherwise it is the root of*         the inflated XML file.*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}
}

第四种方法较长:

/*** Inflate a new view hierarchy from the specified XML node. Throws* {@link InflateException} if there is an error.* <p>* <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance* reasons, view inflation relies heavily on pre-processing of XML files* that is done at build time. Therefore, it is not currently possible to* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.** @param parser XML dom node containing the description of the view*        hierarchy.* @param root Optional view to be the parent of the generated hierarchy (if*        <em>attachToRoot</em> is true), or else simply an object that*        provides a set of LayoutParams values for root of the returned*        hierarchy (if <em>attachToRoot</em> is false.)* @param attachToRoot Whether the inflated hierarchy should be attached to*        the root parameter? If false, root is only used to create the*        correct subclass of LayoutParams for the root view in the XML.* @return The root View of the inflated hierarchy. If root was supplied and*         attachToRoot is true, this is root; otherwise it is the root of*         the inflated XML file.*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];  //获取Context对象mConstructorArgs[0] = inflaterContext;    View result = root;    //存储父视图try {advanceToRootNode(parser);    final String name = parser.getName();if (DEBUG) {System.out.println("**************************");System.out.println("Creating root view: "+ name);System.out.println("**************************");}if (TAG_MERGE.equals(name)) {   //判断是否是merge标签if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, inflaterContext, attrs, false);} else {// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);  //解析单个元素,实例化xml布局的根view对象ViewGroup.LayoutParams params = null;if (root != null) {if (DEBUG) {System.out.println("Creating params from root: " +root);}// Create layout params that match root, if suppliedparams = root.generateLayoutParams(attrs);        // 创建匹配root对象的布局参数if (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)temp.setLayoutParams(params);        // attachToRoot:传进来的参数,如果该view不需要添加到父布局上,则直接将根据父布局生成的params参数来设置}}if (DEBUG) {System.out.println("-----> start inflating children");}// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);if (DEBUG) {System.out.println("-----> done inflating children");}// We are supposed to attach all the views we found (int temp)// to root. Do that now.if (root != null && attachToRoot) {root.addView(temp, params);  //如果传入的父布局不为null,且attachToRoot为true,则给temp设置布局参数,将实例化的view对象加入到父布局root中}// Decide whether to return the root that was passed in or the// top view found in xml.if (root == null || !attachToRoot) {  //如果传入的父布局为null,且attachToRoot为false,则返回tempresult = temp;}}} catch (XmlPullParserException e) {final InflateException ie = new InflateException(e.getMessage(), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(getParserStateDescription(inflaterContext, attrs)+ ": " + e.getMessage(), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} finally {// Don't retain static reference on context.mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;Trace.traceEnd(Trace.TRACE_TAG_VIEW);}return result;}
}

继续跟随前三种重载方法后,你会发现最后都绕到了第四种重载方法里。

此方法可见总共三个参数:

参数一、xml解析器;

参数二、解析布局的父视图;

参数三、是否将要解析的视图添加进父视图中(是否要将当前加载的xml布局添加到第二个参数传入的父布局上面)。

关于XmlPullParser就不详解了,里面解析xml的过程较长,也与本文主旨无关。这里看一个重要方法advanceToRootNode():

/*** Advances the given parser to the first START_TAG. Throws InflateException if no start tag is* found.*/
private void advanceToRootNode(XmlPullParser parser)throws InflateException, IOException, XmlPullParserException {// Look for the root node.int type;   //while循环解析查找xml标签,START_TAG是开始标签,END_DOCUMENT是结束标签while ((type = parser.next()) != XmlPullParser.START_TAG &&type != XmlPullParser.END_DOCUMENT) {// Empty}if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}
}

根据以上代码,我们可以概括其inflate()流程如下:

1、解析xml布局文件中根标签(第一个元素);

2、如果根标签是merge标签,则调用rInflate()来将merge标签下所有子View添加到根标签中;

3、如果不是merge标签,则调用createViewFromTag();

4、调用rInflateChildren()解析temp下所有子View,并将其添加到temp下;

5、根据设置的参数判断返回对应视图。

可以说整段代码就是一个将xml解析成View结构的过程,其过程中细节实现靠rInflate、createViewFromTag和rInflateChildren方法,而rInflateChildren内部又是调用rInflate,因此我们看createViewFromTag()和rInflateChildren()即可。

createViewFromTag

其源码如下:

@UnsupportedAppUsage
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {if (name.equals("view")) {name = attrs.getAttributeValue(null, "class");}// Apply a theme wrapper, if allowed and one is specified.if (!ignoreThemeAttr) {            //主题相关final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);final int themeResId = ta.getResourceId(0, 0);if (themeResId != 0) {context = new ContextThemeWrapper(context, themeResId);}ta.recycle();}try {View view = tryCreateView(parent, name, context, attrs);  //通过tryCreateView返回view对象  if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) {       //内置view控件的解析view = onCreateView(context, parent, name, attrs);} else {view = createView(context, name, null, attrs);        //自定义View的解析}} finally {mConstructorArgs[0] = lastContext;}}return view;} catch (InflateException e) {throw e;} catch (ClassNotFoundException e) {final InflateException ie = new InflateException(getParserStateDescription(context, attrs)+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(getParserStateDescription(context, attrs)+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;}
}

继续看tryCreateView()的内部实现:

@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
public final View tryCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context,@NonNull AttributeSet attrs) {if (name.equals(TAG_1995)) {// Let's party like it's 1995!return new BlinkLayout(context, attrs);}View view;if (mFactory2 != null) {view = mFactory2.onCreateView(parent, name, context, attrs);    //用户可以设置Factory来解析View,此对象默认为Null,可以忽略} else if (mFactory != null) {view = mFactory.onCreateView(name, context, attrs);        //用户可以设置Factory来解析View,此对象默认为Null,可以忽略} else {view = null;}if (view == null && mPrivateFactory != null) {view = mPrivateFactory.onCreateView(parent, name, context, attrs);     //用户可以设置Factory来解析View,此对象默认为Null,可以忽略}return view;
}

所以具体流程下来,你会发现,最终调用的还是:

if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) {view = onCreateView(context, parent, name, attrs);} else {view = createView(context, name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}
}

核心判断还是在这里,createViewFromTag的参数会将该元素的parent及名字传过来,我们修改下核心逻辑部分,即可为下面:

if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) {view = createView(name, "android.view.", attrs);} else {view = createView(name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}
}

相信这里你就理解了,如果这个名字在查找“.”返回-1即没有包含“.”时, 则认为是一个内置View,调用onCreate(),反之,调用createView()。

createView()

protected View onCreateView(String name, AttributeSet attrs)throws ClassNotFoundException {return createView(name, "android.view.", attrs);
}

其实onCreateView()最终还是调用的createView(),所以我们这里看createView()就行了。

@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,@Nullable String prefix, @Nullable AttributeSet attrs)throws ClassNotFoundException, InflateException {Objects.requireNonNull(viewContext);Objects.requireNonNull(name);Constructor<? extends View> constructor = sConstructorMap.get(name);       //    全局缓存if (constructor != null && !verifyClassLoader(constructor)) {constructor = null;sConstructorMap.remove(name);}Class<? extends View> clazz = null;try {Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);if (constructor == null) {            //判断缓存是否为空,为空则自行反射加载// Class not found in the cache, see if it's real, and try to add itclazz = Class.forName(prefix != null ? (prefix + name) : name, false,mContext.getClassLoader()).asSubclass(View.class);if (mFilter != null && clazz != null) {boolean allowed = mFilter.onLoadClass(clazz);if (!allowed) {failNotAllowed(name, prefix, viewContext, attrs);}}constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);sConstructorMap.put(name, constructor);            //添加到缓存} else {// If we have a filter, apply it to cached constructorif (mFilter != null) {// Have we seen this name before?Boolean allowedState = mFilterMap.get(name);if (allowedState == null) {// New class -- remember whether it is allowedclazz = Class.forName(prefix != null ? (prefix + name) : name, false,mContext.getClassLoader()).asSubclass(View.class);boolean allowed = clazz != null && mFilter.onLoadClass(clazz);mFilterMap.put(name, allowed);if (!allowed) {failNotAllowed(name, prefix, viewContext, attrs);}} else if (allowedState.equals(Boolean.FALSE)) {failNotAllowed(name, prefix, viewContext, attrs);}}}Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = viewContext;Object[] args = mConstructorArgs;args[1] = attrs;try {final View view = constructor.newInstance(args);                //创建新View实例,args是自定义主题相关的变量if (view instanceof ViewStub) {                    // 如果是ViewStub,则用同一个Context加载这个ViewStub的LayoutInflater// Use the same context when inflating ViewStub later.final ViewStub viewStub = (ViewStub) view;viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}return view;} finally {mConstructorArgs[0] = lastContext;}} catch (NoSuchMethodException e) {final InflateException ie = new InflateException(getParserStateDescription(viewContext, attrs)+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (ClassCastException e) {// If loaded class is not a View subclassfinal InflateException ie = new InflateException(getParserStateDescription(viewContext, attrs)+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (ClassNotFoundException e) {// If loadClass fails, we should propagate the exception.throw e;} catch (Exception e) {final InflateException ie = new InflateException(getParserStateDescription(viewContext, attrs) + ": Error inflating class "+ (clazz == null ? "<unknown>" : clazz.getName()), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
}

方法很长,这里只看其核心部分:

if (constructor == null) {// Class not found in the cache, see if it's real, and try to add itclazz = Class.forName(prefix != null ? (prefix + name) : name, false,mContext.getClassLoader()).asSubclass(View.class);   //如果prefix不为空则构造完整类的路径,并通过反射形式加载if (mFilter != null && clazz != null) {boolean allowed = mFilter.onLoadClass(clazz);if (!allowed) {failNotAllowed(name, prefix, viewContext, attrs);}}constructor = clazz.getConstructor(mConstructorSignature);    //获取构造函数constructor.setAccessible(true);    //将构造函数存入缓存中sConstructorMap.put(name, constructor);
} else {...
}Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = viewContext;Object[] args = mConstructorArgs;args[1] = attrs;try {final View view = constructor.newInstance(args);        //通过反射构造Viewif (view instanceof ViewStub) {// Use the same context when inflating ViewStub later.final ViewStub viewStub = (ViewStub) view;viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}return view;} finally {mConstructorArgs[0] = lastContext;}

代码里的思路还是很清晰的,通过反射找到对应类的字节码文件,然后找到其对应构造方法,实例化,拿到View。

可能这段代码会让你疑惑:

constructor = clazz.getConstructor(mConstructorSignature);

我们点进去查看内部调用:

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,int which) throws NoSuchMethodException
{if (parameterTypes == null) {parameterTypes = EmptyArray.CLASS;}for (Class<?> c : parameterTypes) {if (c == null) {throw new NoSuchMethodException("parameter type is null");}}Constructor<T> result = getDeclaredConstructorInternal(parameterTypes);if (result == null || which == Member.PUBLIC && !Modifier.isPublic(result.getAccessFlags())) {throw new NoSuchMethodException(getName() + ".<init> "+ Arrays.toString(parameterTypes));}return result;
}

但其实这就是反射获取的有两个参数的构造方法,这也就是为什么在做自定义控件的时候要重载两个函数的构造函数的原因。可以说这就是解析单个View的全部过程。但LayoutInflater要解析的是整个窗口中的视图树,这个靠rInflate()实现:

void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth();        //通过解析器获取视图树的深度,进行遍历int type;boolean pendingRequestFocus = false;while (((type = parser.next()) != XmlPullParser.END_TAG ||              //while循环解析各viewparser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}final String name = parser.getName();if (TAG_REQUEST_FOCUS.equals(name)) {pendingRequestFocus = true;consumeChildElements(parser);} else if (TAG_TAG.equals(name)) {parseViewTag(parser, parent, attrs);} else if (TAG_INCLUDE.equals(name)) {            if (parser.getDepth() == 0) {       //如果根布局是include标签,抛异常throw new InflateException("<include /> cannot be the root element");}parseInclude(parser, context, parent, attrs);} else if (TAG_MERGE.equals(name)) {            //如果merge标签,抛异常,因为merge标签必须为根布局throw new InflateException("<merge /> must be the root element");} else {final View view = createViewFromTag(parent, name, context, attrs);        //根据元素名进行解析final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflateChildren(parser, view, attrs, true);            //递归调用viewGroup.addView(view, params);                 //将解析的View添加到父控件(ViewGroup)中}}if (pendingRequestFocus) {parent.restoreDefaultFocus();}if (finishInflate) {parent.onFinishInflate();}
}

可见,添加全部View的过程就是在while循环中调用一个createViewFromTag生成根view,然后 rInflateChildren → rInflate → rInflateChildren → rInflate → … … 就这样递归调用一直到遍历完全,然后通过addView添加到父控件。

相信看到这里就都明白了,整个inflate过程可以分为两大步骤:

  1. 通过解析器来将xml文件中的内容解析出来。
  2. 使用反射将解析出来的元素创建成View对象。

root为null的情况

而根据LayoutInflater.inflate()中的内部方法和返回对象又可以总结出:

调用的inflate方法对应最终调用inflater()方法参数返回情况
inflate(id, null)inflater(parser, null, false)生成对应View对象并返回
inflate(id, root)inflater(parser, root, true)生成View对象添加到root上并返回root
inflate(id, null, false)inflate(parser, root, false)生成View对象并返回
inflate(id, null, true)inflate(parser, null, true)生成View对象并返回
inflate(id, root, false)inflate(parser, root, false)生成View对象并返回
inflate(id, root, true)inflater(parser, root, true)生成View对象添加到root上并返回root

只要传递的root不为空,则会根据root来创建生成View的LayoutParams。

如果LayoutInflater调用inflate(id, null),不传入root即父布局,则填充的View的layout_width和layout_height的值无论修改成多少,都不会有效果。确切点来讲,所有以layout_开头的属性都会失去作用,原因很简单,一个View的测量结果并不只是由它自己的layout_width和layout_height(即LayoutParams)所决定的,而是由父容器给它的约束(MeasureSpec)和它自身的LayoutParams共同决定的。有兴趣的朋友可以尝试验证下。

常见报错

LayoutInflate.inflate()方法报错大体有两个原因:

1、在传入父布局的情况下重复addView,对应非法状态异常;

java.lang.IllegalStateException The specified child already has a parent. You must call removeView() on the child's parent first.  

最常见的非法状态异常,出现场景原因五花八门,举一个最常见的例子,我们在调用Fragment时,为什么后面的参数一定要是false?

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_layout, container, false);
}

其实在Fragement源码中,onCreateView()里返回的View其实已经添加到container中了。说白了就是Fragment有自己的AddView操作,如果第三个参数传入true,那么就会直接将inflate出来的布局添加到父布局当中。然后再次addView的时候就会发现它已经有一个父布局了,从而抛出IllegalStateException。(对应逻辑源码在上述解析中可看到)

2、传入相关参数有问题导致报空指针。

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object referenceat android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:715)...

这种没什么好说的,要么是layoutInflate对象为空、或layoutInflate所依赖的Context对象为空,要么是inflate()参数中的布局文件有错,导致XmlPullParser对象无法正确的解析布局文件原因。

总结

inflate就是先把最外层的root解析完,然后用rInflate去递归把子view解析完,子view用createViewFromTag方法去解析单个View,用createView反射出view,全都递归完再返回。当然,解析肯定是耗时操作,很明显耗时操作有两处:

解析xml布局文件反射获取实例

而谷歌官方对此做的优化是:

预编译缓存

关于LayoutInflater源码解析先到这里了,如果有其它问题或者疑惑可以留言。

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

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

相关文章

【COMP337 LEC2】

Association Pattern Mining 关联模式挖掘 Special case: Frequent Pattern Mining (binary data sets) 频繁模式挖掘 Given data matrix, identify all subsets of columns ( features ) such that at least a fraction of rows (objects ) in the matrix have all the featur…

雨云裸金属服务器

雨云服务器与裸金属服务器&#xff1a;云端与实体的完美交融 随着信息技术的迅猛发展&#xff0c;云服务已经成为企业和个人数据处理与存储的重要选择。其中&#xff0c;雨云服务器和裸金属服务器作为两种截然不同的服务形式&#xff0c;各自拥有独特的优势和应用场景。本文将深…

Solidworks:从2D走向3D

Sokidworks 的强大之处在于三维实体建模&#xff0c;这个形状看似复杂&#xff0c;实际上只需要拉伸一次&#xff0c;再做一次减法拉伸就行了。第一次做三维模型&#xff0c;费了不少时间才搞明白。 接下来做一个稍微复杂一点的模型&#xff0c;和上面这个操作差不多&#xff0…

Hive SQL编译成MapReduce任务的过程

一、 Hive 底层执行架构 Hive是Facebook实现的一个开源的数据仓库工具。 Hive基于Hadoop实现&#xff0c;底层数据存放HDFS&#xff0c;计算&#xff08;查询&#xff09;使用MapReduce任务实现将结构化的数据文件映射为数据库表&#xff0c;并提供HQL查询功能&#xff0c;将HQ…

Netty应用(九) 之 编解码器概念 Netty常见的编解码器

目录 22.编解码器 22.1 编解码的概念 22.2 netty中的编解码 22.3 序列化 23.编解码器在使用过程中的两部分核心内容 23.1 序列化协议&#xff08;编码格式&#xff09;&#xff08;传输数据的格式&#xff09; 23.1.1 Java默认的序列化与反序列化 23.1.2 XML的序列化与反…

Netty应用(十) 之 自定义编解码器 自定义通信协议

目录 25.自定义编解码器 25.1 自定义编解码器编码 25.2 自定义编解码器的总结和补充 26.自定义通信协议 26.1 关于通信协议的关注点 26.2 自定义通信协议的格式 26.3 编解码 25.自定义编解码器 有了上面这个大体框架的流程之后&#xff0c;我们来聊一个非常特殊的&#x…

《Linux 简易速速上手小册》第5章: 用户与群组管理(2024 最新版)

文章目录 5.1 管理用户账户5.1.1 重点基础知识5.1.2 重点案例&#xff1a;创建一个新的开发者账户5.1.3 拓展案例 1&#xff1a;禁用用户登录5.1.4 拓展案例 2&#xff1a;设置账户到期 5.2 群组概念与管理5.2.1 重点基础知识5.2.2 重点案例&#xff1a;为项目团队设置群组5.2.…

零售连锁门店管理软件有哪些好用?

在当今的零售行业中&#xff0c;随着连锁经营模式的普及和发展&#xff0c;对于高效、便捷的门店管理需求日益增加。一款好用的零售连锁门店管理软件&#xff0c;能够为商家提供全方位的解决方案&#xff0c;助力企业实现信息化管理&#xff0c;提升运营效率。那么&#xff0c;…

Github 2024-02-13 开源项目日报 Top9

根据Github Trendings的统计&#xff0c;今日(2024-02-13统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量JavaScript项目2Python项目2C项目2TypeScript项目2Rust项目1Go项目1Dart项目1Java项目1C项目1 系统设计指南 …

【lesson51】信号之信号处理

文章目录 信号处理可重入函数volatileSIGCHLD信号 信号处理 信号产生之后&#xff0c;信号可能无法被立即处理&#xff0c;一般在合适的时候处理。 1.在合适的时候处理&#xff08;是什么时候&#xff1f;&#xff09; 信号相关的数据字段都是在进程PCB内部。 而进程工作的状态…

【Chrono Engine学习总结】4-vehicle-4.1-vehicle的基本概念

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 1、基本介绍 Vehicle Overview Vehicle Mannel Vehicle的官方demo 1.1 Vehicle的构型 一个车辆由许多子系统构成&#xff1a;悬挂、转向、轮子/履带、刹车/油门、动…

MogaNet实战:使用 MogaNet实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…