从promise到await

news2024/10/1 1:17:31

在之前的这篇文章中,已经讲完了从异步操作到promise的诞生,但是promise也仅仅是做到了异步操作和结果的分离,当我们有多个异步操作,且结果前后有依赖的时候,不可避免的,就会出现一系列的.then方法。还是不大优雅。

最理想的情况是,我们能把异步操作写成同步的样子。当执行异步操作时,让代码卡在那里,等异步操作完成后再执行后续的代码。【注一】

这里,需要先了解迭代器和生成器。

一,可迭代对象和迭代器

Iterator是ES6提出来的迭代器。因为ES6开始数据结构新增了Set和Map,再加上已有的Array和Object,此外用户还可以自行组合它们来形成组合数据结构,复杂的数据结构导致循环遍历难度加大,为简化和统一循环方式,ES6就给出了迭代器(Iterator)这个接口来提供统一访问机制for..of或者…展开符。

迭代意味着遍历的同时能记录结果

它为各种不同的数据结构提供统一的访问机制。提供了一种按序访问集合内各个元素的方法,让我们可以方便地遍历集合内所有的元素。

1.1,可迭代对象和迭代器的区别

其实就是es6新增了迭代器的概念,所有满足迭代器协议的对象,都是可迭代对象。这里需要区分可迭代对象和迭代器的概念。

可迭代对象迭代器
可使用方法for...of...展开符next()方法
来源array,str,set,map等具备[Symbol.iterator]方法,且该方法返回一个迭代器的对象具备next方法,能够指针移动实现遍历可迭代对象的对象
举例array,str,set,map等array,str,set,map等的[Symbol.iterator]方法的返回

迭代器协议:

具备[Symbol.iterator]方法,且该方法会返回一个迭代器对象,该对象的特征是具备next方法,能够进行迭代。

1.2,迭代器的工作原理

创建一个指针对象,指向当前数据结构的起始位置
第一次调用next方法时,指针指向数据结构的第一个成员
接下来调用next方法,指针后移,直到指向最后一个成员

不断地调用next重复获取过程,然后每次都返回一个结果。等到没有东西可返回了,就终止。因此next的返回对象有两个属性donevaluedone表示是否结束了,value表示当前迭代的结果。当donetrue的时候,表示迭代已结束,这时候是没有返回结果的也就是没有value这个属性。

//迭代器生成函数
function myIterator(arr) {
    let index = 0
    return {
        next: function() {
            let done = ( index >= arr.length );
            let value = ! done ? arr[index++] : undefined;
            
            return { value, done };
        }
    }
}

let myIter = myIterator([1,2,3]);
console.log(myIter.next());//{ value: 1, done: false }
console.log(myIter.next());//{ value: 2, done: false } 
console.log(myIter.next());//{ value: 3, done: false }  
console.log(myIter.next());//{ value: undefined, done: true }

1.3,Array等数据结构的默认迭代器

ES6中原生的可迭代对象迭有Array、Set、Map和String,for..of能够遍历它们是因为它们原型对象上具有Symbol.iterator属性,该属性指向该数据结构的默认迭代器方法,**当使用for...of..迭代可迭代对象时,js引擎就会调用其Symbol.iterator方法,从而返回相应的默认迭代器。然后执行完其中的next方法。**举例:

var arr = [1, 2, 3, 4, 5];     //数组是一个迭代器
// 使用for..of时,,js引擎就会调用其`Symbol.iterator`方法,从而返回相应数据的默认迭代器
for(var v of arr){
    console.log(v); // 12345
}

那么既然它的原型对象上有Symbol.iterator方法,且返回的是对应的默认迭代器,我们就可以利用它生成对应的迭代器,然后使用next()方法访问值:

