七道Android面试题,先来简单热个身

news2024/11/25 23:28:41

作者:Coffeeee

马上就要到招(tiao)聘(cao)旺季金勾银十了,一批一批的社会精英在寻找自己的下一家的同时,也开始着手为面试做准备,回想起自己这些年,也大大小小经历过不少面试,有被面试过,也有当过面试官,其中也总结出了两个观点,一个就是不花一定的时间背些八股文还真的不行,一些扯皮的话别去听,都是在害人,另一个就是面试造火箭,入职拧螺丝毕竟都是少数,真正一场合格的面试问的东西,都是实际开发过程中会遇到的,下面我就说几个我遇到过的面试题吧

为什么ArrayMap比HashMap更适合Android开发

我们一般习惯在项目当中使用HashMap去存储键值队这样的数据,所以往往在android面试当中HashMap是必问环节,但有次面试我记得被问到了有没有有过ArrayMap,我只能说有印象,毕竟用的最多的还是HashMap,然后那个面试官又问我,觉得Android里面更适合用ArrayMap还是HashMap,我就说不上来了,因为也没看过ArrayMap的源码,后来回去看了下才给弄明白了,现在就简单对比下ArrayMap与HashMap的特点

HashMap

  • HashMap的数据结构为数组加链表的结构,jdk1.8之后改为数组加链表加红黑树的结构
  • put的时候,会先计算key的hashcode,然后去数组中寻找这个hashcode的下标,如果数据为空就先resize,然后检查对应下标值(下标值=(数组长度-1)&hashcode)里面是否为空,空则生成一个entry插入,否就判断hascode与key值是否分别都相等,如果相等则覆盖,如果不等就发生哈希冲突,生成一个新的entry插入到链表后面,如果此时链表长度已经大于8且数组长度大于64,则先转成树,将entry添加到树里面
  • get的时候,也是先去查找数组对应下标值里面是否为空,如果不为空且key与hascode都相等,直接返回value,否就判断该节点是否为一个树节点,是就在树里面返回对应entry,否就去遍历整个链表,找出key值相等的entry并返回

ArrayMap

  • 内部维护两个数组,一个是int类型的数组(mHashes)保存key的hashcode,另一个是Object的数组(mArray),用来保存与mHashes对应的key-value
  • put数据的时候,首先用二分查找法找出mHashes里面的下标index来存放hashcode,在mArray对应下标index<<1与(index<<1)+1的位置存放key与value
  • get数据的时候,同样也是用二分查找法找出与key值对应的下标index,接着再从mArray的(index<<1)+1位置将value取出

对比

  • HashMap在存放数据的时候,无论存放的量是多少,首先是会生成一个Entry对象,这个就比较浪费内存空间,而ArrayMap只是把数据插入到数组中,不用生成新的对象
  • 存放大量数据的时候,ArrayMap性能上就不如HashMap,因为ArrayMap使用的是二分查找法找的下标,当数据多了下标值找起来时间就花的久,此外还需要将所有数据往后移再插入数据,而HashMap只要插入到链表或者树后面即可

所以这就是为什么,在没有那么大的数据量需求下,Android在性能角度上比较适合用ArrayMap

为什么Arrays.asList后往里add数据会报错

这个问题我当初问过不少人,不缺乏一些资历比较深的大佬,但是他们基本都表示不清楚,这说明平时我们研究Glide,OkHttp这样的三方库源码比较多,而像一些比较基础的往往会被人忽略,而有些问题如果被忽略了,往往会产生一些捉摸不透的问题,比如有的人喜欢用Arrays.asList去生成一个List

val dataList = Arrays.asList(1,2,3)
dataList.add(4)

但是当我们往这个List里面add数据的时候,我们会发现,crash了,看到的日志是

不被支持的操作,这让首次遇到这样问题的人肯定是一脸懵,List不让添加数据了吗?之前明明可以的啊,但是之前我们创建一个List是这样创建的

它所在的包是java.util.ArrayList里面,我们看下里面的代码

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

