前端工程化实践——javaScript 手写rollup

news2024/12/23 11:02:18

webpack打包非常繁琐,打包体积较大。rollup主要打包js库。vue/react/angular都在用rollup作为打包工具。

rollup项目初体验

新增文件夹rollupTest

初始化项目:npm init -y

安装依赖

npm install rollup -D

修改配置文件package.json

{
  "name": "rollupTest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "rollup --config"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "rollup": "^2.79.1"
  }
}

 新增src/main.js文件

console.log('hello')

新增rollup配置文件rollup.config.js

export default {
    input:'./src/main.js',//打包文件入口
    output:{
        file:'./dist/bundle.js',//指定打包后存放的文件路径
        format:'cjs',//打包文件输出格式,输入用require,输出用module.exports
        name:'bundleName'//打包输出文件的名字
    }
}

查看打包结果

执行命令:npm run build

结果如下

生成了打包文件dist

treeshaking初体验

新增src/msg.js文件

export var name = 'zhangshan'
export var age = '18'

修改src/main.js

/*//例子1
console.log('hello')
*/

//treeshaking例子
import {name,age} from './msg'
console.log(name);

执行命令npm run build

查看dist/bundle.js打包后的文件中只有name没有age的定义,这是因为age引入了但没使用

Rollup手写-前置知识

magic-string

magic-string是一个操作字符串和生成source-map的工具。magic-string 是 rollup 作者写的一个关于字符串操作的库。

安装命令:

npm i magic-string -D -S

下面是 github 上的示例:

var MagicString = require('magic-string');
var magicString = new MagicString('export var name = "beijing"');
//类似于截取字符串
console.log(magicString.snip(0,6).toString()); // export
//从开始到结束删除字符串(索引永远是基于原始的字符串,而非改变后的)
console.log(magicString.remove(0,7).toString()); // var name = "beijing"

//很多模块,把它们打包在一个文件里,需要把很多文件的源代码合并在一起
let bundleString = new MagicString.Bundle();
bundleString.addSource({
    content:'var a = 1;',
    separator:'\n'
});
bundleString.addSource({
    content:'var b = 2;',
    separator:'\n'
});
/* let str = '';
str += 'var a = 1;\n'
str += 'var b = 2;\n'
console.log(str); */
console.log(bundleString.toString());
// var a = 1;
//var b = 2;

其中引入方法

const MagicString = require('magic-string');

magic-string的好处是会生成sourcemap

AST

抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。通过操纵这颗树,我们可以精准的定位到声明语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作。

AST通过词法分析和语法分析将一段代码进行拆分。

词法分析:根据空格分词

语法分析:根据每个单词的意思,作用分开

你可以简单理解为 它就是你所写代码的的树状结构化表现形式。webpack、UglifyJs、lint等工具的核心都是通过ast抽象语法书实现的,实现对代码的检查、分析。底层是调用的js parser 来生成抽象语法树。

AST工作流

  • Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点
  • Transform(转换) 对抽象语法树进行转换
  • Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码

acorn

acorn用来将程序解析成ast语法树结构

npm i acorn -d -s

const acorn = require('acorn');

acorn的默认用法非常简单,直接来段代码字符串parse一下就出来AST结构了:

let acorn = require("acorn");

console.log(acorn.parse("for(let i=0;i<10;i+=1){console.log(i);}", {ecmaVersion: 2020}));
Node {
  type: 'Program',
  start: 0,
  end: 39,
  body: [
    Node {
      type: 'ForStatement',
      start: 0,
      end: 39,
      init: [Node],
      test: [Node],
      update: [Node],
      body: [Node]
    }
  ],
  sourceType: 'script'
}

可以看到这个 AST 的类型为 program,表明这是一个程序。body 则包含了这个程序下面所有语句对应的 AST 子节点。

每个节点都有一个 type 类型,例如 Identifier,说明这个节点是一个标识符;

可以通过在线网站astexplorer实时把代码转成语法树