var arr = [1, 2, 3, 4, 5];     //数组是一个迭代器
//arr[Symbol.iterator]()会返回arr的默认迭代器,于是就可以使用next
var it = arr[Symbol.iterator]();
console.log(it.next())//{ value: 1, done: false }
console.log(it.next())//{ value: 2, done: false }
console.log(it.next())//{ value: 3, done: false }
console.log(...it)//4 5,只打印出剩余的没有被迭代的,所以...应该也是利用的迭代器的next
console.log(it.next())//{ value: undefined, done: true }//剩余的两个值被...迭代过了,于是这里就结束了

1.2,手写一个可迭代对象

也就是说,一个数据结构只要有Symbol.iterator方法且Symbol.iterator方法返回具备next方法,就可以认为它是是可迭代的(iterable)对象

也就是说,需要满足两个条件:

1,该对象具备Symbol.iterator方法
2,Symbol.iterator方法返回一个对象,该对象具备next方法(迭代器)。
// 实现一个可迭代对象
const myIterable = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    }
  }
};

// 遍历可迭代对象
for (const item of myIterable) {
  console.log(item);
  // 输出 1 2 3
}

// 通过迭代器对象遍历可迭代对象
const iterator = myIterable[Symbol.iterator]();
console.log(iterator.next()); // {done: false, value: 1}
console.log(iterator.next()); // {done: false, value: 2}
console.log(iterator.next()); // {done: false, value: 3}
console.log(iterator.next()); // {done: true, value: undefined}

能够使用for...of遍历和...扩展符展开。当使用这两种方法时,js引擎其实是调用这个可迭代对象的[Symbol.iterator]方法,从而得到一个迭代器,然后执行这个迭代器的next方法,从而取到其中的值。

但是如果想要使用next方法手动遍历,就需要const myIterable2=createIterator(obj)[Symbol.iterator](),手动执行这个可迭代对象的[Symbol.iterator]方法,从而得到迭代器(具备next方法的对象)。

1.3,将不可迭代的object处理成可迭代

object类型之所以不能迭代,就是因为它的原型对象上没有[Symbol.iterator]属性,想想看,一个对象的属性间并没有严格的顺序要求,自然不要求能迭代。

// 自定义一个可迭代对象
function createIterator(obj){
    return {    // 返回一个迭代器对象
        //模仿原生迭代器添加Symbol.iterator方法
        [Symbol.iterator]: function () { 
            const keys=Object.keys(obj)
            let i=0
            return {
                next:function () {  //迭代器对象一定有next()方法
                    const done = ( i >= keys.length );
                    const key=keys[i]
                    const value = !done ? obj[key] : undefined;
                    i++
                    return {    //next()方法返回结果对象
                        value: value,
                        done: done
                    }
                }
            }
         }
    }
}
const obj={
    name:'名字',
    age:18

}
const myIterable=createIterator(obj)
// 使用 for-of 循环遍历可迭代对象
for(var v of myIterable){
    console.log(v)
    //名字
    //18
}
console.log([...myIterable])//['名字',18]
  
const myIterable2=createIterator(obj)[Symbol.iterator]()
console.log(myIterable2.next())//{ value: '名字', done: false }
console.log(myIterable2.next())//{ value: 18, done: false }  
console.log(myIterable2.next())//{ value: undefined, done: true }

二,生成器Generator

它存在的最大作用就是帮助我们快速生成可迭代对象/生成器

JavaScript 中的生成器(Generator)是一种特殊的函数,可以用来定义在运行时生成一系列值的迭代器。Generator 函数以 function* 开头,内部使用 yield 关键字来指定生成器迭代过程中产生的每个值,每次遇到 yield 关键字时暂停函数的执行,可以通过调用迭代器上的 next 方法来恢复函数的执行。

2.1,创建一个生成器

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const iter = myGenerator(); // 创建迭代器
for(var v of iter){
  console.log(v)//1,2,3
}
const iter2 = myGenerator(); // 创建迭代器
console.log(iter2.next()); // { value: 1, done: false }
console.log(iter2.next()); // { value: 2, done: false }
console.log(iter2.next()); // { value: 3, done: false }
console.log(iter2.next()); // { value: undefined, done: true }

