前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[数据库分区]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...面我们强行将它变成了数据属性描述符 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么 Object.defineProperty 是无能为力的 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象 Ps: 原来的对象是 数据属性描述符,通过 Object.defineProperty 变成了 访问属性描述符 2. Proxy基本使用 在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的: 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象) 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作 将上面的案例用 Proxy 来实现一次: 首先,我们需要 new Proxy 对象,并且传入需要侦听的对象以及一个处理对象,可以称之为 handler; const p = new Proxy(target, handler) 其次,我们之后的操作都是直接对 Proxy 的操作,而不是原有的对象,因为我们需要在 handler 里面进行侦听 const obj = {name: 'why',age: 18}const objProxy = new Proxy(obj, {// 获取值时的捕获器get: function (target, key) {console.log(监听到obj对象的${key}属性被访问了)return target[key]},// 设置值时的捕获器set: function (target, key, newValue) {console.log(监听到obj对象的${key}属性被设置值)target[key] = newValue} })console.log(objProxy.name)console.log(objProxy.age)objProxy.name = 'kobe'objProxy.age = 30console.log(obj.name)console.log(obj.age)/ 监听到obj对象的name属性被访问了why监听到obj对象的age属性被访问了18监听到obj对象的name属性被设置值监听到obj对象的age属性被设置值kobe30/ 2.1 Proxy 的 set 和 get 捕获器 如果我们想要侦听某些具体的操作,那么就可以在 handler 中添加对应的捕捉器(Trap) set 和 get 分别对应的是函数类型 set 函数有四个参数: target:目标对象(侦听的对象) property:将被设置的属性 key value:新属性值 receiver:调用的代理对象 get 函数有三个参数 target:目标对象(侦听的对象) property:被获取的属性 key receiver:调用的代理对象 2.2 Proxy 所有捕获器 (13个) handler.getPrototypeOf() Object.getPrototypeOf 方法的捕捉器 handler.setPrototypeOf() Object.setPrototypeOf 方法的捕捉器 handler.isExtensible() Object.isExtensible 方法的捕捉器 handler.preventExtensions() Object.preventExtensions 方法的捕捉器 handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor 方法的捕捉器 handler.defineProperty() Object.defineProperty 方法的捕捉器 handler.ownKeys() Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器 handler.has() in 操作符的捕捉器 handler.get() 属性读取操作的捕捉器 handler.set() 属性设置操作的捕捉器 handler.deleteProperty() delete 操作符的捕捉器 handler.apply() 函数调用操作的捕捉器 handler.construct() new 操作符的捕捉器 const obj = {name: 'why',age: 18}const objProxy = new Proxy(obj, {// 获取值时的捕获器get: function (target, key) {console.log(监听到obj对象的${key}属性被访问了)return target[key]},// 设置值时的捕获器set: function (target, key, newValue) {console.log(监听到obj对象的${key}属性被设置值)target[key] = newValue},// 监听 in 的捕获器has: function (target, key) {console.log(监听到obj对象的${key}属性的in操作)return key in target},// 监听 delete 的捕获器deleteProperty: function (target, key) {console.log(监听到obj对象的${key}属性的delete操作)delete target[key]} })// in 操作符console.log('name' in objProxy)// delete 操作delete objProxy.name/ 监听到obj对象的name属性的in操作true监听到obj对象的name属性的delete操作/ 2.3 Proxy 的 construct 和 apply 到捕捉器中还有 construct 和 apply,它们是应用于函数对象的 function foo() {console.log('调用了 foo')}const fooProxy = new Proxy(foo, {apply: function (target, thisArg, argArray) {console.log(对 foo 函数进行了 apply 调用)target.apply(thisArg, argArray)},construct: function (target, argArray, newTarget) {console.log(对 foo 函数进行了 new 调用)return new target(...argArray)} })fooProxy.apply({}, ['abc', 'cba'])new fooProxy('abc', 'cba')/ 对 foo 函数进行了 apply 调用调用了 foo对 foo 函数进行了 new 调用调用了 foo/ 3. Reflect 3.1 Reflect 的作用 Reflect 也是 ES6 新增的一个 API,它是一个对象,字面的意思是反射 Reflect 的作用: 它主要提供了很多操作 JavaScript 对象的方法,有点像 Object 中操作对象的方法 比如 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf() 比如 Reflect.defineProperty(target, propertyKey, attributes) 类似于 Object.defineProperty() 如果我们有 Object 可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢? 这是因为在早期的 ECMA 规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些 API 放到了 Object上面 但是 Object 作为一个构造函数,这些操作实际上放到它身上并不合适 另外还包含一些类似于 in、delete 操作符,让 JS 看起来是会有一些奇怪的 所以在 ES6 中新增了 Reflect,让我们这些操作都集中到了 Reflect 对象上 那么 Object 和 Reflect 对象之间的 API 关系,可以参考 MDN 文档: 比较 Reflect 和 Object 方法 3.2 Reflect 的常见方法 Reflect中有哪些常见的方法呢?它和Proxy是一一对应的,也是13个 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf() Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回 true Reflect.isExtensible(target) 类似于 Object.isExtensible() Reflect.preventExtensions(target) 类似于 Object.preventExtensions() , 返回一个 Boolean Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于 Object.getOwnPropertyDescriptor() , 如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined Reflect.defineProperty(target, propertyKey, attributes) 和 Object.defineProperty() 类似, 如果设置成功就会返回 true Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组 (类似于 Object.keys(), 但不会受 enumerable 影响) Reflect.has(target, propertyKey) 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同 Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值,类似于 target[name] Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数,返回一个 Boolean,如果更新成功,则返回 true Reflect.deleteProperty(target, propertyKey) 作为函数的 delete 操作符,相当于执行 delete target[name] Reflect.apply(target, thisArgument, argumentsList) 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似 Reflect.construct(target, argumentsList[, newTarget]) 对构造函数进行 new 操作,相当于执行 new target(...args) 3.3 Reflect 的使用 那么我们可以将之前Proxy案例中对原对象的操作,都修改为Reflect来操作 const obj = {name: 'why',age: 18}const objProxy = new Proxy(obj, {get: function (target, key) {console.log(监听到obj对象的${key}属性被访问了)return Reflect.get(target, key)// return target[key] // 对原来对象进行了直接操作},set: function (target, key, newValue) {console.log(监听到obj对象的${key}属性被设置值)Reflect.set(target, key, newValue)// target[key] = newValue // 对原来对象进行了直接操作} })objProxy.name = 'kobe'console.log(objProxy.name)/ 监听到obj对象的name属性被设置值监听到obj对象的name属性被访问了kobe/ 3.4 Receiver的作用 我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢? 如果我们的源对象(obj)有 setter 、getter 的访问器属性,那么可以通过 receiver 来改变里面的 this const obj = {_name: 'why',get name() {return this._name // 不使用receiver, _name属性的操作不会被objProxy代理,因为this指向obj},set name(newValue) {this._name = newValue} }const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// receiver 是创建出来的代理对象console.log('get 方法被访问-------', key, receiver)console.log(objProxy === receiver) // truereturn Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)} })objProxy.name = 'kobe'console.log(objProxy.name) // kobe/ get 方法被访问------- name { _name: 'kobe', name: [Getter/Setter] }trueget 方法被访问------- _name { _name: 'kobe', name: [Getter/Setter] }truekobe/ 3.5 Reflect 的 construct function Student(name, age) {this.name = namethis.age = age}function Teacher() {}const stu = new Student('why', 18)console.log(stu)console.log(stu.__proto__ === Student.prototype)/ Student { name: 'why', age: 18 }true/// 执行 Student 函数中的内容,但是创建出来的对象是 Teacher 对象const teacher = Reflect.construct(Student, ['why', 18], Teacher)console.log(teacher)console.log(teacher.__proto__ === Teacher.prototype)/ Teacher { name: 'why', age: 18 }true/ 4. 响应式 4.1 什么是响应式? 先来看一下响应式意味着什么?我们来看一段代码: m 有一个初始化的值,有一段代码使用了这个值; 那么在 m 有一个新的值时,这段代码可以自动重新执行 let m = 0// 一段代码console.log(m)console.log(m 2)console.log(m 2)m = 200 上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的 对象的响应式 4.2 响应式函数设计 首先,执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中: 那么问题就变成了,当数据发生变化时,自动去执行某一个函数; 但是有一个问题:在开发中是有很多的函数的,如何区分一个函数需要响应式,还是不需要响应式呢? 很明显,下面的函数中 foo 需要在 obj 的 name 发生变化时,重新执行,做出相应; bar 函数是一个完全独立于 obj 的函数,它不需要执行任何响应式的操作; // 对象的响应式const obj = {name: 'why',age: 18}function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)}function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')}obj.name = 'kobe' // name 发生改变时候 foo 函数执行 响应式函数的实现 watchFn 如何区分响应式函数? 这个时候我们封装一个新的函数 watchFn 凡是传入到 watchFn 的函数,就是需要响应式的 其他默认定义的函数都是不需要响应式的 / 封装一个响应式的函数 /let reactiveFns = []function watchFn(fn) {reactiveFns.push(fn)}// 对象的响应式const obj = {name: 'why',age: 18}watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)})watchFn(function demo() {console.log(obj.name, 'demo function ---------')})function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')}obj.name = 'kobe' // name 发生改变时候 foo 函数执行reactiveFns.forEach((fn) => {fn()}) 4.3 响应式依赖的收集 目前收集的依赖是放到一个数组中来保存的,但是这里会存在数据管理的问题: 在实际开发中需要监听很多对象的响应式 这些对象需要监听的不只是一个属性,它们很多属性的变化,都会有对应的响应式函数 不可能在全局维护一大堆的数组来保存这些响应函数 所以要设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数: 相当于替代了原来的简单 reactiveFns 的数组; class Depend {constructor() {this.reactiveFns = []}addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }const depend = new Depend()function watchFn(fn) {depend.addDepend(fn)}// 对象的响应式const obj = {name: 'why', // depend 对象age: 18 // depend 对象}watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)})watchFn(function demo() {console.log(obj.name, 'demo function ---------')})function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')}obj.name = 'kobe'depend.notify() 4.4 监听对象的变化 那么接下来就可以通过之前的方式来监听对象的变化: 方式一:通过 Object.defineProperty 的方式(vue2采用的方式); 方式二:通过 new Proxy 的方式(vue3采用的方式); 我们这里先以Proxy的方式来监听 class Depend {constructor() {this.reactiveFns = []}addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }const depend = new Depend()function watchFn(fn) {depend.addDepend(fn)}// 对象的响应式const obj = {name: 'why', // depend 对象age: 18 // depend 对象}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)depend.notify()} })watchFn(function foo() {const newName = objProxy.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(objProxy.name)})watchFn(function demo() {console.log(objProxy.name, 'demo function ---------')})objProxy.name = 'kobe'objProxy.name = 'james'/ 你好啊,李银河Hello Worldkobekobe demo function ---------你好啊,李银河Hello Worldjamesjames demo function ---------/ 4.5 对象的依赖管理 目前是创建了一个 Depend 对象,用来管理对于 name 变化需要监听的响应函数: 但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理; 如何可以使用一种数据结构来管理不同对象的不同依赖关系呢? 在前面我们刚刚学习过 WeakMap,并且在学习 WeakMap 的时候我讲到了后面通过 WeakMap 如何管理这种响应式的数据依赖: 实现 可以写一个 getDepend 函数专门来管理这种依赖关系 / 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取mapconst map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象const depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} }) 正确的依赖收集 我们之前收集依赖的地方是在 watchFn 中: 但是这种收集依赖的方式我们根本不知道是哪一个 key 的哪一个 depend 需要收集依赖; 只能针对一个单独的 depend 对象来添加你的依赖对象; 那么正确的应该是在哪里收集呢?应该在我们调用了 Proxy 的 get 捕获器时 因为如果一个函数中使用了某个对象的 key,那么它应该被收集依赖 / 封装一个响应式函数 /let activeReactviceFn = nullfunction watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数activeReactviceFn && depend.addDepend(activeReactviceFn)return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} }) 4.6 对 Depend 重构 两个问题: 问题一:如果函数中有用到两次 key,比如 name,那么这个函数会被收集两次 问题二:我们并不希望将添加 reactiveFn 放到 get 中,因为它是属于 Depend 的行为 所以我们需要对 Depend 类进行重构: 解决问题一的方法:不使用数组,而是使用 Set 解决问题二的方法:添加一个新的方法,用于收集依赖 // 保存当前需要收集的响应式函数let activeReactviceFn = nullclass Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)} }addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }// 对象的响应式const obj = {name: 'why', // depend 对象age: 18 // depend 对象}/ 封装一个响应式函数 /function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)const objProxy = new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} })watchFn(function () {console.log(objProxy.name, '--------------')console.log(objProxy.name, '++++++++++++++')})objProxy.name = 'kobe'/ why --------------why ++++++++++++++kobe --------------kobe ++++++++++++++/ 4.7 创建响应式对象 目前的响应式是针对于obj一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象 / 保存当前需要收集的响应式函数 /let activeReactviceFn = null/ 依赖收集类 /class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)} }addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }/ 封装一个响应式函数 /function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}/ 创建响应式对象函数 /function reactive(obj) {// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)return new Proxy(obj, {get: function (target, key, receiver) {// 根据 target key 获取对应的 depnedconst depend = getDepend(target, key)// 给 depend 对象中添加响应式函数depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()} })}const info = reactive({address: '广州市',height: 1.88})watchFn(() => {console.log(info.address, '---')})info.address = '北京市' 4.8 Vue2 响应式原理 前面所实现的响应式的代码,其实就是 Vue3 中的响应式原理: Vue3 主要是通过 Proxy 来监听数据的变化以及收集相关的依赖的 Vue2 中通过 Object.defineProerty的方式来实现对象属性的监听 可以将 reactive 函数进行如下的重构: 在传入对象时,我们可以遍历所有的 key,并且通过属性存储描述符来监听属性的获取和修改 在 setter 和 getter 方法中的逻辑和前面的 Proxy 是一致的 / 保存当前需要收集的响应式函数 /let activeReactviceFn = null/ 依赖收集类 /class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)} }addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})} }/ 封装一个响应式函数 /function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null}/ 封装一个获取depend的函数 /const taregtMap = new WeakMap()function getDepend(target, key) {// 根据target对象获取maplet map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}// 根据key获取depend对象let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend}/ 创建响应式对象函数 /function reactive(obj) {Object.keys(obj).forEach((key) => {let value = obj[key]Object.defineProperty(obj, key, {get: function () {const dep = getDepend(obj, key)dep.depend()return value},set: function (newValue) {value = newValueconst dep = getDepend(obj, key)dep.notify()} })})return obj}const info = reactive({address: '广州市',height: 1.88})watchFn(() => {console.log(info.address, '---')})info.address = '北京市' 本篇文章为转载内容。原文链接:https://blog.csdn.net/wanghuan1020/article/details/126774033。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-11 12:37:47
679
转载
转载文章
...灵活的分块策略确保了数据安全性和完整性,使开发者能够轻松应对大规模数据迁移或备份的需求。 同时,在前端性能优化方面,Webpack 5等现代构建工具引入了更精细的模块分割功能,结合HTTP/2服务器推送技术,可以在一定程度上改善大资源如视频、音频等文件的加载体验,间接影响着用户上传大文件时的整体流畅度。 总之,无论是前端脚本库的不断迭代更新,还是云服务提供商对大文件上传功能的深度优化,都表明在这个数据爆炸的时代,高效稳定地上传大容量文件已成为互联网基础设施建设的重要一环,值得广大开发者持续关注并深入研究。
2023-12-19 09:43:46
127
转载
Ruby
...说啊,你正在倒腾一堆数据的时候,完全可以把它切成一小块一小块的,然后让每个线程去负责一块,这样一来,效率直接拉满,干活儿的速度蹭蹭往上涨! 但是,问题来了:并发编程虽然强大,但它并不是万能药。哎呀,经常会有这样的情况呢——自个儿辛辛苦苦改代码,还以为是在让程序变得更好,结果一不小心,又给它整出了新麻烦,真是“好心办坏事”的典型啊!接下来,我们来看几个具体的例子。 --- 3. 示例一 共享状态的混乱 场景描述: 假设你正在开发一个电商网站,需要统计用户的购买记录。你琢磨着干脆让多线程上阵,给这个任务提速,于是打算让每个线程各管一拨用户的活儿,分头行动效率肯定更高!看起来很合理对不对? 问题出现: 问题是,当你让多个线程共享同一个变量(比如一个全局计数器),事情就开始变得不可控了。Ruby 的线程可不是完全分开的,这就有点像几个人共用一个记事本,大家都能随便写东西上去。结果就是,这本子可能一会儿被这个写点,一会儿被那个划掉,最后你都不知道上面到底写了啥,数据就乱套了。 代码示例: ruby 错误的代码 counter = 0 threads = [] 5.times do |i| threads << Thread.new do 100_000.times { counter += 1 } end end threads.each(&:join) puts "Counter: {counter}" 分析: 这段代码看起来没什么问题,每个线程都只是简单地增加计数器。但实际情况却是,输出的结果经常不是期望的500_000,而是各种奇怪的数字。这就好比说,counter += 1 其实不是一步到位的简单操作,它得先“读一下当前的值”,再“给这个值加1”,最后再“把新的值存回去”。问题是,在这中间的每一个小动作,都可能被别的线程突然插队过来捣乱! 解决方案: 为了避免这种混乱,我们需要使用线程安全的操作,比如Mutex(互斥锁)。Mutex可以确保每次只有一个线程能够修改某个变量。 修正后的代码: ruby 正确的代码 require 'thread' counter = 0 mutex = Mutex.new threads = [] 5.times do |i| threads << Thread.new do 100_000.times do mutex.synchronize { counter += 1 } end end end threads.each(&:join) puts "Counter: {counter}" 总结: 这一段代码告诉我们,共享状态是一个雷区。如果你非要用共享变量,记得给它加上锁,不然后果不堪设想。 --- 4. 示例二 死锁的诅咒 场景描述: 有时候,我们会遇到更复杂的情况,比如两个线程互相等待对方释放资源。哎呀,这种情况就叫“死锁”,简直就像两只小猫抢一个玩具,谁都不肯让步,结果大家都卡在那里动弹不得,程序也就这样傻乎乎地停在原地,啥也干不了啦! 问题出现: 想象一下,你有两个线程,A线程需要获取锁X,B线程需要获取锁Y。想象一下,A和B两个人都想打开两把锁——A拿到了锁X,B拿到了锁Y。然后呢,A心想:“我得等B先把他的锁Y打开,我才能继续。”而B也在想:“等A先把她的锁X打开,我才能接着弄。”结果俩人就这么干等着,谁也不肯先放手,最后就成了“死锁”——就像两个人在拔河,谁都不松手,僵在那里啥也干不成。 代码示例: ruby 死锁的代码 lock_a = Mutex.new lock_b = Mutex.new thread_a = Thread.new do lock_a.synchronize do puts "Thread A acquired lock A" sleep(1) lock_b.synchronize do puts "Thread A acquired lock B" end end end thread_b = Thread.new do lock_b.synchronize do puts "Thread B acquired lock B" sleep(1) lock_a.synchronize do puts "Thread B acquired lock A" end end end thread_a.join thread_b.join 分析: 在这段代码中,两个线程都在尝试获取两个不同的锁,但由于它们的顺序不同,最终导致了死锁。运行这段代码时,你会发现程序卡住了,没有任何输出。 解决方案: 为了避免死锁,我们需要遵循“总是按照相同的顺序获取锁”的原则。比如,在上面的例子中,我们可以强制让所有线程都先获取锁A,再获取锁B。 修正后的代码: ruby 避免死锁的代码 lock_a = Mutex.new lock_b = Mutex.new thread_a = Thread.new do [lock_a, lock_b].each do |lock| lock.synchronize do puts "Thread A acquired lock {lock.object_id}" end end end thread_b = Thread.new do [lock_a, lock_b].each do |lock| lock.synchronize do puts "Thread B acquired lock {lock.object_id}" end end end thread_a.join thread_b.join 总结: 死锁就像一只隐形的手,随时可能掐住你的喉咙。记住,保持一致的锁顺序是关键! --- 5. 示例三 不恰当的线程池 场景描述: 线程池是一种管理线程的方式,它可以复用线程,减少频繁创建和销毁线程的开销。但在实际使用中,很多人会因为配置不当而导致性能下降甚至崩溃。 问题出现: 假设你创建了一个线程池,但线程池的大小设置得不合理。哎呀,这就好比做饭时锅不够大,菜都堆在那儿煮不熟,菜要是放太多呢,锅又会冒烟、潽得到处都是,最后饭也没做好。线程池也一样,太小了任务堆成山,程序半天没反应;太大了吧,电脑资源直接被榨干,啥事也干不成,还得收拾烂摊子! 代码示例: ruby 线程池的错误用法 require 'thread' pool = Concurrent::FixedThreadPool.new(2) 20.times do |i| pool.post do sleep(1) puts "Task {i} completed" end end pool.shutdown pool.wait_for_termination 分析: 在这个例子中,线程池的大小被设置为2,但有20个任务需要执行。哎呀,这就好比你请了个帮手,但他一次只能干两件事,其他事儿就得排队等着,得等前面那两件事儿干完了,才能轮到下一件呢!这种情况下,整个程序的执行时间会显著延长。 解决方案: 为了优化线程池的性能,我们需要根据系统的负载情况动态调整线程池的大小。可以使用Concurrent::CachedThreadPool,它会根据当前的任务数量自动调整线程的数量。 修正后的代码: ruby 使用缓存线程池 require 'concurrent' pool = Concurrent::CachedThreadPool.new 20.times do |i| pool.post do sleep(1) puts "Task {i} completed" end end sleep(10) 给线程池足够的时间完成任务 pool.shutdown pool.wait_for_termination 总结: 线程池就像一把双刃剑,用得好可以提升效率,用不好则会成为负担。记住,线程池的大小要根据实际情况灵活调整。 --- 6. 示例四 忽略异常的代价 场景描述: 并发编程的一个常见问题是,线程中的异常不容易被察觉。如果你没有妥善处理这些异常,程序可能会因为一个小错误而崩溃。 问题出现: 假设你有一个线程在执行某个操作时抛出了异常,但你没有捕获它,那么整个线程池可能会因此停止工作。 代码示例: ruby 忽略异常的代码 threads = [] 5.times do |i| threads << Thread.new do raise "Error in thread {i}" if i == 2 puts "Thread {i} completed" end end threads.each(&:join) 分析: 在这个例子中,当i == 2时,线程会抛出一个异常。哎呀糟糕!因为我们没抓住这个异常,程序直接就挂掉了,别的线程啥的也别想再跑了。 解决方案: 为了防止这种情况发生,我们应该在每个线程中添加异常捕获机制。比如,可以用begin-rescue-end结构来捕获异常并进行处理。 修正后的代码: ruby 捕获异常的代码 threads = [] 5.times do |i| threads << Thread.new do begin raise "Error in thread {i}" if i == 2 puts "Thread {i} completed" rescue => e puts "Thread {i} encountered an error: {e.message}" end end end threads.each(&:join) 总结: 异常就像隐藏在暗处的敌人,稍不注意就会让你措手不及。学会捕获和处理异常,是成为一个优秀的并发编程者的关键。 --- 7. 结语 好了,今天的分享就到这里啦!并发编程确实是一项强大的技能,但也需要谨慎对待。大家看看今天这个例子,是不是觉得有点隐患啊?希望能引起大家的注意,也学着怎么避开这些坑,别踩雷了! 最后,我想说的是,编程是一门艺术,也是一场冒险。每次遇到新挑战,我都觉得像打开一个神秘的盲盒,既兴奋又紧张。不过呢,光有好奇心还不够,还得有点儿耐心,就像种花一样,得一点点浇水施肥,不能急着看结果。相信只要我们不断学习、不断反思,就一定能写出更加优雅、高效的代码! 祝大家编码愉快!
2025-04-25 16:14:17
32
凌波微步
转载文章
...被加载执行的用户进程数据段所对应的全局描述符号给显示到桌面上,上面代码执行后情况如下: 我们看到,在控制台中执行hlt命令后,内核加载了用户进程,同时在控制台下方输出了一个字符串,也就是0x1E,这个数值对应的就是当前运行用户进程其数据段对应的全局描述符号。一旦有这个信息之后,另一个进程就可以有机可乘了。 接着我们在本地目录创建一个新文件叫crack.c,其内容如下: void main() {char p = (char)0x123;p[0] = 'c';p[1] = 'r';p[2] = 'a';p[3] = 'c';p[4] = 'k';p[5] = 0;} 它的目的简单,就是针对内存地址0x123处写入字符串”crack”.接着我们修改一下makefile,使得内核编译时,能把crack.c编译成二进制文件: CFLAGS=-fno-stack-protectorckernel : ckernel_u.asm app_u.asm crack_u.asm cp ckernel_u.asm win_sheet.h win_sheet.c mem_util.h mem_util.c write_vga_desktop.c timer.c timer.h global_define.h global_define.c multi_task.c multi_task.h app_u.asm app.c crack_u.asm crack.c makefile '/media/psf/Home/Documents/操作系统/文档/19/OS-kernel-win-sheet/'ckernel_u.asm : ckernel.o....crack_u.asm : crack.o./objconv -fnasm crack.o crack_u.asmcrack.o : crack.cgcc -m32 -fno-stack-protector -fno-asynchronous-unwind-tables -s -c -o crack.o crack.c 然后我们在本地目录下,把api_call.asm拷贝一份,并命名为crack_call.asm,后者内容与前者完全相同,只不过稍微有那么一点点改变,例如: BITS 32mov AX, 30 8mov DS, axcall mainmov edx, 4 ;返回内核int 02Dh.... 这里需要注意,语句: mov AX, 30 8mov DS, ax 其中30对应的就是前面显示的0x1E,这两句汇编的作用是,把程序crack的数据段设置成下标为30的全局描述符所指向的内存段一致。这就意味着crack进程所使用的数据段就跟hlt启动的进程所使用的数据段一致了!于是在crack.c中,它对内存地址为0x123的地方写入字符串”crack”,那就意味着对hlt加载用户进程的内存空间写入对应字符串! 完成上面代码后,我们在java项目中,增加代码,一是用来编译crack进程,而是把crack代码写入虚拟磁盘。在OperatingSystem.java中,将代码做如下添加: public void makeFllopy() {writeFileToFloppy("kernel.bat", false, 1, 1);....header = new FileHeader();header.setFileName("crack");header.setFileExt("exe");file = new File("crack.bat");in = null;try {in = new FileInputStream(file);long len = file.length();int count = 0;while (count < file.length()) {bbuf[count] = (byte) in.read();count++;}in.close();}catch(IOException e) {e.printStackTrace();return;}header.setFileContent(bbuf);fileSys.addHeader(header);....}public static void main(String[] args) {CKernelAsmPrecessor kernelPrecessor = new CKernelAsmPrecessor();kernelPrecessor.process();kernelPrecessor.createKernelBinary();CKernelAsmPrecessor appPrecessor = new CKernelAsmPrecessor("hlt.bat", "app_u.asm", "app.asm", "api_call.asm");appPrecessor.process();appPrecessor.createKernelBinary();CKernelAsmPrecessor crackPrecessor = new CKernelAsmPrecessor("crack.bat", "crack_u.asm", "crack.asm", "crack_call.asm");crackPrecessor.process();crackPrecessor.createKernelBinary();OperatingSystem op = new OperatingSystem("boot.bat");op.makeFllopy();} 在main函数中,我们把crack.c及其附属汇编文件结合在一起,编译成二进制文件crack.bat,在makeFllopy中,我们把编译后的crack.bat二进制数据读入,并把它写入到虚拟磁盘中,当系统运行起来后,可以把crack.bat二进制内容作为进程加载执行。 完成上面代码后,回到内核的C语言部分,也就是write_vga_desktop.c做一些修改,在kernel_api函数中,修改如下: int kernel_api(int edi, int esi, int ebp, int esp,int ebx, int edx, int ecx, int eax) {....else if (edx == 14) {sheet_free(shtctl, (struct SHEET)ebx);//change herecons_putstr((char)(task->pTaskBuffer->pDataSeg + 0x123));}....}void console_task(struct SHEET sheet, int memtotal) {....for(;;) {....else if (i == KEY_RETURN) {....else if (strcmp(cmdline, "crack") == 1) {cmd_execute_program("crack.exe");}....}....} 在kernel_api中,if(edx == 14)对应的api调用是api_closewin,也就是当用户进程关闭窗口时,我们把进程数据偏移0x123处的数据当做字符串打印到控制台窗口上,在console_task控制台进程主函数中,我们增加了对命令crack的响应,当用户在控制台上输入命令”crack”时,将crack代码加载到内核中运行。上面代码完成后,编译内核,然后用虚拟机将内核加载,系统启动后,我们现在一个控制台中输入hlt,先启动用户进程。然后点击”shift + w”,启动另一个控制台窗口,在其中输入crack,运行crack程序: 接着把点击tab键,把焦点恢复到窗口task_a,然后用鼠标点击运行hlt命令的窗口,把输入焦点切换到该控制台,然后再次点击tab键,把执行权限提交给运行hlt命令的控制台,此时点击回车,介绍用户进程启动的窗口,结果情况如下: 此时我们可以看到,运行hlt命令,执行用户进程的控制台窗口居然输出了字符串”crack”,而这个字符串正是crack.c在执行时,写入地址0x123的字符串。这就意味着一个恶意进程成功修改了另一个进程的内存数据,也相当于一个流氓程序把一只咸猪手伸到其他用户进程的裙底,蹂躏一番后留下了猥琐的证据。 那么如何防范恶意进程对其他程序的非法入侵呢,这就得使用CPU提供的LDT机制,也就是局部描述符表,该机制的使用,我们将在下一节详细讲解。更详细的讲解和代码演示调试,请参看视频: 更详细的讲解和代码调试演示过程,请参看视频 Linux kernel Hacker, 从零构建自己的内核 更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号: 本篇文章为转载内容。原文链接:https://blog.csdn.net/tyler_download/article/details/78731905。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-14 19:08:07
254
转载
转载文章
...back:接收IMU数据,将IMU数据存到imu_msg_buffer中,这里只会利用开头200帧IMU数据进行静止初始化,不做其他处理。featureCallback:接收双目特征,进行后端处理。利用IMU进行EKF Propagation,利用双目特征进行EKF Update。静止初始化(initializeGravityAndBias):将前200帧加速度和角速度求平均, 平均加速度的模值g作为重力加速度, 平均角速度作为陀螺仪的bias, 计算重力向量(0,0,-g)和平均加速度之间的夹角(旋转四元数), 标定初始时刻IMU系与world系之间的夹角. 因此MSCKF要求前200帧IMU是静止不动的 sudo apt-get install libsuitesparse-devcd ~/catkin_ws/srcgit clone KumarRobotics/msckf_viocd ..catkin_make --pkg msckf_vio --cmake-args -DCMAKE_BUILD_TYPE=Release激活环境变量很关键source /devel/setup.bashroslaunch msckf_vio msckf_vio_euroc.launch注意文件路径rosrun rviz rviz -d rviz/rviz_euroc_config.rviz (改成你自己的rviz文件)rosbag play ~/data/euroc/MH_04_difficult.bag(改成你自己的rosbag文件) 可以看到,s_msckf的输出是没有轨迹的,可以增加如下脚本,将/odom存为/path,在rviz订阅即可可视化轨迹 脚本来自其issue:https://github.com/KumarRobotics/msckf_vio/issues/13 !/usr/bin/env pythonimport rospyfrom nav_msgs.msg import Odometry, Pathfrom geometry_msgs.msg import PoseStampedclass OdomToPath:def __init__(self):self.path_pub = rospy.Publisher('/slz_path', Path, latch=True, queue_size=10)self.odom_sub = rospy.Subscriber('/firefly_sbx/vio/odom', Odometry, self.odom_cb, queue_size=10)self.path = Path()def odom_cb(self, msg):cur_pose = PoseStamped()cur_pose.header = msg.headercur_pose.pose = msg.pose.poseself.path.header = msg.headerself.path.poses.append(cur_pose)self.path_pub.publish(self.path)if __name__ == '__main__':rospy.init_node('odom_to_path')odom_to_path = OdomToPath()rospy.spin() 或者增加一个draw_path的功能包: cpp为: include <stdio.h>include <stdlib.h>include <unistd.h>include <ros/ros.h>include <ros/console.h>include <nav_msgs/Path.h>include <std_msgs/String.h>include <nav_msgs/Odometry.h>include <geometry_msgs/Quaternion.h>include <geometry_msgs/PoseStamped.h>nav_msgs::Path path;ros::Publisher path_pub;ros::Subscriber odomSub;ros::Subscriber odom_raw_Sub;void odomCallback(const nav_msgs::Odometry::ConstPtr& odom){geometry_msgs::PoseStamped this_pose_stamped;this_pose_stamped.header= odom->header;this_pose_stamped.pose = odom->pose.pose;//this_pose_stamped.pose.position.x = odom->pose.pose.position.x;//this_pose_stamped.pose.position.y = odom->pose.pose.position.y;//this_pose_stamped.pose.orientation = odom->pose.pose.orientation;//this_pose_stamped.header.stamp = ros::Time::now();//this_pose_stamped.header.frame_id = "world";//frame_id 是消息中与数据相关联的参考系id,例如在在激光数据中,frame_id对应激光数据采集的参考系 path.header= this_pose_stamped.header;path.poses.push_back(this_pose_stamped);//path.header.stamp = ros::Time::now();//path.header.frame_id= "world";path_pub.publish(path);//printf("path_pub ");//printf("odom %.3lf %.3lf\n",odom->pose.pose.position.x,odom->pose.pose.position.y);}int main (int argc, char argv){ros::init (argc, argv, "showpath");ros::NodeHandle ph;path_pub = ph.advertise<nav_msgs::Path>("/trajectory",10, true);odomSub = ph.subscribe<nav_msgs::Odometry>("/firefly_sbx/vio/odom", 10, odomCallback);//ros::Rate loop_rate(50);while (ros::ok()){ros::spinOnce(); // check for incoming messages//loop_rate.sleep();}return 0;} cmakelists.txt cmake_minimum_required(VERSION 2.8.3)project(draw) Compile as C++11, supported in ROS Kinetic and newer add_compile_options(-std=c++11) Find catkin macros and libraries if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) is used, also find other catkin packagesfind_package(catkin REQUIRED COMPONENTSgeometry_msgsroscpprospystd_msgsmessage_generation)catkin_package( INCLUDE_DIRS include LIBRARIES learning_communicationCATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime DEPENDS system_lib) Build include_directories(include${catkin_INCLUDE_DIRS})add_executable(draw_path draw.cpp)target_link_libraries(draw_path ${catkin_LIBRARIES}) package.xml <?xml version="1.0"?><package><name>draw</name><version>0.0.0</version><description>The learning_communication package</description><!-- One maintainer tag required, multiple allowed, one person per tag --><!-- Example: --><!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> --><maintainer email="hcx@todo.todo">hcx</maintainer><!-- One license tag required, multiple allowed, one license per tag --><!-- Commonly used license strings: --><!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 --><license>TODO</license><!-- Url tags are optional, but multiple are allowed, one per tag --><!-- Optional attribute type can be: website, bugtracker, or repository --><!-- Example: --><!-- <url type="website">http://wiki.ros.org/learning_communication</url> --><!-- Author tags are optional, multiple are allowed, one per tag --><!-- Authors do not have to be maintainers, but could be --><!-- Example: --><!-- <author email="jane.doe@example.com">Jane Doe</author> --><!-- The _depend tags are used to specify dependencies --><!-- Dependencies can be catkin packages or system dependencies --><!-- Examples: --><!-- Use build_depend for packages you need at compile time: --><!-- <build_depend>message_generation</build_depend> --><!-- Use buildtool_depend for build tool packages: --><!-- <buildtool_depend>catkin</buildtool_depend> --><!-- Use run_depend for packages you need at runtime: --><!-- <run_depend>message_runtime</run_depend> --><!-- Use test_depend for packages you need only for testing: --><!-- <test_depend>gtest</test_depend> --><buildtool_depend>catkin</buildtool_depend><build_depend>geometry_msgs</build_depend><build_depend>roscpp</build_depend><build_depend>rospy</build_depend><build_depend>std_msgs</build_depend><run_depend>geometry_msgs</run_depend><run_depend>roscpp</run_depend><run_depend>rospy</run_depend><run_depend>std_msgs</run_depend><build_depend>message_generation</build_depend><run_depend>message_runtime</run_depend><!-- The export tag contains other, unspecified, tags --><export><!-- Other tools can request additional information be placed here --></export></package> vins_fusion: 双目vio等多系统 mkdir -p vins-catkin_ws/srccd vins-catkin_ws/srcgit clone https://github.com/HKUST-Aerial-Robotics/VINS-Fusion.gitcd ..catkin_makesource devel/setup.bash按照readme 3.1 Monocualr camera + IMUroslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_mono_imu_config.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_mono_imu_config.yaml rosbag play YOUR_DATASET_FOLDER/MH_01_easy.bag 3.2 Stereo cameras + IMUroslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_imu_config.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_imu_config.yaml rosbag play YOUR_DATASET_FOLDER/MH_01_easy.bag 3.3 Stereo camerasroslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_config.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_config.yaml rosbag play YOUR_DATASET_FOLDER/MH_01_easy.bag<img src="https://github.com/HKUST-Aerial-Robotics/VINS-Fusion/blob/master/support_files/image/euroc.gif" width = 430 height = 240 /> 4. KITTI Example 4.1 KITTI Odometry (Stereo)Download [KITTI Odometry dataset](http://www.cvlibs.net/datasets/kitti/eval_odometry.php) to YOUR_DATASET_FOLDER. Take sequences 00 for example,Open two terminals, run vins and rviz respectively. (We evaluated odometry on KITTI benchmark without loop closure funtion)roslaunch vins vins_rviz.launch(optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/kitti_odom/kitti_config00-02.yamlrosrun vins kitti_odom_test ~/catkin_ws/src/VINS-Fusion/config/kitti_odom/kitti_config00-02.yaml YOUR_DATASET_FOLDER/sequences/00/ 4.2 KITTI GPS Fusion (Stereo + GPS)Download [KITTI raw dataset](http://www.cvlibs.net/datasets/kitti/raw_data.php) to YOUR_DATASET_FOLDER. Take [2011_10_03_drive_0027_synced](https://s3.eu-central-1.amazonaws.com/avg-kitti/raw_data/2011_10_03_drive_0027/2011_10_03_drive_0027_sync.zip) for example.Open three terminals, run vins, global fusion and rviz respectively. Green path is VIO odometry; blue path is odometry under GPS global fusion.roslaunch vins vins_rviz.launchrosrun vins kitti_gps_test ~/catkin_ws/src/VINS-Fusion/config/kitti_raw/kitti_10_03_config.yaml YOUR_DATASET_FOLDER/2011_10_03_drive_0027_sync/ rosrun global_fusion global_fusion_node<img src="https://github.com/HKUST-Aerial-Robotics/VINS-Fusion/blob/master/support_files/image/kitti.gif" width = 430 height = 240 /> 5. VINS-Fusion on car demonstrationDownload [car bag](https://drive.google.com/open?id=10t9H1u8pMGDOI6Q2w2uezEq5Ib-Z8tLz) to YOUR_DATASET_FOLDER.Open four terminals, run vins odometry, visual loop closure(optional), rviz and play the bag file respectively. Green path is VIO odometry; red path is odometry under visual loop closure.roslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/vi_car/vi_car.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/vi_car/vi_car.yaml rosbag play YOUR_DATASET_FOLDER/car.bag 本篇文章为转载内容。原文链接:https://blog.csdn.net/slzlincent/article/details/104364909。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-13 20:38:56
310
转载
转载文章
...;a>狂神说大数据</a></li><li><a>狂神聊理财</a></li></ul></div></div></div></div></div><el-button @click="download" id="download">下载</el-button><!-- <el-button @click="concurrenceDownload" >并发下载测试</el-button>--><el-button @click="stop">停止</el-button><el-button @click="start">开始</el-button>{ {fileFinalOffset} }{ {contentList} }<el-progress type="circle" :percentage="percentage"></el-progress></div><!--前端使用Vue,实现前后端分离--><script th:src="@{/js/axios.min.js}"></script><script th:src="@{/js/vue.min.js}"></script><!-- 引入样式 --><link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"><!-- 引入组件库 --><script src="https://unpkg.com/element-ui/lib/index.js"></script><script>new Vue({ el: 'app',data: {keyword: '', //搜索关键字results: [] ,//搜索结果percentage: 0, // 下载进度filesCurrentPage:0,//文件开始偏移量fileFinalOffset:0, //文件最后偏移量stopRecursiveTags:true, //停止递归标签,默认是true 继续进行递归contentList: [], // 文件流数组breakpointResumeTags:false, //断点续传标签,默认是false 不进行断点续传temp:[],fileMap:new Map(),timer:null, //定时器名称},methods: {//根据关键字搜索商品信息searchKey(){var keyword=this.keyword;axios.get('/search/JD/search/'+keyword+"/1/10").then(res=>{this.results=res.data;//绑定数据console.log(this.results)console.table(this.results)})},//停止下载stop(){//改变递归标签为falsethis.stopRecursiveTags=false;},//开始下载start(){//重置递归标签为true 最后进行合并this.stopRecursiveTags=true;//重置断点续传标签this.breakpointResumeTags=true;//重新调用下载方法this.download();},// 分段下载需要后端配合download() {// 下载地址const url = "/down?fileName="+this.keyword.trim()+"&drive=E";console.log(url)const chunkSize = 1024 1024 50; // 单个分段大小,这里测试用100Mlet filesTotalSize = chunkSize; // 安装包总大小,默认100Mlet filesPages = 1; // 总共分几段下载//计算百分比之前先清空上次的if(this.percentage==100){this.percentage=0;}let sentAxios = (num) => {let rande = chunkSize;//判断是否开启了断点续传(断点续传没法并行-需要上次请求的结果作为参数)if (this.breakpointResumeTags){rande = ${Number(this.fileFinalOffset)+1}-${num chunkSize + 1};}else {if (num) {rande = ${(num - 1) chunkSize + 2}-${num chunkSize + 1};} else {// 第一次0-1方便获取总数,计算下载进度,每段下载字节范围区间rande = "0-1";} }let headers = {range: rande,};axios({method: "get",url: url.trim(),async: true,data: {},headers: headers,responseType: "blob"}).then((response) => {if (response.status == 200 || response.status == 206) {//检查了下才发现,后端对文件流做了一层封装,所以将content指向response.data即可const content = response.data;//截取文件总长度和最后偏移量let result= response.headers["content-range"].split("/");// 获取文件总大小,方便计算下载百分比filesTotalSize =result[1];//获取最后一片文件位置,用于断点续传this.fileFinalOffset=result[0].split("-")[1]// 计算总共页数,向上取整filesPages = Math.ceil(filesTotalSize / chunkSize);// 文件流数组this.contentList.push(content);// 递归获取文件数据(判断是否要继续递归)if (this.filesCurrentPage < filesPages&&this.stopRecursiveTags==true) {this.filesCurrentPage++;//计算下载百分比 当前下载的片数/总片数this.percentage=Number((this.contentList.length/filesPages)100).toFixed(2);sentAxios(this.filesCurrentPage);//结束递归return;}//递归标签为true 才进行下载if (this.stopRecursiveTags){// 文件名称const fileName =decodeURIComponent(response.headers["fname"]);//构造一个blob对象来处理数据const blob = new Blob(this.contentList);//对于<a>标签,只有 Firefox 和 Chrome(内核) 支持 download 属性//IE10以上支持blob但是依然不支持downloadif ("download" in document.createElement("a")) {//支持a标签download的浏览器const link = document.createElement("a"); //创建a标签link.download = fileName; //a标签添加属性link.style.display = "none";link.href = URL.createObjectURL(blob);document.body.appendChild(link);link.click(); //执行下载URL.revokeObjectURL(link.href); //释放urldocument.body.removeChild(link); //释放标签} else {//其他浏览器navigator.msSaveBlob(blob, fileName);} }} else {//调用暂停方法,记录当前下载位置console.log("下载失败")} }).catch(function (error) {console.log(error);});};// 第一次获取数据方便获取总数sentAxios(this.filesCurrentPage);this.$message({message: '文件开始下载!',type: 'success'});} }})</script></body></html> 本篇文章为转载内容。原文链接:https://blog.csdn.net/kangshihang1998/article/details/129407214。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-19 08:12:45
546
转载
转载文章
...,只向目标发出SYN数据包,如果收到SYN/ACK响应包就认为目标端口正在监听,并立即断开连接;否则认为目标端口并未开放。-sT:TCP连接扫描,这是完整的TCP扫描方式(默认扫描类型),用来建立一个TCP连接,如果成功则认为目标端口正在监听服务,否则认为目标端口并未开放。-sF:TCP的FIN扫描,开放的端口会忽略这种数据包,关闭的端口会回应RST数据包。许多防火墙只对SYN数据包进行简单过滤,而忽略了其他形式的TCP attack 包。这种类型的扫描可间接检测防火墙的健壮性。-sU:UDP扫描,探测目标主机提供哪些UDP服务,UDP扫描的速度会比较慢。-sP:ICMP扫描,类似于ping检测,快速判断目标主机是否存活,不做其他扫描。-P0:跳过ping检测,这种方式认为所有的目标主机是存活的,当对方不响应ICMP请求时,使用这种方式可以避免因无法 ping通而放弃扫描。 总结: 1.账号基本安全措施:系统账号处理、密码安全控制、命令历史清理、自动注销 2.用户切换与提权(su、sudo) 3.开关机安全控制(BIOS引导设置、禁止Ctrl+Alt+Del快捷键、GRUB菜单设置密码) 4.终端控制 5.弱口令检测——John the Ripper 6.端口扫描——namp 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_67474417/article/details/123982900。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-07 23:37:44
95
转载
转载文章
...一、建模背景及目的及数据源说明 二、描述性分析 2.1 连续自变量与连续因变量的相关性分析 2.2 二分类变量与连续变量的相关性分析 2.3 多分类变量与连续变量的相关性分析 三、模型建立与诊断 3.1 一元线形回归及模型解读 3.2 残差可视化分析 3.3 多元线性回归 一、建模背景及目的及数据源说明 本案例数据来源于常国珍等人的《Python数据科学》一书第7章中的信用卡公司客户申请信息(年龄、收入、地区等信息)以及已有开卡客户的申请信息和信用卡消费信息数据,案例希望通过对该数据的分析和建模,根据已有的开卡用户的用户信息和消费来线形回归模型,来预测未开卡用户的消费潜力。数据下载见如下链https://download.csdn.net/download/baidu_26137595/85101874 数据读入及示例: raw = pd.read_csv('./data/creditcard_exp.csv', skipinitialspace = True)raw.head() 数据字段及说明: Acc: 是否开卡, 为0说明未开卡,对应的 avg_exp 为NaN;为1说明已开卡,对应avg_exp有值 avg_exp: 月均信用卡支出 avg_exp_ln:月均信用卡支出的对熟 gender : 性别 Ownrent: 是否自有住房 Selfempl: 是否自谋职业 Income:收入 dist_home_val: 所住小区均价 w dist_avg_income: 当地人均收入 age2: 年龄的平方 high_avg: 高出当地平均收入 edu_class:教育等级,0、1、2、3 依次是小学、初中、高中、大学 二、描述性分析 首先可筛选Acc为1的数据,分别以avg_exp为因变量,其余变量为自变量进行数据探索,主要是发现自变量和因变量是否有线形关系。 raw_1 = raw[raw['Acc'] == 1] 2.1 连续自变量与连续因变量的相关性分析 首先对连续变量和目标变量进行相关性分析,因变量avg_exp为连续变量,一般可以用相关系数来看其线形相关性。 cons_vasr = ['avg_exp', 'avg_exp_ln', 'Age', 'Income', 'dist_home_val', 'dist_avg_income', 'age2', 'high_avg']raw_1[cons_vasr].corr()vg']].corr() 结果如下,可以看到收入 Income 和当地人均收入 dist_avg_income这两个变量和avg_exp月均信用卡支出有较强的相关性,同时观察自变量间的相关性可发现人均收入 Income 和当地人均收入 dist_avg_income 之间也有较强的相关性,相关系数为0.99,说明接下来我们可以把这两个变量加入模型,但要注意可能会存在多重共线性。 2.2 二分类变量与连续变量的相关性分析 分类变量和连续变量之间的相关性可以用t检验进行,接下来以是否自有住房 Ownrent 变量 和 月均收入之间进行相关性检验。首先查看Ownrent 不同取值的数量以及avg_exp均值分布情况如何: pd.pivot_table(raw_1, values = ['avg_exp'], index = ['Ownrent'], aggfunc = {'avg_exp': ['count', np.mean]}) 接着分别对 Ownrent 为0、1的 avg_exp 进行t检验: import scipy.stats as st 引入scipy.stats进行t检验 创建变量Ownrent_0 = raw_1[raw_1['Ownrent'] == 0]['avg_exp'].valuesOwnrent_1 = raw_1[raw_1['Ownrent'] == 1]['avg_exp'].valuesst.ttest_ind(Ownrent_0, Ownrent_1, equal_var = True) p值为0.01 < 0.05,可以拒绝原假设,即认为是否自有住房和月均信用卡支出是相关的。 2.3 多分类变量与连续变量的相关性分析 多分类变量和连续变量之间的相关性检验可以用多次t检验进行,但较为繁琐,用方差分析进行快速检验相关性,然后再运用多重检验查看具体是哪些处理之间存在差异。以教育水平edu_class为例进行分析,同理首先查看分布 raw_1.pivot_table(index = 'edu_class', values = ['avg_exp'], aggfunc={'avg_exp': ['count', np.mean]}) 可以看到不同教育水平之间消费水平有明显差异,接下来通过方差分析进行检验差异是否明显。 from statsmodels.stats.anova import anova_lm 引入anova_lm进行方差分析from ststsmodels.stats.formula import ols 引入ols进行线性回归建模lm = ols('avg_exp~C(edu_class)', data = raw_1).fit() C(edu_class) 将数值型的变量指定为分类型anova_lm(lm, typ = 2) 可以看到不同教育水平之间的月均消费支出之间的差异是显著的,继续用多重检验来看哪些处理之间是显著的。 from statsmodels.stats.multicomp import MultiComparison 引入MultiComparison进行tukey多重检验mc = MultiComparison(raw_1['avg_exp'],raw_1['edu_class'])tukey_result = mc.tukeyhsd(alpha = 0.5)print(tukey_result) 结果是每个处理之间因变量差异的显著性,最后一列reject都为True说明各组之间均存在显著差异。 三、模型建立与诊断 3.1 一元线性回归及模型解读 以Income为自变量,以avg_exp为因变量建立一元线形回归并对模型结果进行解释 lm_1 = ols('avg_exp ~ Income', data = raw_1).fit()print(lm_1.summary()) 首先从第一部分可以看到R^2为0.454,整个模型的F检验p值小于0.05,说明模型通过显著性检验。 其次模型结果的第二块也表明自变量和截距也通过显著性检验。 最后一部分主要是对残差进行检验,左侧Omnibus、Prob(Omnibus)主要是对偏度Skew和峰度Kurtosis进行检验,正态分布的偏度为0,峰度为3,模型的Prob(Omnibus)值为0.156大于0.05,说明不能拒绝残差符合正态分布。 右侧Durbin-Watson主要是对残差的自相关性进行检(改检验可表示为,为残差之间的相关系数),Durbin-Watson的取值范围是0-4,越接近2说明残差不存在自相关性,越接近0说明存在正相关,越接近4说明存在负相关性。 右侧Jarque-Bera (JB)、Prob(JB)是对残差正态性检验,可以用来判断残差是否符合正态分布,本案例中Prob(JB)值为0.173 > 0.05,基不能拒绝残差服从正态分布。 右侧Cond. No.是多重共线性检验,该值越大,共线性越严重。 整体上看模型虽然拟合效果没那么好,但是显著性通过了检验。接下来看一下模型具体的系数,Income的系数为97.7说明模型收入越高信用卡消费越高,是符合业务预期的。 3.2 残差可视化分析 接下来对残差进一步进行可视化分析,主要看残差是否满足以下几个假定,并尝试通过对自变量、因变量进行调整来优化模型。首先来回顾一下残差需要满足的几个假定: a.残差的要服从均值为0,方差为的正态分布; b.残差之间要相互独立 c.残差和自变量没有相关性 (1)通过残差图进行模型优化 模型avg_exp ~ Income的自变量与残差分布图、残差qq图、模型拟合情况图即自变量与因变量及其预测值的图像 lm_1 = ols('avg_exp ~ Income', data = raw_1).fit() 建模raw_1['resid_1'] = lm_1.resid 模型残差raw_1['resid_1_rank'] = raw_1['resid_1'].rank(ascending = False, pct = True) 计算残差的百分位数raw_1['pred_1'] = lm_1.predict() 添加预测值plt.figure(figsize = (20, 6)) 自变量与残差分布图ax1 = plt.subplot(131)ax1.scatter('Income', 'resid', data = raw_1)ax1.set_title('Income & resid') 残差的qq图ax2 = plt.subplot(132)stats.probplot(raw_1['resid_1_rank'], dist = 'norm', plot = ax2) 模型拟合情况图,自变量与因变量以及模型预测值ax3 = plt.subplot(133)ax3.scatter('Income', 'avg_exp', data = raw_1)ax3.plot('Income', 'pred_1', data = raw_1, color = 'red')ax3.legend()ax3.text(12, 1920, 'pred func R^2: %.2f'% lm_1.rsquared)ax3.set_title('Income & avg_exp') 从第一个自变量和残差散点图可以看出,残差基本符合对称分布,但随着自变量增大,残差也在变大,存在方差不齐的情况。第二个图残差的qq图可以看出,残差近似正态分布。第三个图可以看模型的拟合效果并不是很好,R^2只有0.45。对avg_exp取对数,能够改善预测值越大残差越大的情况,但由于只对因变量取对数导致模型不好解释,对自变量Income同时取对数,代码和以上类似,只是改变因变量和自变量形式而已,以下是残差图,可以看到残差的异方差现象被有效的抑制,并且R^2也得到了提高。 (2)通过残差图发现强影响点 仔细观察以上图像结果,左下侧有两个较为异常的数据,对模型的拟和效果有较大的影响, 对于这种影响较大的可将其进行删除并重新建模: 计算学生化残差raw_1['resid_t'] = (raw_1['resid_2'] - raw_1['resid_2'].mean())/raw_1['resid_2'].std() raw_1[abs(raw_1['resid_t']) > 2] 将残差大于2的筛选出来 将强影响点删除后,得到的结果如下,模型结果更稳定。 3.3 多元线性回归 上一篇文章有说到多重共线性会对模型产生致命的影响,用方差膨胀因子来处理的话会非常繁琐。通过正则化处理如Lasso回归,能够产生某些严格等于0的系数,从而达到变量筛选的目的。接下来以Lasso为例,首先用LassoCV来找到最优的alpha。由于statsmodels中的ols的fit_regularized方法没有很好的实现,所以用sklearn中linear_model模块来进行建模 from sklearn.preprocessing import StandardScaler sklearn进行线性回归前必须要进行标准化from sklearn.linear_model import LassoCV Lasso的交叉验证方法con_xcols = ['Age', 'Income', 'dist_home_val', 'dist_avg_income']scaler = StandardScaler()X = scaler.fit_transform(raw_1[con_xcols])y = raw_1['avg_exp_ln']lasso_alphas = np.logspace(-3, 0, 100, base = 10)lcv = LassoCV(alphas = lasso_alphas, cv = 10)lcv.fit(X, y)print('best alpha %.4f' % lcv.alpha_)print('the r-square %.4f' % lcv.score(X, y)) 接下来画出不同alpha下的岭迹图,来看alpha值对系数的影响 from sklearn.linear_model import Lassocoefs = []lasso = Lasso()for i in lasso_alphas:lasso.set_params(alpha = i)lasso.fit(X, y)coefs.append(lasso.coef_)ax = plt.gca()ax.plot(lasso_alphas, coefs)ax.set_xscale('log')ax.set_xlabel('$\\alpha$')ax.set_ylabel('coefs value') 从图中可以看到随着alpha的增大,系数不断在减小,有些系数会优先收缩为0,再继续增大时所欲系数都会为0,通过该特性从而达到变量筛选的目的。将LassoCV得到的系数打印出来,可以看到用户月均信用卡支出和当地小区均价、当地人均收入成正比,当地人均收入水平的影响更大。 以上就是线形回归在应用时的注意事项。 本篇文章为转载内容。原文链接:https://blog.csdn.net/baidu_26137595/article/details/123766191。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 15:52:56
106
转载
转载文章
...定 JDBC 事务的数据源、全局作业和/或触发器侦听器、插件、线程池,以及更多)配置 Quartz,但它根本没有与应用程序服务器的上下文或引用集成在一起。结果就是作业不能访问 Web 服务器的内部函数;例如,在使用 WebSphere 应用服务器时,由 Quartz 调度的作业并不能影响服务器的动态缓存和数据源。 二、java中实现定时任务分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少,这篇文章将不做详细介绍。 使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,稍后会详细介绍。 Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多,稍后会介绍。 从作业类的继承方式来讲,可以分为两类: 作业类需要继承自特定的作业类基类,如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;java.util.Timer中需要继承自java.util.TimerTask。 作业类即普通的java类,不需要继承自任何基类。 注:个人推荐使用第二种方式,因为这样所以的类都是普通类,不需要事先区别对待。 从任务调度的触发时机来分,这里主要是针对作业使用的触发器,主要有以下两种: 每隔指定时间则触发一次,在Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean 每到指定时间则触发一次,在Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean 注:并非每种任务都可以使用这两种触发器,如java.util.TimerTask任务就只能使用第一种。Quartz和spring task都可以支持这两种触发条件。 三、Quartz与Spring的集成 第一种,作业类继承自特定的基类:org.springframework.scheduling.quartz.QuartzJobBean。 第一步:定义作业类 Java代码 import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class Job1 extends QuartzJobBean { private int timeout; private static int i = 0; //调度工厂实例化后,经过timeout时间开始执行调度 public void setTimeout(int timeout) { this.timeout = timeout; } / 要调度的具体任务 / @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("定时任务执行中…"); } } 第二步:spring配置文件中配置作业类JobDetailBean Xml代码 <bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass" value="com.gy.Job1" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="0" /> </map> </property> </bean> 说明:org.springframework.scheduling.quartz.JobDetailBean有两个属性,jobClass属性即我们在java代码中定义的任务类,jobDataAsMap属性即该任务类中需要注入的属性值。 第三步:配置作业调度的触发方式(触发器) Quartz的作业触发器有两种,分别是 org.springframework.scheduling.quartz.SimpleTriggerBean org.springframework.scheduling.quartz.CronTriggerBean 第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。 配置方式如下: Xml代码 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job1" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。 配置方式如下: Xml代码 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job1" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 ?" /> </bean> 关于cronExpression表达式的语法参见附录。 第四步:配置调度工厂 Xml代码 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 说明:该参数指定的就是之前配置的触发器的名字。 第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。 第二种,作业类不继承特定基类。 Spring能够支持这种方式,归功于两个类: org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean 这两个类分别对应spring支持的两种实现任务调度的方式,即前文提到到java自带的timer task方式和Quartz方式。这里我只写MethodInvokingJobDetailFactoryBean的用法,使用该类的好处是,我们的任务类不再需要继承自任何类,而是普通的pojo。 第一步:编写任务类 Java代码 public class Job2 { public void doJob2() { System.out.println("不继承QuartzJobBean方式-调度进行中..."); } } 可以看出,这就是一个普通的类,并且有一个方法。 第二步:配置作业类 Xml代码 <bean id="job2" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"> <bean class="com.gy.Job2" /> </property> <property name="targetMethod" value="doJob2" /> <property name="concurrent" value="false" /><!-- 作业不并发调度 --> </bean> 说明:这一步是关键步骤,声明一个MethodInvokingJobDetailFactoryBean,有两个关键属性:targetObject指定任务类,targetMethod指定运行的方法。往下的步骤就与方法一相同了,为了完整,同样贴出。 第三步:配置作业调度的触发方式(触发器) Quartz的作业触发器有两种,分别是 org.springframework.scheduling.quartz.SimpleTriggerBean org.springframework.scheduling.quartz.CronTriggerBean 第一种SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。 配置方式如下: Xml代码 <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="job2" /> <property name="startDelay" value="0" /><!-- 调度工厂实例化后,经过0秒开始执行调度 --> <property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 --> </bean> 第二种CronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。 配置方式如下: Xml代码 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="job2" /> <!—每天12:00运行一次 --> <property name="cronExpression" value="0 0 12 ?" /> </bean> 以上两种调度方式根据实际情况,任选一种即可。 第四步:配置调度工厂 Xml代码 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> </bean> 说明:该参数指定的就是之前配置的触发器的名字。 第五步:启动你的应用即可,即将工程部署至tomcat或其他容器。 到此,spring中Quartz的基本配置就介绍完了,当然了,使用之前,要导入相应的spring的包与Quartz的包,这些就不消多说了。 其实可以看出Quartz的配置看上去还是挺复杂的,没有办法,因为Quartz其实是个重量级的工具,如果我们只是想简单的执行几个简单的定时任务,有没有更简单的工具,有! 四、Spring-Task 上节介绍了在Spring 中使用Quartz,本文介绍Spring3.0以后自主开发的定时任务工具,spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种 形式,下面将分别介绍这两种方式。 第一种:配置文件方式 第一步:编写作业类 即普通的pojo,如下: Java代码 import org.springframework.stereotype.Service; @Service public class TaskJob { public void job1() { System.out.println(“任务进行中。。。”); } } 第二步:在spring配置文件头中添加命名空间及描述 Xml代码 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:task="http://www.springframework.org/schema/task" 。。。。。。 xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> 第三步:spring配置文件中设置具体的任务 Xml代码 <task:scheduled-tasks> <task:scheduled ref="taskJob" method="job1" cron="0 ?"/> </task:scheduled-tasks> <context:component-scan base-package=" com.gy.mytask " /> 说明:ref参数指定的即任务类,method指定的即需要运行的方法,cron及cronExpression表达式,具体写法这里不介绍了,详情见上篇文章附录。 <context:component-scan base-package="com.gy.mytask" />这个配置不消多说了,spring扫描注解用的。 到这里配置就完成了,是不是很简单。 第二种:使用注解形式 也许我们不想每写一个任务类还要在xml文件中配置下,我们可以使用注解@Scheduled,我们看看源文件中该注解的定义: Java代码 @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scheduled { public abstract String cron(); public abstract long fixedDelay(); public abstract long fixedRate(); } 可以看出该注解有三个方法或者叫参数,分别表示的意思是: cron:指定cron表达式 fixedDelay:官方文档解释:An interval-based trigger where the interval is measured from the completion time of the previous task. The time unit value is measured in milliseconds.即表示从上一个任务完成开始到下一个任务开始的间隔,单位是毫秒。 fixedRate:官方文档解释:An interval-based trigger where the interval is measured from the start time of the previous task. The time unit value is measured in milliseconds.即从上一个任务开始到下一个任务开始的间隔,单位是毫秒。 下面我来配置一下。 第一步:编写pojo Java代码 import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component(“taskJob”) public class TaskJob { @Scheduled(cron = "0 0 3 ?") public void job1() { System.out.println(“任务进行中。。。”); } } 第二步:添加task相关的配置: Xml代码 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd" default-lazy-init="false"> <context:annotation-config /> <!—spring扫描注解的配置 --> <context:component-scan base-package="com.gy.mytask" /> <!—开启这个配置,spring才能识别@Scheduled注解 --> <task:annotation-driven scheduler="qbScheduler" mode="proxy"/> <task:scheduler id="qbScheduler" pool-size="10"/> 说明:理论上只需要加上<task:annotation-driven />这句配置就可以了,这些参数都不是必须的。 Ok配置完毕,当然spring task还有很多参数,我就不一一解释了,具体参考xsd文档http://www.springframework.org/schema/task/spring-task-3.0.xsd。 附录: cronExpression的配置说明,具体使用以及参数请百度google 字段 允许值 允许的特殊字符 秒 0-59 , - / 分 0-59 , - / 小时 0-23 , - / 日期 1-31 , - ? / L W C 月份 1-12 或者 JAN-DEC , - / 星期 1-7 或者 SUN-SAT , - ? / L C 年(可选) 留空, 1970-2099 , - / - 区间 通配符 ? 你不想设置那个字段 下面只例出几个式子 CRON表达式 含义 "0 0 12 ?" 每天中午十二点触发 "0 15 10 ? " 每天早上10:15触发 "0 15 10 ?" 每天早上10:15触发 "0 15 10 ? " 每天早上10:15触发 "0 15 10 ? 2005" 2005年的每天早上10:15触发 "0 14 ?" 每天从下午2点开始到2点59分每分钟一次触发 "0 0/5 14 ?" 每天从下午2点开始到2:55分结束每5分钟一次触发 "0 0/5 14,18 ?" 每天的下午2点至2:55和6点至6点55分两个时间段内每5分钟一次触发 "0 0-5 14 ?" 每天14:00至14:05每分钟一次触发 "0 10,44 14 ? 3 WED" 三月的每周三的14:10和14:44触发 "0 15 10 ? MON-FRI" 每个周一、周二、周三、周四、周五的10:15触发 Cron 表达式包括以下 7 个字段: 秒 分 小时 月内日期 月 周内日期 年(可选字段) 特殊字符 Cron 触发器利用一系列特殊字符,如下所示: 反斜线(/)字符表示增量值。例如,在秒字段中“5/15”代表从第 5 秒开始,每 15 秒一次。 问号(?)字符和字母 L 字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。字母 L 字符是 last 的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果“L”单独存在,就等于“7”,否则代表当月内周内日期的最后一个实例。所以“0L”表示安排在当月的最后一个星期日执行。 在月内日期字段中的字母(W)字符把执行安排在最靠近指定值的工作日。把“1W”放在月内日期字段中,表示把执行安排在当月的第一个工作日内。 井号()字符为给定月份指定具体的工作日实例。把“MON2”放在周内日期字段中,表示把任务安排在当月的第二个星期一。 星号()字符是通配字符,表示该字段可以接受任何可能的值。 字段 允许值 允许的特殊字符 秒 0-59 , - / 分 0-59 , - / 小时 0-23 , - / 日期 1-31 , - ? / L W C 月份 1-12 或者 JAN-DEC , - / 星期 1-7 或者 SUN-SAT , - ? / L C 年(可选) 留空, 1970-2099 , - / 表达式意义 "0 0 12 ?" 每天中午12点触发 "0 15 10 ? " 每天上午10:15触发 "0 15 10 ?" 每天上午10:15触发 "0 15 10 ? " 每天上午10:15触发 "0 15 10 ? 2005" 2005年的每天上午10:15触发 "0 14 ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 ?" 每月15日上午10:15触发 "0 15 10 L ?" 每月最后一日的上午10:15触发 "0 15 10 ? 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? 63" 每月的第三个星期五上午10:15触发 每天早上6点 0 6 每两个小时 0 /2 晚上11点到早上8点之间每两个小时,早上八点 0 23-7/2,8 每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点 0 11 4 1-3 1月1日早上4点 0 4 1 1 本篇文章为转载内容。原文链接:https://zhanghaiyang.blog.csdn.net/article/details/51397459。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-27 18:50:19
344
转载
转载文章
...);通常用在设置不变数据类型的子类。 C.__del__(self) 解构器 C.__str__(self) 可打印的字符输出;内建str()及print 语句 C.__repr__(self) 运行时的字符串输出;内建repr() 和操作符 C.__unicode__(self) Unicode 字符串输出;内建unicode() C.__call__(self, args) 表示可调用的实例 C.__nonzero__(self) 为object 定义False 值;内建bool() (从2.2 版开始) C.__len__(self) “长度”(可用于类);内建len() 对象(值)比较 C.__cmp__(self, obj) 对象比较;内建cmp() C.__lt__(self, obj) C.__le__(self, obj) 小于/小于或等于;对应 C.__gt__(self, obj) C.__ge__(self, obj) 大于/大于或等于;对应>及>=操作符 C.__eq__(self, obj) C.__ne__(self, obj) 等于/不等于;对应==,!=及<>操作符 属性 C.__getattr__(self, attr) 获取属性;内建getattr();仅当属性没有找到时调用 C.__setattr__(self, attr, val) 设置属性 C.__delattr__(self, attr) 删除属性 C.__getattribute__(self, attr) 获取属性;内建getattr();总是被调用 C.__get__(self, attr) (描述符)获取属性 C.__set__(self, attr, val) (描述符)设置属性 C.__delete__(self, attr) (描述符)删除属性 数值类型:二进制操作符 C.__add__(self, obj) 加;+操作符 C.__sub__(self, obj) 减;-操作符 C.__mul__(self, obj) 乘;操作符 C.__div__(self, obj) 除;/操作符 C.__truediv__(self, obj) True 除;/操作符 C.__floordiv__(self, obj) Floor 除;//操作符 C.__mod__(self, obj) 取模/取余;%操作符 C.__divmod__(self, obj) 除和取模;内建divmod() C.__pow__(self, obj[, mod]) 乘幂;内建pow();操作符 C.__lshift__(self, obj) 左移位;< 数值类型:二进制操作符 C.__rshift__(self, obj) 右移;>>操作符 C.__and__(self, obj) 按位与;&操作符 C.__or__(self, obj) 按位或;|操作符 C.__xor__(self, obj) 按位与或;^操作符 数值类型:一元操作符 C.__neg__(self) 一元负 C.__pos__(self) 一元正 C.__abs__(self) 绝对值;内建abs() C.__invert__(self) 按位求反;~操作符 数值类型:数值转换 C.__complex__(self, com) 转为complex(复数);内建complex() C.__int__(self) 转为int;内建int() C.__long__(self) 转 .long;内建long() C.__float__(self) 转为float;内建float() 数值类型:基本表示法(String) C.__oct__(self) 八进制表示;内建oct() C.__hex__(self) 十六进制表示;内建hex() 数值类型:数值压缩 C.__coerce__(self, num) 压缩成同样的数值类型;内建coerce() C.__index__(self) 在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等) 序列类型 C.__len__(self) 序列中项的数目 C.__getitem__(self, ind) 得到单个序列元素 C.__setitem__(self, ind,val) 设置单个序列元素 C.__delitem__(self, ind) 删除单个序列元素 C.__getslice__(self, ind1,ind2) 得到序列片断 C.__setslice__(self, i1, i2,val) 设置序列片断 C.__delslice__(self, ind1,ind2) 删除序列片断 C.__contains__(self, val) 测试序列成员;内建in 关键字 C.__add__(self,obj) 串连;+操作符 C.__mul__(self,obj) 重复;操作符 C.__iter__(self) 创建迭代类;内建iter() 映射类型 C.__len__(self) mapping 中的项的数目 C.__hash__(self) 散列(hash)函数值 C.__getitem__(self,key) 得到给定键(key)的值 C.__setitem__(self,key,val) 设置给定键(key)的值 C.__delitem__(self,key) 删除给定键(key)的值 C.__missing__(self,key) 给定键如果不存在字典中,则提供一个默认值 一:简单定制 classRoundFloatManual(object):def __init__(self, val):assert isinstance(val, float), "Value must be a float!"self.value= round(val, 2)>>> rfm =RoundFloatManual(42) Traceback (mostrecent call last): File"", line 1, in? File"roundFloat2.py", line 5, in __init__assertisinstance(val, float), \ AssertionError: Value must be a float!>>> rfm =RoundFloatManual(4.2)>>>rfm >>> printrfm 它因输入非法而异常,但如果输入正确时,就没有任何输出了。在解释器中,我们得到一些信息,却不是我们想要的。print(使用str())和真正的字符串对象表示(使用repr())都没能显示更多有关我们对象的信息。这就需要实现__str__()和__repr__()二者之一,或者两者都实现。加入下面的方法: def __str__(self):return str(self.value) 现在我们得到下面的: >>> rfm = RoundFloatManual(5.590464)>>>rfm >>> printrfm5.59 >>> rfm = RoundFloatManual(5.5964)>>> printrfm5.6 但是在解释器中转储(dump)对象时,仍然显示的是默认对象符号,要修复它,只需要覆盖__repr__()。可以让__repr__()和__str__()具有相同的代码,但最好的方案是:__repr__ = __str__ 在带参数5.5964的第二个例子中,我们看到它舍入值刚好为5.6,但我们还是想显示带两位小数的数。可以这样修改: def __str__(self):return '%.2f' % self.value 这里就同时具备str()和repr()的输出了: >>> rfm =RoundFloatManual(5.5964)>>>rfm5.60 >>>printrfm5.60 所有代码如下: classRoundFloatManual(object):def __init__(self,val):assert isinstance(val, float), "Valuemust be a float!"self.value= round(val, 2)def __str__(self):return '%.2f' %self.value__repr__ = __str__ 二:数值定制 定义一个Time60,其中,将整数的小时和分钟作为输入传给构造器: classTime60(object):def __init__(self, hr, min): self.hr=hr self.min= min 1:显示 需要在显示实例的时候,得到一个有意义的输出,那么就要覆盖__str__()(如果有必要的话,__repr__()也要覆盖): def __str__(self):return '%d:%d' % (self.hr, self.min) 比如: >>> mon =Time60(10, 30)>>> tue =Time60(11, 15)>>> >>> printmon, tue10:30 11:15 2:加法 Python中的重载操作符很简单。像加号(+),只需要重载__add__()方法,如果合适,还可以用__radd__()及__iadd__()。注意,实现__add__()的时候,必须认识到它返回另一个Time60对象,而不修改原mon或tue: def __add__(self, other):return self.__class__(self.hr + other.hr, self.min + other.min) 在类中,一般不直接调用类名,而是使用self 的__class__属性,即实例化self 的那个类,并调用它。调用self.__class__()与调用Time60()是一回事。但self.__class__()的方式更好。 >>> mon = Time60(10, 30)>>> tue = Time60(11, 15)>>> mon +tue >>> print mon +tue21:45 如果没有定义相对应的特殊方法,但是却使用了该方法对应的运算,则会引起一个TypeError异常: >>> mon -tue Traceback (mostrecent call last): File"", line 1, in? TypeError:unsupported operand type(s)for -: 'Time60' and 'Time60' 3:原位加法 __iadd__(),是用来支持像mon += tue 这样的操作符,并把正确的结果赋给mon。重载一个__i__()方法的唯一秘密是它必须返回self: def __iadd__(self, other): self.hr+=other.hr self.min+=other.minreturn self 下面是结果输出: >>> mon = Time60(10,30)>>> tue = Time60(11,15)>>>mon10:30 >>>id(mon)401872 >>> mon +=tue>>>id(mon)401872 >>>mon21:45 下面是Time60的类的完全定义: classTime60(object):'Time60 - track hours and minutes' def __init__(self,hr, min):'Time60 constructor - takes hours andminutes'self.hr=hr self.min=mindef __str__(self):'Time60 - string representation' return '%d:%d' %(self.hr, self.min)__repr__ = __str__ def __add__(self, other):'Time60 - overloading the additionoperator' return self.__class__(self.hr + other.hr,self.min +other.min)def __iadd__(self,other):'Time60 - overloading in-place addition'self.hr+=other.hr self.min+=other.minreturn self 4:升华 在这个类中,还有很多需要优化和改良的地方。首先看下面的例子: >>> wed =Time60(12, 5)>>>wed12:5 正确的显示应该是:“12:05” >>> thu =Time60(10, 30)>>> fri =Time60(8, 45)>>> thu +fri18:75 正确的显示应该是:19:15 可以做出如下修改: def __str__(self):return '%02d:%02d'%(self.hr, self.min)__repr__ = __str__ def __add__(self, othertime): tmin= self.min +othertime.min thr= self.hr +othertime.hrreturn self.__class__(thr + tmin/60, tmin%60)def __iadd__(self, othertime): self.min+=othertime.min self.hr+=othertime.hr self.hr+= self.min/60self.min%= 60 return self 三:迭代器 迭代器对象本身需要支持以下两种方法,它们组合在一起形成迭代器协议: iterator.__iter__() 返回迭代器对象本身。 iterator.next() 从容器中返回下一个元素。 实现了__iter__()和next()方法的类就是一个迭代器。自定义迭代器的例子如下: RandSeq(Random Sequence),传入一个初始序列,__init__()方法执行前述的赋值操作。__iter__()仅返回self,这就是如何将一个对象声明为迭代器的方式,最后,调用next()来得到迭代器中连续的值。这个迭代器唯一的亮点是它没有终点。代码如下: classRandSeq(object):def __init__(self, seq): self.data=seqdef __iter__(self):returnselfdefnext(self):return choice(self.data) 运行它,将会看到下面的输出: >>> from randseq importRandSeq>>> for eachItem in RandSeq(('rock', 'paper', 'scissors')): ...printeachItem ... scissors scissors rock paper paper scissors ...... 四:多类型定制 现在创建另一个新类,NumStr,由一个数字-字符对组成,记为n和s,数值类型使用整型(integer)。用[n::s]来表示它,这两个数据元素构成一个整体。NumStr有下面的特征: 初始化: 类应当对数字和字符串进行初始化;如果其中一个(或两)没有初始化,则使用0和空字符串,也就是, n=0 且s=''作为默认。 加法: 定义加法操作符,功能是把数字加起来,把字符连在一起;比如,NumStr1=[n1::s1]且NumStr2=[n2::s2]。则NumStr1+NumStr2 表示[n1+n2::s1+s2],其中,+代表数字相加及字符相连接。 乘法: 类似的, 定义乘法操作符的功能为, 数字相乘,字符累积相连, 也就是,NumStr1NumStr2=[n1n::s1n]。 False 值:当数字的数值为 0 且字符串为空时,也就是当NumStr=[0::'']时,这个实体即有一个false值。 比较: 比较一对NumStr对象,比如,[n1::s1] vs. [n2::s2],有九种不同的组合。对数字和字符串,按照标准的数值和字典顺序的进行比较。 如果obj1< obj2,则cmp(obj1, obj2)的返回值是一个小于0 的整数, 当obj1 > obj2 时,比较的返回值大于0, 当两个对象有相同的值时, 比较的返回值等于0。 我们的类的解决方案是把这些值相加,然后返回结果。为了能够正确的比较对象,我们需要让__cmp__()在 (n1>n2) 且 (s1>s2)时,返回 1,在(n1s2),或相反),返回0. 反之亦然。代码如下: classNumStr(object):def __init__(self, num=0, string=''): self.__num =num self.__string =stringdef __str__(self):return '[%d :: %r]' % (self.__num, self.__string)__repr__ = __str__ def __add__(self, other):ifisinstance(other, NumStr):return self.__class__(self.__num + other.__num, self.__string + other.__string)else:raise TypeError, 'Illegal argument type for built-in operation' def __mul__(self, num):ifisinstance(num, int):return self.__class__(self.__num num, self.__string num)else:raise TypeError, 'Illegal argument type for built-inoperation' def __nonzero__(self):return self.__num or len(self.__string)def __norm_cval(self, cmpres):returncmp(cmpres, 0)def __cmp__(self, other):return self.__norm_cval(cmp(self.__num, other.__num))+\ self.__norm_cval(cmp(self.__string,other.__string)) 执行一些例子: >>> a =NumStr(3, 'foo')>>> b =NumStr(3, 'goo')>>> c =NumStr(2, 'foo')>>> d =NumStr()>>> e =NumStr(string='boo')>>> f =NumStr(1)>>>a [3 :: 'foo']>>>b [3 :: 'goo']>>>c [2 :: 'foo']>>>d [0 ::'']>>>e [0 ::'boo']>>>f [1 :: '']>>> a True>>> b False>>> a ==a True>>> b 2[6 :: 'googoo']>>> a 3[9 :: 'foofoofoo']>>> b +e [3 :: 'gooboo']>>> e +b [3 :: 'boogoo']>>> if d: 'not false'...>>> if e: 'not false'...'not false' >>>cmp(a, b)-1 >>>cmp(a, c)1 >>>cmp(a, a) 0 如果在__str__中使用“%s”,将导致字符串没有引号: return '[%d :: %s]' % (self.__num, self.__string)>>> printa [3 :: foo] 第二个元素是一个字符串,如果用户看到由引号标记的字符串时,会更加直观。要做到这点,使用“repr()”表示法对代码进行转换,把“%s”替换成“%r”。这相当于调用repr()或者使用单反引号来给出字符串的可求值版本--可求值版本的确要有引号: >>> printa [3 :: 'foo'] __norm_cval()不是一个特殊方法。它是一个帮助我们重载__cmp__()的助手函数:唯一的目的就是把cmp()返回的正值转为1,负值转为-1。cmp()基于比较的结果,通常返回任意的正数或负数(或0),但为了我们的目的,需要严格规定返回值为-1,0 和1。 对整数调用cmp()及与 0 比较,结果即是我们所需要的,相当于如下代码片断: def __norm_cval(self, cmpres):if cmpres<0:return -1 elif cmpres>0:return 1 else:return 0 两个相似对象的实际比较是比较数字,比较字符串,然后返回这两个比较结果的和。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_30849865/article/details/112989450。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-19 14:30:42
132
转载
转载文章
...PR等相关法规对用户数据处理提出了严格要求,这也促使各平台在设计头像上传功能时,必须兼顾到用户信息的安全存储与传输。众多企业开始采用加密上传、权限控制等手段,确保用户头像数据的安全性。 综上所述,在当前互联网环境下,用户头像处理技术正不断迭代创新,以满足日益增长的个性化需求和严格的隐私保护规范。无论是大型社交平台的技术突破,还是各类开发框架对头像上传功能的优化改进,都为我们提供了丰富的实践案例与参考思路,值得广大开发者持续关注并深入研究。
2023-07-18 10:58:17
268
转载
转载文章
...va 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。目前很多开源库都使用到了注解,最熟悉的ButtonKnife中的@ViewInject(R.id.x)就可以替代findViewId,不懂这一块技术的同学第一眼看上去肯定会一脸懵逼,下面会手把手带大家写出ButtonKnife的注解使用。使用注解可以简化代码,提高开发效率。本文简单介绍下注解的使用,并对几个 Android 开源库的注解使用原理进行简析。 1、作用 标记,用于告诉编译器一些信息 ; 编译时动态处理,如动态生成代码 ; 运行时动态处理,如得到注解信息。 2、分类 标准 Annotation, 包括 Override, Deprecated, SuppressWarnings。也都是Java自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning; 元 Annotation ,@Retention, @Target, @Inherited, @Documented。元 Annotation 是指用来定义 Annotation 的 Annotation,在后面 Annotation 自定义部分会详细介绍含义; 自定义 Annotation , 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation 这里只是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation。通过 @interface 定义,注解名即为自定义注解名。 一、自定义注解 例如,注解@MethodInfo: @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Inheritedpublic @interface MethodInfo {String author() default "annotation@gmail.com";String date();int version() default 1;} 使用到了元Annotation: @Documented 是否会保存到 Javadoc 文档中 ; @Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, Deprecated, SuppressWarnings ; @Target 用来指定修饰的元素,如 CONSTRUCTOR:用于描述构造器、FIELD:用于描述域、LOCAL_VARIABLE:用于描述局部变量、METHOD:用于描述方法、PACKAGE:用于描述包、PARAMETER:用于描述参数、TYPE:用于描述类、接口(包括注解类型) 或enum声明。 @Inherited 是否可以被继承,默认为 false。 注解的参数名为注解类的方法名,且: 所有方法没有方法体,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常; 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组; 若只有一个默认属性,可直接用 value() 函数。一个属性都没有表示该 Annotation 为 Mark Annotation。 public class App {@MethodInfo(author = “annotation.cn+android@gmail.com”,date = "2011/01/11",version = 2)public String getAppName() {return "appname";} } 调用自定义MethodInfo 的示例,这里注解的作用实际是给方法添加相关信息: author、date、version 。 二、实战注解Butter Knife 首先,先定义一个ViewInject注解。 public @interface ViewInject { int value() default -1;} 紧接着,为刚自定义注解添加元注解。 @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject {int value() default -1;} 再定义一个注解LayoutInject @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface LayoutInject {int value() default -1;} 定义一个基础的Activity。 package cn.wsy.myretrofit.annotation;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import java.lang.reflect.Field;public class InjectActivity extends AppCompatActivity {private int mLayoutId = -1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);displayInjectLayout();displayInjectView();}/ 解析注解view id/private void displayInjectView() {if (mLayoutId <=0){return ;}Class<?> clazz = this.getClass();Field[] fields = clazz.getDeclaredFields();//获得声明的成员变量for (Field field : fields) {//判断是否有注解try {if (field.getAnnotations() != null) {if (field.isAnnotationPresent(ViewInject.class)) {//如果属于这个注解//为这个控件设置属性field.setAccessible(true);//允许修改反射属性ViewInject inject = field.getAnnotation(ViewInject.class);field.set(this, this.findViewById(inject.value()));} }} catch (Exception e) {Log.e("wusy", "not found view id!");} }}/ 注解布局Layout id/private void displayInjectLayout() {Class<?> clazz = this.getClass();if (clazz.getAnnotations() != null){if (clazz.isAnnotationPresent(LayouyInject.class)){LayouyInject inject = clazz.getAnnotation(LayouyInject.class);mLayoutId = inject.value();setContentView(mLayoutId);} }} } 首先,这里是根据映射实现设置控件的注解,java中使用反射的机制效率性能并不高。这里只是举例子实现注解。ButterKnife官方申明不是通过反射机制,因此效率会高点。 package cn.wsy.myretrofit;import android.os.Bundle;import android.widget.TextView;import cn.wsy.myretrofit.annotation.InjectActivity;import cn.wsy.myretrofit.annotation.LayouyInject;import cn.wsy.myretrofit.annotation.ViewInject;@LayoutInject(R.layout.activity_main)public class MainActivity extends InjectActivity {@ViewInject(R.id.textview)private TextView textView;@ViewInject(R.id.textview1)private TextView textview1;@ViewInject(R.id.textview2)private TextView textview2;@ViewInject(R.id.textview3)private TextView textview3;@ViewInject(R.id.textview4)private TextView textview4;@ViewInject(R.id.textview5)private TextView textview5;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//设置属性textView.setText("OK");textview1.setText("OK1");textview2.setText("OK2");textview3.setText("OK3");textview4.setText("OK4");textview5.setText("OK5");} } 上面直接继承InjectActivity即可,文章上面也有说过:LayouyInject为什么作用域是TYPE,首先在加载view的时候,肯定是优先加载布局啊,ButterKnife也不例外。因此选择作用域在描述类,并且存在运行时。 二、解析Annotation原理 1、运行时 Annotation 解析 (1) 运行时 Annotation 指 @Retention 为 RUNTIME 的 Annotation,可手动调用下面常用 API 解析 method.getAnnotation(AnnotationName.class);method.getAnnotations();method.isAnnotationPresent(AnnotationName.class); 其他 @Target 如 Field,Class 方法类似 。 getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息,一个 Target 可以被多个 Annotation 修饰; getAnnotations() 则表示得到该 Target 所有 Annotation ; isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰; (2) 解析示例如下: public static void main(String[] args) {try {Class cls = Class.forName("cn.trinea.java.test.annotation.App");for (Method method : cls.getMethods()) {MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);if (methodInfo != null) {System.out.println("method name:" + method.getName());System.out.println("method author:" + methodInfo.author());System.out.println("method version:" + methodInfo.version());System.out.println("method date:" + methodInfo.date());} }} catch (ClassNotFoundException e) {e.printStackTrace();} } 以之前自定义的 MethodInfo 为例,利用 Target(这里是 Method)getAnnotation 函数得到 Annotation 信息,然后就可以调用 Annotation 的方法得到响应属性值 。 2、编译时 Annotation 解析 (1) 编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴 apt(Annotation Processing Tool) 解析自动解析。 使用方法: 自定义类集成自 AbstractProcessor; 重写其中的 process 函数 这块很多同学不理解,实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。 (2) 假设之前自定义的 MethodInfo 的 @Retention 为 CLASS,解析示例如下: @SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })public class MethodInfoProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {HashMap<String, String> map = new HashMap<String, String>();for (TypeElement te : annotations) {for (Element element : env.getElementsAnnotatedWith(te)) {MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);map.put(element.getEnclosingElement().toString(), methodInfo.author());} }return false;} } SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。 process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境 process 函数返回值表示这组 annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理 三、几个 Android 开源库 Annotation 原理简析 1、Retrofit (1) 调用 @GET("/users/{username}")User getUser(@Path("username") String username); (2) 定义 @Documented@Target(METHOD)@Retention(RUNTIME)@RestMethod("GET")public @interface GET {String value();} 从定义可看出 Retrofit 的 Get Annotation 是运行时 Annotation,并且只能用于修饰 Method (3) 原理 private void parseMethodAnnotations() {for (Annotation methodAnnotation : method.getAnnotations()) {Class<? extends Annotation> annotationType = methodAnnotation.annotationType();RestMethod methodInfo = null;for (Annotation innerAnnotation : annotationType.getAnnotations()) {if (RestMethod.class == innerAnnotation.annotationType()) {methodInfo = (RestMethod) innerAnnotation;break;} }……} } RestMethodInfo.java 的 parseMethodAnnotations 方法如上,会检查每个方法的每个 Annotation, 看是否被 RestMethod 这个 Annotation 修饰的 Annotation 修饰,这个有点绕,就是是否被 GET、DELETE、POST、PUT、HEAD、PATCH 这些 Annotation 修饰,然后得到 Annotation 信息,在对接口进行动态代理时会掉用到这些 Annotation 信息从而完成调用。 因为 Retrofit 原理设计到动态代理,这里只介绍 Annotation。 2、Butter Knife (1) 调用 @InjectView(R.id.user) EditText username; (2) 定义 @Retention(CLASS) @Target(FIELD)public @interface InjectView {int value();} 可看出 Butter Knife 的 InjectView Annotation 是编译时 Annotation,并且只能用于修饰属性 (3) 原理 @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env);for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {TypeElement typeElement = entry.getKey();ViewInjector viewInjector = entry.getValue();try {JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement);Writer writer = jfo.openWriter();writer.write(viewInjector.brewJava());writer.flush();writer.close();} catch (IOException e) {error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage());} }return true;} ButterKnifeProcessor.java 的 process 方法如上,编译时,在此方法中过滤 InjectView 这个 Annotation 到 targetClassMap 后,会根据 targetClassMap 中元素生成不同的 class 文件到最终的 APK 中,然后在运行时调用 ButterKnife.inject(x) 函数时会到之前编译时生成的类中去找。 3、ActiveAndroid (1) 调用 @Column(name = “Name") public String name; (2) 定义 @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Column {……} 可看出 ActiveAndroid 的 Column Annotation 是运行时 Annotation,并且只能用于修饰属性 (3) 原理 Field idField = getIdField(type);mColumnNames.put(idField, mIdName);List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));Collections.reverse(fields);for (Field field : fields) {if (field.isAnnotationPresent(Column.class)) {final Column columnAnnotation = field.getAnnotation(Column.class);String columnName = columnAnnotation.name();if (TextUtils.isEmpty(columnName)) {columnName = field.getName();}mColumnNames.put(field, columnName);} } TableInfo.java 的构造函数如上,运行时,得到所有行信息并存储起来用来构件表信息。 ———————————————————————— 最后一个问题,看看这段代码最后运行结果: public class Person {private int id;private String name;public Person(int id, String name) {this.id = id;this.name = name;}public boolean equals(Person person) {return person.id == id;}public int hashCode() {return id;}public static void main(String[] args) {Set<Person> set = new HashSet<Person>();for (int i = 0; i < 10; i++) {set.add(new Person(i, "Jim"));}System.out.println(set.size());} } 答案:示例代码运行结果应该是 10 而不是 1,这个示例代码程序实际想说明的是标记型注解 Override 的作用,为 equals 方法加上 Override 注解就知道 equals 方法的重载是错误的,参数不对。 本篇文章为转载内容。原文链接:https://blog.csdn.net/csdn_aiyang/article/details/81564408。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-28 22:30:35
104
转载
Golang
...er函数向通道发送数据,而consumer函数从通道接收数据。用这种方法,咱们就能又优雅又稳妥地搞定多线程里的同步难题,还不用担心被死锁给缠上。 --- 3. 内存管理 GC的奥秘 接下来谈谈内存管理。Go的垃圾回收器(GC)是它的一大亮点。就像用老式工具编程一样,C/C++这种传统语言就得让程序员自己动手去清理内存,稍不留神,就可能搞出内存泄漏,或者戳到那些讨厌的野指针,简直让人头大!而Go则完全解放了我们的双手,它会自动帮你清理不再使用的内存。 不过,GC也不是万能的。有时候,如果你对性能要求特别高,可能会遇到GC停顿的问题。为了解决这个问题,Go团队一直在优化GC算法。最新版本中引入了分代GC(Generational GC),大幅降低了停顿时间。 那么,我们在实际开发中应该如何减少GC的压力呢?最直接的方法就是尽量避免频繁的小对象分配。比如,我们可以复用一些常见的结构体,而不是每次都新建它们: go type Buffer struct { data []byte } func NewBuffer(size int) Buffer { return &Buffer{data: make([]byte, size)} } func (b Buffer) Reset() { b.data = b.data[:0] } func main() { buf := NewBuffer(1024) for i := 0; i < 100; i++ { buf.Reset() // 使用buf... } } 在这个例子中,我们通过Reset()方法复用了同一个Buffer实例,而不是每次都调用make([]byte, size)重新创建一个新的切片。这样可以显著降低GC的压力。 --- 4. 网络优化 TCP/IP的实战 再来说说网络优化。Go的net包提供了强大的网络编程支持,无论是HTTP、WebSocket还是普通的TCP/UDP,都能轻松搞定。特别是对那些高性能服务器而言,怎么才能又快又稳地搞定海量连接,这简直就是一个绕不开的大难题啊! 举个例子,假设我们要实现一个简单的HTTP长连接服务器。传统的做法可能是监听端口,然后逐个处理请求。但这种方式效率不高,特别是在高并发场景下。Go提供了一个更好的解决方案——使用net/http包的Serve方法: go package main import ( "log" "net/http" ) func handler(w http.ResponseWriter, r http.Request) { w.Write([]byte("Hello, World!")) } func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(":8080", nil)) } 这段代码看起来很简单,但它实际上已经具备了处理大量并发连接的能力。为啥呢?就是因为Go语言里的http.Server自带了一个超级能打的“工具箱”,里面有个高效的连接池和请求队列,遇到高并发的情况时,它就能像一个经验丰富的老司机一样,把各种请求安排得明明白白,妥妥地hold住场面! 当然,如果你想要更底层的控制,也可以直接使用net包来编写TCP服务器。比如下面这个简单的TCP回显服务器: go package main import ( "bufio" "fmt" "net" ) func handleConnection(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { message, err := reader.ReadString('\n') if err != nil { fmt.Println("Error reading:", err) break } fmt.Print("Received:", message) conn.Write([]byte(message)) } } func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { fmt.Println("Error listening:", err) return } defer listener.Close() fmt.Println("Listening on :8080...") for { conn, err := listener.Accept() if err != nil { fmt.Println("Error accepting:", err) continue } go handleConnection(conn) } } 在这个例子中,我们通过listener.Accept()不断接受客户端连接,并为每个连接启动一个协程来处理请求。这种模式非常适合处理大量短连接的场景。 --- 5. 代码结构 模块化与可扩展性 最后,我们来聊聊代码结构。一个高性能的服务器不仅仅依赖于语言特性,还需要良好的设计思路。Go语言特别推崇把程序分成小块儿来写,就像搭积木一样,每个功能都封装成独立的小模块或包。这样不仅修 bug 的时候方便找问题,写代码的时候也更容易看懂,以后想加新功能啥的也简单多了。 比如,假设我们要开发一个分布式任务调度系统,可以按照以下方式组织代码: go // tasks.go package task type Task struct { ID string Name string Param interface{} } func NewTask(id, name string, param interface{}) Task { return &Task{ ID: id, Name: name, Param: param, } } // scheduler.go package scheduler import "task" type Scheduler struct { tasks []task.Task } func NewScheduler() Scheduler { return &Scheduler{ tasks: make([]task.Task, 0), } } func (s Scheduler) AddTask(t task.Task) { s.tasks = append(s.tasks, t) } func (s Scheduler) Run() { for _, t := range s.tasks { fmt.Printf("Executing task %s\n", t.Name) // 执行任务逻辑... } } 通过这种方式,我们将任务管理和调度逻辑分离出来,使得代码更加清晰易懂。同时,这样的设计也方便未来扩展新的功能,比如添加日志记录、监控指标等功能。 --- 6. 总结与展望 好了,到这里咱们就差不多聊完了如何用Go语言进行高性能服务器开发。说实话,写着这篇文章的时候,我脑海里突然蹦出大学时那股子钻研劲儿,感觉就像重新回到那些熬夜敲代码的日子了,整个人都热血上头!Go这门语言真的太带感了,简单到没话说,效率还超高,稳定性又好得没话说,简直就是程序员的救星啊! 不过,我也想提醒大家一句:技术再好,最终还是要服务于业务需求。不管你用啥法子、说啥话,老老实实问问自己:“这招到底管不管用?是不是真的解决问题了?”这才是真本事! 希望这篇文章对你有所帮助,如果你有任何疑问或者想法,欢迎随时留言讨论!让我们一起继续探索Go的无限可能吧!
2025-04-23 15:46:59
39
桃李春风一杯酒
Netty
Netty与大数据流处理平台的优化 1. Netty是什么?为什么它这么重要? 嗨,大家好!我是你们的老朋友,今天我们要聊聊一个超级厉害的技术——Netty。嘿,要是你对分布式系统、高能网络编程或者大数据流处理这些酷炫的东西感兴趣,那Netty可就太值得一试了!它就像是个隐藏的宝藏,能让你在这些领域玩得更溜。 首先,Netty是什么?简单来说,Netty是一个基于Java的异步事件驱动网络应用框架。它可以帮助开发者快速构建可扩展的服务器端应用程序。想象一下,你正在开发一个需要处理海量数据的大数据流处理平台,这时候Netty就显得尤为重要了。它不仅能够帮助我们高效地管理网络连接,还能让我们轻松应对高并发场景。 我第一次接触Netty的时候,真的被它的灵活性震撼到了。哎,说到程序员的烦心事,那肯定得提一提怎么让程序在被成千上万的人同时戳的时候还能稳如老狗啊!这事儿真心让人头大,尤其是看着服务器指标噌噌往上涨,心里直打鼓,生怕哪一秒就崩了。而Netty通过非阻塞I/O模型,完美解决了这个问题。这就像是一个超级能干的服务员,能够在同一时间同时服务上万个客人,而且就算有个客人纠结半天点菜(也就是某个请求拖拉),也不会耽误其他客人的服务,更不会让整个餐厅都停下来等他。 举个栗子: java EventLoopGroup bossGroup = new NioEventLoopGroup(); // 主线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作线程组 try { ServerBootstrap b = new ServerBootstrap(); // 启动辅助类 b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 使用NIO通道 .childHandler(new ChannelInitializer() { // 子处理器 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder()); // 解码器 ch.pipeline().addLast(new StringEncoder()); // 编码器 ch.pipeline().addLast(new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("Received message: " + msg); ctx.writeAndFlush("Echo: " + msg); // 回显消息 } }); } }); ChannelFuture f = b.bind(8080).sync(); // 绑定端口并同步等待完成 f.channel().closeFuture().sync(); // 等待服务关闭 } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } 这段代码展示了如何用Netty创建一个简单的TCP服务器。话说回来,Netty这家伙简直太贴心了,它的API设计得特别直观,想设置啥处理器或者监听事件都超简单,用起来完全没压力,感觉开发效率直接拉满! 2. 大数据流处理平台中的挑战 接下来,我们聊聊大数据流处理平台面临的挑战。在这个领域,我们通常会遇到以下几个问题: - 高吞吐量:我们需要处理每秒数百万条甚至更多的数据记录。 - 低延迟:对于某些实时应用场景(如股票交易),毫秒级的延迟都是不可接受的。 - 可靠性:数据不能丢失,必须保证至少一次投递。 - 扩展性:随着业务增长,系统需要能够无缝扩容。 这些问题听起来是不是很让人头大?但别担心,Netty正是为此而生的! 让我分享一个小故事吧。嘿,有次我正忙着弄个日志收集系统,结果一测试才发现,这传统的阻塞式I/O模型简直是“人形瓶颈”啊!流量一大就直接崩溃,完全hold不住那个高峰时刻,简直让人头大!于是,我开始研究Netty,并将其引入到项目中。哈哈,结果怎么样?系统的性能直接翻了三倍!这下我可真服了,选对工具真的太重要了,感觉像是找到了开挂的装备一样爽。 为了更好地理解这些挑战,我们可以看看下面这段代码,这是Netty中用来实现高性能读写的示例: java public class HighThroughputHandler extends ChannelInboundHandlerAdapter { private final ByteBuf buffer; public HighThroughputHandler() { buffer = Unpooled.buffer(1024); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 1024; i++) { buffer.writeByte((byte) i); } ctx.writeAndFlush(buffer.retain()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.write(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 在这段代码中,我们创建了一个自定义的处理器HighThroughputHandler,它能够在每次接收到数据后立即转发出去,从而实现高吞吐量的传输。 3. Netty如何优化大数据流处理平台? 现在,让我们进入正题——Netty是如何具体优化大数据流处理平台的呢? 3.1 异步非阻塞I/O Netty的核心优势在于其异步非阻塞I/O模型。这就相当于,当有请求进来的时候,Netty可不会给每个连接都专门安排一个“服务员”,而是让这些连接共用一个“服务团队”。这样既能节省人手,又能高效处理各种任务,多划算啊!这样做的好处是显著减少了内存占用和上下文切换开销。 假设你的大数据流处理平台每天要处理数十亿条数据记录,采用传统的阻塞式I/O模型,很可能早就崩溃了。而Netty则可以通过单线程处理数千个连接,极大地提高了资源利用率。 3.2 零拷贝技术 另一个让Netty脱颖而出的特点是零拷贝技术。嘿,咱们就拿快递打个比方吧!想象一下,你在家里等着收快递,但这个快递特别麻烦——它得先从仓库(相当于内核空间)送到快递员手里(用户空间),然后快递员再把东西送回到你家(又回到内核空间)。这就像是数据在网络通信里来回折腾了好几趟,一会儿在系统深处待着,一会儿又被搬出来给应用用,真是费劲啊!这种操作不仅耗时,还会消耗大量CPU资源。 Netty通过ZeroCopy机制,直接将数据从文件系统传递到网络套接字,避免了不必要的内存拷贝。这种做法不仅加快了数据传输速度,还降低了系统的整体负载。 这里有一个实际的例子: java FileRegion region = new DefaultFileRegion(fileChannel, 0, fileSize); ctx.write(region); 上述代码展示了如何利用Netty的零拷贝功能发送大文件,无需手动加载整个文件到内存中。 3.3 灵活的消息编解码 在大数据流处理平台中,数据格式多种多样,可能包括JSON、Protobuf、Avro等。Netty提供了一套强大的消息编解码框架,允许开发者根据需求自由定制解码逻辑。 例如,如果你的数据是以Protobuf格式传输的,可以这样做: java public class ProtobufDecoder extends MessageToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { byte[] data = new byte[in.readableBytes()]; in.readBytes(data); MyProtoMessage message = MyProtoMessage.parseFrom(data); out.add(message); } } 通过这种方式,我们可以轻松解析复杂的数据结构,同时保持代码的整洁性和可维护性。 3.4 容错与重试机制 最后但同样重要的是,Netty内置了强大的容错与重试机制。在网上聊天或者传输文件的时候,有时候会出现消息没发出去、对方迟迟收不到的情况,就像快递丢了或者送慢了。Netty这个小助手可机灵了,它会赶紧发现这些问题,然后试着帮咱们把没送到的消息重新发一遍,就像是给快递员多派一个人手,保证咱们的信息能安全顺利地到达目的地。 java RetryHandler retryHandler = new RetryHandler(maxRetries); ctx.pipeline().addFirst(retryHandler); 上面这段代码展示了如何添加一个重试处理器到Netty的管道中,让它在遇到错误时自动重试。 4. 总结与展望 经过这一番探讨,相信大家已经对Netty及其在大数据流处理平台中的应用有了更深入的理解。Netty可不只是个工具库啊,它更像是个靠谱的小伙伴,陪着咱们一起在高性能网络编程的大海里劈波斩浪、寻宝探险! 当然,Netty也有它的局限性。比如说啊,遇到那种超级复杂的业务场景,你可能就得绞尽脑汁写一堆专门定制的代码,不然根本搞不定。还有呢,这门技术的学习难度有点大,刚上手的小白很容易觉得晕头转向,不知道该怎么下手。但我相信,只要坚持实践,总有一天你会爱上它。 未来,随着5G、物联网等新技术的发展,大数据流处理的需求将会更加旺盛。而Netty凭借其卓越的性能和灵活性,必将在这一领域继续发光发热。所以,不妨大胆拥抱Netty吧,它会让你的开发之旅变得更加精彩! 好了,今天的分享就到这里啦!如果你有任何疑问或者想法,欢迎随时交流。记住,编程之路没有终点,只有不断前进的脚步。加油,朋友们!
2025-04-26 15:51:26
46
青山绿水
转载文章
...以更好地实现工厂级的数据采集和管理; 不再基于DCOM通讯,不需要进行DCOM安全设置; OPC UA定义了统一数据和服务模型,使数据组织更为灵活,可以实现报警与事件、数据存取、历史数据存取、控制命令、复杂数据的交互通信; OPC UA比OPC DA更安全。OPC UA传递的数据是可以加密的,并对通信连接和数据本身都可以实现安全控制。新的安全模型保证了数据从原始设备到MES,ERP系统,从本地到远程的各级自动化和信息化系统的可靠传递; OPC UA可以穿越防火墙,实现Internet 通讯。 依赖 我们通常不会从头写,可以基于OpcUa.core.dll库和OpcUa.Client.dll库,而且附上这2个库的源代码。 配置OpcUA Server 您可以安装任何一款支持OPCUA的服务端软件进行以下配置(此为示例配置,您可根据你的实际情况进行配置) 1、OpcUa Server Url:opc.tcp://192.168.100.1:4840。 2、OpcUa EndPoint:[UaServer@cMT-EAB9] [None] [None] [opc.tcp://192.168.100.1:4840/G01] 3、PLC Device Name:Siemens S7-1200/S7-1500 4、Account:user1 5、Password:自己设置 6、在PLC中开了2个数据块,分别为DB4长度110个字、DB5长度122个字。 7、对应第4块创建标签,第一个名称为DB4.0-99,地址为DB4DBW0.100,数据类型为Short,长度100,即定义长度最长为100的Short数组。第二个名称为DB4.100-109,地址为DB4DBW100.10,数据类型为Short,方便快速读取。 5、对应第5块创建3个标签,第一个名称为DB5.0-99,地址为DB5DBW0.100,数据类型为Short,第二个名称为DB5.100-121, 地址为DB5DBW100.22,数据类型为Short,即定义长度最长为100的Short数组。方便快速读取。第三个标签名称为DB5DBW64,地址为DB5DBW64,数据类型为Short。 具体如下图: 关键代码 using System;using System.Collections.Generic;using System.Linq;using Opc.Ua.Helper;using Mesnac.Equips;namespace Mesnac.Equip.OPC.OpcUa.OPCUA{public class Equip : BaseEquip{region 字段定义private bool _isOpen = false; //是否已打开设备private bool _isClosing = false; //是否正在关闭设备private OPCUAClass myOpcHelper; //OPCUA设备访问辅助对象private Dictionary<string, string> dicTags = null; //保存标签集合private Dictionary<string, object> readResult = null; //设备标签数据缓存private int stepLen = 250; //标签变量的步长设置private string groupNamePrefix = "DB"; //数据块号前缀private string childTagFlag = "~"; //子元素标签标志符private System.Threading.Thread innerReadThread = null; //内部读取线程对象private int innerReadRate = 1000; //内部读取频率endregionregion 属性定义/// <summary>/// OPCUA Server Url/// </summary>public string OpcUaServerUrl{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).OpcUaServerUrl;return "opc.tcp://192.168.1.102:4840";//return "opc.tcp://192.168.100.1:4840";//return "opc.tcp://192.168.100.2:4840";} }/// <summary>/// 要连接的OPCUA服务器上的服务名/// </summary>public string OpcUaServiceName{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).OpcUaServiceName;return "[UaServer@cMT-9F1F] [None] [None] [opc.tcp://192.168.1.102:4840/G01]";//return "[UaServer@cMT-EAB9] [None] [None] [opc.tcp://192.168.100.1:4840/G01]";//return "[UaServer@cMT-EA5B] [None] [None] [opc.tcp://192.168.100.2:4840/G02]";//return "[UaServer@cMT-EA5B] [None] [None] [opc.tcp://192.168.100.2:4840/G01]";} }/// <summary>/// 要连接的OPCUA服务器上指定服务名下的PLC的名称/// </summary>public string PLCName{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).PLCName;//return "Feeding";return "Siemens_192.168.2.1";//return "Rockwell_192.168.1.10";} }/// <summary>/// OPCUA服务器的访问账户/// </summary>public string Account{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).Account;return "user1";} }/// <summary>/// OPCUA服务器的访问密码/// </summary>public string Password{get{//return (this.Main.ConnType as Mesnac.Equips.Connection.OPCUA.ConnType).Password;return "1";} }endregionregion BaseEquip成员实现/// <summary>/// 打开连接设备/// </summary>/// <returns>成功返回true,失败返回false</returns>public override bool Open(){lock (this){this._isClosing = false;if (this._isOpen == true && this.myOpcHelper != null){return true;}this.State = false;this.myOpcHelper = new OPCUAClass();this.dicTags = this.myOpcHelper.ConnectOPCUA(this.OpcUaServerUrl, this.Account, this.Password, this.OpcUaServiceName, this.PLCName); //连接OPCServerif (this.dicTags == null || this.dicTags.Count == 0){this.myOpcHelper = null;Console.WriteLine("OPC连接失败!");this.State = false;return false;}else{this.State = true;this._isOpen = true;region 初始化读取结果this.readResult = new Dictionary<string, object>();foreach (Equips.BaseInfo.Group group in this.Group.Values){if (!group.IsAutoRead){continue;}int groupMinStart = group.Start;int groupMaxEnd = group.Start + group.Len;int groupMaxLen = group.Len;foreach (Equips.BaseInfo.Group g in this.Group.Values){if (!g.IsAutoRead){continue;}if (g.Block == group.Block){if (g.Start < group.Start){groupMinStart = g.Start;}if (g.Start + g.Len > groupMaxEnd){groupMaxEnd = g.Start + g.Len;} }}groupMaxLen = groupMaxEnd - groupMinStart;int tagCount = groupMaxLen % this.stepLen == 0 ? groupMaxLen / this.stepLen : groupMaxLen / this.stepLen + 1;int currLen = 0;for (int i = 0; i < tagCount; i++){string tagName = String.Empty;if (tagCount == 1){tagName = String.Format("{0}-{1}", groupMinStart, groupMinStart + groupMaxLen - 1);currLen = groupMaxLen;}else if (i == tagCount - 1){tagName = String.Format("{0}-{1}", groupMinStart + (i this.stepLen), groupMinStart + (i this.stepLen) + (groupMaxLen % this.stepLen == 0 ? this.stepLen : groupMaxLen % this.stepLen) - 1);currLen = groupMaxLen % this.stepLen;}else{tagName = String.Format("{0}-{1}", groupMinStart + (i this.stepLen), groupMinStart + (i this.stepLen) + this.stepLen - 1);currLen = this.stepLen;}string tagFullName = String.Format("{0}{1}.{2}", groupNamePrefix, group.Block, tagName);if (!this.readResult.ContainsKey(tagFullName)){bool exists = false;region 判断读取结果标签组的范围是否包括了此标签 比如tagFullName DB5.220-299,在readResult中存在 DB5.200-299,则认为已存在,不需要再添加string[] beginend = null;int begin = 0;int end = 0;string[] startstop = tagFullName.Replace(String.Format("{0}{1}.", groupNamePrefix, group.Block), String.Empty).Split(new char[] { '-' });int start = 0;int stop = 0;bool parseResult = false;if (startstop.Length == 2){parseResult = int.TryParse(startstop[0], out start);if (parseResult){parseResult = int.TryParse(startstop[1], out stop);} }if (parseResult){int existsMinBegin = 0; //已存在标签的最小开始索引int existsMaxEnd = 0; //已存在标签的最大结束索引bool isContinue = true; //标签值是否连续string[] existsTags = this.readResult.Keys.ToArray<string>();foreach (string tag in existsTags){if (tag.StartsWith(String.Format("{0}{1}.", groupNamePrefix, group.Block)) && tag.Contains(".") && tag.Contains("-")){string[] tagname = tag.Split(new char[] { '.' });if (tagname.Length == 2){beginend = tagname[1].Split(new char[] { '-' });if (beginend.Length == 2){parseResult = int.TryParse(beginend[0], out begin);if (parseResult){parseResult = int.TryParse(beginend[1], out end);}region 计算最小开始索引和最大结束索引if (begin < existsMinBegin){existsMinBegin = begin;region 判断标签值是否连续if (existsMaxEnd != 0 && begin != existsMaxEnd + 1){isContinue = false;}endregion}if (end > existsMaxEnd){existsMaxEnd = end;}endregion} }if (parseResult){if (start >= begin && stop <= end){exists = true;break;}if (isContinue){if (start >= existsMinBegin && stop <= existsMaxEnd){exists = true;break;} }} }} }endregionif (!exists){ushort[] groupData = new ushort[currLen];this.readResult[tagFullName] = groupData;Console.WriteLine(tagFullName);} }}//int tagCount = group.Len % this.stepLen == 0 ? group.Len / this.stepLen : group.Len / this.stepLen + 1;//int currLen = 0;//for (int i = 0; i < tagCount; i++)//{// string tagName = String.Empty;// if (tagCount == 1)// {// tagName = String.Format("{0}-{1}", group.Start, group.Start + group.Len - 1);// currLen = group.Len;// }// else if (i == tagCount - 1)// {// tagName = String.Format("{0}-{1}", group.Start + (i this.stepLen), group.Start + (i this.stepLen) + (group.Len % this.stepLen == 0 ? this.stepLen : group.Len % this.stepLen) - 1);// currLen = group.Len % this.stepLen;// }// else// {// tagName = String.Format("{0}-{1}", group.Start + (i this.stepLen), group.Start + (i this.stepLen) + this.stepLen - 1);// currLen = this.stepLen;// }// string tagFullName = String.Format("{0}{1}.{2}", groupNamePrefix, group.Block, tagName);// if (!this.readResult.ContainsKey(tagFullName))// {// short[] groupData = new short[currLen];// this.readResult[tagFullName] = groupData;// }//} }endregionregion 开启内部定时读取if (this.innerReadThread == null){this.innerReadRate = this.Main.ReadHz / 2;this.innerReadThread = new System.Threading.Thread(this.InnerAutoRead);this.innerReadThread.Start();}endregion}return this.State;} }/// <summary>/// 从设备读取数据/// </summary>/// <param name="block">要读取的块号</param>/// <param name="start">要读取的起始字</param>/// <param name="len">要读取的长度</param>/// <param name="buff">读取成功后的输出数据</param>/// <returns>成功返回true,失败返回false</returns>public override bool Read(string block, int start, int len, out object[] buff){lock (this){buff = null;if (this._isClosing){return false;}string readstrflag = String.Format("{0}{1}.{2}-{3}", this.groupNamePrefix, block, start, start + len - 1);System.Text.StringBuilder sbtaglength = new System.Text.StringBuilder();string startTag = String.Empty;string groupName = String.Format("{0}{1}", this.groupNamePrefix, block); //要读取的OPCServer块List<ushort> groupData = new List<ushort>();List<string> groupTagNames = new List<string>();int startIndex = 0;try{if (!Open()){return false;}//return true;string[] keys = this.readResult.Keys.ToArray<string>();foreach (string key in keys){if (key.StartsWith(groupName) && key.Replace(String.Format("{0}.", groupName), String.Empty).Contains("-")){groupTagNames.Add(key);} }groupTagNames.Sort(); //对块标签进行排序foreach (string key in groupTagNames){if (String.IsNullOrEmpty(startTag)){startTag = key.Replace(String.Format("{0}.", groupName), String.Empty);}ushort[] values;if (this.readResult[key] is ushort[]){values = this.readResult[key] as ushort[];}else{values = new ushort[] { (ushort)this.readResult[key] };}sbtaglength.Append(String.Format("tagName={0}, buff length = {1}", key, values.Length));groupData.AddRange(values);}buff = new object[len];if (!String.IsNullOrEmpty(startTag)){string strStartIndex = startTag.Substring(0, startTag.IndexOf("-"));int.TryParse(strStartIndex, out startIndex);startIndex = start - startIndex;Array.Copy(groupData.ToArray(), startIndex, buff, 0, buff.Length);}else{}return true;}catch (Exception ex){Console.WriteLine(String.Join(";", groupTagNames.ToArray<string>()));Console.WriteLine("data length = " + groupData.Count);Console.WriteLine(this.Name + "读取失败[" + readstrflag + "]:" + ex.Message);Console.WriteLine(sbtaglength.ToString());this.State = false;return false;} }}/// <summary>/// 写入数据到设备/// </summary>/// <param name="block">要写入的块号</param>/// <param name="start">要写入的起始字</param>/// <param name="buff">要写如的数据</param>/// <returns>成功返回true,失败返回false</returns>public override bool Write(int block, int start, object[] buff){bool result = true;lock (this){try{if (this._isClosing){return false;}if (!Open()){return false;}bool isWrite = false;region 按标签变量写入string itemId = "";foreach (Equips.BaseInfo.Group group in this.Group.Values){if (group.Block == block.ToString()){foreach (Equips.BaseInfo.Data data in group.Data.Values){if (group.Start + data.Start == start && data.Len == buff.Length){if (this.dicTags.ContainsKey(data.Name)){itemId = this.dicTags[data.Name];}break;} }} }if (!String.IsNullOrEmpty(itemId)){UInt16[] intBuff = new UInt16[buff.Length];for (int i = 0; i < intBuff.Length; i++){intBuff[i] = 0;if (!UInt16.TryParse(buff[i].ToString(), out intBuff[i])){Console.WriteLine("在写入OPCUA标签时把buff中的元素转为UInt16类型失败!");} }result = this.myOpcHelper.WriteUInt16(itemId, intBuff);if (!result){Console.WriteLine(String.Format("标签变量[{0}]写入失败!", itemId));return false;}else{Console.WriteLine("按标签变量写入..." + itemId);isWrite = true;} }if (isWrite){return true;}endregionregion 按块写入region 先读取相应标签数数据string startTag = String.Empty;string groupName = String.Format("{0}{1}", this.groupNamePrefix, block); //要读取的OPCServer块List<ushort> groupData = new List<ushort>();string[] keys = readResult.Keys.Where(o => o.StartsWith(groupName) && o.Contains("-")).OrderBy(c => c).ToArray<string>();foreach (string key in keys){if (String.IsNullOrEmpty(startTag)){startTag = key.Replace(String.Format("{0}.", groupName), String.Empty);}string[] beginEnd = key.Replace(String.Format("{0}.", groupName), String.Empty).Split(new char[] { '-' });if (beginEnd.Length != 2){Console.WriteLine(String.Format("标签变量[{0}]未按约定方式命名,请按[DB块号].[起始字-结束字]方式标签变量进行命名!", String.Format("{0}.{1}", key)));return false;}int begin = 0;int end = 0;int.TryParse(beginEnd[0], out begin);int.TryParse(beginEnd[1], out end);region 写入之前,先读取一下PLC的值if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end) || (start < begin && (start + buff.Length - 1) > end)){this.ReadTag(key);if (this.readResult.ContainsKey(key) && this.readResult[key] is Array){Console.WriteLine("read = " + key);groupData.AddRange(this.readResult[key] as ushort[]);}else{Console.WriteLine(String.Format("读取结果中不包含标签变量[{0}]的值!", String.Format("{0}", key)));} }else{if (this.readResult.ContainsKey(key) && this.readResult[key] is Array){Console.WriteLine("no read = " + key);groupData.AddRange(this.readResult[key] as ushort[]);} }endregion}endregionif (String.IsNullOrEmpty(startTag)){Console.WriteLine("写入失败,未在OPCUAserver中找到对应的标签,block = {0}, start = {1}, len = {2}", block, start, buff.Length);return false;}region 更新标签中对应的数据后,再写回OPCServerint startIndex = 0;string strStartIndex = startTag.Substring(0, startTag.IndexOf("-"));int.TryParse(strStartIndex, out startIndex);startIndex = start - startIndex;ushort[] newDataBuffer = groupData.ToArray();for (int i = 0; i < buff.Length; i++){ushort svalue = 0;ushort.TryParse(buff[i].ToString(), out svalue);newDataBuffer[startIndex + i] = svalue;}int index = 0;string[] keys2 = readResult.Keys.Where(o => o.StartsWith(groupName) && o.Contains("-")).OrderBy(c => c).ToArray<string>();foreach (string key2 in keys2){string[] beginEnd = key2.Replace(String.Format("{0}.", groupName), String.Empty).Split(new char[] { '-' });if (beginEnd.Length != 2){Console.WriteLine(String.Format("标签变量[{0}]未按约定方式命名,请按[DB块号].[起始字-结束字]方式标签变量进行命名!", String.Format("{0}", key2)));return false;}int begin = 0;int end = 0;int.TryParse(beginEnd[0], out begin);int.TryParse(beginEnd[1], out end);if ((start >= begin && start <= end) || ((start + buff.Length - 1) >= begin && (start + buff.Length - 1) <= end) || (start < begin && (start + buff.Length - 1) > end)){//Console.WriteLine("---------------------------------------------------------");//Console.WriteLine("start = " + start);//Console.WriteLine("start + buff.Length - 1 = " + (start + buff.Length -1));//Console.WriteLine("begin = " + begin);//Console.WriteLine("end = " + end);//Console.WriteLine("---------------------------------------------------------");if (!this.dicTags.ContainsKey(key2)){Console.WriteLine(String.Format("写入失败:标签变量[{0}]在OpcUA Server中未定义!", String.Format("{0}", key2)));return false;}int len = (this.readResult[key2] as ushort[]).Length;ushort[] tagDataBuff = new ushort[len];//Console.WriteLine("newDataBuff");//Console.WriteLine(String.Join(",", newDataBuffer));//Console.WriteLine("index = " + index);//Console.WriteLine("tagDataBuff.Length = " + tagDataBuff.Length);//Array.Copy(newDataBuffer, begin, tagDataBuff, 0, tagDataBuff.Length);int existsMinBegin = this.GetExistsMinBeginByBlock(block.ToString());Array.Copy(newDataBuffer, begin - existsMinBegin, tagDataBuff, 0, tagDataBuff.Length);index += tagDataBuff.Length;//Console.WriteLine("Write " + key2);//Console.WriteLine(String.Join(",", tagDataBuff));//Console.WriteLine("写入标签:" + this.dicTags[key2]);result = this.myOpcHelper.WriteUInt16(this.dicTags[key2], tagDataBuff);if (!result){Console.WriteLine(String.Format("向标签变量[{0}]中写入值失败!", String.Format("{0}", key2)));return false;}else{this.ReadTag(key2);Console.WriteLine("写入...");}//Console.WriteLine("---------------------------------------------------------");} }endregionendregionreturn result;}catch (Exception ex){Console.WriteLine(this.Name + "写入失败:" + ex.Message);return false;} }}/// <summary>/// 关闭方法,断开与设备的连接释放资源/// </summary>public override void Close(){try{this._isClosing = true;System.Threading.Thread.Sleep(this.Main.ReadHz);if (this.innerReadThread != null){this.innerReadThread.Abort();this.innerReadThread = null;} }catch (Exception ex){Console.WriteLine("关闭内部读取OPCUA线程异常:" + ex.Message);}try{if (this.myOpcHelper != null){this.myOpcHelper.Close();this.myOpcHelper = null;this.State = false;this._isOpen = false;} }catch (Exception ex){Console.WriteLine("关于与OPCUA服务连接异常:" + ex.Message);} }endregionregion 辅助方法/// <summary>/// 获取某个数据块标签的最小开始索引/// </summary>/// <param name="block">块号</param>/// <returns>返回数据块标签的最小开始索引</returns>private int GetExistsMinBeginByBlock(string block){int existsMinBegin = 99999; //已存在标签的最小开始索引int existsMaxEnd = 0; //已存在标签的最大结束索引bool isContinue = true; //标签值是否连续string[] existsTags = this.readResult.Keys.ToArray<string>();string[] beginend = null;bool parseResult = false;int begin = 0;int end = 0;foreach (string tag in existsTags){if (tag.StartsWith(String.Format("{0}{1}.", groupNamePrefix, block)) && tag.Contains(".") && tag.Contains("-")){string[] tagname = tag.Split(new char[] { '.' });if (tagname.Length == 2){beginend = tagname[1].Split(new char[] { '-' });if (beginend.Length == 2){parseResult = int.TryParse(beginend[0], out begin);if (parseResult){parseResult = int.TryParse(beginend[1], out end);}region 计算最小开始索引和最大结束索引if (begin < existsMinBegin){existsMinBegin = begin;region 判断标签值是否连续if (existsMaxEnd != 0 && begin != existsMaxEnd + 1){isContinue = false;}endregion}if (end > existsMaxEnd){existsMaxEnd = end;}endregion} }if (parseResult){//} }}return existsMinBegin;}/// <summary>/// 读取标签/// </summary>/// <param name="tagName"></param>private void ReadTag(string tagName){UInt16[] buff = null;if (this.dicTags.ContainsKey(tagName)){if (this.myOpcHelper.ReadUInt16(this.dicTags[tagName], out buff)){//Console.WriteLine("tagName={0}, buff length = {1}", tagName, buff.Length);if (this.readResult.ContainsKey(tagName)){this.readResult[tagName] = buff;}else{this.readResult.Add(tagName, buff);} }else{Console.WriteLine("Mesnac.Equip.OPC.OpcUa.OPCUA.Equip.ReadTag Exception 读取标签:[{0}]失败!", tagName);} }else{Console.WriteLine("Mesnac.Equip.OPC.OpcUa.OPCUA.Equip.ReadTag Exception OPCUA Server中未定义此标签:[{0}]!", tagName);} }/// <summary>/// 内部自动读取方法/// </summary>private void InnerAutoRead(){while (this._isOpen && this._isClosing == false){try{if (this.myOpcHelper == null){this._isClosing = true;this.State = false;return;}lock (this){string[] keys = this.readResult.Keys.ToArray<string>();foreach (string key in keys){this.ReadTag(key);} }System.Threading.Thread.Sleep(this.innerReadRate);}catch (Exception ex){Console.WriteLine("Mesnac.Equip.OPC.OpcUa.OPCUA.Equip.InnerAutoRead Exception : " + ex.Message);} }this.innerReadThread = null;}endregionregion 析构方法~Equip(){this.Close();}endregion} } 代码下载 代码下载 本篇文章为转载内容。原文链接:https://blog.csdn.net/zlbdmm/article/details/96714776。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-10 18:43:00
269
转载
转载文章
...提供的一种高性能网络数据路径。它允许网络包,在进入内核协议栈之前,就进行处理,也可以带来更高的性能。XDP 底层跟我们之前用到的 bcc-tools 一样,都是基于 Linux 内核的 eBPF 机制实现的。 XDP 的原理如下图所示: 你可以看到,XDP 对内核的要求比较高,需要的是 Linux 4.8 以上版本,并且它也不提供缓存队列。基于 XDP 的应用程序通常是专用的网络应用,常见的有 IDS(入侵检测系统)、DDoS 防御、 cilium 容器网络插件等。 总结 C10K 问题的根源,一方面在于系统有限的资源;另一方面,也是更重要的因素,是同步阻塞的 I/O 模型以及轮询的套接字接口,限制了网络事件的处理效率。Linux 2.6 中引入的 epoll ,完美解决了 C10K 的问题,现在的高性能网络方案都基于 epoll。 从 C10K 到 C100K ,可能只需要增加系统的物理资源就可以满足;但从 C100K 到 C1000K ,就不仅仅是增加物理资源就能解决的问题了。这时,就需要多方面的优化工作了,从硬件的中断处理和网络功能卸载、到网络协议栈的文件描述符数量、连接状态跟踪、缓存队列等内核的优化,再到应用程序的工作模型优化,都是考虑的重点。 再进一步,要实现 C10M ,就不只是增加物理资源,或者优化内核和应用程序可以解决的问题了。这时候,就需要用 XDP 的方式,在内核协议栈之前处理网络包;或者用 DPDK 直接跳过网络协议栈,在用户空间通过轮询的方式直接处理网络包。 当然了,实际上,在大多数场景中,我们并不需要单机并发 1000 万的请求。通过调整系统架构,把这些请求分发到多台服务器中来处理,通常是更简单和更容易扩展的方案。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_23864697/article/details/114626793。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-11 18:25:52
260
转载
转载文章
...包括测试自动化、测试数据生成、面向对象软件测试、验证确认过程等 http://www.csr.ncl.ac.uk/index.html 学校里面的一个软件可靠性研究中心,提供有关软件可靠性研究方面的一些信息和资料,对这方面感兴趣的人可以参考 http://www.dcs.shef.ac.uk/research/groups/vt/ 学校里的一个验证和测试研究机构,有一些相关项目和论文可供参考 http://www.esi.es/en/main/ ESI(欧洲软件组织),提供包括CMM评估方面的各种服务 http://www.europeindia.org/cd02/index.htm 一个可靠性研究网站,有可靠性方面的一些资料提供参考 http://www.fortest.org.uk/ 一个测试研究网站,研究包括了静态测试技术(如模型检查、理论证明)和动态测试(如测试自动化、特定缺陷的检查、测试有效性分析等) http://www.grove.co.uk/ 一个有关软件测试和咨询机构的网站,有一些测试方面的课程和资料供下载 http://www.hq.nasa.gov/office/codeq/relpract/prcls-23.htm NASA可靠性设计实践资料 http://www.io.com/~wazmo/ Bret Pettichord的主页,他的一个热点测试页面连接非常有价值,从中可以获得相当大的测试资料,很有价值 http://www.iso.ch/iso/en/ISOOnline.frontpage 国际标准化组织,提供包括ISO标准系统方面的各类参考资料 http://www.isse.gmu.edu/faculty/ofut/classes/ 821-ootest/papers.html 提供面向对象和基于构架的测试方面著作下载,对这方面感兴趣的读者可以参考该网站,肯定有价值 http://www.ivv.nasa.gov/ NASA设立的独立验证和确认机构,该机构提出了软件开发的全面验证和确认,在此可以获得这方面的研究资料 http://www.kaner.com/ 著名的测试专家Cem Kanner的主页,里面有许多关于测试的专题文章,相信对大家都有用。Cem Kanner关于测试的最著名的书要算Testing Software,这本书已成为一个测试人员的标准参考书 http://www.library.cmu.edu/Re-search/Engineer-ingAndSciences/CS+ECE/index.html 卡耐基梅陇大学网上图书馆,在这里你可以获得有关计算机方面各类论文资料,内容极其庞大,是研究软件测试不可获取的资料来源之一 http://www.loadtester.com/ 一个性能测试方面的网站,提供有关性能测试、性能监控等方面的资源,包括论文、论坛以及一些相关链接 http://www.mareinig.ch/mt/index.html 关于软件工程和应用开发领域的各种免费的实践知识、时事信息和资料文件下载,包括了测试方面的内容 http://www.mtsu.ceu/-storm/ 软件测试在线资源,包括提供目前有哪些人在研究测试,测试工具列表连接,测试会议,测试新闻和讨论,软件测试文学(包括各种测试杂志,测试报告),各种测试研究组织等内容 http://www.psqtcomference.com/ 实用软件质量技术和实用软件测试技术国际学术会议宣传网站,每年都会举行两次 http://www.qacity.com/front.htm 测试工程师资源网站,包含各种测试技术及相关资料下载 http://www.qaforums.com/ 关于软件质量保证方面的一个论坛,需要注册 http://www.qaiusa.com/ QAI是一个提供质量保证方面咨询的国际著名机构,提供各种质量和测试方面证书认证 http://www.qualitytree.com/ 一个测试咨询提供商,有一些测试可供下载,有几篇关于缺陷管理方面的文章值得参考 http://www.rational.com/ IBM Rational的官方网站,可以在这里寻找测试方面的工具信息。IBM Rational提供测试方面一系列的工具,比较全面 http://rexblackconsulting.com/Pages/publicat-ions.htm Rex Black的个人主页,有一些测试和测试管理方面的资料可供下载 http://www.riceconsulting.com/ 一个测试咨询提供商,有一些测试资料可供下载,但不多 http://www.satisfice.com/ 包含James Bach关于软件测试和过程方面的很多论文,尤其在启发式测试策略方面值得参考 http://www.satisfice.com/seminars.shtml 一个黑盒软件测试方面的研讨会,主要由测试专家Cem Kanar和James Bach组织,有一些值得下载的资料 http://www.sdmagazine.com/ 软件开发杂志,经常会有一些关于测试方面好的论文资料,同时还包括了项目和过程改进方面的课题,并且定期会有一些关于质量和测试方面的问题讨论 http://www.sei.cmu.edu/ 著名的软件工程组织,承担美国国防部众多软件工程研究项目,在这里你可以获俄各类关于工程质量和测试方面的资料。该网站提供强有力的搜索功能,可以快速检索到你想要的论文资料,并且可以免费下载 http://www.soft.com/Institute/HotList/ 提供了网上软件质量热点连接,包括:专业团体组织连接、教育机构连接、商业咨询公司连接、质量相关技术会议连接、各类测试技术专题连接等 http://www.soft.com/News/QTN-Online/ 质量技术时事,提供有关测试质量方面的一些时事介绍信息,对于关心测试和质量发展的人士来说是很有价值的 http://www.softwaredioxide.com/ 包括软件工程(CMM,CMMI,项目管理)软件测试等方面的资源 http://www.softwareqatest.com/ 软件质量/测试资源中心。该中心提供了常见的有关测试方面的FAQ资料,各质量/测试网站介绍,各质量/测试工具介绍,各质量/策划书籍介绍以及与测试相关的工作网站介绍 http://www.softwaretestinginstitute.com 一个软件测试机构,提供软件质量/测试方面的调查分析,测试计划模板,测试WWW的技术,如何获得测试证书的指导,测试方面书籍介绍,并且提供了一个测试论坛 http://www.sqatester.com/index.htm 一个包含各种测试和质量保证方面的技术网站,提供咨询和培训服务,并有一些测试人员社团组织,特色内容是缺陷处理方面的技术 http://www.sqe.com/ 一个软件质量工程服务性网站,组织软件测试自动化、STAR-EASE、STARWEST等方面的测试学术会议,并提供一些相关信息资料和课程服务 http://www.stickyminds.com/ 提供关于软件测试和质量保证方面的当前发展信息资料,论文等资源 http://www.stqemagazine.com/ 软件策划和质量工程杂志,经常有一些好的论文供下载,不过数量较少,更多地需要通过订购获得,内容还是很有价值的 http://www.tantara.ab.ca/ 软件质量方面的一个咨询网站,有过程改进方面的一些资料提供 http://www.tcse.org/ IEEE的一个软件工程技术委员会,提供技术论文下载,并有一个功能强大的分类下载搜索功能,可以搜索到测试类型、测试管理、 测试分析等各方面资料 http://www.testing.com/ 测试技术专家Brain Marick的主页,包含了Marick 研究的一些资料和论文,该网页提供了测试模式方面的资料,值得研究。总之,如果对测试实践感兴趣,该网站一定不能错过 http://www.testingcenter.com/ 有一些测试方面的课程体系,有一些价值 http://www.testingconferences.com/asiastar/home 著名的AsiaStar测试国际学术会议官方网站,感兴趣的人一定不能错过 http://www.testingstuff.com/ Kerry Zallar的个人主页,提供一些有关培训、工具、会议、论文方面的参考信息 http://www-sqi.cit.gu.edu.au/ 软件质量机构,有一些技术资料可以供下载,包括软件产品质量模型、再工程、软件质量改进等 这里有些网站已经不能使用了. 转载于:https://www.cnblogs.com/mmsky/p/4581975.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/aizongzhuang2281/article/details/101129638。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-08-29 09:17:46
134
转载
转载文章
...《中国劳动统计年鉴》数据显示,我国40-59岁劳动力人口占比逐年上升,他们在面临新兴技术冲击、行业变革的同时,还要应对来自年轻一代的竞争压力。 《人民日报》曾发表一篇深度报道,聚焦中年职场转型与再发展问题,报道指出,在数字化时代背景下,中年人应主动拥抱变化,通过不断学习新技术、新知识,更新自身技能树,并积极参与职业培训和继续教育,拓宽职业发展空间。 此外,据LinkedIn(领英)发布的《中国人才趋势报告》显示,企业对具备跨界能力、持续学习力以及深厚行业经验的中高级人才需求不减反增。这进一步印证了文章中的观点:无论年龄大小,职场人士都需要设立明确目标,增强执行力,并懂得投资自己,通过不断学习实现职业生涯的可持续发展。 同时,心理学专家也强调,保持积极心态是中年人应对职场挑战的关键要素之一。正如美国心理学家卡罗尔·德韦克提出的“成长思维模式”,鼓励人们以开放的态度看待困难和挑战,相信能力可以通过努力得以提升,这对于中年职场人士打破现状、激发潜力具有深远意义。 综上所述,面对日新月异的社会变迁和职场环境,中年群体需树立长期职业规划意识,提高实际行动力,强化个人核心竞争力,并始终保持与时俱进的学习态度和积极进取的心态,以此来应对职业道路上的各种挑战,实现职业生涯的二次腾飞。
2023-06-29 14:16:29
119
转载
转载文章
...ndle'// 定义数据返回结构体(此处我简单定义一个比较常见的后端数据返回结构体,实际使用我们需要按照自己所在的项目开发)interface ResponseData<T = null> {code: string | numberdata: Tsuccess: booleanmessage?: string[key: string]: any}const axiosInstance = axios.create()// 设定响应超时时间axiosInstance.defaults.timeout = 30000// 可以后续根据自己http请求头特殊邀请设定请求头axiosInstance.interceptors.request.use((req: AxiosRequestConfig<any>) => {// 特殊处理,后续如果项目中有全局通传参数,可以在这儿做一些处理return req},error => Promise.reject(error),)// 响应拦截axiosInstance.interceptors.response.use((res: AxiosResponse<any, any>) => {// 数组处理return res},error => Promise.reject(error),)// 通用的请求方法体const axiosHttp = async <T extends Record<string, any> | null>(config: AxiosRequestConfig,desc: string,): Promise<T> => {try {const { data } = await axiosInstance.request<ResponseData<T>>(config)if (data.success) {return data.data}// 如果请求失败统一做提示(此处我没有安装组件库,我简单写个mock例子)ElNotification({title: desc,message: ${data.message || '请求失败,请检查'},})} catch (e: any) {// 统一的错误处理if (e.response && e.response.status) {errorHandle(e.response.status, desc)} else {ElNotification({title: desc,message: '接口异常,请检查',})} }return null as T}// get请求方法封装export const get = async <T = Record<string, any> | null>(url: string, params: Record<string, any>, desc: string) => {const config: AxiosRequestConfig = {method: 'get',url,params,}const data = await axiosHttp<T>(config, desc)return data}// Post请求方法export const post = async <T = Record<string, any> | null>(url: string, data: Record<string, any>, desc: string) => {const config: AxiosRequestConfig = {method: 'post',url,data,}const info = await axiosHttp<T>(config, desc)return info} 请求错误(状态码错误相关提示) import { ElNotification } from 'element-plus'function notificat(message: string, title: string) {ElNotification({title,message,})}/ @description 获取接口定义 @param status {number} 错误状态码 @param desc {string} 接口描述信息/export default function errorHandle(status: number, desc: string) {switch (status) {case 401:notificat('用户登录失败', desc)breakcase 404:notificat('请求不存在', desc)breakcase 500:notificat('服务器错误,请检查服务器', desc)breakdefault:notificat(其他错误${status}, desc)break} } 6、关于vue-router 及 pinia 这两个相对来讲简单一些,会使用vuex状态管理,上手pinia也是很轻松的事儿,只是更简单化了、更方便了,可以参考模板项目里面的用法example,这里附上router及pinia配置方法,路由守卫,大家可以根据项目的要求再添加 import type { RouteRecordRaw } from 'vue-router'import { createRouter, createWebHistory } from 'vue-router'// 配置路由const routes: Array<RouteRecordRaw> = [{path: '/',redirect: '/home',},{name: 'home',path: '/home',component: () => import('page/Home'),},]const router = createRouter({routes,history: createWebHistory(),})export default router 针对与pinia,参考如下: import { createPinia } from 'pinia'export default createPinia() 在入口文件将router和store注入进去 import { createApp } from 'vue'import App from './App'import store from './store/index'import './style/index.css'import './style/index.scss'import 'element-plus/dist/index.css'import router from './router'// 注入全局的storeconst app = createApp(App).use(store).use(router)app.mount('app') 说这些比较枯燥,建议大家去github参考项目说明文档,下载项目,自己过一遍,喜欢的朋友收藏点赞一下,如果喜欢我构建好的项目给个star不丢失,谢谢各位看官的支持。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_37764929/article/details/124860873。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-05 12:27:41
116
转载
转载文章
...给Direct3D的数据做两点改变。 颠倒三角形顶点的顺序,这样系统会从正面以顺时针的方向遍历它们。换句话说,如果顶点是v0,v1,v2,那么以v0,v2,v1的顺序传给Direct3D。 用观察矩阵对世界空间中的z值取反。要做到这一点,将表示观察矩阵的D3DMATRIX结构的_31、_32、_33和_34成员的符号取反。 要得到等同于右手系的效果,可以使用D3DXMatrixPerspectiveRH和D3DXMatrixOrthoRH函数定义投影矩阵。但是,要小心使用D3DXMatrixLookAtRH函数,并相应地颠倒背面剔除的顺序及放置立方体贴图。 虽然左手坐标系和右手坐标系是最为常用的系统,但在三维软件中还使用许多其它坐标系。例如,对三维建模应用程序而言,使用y轴指向或背向观察者的坐标系统并非罕见。在这种情况下,任意轴(x,y或z)的正半轴指向观察者的被定义为右手系。任意轴(x,y或z)的正半轴背向观察者的被定义为左手系。如果正在移植一个基于左手系进行建模的应用程序,z轴向上,那么除了前面的步骤外,还必须旋转所有的顶点数据(译注:如果原来的坐标系为正x轴向里,正y轴向左,正z轴向上,那么传给Direct3D的顶点的x值对应原来的y值,y值对应原来的z值,z值对应原来的x值,亦即旋转顶点数据)。 对三维坐标系统中定义的三维物体执行的最基本操作是变换、旋转和缩放。可以合并这些基本变换以创建一个新的变换矩阵。细节请参阅三维变换。 即使合并相同的变换操作,不同的合并顺序得到的结果是不可交换的——矩阵相乘的顺序很重要。 三维图元 三维图元是组成单个三维实体的顶点集合。三维坐标系统中最简单的图元是点的集合,称为点表。 通常三维图元是多边形。一个多边形是由至少三个顶点描绘的三维形体。最简单的多边形是三角形。Microsoft® Direct3D®使用三角形组成大多数多边形,因为三角形的三个顶点一定是共面的。应用程序可以用三角形组合成大而复杂的多边形及网格(mesh)。 下图显示了一个立方体。立方体的每个面由两个三角形组成。整个三角形的集合构成了一个立方体图元。可以将纹理和材质应用于图元的表面使它们看起来像是实心的。 可以使用三角形创建具有光滑曲面的图元。下图显示了如何用三角形模拟一个球体。应用了材质后,渲染得到的球体看起来是弯曲的。如果使用高洛德着色,结果更是如此。更多信息请参阅高洛德着色。 表面和顶点法向量 网格中的每个面有一个垂直的法向量。该向量的方向由定义顶点的顺序及坐标系统是左手系还是右手系决定。表面法向量从表面上指向正向面那一侧,如果把表面水平放置,正向面朝上,背向面朝下,那么表面法向量为垂直于表面从下方指向上方。在Microsoft® Direct3D®中,只有面的正向是可视的。一个正向面是顶点按照顺时针顺序定义的面。 任何不是正向面的面都是背向面。由于Direct3D不总是渲染背向面,因此背向面要被剔除。如果想要渲染背向面的话,可以改变剔除模式。更多信息请参阅剔除状态。 Direct3D在计算高洛德着色、光照和纹理效果时使用顶点法向。 Direct3D使用顶点法向计算光源和表面间的夹角,对多边形进行高洛德着色。Direct3D计算每个顶点的颜色和亮度值,并对图元表面所覆盖的所有像素点进行插值。Direct3D使用夹角计算光强度,夹角越大,表面得到的光照就越少。 如果正在创建的物体是平直的,可将顶点法向设为与表面垂直,如下图所示。该图定义了一个由两个三角形组成的平直表面。 但是,更可能的情况是物体由三角形带(triangle strips)组成且三角形不共面。要对整个三角形带的三角形平滑着色的一个简单方法是首先计算与顶点相关联的每个多边形表面的表面法向量。可以这样计算顶点法向,使顶点法向与顶点所属的每个表面的法向的夹角相等。但是,对复杂图元来说这种方法可能不够有效。 这种方法如下图所示。图中有两个表面,S1与S2,它们的邻边在上方。S1与S2的法向量用蓝色显示。顶点的法向量用红色显示。顶点法向量与S1表面法向的夹角和顶点法向量与S2表面法向的夹角相同。当对这两个表面进行光照计算和高洛德着色时,得到结果是中间的边被平滑着色,看起来像是弧形的(而不是有棱角的)。 如果顶点法向偏向与它相关联的某个面,那么会导致那个面上的点光强度的增加或减少。下图显示了一个例子。这些面的邻边依然朝上。顶点法向倾向S1,与顶点法向与表面法向有相同的夹角相比,这使顶点法向与光源间的夹角变小。 可以用高洛德着色在三维场景中显示一些有清晰边缘的物体。要达到这个目的,只要在需要产生清晰边缘的表面交线处,把表面法向复制给交线处顶点的法向,如下图所示。 如果使用DrawPrimitive方法渲染场景,要将有锋利边缘的物体定义为三角形表,而非三角形带。当将物体定义为三角形带时,Direct3D会将它作为由多个三角形组成的单个多边形处理。高洛德着色被同时应用于多边形每个表面的内部和表面之间。结果产生表面之间平滑着色的物体。因为三角形表由一系列不相连的三角形面组成,所以Direct3D对多边形每个面的内部使用高洛德着色。但是,没有在表面之间应用高洛德着色。如果三角形表的两个或更多的三角形是相邻的,那么在它们之间看起来会有一条锋利边缘。 另一种可选的方法是在渲染具有锋利边缘的物体时改变到平面着色模式。这在计算上是最有效的方法,但它可能导致场景中的物体不如用高洛德着色渲染的物体真实。 三角形光栅化法则 顶点指定的点经常不能精确地对应到屏幕上的像素。此时,Microsoft® Direct3D®使用三角形光栅化法则决定对于给定三角形使用哪个像素。 三角形光栅化法则 点、线光栅化法则 点精灵光栅化法则 三角形光栅化法则 Direct3D在填充几何图形时使用左上填充约定(top-left filling convention)。这与Microsoft Windows®的图形设备接口(GUI)和OpenGL中的矩形使用的约定相同。Direct3D中,像素的中心是决定点。如果中心在三角形内,那么该像素就是三角形的一部分。像素中心用整数坐标表示。 这里描述的Direct3D使用的三角形光栅化法则不一定适用于所有可用的硬件。测试可以发现这些法则的实现间的细微变化。 下图显示了一个左上角为(0,0),右下角为(5,5)的矩形。正如大家想象的那样,此矩形填充25个像素。矩形的宽度由right减left定义。高度由bottom减top定义。 在左上填充约定中,上表示水平span在垂直方向上的位置,左表示span中的像素在水平方向上的位置。一条边除非是水平的,否则不可能是顶边——一般来说,大多数三角形只有左边或右边。 左上填充约定确定当一个三角形穿过像素的中心时Direct3D采取的动作。下图显示了两个三角形,一个在(0,0),(5,0)和(5,5),另一个在(0,5),(0,0)和(5,5)。在这种情况下第一个三角形得到15个像素(显示为黑色),而第二个得到10个像素(显示为灰色),因为公用边是第一个三角形的左边。 如果应用程序定义一个左上角为(0.5,0.5),右下角为(2.5,4.5)的矩形,那么这个矩形的中心在(1.5,2.5)。当Direct3D光栅化器tessellate这个矩形时,每个像素的中心都毫无异义地分别位于四个三角形中,此时就不需要左上填充约定。下图显示了这种情况。矩形内的像素根据在Direct3D中被哪个三角形包含做了相应的标注。 如果将上例中的矩形移动,使之左上角为(1.0,1.0),右下角为(3.0,5.0),中心为(2.0,3.0),那么Direct3D使用左上角填充约定。这个矩形中大多数的像素跨越两个或更多的三角形的边界,如下图所示。 这两个矩形会影响到相同的像素。 点、线光栅化法则 点和点精灵一样,都被渲染为与屏幕边缘对齐的四边形,因此它们使用与多边形同样的渲染法则。 非抗锯齿线段的渲染法则与GDI使用的法则完全相同。 更多有关抗锯齿线段的渲染,请参阅ID3DXLine。 点精灵光栅化法则 对点精灵和patch图元的渲染,就好像先把图元tessellate成三角形,然后将得到的三角形进行光栅化。更多信息,请参阅点精灵。 矩形 贯穿Microsoft® Direct3D®和Microsoft Windows®编程,都是用术语包围矩形来讨论屏幕上的物体。由于包围矩形的边总是与屏幕的边平行,因此矩形可以用两个点描述,左上角和右下角。当在屏幕上进行位块传输(Blit = Bit block transfer)或命中检测时,大多数应用程序使用RECT结构保存包围矩形的信息。 C++中,RECT结构有如下定义。 typedef struct tagRECT { LONG left; // 这是左上角的x坐标。 LONG top; // 这是左上角的y坐标。 LONG right; // 这是右下角的x坐标。 LONG bottom; // 这是右下角的y坐标。 } RECT, PRECT, NEAR NPRECT, FAR LPRECT; 在上例中,left和top成员是包围矩形左上角的x-和y-坐标。类似地,right和bottom成员组成右下角的坐标。下图直观地显示了这些值。 为了效率、一致性及易用性, Direct3D所有的presentation函数都使用矩形。 三角形插值对象(interpolants) 在渲染时,流水线会贯穿每个三角形的表面进行顶点数据插值。有五种可能的数据类型可以进行插值。顶点数据可以是各种类型的数据,包括(但不限于):漫反射色、镜面反射色、漫反射阿尔法(三角形透明度)、镜面反射阿尔法、雾因子(固定功能流水线从镜面反射的阿尔法分量中取得,可编程顶点流水线则从雾寄存器中取得)。顶点数据通过顶点声明定义。 对一些顶点数据的插值取决于当前的着色模式,如下表所示。 着色模式 描述 平面 在平面着色模式下只对雾因子进行插值。对所有其它的插值对象,整个面都使用三角形第一个顶点的颜色。 高洛德 在所有三个顶点间进行线性插值。 根据不同的颜色模型,对漫反射色和镜面反射色的处理是不同的。在RGB颜色模型中,系统在插值时使用红、绿和蓝颜色分量。 颜色的阿尔法成员作为单独的插值对象对待,因为设备驱动程序可以以两种不同的方法实现透明:使用纹理混合或使用点画法(stippling)。 可以用D3DCAPS9结构的ShadeCaps成员确定设备驱动程序支持何种插值。 向量、顶点和四元数 贯穿Microsoft® Direct3D®,顶点用于描述位置和方向。图元中的每个顶点由指定其位置的向量、颜色、纹理坐标和指定其方向的法向量描述。 四元数给三元素向量的[ x, y, z]值增加了第四个元素。用于三维旋转的方法,除了典型的矩阵以外,四元数是另一种选择。四元数表示三维空间中的一根轴及围绕该轴的一个旋转。例如,一个四元数可能表示轴(1,1,2)和1度的旋转。四元数包含了有价值的信息,但它们真正的威力源自可对它们执行的两种操作:合成和插值。 对四元数进行插值与合成它们类似。两个四元数的合成如下表示: 将两个四元数的合成应用于几何体意味着“把几何体绕axis2轴旋转rotation2角度,然后绕axis1轴旋转rotation1角度”。在这种情况下,Q表示绕单根轴的旋转,该旋转是先后将q2和q1应用于几何体的结果。 使用四元数,应用程序可以计算出一条从一根轴和一个方向到另一根轴和另一个方向的平滑、合理的路径。因此,在q1和q2间插值提供了一个从一个方向变化到另一个方向的简单方法。 当同时使用合成与插值时,四元数提供了一个看似复杂而实际简单的操作几何体的方法。例如,设想我们希望把一个几何体旋转到某个给定方向。我们已经知道希望将它绕axis2轴旋转r2度,然后绕axis1轴旋转r1度,但是我们不知道最终的四元数。通过使用合成,我们可以在几何体上合成两个旋转并得到最终单个的四元数。然后,我们可以在原始四元数和合成的四元数间进行插值,得到两者之间的平滑转换。 Direct3D扩展(D3DX)工具库包含了帮助用户使用四元数的函数。例如,D3DXQuaternionRotationAxis函数给一个定义旋转轴的向量增加一个旋转值,并在由D3DXQUTERNION结构定义的四元数中返回结果。另外,D3DXQuaternionMultiply函数合成四元数,D3DXQuaternionSlerp函数在两个四元数间进行球面线性插值(spherical linear interpolation)。 Direct3D应用程序可以使用下列函数简化对四元数的使用。 D3DXQuaternionBaryCentric D3DXQuaternionConjugate D3DXQuaternionDot D3DXQuaternionExp D3DXQuaternionIdentity D3DXQuaternionInverse D3DXQuaternionIsIdentity D3DXQuaternionLength D3DXQuaternionLengthSq D3DXQuaternionLn D3DXQuaternionMultiply D3DXQuaternionNormalize D3DXQuaternionRotationAxis D3DXQuaternionRotationMatrix D3DXQuaternionRotationYawPitchRoll D3DXQuaternionSlerp D3DXQuaternionSquad D3DXQuaternionToAxisAngle Direct3D应用程序可以使用下列函数简化对三成员向量的使用。 D3DXVec3Add D3DXVec3BaryCentric D3DXVec3CatmullRom D3DXVec3Cross D3DXVec3Dot D3DXVec3Hermite D3DXVec3Length D3DXVec3LengthSq D3DXVec3Lerp D3DXVec3Maximize D3DXVec3Minimize D3DXVec3Normalize D3DXVec3Project D3DXVec3Scale D3DXVec3Subtract D3DXVec3Transform D3DXVec3TransformCoord D3DXVec3TransformNormal D3DXVec3Unproject D3DX工具库提供的数学函数中包含了许多辅助函数,可以简化对二成员和四成员向量的使用 http://www.gesoftfactory.com/developer/3DCS.htm 本篇文章为转载内容。原文链接:https://blog.csdn.net/okvee/article/details/3438011。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-24 12:49:42
271
转载
转载文章
...便: print( 数据类 在Java中没有专门的数据类,常常是通过JavaBean来作为数据类,但在Kotlin中提供了专门的数据类。 Java public 从上面的例子中可以看到,如果要使用数据类,需要手动写相应的setter/getter方法(尽管IDE也可以批量生成),但是从代码阅读的角度来说,在属性较多的情况下,诸多的seeter/getter方法还是不利于代码的阅读和维护。 Kotlin 在Kotlin中,可以通过关键字data来生成数据类: data 即在class关键字之前添加data关键字即可。编译器会根据主构造函数中的参数生成相应的数据类。自动生成setter/getter、toString、hashCode等方法 要声明一个数据类,需要满足: 主构造函数中至少有一个参数 主构造函数中所有参数需要标记为val或var 数据类不能是抽象、开发、密封和内部的 枚举类 枚举类是一种特殊的类,kotlin可以通过enum class关键字定义枚举类。 枚举类可以实现0~N个接口; 枚举类默认继承于kotlin.Enum类(其他类最终父类都是Any),因此kotlin枚举类不能继承类; 非抽象枚举类不能用open修饰符修饰,因此非抽象枚举类不能派生子类; 抽象枚举类不能使用abstract关键字修饰enum class,抽象方法和抽象属性需要使用; 枚举类构造器只能使用private修饰符修饰,若不指定,则默认为private; 枚举类所有实例在第一行显式列出,每个实例之间用逗号隔开,整个声明以分号结尾; 枚举类是特殊的类,也可以定义属性、方法、构造器; 枚举类应该设置成不可变类,即属性值不允许改变,这样更安全; 枚举属性设置成只读属性后,最好在构造器中为枚举类指定初始值,如果在声明时为枚举指定初始值,会导致所有枚举值(或者说枚举对象)的该属性都一样。 定义枚举类 / 定义一个枚举类 / 枚举类实现接口 枚举值分别实现接口的抽象成员 enum 枚举类统一实现接口的抽象成员 enum 分别实现抽象枚举类抽象成员 enum 委托 委托模式 是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承。 Java中委托: interface Printer { Kotlin: interface Printer { by表示 p 将会在 PrintImpl 中内部存储, 并且编译器将自动生成转发给 p 的所有 Printer 的方法。 委托属性 有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括: 延迟属性(lazy properties): 其值只在首次访问时计算; 可观察属性(observable properties): 监听器会收到有关此属性变更的通知; 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。 为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性 。 委托属性的语法是: var : 在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 标准委托: Kotlin 标准库为几种有用的委托提供了工厂方法。 延迟属性 Lazy lazy() 接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。例如: val lazyValue: String 可观察属性 Observable Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值: class User { 如果想拦截赋的新值,并根据你是不是想要这个值来决定是否给属性赋新值,可以使用 vetoable() 取代 observable(),接收的参数和 observable 一样,不过处理程序 返回值是 Boolean 来决定是否采用新值,即在属性被赋新值生效之前 会调用传递给 vetoable 的处理程序。例如: class User { 把属性存在map 中 一个常见的用例是在一个映射(map)里存储属性的值。这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。 例如: class User(map: Map 在上例中,委托属性会从构造函数传入的map中取值(通过字符串键——属性的名称),如果遇到声明的属性名在map 中找不到对应的key 名,或者key 对应的value 值的类型与声明的属性的类型不一致,会抛出异常。 内联函数 当一个函数被声明为inline时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方 inline函数(内联函数)从概念上讲是编译器使用函数实现的真实代码来替换每一次的函数调用,带来的最直接的好处就是节省了函数调用的开销,而缺点就是增加了所生成字节码的尺寸。基于此,在代码量不是很大的情况下,我们是否有必要将所有的函数定义为内联?让我们分两种情况进行说明: 将普通函数定义为内联:众所周知,JVM内部已经实现了内联优化,它会在任何可以通过内联来提升性能的地方将函数调用内联化,并且相对于手动将普通函数定义为内联,通过JVM内联优化所生成的字节码,每个函数的实现只会出现一次,这样在保证减少运行时开销的同时,也没有增加字节码的尺寸;所以我们可以得出结论,对于普通函数,我们没有必要将其声明为内联函数,而是交给JVM自行优化。 将带有lambda参数的函数定义为内联:是的,这种情况下确实可以提高性能;但在使用的过程中,我们会发现它是有诸多限制的,让我们从下面的例子开始展开说明: inline 假如我们这样调用doSomething: fun main(args: Array<String>) { 上面的调用会被编译成: fun main(args: Array<String>) { 从上面编译的结果可以看出,无论doSomething函数还是action参数都被内联了,很棒,那让我们换一种调用方式: fun main(args: Array<String>) { 上面的调用会被编译成: fun main(args: Array<String>) { doSomething函数被内联,而action参数没有被内联,这是因为以函数型变量的形式传递给doSomething的lambda在函数的调用点是不可用的,只有等到doSomething被内联后,该lambda才可以正常使用。 通过上面的例子,我们对lambda表达式何时被内联做一下简单的总结: 当lambda表达式以参数的形式直接传递给内联函数,那么lambda表达式的代码会被直接替换到最终生成的代码中。 当lambda表达式在某个地方被保存起来,然后以变量形式传递给内联函数,那么此时的lambda表达式的代码将不会被内联。 上面对lambda的内联时机进行了讨论,消化片刻后让我们再看最后一个例子: inline 上面的例子是否有问题?是的,编译器会抛出“Illegal usage of inline-parameter”的错误,这是因为Kotlin规定内联函数中的lambda参数只能被直接调用或者传递给另外一个内联函数,除此之外不能作为他用;那我们如果确实想要将某一个lambda传递给一个非内联函数怎么办?我们只需将上述代码这样改造即可: inline 很简单,在不需要内联的lambda参数前加上noinline修饰符就可以了。 以上便是我对内联函数的全部理解,通过掌握该特性的运行机制,相信大家可以做到在正确的时机使用该特性,而非滥用或因恐惧弃而不用。 Kotlin下单例模式 饿汉式实现 //Java实现 懒汉式 //Java实现 上述代码中,我们可以发现在Kotlin实现中,我们让其主构造函数私有化并自定义了其属性访问器,其余内容大同小异。 如果有小伙伴不清楚Kotlin构造函数的使用方式。请点击 - - - 构造函数 不清楚Kotlin的属性与访问器,请点击 - - -属性和字段 线程安全的懒汉式 //Java实现 大家都知道在使用懒汉式会出现线程安全的问题,需要使用使用同步锁,在Kotlin中,如果你需要将方法声明为同步,需要添加@Synchronized注解。 双重校验锁式 //Java实现 哇!小伙伴们惊喜不,感不感动啊。我们居然几行代码就实现了多行的Java代码。其中我们运用到了Kotlin的延迟属性 Lazy。 Lazy内部实现 public 观察上述代码,因为我们传入的mode = LazyThreadSafetyMode.SYNCHRONIZED, 那么会直接走 SynchronizedLazyImpl,我们继续观察SynchronizedLazyImpl。 Lazy接口 SynchronizedLazyImpl实现了Lazy接口,Lazy具体接口如下: public 继续查看SynchronizedLazyImpl,具体实现如下: SynchronizedLazyImpl内部实现 private 通过上述代码,我们发现 SynchronizedLazyImpl 覆盖了Lazy接口的value属性,并且重新了其属性访问器。其具体逻辑与Java的双重检验是类似的。 到里这里其实大家还是肯定有疑问,我这里只是实例化了SynchronizedLazyImpl对象,并没有进行值的获取,它是怎么拿到高阶函数的返回值呢?。这里又涉及到了委托属性。 委托属性语法是:val/var : by 。在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。 而Lazy.kt文件中,声明了Lazy接口的getValue扩展函数。故在最终赋值的时候会调用该方法。 internal.InlineOnly 静态内部类式 //Java实现 静态内部类的实现方式,也没有什么好说的。Kotlin与Java实现基本雷同。 补充 在该篇文章结束后,有很多小伙伴咨询,如何在Kotlin版的Double Check,给单例添加一个属性,这里我给大家提供了一个实现的方式。(不好意思,最近才抽出时间来解决这个问题) class SingletonDemo private constructor( 其中关于?:操作符,如果 ?: 左侧表达式非空,就返回其左侧表达式,否则返回右侧表达式。请注意,当且仅当左侧为空时,才会对右侧表达式求值。 Kotlin 智能类型转换 对于子父类之间的类型转换 先看这样一段 Java 代码 public 尽管在 main 函数中,对 person 这个对象进行了类型判断,但是在使用的时候还是需要强制转换成 Student 类型,这样是不是很不智能? 同样的情况在 Kotlin 中就变得简单多了 fun main(args: Array<String>) { 在 Kotlin 中,只要对类型进行了判断,就可以直接通过父类的对象去调用子类的函数了 安全的类型转换 还是上面的那个例子,如果我们没有进行类型判断,并且直接进行强转,会怎么样呢? public static void main(String[] args) { 结果就只能是 Exception in thread "main" java.lang.ClassCastException 那么在 Kotlin 中是不是会有更好的解决方法呢? val person: Person = Person() 在转换操作符后面添加一个 ?,就不会把程序 crash 掉了,当转化失败的时候,就会返回一个 null 在空类型中的智能转换 需要提前了解 Kotlin 类型安全的相关知识(Kotlin 中的类型安全(对空指针的优化处理)) String? = aString 在定义的时候定义成了有可能为 null,按照之前的写法,我们需要这样写 String? = 但是已经进行了是否为 String 类型的判断,所以就一定 不是 空类型了,也就可以直接输出它的长度了 T.()->Unit 、 ()->Unit 在做kotlin开发中,经常看到一些系统函数里,用函数作为参数 public .()-Unit与()->Unit的区别是我们调用时,在代码块里面写this,的时候,两个this代表的含义不一样,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例。 推荐阅读 对 Kotlin 与 Java 编程语言的思考 使用 Kotlin 做开发一个月后的感想 扫一扫 关注我的公众号如果你想要跟大家分享你的文章,欢迎投稿~ 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39611037/article/details/109984124。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-23 23:56:14
470
转载
转载文章
...以存放基于键值的任意数据,帮助在异常信息中获得更多可以用于调试的数据 HelpLink 这是一个 url,这个 url 里可以提供大量用于说明此异常原因的信息 如果你自己写一个自定义异常类,那么你可以在自定义的异常类中记录更多的信息。然而大多数情况下我们都考虑使用 .NET 中自带的异常类,因此可以充分利用 Exception 类中的已有属性在特殊情况下报告更详细的利于调试的异常信息。 捕捉异常 捕捉异常的基本语法是: try{// 可能引发异常的代码。}catch (FileNotFoundException ex){// 处理一种类型的异常。}catch (IOException ex){// 处理另一种类的异常。} 除此之外,还有 when 关键字用于筛选异常: try{// 可能引发异常的代码。}catch (FileNotFoundException ex) when (Path.GetExtension(ex.FileName) is ".png"){// 处理一种类型的异常,并且此文件扩展名为 .png。}catch (FileNotFoundException ex){// 处理一种类型的异常。} 无论是否有带 when 关键字,都是前面的 catch 块匹配的时候执行匹配的 catch 块而无视后面可能也匹配的 catch 块。 如果 when 块中抛出异常,那么此异常将被忽略,when 中的表达式值视为 false。有个但是,请看:.NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃 - walterlv。 引发异常 引发异常使用 throw 关键字。只是注意如果要重新抛出异常,请使用 throw; 语句或者将原有异常作为内部异常。 创建自定义异常 如果你只是随便在业务上创建一个异常,那么写一个类继承自 Exception 即可: public class MyCustomException : Exception{public string MyCustomProperty { get; }public MyCustomException(string customProperty) => MyCustomProperty = customProperty;} 不过,如果你需要写一些比较通用抽象的异常(用于被继承),或者在底层组件代码中写自定义异常,那么就建议考虑写全异常的所有构造函数,并且加上可序列化: [Serializable]public class InvalidDepartmentException : Exception{public InvalidDepartmentException() : base() { }public InvalidDepartmentException(string message) : base(message) { }public InvalidDepartmentException(string message, Exception innerException) : base(message, innerException) { }// 如果异常需要跨应用程序域、跨进程或者跨计算机抛出,就需要能被序列化。protected InvalidDepartmentException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 在创建自定义异常的时候,建议: 名称以 Exception 结尾 Message 属性的值是一个句子,用于描述异常发生的原因。 提供帮助诊断错误的属性。 尽量写全四个构造函数,前三个方便使用,最后一个用于序列化异常(新的异常类应可序列化)。 finally 异常堆栈跟踪 堆栈跟踪从引发异常的语句开始,到捕获异常的 catch 语句结束。 利用这一点,你可以迅速找到引发异常的那个方法,也能找到是哪个方法中的 catch 捕捉到的这个异常。 异常处理原则 try-catch-finally 我们第一个要了解的异常处理原则是——明确 try catch finally 的用途! try 块中,编写可能会发生异常的代码。 最好的情况是,你只将可能会发生异常的代码放到 try 块中,当然实际应用的时候可能会需要额外放入一些相关代码。但是如果你将多个可能发生异常的代码放到一个 try 块中,那么将来定位问题的时候你就会很抓狂(尤其是多个异常还是一个类别的时候)。 catch 块的作用是用来 “恢复错误” 的,是用来 “恢复错误” 的,是用来 “恢复错误” 的。 如果你在 try 块中先更改了类的状态,随后出了异常,那么最好能将状态改回来——这可以避免这个类型或者应用程序的其他状态出现不一致——这很容易造成应用程序“雪崩”。举一个例子:我们写一个程序有简洁模式和专业模式,在从简洁模式切换到专业模式的时候,我们设置 IsProfessionalMode 为 true,但随后出现了异常导致没有成功切换为专业模式;然而接下来所有的代码在执行时都判断 IsProfessionalMode 为 true 状态不正确,于是执行了一些非预期的操作,甚至可能用到了很多专业模式中才会初始化的类型实例(然而没有完成初始化),产生大量的额外异常;我们说程序雪崩了,多数功能再也无法正常使用了。 当然如果任务已全部完成,仅仅在对外通知的时候出现了异常,那么这个时候不需要恢复状态,因为实际上已经完成了任务。 你可能会有些担心如果我没有任何手段可以恢复错误怎么办?那这个时候就不要处理异常!——如果不知道如何恢复错误,请不要处理异常!让异常交给更上一层的模块处理,或者交给整个应用程序全局异常处理模块进行统一处理(这个后面会讲到)。 另外,异常不能用于在正常执行过程中更改程序的流程。异常只能用于报告和处理错误条件。 finally 块的作用是清理资源。 虽然 .NET 的垃圾回收机制可以在回收类型实例的时候帮助我们回收托管资源(例如 FileStream 类打开的文件),但那个时机不可控。因此我们需要在 finally 块中确保资源可被回收,这样当重新使用这个文件的时候能够立刻使用而不会被占用。 一段异常处理代码中可能没有 catch 块而有 finally 块,这个时候的重点是清理资源,通常也不知道如何正确处理这个错误。 一段异常处理代码中也可能 try 块留空,而只在 finally 里面写代码,这是为了“线程终止”安全考虑。在 .NET Core 中由于不支持线程终止因此可以不用这么写。详情可以参考:.NET/C 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions) - walterlv。 该不该引发异常? 什么情况下该引发异常?答案是——这真的是一个异常情况! 于是,我们可能需要知道什么是“异常情况”。 一个可以参考的判断方法是——判断这件事发生的频率: 如果这件事并不常见,当它发生时确实代表发生了一个错误,那么这件事情就可以认为是异常。 如果这件事经常发生,代码中正常情况就应该处理这件事情,那么这件事情就不应该被认为是异常(而是正常流程的一部分)。 例如这些情况都应该认为是异常: 方法中某个参数不应该传入 null 时但传入了 null 这是开发者使用这个方法时没有遵循此方法的契约导致的,让开发者改变调用此方法的代码就可以完全避免这件事情发生 而下面这些情况则不应该认为是异常: 用户输入了一串字符,你需要将这串字符转换为数字 用户输入的内容本身就千奇百怪,出现非数字的输入再正常不过了,对非数字的处理本就应该成为正常流程的一部分 对于这些不应该认为是异常的情况,编写的代码就应该尽可能避免异常。 有两种方法来避免异常: 先判断再使用。 例如读取文件之前,先判断文件是否存在;例如读取文件流时先判断是否已到达文件末尾。 如果提前判断的成本过高,可采用 TryDo 模式来完成,例如字符串转数字中的 TryParse 方法,字典中的 TryGetValue 方法。 对极为常见的错误案例返回 null(或默认值),而不是引发异常。极其常见的错误案例可被视为常规控制流。通过在这些情况下返回 NULL(或默认值),可最大程度地减小对应用的性能产生的影响。(后面会专门说 null) 而当存在下列一种或多种情况时,应引发异常: 方法无法完成其定义的功能。 根据对象的状态,对某个对象进行不适当的调用。 请勿有意从自己的源代码中引发 System.Exception、System.SystemException、System.NullReferenceException 或 System.IndexOutOfRangeException。 该不该捕获异常? 在前面 try-catch-finally 小节中,我们提到了 catch 块中应该写哪些代码,那里其实已经说明了哪些情况下应该处理异常,哪些情况下不应该处理异常。一句总结性的话是——如果知道如何从错误中恢复,那么就捕获并处理异常,否则交给更上层的业务去捕获异常;如果所有层都不知道如何处理异常,就交给全局异常处理模块进行处理。 应用程序全局处理异常 对于 .NET 程序,无论是 .NET Framework 还是 .NET Core,都有下面这三个可以全局处理的异常。这三个都是事件,可以自行监听。 AppDomain.UnhandledException 应用程序域未处理的异常,任何线程中未处理掉的异常都会进入此事件中 当这里能够收到事件,意味着应用程序现在频临崩溃的边缘(从设计上讲,都到这里了,也再没有任何代码能够使得程序从错误中恢复了) 不过也可以配置 legacyUnhandledExceptionPolicy 防止后台线程抛出的异常让程序崩溃退出 建议在这个事件中记录崩溃日志,然后对应用程序进行最后的拯救恢复操作(例如保存用户的文档数据) AppDomain.FirstChanceException 应用程序域中的第一次机会异常 我们前面说过,一个异常被捕获时,其堆栈信息将包含从 throw 块到 catch 块之间的所有帧,而在第一次机会异常事件中,只是刚刚 throw 出来,还没有被任何 catch 块捕捉,因此在这个事件中堆栈信息永远只会包含一帧(不过可以稍微变通一下在第一次机会异常 FirstChanceException 中获取比较完整的异常堆栈) 注意第一次机会异常事件即便异常会被 catch 也会引发,因为它引发在 catch 之前 不要认为异常已经被 catch 就万事大吉可以无视这个事件了。前面我们说过异常仅在真的是异常的情况才应该引发,因此如果这个事件中引发了异常,通常也真的意味着发生了错误(差别只是我们能否从错误中恢复而已)。如果你经常在正常的操作中发现可以通过此事件监听到第一次机会异常,那么一定是应用程序或框架中的异常设计出了问题(可能把正常应该处理的流程当作了异常,可能内部实现代码错误,可能出现了使用错误),这种情况一定是要改代码修 Bug 的。而一些被认为是异常的情况下收到此事件则是正常的。 TaskScheduler.UnobservedTaskException 在使用 async / await 关键字编写异步代码的时候,如果一直有 await 传递,那么异常始终可以被处理到;但中间有异步任务没有 await 导致异常没有被传递的时候,就会引发此事件。 如果在此事件中监听到异常,通常意味着代码中出现了不正确的 async / await 的使用(要么应该修改实现避免异常,要么应该正确处理异常并从中恢复错误) 对于 GUI 应用程序,还可以监听 UI 线程上专属的全局异常: WPF:Application.DispatcherUnhandledException 或者 Dispatcher.UnhandledException Windows Forms:Application.ThreadException 关于这些全局异常的处理方式和示例代码,可以参阅博客: WPF UnhandledException - Iron 的博客 - CSDN博客 抛出哪些异常? 任何情况下都不应该抛出这些异常: 过于抽象,以至于无法表明其含义 Exception 这可是顶级基类,这都抛出来了,使用者再也无法正确地处理此异常了 SystemException 这是各种异常的基类,本身并没有明确的意义 ApplicationException 这是各种异常的基类,本身并没有明确的意义 由 CLR 引发的异常 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 .NET 设计失误 FormatException 因为当它抛出来时无法准确描述到底什么错了 首先是你自己不应该抛出这样的异常。其次,你如果在运行中捕获到了上面这些异常,那么代码一定是写得有问题。 如果是捕获到了上面 CLR 的异常,那么有两种可能: 你的代码编写错误(例如本该判空的代码没有判空,又如索引数组超出界限) 你使用到的别人写的代码编写错误(那你就需要找到它改正,或者如果开源就去开源社区中修复吧) 而一旦捕获到了上面其他种类的异常,那就找到抛这个异常的人,然后对它一帧狂扁即可。 其他的异常则是可以抛出的,只要你可以准确地表明错误原因。 另外,尽量不要考虑抛出聚合异常 AggregateException,而是优先使用 ExceptionDispatchInfo 抛出其内部异常。详见:使用 ExceptionDispatchInfo 捕捉并重新抛出异常 - walterlv。 异常的分类 在 该不该引发异常 小节中我们说到一个异常会被引发,是因为某个方法声称的任务没有成功完成(失败),而失败的原因有四种: 方法的使用者用错了(没有按照方法的契约使用) 方法的执行代码写错了 方法执行时所在的环境不符合预期 简单说来,就是:使用错误,实现错误、环境错误。 使用错误: ArgumentException 表示参数使用错了 ArgumentNullException 表示参数不应该传入 null ArgumentOutOfRangeException 表示参数中的序号超出了范围 InvalidEnumArgumentException 表示参数中的枚举值不正确 InvalidOperationException 表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态) ObjectDisposedException 表示对象已经 Dispose 过了,不能再使用了 NotSupportedException 表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持) PlatformNotSupportedException 表示在此平台下不支持(如果程序跨平台的话) NotImplementedException 表示此功能尚在开发中,暂时请勿使用 实现错误: 前面由 CLR 抛出的异常代码主要都是实现错误 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 环境错误: IOException 下的各种子类 Win32Exception 下的各种子类 …… 另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。 其他 一些常见异常的原因和解决方法 在平时的开发当中,你可能会遇到这样一些异常,它不像是自己代码中抛出的那些常见的异常,但也不包含我们自己的异常堆栈。 这里介绍一些常见这些异常的原因和解决办法。 AccessViolationException 当出现此异常时,说明非托管内存中发生了错误。如果要解决问题,需要从非托管代码中着手调查。 这个异常是访问了不允许的内存时引发的。在原因上会类似于托管中的 NullReferenceException。 参考资料 Handling and throwing exceptions in .NET - Microsoft Docs Exceptions and Exception Handling - C Programming Guide - Microsoft Docs 我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。 如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。 本篇文章为转载内容。原文链接:https://blog.csdn.net/WPwalter/article/details/94610764。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-13 13:38:26
59
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
env -i command
- 在干净的环境变量状态下执行命令。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"