【Android12】Monkey压力测试源码执行流程分析

Monkey压力测试源码执行流程分析

Monkey是Android提供的用于应用程序自动化测试、压力测试的测试工具。
其源码路径(Android12)位于

/development/cmds/monkey/

部署形式为Java Binary

# development/cmds/monkey/Android.bp
// Copyright 2008 The Android Open Source Project
//

package {
    default_applicable_licenses: ["development_cmds_monkey_license"],
}

// See: http://go/android-license-faq
license {
    name: "development_cmds_monkey_license",
    visibility: [":__subpackages__"],
    license_kinds: [
        "SPDX-license-identifier-Apache-2.0",
    ],
    license_text: [
        "NOTICE",
    ],
}

//###############################################################
java_binary {
    name: "monkey",
    srcs: ["**/*.java"],
    wrapper: "monkey",
}

通过Monkey,可以模拟用户的Touch(单指、多指、手势)、按键(key)事件等,检测应用程序发生的ANR、Crash事件,并收集相关Debug信息等。
例如测试应用com.package.linduo,

adb shell monkey -p com.package.linduo --pct-touch 10 --pct-motion 20 10000
# 该命令表示,执行1万次测试事件,其中Touch事件占10%,Motion事件占20%

# 或者adb shell进入android终端,直接使用monkey命令