是存在add方法的,我们再回头再去看看asList生成的List

是在java.util.Arrays包里面的,而这里面的ArrayList我们看到了,并没有去实现List接口,所以也就没有add,get等方法,另外在kotlin里面,我们会看到一个细节,当你敲完Arrays.asList的时候,编译器会提示你,可以转换成listof函数,而这个还是我们知道生成的list都是只能读取,不能往里写数据

Thread.sleep(0)到底“睡没睡”

记得在上上家公司,接手的第一个需求就是做一个动画,这个动画需要一个延迟启动的功能,我那个时候想都没想加了个Thread.sleep(3000),后来被领导批了,不可以用Thread.sleep实现延迟功能,那会还不太明白,后来知道了,Thread.sleep(3000)不一定真的暂停三秒,我们来举个例子

println("start:${System.currentTimeMillis()}")
Thread(Runnable {
    Thread.sleep(3000)
    println("end:${System.currentTimeMillis()}")
}).start()

我们在主线程先打印一条数据展示时间,然后开启一个子线程,在里面sleep三秒以后在打印一下时间,我们看下结果如何

start:1675665421590
end:1675665424591

好像对了又好像没对,为什么是过了3001毫秒才打印出来呢?有的人会说,1毫秒而已,忽略嘛,那我们把上面的代码改下再试试

println("start:${System.currentTimeMillis()}")
Thread(Runnable {
    Thread.sleep(0)
    println("end:${System.currentTimeMillis()}")
}).start()

现在sleep了0毫秒,那是不是两条打印日志应该是一样的呢,我们看看结果

start:1675666764475
end:1675666764477

这下子给整不会了,明明sleep0毫秒,那么多出来的2毫秒是怎么回事呢?其实在Android操作系统中,每个线程使用cpu资源都是有优先级的,优先级高的才有资格使用,而操作系统则是在一个线程释放cpu资源以后,重新计算所有线程的优先级来重新分配cpu资源,所以sleep真正的意义不是暂停,而是在接下去的时间内不参与cpu的竞争,等到cpu重新分配完资源以后,如果优先级没变,那么继续执行,所以sleep(0)秒的真正含义是触发cpu资源重新分配

View.post为什么可以获取控件的宽高

我们都知道在onCreate里面想要获取一个控件的宽高,如果直接获取是拿不到的

val mWith = bindingView.mainButton.width
val mHeight = bindingView.mainButton.height
println("按钮宽:$mWith,高:$mHeight")
......
按钮宽:0,高:0

而如果想要获取宽高,则必须调用View.post的方法

bindingView.mainButton.post {
    val mWith = bindingView.mainButton.width
    val mHeight = bindingView.mainButton.height
    println("按钮宽:$mWith,高:$mHeight")
}
......
按钮宽:979,高:187

很神奇,加个post就可以在同样的地方获取控件宽高了,至于为什么呢?我们来分析一下

简单的来说

Activity生命周期,onCreate方法里面视图还在绘制过程中,所以没法直接获取宽高,而在post方法中执行,就是在线程里面获取宽高,这个线程会在视图没有绘制完成的时候放在一个等待队列里面,等到视图绘制执行完毕以后再去执行队列里面的线程,所以在post里面也可以获取宽高

复杂的来说

我们首先从View.post方法里面开始看

这个代码里面的两个框子,说明了post方法做了两件事情,当mAttachInfo不为空的时候,直接让mHandler去执行线程action,当mAttachInfo为空的时候,将线程放在了一个队列里面,从注释里面的第一个单词Postpone就可以知道,这个action是要推迟进行,什么时候进行呢,我们在慢慢看,既然是判断当mAttachInfo不为空才去执行线程,那我们找找什么时候对mAttachInfo赋值,整个View的源码里面只有一处是对mAttachInfo赋值的,那就是在dispatchAttachedToWindow 这个方法里面,我们看下

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
    ...省略部分源码...
  
    // Transfer all pending runnables.
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }

}