代码目录结构

节点遍历walk.js函数

walk接收两个参数,参数一是节点,参数二是一个对象

对象第一个值是enter节点进入函数,对象第二个值是leave节点退出函数

walk函数实现的深入优先搜索方式遍历节点,优先遍历子节点,子节点遍历结束后才遍历兄弟节点。

Object.keys()用于获得由对象属性名组成的数组,可与数组遍历相结合使用

walk.js

/*
walk.js AST节点遍历函数
*/
//walk接收两个参数,参数一是节点,参数二是一个对象
//对象第一个值是enter节点进入函数,对象第二个值是leave节点退出函数
//enter和leave函数在调用时传入,不在此定义
function walk(ast,{enter,leave}){
    //调用visit方法 第二个参数是父节点,顶级语句没有父节点
    visit(ast,null,enter,leave);
}
/**
 * 访问此node节点
 * @param {*} node 
 * @param {*} parent 
 * @param {*} enter 
 * @param {*} leave 
 */
function visit(node,parent,enter,leave){
    //1.如果调用了enter方法 先执行此节点的enter方法
    if(enter){
        enter(node,parent)
    }
    //2.根据深度优先搜索,先遍历该节点的子节点
    //观察语法树可以发现,子节点所在的节点必然是个对象,因此只需要遍历是对象的节点
    // Object.keys(node)获取node对象里所有属性的Key值,在结合filter过滤器使用,筛选出符合条件的
    let childKeys = Object.keys(node).filter(key=>typeof node[key]==='object')
    childKeys.map(childKey=>{
        let value = node[childKey]
        //分情况讨论
        //2.1.如果value是数组 对数组每项进行遍历
        if(Array.isArray(value)){
            //递归调用visit,将当前节点val、父节点node、进入函数和退出函数传进去
            value.map(val=>{
                visit(val,node,enter,leave)
            })
        } else {
            //2.2不是数组则调用一次visit
            visit(value,node,enter,leave)
        }

    })
    //3.如果调用了leave方法再执行离开的方法
    if(leave){
        leave(node,parent)
    }
}
module.exports = walk;



测试walk函数-新增测试文件ast.json

{
    "type": "Program",
    "start": 0,
    "end": 25,
    "body": [
      {
        "type": "ImportDeclaration",
        "start": 0,
        "end": 23,
        "specifiers": [
          {
            "type": "ImportDefaultSpecifier",
            "start": 7,
            "end": 8,
            "local": {
              "type": "Identifier",
              "start": 7,
              "end": 8,
              "name": "$"
            }
          }
        ],
        "source": {
          "type": "Literal",
          "start": 14,
          "end": 22,
          "value": "jquery",
          "raw": "'jquery'"
        }
      }
    ],
    "sourceType": "module"
  }

测试walk函数-新增ast.js文件

/*ast.js
实现功能:
1.将源代码解析为AST语法树
2.调用walk函数遍历AST语法树
*/
//引入acorn 
//webpack和rollup都是使用acorn模块把源代码转成抽象语法树AST
let acorn = require('acorn');
let walk = require('./walk');
//代码也可以从文件中导入,不必写死
let astTree = acorn.parse(`import $ from 'jquery';`,{
    locations:true,ranges:true,sourceType:'module',ecmaVersion:8
});

//定义缩进,padding每次填充ident个空格
let ident = 0;
const padding = ()=>" ".repeat(ident)
console.log(astTree.body)
//从ast在线网站https://astexplorer.net/可以发现,有意义的语句开始在body中
astTree.body.map(statement=>{
    //遍历AST语法树种的每个语句,由walk遍历这条语句
    //遍历时采用深度优先方式
    walk(statement,{
        enter(node){
            //我们只关心有type的节点
            if(node.type){
                console.log(padding() + node.type);
                ident+=2;
            }
        },
        leave(node){
            if(node.type){
                ident-=2;
                console.log(padding()+node.type)
            }
        }
    })
})