Monkey支持的命令

    private void showUsage() {
        StringBuffer usage = new StringBuffer();
        usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
        usage.append("              [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
        usage.append("              [--ignore-crashes] [--ignore-timeouts]\n");
        usage.append("              [--ignore-security-exceptions]\n");
        usage.append("              [--monitor-native-crashes] [--ignore-native-crashes]\n");
        usage.append("              [--kill-process-after-error] [--hprof]\n");
        usage.append("              [--match-description TEXT]\n");
        usage.append("              [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
        usage.append("              [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
        usage.append("              [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
        usage.append("              [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
        usage.append("              [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
        usage.append("              [--pct-permission PERCENT]\n");
        usage.append("              [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
        usage.append("              [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
        usage.append("              [--wait-dbg] [--dbg-no-events]\n");
        usage.append("              [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
        usage.append("              [--port port]\n");
        usage.append("              [-s SEED] [-v [-v] ...]\n");
        usage.append("              [--throttle MILLISEC] [--randomize-throttle]\n");
        usage.append("              [--profile-wait MILLISEC]\n");
        usage.append("              [--device-sleep-time MILLISEC]\n");
        usage.append("              [--randomize-script]\n");
        usage.append("              [--script-log]\n");
        usage.append("              [--bugreport]\n");
        usage.append("              [--periodic-bugreport]\n");
        usage.append("              [--permission-target-system]\n");
        usage.append("              COUNT\n");
        Logger.err.println(usage.toString());
    }

Monkey执行测试的源码分析

这里主要关注模式事件的执行流程

  • Monkey启动
  • Monkey生成模拟事件
  • Monkey向系统发送模拟事件
    在这里插入图片描述
Monkey启动

Monkey.java中定义了程序入口函数main,该函数中启动了Monkey程序。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java

public static void main(String[] args) {
	// Set the process name showing in "ps" or "top"
	Process.setArgV0("com.android.commands.monkey");

	Logger.err.println("args: " + Arrays.toString(args));
	int resultCode = (new Monkey()).run(args);
	System.exit(resultCode);
}
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java

/**
 * Run the command!
 *
 * @param args The command-line arguments
 * @return Returns a posix-style result code. 0 for no error.
 */
private int run(String[] args) {
	// Default values for some command-line options
	mVerbose = 0;
	// 默认的测试次数
	mCount = 1000;
	// 生成radom的seed
	mSeed = 0;
	// 记录事件之间的延迟,就是每个事件执行的间隔
	mThrottle = 0;

	// prepare for command-line processing
	mArgs = args;
	
	// 解析参数
	if (!processOptions()) {
		return -1;
	}
	
	// 确定待测试的Package
	if (!loadPackageLists()) {
		return -1;
	}
	
	// now set up additional data in preparation for launch
	if (mMainCategories.size() == 0) {
		mMainCategories.add(Intent.CATEGORY_LAUNCHER);
		mMainCategories.add(Intent.CATEGORY_MONKEY);
	}
	
	if (mSeed == 0) {
		mSeed = System.currentTimeMillis() + System.identityHashCode(this);
	}
	// 获取系统服务接口(AMS、PMS、WMS)
	if (!getSystemInterfaces()) {
		return -3;
	}
	// 获取用于启动应用的Activity
	if (!getMainApps()) {
		return -4;
	}
	
	if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
		// script mode, ignore other options
	} else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
	} else if (mServerPort != -1) {
	} else {
		// 创建用于产生模拟器事件的Source对象
		mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
				mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
		mEventSource.setVerbose(mVerbose);
		// 设置各测试类型的测试比例
		// set any of the factors that has been set
		for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
			if (mFactors[i] <= 0.0f) {
				((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
			}
		}
		
		// 产生activity事件,该事件用来启动应用
		// in random mode, we start with a random activity
		((MonkeySourceRandom) mEventSource).generateActivity();
	}
	
	try {
		// 执行模拟测试事件
		crashedAtCycle = runMonkeyCycles();
	} finally {
		// Release the rotation lock if it's still held and restore the
		// original orientation.
		new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
			mWm, mAm, mVerbose);
	}

}
Monkey解析输入参数

processOptions函数解析输入参数(就是monkey命令后跟着的参数信息),根据入参设置Monkey类中相关成员变量。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
private boolean processOptions() {
	// quick (throwaway) check for unadorned command
	if (mArgs.length < 1) {
		showUsage();
		return false;
	}

	try {
		String opt;
		Set<String> validPackages = new HashSet<>();
		while ((opt = nextOption()) != null) {
			if (opt.equals("-s")) {
				mSeed = nextOptionLong("Seed");
			} else if (opt.equals("-p")) {
				validPackages.add(nextOptionData());
			} else if (opt.equals("-c")) {
				// 省略
			} else {
				Logger.err.println("** Error: Unknown option: " + opt);
				showUsage();
				return false;
			}
		}
		// 根据输入参数,设置待测试的应用
		MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
	} catch (RuntimeException ex) {
		Logger.err.println("** Error: " + ex.toString());
		showUsage();
		return false;
	}

	// If a server port hasn't been specified, we need to specify
	// a count
	if (mServerPort == -1) {
		// 省略
	}

	return true;
}
Monkey获取系统服务

getSystemInterfaces函数用于获取Android系统服务,包括AMS、PMS、WMS服务。调用AMS服务的setActivityController接口,通过该接口向AMS设置IActivityController.Stub对象,通过该对象监听应用(Activity)的ANR和Crash事件。

/**
 * Attach to the required system interfaces.
 *
 * @return Returns true if all system interfaces were available.
 */
private boolean getSystemInterfaces() {
	mAm = ActivityManager.getService();
	if (mAm == null) {
		Logger.err.println("** Error: Unable to connect to activity manager; is the system "
				+ "running?");
		return false;
	}

	mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
	if (mWm == null) {
		Logger.err.println("** Error: Unable to connect to window manager; is the system "
				+ "running?");
		return false;
	}

	mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
	if (mPm == null) {
		Logger.err.println("** Error: Unable to connect to package manager; is the system "
				+ "running?");
		return false;
	}

	try {
		mAm.setActivityController(new ActivityController(), true);
		mNetworkMonitor.register(mAm);
	} catch (RemoteException e) {
		Logger.err.println("** Failed talking with activity manager!");
		return false;
	}

	return true;
}

/**
 * Monitor operations happening in the system.
 */
private class ActivityController extends IActivityController.Stub {
	public boolean activityStarting(Intent intent, String pkg) {
		// 省略
	}

	private boolean isActivityStartingAllowed(Intent intent, String pkg) {
		// 省略
	}

	public boolean activityResuming(String pkg) {
		// 省略
	}

	public boolean appCrashed(String processName, int pid,
			String shortMsg, String longMsg,
			long timeMillis, String stackTrace) {
		// 省略
	}

	public int appEarlyNotResponding(String processName, int pid, String annotation) {
		return 0;
	}

	public int appNotResponding(String processName, int pid, String processStats) {
		// 省略
	}

	public int systemNotResponding(String message) {
		// 省略
	}
}
Monkey获取待测试应用的Activity

monkey通过PackageManager的queryIntentActivities接口,查询带有 Intent.CATEGORY_LAUNCHERIntent.CATEGORY_MONKEY信息的Activity,并判断Activity是否属于待测试应用。将待测试应用的Activity添加到mMainApps变量中。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
/**
 * Using the restrictions provided (categories & packages), generate a list
 * of activities that we can actually switch to.
 *
 * @return Returns true if it could successfully build a list of target
 *         activities
 */
private boolean getMainApps() {
	try {
		final int N = mMainCategories.size();
		for (int i = 0; i < N; i++) {
			Intent intent = new Intent(Intent.ACTION_MAIN);
			String category = mMainCategories.get(i);
			if (category.length() > 0) {
				intent.addCategory(category);
			}
			// 查找带有 Intent.CATEGORY_LAUNCHER、Intent.CATEGORY_MONKEY的Activity
			List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0,
					ActivityManager.getCurrentUser()).getList();
			
			final int NA = mainApps.size();
			for (int a = 0; a < NA; a++) {
				ResolveInfo r = mainApps.get(a);
				String packageName = r.activityInfo.applicationInfo.packageName;
				if (MonkeyUtils.getPackageFilter().checkEnteringPackage(packageName)) {
					// 如果Activity属于待测试Package,将其添加到mMainApps中。
					mMainApps.add(new ComponentName(packageName, r.activityInfo.name));
				} else {
				}
			}
		}
	} catch (RemoteException e) {
		Logger.err.println("** Failed talking with package manager!");
		return false;
	}

	if (mMainApps.size() == 0) {
		Logger.out.println("** No activities found to run, monkey aborted.");
		return false;
	}

	return true;
}
Monkey生成模拟测试事件,并执行
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
private int run(String[] args) {
	// 创建该对象,用于产生测试事件
	mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
			mThrottle, mRandomizeThrottle, mPermissionTargetSystem);

	for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
		if (mFactors[i] <= 0.0f) {
			((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
		}
	}

	try {
		// 执行Monkey测试
		crashedAtCycle = runMonkeyCycles();
	} finally {
		// Release the rotation lock if it's still held and restore the
		// original orientation.
		new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
			mWm, mAm, mVerbose);
	}
}

runMonkeyCycles函数中调用MonkeySourceRandom的getNextEvent函数生成模拟测试事件(MonkeyEvent),调用MonkeyEventinjectEvent执行模拟测试。

private int runMonkeyCycles() {
	int eventCounter = 0;
	int cycleCounter = 0;

	boolean shouldReportAnrTraces = false;
	boolean shouldReportDumpsysMemInfo = false;
	boolean shouldAbort = false;
	boolean systemCrashed = false;

	try {
		// TO DO : The count should apply to each of the script file.
		while (!systemCrashed && cycleCounter < mCount) {
			synchronized (this) {
			// 注意:因为先执行过generateActivity,所以第一次调用会,会获得启动Activity的模拟测试事件
			MonkeyEvent ev = mEventSource.getNextEvent();
			if (ev != null) {
				int injectCode = ev.injectEvent(mWm, mAm, mVerbose);

			} else {
			}
		}
	} catch (RuntimeException e) {
		Logger.error("** Error: A RuntimeException occurred:", e);
	}
	Logger.out.println("Events injected: " + eventCounter);
	return eventCounter;
}

MonkeySourceRandom的getNextEvent,会在事件队列(存储模拟测试事件)为空时,产生测试对象。生成测试事件时,先生成一个随机数,然后根据测试类型所占比例(比例越大,生成该测试类型的概率越大),生成不同测试类型。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
/**
 * generate an activity event
 */
public void generateActivity() {
	MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
			mRandom.nextInt(mMainApps.size())));
	mQ.addLast(e);
}

/**
 * if the queue is empty, we generate events first
 * @return the first event in the queue
 */
public MonkeyEvent getNextEvent() {
	if (mQ.isEmpty()) {
		generateEvents();
	}
	mEventCount++;
	MonkeyEvent e = mQ.getFirst();
	mQ.removeFirst();
	return e;
}

/**
 * generate a random event based on mFactor
 */
private void generateEvents() {
	// 生成随机数
	float cls = mRandom.nextFloat();
	int lastKey = 0;
	// 根据Factor,即不同测试类型所占的比例,生成测试事件
	if (cls < mFactors[FACTOR_TOUCH]) {
		generatePointerEvent(mRandom, GESTURE_TAP);
		return;
	} else if (cls < mFactors[FACTOR_MOTION]) {
		generatePointerEvent(mRandom, GESTURE_DRAG);
		return;
	} else if (cls < mFactors[FACTOR_PINCHZOOM]) {
		generatePointerEvent(mRandom, GESTURE_PINCH_OR_ZOOM);
		return;
	} else if (cls < mFactors[FACTOR_TRACKBALL]) {
		generateTrackballEvent(mRandom);
		return;
	} else if (cls < mFactors[FACTOR_ROTATION]) {
		generateRotationEvent(mRandom);
		return;
	} else if (cls < mFactors[FACTOR_PERMISSION]) {
		mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom));
		return;
	}

	// The remaining event categories are injected as key events
	for (;;) {
		if (cls < mFactors[FACTOR_NAV]) {
			lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];
		} else if (cls < mFactors[FACTOR_MAJORNAV]) {
			lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];
		} else if (cls < mFactors[FACTOR_SYSOPS]) {
			lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];
		} else if (cls < mFactors[FACTOR_APPSWITCH]) {
			MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
					mRandom.nextInt(mMainApps.size())));
			mQ.addLast(e);
			return;
		} else if (cls < mFactors[FACTOR_FLIP]) {
			MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
			mKeyboardOpen = !mKeyboardOpen;
			mQ.addLast(e);
			return;
		} else {
			lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
		}

		if (lastKey != KeyEvent.KEYCODE_POWER
				&& lastKey != KeyEvent.KEYCODE_ENDCALL
				&& lastKey != KeyEvent.KEYCODE_SLEEP
				&& lastKey != KeyEvent.KEYCODE_SOFT_SLEEP
				&& PHYSICAL_KEY_EXISTS[lastKey]) {
			break;
		}
	}

	MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
	mQ.addLast(e);

	e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
	mQ.addLast(e);
}