当走到dispatchAttachedToWindow这个方法的时候,mAttachInfo才不为空,也就是从这里开始,我们就可以获取控件的宽高等信息了,另外我们顺着这个方法往下看,可以发现,之前的那个队列在这里开始执行了,现在就关键在于,什么时候执行dispatchAttachedToWindow这个方法,这个时候就要去ViewRootIml类里面查看,发现只有一处调用了这个方法,那就是在performTraversals这个方法里面

private void performTraversals() {
        ...省略部分源码...
    host.dispatchAttachedToWindow(mAttachInfo, 0);
        ...省略部分源码...
    // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...省略部分源码...
    performLayout(lp, mWidth, mHeight);
        ...省略部分源码...
    performDraw();
}

performTraversals这个方法我们就很熟悉了,整个View的绘制流程都在里面,所以只有当mAttachInfo在这个环节赋值了,才可以得到视图的信息

IdleHandler到底有啥用

Handler是面试的时候必问的环节,除了问一下那四大组件之外,有的面试官还会问一下IdleHandler,那IdleHandler到底是什么呢,它是干什么用的呢,我们来看看

Message next() {
...省略部分代码...
    synchronized (this) {
        // If first time idle, then get the number of idlers to run.
        // Idle handles only run if the queue is empty or if the first message
        // in the queue (possibly a barrier) is due to be handled in the future.
        if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
            pendingIdleHandlerCount = mIdleHandlers.size();
        }
        if (pendingIdleHandlerCount <= 0) {
            // No idle handlers to run.  Loop and wait some more.
            mBlocked = true;
            continue;
        }

        if (mPendingIdleHandlers == null) {
            mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
        }
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }

    // Run the idle handlers.
    // We only ever reach this code block during the first iteration.
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
        final IdleHandler idler = mPendingIdleHandlers[i];
        mPendingIdleHandlers[i] = null; // release the reference to the handler

        boolean keep = false;
        try {
            keep = idler.queueIdle();
        } catch (Throwable t) {
            Log.wtf(TAG, "IdleHandler threw exception", t);
        }

        if (!keep) {
            synchronized (this) {
                mIdleHandlers.remove(idler);
            }
        }
    }

}

只有在MessageQueue中的next方法里面出现了IdleHandler,作用也很明显,当消息队列在遍历队列中的消息的时候,当消息已经处理完了,或者只存在延迟消息的时候,就会去处理mPendingIdleHandlers里面每一个idleHandler的事件,而这些事件都是通过方法addIdleHandler注册进去的

Looper.myQueue().addIdleHandler {
    false
}

addIdlehandler接受的参数是一个返回值为布尔类型的函数类型参数,至于这个返回值是true还是false,我们从next()方法中就能了解到,当为false的时候,事件处理完以后,这个IdleHandler就会从数组中删除,下次再去遍历执行这个idleHandler数组的时候,该事件就没有了,如果为true的话,该事件不会被删除,下次依然会被执行,所以我们按需设置。现在我们可以利用idlehandler去解决上面讲到的在onCreate里面获取控件宽高的问题

Looper.myQueue().addIdleHandler {
    val mWith = bindingView.mainButton.width
    val mHeight = bindingView.mainButton.height
    println("按钮宽:$mWith,高:$mHeight")
    false
}

当MessageQueue中的消息处理完的时候,我们的视图绘制也完成了,所以这个时候肯定也能获取控件的宽高,我们在IdleHandler里面执行了同样的代码之后,运行后的结果如下

按钮宽:979,高:187

除此之外,我们还可以做点别的事情,比如我们常说的不要在主线程里面做一些耗时的工作,这样会降低页面启动速度,严重的还会出现ANR,这样的场景除了开辟子线程去处理耗时操作之外,我们现在还可以用IdleHandler,这里举个例子,我们在主线程中给sp塞入一些数据,然后在把这些数据读取出来,看看耗时多久