打印结果

作用域模拟

作用域:在js中,作用域是用来规定变量访问范围的规则

作用域链:作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

Scope 的作用很简单,它有一个

names 属性数组,用于保存这个 AST 节点内的变量。rollup根据这个

Scope链构建出变量的作用域。

Scope.js

/**
 * scope.js模拟作用域类
 */
class Scope{
    /**
     * 定义构造函数,入参是options对象,默认值是{}
     */
    constructor(options ={}){
        this.name = options.name//给作用域起个名字
        this.parent = options.parent//父作用域
        this.names = options.params || []//此作用域中有哪些变量
    }
    /**
     * add添加方法,添加name到作用域对象
     */
    add(name){
        this.names.push(name)
    }
    /**
     * findDefiningScope查找方法,查找name属于哪个作用域对象
     */
    findDefiningScope(name){
        if(this.names.includes(name)){
            return this//如果当前对象的names包含name则返回当前对象
        }else if(this.parent){
            return this.parent.findDefiningScope(name)
        }else{
            return null
        }
    }
}
module.exports = Scope;

测试scope--testScope

let Scope = require('./scope');
let a = 1;

function one() {
  let b = 2;

  function two(age) {
    let c = 3;
    console.log(a, b, c, age);
  }

  two();
}

one();
let globalScope = new Scope({
  name: 'globalScope', params: [], parent: null
});
globalScope.add('a');
let oneScope = new Scope({
  name: 'oneScope', params: [], parent: globalScope
});
oneScope.add('b');
let twoScope = new Scope({
  name: 'twoScope', params: ['age'], parent: oneScope
});
twoScope.add('c');

let aScope = twoScope.findDefiningScope('a');
console.log(aScope.name);

let bScope = twoScope.findDefiningScope('b');
console.log(bScope.name);

let cScope = twoScope.findDefiningScope('c');
console.log(cScope.name);

let ageScope = twoScope.findDefiningScope('age');
console.log(ageScope.name);

let xxxScope = twoScope.findDefiningScope('xxx');
console.log(xxxScope);

Mini-rollup实现

新增analyse.js

/**
 * analyse.js 找出当前模块使用到了哪些变量
 * 还要知道哪些是当前模块产生的,哪些是引用别的模块产生的
 * @param {*} ast ast语法树
 * @param {*} MagicString 源代码
 * @param {*} module 属于哪个模块
 */
const Scope = require('./scope')
const walk = require('./walk')
function analyse(ast,MagicString,module){
    let scope = new Scope()
    //遍历当前语法树的所有的顶级节点
    ast.body.map(statement=>{
        //建立作用域链
        function addToScope(declaration){
            let name = declaration.id.name//获得这个声明的变量
            scope.add(name)//将变量添加到当前的全局作用域中
            if(!scope.parent){//当前是全局作用域
                statement._defines[name] = true//在全局作用域下声明一个全局的变量say
            }
        }
        //通过Object.defineProperties属性为statement添加_source属性,方便日后取源代码数据
        Object.defineProperties(statement,{
            _defines:{value:{}},//存放当前模块定义的所有全局遍历
            _dependsOn:{value:{}},//当前模块没有定义但是使用的变量,即引入的外部的变量
            _included:{value:false,writable:true},//此语句是否已包含在打包中,防止同一条语句被多次打包
            _source:{value:MagicString.snip(statement.start,statement.end)}
        })
        //构建作用域链
        walk(statement,{
            enter(node){
                let newScope
                switch(node.type){
                    case 'FunctionDeclaration'://节点类型为函数声明,创建新的作用域对象
                        const params = node.params.map(x=>x.name)
                        addToScope(node)
                        newScope = new Scope({
                            parent:scope,//父作用域就是当前的作用域
                            params
                        })
                        break
                    case 'VariableDeclaration':
                        node.declarations.forEach(addToScope)//这样写不会有问题码????
                        break
                }
                if(newScope){//说明当前节点声明了一个新的作用域
                    //如果此节点生成一个新的作用域,那么会在这个节点放一个_scope,指向新的作用域
                    Object.defineProperty(node,'_scope',{value:newScope})
                    scope = newScope
                }
            },
            leave(node){
                if(node._scope){//离开的节点产生了新的作用域,离开节点时需要让scope回到父作用域
                    scope = scope.parent
                }
            }
        })
    })
    console.log('第一次遍历结束',scope)
    ast._scope = scope
    //找出外部依赖_dependsOn
    ast.body.map(statement=>{
        walk(statement,{
            enter(node){
                if(node._scope){//该节点产生了新的作用域
                    scope = node._scope
                }
                if(node.type === 'Identifier'){//是个标识符
                    //从当前的作用域向上递归,照这个变量在哪个作用域中定义
                    const definingScope = scope.findDefiningScope(node.name)//查看该name是否已经被调用过
                    if(!definingScope){
                        statement._dependsOn[node.name]=true //表示这是一个外部依赖的变量
                    }
                }
            },
            leave(node){
                if(node._scope){
                    scope = scope.parent
                }
            }
        })

    })
}
module.exports = analyse;