以Ttouch事件为例子,调用generatePointerEvent函数。通过DMS获取Display对象(用于得知屏幕大小),生成MonkeyTouchEvent对象。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
private void generatePointerEvent(Random random, int gesture) {
	Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);

	PointF p1 = randomPoint(random, display);
	PointF v1 = randomVector(random);

	long downAt = SystemClock.uptimeMillis();

	mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
			.setDownTime(downAt)
			.addPointer(0, p1.x, p1.y)
			.setIntermediateNote(false));

	// 省略

	randomWalk(random, display, p1, v1);
	mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
			.setDownTime(downAt)
			.addPointer(0, p1.x, p1.y)
			.setIntermediateNote(false));
}

调用MonkeyTouchEvent的injectEvent函数,使用InputManager向系统派发TouchEvent。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java
@Override
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
	MotionEvent me = getEvent();
	if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
		StringBuilder msg = new StringBuilder(":Sending ");
		msg.append(getTypeLabel()).append(" (");
		switch (me.getActionMasked()) {
			case MotionEvent.ACTION_DOWN:
				msg.append("ACTION_DOWN");
				break;
			case MotionEvent.ACTION_MOVE:
				msg.append("ACTION_MOVE");
				break;
			case MotionEvent.ACTION_UP:
				msg.append("ACTION_UP");
				break;
			case MotionEvent.ACTION_CANCEL:
				msg.append("ACTION_CANCEL");
				break;
			case MotionEvent.ACTION_POINTER_DOWN:
				msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));
				break;
			case MotionEvent.ACTION_POINTER_UP:
				msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));
				break;
			default:
				msg.append(me.getAction());
				break;
		}
		msg.append("):");

		int pointerCount = me.getPointerCount();
		for (int i = 0; i < pointerCount; i++) {
			msg.append(" ").append(me.getPointerId(i));
			msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");
		}
		Logger.out.println(msg.toString());
	}
	try {
		// 派发TouchEvent
		if (!InputManager.getInstance().injectInputEvent(me,
				InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
			return MonkeyEvent.INJECT_FAIL;
		}
	} finally {
		me.recycle();
	}
	return MonkeyEvent.INJECT_SUCCESS;
}
Monkey主要相关类图