println(System.currentTimeMillis())
val testData = "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhas" +
        "jkhdaabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd" +
        "aabbbbakjsdhjkahsjkasdjasdhjakshdjkahsdjkhasjdkhjaskhdjkashdjkhasjkhasjkhd"
sharePreference = getSharedPreferences(packageName, MODE_PRIVATE)
for (i in 1..5000) {
    sharePreference.edit().putString("test$i", testData).commit()
}
for (i in 1..5000){
    sharePreference.getString("test$i","")
}
println(System.currentTimeMillis())

......运行结果
1676260921617
1676260942770

我们看到在塞入5000次数据,再读取5000次数据之后,一共耗时大概20秒,同时也阻塞了主线程,导致的现象是页面一片空白,只有等读写操作结束了,页面才展示出来,我们接着把读写操作的代码用IdleHandler执行一下看看

Looper.myQueue().addIdleHandler {
    sharePreference = getSharedPreferences(packageName, MODE_PRIVATE)
    val editor = sharePreference.edit()
    for (i in 1..5000) {
        editor.putString("test$i", testData).commit()
    }
    for (i in 1..5000){
        sharePreference.getString("test$i","")
    }
    println(System.currentTimeMillis())
    false
}
......运行结果
1676264286760
1676264308294

运行结果依然耗时二十秒左右,但区别在于这个时候页面不会受到读写操作的阻塞,很快就展示出来了,说明读写操作的确是等到页面渲染完才开始工作,上面过程没有放效果图主要是因为时间太长了,会影响gif的体验,有兴趣的可以自己试一下

如何让指定视图不被软键盘遮挡

我们通常使用android:windowSoftInputMode属性来控制软键盘弹出之后移动界面,让输入框不被遮挡,但是有些场景下,键盘永远都会挡住一些我们使用频次比较高的控件,比如现在我们有个登录页面,大概的样子长这样

它的布局文件是这样

<RelativeLayout
    android:id="@+id/mainroot"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:src="@mipmap/ic_launcher_round" />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/ll_view1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="120dp"
        android:gravity="center"
        android:orientation="vertical">

        <EditText
            android:id="@+id/main_edit"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:hint="请输入用户名"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <EditText
            android:id="@+id/main_edit2"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginTop="30dp"
            android:hint="请输入密码"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_marginHorizontal="10dp"
            android:layout_marginTop="20dp"
            android:text="登录" />

    </androidx.appcompat.widget.LinearLayoutCompat>

</RelativeLayout>

在这样一个页面里面,由于输入框与登录按钮都比较靠页面下方,导致当输入完内容想要点击登录按钮时候,必须再一次关闭键盘才行,这样的操作在体验上就比较大打折扣了

现在希望可以键盘弹出之后,按钮也展示在键盘上面,这样就不用收起弹框以后才能点击按钮了,这样一来,windowSoftInputMode这一个属性已经不够用了,我们要想一下其他方案

  • 首先,需要让按钮也展示在键盘上方,那只能让布局整体上移把按钮露出来,在这里我们可以改变LayoutParam的bottomMargin参数来实现
  • 其次,需要知道键盘什么时候弹出,我们都知道android里面并没有提供任何监听事件来告诉我们键盘什么时候弹出,我们只能从其他角度入手,那就是监听根布局可视区域大小的变化

ViewTreeObserver

我们先获取视图树的观察者,使用addOnGlobalLayoutListener去监听全局视图的变化

bindingView.mainroot.viewTreeObserver.addOnGlobalLayoutListener {

}

接下去就是要获取根视图的可视化区域了,如何来获取呢?View里面有这么一个方法,那就是getWindowVisibleDisplayFrame,我们看下源码注释就知道它是干什么的了

一大堆英文没必要都去看,只需要看最后一句就好了,大概意思就是获取能够展示给用户的可用区域,所以我们在监听器里面加上这个方法

bindingView.mainroot.viewTreeObserver.addOnGlobalLayoutListener {
    val rect = Rect()
    bindingView.mainroot.getWindowVisibleDisplayFrame(rect)
}