console.log(iter === iter[Symbol.iterator]()); // true,对象本身是可迭代对象

生成器函数返回的,既是一个可迭代对象(可以使用for...of),又是一个迭代器(可以直接使用next())

每次yield就会生成一个断点。每次next就执行到这个断点过。

2.2,既是可迭代对象,又是迭代器

类似于这样,Symbol.iterator是返回自身。自身上既有Symbol.iterator又有next,从而可以返回既是可迭代对象,又是迭代器。

function myGenerator(arr) {
  let i=0
  return {
    next(){
      const done =  i >= arr.length;
      const value = !done ? arr[i++] : undefined;
      return {    //next()方法返回结果对象
          value: value,
          done: done
      }
    },
    [Symbol.iterator]() { 
      return this
    }
  }
}

const iter = myGenerator([1,2,3,4,5,6]); // 创建迭代器
for(var v of iter){
  console.log(v)//1,2,3,4,5,6
}
console.log(iter === iter[Symbol.iterator]()); // true,对象本身是可迭代对象
const iter2 = myGenerator([1,2,3,4,5,6,7]); // 创建迭代器
console.log(iter2.next()); // { value: 1, done: false }
console.log(iter2.next()); // { value: 2, done: false }
console.log(iter2.next()); // { value: 3, done: false }
console.log(iter2.next()); // { value: 4, done: false }

生成器生成的对象就有这样的特征。

2.3,next传递参数

yield 表达式本身没有返回值,或者说总是返回 undefined 。 next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。【注意这里是上一个】,也就是说,next在第一个yield处停住,第二次的next入参才是这第一个yield的返回值。

也就是说第一个next带入参没意义,因为它没有上一个断点yield。

function* foo(x) {
    let y = x * (yield)
    return y
    console.log("111")//后续的代码不会被执行
    yield 2
}
const it = foo(6)
it.next()//执行到yield过,x*这段语句还未执行
let res = it.next(7)//next传入7作为上一个yield的返回,代码接着执行,计算后赋值给y,然后return结束
console.log(res) // { value: 42, done: true }
it.next(8)

return 会强制生成器进入关闭状态,提供给 return 方法的值,就是终止迭代器对象的值,也就是说此时返回的对象状态为true,值为传入的值。

2.4,return方法提前终止生成器

和上文中一样,return方法也会强制生成器进入关闭状态,提供给 return 方法的值,就是终止迭代器对象的值,也就是说此时返回的对象状态为true,值为传入的值。

function* foo() {
    console.log("11")
    yield 1
    console.log("22")
    yield 2
    console.log("33")
    yield 3
    console.log("44")
    yield 4
}
const it = foo()
console.log("next",it.next())//next { value: 1, done: false }
console.log("return",it.return("test"))//return { value: 'test', done: true }

2.5,throw抛出错误终止生成器

throw() 方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭。

function* foo() {
    console.log("11")
    yield 1
    console.log("22")
    yield 2
    console.log("33")
    yield 3
    console.log("44")
    yield 4
}
const it = foo()
console.log("next",it.next())//next { value: 1, done: false }
console.log("return",it.throw(new Error('出错了!')))//Error: 出错了!

简单的理解就是,next()、throw()、return()方法,都是把yield以及后面换成传入的参数。

2.6,yield* 表达式委托迭代

yield* 允许我们在 Generator 函数中调用另一个 Generator 函数或可迭代对象。

Generator 函数执行到一个 yield* 表达式时,它会暂停执行,并且将执行权转移到另一个 Generator 函数或可迭代对象中。直到这个函数或对象迭代结束后,执行权才会返回到原 Generator 函数中。

这也叫委托迭代。通过这样的方式,能将多个生成器连接在一起。

function * anotherGenerator(i) {
    yield i + 1;
    yield i + 2;
    yield i + 3;
}

