加入 Gitee与超过 1000 万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)免费加入已有帐号? 立即登录master`
});
$branchesDropdown.append(html);
$('.protected-branch-popup').popup()
},
complete: function () {
concurrentRequestLock = false;
}
});
}
java-notes/基础知识.md
注意点:
强类型:必须符合规定才可以进行使用
java分为两大数据类型:
序号
数据类型
大小/位
封装类
默认值
可表示数据范围
1
byte(位)
8
Byte
0
-128~127
2
short(短整数)
16
Short
0
-32768~32767
3
int(整数)
32
Integer
0
-2147483648~2147483647
4
long(长整数)
64
Long
0
-9223372036854775808~9223372036854775807
5
float(单精度)
32
Float
0.0
1.4E-45~3.4028235E38
6
double(双精度)
64
Double
0.0
4.9E-324~1.7976931348623157E308
7
char(字符)
16
Character
空
0~65535
8
boolean
8
Boolean
flase
true或false
引用类型
类
接口
数组
字节:1bit是1位、最小的存储单位
1字节(byte)是8位 1B
1B=8b
==所有的包装类都是final修饰的,和String类型一致,都是不可改变的==
整数拓展
二进制 0b开头
八进制 0开头
十进制
十六进制 0x开头 09 AF
浮点数扩展
银行业务怎么表示
BigDecimal 数学工具类(精确)
最好完全避免使用浮点数进行比较
float 表示的数 有限、离散、舍入误差、大约、接近不等于
double
字符扩展
字符的本质还是数字
编码
unicode编码(97=a
65=A)
System,out. printin(int)c2);/强制转换
U0000 ~ UFFFF
char c3=\u0061
System. out. println(c3);//a
\u 进行转义
转义字符
\t 制表符
\n 换行
低---------------------------------------------------------高
byte、short、char---int---long---float---double
强制类型转换 高----低 可能会出现内存溢出或者精度问题
自动类型转换 低----高
变量方法域:
类变量:static
使用范围,所有类以类中方法都可使用
实例变量:
在类中声明,在类中使用,从属于对象,如果不进行赋初值的话,就会输出默认值
不赋初值,使用的话,需要进行实例化使用
方法变量:
在方法中声明,在方法中使用,必须声明并且初始化值
final修饰,一般方法名使用大写来定义,规范代码
命名规范:驼峰命名法
短路运算:
&&(逻辑与):两个都真才真
(逻辑或):两个都假才假
!(逻辑非):非真即假
位运算:
A:0011 1100
B:0000 1101
A&B:0000 1100
A|B:0011 1101
A^B(亦或,相同为0,不相同为1):0011 0001
~B:1111 0010
速度比较快,计算机底层实现
<<: *2
右移 /2
>>> 无符号右移,左边空出的位以0填充
关系运算符
instanceof:
三元运算符
x ? y : z
优先级
推荐使用()使代码更加清晰
java提供包机制,更好的管理java类,用于区别类名的命名空间
一般包名:使用公司域名倒置
关键字:import
参数:
@author 作者信息
@version 版本号
@since 所需jdk版本号
@param 参数名
@return 返回值
@throws 异常抛出
命令行中 javadoc (参数) xx.java
基本语法:
Scanner s=new Scanner(System.in);
.....
s.close();
涉及到io流的都需要进行关闭,否则会占用内存
通过Scanner的next()或者是nextLine()来获取用户输入的字符串
在读取前,我们会通过hasNext()或者hasNextLine()判断是否还有输入的数据
next()和nextLine()
next():以空格为结束符,不能获取带有空格的字符串
nextLine():以回车为结束符,可以获取带有空格的字符串,读取一行!
java最基本的结构就是顺序结构
顺序结构是最简单的算法结构
if单选择结构 if...
if双选择结构 if...else
if多选择结构 if...else if...else
嵌套if结构 if...if...else
switch多选择结构:匹配一个具体的值
变量类型:byte、short、char、int
String(JDK SE7之后)
case穿透现象(未添加break导致后面的语句被执行)
反编译:java-------class----------反编译(IDEA)
switch比较字符串,反编译为字符串对应的hashcode,然后比较hashcode码来比较是否相等
while循环
while(布尔表达式),一般条件是一个可以让循环停下来的条件,当不满足条件的时候就停止
while(true)死循环
do...while循环
至少执行一次,先执行,后判断
for循环
for(初始化值;条件判断(满足执行,不满足退出);迭代){}
IDEA里可以使用100.for 自动生成for循环
for(;;){}
//死循环
增强型for循环
输出数据的值
for(数据类型
参数:数组名称){}
break
使用break控制任何循环,用于强制退出循环,不执行break之后的语句
continue
用在循环语句体中,用于终止某次循环,跳过循环中尚未执行的部分,进行下一次循环
goto
使用标签的形式
public static void main(string[] args)(
//打101-150之间历所有的质数质数是指在大的自然数中,除1和它本身以外不再有其他因数的自然数
int count = 0;
//不建议使用!
outer:for (int i=101; 1<150; 1++){
for (int j= 2; j<1/2; j++){
lf( i % j == 0)
continue outer;
}
System. out. print(i+"");
}public class sanjiao {
public static void main(String[] args) {
for (int i = 1; i <=5; i++) {
for (int j = 5; j >= i; j--) {
System.out.print(" ");
}
for (int j = 1; j <=i ; j++) {
System.out.print("*");
}
for (int j = 1; j < i; j++) {
System.out.print("*");
}
System.out.println();
}
}
打印结果:
*
***
*****
*******
*********
java方法是语句的集合,包含于类或者对象中,在程序中创建,在其他地方被调用
设计方法的原则:
最好保持方法的原子性。即一个方法只完成一个功能,有利于后期的扩展
方法包含一个方法头和一个方法体
方法头:
修饰符
返回值
方法名
参数类型
参数
形式参数:方法体上用于接收数据的参数
实际参数:调用方法时,传递给方法的参数
方法体:
return:可以返回值给调用者,也可以用于结束方法(return 0)
java支持两种方法调用的形式
当方法有返回值:通常通过参数去接收数值
当方法无返回值:通常都是一条输出语句。例如:System. out. println ("Hello, kuangshen ");
概念:
重载就是在一个类中,有相同的函数名称,但是形参不一样的函数
规则
名称必须相同
参数列表不同(个数不同,或者类型不同,或者顺序不同)
理论
方法名称相同时,编译器会通过调用方法的参数个数或者类型等逐个匹配,未找到就报错!
通过找到当前类所在的包的根路径,然后运行对应的函数,传递参数执行!
同一种类型的多个参数,同时声明
JDK1.5之后,java支持传递同类型的可变参数给一个方法
在方法的参数声明中,给参数类型后加一个...
一个方法只能指定一个可变参数,同时必须要是最后一个参数
可以使用数组的形式进行传递!
递归结构分为两部分:
递归头:什么时候不调用自身方法,如果没有头,将会写入死循环!
递归体:什么时候需要调用自身方法
//问题:阶乘
1!=1
2!=2*1
3!=3*2*1
...
5!=5*4*3*2*1
public static int f(int n){
if(n==1){
return 1;
}else{
return n*f(n-1);
}
}
只适合计数比较小的计算,因为会使用大量的函数,在内存中堆栈,导致内存溢出!递归的深度越大,内存就会负载越大!
相同类型数据的有序集合
通过数组的下标来访问数组中的元素
首先必须声明数组才能使用数组
声明方式
dataType[] arrayName 首选方式! java中默认在参数类型后加[]表示数组
dataType arrayName[] 可以使用,但不推荐!
int[] array=new int[10] 声明一个数组并为数组开辟10个空间,创建并赋值!
获取数组的长度:array.length
长度确定,一旦创建,数组的大小是不可以改变的
元素必须是相同类型的
元素可以是任意数据类型:基本数据类型和引用数据类型
数组变量属于引用类型,因此数组也可以看成对象,对象在java中是存储在堆中
java内存:
堆
存放new的对象和数组
可以被所有的线程共享,不会被别的对象引用
栈
存放基本数据类型(会包含这个基本类型的具体数据)
引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区
可以被所有线程共享
包含了所有class和static变量
静态初始化:直接进行声明并赋值
int[] a={1,2,3,4}
Man[] mans={new Man(1,1),new Man(2,2)}
动态初始化:先声明,自己赋值
int[] a =new int[2];
a[0]=1;
a[1]=2;
默认初始化
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。默认值是0;
下标的合法区间:[0,array.length-1] ,如果越界的话就会出现ArrayIndexOutofBounds异常
普通for循环:使用下标获取值
For-Each使用:使用增强for循环输出数组
数组作方法入参:作为参数进行使用
数组作返回值:返回一个数组,调用者通过接收,然后输出
多维数组可以看成数组的数组
定义:int[] [] a=new int[2] [5];
上述这种可以看成2行5列的数组
int [][] array={{1,2},{2,3},{3,4},{4,5}};
for(int i=0;i
array[j]){
temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
}
//
改进版
// 添加一个flag变量,如果array[j+1]=array[j],那么这时就可以不需要进行比较,增加效率
public static int[] sort(int[] array){
int temp=0;
boolean flag=false;
for(int i=0;iarray[j]){
temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
flag=true;
}
if(flag==false){
break;
}
}
}
}
是一种数据结构,是一种数组的压缩形式
介绍
当一个数组中大部分元素都是0或者是相同元素的话或者无效元素远大于有效元素,可以使用稀疏数组来保存数组
处理方式
记录几行几列具体的值
将不同值元素的行列和值的信息记录在一个数组中来使其压缩
public static void main(String[] args) {
/**
* 初始化二维数组
*
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 1 0 0 0 0 0 0 0 0
*
0 0 0 0 2 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
*/
//初始化原数组
int[][] array = new int[11][11];
array[1][2] = 1;
array[2][4] = 2;
for(int[] row : array){
for(int item : row){
System.out.printf("%d\t",item);
}
}
System.out.println("---------> 二维数组转稀疏数组");
/**
* 稀疏数组
*
*
11 11 2
*
1
2
1
*
2
4
2
*
*/
//得到非0数据数
int sum = 0;
for (int i = 0;i<11;i++){
for(int j = 0;j<11;j++){
if(array[i][j] != 0){
sum++;
}
}
}
//创建稀疏数组
int[][] sparseArray = new int[sum+1][3];
//给稀疏数组赋值
sparseArray[0][0] = 11;
sparseArray[0][1] = 11;
sparseArray[0][2] = sum;
//将非0的数放入稀疏数组
//count:标识第几个非0数
int count = 0;
for (int i = 0;i<11;i++){
for(int j = 0;j<11;j++){
if(array[i][j] != 0){
count++;
sparseArray[count][0] = i;
sparseArray[count][1] = j;
sparseArray[count][2] = array[i][j];
}
}
}
//遍历稀疏数组
for(int i = 0;i稀疏数组转回原始数组");
/**
* 恢复的二维数组
*
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 1 0 0 0 0 0 0 0 0
*
0 0 0 0 2 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
0 0 0 0 0 0 0 0 0 0 0
*
*/
int[][] oldArray = new int[sparseArray[0][0]][sparseArray[0][1]];
//将原来非0的数填充回去
for(int i = 1;i<=count;i++){
oldArray[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
}
//遍历刚转回的原始数组
for(int[] row : oldArray){
for(int item : row){
System.out.printf("%d\t",item);
}
}
}
以类的方式组织代码,以对象的形式封装数据
static修饰,直接通过类.方法调用
未修饰,需要通过实例化调用函数
值传递
对于基本数据类型而言,不可改变的
引用传递
一般是针对对象型变量而言的,是可以进行改变值的
三大特性
封装
继承
多态
使用new 关键字创建对象,new对象来调用构造方法,构造方法用来初始化值
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩特点:
必须和类的名字相同
必须没有返回类型也不能写void
无参构造函数,默认类中有无参构造方法
public person(){}
有参构造函数,一旦定义了有参构造,无参构造必须显式定义
public person (String name){
this.name=name;
//this,代表当前类的,将本类中的name赋值为传参过来的name
}
栈中主要存放一些方法及变量引用
堆中有一个特殊的区域是方法区
加载主类,引用类,然后在栈中加载main方法,new的时候在栈中生成一个引用变量名,在堆中开辟一个内存空间用于指向
高内聚,低耦合
禁止直接访问对象中实际数据,而应通过操作接口来访问,成为信息隐藏
属性私有,get/set
特点
提高程序的安全性,保护数据,可以在set中做一些数据保护
隐藏代码的实现细节
统一接口
系统可维护增加了
Object类
在java中所有的类的默认直接或间接继承Object类
java中只要单继承没有多继承!!!
super和this关键字
子类调用父类的方法使用关键字:super
1、调用父类的构造器必须放在子类构造器的第一行。
2、父类的无参一般显式的声明出来,如果没有声明,子类无法声明无参构造
3、执行顺序:父类构造器>子类构造器
类调用本身的属性或者方法,可以使用关键字:this
super和this不能同时使用
原因:因为super和this都需要在第一行显示
重写
标识符:@override
需要有继承关系,子类重写父类的方法!
1、方法名必须相同
2、参数列表必须相同(重载是不相同的)
3、修饰符范围,可以扩大不可以缩小 public>protected>default>private
4、抛出的异常,范围可以缩小,但不能扩大:Exception--->ClassNotFoundException
重写:子类的方法和父类必须要一致,方法体不同!
理解概念:
同一种行为具有多个不同表现形式的能力
父类引用指向子类对象,子类对象重写父类对应的方法,实现扩展
该类型只能调用父类中定义的方法及变量
==如果子类重写了父类的一个方法,那么在调用这个方法的时候,将会调用子类的方法,实现动态的绑定,动态调用==
==变量不能被重写覆盖!==
实现多态的三种体现
重写父类的方法、抽象类、接口
一个对象的实际类型是确定的(new Student() )
但是他的指向是不确定的 子类的对象指向父类的引用
//能调用的方法都是子类的方法或者继承父类的方法
Student s1=new Student()
//引用是指向父类,但是不能调用子类独有的方法
Person p1=new Student();
Object o1=new Student();
object类是所有类的直接或间接父类
//对象能执行什么方法,主要看左边的引用和右边的实际类型关系不大
多态注意事项:
1、多态是方法的多态,属性没有多态
2、存在条件:继承关系,方法需要重写,父类引用指向子类对象
(1)static:属于类,不属于实例
(2)final:常量
(3)private方法
这些是没有进行重写的
通过instanceof来判断类型之间是否有关系
Object --> Person --> Teacher
Object --> Person --> Student
student和Teacher是没有关系的,是同级的
System.out.print(student instanceof teacher);//true
System.out.print(student instanceof Person);//true
System.out.print(student instanceof Object);//true
把子类转换为父类,向上转型
把父类转换成子类,向下转型,需要强制转换(可能丢失精度)
静态代码块:当类加载的时候,就会执行一次,永久性的
构造代码块:{....}依托于构造函数,并且优先于构造函数执行,只要创建一次对象,构造代码块都会执行一次!可以用来做统计创建对象次数的操作
构造函数:函数名和类名相同,用来初始化参数,通过new来调用
静态属性、静态方法,可以直接通过类.属性名,类.方法名来进行调用
public class Person{
2、
{
//匿名代码块
当对象person被加载之后就会被执行,同时它是在构造函数之前执行
}
1、
static{
//静态代码块
当类加载的时候执行,同时永久性只执行一次
}
3、
public Person(){
}
}
静态导入包:
import static java.lang.Math.random
调用的时候可以直接
System.out.print(random());这样子调用
关键字:abstract
抽象类中的方法,只有方法名,没有方法体
抽象类需要被继承(extend),由子类进行实现,除非子类也是个抽象类
注意点:
1、不能new这个抽象类,只能靠子类去实现:约束
2、抽象类中可以有普通方法
3、抽象方法必须存在抽象类中
思考题:
抽象类中有构造函数吗?
抽象类中可以有构造函数,因为抽象类中含有普通变量 ,构造方法用来初始化这些变量
关键字:interface
接口中的方法默认都是public abstract修饰的
接口支持多继承(伪继承),可以实现多个类,通过 implements接口实现,同时要实现接口的类就要实现接口中所有的方法
接口中一般都是写方法约束,如果写常量的话,默认是public static final修饰,是一个静态常量
成员内部类
Out.inner in=outer.new inner();
可以获取外部类的私有属性,方法
静态内部类
局部内部类
匿名内部类
Exception:异常问题
异常简单分类:
非运行时异常
运行时异常
错误ERROR
由jvm生成并抛出的
异常处理5个关键字
try catch finally throw throws
假设捕获多个异常,需要从小到大进行捕获
进行捕获异常的快捷键:ctrl+Alt+T
捕获的好处:捕获之后可以继续正常执行,不会中止
throw:在方法内主动抛出异常
throws:在方法声明的时候向上抛出异常
e.printStackTrace();在控制台输出异常信息
用户自己定义一个类,继承Exception
经验总结:
在多重catch块后面,可以加一个catch(Exception)来处理可能漏掉的异常
尽量使用finally语句去释放一些占用的资源
";
}
$complainCommentType.find('.menu').html(result);
}
});
$complainCommentType.dropdown({showOnFocus: false});
initedCommentsType = true;
}
}
$complainCommentType.on('click', function() {
$complaintCommentsModal.modal({
autofocus: false,
onApprove: function() {
return false;
},
onHidden: function() {
restoreCommonentDefault();
}
}).modal('show');
});
$complaintCommentsContent.on('change keyup', function(e) {
var content = $(this).val();
if ($.trim(content).length > 0 && $complainCommentType.dropdown('get value').length > 0 ) {
$complaintCommentBtn.removeClass('disabled');
return;
}
$complaintCommentBtn.addClass('disabled');
});
$complainCommentType.dropdown({
showOnFocus: false,
onChange: function(value, text, $selectedItem) {
if (value.length > 0 && $.trim($complaintCommentsContent.val()).length > 0) {
$complaintCommentBtn.removeClass('disabled');
return
}
$complaintCommentBtn.addClass('disabled');
}
});
function restoreCommonentDefault() {
$complainCommentType.dropdown('restore defaults');
$complaintCommentsContent.val('');
$('.exceeded-size-tip').text('').hide();
$complaintModalTip.text('').hide();
setTimeout(function() {
setCommentSendTip(false);
}, 1500);
}
$complaintCommentBtn.on('click',function(e){
var reason = $complaintCommentsContent.val();
var appealableId = $('#landing-comments-complaint-modal').attr('data-id');
if (complaintSending) {
return;
}
var appealType = $complainCommentType.dropdown('get value');
var formData = new FormData();
formData.append('appeal_type_id', appealType);
formData.append('reason', reason);
formData.append('appeal_type','Note');
formData.append('target_id',appealableId);
$.ajax({
type: 'POST',
url: "/appeals",
cache: false,
contentType: false,
processData: false,
data: formData,
beforeSend: function() {
setCommentSendStatus(true);
},
success: function(res) {
if (res.status == 200) {
setCommentSendTip(true);
setTimeout(function() {
$complaintCommentsModal.modal('hide');
restoreCommonentDefault();
}, 3000);
}
setCommentSendStatus(false);
},
error: function(err) {
showCommonTips(err.responseJSON.message, 'error');
setCommentSendStatus(false);
}
})
});
function showCommonTips(text, type) {
$complaintModalTip.text(text).show();
if (type == 'error') {
$complaintModalTip.removeClass('success').addClass('error');
} else {
$complaintModalTip.removeClass('error').addClass('success');
}
}
function setCommentSendStatus(value) {
complaintSending = value;
if (complaintSending) {
$complaintCommentBtn.addClass('loading');
$complaintCommentsContent.attr('readonly', true);
$complainCommentType.attr('readonly', true);
} else {
$complaintCommentBtn.removeClass('loading');
$complaintCommentsContent.attr('readonly', false);
$complainCommentType.attr('readonly', false);
}
}
function setCommentSendTip(value) {
if (value) {
$('.appeal-success-tip').removeClass('hide');
$('.appeal-tip').addClass('hide');
$('.appeal-form').addClass('hide');
$('#landing-comments-complaint-modal .actions').addClass('hide');
} else {
$('.appeal-success-tip').addClass('hide');
$('.appeal-tip').removeClass('hide');
$('.appeal-form').removeClass('hide');
$('#landing-comments-complaint-modal .actions').removeClass('hide');
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
}
BFF什么意思(bff字符什么意思)?如果你对这个不了解,来看看!
昨天晚上被新闻刷屏了的区块链,它到底是个啥,下面一起来看看本站小编中科院物理所给大家精心整理的答案,希望对您有帮助
BFF什么意思(bff字符什么意思)1
相信不少人昨天晚上被突如其来的区块链刷了个屏。
中共中央政治局10月24日下午就区块链技术发展现状和趋势进行第十八次集体学习。中共中央总书记习近平在主持学习时强调,区块链技术的集成应用在新的技术革新和产业变革中起着重要作用。我们要把区块链作为核心技术自主创新的重要突破口,明确主攻方向,加大投入力度,着力攻克一批关键核心技术,加快推动区块链技术和产业创新发展。
公众号:人民日报习近平:把区块链作为核心技术自主创新重要突破口
可能有一些读者有疑问,“区块链”到底是什么?为什么会这么受重视呢?今天,小编就来带大家一起了解一下。
引言
首先,区块链是一种电子化数据的存储方法。数据是以区块的形式出现的,想象一下有很多存储着数字化数据的区块。这些区块都链接在了一起,为其内部数据赋予了不可变性。当一个数据块被链接到了这条链上,其内部数据就再也无法更改了。一旦某个区块被添加到了链上,里面的数据对任何人都是公开可见的。这项技术具有非凡的革新意义,可以用来记录我们能想到的几乎所有数据(例如,产权、身份、余额、病历等等),同时不存在被篡改记录的风险。假设我买了一套房子,把产权证拍照上传到了区块链上,我就可以证明我在那个时刻享有这套房产的所有权。也就是说,区块链是一种存储数据且保证数据不被篡改的方法。这听上去不错,不过随之而来的问题是:我们是怎么实现这样的技术的?
(校对注:吹毛求疵来说,这一段表述并非无懈可击,但姑且可以这么先记着。)
第一阶——交易数据
好吧,我们先拿比特币区块链做个例子。比特币区块链是现存历史最悠久的区块链。在比特币区块链上,每个区块的大小在 1 MB 左右。至截稿日,这条链上已经累积了 52.5 万个区块,链上存储的数据总量约为 52.5 万 MB 。(校对注:其实远远没有 52.5万 MB,因为在早期,很多区块都没有打满 1MB。)
比特币区块链上仅存储比特币的交易数据 。它就像是一个庞大的交易记录库,可追溯至第一笔比特币交易。在本文中,我们假设有一条存储交易数据的区块链,就像比特币区块链那样。
第二阶——(通过哈希运算)链接区块
想象有三个存储着交易数据的区块(如图一所示)。
这三个区块内都存有一些交易数据。这没什么特别的。就好比是三个独立的 word 文档,里面描述了交易的内容和余额变化情况。文档 1 会按照时间顺序从第一笔交易开始记录,直到数据量达到 1 MB 为止,之后的交易会记录在文档 2 中,直到数据量达到 1 MB
为止,以此类推。这些文档就是数据块。它们一个接着一个联系(链接)在一起。为此,每个区块会根据其内部数据串生成一个特殊的(数字)签名。如果这个区块中的数据发生任何变动,即使只改变了一个数字,这个区块的签名也会发生改变。这是如何实现的?欲知详情,请阅读步骤三中的 哈希运算部分。
(校对注:如上文所述,实际情形中的区块并不是个个都接近区块大小的上限,实际数据大小要看把区块打包上链的矿工在区块中记录了多少交易,而他们并不会等到有了 1MB 交易数据才开始动手。实际情形见下文)
假设区块 1 中记录了两笔交易,分别是交易 1 和交易 2 。这两笔交易的总数据量达到了 1 MB (实际上一个区块中包含的交易笔数远不止这点)。根据这个区块内的数据串会生成一个签名。假设这个签名是 “X32” 。如下图所示:
请记住,即使区块 1 中存储的数据改动了一个数字,也会得到一个完全 不同的签名!只要将区块 1 的签名添加到区块 2 中,就可以将区块 1 的数据与区块 2 关联起来。区块 1 的签名也包含在区块 2 的数据串内,因此这个签名与区块 2 中的其它数据一样,成了区块 2 签名的数据基础。如下图所示:
正是这些签名将区块链接在了一起,形成了一条区块链。现在加上区块 3 ,整条链的情况如下图所示:
现在,假设区块 1 中的数据被 更改了。比方说 Damian 和 George 之间的交易被更改了。Damian 向 George 发送了 500 个而非 100 个比特币。由于区块 1 中的数据串改变了,其签名也相应改变了。更改过数据之后,区块 1 的签名不再是 “X32” ,而是变成了 “W10” ,如下图所示:
-请访问 r/BlockchainSchool 查看更多关于区块链的科普知识-
这样一来,区块 1 的新签名 “W10” 跟之前添加进区块 2 数据串的旧签名 “X32” 产生了冲突。区块 1 和区块 2 之间的链接就断了。这条链上的其他用户就会知道区块 1 中的数据被更改了。为了维护区块链的不可变性,其他用户会拒绝同步更改后的交易信息,依旧维持原有的交易记录(即 Damian 向 George 发送 100 BTC
)不变,整条链依旧保持完整。这就意味着,要想不露痕迹地篡改交易,必须将区块 2 数据串中区块 1 的旧签名替换成新签名。然而,一旦区块 2 中的数据串发生变化,区块 2 的签名也会随之发生变化。假设区块 2 的签名从 “9BZ” 变成了“PP4” 。那么区块 2 和区块 3 之间的链接就断了!
区块链上的区块对所有人都是可见的。因此,如果篡改者真想要不露痕迹地篡改交易,就必须保证篡改之后的区块仍然都联系在一起(否则人们就很容易发现哪个区块跟其他区块并不相连,进而判断出该区块已经被改过了)。也就是说,改掉一个区块必须为后续的所有区块计算新的签名。可以认为这几乎是不可能的,但要理解这是为什么,请看下文。
第三阶——生成签名(哈希值)
那么,我们以区块 1 为例再画一个示意图。假设区块 1 只记录一笔交易,即 Thomas 向 David 发送 100 BTC 。需要根据这个数据串生成一个签名。在区块链上,这个签名是通过密码学哈希函数生成的。密码学哈希函数是一个极其复杂的数学公式:将任意数据串作为输入值代入公式,可以得到一个独一无二的 64 位输出值。例如,你可以将 “Jinglebells” 一词代入这个哈希函数(哈希函数的种类有很多,这只是其中一例),得到的输出为:
761A7DD9CAFE34C7CDE6C1270E17F773025A61E511A56F700D415F0D3E199868
只要这个输入中有一个字符发生变化,包括改变大小写或是增加空格和标点,就会得到截然不同的输出。如果你在这个输入后面加上一个句号变成了“Jinglebells.”,得到的输出就变成了:
B9B324E2F987CDE8819C051327966DD4071ED72D998E0019981040958FEC291B
如果我们把句号去掉,还是能得到跟之前一样的输入:
761A7DD9CAFE34C7CDE6C1270E17F773025A61E511A56F700D415F0D3E199868
对于同一个密码学哈希函数来说,相同的输入必定会得到相同的输出,不同的输入必定会得到不同的输出。比特币区块链就是利用哈希函数为区块生成签名的,将区块中的数据作为输入,得到的输出就是区块的签名。我们再来看看只含有一笔交易( Thomas 向 David 发送 100 BTC )的区块 1 示意图。
假设区块 1 中的 数据串如下所示:
Block 1 Thomas -100 David +100
将这个数据串输入哈希函数,得到的输出(签名)如下所示:
BAB5924FC47BBA57F4615230DDBC5675A81AB29E2E0FF85D0C0AD1C1ACA05BFF
这个签名会被添加进区块 2 的中。再假设现在 David 向 Jimi 转了 100 BTC ,这笔交易被打包进了区块 2 。那么如下图所示:
区块 2 的数据串如下所示:
Block 2 David -100 Jimi +100 BAB5924FC47BBA57F4615230DDBC5675A81AB29E2E0FF85D0C0AD1C1ACA05BFF
将这个数据串输入哈希函数,得到的输出(签名)如下所示:
25D8BE2650D7BC095D3712B14136608E096F060E32CEC7322D22E82EA526A3E5
这就是区块 2 的签名。每一个区块都会通过这个密码学哈希函数生成一个数字签名。哈希函数种类繁多,比特币区块链用的是 SHA-256 哈希算法。
但是,(仅有上述措施显然还不够)如果有人想篡改区块中的数据,TA 可以在篡改之后生成新的签名,塞下一个区块中,然后逐个逐个区块生成新的签名,这些改动后的区块还是形成了一条链,他人就没法分辨出数据已经被更改过了。如何防止这种情形呢?
答案是只有符合特定要求的哈希值(签名)才会被区块链接受。这就是第四阶中介绍的挖矿。
第四阶——什么是合格的签名?由谁来签署区块?
并非所有的签名都符合要求。区块链协议会预先确定一些要求,比如,在比特币区块链上,只有以连续的零开头的数字签名相对应的区块才能上链。例如,只有在数字签名以不少于 连续 10 个零开头的情况下,对应的区块才能上链。
然而,由第三小节可知,每个数据串对应的哈希值都是 唯一的。如果一个区块的签名(哈希值)开头少于 10 个零呢?为了获得符合条件的区块签名,需要反复改变输入的数据串,直到能生成以连续 10
个零开头的签名为止。但由于交易数据和元数据(区块编号、时间戳等等)需要保持原样(否则意义就改变了),每个区块里面还另外添加了一段特定长度的、可以改动的数据。想把区块添加到链上时,人们可以不断改变这段数据,直到找到一个合格的签名,然后确定下这段数据的具体值。这段数据就是区块的nonce。nonce
不是预先确定的数据,而是应实际需要而找出的一串完全随机的数字(注:图中所示的其他数据可以由任意字符组成,nonce 只能由数字组成)。
综上所述,区块包含:1)交易数据;2)上一个区块的签名;3)nonce 。这种通过反复更改 nonce、对区块数据进行哈希运算、寻找合格签名的过程就叫做 挖矿,也就是 矿工所做的事。矿工投入大量电力,转化成算力,不断代入 nonce
进行哈希运算,直到找到合格的签名(输出)为止。矿工手中掌握的算力越多,哈希运算的速度就越快,抢先找到合格签名的可能性就越高。这是一种 反复试错 的过程,如下图所示:
-注:nonce 必须是数字(详情请阅读 r/BlockchainSchool 上的讲解)-
区块链网络上的任何用户都可以通过下载并启动 挖矿软件 来参与挖矿,实际上,这就是用他们的硬件计算能力来计算区块的 nonce 。以比特币区块链上的 Block #521,477 为例:
-源自区块链浏览器 blockchain.com -
可以看出,这个区块的哈希值(签名)和上一个区块的哈希值都是以相同数量的零开头的。找到这样一个哈希值并非易事,需要付出大量算力和时间,或者 运气爆棚。没错,有时候运气爆棚的矿工在几分钟之内就能算出合格的签名,花的算力也很少。Block #523034 就是一个极其罕见的例子。一个算力很少的小矿工很快就找到了合格的签名,而其他矿工的算力加起来是他的 7
万亿倍。相比之下,赢得 Powerball 彩票头奖的概率是 2.92 亿分之一,而这位幸运儿挖到矿的概率是中头奖的 1/24000 。
不要小看这些零。这一小节的重点是,找到一个合格的签名很难。
第五阶——区块链的不可变性是如何是实现的?
正如第三阶中所述,更改某个区块会导致它的签名改变,与后续区块记录的对不上,从而与后面的区块断开链接。要想让网络中的其他参与者接受这个被更改过的区块,就要把它跟后面的区块重新链接起来。也就是说,一个区块的签名变了,跟在它后面的所有区块的签名都要改变,才能让别人觉得这是一条前后一致的链。
你想起什么事没有?
如第四节所述,签名必须符合要求!虽然更改所有区块的签名看似可行,但是要花费很多成本和时间,因此被认为是不可能的,原因如下:
假设有一个矿工恶意篡改了某个区块内的交易,然后根据哈希运算为这个区块连同跟在它后面的所有区块生成了新的签名,以此让网络中的其他参与者都接受被篡改过的交易。问题在于,网络中的其他矿工也在原来的链上不断为新的区块计算签名。随着新的区块不断上链,作恶的矿工也要重新计算这些区块的签名。他必须保证所有区块都链接在一起,包括不断被添加到链上的新区块。除非这个矿工拥有的算力超过全网其他人的总算力,否则他永远赶超不了其他矿工。
(校对注:这一段的实际意思是,只要矿工都在自己看到的最长区块链上挖矿,所有算力就会随时间自然汇聚到一条主链上,而攻击者只有制造出一条比当前主链更长的链,才能成功改变大家共同认可的交易记录。这种始终以最长链为主链(有效链)的原则,就是所谓的 “最长链规则”,是 Nakamoto Concensus(中本聪共识机制)的一部分。另,并不是所有区块链都采用了中本聪共识。)
如今有数百万用户在比特币区块链上挖矿,由此可以推定某个恶意参与者或实体的算力是不可能超过全网剩余算力的。这就意味着网络中的其他参与者不可能接受任何对区块链的修改,从而实现了区块链的不可变性。一旦数据被添加到区块链上,就无法再修改了。
只有一种例外,就是恶意参与者的算力真的超过全网其他人的算力总和。从理论上来说,这种情况下是有可能篡改区块链的(即改变大家共同认可的历史记录)。这就叫做 51% 攻击(我写了另一篇文章来解释这种情形),过去也有很多区块链遭受过这种攻击。
(校对注:目前为止,遭受过 51% 攻击的著名区块链有 bitGold、Verge、Ethereum Classic。)
实际上,对比特币区块链发动 51% 攻击所能获得的收益远抵不上高昂的攻击成本。要想获得足够多的算力,除了要负担硬件、冷却设备和存储空间方面的成本,还要承担被千夫所指的风险,更重要的是,会对被攻击区块链的生态系统造成极大的损害,攻击所得的收益也会大幅贬值。51% 攻击实际上就是以一己之力对抗区块链上的其他用户。这也就是为何参与挖矿的用户人数越多,整条链的安全性就越高。
恭喜你已经又进了一阶!现在,你应该已经理解(大型)区块链被认为具有不可更改性的原因了吧。不过现在又出现一个很重要的问题:如何防止矿工将伪造的交易数据添加到区块链上?从技术上来说是做不到的。关于区块链交易的详细解释可参见这篇文章。
(校对注:只有私钥掌控者才能花费相应地址中的资金,而矿工并不知道你的私钥,他人只能通过你公开的公钥来验证某笔交易是不是你发起的。所以伪造交易并不可行)
第六阶——如何治理区块链?由谁决定规则?
……区块链协议自动以最长链上的交易记录为准,将这条链视为代表绝大多数参与者的链。打造最长链需要消耗全网绝大部分算力。被篡改过的区块就与最长链断开了链接,因此会被全网绝大多数节点自动拒绝。
在比特币区块链上, 所有交易历史和钱包余额都是公开可见的(blockchain.info)。任何人都可以查看任一钱包的余额情况,或是始自(2009 年 1 月 3 日的)第一笔交易的所有交易记录。虽然任何人都能查看钱包余额,但是这些钱包的所有者大多都是不为人知的。例如,一个钱包里存有 6.9 万个比特币,至本文截稿之时价值约 5 亿美元。这个钱包在 2015 年 4
月使用过一次,之后就再也没有过交易。
(校对注:这一部分其实并没有回答 “由谁决定规则” 的问题,只大概说明了 “根据现有规则,这种技术是可以实现的”。公链治理是一个复杂的问题,也超出了这篇文章需要说明的范围了。)
第七阶——这些对密码学货币有何意义?
密码学货币从本质上来说都是比特币的变体。绝大多数加密货币都是按照自己的区块链协议搭建的,遵循不同于比特币的规则。比特币应当被归类为一种货币,也就是说它明确具备货币功能。门罗币也是一种具有相同功能的加密货币,不过它的区块链协议还增加了一些规则来增强隐私性(提高交易溯源的难度)。
不过,用区块链发行的资产可以被赋予很多种不同的用途,这点由发行方决定,如此发行的资产一般被称为“代币”。这些代币可以赋予其所有人某种权利,例如社交媒体渠道、 水电等等。所有这些资产交易都记录在不同的区块链上,并且可以通过币安之类的交易所进行线上交易。
代币其实是一种新型互联网货币,可能会影响到一部分行业,其中一个典型的例子就是股票市场。在未来,公司股份之类的产权很有可能会以代币的形式存储到区块链上。区块链不仅限于以代币的形式代表实物价值,也可以安全地记录病历、身份、历史记录、纳税记录等数据。
原文链接: https://blog.goodaudience.com/blockchain-for-beginners-what-is-blockchain-519db8c6677a
作者: Jimi S.
翻译&校对:闵敏 & 阿剑
来源:以太坊爱好
编辑:Be
BFF什么意思(bff字符什么意思)2
微服务框架相关技术
微服务整体框架
API Gateway
API Gateway两种方式:
API Gateway的作用
API Gateway的架构
Eureka(服务发现框架)
Eureka的两个组件
RPC框架
RPC定义
RPC主要组成部分
影响RPC框架性能的因素
工业界的 RPC 框架
如何选择RPC框架
Dubbo
核心组件
工作原理
Dubbo特性
使用示例
Zuul
Zuul工作原理
Zuul的作用
Zuul与应用的集成方式
React前端框架
React定义
React核心
React特点
React的虚拟DOM
React的组件
组件的三大属性
props属性
refs属性
state属性
组件的生命周期
React的函数式编程
React的JSX
React的其它操作
双向绑定
React发送ajax请求
RESTful
RESTful的关键
RESTful与 RPC
RESTful Web 服务的Java框架
RESTful API
RESTful API设计原则
RESTful API设计规范
RESTful API对资源的操作
微服务整体框架
开发前后台分离:前台与后台之间,通过Restful风格接口通信(HTTP协议)
内部服务:Dubbo( RPC框架)
外部服务:SpringCloud Zuul(提供Restful API接口)
微服务应用开发
API Gateway
API Gateway:网关,统一应用请求接口.API 网关在微服务们的最前端,让 API 网关变成由应用所发起的每个请求的入口,简化客户端实现和微服务应用程序间的沟通方式。
API Gateway两种方式:
单节点API Gateway
BFF (Backends for frontends) Gateway
API Gateway的作用
请求路由,版本控制: API Gateway 是微服务的入口,可以根据不同的请求路由到不同的服务上. 也可以进行路由的版本控制,这样即使后服务发生了变化,Gateway 的路径依然可以不改变
用户登录,权限认证: 客户端在与我们后端服务进行交互之前,由API Gateway先进行登录鉴权操作,这是后端所有的服务都需要有的共有逻辑
数据聚合: 由于不同的客户端往往需要的数据完全不同,而这些数据又是不同的 service 提供的,可以借助 Gateway 方便完成来自不同 service 的数据聚合
协议转换: 在项目实践中,CS(Client to Server)协议和SS(Server to Server)协议是不一样的,为了保证数据传输的可靠性,CS协议会有鉴权以及加密解密的逻辑,而在内部的SS协议则不需要这些逻辑,因此在 Gateway 我们需要有一个协议转换的过程
熔断,降级,限流: 通过API Gateway可以在监测到某个服务发生异常,或者当服务的流量超过服务的承载能力等情况时,可以采取相应的措施. 提高整个系统的容错性、稳定性
负载均衡: API Gateway知道所有服务实例的地址,可以根据不同服务采取不同的负载均衡策略
灰度发布: 灰度发布允许直接只导入指定量的流量请求到新的版本
API Gateway的架构
多网关集群(Backends for frontends): 针对不同的客户端,都有相应的网关层来接入.功能主要有:用户登录,鉴权,服务发现注册,协议转换,接口版本控制等以及监控,APM调用链,日志,流控策略等
聚合服务(Merge Service): 在某些客户端的需求中,需要从多个服务拉取数据,为了减少客户端的复杂度,以及加快客户端的访问速度,可以加一个聚合层,用来做聚合查询,在某些接口中可以把多个服务的数据一次性返回给客户端
仪表盘管理端(Dashboard): Dashboard 提供可视化的分析平台,包括服务的管理,监控数据报警配置,日志查询,灰度发布操作,API文档管理等
Eureka(服务发现框架)
Eureka是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的. SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能
Eureka的两个组件
Eureka Server: Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中看到. Eureka Server之间通过复制的方式完成数据的同步
Eureka Client: 是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器
Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性在应用启动后,将会向Eureka Server发送心跳, 如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除。Eureka还提供了客户端缓存机制,即使所有的Eureka
Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性
RPC框架
RPC定义
RPC(Remote Procedure Call Protocol): 远程过程调用协议,一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.也就是
客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样
RPC是协议: 协议就是一套规范,目前典型的RPC实现包括:Dubbo,Thrift,GRPC,Hetty等.从目前技术的发展趋势来看,实现了RPC协议的应用工具往往都会附加其他重要功能
网络协议和网络IO模型对其透明: 既然RPC的客户端认为自己是在调用本地对象。那么传输层使用的是TCP/UDP还是HTTP协议,又或者是一些其他的网络协议它就不需要关心了。既然网络协议对其透明,那么调用过程中,使用的是哪一种网络IO模型调用者也不需要关心
信息格式对其透明: 我们知道在本地应用程序中,对于某个对象的调用需要传递一些参数,并且会返回一个调用结果。至于被调用的对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于远程调用来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的
应该有跨语言能力: 调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述
RPC主要组成部分
Client: RPC协议的调用方.最理想的情况是RPC Client在完全不知道有RPC框架存在的情况下发起对远程服务的调用.但实际情况来说Client或多或少的都需要指定RPC框架的一些细节
Server: 在RPC规范中,这个Server并不是提供RPC服务器IP,端口监听的模块。而是远程服务方法的具体实现(在JAVA中就是RPC服务接口的具体实现).其中的代码是最普通的和业务相关的代码,甚至其接口实现类本身都不知道将被某一个RPC远程客户端调用
Stub/Proxy: RPC代理存在于客户端,因为要实现客户端对RPC框架“透明”调用,那么客户端不可能自行去管理消息格式、不可能自己去管理网络传输协议,也不可能自己去判断调用过程是否有异常。这一切工作在客户端都是交给RPC框架中的“代理”层来处理的
Message Protocol: 一次完整的client-server的交互肯定是携带某种两端都能识别的,共同约定的消息格式.RPC的消息管理层专门对网络传输所承载的消息信息进行编码和解码操作.目前流行的技术趋势是不同的RPC实现,为了加强自身框架的效率都有一套(或者几套)私有的消息格式
Transfer/Network Protocol: 传输协议层负责管理RPC框架所使用的网络协议,网络IO模型. 传输层还需要统一RPC客户端和RPC服务端所使用的IO模型
Selector/Processor: 存在于RPC服务端,用于服务器端某一个RPC接口的实现的特性(它并不知道自己是一个将要被RPC提供给第三方系统调用的服务).所以在RPC框架中应该有一种 “负责执行RPC接口实现”
的角色.包括:管理RPC接口的注册,判断客户端的请求权限,控制接口实现类的执行在内
IDL: IDL(接口定义语言)并不是RPC实现中所必须的.但是需要跨语言的RPC框架一定会有IDL部分的存在.这是因为要找到一个各种语言能够理解的消息结构、接口定义的描述形式.如果RPC实现没有考虑跨语言性,那么IDL部分就不需要包括,例如JAVA
RMI因为就是为了在JAVA语言间进行使用,所以JAVA RMI就没有相应的IDL
不同的RPC框架实现都有一定设计差异。例如生成Stub的方式不一样,IDL描述语言不一样、服务注册的管理方式不一样、运行服务实现的方式不一样、采用的消息格式封装不一样、采用的网络协议不一样。但是基本的思路都是一样的,上图中的所列出的要素也都是具有的
影响RPC框架性能的因素
使用的网络IO模型: RPC服务器可以只支持传统的阻塞式同步IO,也可以做一些改进让RPC服务器支持非阻塞式同步IO,或者在服务器上实现对多路IO模型的支持.这样的RPC服务器的性能在高并发状态下,会有很大的差别.特别是单位处理性能下对内存,CPU资源的使用率
基于的网络协议: 一般来说可以选择让RPC使用应用层协议,例如HTTP或者HTTP/2协议,或者使用TCP协议.让RPC框架工作在传输层.工作在哪一层网络上会对RPC框架的工作性能产生一定的影响,但是对RPC最终的性能影响并不大.但是至少从各种主流的RPC实现来看,没有采用UDP协议做为主要的传输协议的
消息封装格式: 选择或者定义一种消息格式的封装,要考虑的问题包括:消息的易读性,描述单位内容时的消息体大小,编码难度,解码难度,解决半包/粘包问题的难易度.
当然如果您只是想定义一种RPC专用的消息格式,那么消息的易读性可能不是最需要考虑的.消息封装格式的设计是目前各种RPC框架性能差异的最重要原因,这就是为什么几乎所有主流的RPC框架都会设计私有的消息封装格式的原因.dubbo中消息体数据包含dubbo版本号,接口名称,接口版本,方法名称,参数类型列表,参数,附加信息
序列化和反序列化(Schema & Data Serialization): 序列化和反序列化,是对象到二进制数据的转换,程序是可以理解对象的,对象一般含有 schema 或者结构,基于这些语义来做特定的业务逻辑处理.
序列化框架一般会关注以下几点:
Encoding format:是human readable(是否能直观看懂 json)还是binary(二进制)
Schema declaration:也叫作契约声明,基于IDL,比如 Protocol Buffers/Thrift.还是自描述的,比如 JSON、XML.另外还需要看是否是强类型的
语言平台的中立性:比如Java的Native Serialization就只能自己玩,而Protocol Buffers可以跨各种语言和平台
新老契约的兼容性:比如IDL加了一个字段,老数据是否还可以反序列化成。
和压缩算法的契合度 :运行benchmark(基准)和实际应用都会结合各种压缩算法,例如gzip,snappy
性能 :这是最重要的,序列化,反序列化的时间,序列化后数据的字节大小是考察重点。
序列化方式非常多,常见的有Protocol Buffers,Avro,Thrift,XML,JSON,MessagePack,Kyro,Hessian,Protostuff,Java Native Serialize,FST
实现的服务处理管理方式: 在高并发请求下,如何管理注册的服务也是一个性能影响点.可以让RPC的Selector/Processor使用单个线程运行服务的具体实现(这意味着上一个客户端的请求没有处理完,下一个客户端的请求就需要等待).
也可以为每一个RPC具体服务的实现开启一个独立的线程运行(可以一次处理多个请求,但是操作系统对于“可运行的最大线程数”是有限制的). 也可以线程池来运行RPC具体的服务实现(目前看来,在单个服务节点的情况下,这种方式是比较好的). 还可以通过注册代理的方式让多个服务节点来运行具体的RPC服务实现
工业界的 RPC 框架
国内Dubbo: 来自阿里巴巴http://dubbo.I/O/Motan: 新浪微博自用https://github.com/weibocom/motanDubbox: 当当基于 dubbo
的https://github.com/dangdangdotcom/dubboxrpcx: 基于 Golang 的https://github.com/smallnest/rpcx
国外Thrift from facebook:https://thrift.apache.orgAvro from hadoop:https://avro.apache.orgFinagle by
twitter:https://twitter.github.I/O/finaglegRPC by Google:http://www.grpc.I/O(Google inside use Stuppy)Hessian from
cuacho:http://hessian.caucho.comCoral Service inside amazon: not open sourced
如何选择RPC框架
选择一个rpc框架会基于多方面的考虑:框架特性、性能、成熟度、技术支持、社区活跃度等多个方面.最重要一点,这也是往往很多技术人员进入的误区, “对于技术,不要为了使用而使用,用最简单合适的技术实现解决问题才是正道”
.架构是服务于业务的,能快速方便的满足业务需求的架构才是好的架构.没有最好的,只有适合自己的
Dubbo
Dubbo是一个开源分布式服务框架,阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和Spring框架无缝集成.
Dubbo是一款高性能,轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现
核心组件
Remoting: 网络通信框架,实现了sync-over-async和request-response消息机制
RPC: 一个远程过程调用的抽象.支持负载均衡,容灾和集群功能
Registry: 服务目录框架,用于服务的注册和服务事件发布和订阅
工作原理
Provider:暴露服务方称之为“服务提供者”
Consumer:调用远程服务方称之为“服务消费者”
Registry:服务注册与发现的中心目录服务称之为“服务注册中心”
Monitor:统计服务的调用次数和调用时间的日志服务称之为“服务监控中心”
连通性:
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性:
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性:
注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
Dubbo特性
面向接口代理的高性能RPC调用: 提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节
智能负载均衡: 内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量
服务自动注册与发现: 支持多种注册中心服务,服务实例上下线实时感知
高度可扩展能力: 遵循微内核+插件的设计原则,所有核心能力如Protocol,Transport,Serialization被设计为扩展点,平等对待内置实现和第三方实现
运行期流量调度: 内置条件,脚本等路由策略.通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能
可视化的服务治理与运维: 提供丰富服务治理,运维工具:随时查询服务元数据,服务健康状态及调用统计,实时下发路由策略,调整配置参数
使用示例
Zuul
Zuul是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用-Zuul是一个基于JVM路由和服务端的负载均衡器,提供动态路由,监控,弹性,安全等边缘服务的框架,相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门
Zuul工作原理
过滤器机制Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行1.Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据
2.Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用 标准过滤器类型:Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期PRE: 在请求被路由之前调用,利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等ROUTING: 请求路由到微服务,用于构建发送给微服务的请求,使用Apache
HttpClient或Netfilx Ribbon请求微服务POST: 在路由到微服务以后执行,用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等ERROR:
在其他阶段发生错误时执行该过滤器内置的特殊过滤器:StaticResponseFilter: StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源SurgicalDebugFilter:
SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机自定义的过滤器:除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。如STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务
过滤器的生命周期Zuul请求的生命周期详细描述了各种类型的过滤器的执行顺序
过滤器调度过程
动态加载过滤器
Zuul的作用
Zuul可以通过加载动态过滤机制实现Zuul的功能:
验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求
审查与监控: 在边缘位置追踪有意义数据及统计结果,得到准确的生产状态结论
动态路由: 以动态方式根据需要将请求路由至不同后端集群处
压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平
负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群
多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近
Zuul与应用的集成方式
ZuulServlet - 处理请求(调度不同阶段的filters,处理异常等)所有的Request都要经过ZuulServlet的处理,Zuul对request处理逻辑的三个核心的方法: preRoute(),route(),
postRoute()ZuulServletZuulServlet交给ZuulRunner去执行。由于ZuulServlet是单例,因此ZuulRunner也仅有一个实例。ZuulRunner直接将执行逻辑交由FilterProcessor处理,FilterProcessor也是单例,其功能就是依据filterType执行filter的处理逻辑FilterProcessor对filter的处理逻辑:1.首先根据Type获取所有输入该Type的filter:List
list 2.遍历该list,执行每个filter的处理逻辑:processZuulFilter(ZuulFilter filter) 3.RequestContext对每个filter的执行状况进行记录,应该留意,此处的执行状态主要包括其执行时间、以及执行成功或者失败,如果执行失败则对异常封装后抛出
4.到目前为止,Zuul框架对每个filter的执行结果都没有太多的处理,它没有把上一filter的执行结果交由下一个将要执行的filter,仅仅是记录执行状态,如果执行失败抛出异常并终止执行 ContextLifeCycleFilter - RequestContext
的生命周期管理:ContextLifecycleFilter的核心功能是为了清除RequestContext;请求上下文RequestContext通过ThreadLocal存储,需要在请求完成后删除该对象RequestContext提供了执行filter
Pipeline所需要的Context,因为Servlet是单例多线程,这就要求RequestContext即要线程安全又要Request安全。context使用ThreadLocal保存,这样每个worker线程都有一个与其绑定的RequestContext,因为worker仅能同时处理一个Request,这就保证了Request Context
即是线程安全的由是Request安全的。GuiceFilter - GOOLE-IOC(Guice是Google开发的一个轻量级,基于Java5(主要运用泛型与注释特性)的依赖注入框架(IOC).Guice非常小而且快.)StartServer - 初始化 zuul
各个组件(ioc,插件,filters,数据库等)FilterScriptManagerServlet - uploading/downloading/managing scripts, 实现热部署Filter源码文件放在zuul 服务特定的目录, zuul server会定期扫描目录下的文件的变化,动态的读取\编译\运行这些filter,如果有Filter文件更新,源文件会被动态的读取,编译加载进入服务,接下来的Request处理就由这些新加入的filter处理
React前端框架
React定义
React前端框架是Facebook开源的一个js库,用于动态构建用户界面.
React解决的问题:数据绑定的时候,大量操作真实dom,性能成本太高网站的数据流向太混乱,不好控制
React 把用户界面抽象成一个个组件.如按钮组件 Button,对话框组件 Dialog,日期组件 Calendar.开发者通过组合这些组件,最终得到功能丰富,可交互的页面.通过引入 JSX 语法,复用组件变得非常容易,同时也能保证组件结构清晰.有了组件这层抽象,React 把代码和真实渲染目标隔离开来,除了可以在浏览器端渲染到 DOM
来开发网页外,还能用于开发原生移动应用
React核心
虚拟DOM是React的基石,React的核心是组件,React的精髓是函数式编程 ,在React中是单向响应的数据流
组件的设计目的是提高代码复用率,降低测试难度和代码复杂度:
提高代码复用率:组件将数据和逻辑封装,类似面向对象中的类
降低测试难度:组件高内聚低耦合,很容易对单个组件进行测试
降低代码复杂度:直观的语法可以极大提高可读性
React特点
JSX: JSX 是 JavaScript 语法的扩展
组件: 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中
单向响应的数据流: React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单
Declarative(声明式编码): React采用声明范式,可以轻松描述应用(自动dom操作)
Component-Based(组件化编码)
Learn Once,Write Anywhere(支持客户端与服务器渲染)
高效:React通过对DOM的模拟(虚拟dom),最大限度地减少与DOM的交互
1.虚拟(virtual)DOM, 不总是直接操作DOM,减少页面更新次数;
2.高效的DOM Diff算法, 最小化页面重绘;
灵活:React可以与已知的库或框架很好地配合
React的虚拟DOM
传统DOM更新真实页面对应一个 DOM 树.在传统页面的开发模式中,每次需要更新页面时,都要手动操作 DOM 来进行更新
虚拟DOMDOM操作非常昂贵.我们都知道在前端开发中,性能消耗最大的就是DOM操作,而且这部分代码会让整体项目的代码变得难以维护.React把真实DOM树转换成JavaScript对象树,也就是Virtual DOM虚拟DOM定义:一个虚拟DOM(元素)是一个一般的js对象,准确的说是一个对象树(倒立的)虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应如果只是更新虚拟DOM, 页面是不会重绘的Virtual
DOM算法步骤:用JS对象树表示DOM树的结构.然后用这个树构建一个真正的DOM树插入到文档中当状态变更的时候,重新构造一棵新的对象树.然后用新的树和旧的树进行比较,记录两棵树差异把差异应用到真实DOM树上,视图就更新了进一步理解:Virtual
DOM本质上就是在JS和DOM之间做了一个缓存可以类比CPU和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢, 我们就在它们JS和DOM之间加个缓存.CPU(JS)只操作内存(Virtual DOM,最后的时候再
把变更写入硬盘(DOM)
React提供了一些API来创建一种特别的一般js对象
//创建的就是一个简单的虚拟DOM对象
var element = React.createElement('h3', {id:'myTitle'}, 'hello');
虚拟DOM对象最终都会被React转换为真实的DOM
我们编码时基本只需要操作react的虚拟DOM相关数据,react会转换为真实DOM变化而更新界面
创建虚拟DOM的2种方式JSX方式
//
jsx方式创建虚拟dom元素对象
const vDOM2 = {msg.toLowerCase()}
还有一种是纯JS,一般不使用:
//
纯JS方式
const msg = 'I like you';
const myId = 'atguigu';
const vDOM1 = React.createElement('h2',{id:myId},msg);
渲染虚拟DOM(元素)语法: ReactDOM.render(virtualDOM,containerDOM)作用: 将虚拟DOM元素渲染到真实容器DOM中显示参数说明:参数一: 纯js或jsx创建的虚拟DOM对象参数二:
用来包含虚拟DOM元素的真实dom元素对象(一般是一个p)
//
渲染到真实的页面中
ReactDOM.render(vDOM1,document.getElementById('example1'));
ReactDOM.render(vDOM2,document.getElementById('example2'));
使用示例:
02_JSX_DEMO
React的组件
模块什么是模块: 向外提供特定功能的js程序, 一般就是一个js文件为什么要用模块: js代码越多越复杂了使用模块的优势: 简化js的编写, 阅读, 提高运行效率模块化: 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
组件什么是组件: 用来实现特定功能效果的代码集合(html/css/js)为什么要用组件: 单个界面的功能更复杂使用组件的优势: 复用, 简化项目编码, 提高运行效率组件化: 当应用是以多组件的方式实现功能, 这样应用就是一个组件化的应用
自定义组件:1. 定义组件
1.工厂(无状态)函数(简单组件,推荐使用)
//
方式一:工厂函数,推荐使用
function MyComponent() {
return 工厂函数
}
2.ES6类语法
//
方式二:ES6类语法(复杂组件,推荐使用)
class MyComponent2 extends React.Component{
render(){
return ES6的语法
}
}
2. 渲染组件标签
//语法规则
ReactDOM.render(, document.getElementById('example'));
注意返回的组件类必须首字母大写虚拟DOM元素必须只有一个根元素虚拟DOM元素必须有结束标签
ReactDOM.render()渲染组件标签的基本流程:
1.React内部会创建组件实例对象;
2.得到包含的虚拟DOM并解析为真实DOM;
3.插入到指定的页面元素内部;
组件的三大属性
props属性
1.每个组件对象都会有props(properties的简写)属性2.组件标签的所有属性都保存在props中3.内部读取某个属性值:this.props.propertyName4.作用: 通过标签属性从组件外向组件内传递数据(只读)5.对props中的属性值进行类型限制和必要性限制:
//
对标签属性进行限制
Person.propTypes = {
name:React.PropTypes.string.isRequired,
sex:React.PropTypes.string,
age:React.PropTypes.number
}
6.扩展属性: 将对象的所有属性通过props传递
//具体如下:
ReactDOM.render(,document.getElementById('example'))
7.默认属性值
//
指定属性的默认值
Person.defaultProps = {
sex:'男',
age:18
}
8.组件类的构造函数
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
refs属性
1.组件内的标签都可以定义ref属性来标识本身2.在组件中可以通过this.refs.refName来得到对应的真实DOM对象3.作用: 用于操作指定的ref属性的dom元素对象(表单标签居多)
事件处理通过onXxx属性指定组件的事件处理函数(注意大小写)React使用的是自定义(合成)事件, 而不是使用的DOM事件React中的事件是通过委托方式处理的(委托给组件最外层的元素)通过event.target得到发生事件的DOM元素对象 handleFocus(event) { event.target //返回input对象 }
强烈注意组件内置的方法中的this为组件对象在组件中自定义的方法中的this为null1.强制绑定thisthis.change = this.change.bind(this); 2.箭头函数(ES6模块化编码时才能使用)
state属性
组件被称为 “状态机” ,通过更新组件的状态值来更新对应的页面显示(重新渲染)
初始化状态:
constructor (props) {
super(props)
this.state = {
stateProp1 : value1,
stateProp2 : value2
}
}
读取某个状态值:
this.state.statePropertyName
更新状态->组件界面更新
this.setState({
stateProp1 : value1,
stateProp2 : value2
})
组件的生命周期
组件的三个生命周期状态:Mount: 插入真实 DOMUpdate: 被重新渲染Unmount: 被移出真实 DOM
React 为每个状态都提供了两种勾子(hook)函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用:componentWillMount()componentDidMount(): 已插入页面真实DOM,在render之后才会执行componentWillUpdate(object
nextProps,object nextState)componentDidUpdate(object prevProps,object prevState)componentWillUnmount()
生命周期流程:第一次初始化渲染显示:render()constructor(): 创建对象初始化statecomponentWillMount(): 将要插入回调函数render():
用于插入虚拟DOM回调函数componentDidMount(): 已经插入回调函数.在此方法中启动定时器,绑定监听,发送Ajax请求每次更新state:this.setSate()componentWillUpdate(): 将要更新回调函数render():
更新,重新渲染componentDidUpdate(): 已经更新回调删除组件ReactDOM.unmountComponentAtNode(p):移除组件componentWillUnmount():组件将要被移除回调
常用的方法render(): 必须重写,返回一个自定义的虚拟DOMconstructor(): 初始化状态,绑定this(可以箭头函数代替)componentDidMount(): 只执行一次,已经在DOM树中,适合启动,设置一些监听
注意一般会在componentDidMount() 中:开启监听,发送ajax请求可以在componentWillUnmount()
做一些收尾工作:停止监听生命周期还有一个方法:componentWillReceiveProps()
React的函数式编程
函数式编程: 结构化编程的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用
声明式编程: 只关注做什么,而不关注怎么做(流程),类似于填空题,数组中常见声明式方法:map() , forEach() ,find() ,findIndex()
命令式编程: 要关注做什么和怎么做(流程), 类似于问答题
var arr = [1, 3, 5, 7]
// 需求: 得到一个新的数组, 数组中每个元素都比arr中对应的元素大10: [11, 13, 15, 17]
// 命令式编程
var arr2 = []
for(var i =0;iHello JSX!;
JSX编码:基本语法规则:遇到 < 开头的代码, 以标签的语法解析:html同名标签转换为html同名元素,其它标签需要特别解析遇到以 {
开头的代码,以JS的语法解析:标签中的js代码必须用{}包含js中直接可以套标签, 但标签要套js需要放在 { } 中在解析显示js数组时,会自动遍历显示把数据的数组转换为标签的数组 var liArr = dataArr.map(function(item, index){ return {item} }) babel.js的作用浏览器的js引擎是不能直接解析JSX语法代码的,需要babel转译为纯JS的代码才能运行只要用了JSX,都要加上type=“text/babel”,声明需要babel来处理
注意:标签必须有结束标签的class属性必须改为className属性标签的style属性值必须为: {{color:‘red’, width:12}}
React的其它操作
双向绑定
React是一个单向数据流
可以自定义双向数据流组件(受控组件),需要通过onChange监听手动实现
React发送ajax请求
React没有ajax模块,所以只能集成其它的js库(如jQuery/axios/fetch),
发送ajax请求axios封装XmlHttpRequest对象的ajaxpromise可以用在浏览器端和服务器fetch不再使用XmlHttpRequest对象提交ajax请求fetch就是用来提交ajax请求的函数,只是新的浏览才内置了fetch为了兼容低版本的浏览器,可以引入fetch.js
在哪个方法去发送ajax请求:只显示一次(请求一次): componentDidMount()显示多次(请求多次): componentWillReceiveProps()
//做一个跳转页面
RESTful
RESTful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件. 它主要用于客户端和服务器交互类的软件. 可以使软件更简洁,更有层次,更易于实现缓存等机制
REST原则:客户端和服务器之间的交互在请求之间是无状态的分层系统
RESTful的关键
定义可表示流程元素或资源的对象: 在REST中,每一个对象都是通过URL来表示的,对象用户负责将状态信息打包进每一条消息内,以便对象的处理总是无状态的
组合管理及流程绑定
RESTful与 RPC
RPC 样式的 Web 服务客户端将一个装满数据的信封:包括方法和参数信息, 通过 HTTP 发送到服务器。服务器打开信封并使用传入参数执行指定的方法。方法的结果打包到一个信封并作为响应发回客户端。客户端收到响应并打开信封。每个对象都有自己独特的方法以及仅公开一个 URI 的 RPC 样式 Web 服务,URI 表示单个端点。它忽略 HTTP 的大部分特性且仅支持
POST 方法
RESTful Web 服务的Java框架
Restlet客户端和服务器都是组件, 组件通过连接器互相通信该框架最重要的类是抽象类 Uniform 及其具体的子类 Restlet,该类的子类是专用类,比如 Application、Filter、Finder、Router 和
Route。这些子类能够一起处理验证、过滤、安全、数据转换以及将传入请求路由到相应资源等操作。Resource 类生成客户端的表示形式RESTful Web 服务也是多层架构:数据存储层,数据访问层,业务层,表示层
RESTful API
RESTful:
URL定位资源,用HTTP动词(GET,POST,PUT,DELETE)描述操作
RESTful API就是一套协议来规范多种形式的前端和同一个后台的交互方式.由SERVER来提供前端来调用,前端调用API向后台发起HTTP请求,后台响应请求将处理结果反馈给前端
RESTful API设计原则
资源: 首先是弄清楚资源的概念,资源总是要通过一种载体来反应它的内容.JSON是现在最常用的资源表现形式
统一接口:
RESTful风格的数据元操CRUD(create,read,update,delete)分别对应HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,统一数据操作的接口
URI: 可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源.要获取这个资源访问它的URI就可以,因此URI就成了每一个资源的地址或识别符.一般的,每个资源至少有一个URI与之对应,最典型的URI就是URL
**无状态:**所有的资源都可以URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。
有状态和无状态的区别:
例如要查询员工工资的步骤
第一步:登录系统。
第二步:进入查询工资的页面。
第三步:搜索该员工。
第四步:点击姓名查看工资。
这样的操作流程就是有状态的,查询工资的每一个步骤都依赖于前一个步骤,只要前置操作不成功,
后续操作就无法执行。如果输入一个URL就可以得到指定员工的工资,则这种情况就是无状态的,
因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个URL与之
对应可以通过HTTP中的GET方法得到资源,这就是典型的RESTful风格。
RESTful API设计规范
URI语法
URI=scheme"://"authority"/"path["?"query]["#"fragment]
- scheme:指底层用的协议:http,https,ftp
- host:服务器的IP地址或者域名
- port:端口,http中默认80
- path:访问资源的路径,就是各种web 框架中定义的route路由
- query:为发送给服务器的参数
- fragment:锚点,定位到页面的资源,锚点为资源id
资源路径: rest资源的定义,即URL的定义,是最重要的;要设计出优雅的、易读的rest接口
URL中不能有动词: 在Restful架构中,每个网址代表的是一种资源,所以网址中不能有动词,只能有名词,动词由HTTP的 get、post、put、delete 四种方法来表示
URL结尾不应该包含斜杠 “/”: URI中的每个字符都会计入资源的唯一身份的识别中,这是作为URL路径中处理中最重要的规则之一,正斜杠"/"不会增加语义值,且可能导致混淆.RESTful
API不允许一个尾部的斜杠,不应该将它们包含在提供给客户端的链接的结尾处.**两个不同的URI映射到两个不同的资源.如果URI不同,那么资源也是如此,反之亦然.**因此,RESTful API必须生成和传递精确的URI,不能容忍任何的客户端尝试不精确的资源定位.
正斜杠分隔符 “/” 必须用来指示层级关系: URI的路径中的正斜杠 “/” 字符用于指示资源之间的层次关系
应该使用连字符 “-” 来提高URL的可读性,而不是使用下划线 “_”: 为了使URL容易让人们理解,要使用连字符 “-” 字符来提高长路径中名称的可读性
URL路径中首选小写字母: RFC 3986将URI定义为区分大小写,但scheme 和 host components 除外
URL路径名词均为复数: 为了保证url格式的一致性,建议使用复数形式
RESTful API对资源的操作
对于RESTful API资源的操作,由HTTP动词表示:get: 获取资源post: 新建资源put: 在服务器更新资源(向客户端提供改变后的所有资源)delete: 删除资源patch:在服务器更新资源(向客户端提供改变的属性),一般不用,用put
资源过滤: 在获取资源的时候,有可能需要获取某些“过滤”后的资源
例如指定前10行数据:
http://api.user.com/schools/grades/classes/boys?page=1&page-size=10
返回状态码,推荐标准HTTP状态码: 有很多服务器将返回状态码一直设为200,然后在返回body里面自定义一些状态码来表示服务器返回结果的状态码.由于RESTful API是直接使用的HTTP协议,所以它的状态码也要尽量使用HTTP协议的状态码
200 OK 服务器返回用户请求的数据,该操作是幂等的
201 CREATED 新建或者修改数据成功
204 NOT CONTENT 删除数据成功
400 BAD REQUEST 用户发出的请求有问题,该操作是幂等的
401 Unauthoried 表示用户没有认证,无法进行操作
403 Forbidden 用户访问是被禁止的
422 Unprocesable Entity 当创建一个对象时,发生一个验证错误
500 INTERNAL SERVER ERROR 服务器内部错误,用户将无法判断发出的请求是否成功
503 Service Unavailable 服务不可用状态,多半是因为服务器问题,例如CPU占用率大,等等
本文由攻城狮Chova原创,欢迎关注,带你一起长知识!
BFF什么意思(bff字符什么意思)3
学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。
正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:
#include int main (){
当上面的代码被编译和执行时,它会产生下列结果:
var1 变量的地址: bff5a400
通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。
什么是指针?
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */double *dp; /* 一个 double 型的指针 */float *fp; /* 一个浮点型的指针 */char *ch /* 一个字符型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
如何使用指针?
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include int main (){
当上面的代码被编译和执行时,它会产生下列结果:
Address of var variable: bffd8b3cAddress stored in ip variable: bffd8b3cValue of *ip variable: 20
C 中的 NULL 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include int main (){
当上面的代码被编译和执行时,它会产生下列结果:
ptr 的值是 0
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句,如下所示:
if(ptr) /* 如果 p 非空,则完成 */if(!ptr) /* 如果 p 为空,则完成 */
C 指针详解
在 C 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C 程序员必须清楚的一些与指针相关的重要概念:
指针的算术运算可以对指针进行四种算术运算:++、--、+、-指针数组可以定义用来存储指针的数组。指向指针的指针C 允许指向指针的指针。传递指针给函数通过引用或地址传递参数,使传递的参数在调用函数中被改变。从函数返回指针C 允许函数返回指针到局部变量、静态变量和动态内存分配。
← C 数组
C 函数指针与回调函数 →
笔记列表
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
要理解指针就要先理解计算机的内存。计算机内存会被划分为按顺序编号的内存单元。每个变量都是存储在内存单元中的,称之为地址。
#include int main (){
指针是一个变量,所以可以使用任何合法的变量名。在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。
然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。
但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
所有指针在创建时都要初始化,如果不知道他指向什么就讲 0 赋值给他。必须初始化指针,没有被初始化的指针被称为失控指针(野指针)。
#include int main (){
实例定义了变量 a 和指针变量pAbc。pAbc = &a;表示指针变量指向了变量 a,p 中存放的地址为 a 的地址 &a,*p 所指的是 p 中存放的地址 a 内存单元中的值。
如您还有不明白的可以在下面与我留言或是与我探讨QQ群308855039,我们一起飞!
小伙伴们,感觉我的分享很不错的别忘记“赞赏”我一下哟!
阅读全文 }