当键盘弹出或者收起的时候,rect的高度就会跟着变化,我们就可以用这个作为条件来改变bottomMargin的值,现在我们增加一个变量oldDelta来保存前一个rect变化的高度值,用来做比较,完整的代码如下

var oldDelta = 0
val params:RelativeLayout.LayoutParams = bindingView.llView1.layoutParams as RelativeLayout.LayoutParams
val originBottom = params.bottomMargin
bindingView.mainroot.viewTreeObserver.addOnGlobalLayoutListener {
    val rect = Rect()
    bindingView.mainroot.getWindowVisibleDisplayFrame(rect)
    val deltaHeight = r.height()
    if (oldDelta != deltaHeight) {
        if (oldDelta != 0) {
            if (oldDelta > deltaHeight) {
                params.bottomMargin = oldDelta - deltaHeight
            } else if (oldDelta < deltaHeight) {
                params.bottomMargin = originBottom
            }
            bindingView.llView1.layoutParams = params
        }
        oldDelta = deltaHeight
    }
}

最终效果如下

弹出后页面有个抖动是因为本身有个页面平移的效果,然后再去计算layoutparam,如果不想抖动可以在布局外层套个scrollView,用smoothScrollTo把页面滑上去就可以了,有兴趣的可以业余时间试一下

为什么LiveData的postValue会丢失数据

LiveData已经问世好多年了,大家都很喜欢用,因为它上手方便,一般知道塞数据用setValue和postValue,监听数据使用observer就可以了,然而实际开发中我遇到过好多人,一会这里用setValue一会那里用postValue,或者交替着用,这种做法也不能严格意义上说错,毕竟运行起来的确没问题,但是这种做法确实是存在风险隐患,那就是连续postValue会丢数据,我们来做个实验,连续setValue十个数据和连续postValue十个数据,收到的结果都分别是什么

var testData = MutableLiveData<Int>()
fun play(){
    for (i in 1..10) {
        testData.value = i
    }
}

mainViewModel.testData.observe(this) {
    println("收到:$it")
}

//执行结果
收到:1
收到:2
收到:3
收到:4
收到:5
收到:6
收到:7
收到:8
收到:9
收到:10

setValue十次数据都可以收到,现在把setValue改成postValue再来试试

var testData = MutableLiveData<Int>()
fun play(){
    for (i in 1..10) {
        testData.postValue(i)
    }
}

得到的结果是

收到:10

只收到了最后一条数据10,这是为什么呢?我们进入postValue里面看看里面的源码就知道了

主要看红框里面,有一个synchronized同步锁锁住了一个代码块,我们称为代码块1,锁的对象是mDataLock,代码块1做的事情先是给postTask这个布尔值赋值,接着把传进来的值赋给mPendingData,那我们知道了,postTask除了第一个被执行的时候,值是true,结下去等mPendingData有值了以后就都为false,前提是mPendingData没有被重置为NOT_SET,然后我们顺着代码往下看,会看到代码接下来就要到一个mPostValueRunnable的线程里面去了,我们看下这个线程

发现同样的锁,锁住了另一块代码块,我们称为代码块2,这个代码块里面恰好是把mPendingData的值赋给newValue以后,重置为NOT_SET,这样一来,postValue又可以接受新的值了,所以这也是正常情况下每次postValue都可以接受到值的原因,但是我们想想连续postValue的场景,我们知道如果synchronized如果修饰一段代码块,那么当这段代码块获取到锁的时候,就具有优先级,只有当全部执行完以后才会释放锁,所以当代码块1连续被访问时候,代码块2是不会被执行的,只有等到代码块1执行完,释放了锁,代码块2才会被执行,而这个时候,mPendingData已经是最新的值了,之前的值已经全部被覆盖了,所以我们说的postValue会丢数据,其实说错了,应该是postValue只会发送最新数据

总结

这篇文章讲到的面试题还仅仅只是过去几年遇到的,现在面试估计除了一些常规问题之外,比重会更倾向于Kotlin,Compose,Flutter的知识点,所以只有不断的日积月累,让自己的知识点更加的全面,才能在目前竞争激烈的行情趋势下逆流而上,不会被拍打在沙滩上