新增module.js

每个文件都是一个模块,模块和module实例是一一对应的关系。

let MagicString = require('magic-string');
const { parse } = require('acorn');
const analyse = require('./ast/analyse');

//判断一下obj对象上是否有prop属性
function hasOwnProperty(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}

/**
 * 每个文件都是一个模块,每个模块都会对应一个Module实例
 */
class Module {
  constructor({ code, path, bundle }) {
    this.code = new MagicString(code, { filename: path });
    this.path = path;//模块的路径
    this.bundle = bundle;//属于哪个bundle的实例
    this.ast = parse(code, {//把源代码转成抽象语法树
      ecmaVersion: 7,
      sourceType: 'module'
    });
    this.analyse();
  }

  analyse() {
    this.imports = {};//存放着当前模块所有的导入
    this.exports = {};//存放着当前模块所有的导出
    this.ast.body.forEach(node => {
      if (node.type === 'ImportDeclaration') {//说明这是一个导入语句
        let source = node.source.value;//./msg 从哪个模块进行的导入
        let specifiers = node.specifiers;
        specifiers.forEach(specifier => {
          const name = specifier.imported.name;//name
          const localName = specifier.local.name;//name
          //本地的哪个变量,是从哪个模块的的哪个变量导出的
          //this.imports.age = {name:'age',localName:"age",source:'./msg'};
          this.imports[localName] = { name, localName, source }
        });
        //}else if(/^Export/.test(node.type)){
      } else if (node.type === 'ExportNamedDeclaration') {
        let declaration = node.declaration;//VariableDeclaration
        if (declaration.type === 'VariableDeclaration') {
          let name = declaration.declarations[0].id.name;//age
          //记录一下当前模块的导出 这个age通过哪个表达式创建的
          //this.exports['age']={node,localName:age,expression}
          this.exports[name] = {
            node, localName: name, expression: declaration
          }
        }
      }
    });
    analyse(this.ast, this.code, this);//找到了_defines 和 _dependsOn
    this.definitions = {};//存放着所有的全局变量的定义语句
    this.ast.body.forEach(statement => {
      Object.keys(statement._defines).forEach(name => {
        //key是全局变量名,值是定义这个全局变量的语句
        this.definitions[name] = statement;
      });
    });

  }

  //展开这个模块里的语句,把些语句中定义的变量的语句都放到结果里
  expandAllStatements() {
    let allStatements = [];
    this.ast.body.forEach(statement => {
      if (statement.type === 'ImportDeclaration') {return}
      let statements = this.expandStatement(statement);
      allStatements.push(...statements);
    });
    return allStatements;
  }