在这里插入图片描述

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

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

相关文章

Windows 安装Redis(图文详解)

一、Redis是什么数据库&#xff1f; Remote Dictionary Server(Redis) 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库&#xff0c;并提供多种语言的 API&#xff0c;是跨平台的非关系型数据库。 …

多线程和并发

线程 进程&#xff1a;在操作系统中运行的程序&#xff0c;一个进程可以包含多个线程 程序就是指令和数据的有序集合&#xff0c;静态概念 进程就是执行程序的一次执行过程&#xff0c;动态概念系统资源分配的单元 一个进程中包含多个线程&#xff0c;一个进程至少包含一个线…

图解KMP算法

目录 1.最长公共前后缀1.1前缀1.2后缀1.3最长公共前后缀 2、KMP算法过程2.1例子12.2例子22.3Python代码&#xff1a;2.4next数组的计算过程 1.最长公共前后缀 1.1前缀 前缀说的是一个字符串除了最后一个字符以外&#xff0c;所有的子串都算是前缀。 前缀字符串&#xff1a;A…

KubeSphere实战

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

49.仿简道云公式函数实战-文本函数-Ip

1. Ip函数 获取当前用户的ip地址 注意是Ipv4的地址 2. 函数用法 IP() 3. 函数示例 获取当前用户的ip地址IP() 4. 代码实战 首先我们在function包下创建text包&#xff0c;在text包下创建IpFunction类&#xff0c;代码如下&#xff1a; package com.ql.util.express.sel…