下面针对了Android 各种知识点进行了完美的总结(包含了:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题等等):https://qr18.cn/CgxrRy

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mybatis-plus的逻辑删除的坑

一旦在逻辑字段上加了TableLogic逻辑删除的配置&#xff0c;并且使用mybatis-plus自带的方法时&#xff08;如果自己用xml写SQL不会出现下面的情况&#xff09; 查询、修改时会自动排除逻辑删除的数据 当使用mybatis-plus自带的查询方法时&#xff0c;就不用每次查询的时候跟…

构建 LVS-DR 群集、配置nginx负载均衡。

目录 一、基于 CentOS 7 构建 LVS-DR 群集 1、准备四台虚拟机 2、配置负载调度器&#xff08;192.168.2.130&#xff09; 3、部署共享存储&#xff08;192.168.2.133&#xff09; 4、配置两个Web服务器&#xff08;192.168.2.131、192.168.2.132&#xff09; 测试集群 二…

【HCIP】重发布实验

题目&#xff1a; 配置&#xff1a; R1 //配置ip地址 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [r1-GigabitEthernet0/0/0]int g0/0/1 [r1-GigabitEthernet0/0/1]ip add 13.1.1.1 24 [r1-GigabitEthernet0/0/1]int lo0 [r1-LoopBack0]ip add 1.1.1.1 24 /…

天马上位在即,将成为iPhoneSE4二级供应商,SE4有望2025年量产

根据知情人士透露&#xff0c;国内OLED面板制造商&#xff0c;如天马&#xff0c;正在积极争取成为苹果iPhone SE 4的AMOLED面板供应商。 苹果面板采购负责人已经在今年上半年访问了中国大陆的主要柔性AMOLED面板制造商之一&#xff0c;这意味着国内一家OLED面板厂商有望成为iP…

哪些人会看作业指导书?作业指导书怎样才能发挥作用?

一般人普遍人为&#xff0c;作业指导书就是给操作人员看的。其实不然&#xff0c;那么哪些人会看作业指导书&#xff1f;大致可以分为: 第一类&#xff1a;新到工作岗位的员工。其中包括新进员工和新转岗位的员工&#xff0c;他们都会在师傅或领班组长的带领指导下&#xff0c;…

MySQL 数据类型总结

整形数据类型 1字节 8bit 2^8256

安灯Andon系统的应用与优势

安灯系统是一款与硬件相结合&#xff0c;实时了解机台与工位状态&#xff0c;让异常的信息得到快速、高效的解决的系统软件&#xff0c;同时记录每次异常报警的种类、响应时间和处理问题用时&#xff0c;提供改善生产管理和人员考核的数据参考&#xff0c;实现透明、快速的生产…

C++实现一键关闭桌面

方法一&#xff1a; C关闭桌面,explorer.exe #include<Windows.h> #include <TlHelp32.h> #include"resource.h" #pragma warning(disable:4996) void taskkill(const char * name) {HANDLE info_handle CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,…

C++ 派生类的拷贝构造函数

当存在类的继承关系时&#xff0c;对于一个类&#xff0c;如果程序员没有编写拷贝构造函数&#xff0c;编译系统会在必要时自动生成一个隐含的拷贝构造函数&#xff0c;这个隐含的拷贝构造函数会自动调用基类的拷贝构造函数&#xff0c;然后对派生类新增的成员对象一一执行拷贝…

更新镜像配置

安装docker Install Docker Desktop on Linux | Docker Documentation 拉取镜像 sudo docker pull reg.docker.xx-inc.com/xxx/xxxx:20220916 基于镜像运行容器 sudo docker run -it reg.docker.xx-inc.com/xx/xxxx:20230317 /bin/bash 修改配置后ctrlp&#xff0c;再ctrlq…

激光切割机的操作中蛙跳技术是什么意思