  //展开一个节点
  //找到当前节点依赖的变量,它访问的变量,找到这些变量的声明语句。
  //这些语句可能是在当前模块声明的,也也可能是在导入的模块的声明的
  expandStatement(statement) {
    let result = [];
    const dependencies = Object.keys(statement._dependsOn);//外部依赖 [name]
    dependencies.forEach(name => {
      //找到定义这个变量的声明节点,这个节点可以有在当前模块内,也可能在依赖的模块里
      let definition = this.define(name);
      result.push(...definition);
    });
    if (!statement._included) {
      statement._included = true;//表示这个节点已经确定被纳入结果 里了,以后就不需要重复添加了
      result.push(statement);
    }
    return result;
  }

  define(name) {
    //查找一下导入变量里有没有name
    if (hasOwnProperty(this.imports, name)) {
      //this.imports.age = {name:'age',localName:"age",source:'./msg'};
      const importData = this.imports[name];
      //获取msg模块 exports imports msg模块
      const module = this.bundle.fetchModule(importData.source, this.path);
      //this.exports['age']={node,localName:age,expression}
      const exportData = module.exports[importData.name];
      //调用msg模块的define方法,参数是msg模块的本地变量名age,目的是为了返回定义age变量的语句
      return module.define(exportData.localName);
    } else {
      //definitions是对象,key当前模块的变量名,值是定义这个变量的语句
      let statement = this.definitions[name];
      if (statement && !statement._included) {
        return this.expandStatement(statement);
      } else {
        return [];
      }
    }
  }
}

module.exports = Module;

新增bundle.js

const fs = require('fs');
const MagicString = require('magic-string');
const Module = require('./module');
const path = require('path')
class Bundle {
  constructor(options) {
    //入口文件的绝对路径,包括后缀
    this.entryPath = options.entry.replace(/\.js$/, '') + '.js';
    this.modules = {};//存放着所有模块 入口文件和它依赖的模块
  }

  build(outputFileName) {
    //从入口文件的绝对路径出发找到它的模块定义
    let entryModule = this.fetchModule(this.entryPath);
    //把这个入口模块所有的语句进行展开,返回所有的语句组成的数组
    this.statements = entryModule.expandAllStatements();
    const { code } = this.generate();
    fs.writeFileSync(outputFileName, code, 'utf8');//写文件,第一个是文件名,第二个是内容
  }

  //获取模块信息
  /**
   * 
   * @param {*} importee //入口文件的绝对路径
   * @param {*} importer 由哪个入口导入的
   * @returns 
   */
  fetchModule(importee,importer) {
    let route;
    if(!importer){
      route = importee
    } else {
      if(path.isAbsolute(importee)){//如果是绝对路径
          route = importee
      }else if(importee[0]=='.'){//如果是相对路径
          route = path.resolve(path.dirname(importer),importee.replace(/\.js$/,'')+'.js')
      }
    }
    if (route) {
      //从硬盘上读出此模块的源代码
      let code = fs.readFileSync(route, 'utf8');
      let module = new Module({
        code,//模块的源代码
        path: route,//模块的绝对路径
        bundle: this//属于哪个Bundle
      });
      return module;
    }
  }

  //把this.statements生成代码
  generate() {
    let magicString = new MagicString.Bundle();
    this.statements.forEach(statement => {
      const source = statement._source;
      if(statement.type === 'ExportNamedDeclaration'){
        source.remove(statement.start,statement.declaration.start)//将单词export去掉
      }
      magicString.addSource({
        content: source,
        separator: '\n'//分割符
      });
    });
    return { code: magicString.toString() };
  }
}

module.exports = Bundle;

新增rollup.js

let Bundle = require('./bundle');
function rollup(entry,outputFileName){
    //Bundle就代表打包对象,里面会包含所有的模块信息
    const bundle = new Bundle({entry});
    //调用build方法开始进行编译
    bundle.build(outputFileName);
}
module.exports = rollup;

新增debugger.js

const path = require('path');
const rollup = require('./lib/rollup');
//入口文件的绝对路径
let entry = path.resolve(__dirname,'src/main.js');
let output = path.resolve(__dirname,'src/dist/bundle.js')
rollup(entry,output);