11:日志分析系统ELK|Elasticsearch|kibana

日志分析系统ELK&#xff5c;Elasticsearch&#xff5c;kibana 日志分析系统ELKELK概述Elasticsearch安装Elasticsearch部署Elasticsearch集群Elasticsearch插件 熟悉Elasticsearch的API调用_cat API创建 tedu 索引使用 PUT 方式增加数据查询数据修改数据删除数据 KibanaKibana…

(挖坑) Python调用图工具

基本效果 输入 #!/usr/bin/env pythonThis example demonstrates a simple use of pycallgraph.from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutputclass Banana:def eat(self):passclass Person:def __init__(self):self.no_bananas()def…

Xcode与Swift开发小记

引子 鉴于React Native目前版本在iOS上开发遇到诸多问题&#xff0c;本以为搞RN只需理会Javascript开发&#xff0c;没想到冒出CocoaPod的一堆编译问题。所以横下一条心&#xff0c;决定直接进攻iOS本身。不管你是用React Native&#xff0c;还是用Flutter&#xff0c;iOS下的…

算能RISC-V通用云开发空间编译pytorch @openKylin留档

终于可以体验下risc-v了&#xff01; 操作系统是openKylin&#xff0c;算能的云空间 尝试编译安装pytorch 首先安装git apt install git 然后下载pytorch和算能cpu的库&#xff1a; git clone https://github.com/sophgo/cpuinfo.git git clone https://github.com/pytorc…