function * generator(i) {
    yield* anotherGenerator(i);
    yield "最后一个"
}
var gen = generator(1);
console.log(gen.next().value)//2
console.log(gen.next().value)//3
console.log(gen.next().value)//4
console.log(gen.next().value)//最后一个
for (let value of generator(2)) {
    console.log(value); // 输出 3,4,5,'最后一个'
}

三,Generator在异步编程的应用到await的诞生

3.1,作为异步编程的解决方案

当我们知道Generator和yield配合使用,能够暂停代码的执行,就应该能敏锐地意识到它可以用来解决异步编程,尽管Promise已经极大的提升了异步编程的可读性和可维护性,避免了回调地狱的产生。但在某些情况下,代码还是会变得非常复杂,特别是在需要同时处理多个异步操作,或者需要控制这些异步操作的顺序时。使用 Promise 还是需要写大量的 then 语句来处理异步操作的结果,这可能在一定程度上增加了代码的复杂性。

而Generator函数凭借其阻塞式调用机制使得异步代码可以写得像同步代码一样,举之前读取文件的例子:

const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
let res1,res2,res3
readFile('test.txt')
.then((res)=>{
    res1=res
    return readFile('test1.txt')
})
.then((res)=>{
    res2=res
    return readFile('test2.txt')
})
.then((res)=>{
    res3=res
    console.log("结果",res1+res2+res3)
})

使用generator函数改写:

const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
const generator = myGenerator();
generator.next().value//取得第一个promise对象
.then((result1)=>{
   return generator.next(result1).value
})//取得第二个promise对象
.then((result2)=>{
   return generator.next(result2).value
})//取得第三个promise对象
.then((result3)=>{
    generator.next(result3)//取得最后一个文件的值,继续执行代码
})

如果只看:

function* myGenerator() {
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}

是不是就和同步差不多了?

但是看起来底下的generator.next()还是有一堆的then,和之前使用promise差不多呀。

为了方便理解,我们把上面的代码改写成回调地狱的形式。


const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
const generator = myGenerator();
generator.next().value.then((result1)=>{
    generator.next(result1).value.then((result2)=>{
        generator.next(result2).value.then((result3)=>{
            generator.next(result3)
        })
    })
})

3.2 ,封装生成器的递归传值

注意到这一坨玩意儿其实是递归传值:

generator.next().value.then((result1)=>{
    generator.next(result1).value.then((result2)=>{
        generator.next(result2).value.then((result3)=>{
            generator.next(result3)
        })
    })
})

那么,我们其实可以想办法把他封装一下,于是代码就变成了这个样子:

const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
//封装函数
function co(generator){
    const gen=generator();
    function _next(val){
        var p=gen.next(val);
        if(p.done) return;
        //这里用Promise.resolve是为了避免p.value不是promise,将之转化一下
        Promise.resolve(p.value).then(res=>{
            _next(res);
        })
    }
    _next()
}
co(myGenerator)

到这一步其实已经差不多了,网上有复杂一丢丢的实现,但是目前这样的封装,已经足够我们理解它是如何将异步转化成看起来同步的写法。

四,async和await的诞生

假如说js引擎帮我们在代码执行时引入co函数,并且执行co(myGenerator)的话,那么对于我们写代码的人而言,我们是不是只需要写:

const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
function* myGenerator() {
    //先按照顺序读取三个文件
   const txt1= yield readFile('./text1.txt');
   const txt2=yield readFile('./text2.txt');
   const txt3=yield readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}

这不和async/await差不多了吗。

也就是说,可以这样子理解:

当我们写下asycn的时候,js引擎就把它当做一个生成器函数处理,并且帮助我们引入了co函数,并且做好递归传值的工作。
而await就等同于yield。

请添加图片描述

于是就产生了async和await,它才让我们写异步代码像同步一样丝滑。

