有趣且重要的JS知识合集(18)浏览器实现前端录音功能
1、主题描述
兼容多个浏览器下的前端录音功能,实现六大录音功能:
1、开始录音
2、暂停录音
3、继续录音
4、结束录音
5、播放录音
6、上传录音
2、示例功能
初始状态:
开始录音:
结束录音:
录音流程 :
示例中的三个按钮其实包含了六个上述功能,点击开始时开始录音,可以暂停/结束录音,此操作后就可以播放播音/上传录音了噢~以下是对应六大录音功能示例代码,那大家会发现HZRecorder是啥呢? 其实 HZRecorder 是录音类,我们调用的都是该类里面的方法。
那大家肯定好奇,录音是通过怎样一种形式存在呢?其实用的就是浏览器的AudioContext对象,他旨在创建一个音频dom,有输入和输出。具体想了解这对象的,可以去mdn看看
AudioContext
/*** 录音前准备 检查录音设备是否到位*/this.readyRecording = async function() {let recorder // 表示录音类实例// 流模式下ready钩子 res 为录音类实例 或者 falseawait HZRecorder.ready().then(res => {recorder = res})return recorder}/*** 开始录音*/this.startRecording = function() {recorder.start();}/*** 结束录音*/this.stopRecording = function() {recorder.end();}/*** 播放录音*/this.playRecording = function() {recorder.play(audio);}/*** 继续录音*/this.resumeRecord = function() {recorder.again();}/*** 暂停录音*/this.pauseRecord = function() {recorder.stop();}/*** 重新录音*/this.reRecord = function() {this.startRecording()}/*** 上传录音*/this.uploadRecord = function() {// 流模式下上传recorder.upload(url, succ, fail)}
3、流模式下的录音类
大家看到这标题就好奇,啥叫流模式下的录音类呢?那还有其他模式吗?的确,我总结了下,是根据上传录音时的数据来区分的~我们常规情况下,上传录音都是流模式,也就是Content-Type为application/octem-stream,源码如下
/*** 录音类(针对content-type为application/octem-stream 的使用)* @param {*} stream * @param {*} config */
const HZRecorder = function (stream, config) {config = config || {};config.sampleBits = config.sampleBits || 8; //采样数位 8, 16 config.sampleRate = config.sampleRate || (44100 / 6); //采样率(1/6 44100) //创建一个音频环境对象 audioContext = window.AudioContext || window.webkitAudioContext;var context = new audioContext();//将声音输入这个对像 var audioInput = context.createMediaStreamSource(stream);//设置音量节点 var volume = context.createGain();audioInput.connect(volume);//创建缓存,用来缓存声音 var bufferSize = 4096;// 创建声音的缓存节点,createScriptProcessor方法的 // 第二个和第三个参数指的是输入和输出都是双声道。 var recorder = context.createScriptProcessor(bufferSize, 2, 2);var audioData = {size: 0 //录音文件长度 , buffer: [] //录音缓存 , inputSampleRate: context.sampleRate //输入采样率 , inputSampleBits: 16 //输入采样数位 8, 16 , outputSampleRate: config.sampleRate //输出采样率 , oututSampleBits: config.sampleBits //输出采样数位 8, 16 , input: function (data) {this.buffer.push(new Float32Array(data));this.size += data.length;}, compress: function () { //合并压缩 //合并 var data = new Float32Array(this.size);var offset = 0;for (var i = 0; i < this.buffer.length; i++) {data.set(this.buffer[i], offset);offset += this.buffer[i].length;}//压缩 var compression = parseInt(this.inputSampleRate / this.outputSampleRate);var length = data.length / compression;var result = new Float32Array(length);var index = 0, j = 0;while (index < length) {result[index] = data[j];j += compression;index++;}return result;}, encodeWAV: function () {var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);var bytes = this.compress();var dataLength = bytes.length * (sampleBits / 8);var buffer = new ArrayBuffer(44 + dataLength);var data = new DataView(buffer);var channelCount = 1;//单声道 var offset = 0;var writeString = function (str) {for (var i = 0; i < str.length; i++) {data.setUint8(offset + i, str.charCodeAt(i));}};// 资源交换文件标识符 writeString('RIFF'); offset += 4;// 下个地址开始到文件尾总字节数,即文件大小-8 data.setUint32(offset, 36 + dataLength, true); offset += 4;// WAV文件标志 writeString('WAVE'); offset += 4;// 波形格式标志 writeString('fmt '); offset += 4;// 过滤字节,一般为 0x10 = 16 data.setUint32(offset, 16, true); offset += 4;// 格式类别 (PCM形式采样数据) data.setUint16(offset, 1, true); offset += 2;// 通道数 data.setUint16(offset, channelCount, true); offset += 2;// 采样率,每秒样本数,表示每个通道的播放速度 data.setUint32(offset, sampleRate, true); offset += 4;// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8 data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8 data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;// 每样本数据位数 data.setUint16(offset, sampleBits, true); offset += 2;// 数据标识符 writeString('data'); offset += 4;// 采样数据总数,即数据总大小-44 data.setUint32(offset, dataLength, true); offset += 4;// 写入采样数据 if (sampleBits === 8) {for (var i = 0; i < bytes.length; i++, offset++) {var s = Math.max(-1, Math.min(1, bytes[i]));var val = s < 0 ? s * 0x8000 : s * 0x7FFF;val = parseInt(255 / (65535 / (val + 32768)));data.setInt8(offset, val, true);}} else {for (var i = 0; i < bytes.length; i++, offset += 2) {var s = Math.max(-1, Math.min(1, bytes[i]));data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);}}return new Blob([data], { type: 'audio/wav' });}};//开始录音 this.start = function () {audioInput.connect(recorder);recorder.connect(context.destination);};//停止 this.stop = function () {recorder.disconnect();};// 结束this.end = function () {context.close();};// 继续this.again = function () {recorder.connect(context.destination);};//获取音频文件 this.getBlob = function () {this.stop();return audioData.encodeWAV();};//回放 this.play = function (audio) {audio.src = window.URL.createObjectURL(this.getBlob());};//上传 this.upload = function (url, succ, fail) {const xhr = new XMLHttpRequest();xhr.overrideMimeType("application/octet-stream")// xhr.upload.addEventListener('progress', function (e) {// }, false);xhr.addEventListener('load', function (e) {succ(xhr.response)}, false);xhr.addEventListener('error', function (e) {fail(xhr.response);}, false);xhr.addEventListener('abort', function (e) {fail(xhr.response);}, false);xhr.open('POST', url);if(xhr.sendAsBinary){xhr.sendAsBinary(this.getBlob());}else{xhr.send(this.getBlob());}};//音频采集 recorder.onaudioprocess = function (e) {audioData.input(e.inputBuffer.getChannelData(0));};
}/*** 多浏览器兼容* @param {*} videoConfig 参数配置* @param {*} succ 成功回调* @param {*} fail 失败回调* @returns promise*/
HZRecorder.compatibleMedia = async function(videoConfig) {let streamPromise // 视频promiseif (navigator.mediaDevices && navigator.mediaDevices.getUserMedia){// 最新标准APIstreamPromise = await navigator.mediaDevices.getUserMedia(videoConfig)} else if (navigator.webkitGetUserMedia){// webkit内核浏览器streamPromise = await navigator.webkitGetUserMedia(videoConfig)} else if (navigator.mozGetUserMedia){// Firefox浏览器streamPromise = await navagator.mozGetUserMedia(videoConfig)} else if (navigator.getUserMedia){// 旧版APIstreamPromise = await navigator.getUserMedia(videoConfig)}return streamPromise
}
/*** 是否支持录音* @returns 支持直接返回录音类实例 : 返回false*/
HZRecorder.ready = async function() {let instance // 录音类实例(ready ok) | false (ready no)await HZRecorder.compatibleMedia({ audio: true }).then(stream => {instance = new HZRecorder(stream);}).catch(() => {instance = false})return instance
}
4、表单模式下的录音类
和上述流模式的录音类有区别的是,表单模式下适用于上传录音时Content-Type为application/x-www-form-urlencoded噢~
// --------------------------------------------------/*** 录音类(指定content-type为application/x-www-form-urlencoded使用)* @param {*} stream 流对象*/const HZRecorderForm = function (stream) {//创建一个音频环境对象 audioContext = window.AudioContext || window.webkitAudioContext;var ac = new audioContext();var chunks = [];var mediaRecordervar blobResult//开始录音 this.start = function () {if (!mediaRecorder) {var origin = ac.createMediaStreamSource(stream)var dest = ac.createMediaStreamDestination();mediaRecorder = new MediaRecorder(dest.stream);mediaRecorder.ondataavailable = function(e) {chunks.push(e.data);}mediaRecorder.onstop = function(evt) {blobResult = new Blob(chunks, { 'type' : 'audio/mpeg' });};origin.connect(dest);}mediaRecorder.start();};// 结束录音this.end = function () {// 当录音类处于不活跃状态时,停止操作if (mediaRecorder.state === 'inactive') returnmediaRecorder.requestData()mediaRecorder.stop();};// 暂停录音this.stop = function() {// 当录音类处于不活跃状态时,停止操作if (mediaRecorder.state === 'inactive') returnmediaRecorder.pause()}// 恢复录音this.again = function() {// 当录音类处于不活跃状态时,停止操作if (mediaRecorder.state === 'inactive') returnmediaRecorder.resume()}//上传 this.upload = function (url, succ, err) {setTimeout(() => {var xhr = new XMLHttpRequest();xhr.open('POST', url);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')xhr.send(blobResult);xhr.onload = e => {// 请求完成 && 外部状态码200 && 内部状态码1(这个内部状态码自定义)if (xhr.readyState === 4 && xhr.status === 200 && JSON.parse(xhr.response).status === 1) {succ && succ(xhr.response)} else {err && err(JSON.parse(xhr.response).message)}}})};}/*** 多浏览器兼容* @param {*} videoConfig 参数配置* @param {*} succ 成功回调* @param {*} fail 失败回调* @returns promise*/
HZRecorderForm.compatibleMedia = async function(videoConfig) {let streamPromise // 视频promiseif (navigator.mediaDevices && navigator.mediaDevices.getUserMedia){// 最新标准APIstreamPromise = await navigator.mediaDevices.getUserMedia(videoConfig)} else if (navigator.webkitGetUserMedia){// webkit内核浏览器streamPromise = await navigator.webkitGetUserMedia(videoConfig)} else if (navigator.mozGetUserMedia){// Firefox浏览器streamPromise = await navagator.mozGetUserMedia(videoConfig)} else if (navigator.getUserMedia){// 旧版APIstreamPromise = await navigator.getUserMedia(videoConfig)}return streamPromise
}
/*** 是否支持录音* @returns 支持直接返回录音类实例 : 返回false*/
HZRecorderForm.ready = async function() {let instance // 录音类实例(ready ok) | false (ready no)await HZRecorderForm.compatibleMedia({ audio: true }).then(stream => {instance = new HZRecorderForm(stream);}).catch(() => {instance = false})return instance
}
5、疑难解答
1、在录音开始前都必须调用readyRecording方法吗?
必须噢,你也可以自己实现这功能,HZRecorder.ready()方法返回的是promise对象,其值在当前有麦克风时候,返回的是录音类实例,你拿到此值就可以调用录音类的方法,无麦克风时候,返回的是false,表示当前不具备录音环境~
/*** 录音前准备 检查录音设备是否到位*/this.readyRecording = async function() {let recorder // 表示录音类实例// 流模式下ready钩子 res 为录音类实例 或者 falseawait HZRecorder.ready().then(res => {recorder = res})// 表单模式下ready钩子 res 为录音类实例 或者 falseawait HZRecorderForm.ready().then(res => {recorder = res})return recorder}
2、火狐浏览器提示 navigator.mediaDevices is undefined,找不到?
是的噢,火狐浏览器的navigator对象没有mediaDevices这个属性,所以这也是我为啥在录音类里要加入compatibleMedia方法,此方法就是用来兼容各个浏览器的噢~火狐就是用的navigator.mozGetUserMedia方法
/*** 多浏览器兼容* @param {*} videoConfig 参数配置* @param {*} succ 成功回调* @param {*} fail 失败回调* @returns promise*/
HZRecorder.compatibleMedia = async function(videoConfig) {let streamPromise // 视频promiseif (navigator.mediaDevices && navigator.mediaDevices.getUserMedia){// 最新标准APIstreamPromise = await navigator.mediaDevices.getUserMedia(videoConfig)} else if (navigator.webkitGetUserMedia){// webkit内核浏览器streamPromise = await navigator.webkitGetUserMedia(videoConfig)} else if (navigator.mozGetUserMedia){// Firefox浏览器streamPromise = await navagator.mozGetUserMedia(videoConfig)} else if (navigator.getUserMedia){// 旧版APIstreamPromise = await navigator.getUserMedia(videoConfig)}return streamPromise
}
3、录音类的ready方法,为啥要使用async/await呢?
这个就有点涉及异步的知识了,在一个异步函数里,return是属于同步逻辑噢,promise.then属于异步,所以 return 会先于 .then执行的噢,这就和我们的想法不一致了,所以要await 阻塞代码,拿到instance值了,再返回
/*** 是否支持录音* @returns 支持直接返回录音类实例 : 返回false*/
HZRecorder.ready = async function() {let instance // 录音类实例(ready ok) | false (ready no)await HZRecorder.compatibleMedia({ audio: true }).then(stream => {instance = new HZRecorder(stream);}).catch(() => {instance = false})return instance
}
4、录音类为啥要使用 XMLHttpRequest 来触发接口呢?
不一定要使用原生xhr噢,你也可以根据你需求来修改成axios/fetch/ajax等~这个不影响整体代码的使用
5、流模式和表单模式的录音类本质上有啥区别?
其实在外层是上传接口的请求头区别,但在实际上,只是由于流模式下的写法,无法将音频转成mp3格式(默认为wav格式),当然网上也有小伙伴认为引入lame库来实现wav转换mp3的操作,当然可以啦~这不影响,只是对我来说,我是能不引入第三方库就不引入。
而表单模式实际上用的浏览器支持的另一个接口 MediaRecorder
而MediaRecorder是专门来做录制的,他想转换格式的话,就简单的多,在录制完触发onstop时,将可以将二进制数据转换成任意想要的格式,audio/mpeg就是mp3的格式~
mediaRecorder.ondataavailable = function(e) {chunks.push(e.data);}mediaRecorder.onstop = function(evt) {blobResult = new Blob(chunks, { 'type' : 'audio/mpeg' });};
6、表单模式下的录音类为啥要判断inactive状态呢?
因为
MediaRecorder 接口 有三个状态 inactive, recording, paused
这三个状态分别是设备闲置,设备使用,设备暂停,有点类似于window的未响应,当我们想要操作麦克风时,此时麦克风inactive了,那就无法响应我们的请求,所以当状态为inactive时,我们都return掉,使他不执行我们的方法。
// 结束录音this.end = function () {// 当录音类处于不活跃状态时,停止操作if (mediaRecorder.state === 'inactive') returnmediaRecorder.requestData()mediaRecorder.stop();};// 暂停录音this.stop = function() {// 当录音类处于不活跃状态时,停止操作if (mediaRecorder.state === 'inactive') returnmediaRecorder.pause()}// 恢复录音this.again = function() {// 当录音类处于不活跃状态时,停止操作if (mediaRecorder.state === 'inactive') returnmediaRecorder.resume()}
------有不懂的可以评论区聊聊噢~------
相关文章:
有趣且重要的JS知识合集(18)浏览器实现前端录音功能
1、主题描述 兼容多个浏览器下的前端录音功能,实现六大录音功能: 1、开始录音 2、暂停录音 3、继续录音 4、结束录音 5、播放录音 6、上传录音 2、示例功能 初始状态: 开始录音: 结束录音: 录音流程 …...
面试官:聊聊你知道的跨域解决方案
跨域是开发中经常会遇到的一个场景,也是面试中经常会讨论的一个问题。掌握常见的跨域解决方案及其背后的原理,不仅可以提高我们的开发效率,还能在面试中表现的更加游刃有余。 因此今天就来和大家从前端的角度来聊聊解决跨域常见的几种方式。…...
SpringCloud五大核心组件
Consul 等,提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等,满足了构建微服务所需的所有解决方案。 服务发现——Netflix Eureka …...
Verilog HDL语言入门(二)
强烈建议用同步设计2.在设计时总是记住时序问题3.在一个设计开始就要考虑到地电平或高电平复位、同步或异步复位、上升沿或下降沿触发等问题,在所有模块中都要遵守它4.在不同的情况下用if和case,最好少用if的多层嵌套(1层或2层比较合适&#…...
Simpleperf详细使用
一、Simpleperf介绍 Simpleperf是一个强大的命令行工具,它包含在NDK中,可以帮助我们分析应用的CPU性能。Simpleperf可以帮助我们找到应用的热点,而热点往往与性能问题相关,这样我们就可以分析修复热点源。 如果您更喜欢使用命令…...
【算法基础】二分图(染色法 匈牙利算法)
一、二分图 1. 染色法 一个图是二分图,当且仅当,图中不含奇数环。在判别一个图是否为二分图⑩,其实相当于染色问题,每条边的两个点必须是不同的颜色,一共有两种颜色,如果染色过程中出现矛盾,则说明不是二分图。 for i = 1 to n:if i 未染色DFS(i, 1); //将i号点染色未…...
Caputo 分数阶微分方程-慢扩散方程初边值问题基于L1 逼近的空间二阶方法及其Matlab程序实现
2.3.3 Caputo 分数阶一维问题基于 L1 逼近的空间二阶方法 考虑如下时间分数阶慢扩散方程初边值问题 { 0 C D t α u ( x , t ) = u...
I.MX6ULL_Linux_驱动篇(29) GPIO驱动
Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以本篇的 LED 灯驱动最终也是对 I.MX6ULL 的 IO 口进行配置,与裸机实验不同的是,在 Linux 下编写驱动要符合 Linux 的驱动框架。I.MX6U-ALPHA 开发板上的 LED 连接到 I.MX6ULL 的 …...
jupyter的安装和使用
目录 ❤ Jupyter Notebook是什么? notebook jupyter 简介 notebook jupyter 组成 网页应用 文档 主要特点 ❤ jupyter notebook的安装 notebook jupyter 安装有两种途径 1.通过Anaconda进行安装 2.通过pip进行安装 启动jupyter notebook ❤ jupyter …...
Springboot新手开发 Cloud篇
前言: 👏作者简介:我是笑霸final,一名热爱技术的在校学生。 📝个人主页:个人主页1 || 笑霸final的主页2 📕系列专栏:后端专栏 📧如果文章知识点有错误的地方,…...
Linux:函数指针做函数参数
#include <stdio.h> #include <stdlib.h> //创建带有函数指针做参数的函数框架api //调用者要先实现回调函数 //调用者再去调用函数框架 //所谓的回调是指 调用者去调用一个带有函数指针做参数的函数框架,函数框架反过来要调用调用者提供的回调函数 …...
Vue3(递归组件) + 原生Table 实现树结构复杂表格
一、递归组件 什么是递归,Javascript中经常能接触到递归函数。也就是函数自己调用自己。那对于组件来说也是一样的逻辑。平时工作中见得最多应该就是菜单组件,大部分系统里面的都是递归组件。文章中我做了按需引入的配置,所以看不到我引用组…...
ArrayList底层源码解析
Java源码系列:下方连接 http://t.csdn.cn/Nwzed 文章目录前言一、**ArrayList底层结构和源码分析**无参构造调用创建ArrayList集合无参构造总结:发文3个工作日后 up 会把总结放入前言部分,但也诚邀读者总结,可放入评论区有参构造…...
python:DIY字符画的程序使用说明.doc
目录开发环境要求运行方法具体的操作步骤如下:代码示例源码及运行程序下载地址开发环境要求 本系统的软件开发及运行环境具体如下。 操作系统:Windows 7、Windows 10。 Python版本:Python 3.7.0。 开发工具:Python IDLE。 …...
【Python/Opencv】图像权重加法函数:cv2.addWeighted()详解
【Python/Opencv】图像权重加法函数:cv2.addWeighted()详解 文章目录【Python/Opencv】图像权重加法函数:cv2.addWeighted()详解1. 介绍2. API3. 代码示例与效果3.1 代码3.2 效果4. 参考1. 介绍 在OpenCV图像加法cv2.add函数详解详细介绍了图像的加法运…...
容器的老祖宗LXC和Docker的关系
一、什么是LXC? LXC(Linux Container的缩写)是一个基于Linux内核的容器虚拟化技术,它提供了一种轻量级、快速、简便的方式来创建和管理系统容器。与传统虚拟化技术不同,LXC并不会模拟硬件,而是利用Linux内…...
Webpack迁移Rspack速攻实战教程(前瞻版)
前言 rspack 即将开源,但社区中不乏有已经落地的 case ,比如 rspack-migration-showcase 、 modern.js 等。 基于此,本文将介绍如何迁移一个近似于 CRA( create-react-app ) 的项目到 rspack 。 在阅读本文前&#…...
一行代码“黑”掉任意网站
文章目录只需一行代码,轻轻一点就可以把任意网站变成暗黑模式。 首先我们先做一个实验,在任意网站中,打开浏览器开发者工具(F12),在 C1onsole 控制台输入如下代码并回车: document.documentElement.style.filterinve…...
51单片机入门 -驱动 8x8 LED 点阵屏
硬件型号、软件版本、以及烧录流程 操作系统:Windows 10 x84-64单片机:STC89C52RC编译器:SDCC烧录软件:stcgal 1.6开发板:普中51单片机开发板A2套件(2022) 在 VS Code 中新建项目到烧录的过程…...
Xinlinx zynq7045国产替代 FMQL45T900全国产化 ARM 核心板+扩展板
TES745D 是一款基于 FMQL45T900 的全国产化 ARM 核心板。该核心板将 FMQL45T900(与XC7Z045-2FFG900I 兼容)的最小系统集成在了一个 87*117mm 的核心板上,可以作为一个核心模块,进行功能性扩展,能够快速的搭建起一个信号…...
硬刚ChatGPT!文心一言能否为百度止颓?中国版ChatGPT“狂飙”的机会在哪儿?
文章目录目录产品背景发展历程科技简介主要功能合作伙伴结语文心一言 (英文名:ERNIE Bot) *是百度基于文心大模型技术推出的生成式对话产品,被外界誉为“中国版ChatGPT”,将于2023年3月份面向公众开放。 [40] 百度在人…...
Python 异步: 在非阻塞子进程中运行命令(19)
动动发财的小手,点个赞吧! 我们可以从 asyncio 执行命令。该命令将在我们可以使用非阻塞 I/O 写入和读取的子进程中运行。 1. 什么是 asyncio.subprocess.Process asyncio.subprocess.Process 类提供了由 asyncio 运行的子进程的表示。它在 asyncio 程序…...
蓝桥杯嵌入式第五课--输入捕获
前言输入捕获的考题十分明确,就是测量输入脉冲波形的占空比和频率,对我们的板子而言,就是检测板载的两个信号发生器产生的信号:具体来说就是使用PA15和PB4来做输入捕获。输入捕获原理简介输入捕获能够对输入信号的上升沿和下降沿进…...
Spring事务和事务传播机制
目录 Spring中事务的实现 1、通过代码的方式手动实现事务 2、通过注解的方式实现声明式事务 2.1、Transactional作用范围 2.2、Transactional参数说明 2.3、注意事项 2.4、Transactional工作原理 事务隔离级别 1、事务特性 2、Spring中设置事务隔离级别 2.1、MySQL事…...
基于OpenCV+CUDA实时视频抠绿、背景合成以及抠绿算法小结
一、关于抠绿 百度百科上描述抠绿“抠绿是指在摄影或摄像时,以绿色为背景进行拍摄,在后期制作时使用特技机的“色键”将绿色背景抠去,改换其他更理想的背景的技术。”绿幕的使用已经非常普遍,大到好莱坞大片,小到自媒体的节目,一些商业娱乐场景,几乎都用使用。但是很多非…...
MySQL 中的 UNION 语句
文章目录一、数据准备一、UNION 和 UNION ALL二、UNION 的执行顺序(UNION 和其他语句一同出现)三、MySQL 使用 UNION(ALL) ORDER 导致排序失效四、UNION 报错语法一、数据准备 -- 创建表 CREATE TABLE test_user (ID int(11) NO…...
高完整性系统工程(三): Logic Intro Formal Specification
目录 1. Propositions 命题 2.1 Propositional Connectives 命题连接词 2.2 Variables 变量 2.3 Sets 2.3.1 Set Operations 2.4 Predicates 2.5 Quantification 量化 2.6 Relations 2.6.1 What Is A Relation? 2.6.2 Relations as Sets 2.6.3 Binary Relations as…...
【linux】多线程概念详述
文章目录一、线程基本概念1.1 进程地址空间与页表1.2 页表结构1.3 线程的理解1.3.1 如何描述线程1.4 再谈进程1.5 代码理解1.5.1 原生库提供线程pthread_create1.6 资源共享问题1.7 资源私有问题二、总结2.1 什么是线程2.2 并行与并发2.3 线程的优点2.4 线程的缺点2.5 线程异常…...
【Java】P8 面向对象(3)方法 基本知识
面向对象 方法方法方法的声明权限修饰符返回值类型方法名形参列表方法体简单案例方法 方法 是对类或对象行为特征的抽象,用来完成某个功能的操作。方法的目的 是为了实现代码复用,减少冗余,简化代码;方法不能独立存在,…...
js中null和undefined的区别
js中null和undefined的区别?这也是一个常见的js面试题 相同点 1,都是基本类型。 2,做判断值都是false。 !!null false // true !!undefined false // true不同点 1,诞生时间null在前,undefined在后。因为js作者Brendan-Eic…...
外贸免费开发网站建设/百家号排名
看了藏地密码,得到了一个更通俗的解释: 为了一头獒而这样做,连我都有些敬佩你了。 人,活一辈子,总该做点什么,他们有自己存在的目的和追求。但大多数的人,仅仅是为了生存而疲于奔波ÿ…...
网站制作与网页制作/成都seo优化推广
Nacos除了服务的注册发现之外,还整合了配置中心。 通过Nacos的配置管理功能,可以将整个架构体系内的所有配置都集中在Nacos中存储。 改造nacos-provider如下 1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <proje…...
樟木头镇做网站/吉林seo推广
4.1 场景问题 4.1.1 装配电脑的例子 1:旧的硬盘和电源 小李有一台老的台式电脑,硬盘实在是太小了,仅仅40GB,但是除了这个问题外,整机性能还不错,废弃不用太可惜了,于是决定去加装一块新的硬…...
微信微商城在哪里进入/网络seo首页
这两天在做一个问题的二次开发,遇到一些小问题;问题如下 : 在写日志文件时导致Session丢失,日志文件输出目录为/BIN/log.log文件,没办法只能找同事帮忙,同事说是因为强制向Bin目录中写入文件导致IIS进程崩溃…...
wordpress 模板结构/宁波seo教程
Focus 事件是当页面中的元素获得或失去焦点时触发,相关事件通常与document.hasFocus()方法和document.activeElement协同使用,来洞察用户是如何浏览网页的。 下面是六个focus event: blur:当一个元素失去焦点时发生的事件…...
建设网站都要学些什么问题/百度关键词排名查询
高级语言虚拟机 https://hllvm-group.iteye.co m/ RednaxelaFX https://www.iteye.com/blog/rednaxelafx-2033104...