其实&#xff0c;蛙跳技术就是指在激光切割机运行的过程中&#xff0c;机器换位置的方式。打个比方&#xff0c;你刚刚在这儿把孔1切好了&#xff0c;接下来就得跑到那儿把孔2切了。 在这个过程中&#xff0c;激光切割机就像是一只青蛙&#xff0c;要从一个位置跳到另一个位置。…

#python# #Matplotlib# 常用可视化图形

工作中&#xff0c;我们经常需要将数据可视化&#xff0c;分享一些Matplotlib图的汇总&#xff0c;在数据分析与可视化中是非常有用。 如下协一些常用图形。 安装相关插件 python3 pip3 install scipy python3 pip3 install pandas python3 pip3 install matplotlib python…

【技巧】如何设置Word文档部分内容“限制编辑”?

我们知道&#xff0c;Word文档可以设置“限制编辑”&#xff0c;也就是保护文档不被随意更改。 那如果只想保护文档中的部分内容&#xff0c;其他内容还是随意编辑更改&#xff0c;是否可以设置部分内容“限制编辑”&#xff1f;答案是可以的&#xff0c;下面小编来举例说明一…

【Tomcat】Tomcat部署及优化

Tomcat 它是一个免费、开源的web应用服务器&#xff1b;基于java代码开发的软件&#xff1b;处理动态请求和基于Java代码的页面开发&#xff1b; 可以在html当中写入Java代码&#xff0c;Tomcat可以解析html页面当中的Java代码&#xff0c;执行动态请求以及动态页面 缺点&#…

静态时序分析与时序约束

一、时序分析的基本概念 1. 时钟 理性的时钟模型是一个占空比为50%且周期固定的方波&#xff1a; 实际电路中输入给FPGA的晶振时钟信号是正弦波&#xff1a; 2. 时钟抖动 Clock Jitter&#xff0c;时钟抖动&#xff0c;相对于理想时钟沿&#xff0c;实际时钟存在不随时钟存在…

生信豆芽菜-火山图绘制使用说明

网站&#xff1a;http://www.sxdyc.com/visualsVolcano 一、火山图简介 火山图是散点图的一种&#xff0c;它将统计测试中的统计显著性量度&#xff08;如p value&#xff09;和变化幅度&#xff08;logFC&#xff09;相结合&#xff0c;能够快速直观地识别那些变化幅度较大且具…

记一次前端直接上传图片到oss报错

前端直接上传图片到阿里云oss,相关过程官网和网上资料已经很详细&#xff0c;不做赘述。 但这个过程比较复杂&#xff0c;前后端对接过程中很容易出现报错&#xff0c;这里遇到了以下报错&#xff0c;不容易排查。 请求显示net::ERR_NAME_NOT_RESOLVED错误&#xff0c;catch输…

『Samba』在Linux中实现高效管理共享文件夹的基本操作与实践

&#x1f4e3;读完这篇文章里你能收获到 Samba 的安装和配置&#xff1a;详细介绍了如何在 Linux 操作系统上安装和配置 Samba 服务器共享文件夹的设置&#xff1a;指导如何选择要共享的文件夹&#xff0c;并为其设置共享名称、路径以及访问权限Samba 用户的创建&#xff1a;提…

一行JS代码导出ant-design中复杂table表格的Excel

使用方式 1、安装依赖 npm install xlsx-js-style2、复制代码文件exportExcel.js至工程 https://github.com/EnthuDai/export-excel-in-one-line 3、在引入excel.js后调用 Excel.export(columns, dataSource, 导出文件名)4、代码demo 5、效果 页面excel 适用范围 对于使…

嵌入式面试笔试刷题(day7)

文章目录 前言一、switch能不能用浮点数二、指针函数和函数指针三、如何防止重复引用头文件四、如何写一个函数可以在main之前执行五、栈和队列区别及应用场景六、linux上查看磁盘内存占用率的命令七、什么是磁盘碎片八、内存泄露是怎么产生的九、发生了coredump怎么解决总结 前…