java农产品商城商城计算机毕业设计包运行调试讲解

jsp mysql农业商城 特效&#xff1a;js产品轮播 功能&#xff1a; 前台&#xff1a; 1.绿色水果 图文列表 详情 2.新闻动态 文章标题列表 详情 3.有机蔬菜 图文列表 详情 4.有机谷物 图文列表 详情 5.有机大米 图文列表 详情 6.用户注册 登陆&#xff08;选择用户和管…

c++ 广度优先搜索(Breadth-First Search,BFS)

广度优先搜索&#xff08;Breadth-First Search&#xff0c;BFS&#xff09;是一种图遍历算法&#xff0c;通常用于搜索或遍历树和图等数据结构。其基本思想是先访问起始顶点&#xff0c;然后逐层遍历其相邻的顶点&#xff0c;直到找到目标顶点或遍历完所有顶点。 BFS通常使用…

前端基础面试题(一)

摘要&#xff1a;最近&#xff0c;看了下慕课2周刷完n道面试题&#xff0c;记录下... 1.请说明Ajax、Fetch、Axios三者的区别 三者都用于网络请求&#xff0c;但维度不同&#xff1a; Ajax&#xff08;Asynchronous Javascript ang XML&#xff09;&#xff0c;是一种在不重新…

xss-跨站脚本攻击漏洞

前备知识&#xff1a; Cookie和Session是Web开发中用于维持用户状态、跟踪用户会话的核心技术&#xff0c;它们的主要目的是在无状态的HTTP协议基础上实现有状态的用户交互。 **Cookie**&#xff1a; - Cookie是一种由服务器发送到客户端&#xff08;通常是用户的浏览器&#x…

【JavaEE】_HttpServlet类

目录 1. init方法 2. destory方法 3. service方法 4. servlet生命周期 前文已经提及到&#xff1a;servlet是tomcat提供的&#xff0c;用于操作HTTP协议的一组API&#xff0c;可以将这组API理解为HTTP服务器的框架&#xff1b; 编写一个servlet程序&#xff0c;往往都要继…

【小尘送书-第十四期】《高效使用Redis:一书学透数据存储与高可用集群》

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

MySQL 篇-深入了解 DDL 语言(一)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 MySQL 说明 2.0 DDL 语言 2.1 DDL 语言 - 定义数据库 2.1.1 创建数据库操作 2.1.2 查看数据库操作 2.1.3 使用数据库操作 2.1.4 删除数据库操作 2.2 DDL 语言 …

芯片开发erp软件有哪些优势?

随着科技的飞速发展&#xff0c;芯片开发行业正逐渐成为推动科技进步的关键力量。在这一领域中&#xff0c;企业资源规划(ERP)软件的应用正逐渐普及&#xff0c;为芯片开发企业带来了许多显著的优势。下面&#xff0c;我们将详细介绍芯片开发ERP软件的优势。 一、提升管理效率 …

python JZ35 复杂链表的复制(剑指offer)

题目要求: 思路: 思路1&#xff1a;引入dict 思路1&#xff1a;双指针 代码如下: 思路1代码&#xff1a; # -*- coding:utf-8 -*- # class RandomListNode: # def __init__(self, x): # self.label x # self.next None # self.random None …

第十二章 Linux——日志管理

第十二章 Linux——日志管理 基本介绍系统常用日志日志管理服务日志轮替基本介绍日志轮替文件命名logrotate配置文件自定义加入日志轮转应用实例 日志轮替机制原理查看内存日志 基本介绍 日志文件是重要的系统信息文件&#xff0c;其中记录了许多重要的系统事件&#xff0c;包…

Vue.js+SpringBoot开发生活废品回收系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容三、界面展示3.1 登录注册3.2 资源类型&资源品类模块3.3 回收机构模块3.4 资源求购/出售/交易单模块3.5 客服咨询模块 四、免责说明 一、摘要 1.1 项目介绍 生活废品回收系统是可持续发展的解决方案&#xff0c;旨在鼓…
最新文章