rollup流程解析

运行debugger.js->entry入口文件-》rollup.js调用rollup方法传入入口地址和出口文件名-》--》bundle.js根据入口entry创建bundle实例-》rollup.js调用bundle.build方法,传入出口地址--》bundle.js build方法调用fetchModule方法,传入入口文件地址entryPath--》bundle.js fetchModule方法根据入口文件读取code源码,并创建module对象实例--》module.js 调用构造器方法生成ast语法树,并调用analyse方法--》analyse.js方法 给每个statement加上源代码_source属性--》返回bundle.js调用expandAllStatements方法--》module.js expandAllStatements方法获取到所有的statement--》bundle.js调用generate方法,把每行拼在一起。

执行debugger.js

node .\debugger.js

 

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

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

相关文章

Python模块fileinput操作文件和目录操作总结

前言 之前介绍Python的 pathlib 模块可以有效的路径及文件查找等方便操作&#xff0c;本篇介绍一个相对 readlines() 获取文件内容更高效的用法 fileinput模块 对一个或者多个文件的内容迭代遍历&#xff08;类似文件操作的readlines()&#xff09;,但是返回的是迭代对象&…

ffmpeg库编译安装及入门指南(Windows篇)- 2022年底钜献

最近项目需要&#xff0c;使用了 ffmpeg 做摄像头视频采集和串流。这几天有点时间&#xff0c;打算把相关的一些知识记录分享一下。 在撰写本文时&#xff0c;我又在另外一台电脑上把 ffmpeg 重新安装了一遍&#xff0c;所以绝对真实靠谱&#xff01;如果你觉得文章写得还不错…

MAC QT OpenGL 波浪特效

目录 一.MAC QT OpenGL 波浪特效效果演示 1.原始图片2.效果演示 二.MAC QT OpenGL 波浪特效源码下载三.其他平台波浪特效版本 1.IOS 演示效果2.Windows OpenGL ES 演示效果3.Windows OpenGL 演示效果 四.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >>…

安装微信开发者工具及创建小程序

大纲&#xff1a; 一、官网&#xff1a;微信公众平台微信公众平台&#xff0c;给个人、企业和组织提供业务服务与用户管理能力的全新服务平台。https://mp.weixin.qq.com/ 开发者工具下载页面https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html《开发者…

【教学类-22-02】20221210《八款字体的描字帖-4*4格整张-不用订书机》(大班主题《我是中国人-中国字》)

成品样式&#xff1a; 48号字&#xff08;适应2-3个名字的大小&#xff09; 44号字&#xff08;适应4个名字大小&#xff08;2-3个名字也可以用&#xff0c;字会稍微小&#xff09;&#xff09; 打印样式&#xff1a; 背景需求&#xff1a; 观摩中3班做“描花体字”的本子的情…

【前沿技术RPA】 一文了解 UiPath 状态机 State Machine

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;UiPath &#x1f980;专栏简介&#xff1a;UiPath在传统的RPA&#xff08;Robotic…

【流量回放探索】啄木鸟流量回放平台

啄木鸟流量回放平台 项目简介 本项目是基于RuoYi-Vue单应用版本流量回放引擎基于goreplay演示系统基于newbee-mall-api-go 如果你想从录制开始体验&#xff0c;需要搭建演示系统newbee-mall-api-go &#xff0c;同时需要将gor 部署在演示系统服务中&#xff0c;搭建kafka以便…