const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
async function test() {
    //先按照顺序读取三个文件
   const txt1= await readFile('./text1.txt');
   const txt2= await readFile('./text2.txt');
   const txt3= await readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
test()

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

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

相关文章

【JS每N日一练】 将CSDN文章自动生成Markdown列表

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 分析及编码页面分析枚举列表处理li&#xff0c;获取Markdown完成功能拼装 2️⃣ 完整代码及运行结果完整代码运行结果 &#x1f6ec; 总结 &#x1f6eb; 问题 描述 作为程序员必备素质-懒&#xff0c;今天又给自己了一个造了个需…

QGIS--开发OpenSCENARIO动态场景(一)--Ubuntu20.04 安装QGIS

qgis的git&#xff1a; GitHub - qgis/QGIS: QGIS is a free, open source, cross platform (lin/win/mac) geographical information system (GIS) qgis的官网:Welcome to the QGIS project! qgis插件包下载地址&#xff1a;https://plugins.qgis.org/plugins/ 1.Prerequisi…

java IO流_2

目录 字节缓冲流 序列化 使用对象流实现序列化 标准输入输出流 转换流 打印流 PrintStream PrintWriter properties读写文件 字节缓冲流 字节流 import java.io.FileInputStream; import java.io.FileOutputStream;public class FileCopy {public static void main(Str…

年薪30W+,待遇翻倍,我的经历值得每个测试人借鉴

从自考大专到出走公司&#xff0c;从半年无业露宿深圳北站&#xff0c;从8k…到11.5k…再到20k&#xff0c;我的经历值得每个测试人借鉴 或许学历并没有那么重要 12年高考之后&#xff0c;在朋友的介绍下&#xff08;骗了过去&#xff09;&#xff0c;没有好好的读大学&#x…

java mysql高校后勤管理系统的设计与开发 宿舍公寓管理

1&#xff0e;用户管理子系统&#xff1a;采用通过用户密码的登录机制&#xff0c;管理员登录后可以修改个人信息&#xff08;用户名、密码、姓名、联系方式&#xff09;&#xff0c;具有楼的属性&#xff0c;只能对所管辖宿舍楼进行查询、修改等操作。 2.宿舍信息管理子系统&a…

上海出台新政数字经济、人工智能利好

利好一、上海印发 《上海市推进算力资源统一调度指导意见》的通知 数字经济、人工智能的利好不断。 19日&#xff0c;上海市经济信息化委印发《上海市推进算力资源统一调度指导意见》的通知。 其中提到&#xff1a; 到2023年底&#xff0c;依托本市人工智能公共算力服务平台…

【U8+】win10/11系统注册用友U8硬加密

【问题需求】 由于用友U813.0及之前版本都是硬加密&#xff0c; 并且注册的时候严格要求使用IE浏览器。 但是随着系统的升级&#xff0c;绝大多数的都开始使用win10/11系统。 并且IE浏览器已经不支持win10/11系统&#xff0c; 取而代之的是Edge浏览器。 【经验分享】 以下分享…

扬帆优配|放量跌停和缩量跌停的区别?哪个更好?

投资者在买卖股票的过程中股票跌停的话&#xff0c;一般会呈现两种状况&#xff1a;一种是放量跌停&#xff0c;一种是缩量跌停。很多投资者不明白两者到底有什么差异&#xff0c;那么放量跌停和缩量跌停的差异是什么&#xff1f;哪个更好&#xff1f;下面就由扬帆优配有为我们…

使用node版本管理器gnvm

目录 一、官网 二、下载 三、查看本机node安装地址 四、将gnvm放到node安装目录 五、安装其他版本node&#xff08;以管理员身份打开CMD&#xff09; 六、使用指定版本&#xff08;以管理员身份打开CMD&#xff09; 七、查看当前版本&#xff08;以管理员身份打开CMD&…

Spring依赖注入的三种方式使用及优缺点

初学Spring的时候,我们从Spring容器中获取Bean对象都是通过bean标签先将Bean对象注册到Spring容器中&#xff0c;然后通过上下文对象congtext的getBean方法进行获取&#xff0c;显然这种方法较为麻烦&#xff0c;所以有了更简单的存方法&#xff1a;五大类注解&#xff1b;取方…

应急响应 - Windows用户分析,Windows隐藏账号分析,Windows克隆账号分析

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 Windows账户分析 一、普通用户二、隐藏用户1、lusrmgr.msc2、注册表 三、克隆账号 攻击者…

mysql varchar类型字段为数字时,不带引号查询时查询结果与事实不符

背景 今天出现了一个bug&#xff0c;在数据库中我们将订单表中的order_no从之前的bigint(20)改成varchar(20)后&#xff0c;原有的代码逻辑在进行时查询时&#xff0c;之前是以Long类型传参查询的。 select * from order_main where order_no16541913435669023debug时的时候发现…

2023年湖北安全员ABC报名条件和报名资料是什么?

一、湖北安全员ABC报名条件要求&#xff1a; 1.安全员A证针对的是企业主要负责人&#xff0c;包括法定代表人、总经理&#xff08;总裁&#xff09;、分管安全生产的副总经理&#xff08;副总裁&#xff09;、分管生产经营的副总经理&#xff08;副总裁&#xff09;、技术负责…

全景丨0基础学习VR全景制作,平台篇第八章:全局功能-开场地图

大家好欢迎观看蛙色平台使用教程 功能位置示意 一、本功能将用在哪里&#xff1f; 开场地图分为两种&#xff0c;分别是高德地图和手绘地图。 高德地图点位目前系统自动借用高德官方地图位置&#xff0c;手绘地图点位需手动添加 高德地图展示 高德地图展示 二、如何使用本功…

JaveEE UDP 与 TCP 原理

这篇博客真的很详细很详细很详细&#xff0c;不打算试试看吗 > 。o 文章目录 JaveEE & UDP 与 TCP 原理1. 应用层协议&#xff08;自定义组织格式&#xff09;2. 传输层UDP协议2.1 数据报报文格式2.1.1 源端口与目的端口2.1.2 报文长度和校验和 3. 传输层TCP协议3.1 TCP…

MySQL 主键自增也有坑?

在上篇文章中&#xff0c;松哥和小伙伴们分享了 MySQL 的聚簇索引&#xff0c;也顺便和小伙伴们分析了为什么在 MySQL 中主键不应该使用随机字符串。但是主键不用随机字符串用什么&#xff1f;主键自增&#xff1f;主键自增就是最佳方案吗&#xff1f;有没有其他坑&#xff1f;…

大数据实战 --- 日志文件

目录 开发环境 数据描述 功能需求 数据准备 分析数据 HBase HIive 统计查询 开发环境 HadoopHiveSparkHBase 启动Hadoop&#xff1a;start-all.sh 启动zookeeper&#xff1a;zkServer.sh start 启动Hive&#xff1a; nohup hiveserver2 1>/dev/null 2>&1 …

Python 图像处理实用指南:1~5

原文&#xff1a;Hands-On Image Processing with Python 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 计算机视觉 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 当别人说你没有底线的时候&#xff0c;…

如何恢复回收站中被删除的文件?高效的恢复技巧

一般情况下&#xff0c;我们从电脑上普通删除的文件&#xff0c;会经过回收站&#xff08;除非文件过大&#xff09;&#xff0c;想要在回收站找回删除的东西&#xff0c;是很简单的&#xff0c;我们只需要打开回收站&#xff0c;找到删除的文件&#xff0c;右键点击并选择还原…

jmeter配置文件

在jmeter安装目录的bin目录下&#xff0c;有多个配置文件 其中最核心的是jmeter.properties jmeter.properties 修改语言&#xff1a;languagezh_CN #简体中文&#xff0c;也可以在GUI页面修改 远程主机配置   # 配置远程主机的 IP&#xff0c;默认为本机。用逗号&q…