[附源码]Python计算机毕业设计SSM基于技术的高校学生勤工俭学管理系统的设计与开发(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

HBase

1 HBase存储结构 HMaster 1. 监控 RegionServer   2. 处理 RegionServer 故障转移   3. 处理元数据的变更   4. 处理 region 的分配或移除   5. 在空闲时间进行数据的负载均衡   6. 通过 Zookeeper 发布自己的位置给客户端 RegionServer 1. 负责存储 HBase 的实际数…

编写高质量代码 - 多线程和并发(2)

文章目录1. 使用线程异常处理器提升系统可靠性2. volatile不能保证数据同步3. 异步运算考虑使用Callable接口1. 使用线程异常处理器提升系统可靠性 我们要编写一个Socket应用&#xff0c;监听指定端口&#xff0c;实现数据包的接收和发送逻辑&#xff0c;这在早期系统间进行数据…

分布式计算MapReduce究竟是怎么一回事?

前言 如果要对文件中的内容进行统计&#xff0c;大家觉得怎么做呢&#xff1f;一般的思路都是将不同地方的文件数据读取到内存中&#xff0c;最后集中进行统计。如果数据量少还好&#xff0c;但是面对海量数据、大数据的场景这样真的合适吗&#xff1f;不合适的话&#xff0c;…

1560_AURIX_TC275_NMI Trap以及PMC

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 1. 连同上一次的笔记中最后一页&#xff0c;看得出来NMI的trap软件触发至少是有三种方法。 2. 后半页给出了trap发生的状态的清除操作寄存器。 前面的文字描述部分&#xff0c;几个trap是…

基于jsp+mysql+ssm公共交通失信人员管理系统-计算机毕业设计

项目介绍 本南昌公共交通失信人员管理系统主要包括系统用户管理模块、用户信息管理模块、处罚类型管理、失信人员管理、登录模块、和退出模块等多个模块, 本系统基于SSM(SpringSpringMVCMyBatis)框架,适用于毕业设计&#xff0c;采用javaweb,基于B/S模式,Myeclipse或者eclipse…

手把手教你---猿如意之八大高效利器使用

开篇之前我们可能要来了解一下&#xff0c;《猿如意》是CSDN为提高开发者工作效率&#xff0c;发布客户端和低代码平台产品——《猿如意》&#xff1b;记得第一次在接触猿如意实在今年八月份&#xff0c;之前使用过其他的工具&#xff0c;但是有利有弊&#xff0c;先说下为啥选…

趋势分析 | 零信任实践之关键技术解读

SmartX 趋势分享 SmartX 趋势分享由 SmartX 团队内部分享的权威机构市场报告、全球重要媒体文章精选整理而成。内容涉及现代数据中心相关产业趋势以及金融、医疗、制造等行业全球用户需求与实践前沿洞察。本期&#xff0c;我们分享一篇 Gartner 关于零信任实践策略的文章[1]&am…

[附源码]JAVA毕业设计校园快递联盟系统(系统+LW)

[附源码]JAVA毕业设计校园快递联盟系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

【大数据入门核心技术-Azkaban】(二)Azkaban核心架构

目录 一、核心架构 1、Relational Database(Mysql) 2、Azkaban Web Server 3、Azkaban Executor Server 二、三种运行模式 1、solo server mode 2、two server mode 3、multiple executor mode 一、核心架构 Azkaban架构由三部分构成&#xff1a; 1、Relational Databa…

【chatGPT免注册】openAI机器人直接访问,不需要注册的方法

最近&#xff0c;chat GPT可谓是火出圈了。但是这个服务在国内不可用&#xff0c;网上你能找到的教程无非是注册一个虚拟的手机号去接受短信&#xff0c;可就算你能注册成功&#xff0c;还是无法访问。 还有人说可以去某宝买一个账号&#xff0c;这是可以的&#xff0c;账号的确…

安卓玩机搞机技巧综合资源-----干掉手机广告 禁用 冻结 关闭内置软件【八】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红米…

Java 并发编程(三)之synchronized

带着BAT大厂的面试问题去理解Synchronized 请带着这些问题继续后文&#xff0c;会很大程度上帮助你更好的理解synchronized。 Synchronized可以作用在哪里? 分别通过对象锁和类锁进行举例。Synchronized本质上是通过什么保证线程安全的? 分三个方面回答&#xff1a;加锁和释放…