From 56ae1eb2a5f60b81b11cb15e7b59399c1c7fda38 Mon Sep 17 00:00:00 2001
From: yangfubing <1928881525@qq.com>
Date: Sun, 19 Jan 2020 11:56:49 +0800
Subject: [PATCH] update blog
Signed-off-by: yangfubing <1928881525@qq.com>
---
en-us/blog/dart/syntax.html | 1905 +++++++++++++++++++++++++
en-us/blog/dart/syntax.json | 6 +
en-us/blog/docker/docker-compose.html | 474 ++++++
en-us/blog/docker/docker-compose.json | 6 +
en-us/blog/docker/docker-jenkins.html | 116 ++
en-us/blog/docker/docker-jenkins.json | 6 +
en-us/blog/docker/docker.html | 75 +
en-us/blog/docker/docker.json | 2 +-
en-us/blog/exp/cto.html | 212 +++
en-us/blog/exp/cto.json | 6 +
en-us/blog/java/feature.html | 70 +
en-us/blog/java/feature.json | 2 +-
en-us/blog/java/javavm.html | 466 ++++++
en-us/blog/java/javavm.json | 6 +
en-us/blog/java/springAnnotation.html | 220 +++
en-us/blog/java/springAnnotation.json | 6 +
en-us/blog/linux/ope.html | 78 +
en-us/blog/linux/ope.json | 2 +-
en-us/blog/net/c_sqlserver_nginx.html | 337 +++++
en-us/blog/net/c_sqlserver_nginx.json | 6 +
en-us/blog/sql/mybatis.html | 344 +++++
en-us/blog/sql/mybatis.json | 6 +
en-us/blog/sql/mysql_backups.html | 310 ++++
en-us/blog/sql/mysql_backups.json | 6 +
en-us/blog/tool/git.html | 151 ++
en-us/blog/tool/git.json | 2 +-
en-us/blog/tool/minio.html | 221 +++
en-us/blog/tool/minio.json | 6 +
en-us/blog/web/es6.html | 3 +-
en-us/blog/web/es6.json | 2 +-
en-us/blog/web/javascript.html | 685 +++++++++
en-us/blog/web/javascript.json | 6 +
en-us/blog/web/js_tool_method.html | 978 +++++++++++++
en-us/blog/web/js_tool_method.json | 6 +
en-us/blog/web/react_interview.html | 845 +++++++++++
en-us/blog/web/react_interview.json | 6 +
en-us/blog/web/vue_cp_react.html | 264 ++++
en-us/blog/web/vue_cp_react.json | 6 +
md_json/blog.json | 160 ++-
zh-cn/blog/dart/syntax.html | 1905 +++++++++++++++++++++++++
zh-cn/blog/dart/syntax.json | 6 +
zh-cn/blog/docker/docker-compose.html | 474 ++++++
zh-cn/blog/docker/docker-compose.json | 6 +
zh-cn/blog/docker/docker-jenkins.html | 116 ++
zh-cn/blog/docker/docker-jenkins.json | 6 +
zh-cn/blog/docker/docker.html | 75 +
zh-cn/blog/docker/docker.json | 2 +-
zh-cn/blog/exp/cto.html | 212 +++
zh-cn/blog/exp/cto.json | 6 +
zh-cn/blog/java/feature.html | 70 +
zh-cn/blog/java/feature.json | 2 +-
zh-cn/blog/java/javavm.html | 466 ++++++
zh-cn/blog/java/javavm.json | 6 +
zh-cn/blog/java/springAnnotation.html | 220 +++
zh-cn/blog/java/springAnnotation.json | 6 +
zh-cn/blog/linux/ope.html | 78 +
zh-cn/blog/linux/ope.json | 2 +-
zh-cn/blog/net/c_sqlserver_nginx.html | 337 +++++
zh-cn/blog/net/c_sqlserver_nginx.json | 6 +
zh-cn/blog/sql/mybatis.html | 344 +++++
zh-cn/blog/sql/mybatis.json | 6 +
zh-cn/blog/sql/mysql_backups.html | 310 ++++
zh-cn/blog/sql/mysql_backups.json | 6 +
zh-cn/blog/tool/git.html | 151 ++
zh-cn/blog/tool/git.json | 2 +-
zh-cn/blog/tool/minio.html | 221 +++
zh-cn/blog/tool/minio.json | 6 +
zh-cn/blog/web/es6.html | 3 +-
zh-cn/blog/web/es6.json | 2 +-
zh-cn/blog/web/javascript.html | 685 +++++++++
zh-cn/blog/web/javascript.json | 6 +
zh-cn/blog/web/js_tool_method.html | 978 +++++++++++++
zh-cn/blog/web/js_tool_method.json | 6 +
zh-cn/blog/web/react_interview.html | 845 +++++++++++
zh-cn/blog/web/react_interview.json | 6 +
zh-cn/blog/web/vue_cp_react.html | 264 ++++
zh-cn/blog/web/vue_cp_react.json | 6 +
77 files changed, 15824 insertions(+), 32 deletions(-)
create mode 100644 en-us/blog/dart/syntax.html
create mode 100644 en-us/blog/dart/syntax.json
create mode 100644 en-us/blog/docker/docker-compose.html
create mode 100644 en-us/blog/docker/docker-compose.json
create mode 100644 en-us/blog/docker/docker-jenkins.html
create mode 100644 en-us/blog/docker/docker-jenkins.json
create mode 100644 en-us/blog/exp/cto.html
create mode 100644 en-us/blog/exp/cto.json
create mode 100644 en-us/blog/java/javavm.html
create mode 100644 en-us/blog/java/javavm.json
create mode 100644 en-us/blog/java/springAnnotation.html
create mode 100644 en-us/blog/java/springAnnotation.json
create mode 100644 en-us/blog/net/c_sqlserver_nginx.html
create mode 100644 en-us/blog/net/c_sqlserver_nginx.json
create mode 100644 en-us/blog/sql/mybatis.html
create mode 100644 en-us/blog/sql/mybatis.json
create mode 100644 en-us/blog/sql/mysql_backups.html
create mode 100644 en-us/blog/sql/mysql_backups.json
create mode 100644 en-us/blog/tool/minio.html
create mode 100644 en-us/blog/tool/minio.json
create mode 100644 en-us/blog/web/javascript.html
create mode 100644 en-us/blog/web/javascript.json
create mode 100644 en-us/blog/web/js_tool_method.html
create mode 100644 en-us/blog/web/js_tool_method.json
create mode 100644 en-us/blog/web/react_interview.html
create mode 100644 en-us/blog/web/react_interview.json
create mode 100644 en-us/blog/web/vue_cp_react.html
create mode 100644 en-us/blog/web/vue_cp_react.json
create mode 100644 zh-cn/blog/dart/syntax.html
create mode 100644 zh-cn/blog/dart/syntax.json
create mode 100644 zh-cn/blog/docker/docker-compose.html
create mode 100644 zh-cn/blog/docker/docker-compose.json
create mode 100644 zh-cn/blog/docker/docker-jenkins.html
create mode 100644 zh-cn/blog/docker/docker-jenkins.json
create mode 100644 zh-cn/blog/exp/cto.html
create mode 100644 zh-cn/blog/exp/cto.json
create mode 100644 zh-cn/blog/java/javavm.html
create mode 100644 zh-cn/blog/java/javavm.json
create mode 100644 zh-cn/blog/java/springAnnotation.html
create mode 100644 zh-cn/blog/java/springAnnotation.json
create mode 100644 zh-cn/blog/net/c_sqlserver_nginx.html
create mode 100644 zh-cn/blog/net/c_sqlserver_nginx.json
create mode 100644 zh-cn/blog/sql/mybatis.html
create mode 100644 zh-cn/blog/sql/mybatis.json
create mode 100644 zh-cn/blog/sql/mysql_backups.html
create mode 100644 zh-cn/blog/sql/mysql_backups.json
create mode 100644 zh-cn/blog/tool/minio.html
create mode 100644 zh-cn/blog/tool/minio.json
create mode 100644 zh-cn/blog/web/javascript.html
create mode 100644 zh-cn/blog/web/javascript.json
create mode 100644 zh-cn/blog/web/js_tool_method.html
create mode 100644 zh-cn/blog/web/js_tool_method.json
create mode 100644 zh-cn/blog/web/react_interview.html
create mode 100644 zh-cn/blog/web/react_interview.json
create mode 100644 zh-cn/blog/web/vue_cp_react.html
create mode 100644 zh-cn/blog/web/vue_cp_react.json
diff --git a/en-us/blog/dart/syntax.html b/en-us/blog/dart/syntax.html
new file mode 100644
index 0000000..f56fe42
--- /dev/null
+++ b/en-us/blog/dart/syntax.html
@@ -0,0 +1,1905 @@
+
+
+
+
+
+
+
+
+
+ syntax
+
+
+
+
+ Dart语法学习
+目录
+
+参考资料
+语言特性
+关键字
+变量与常量
+数据类型
+运算符 operators
+控制流程语句
+异常 Exceptions
+函数 Function
+类 Class
+类-方法
+类-抽象类
+类-隐式接口
+类-扩展一个类(重写)
+库和可见性
+异步支持
+
+参考资料
+
+语言特性
+
+
+Dart所有的东西都是对象, 即使是数字numbers、函数function、null也都是对象,所有的对象都继承自Object类。
+
+
+Dart动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)。
+
+
+Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
+
+
+Dart中的类和接口是统一的,类即接口,你可以继承一个类,也可以实现一个类(接口),自然也包含了良好的面向对象和并发编程的支持。
+
+
+Dart 提供了顶级函数(如:main())。
+
+
+Dart 没有 public、private、protected 这些关键字,变量名以"_"开头意味着对它的 lib 是私有的。
+
+
+没有初始化的变量都会被赋予默认值 null。
+
+
+final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const。
+
+
+编程语言并不是孤立存在的,Dart也是这样,他由语言规范、虚拟机、类库和工具等组成:
+
+SDK:SDK 包含 Dart VM、dart2js、Pub、库和工具。
+Dartium:内嵌 Dart VM 的 Chromium ,可以在浏览器中直接执行 dart 代码。
+Dart2js:将 Dart 代码编译为 JavaScript 的工具。
+Dart Editor:基于 Eclipse 的全功能 IDE,并包含以上所有工具。支持代码补全、代码导航、快速修正、重构、调试等功能。
+
+
+
+关键字(56个)
+
+
+
+关键字
+-
+-
+-
+
+
+
+
+abstract
+do
+import
+super
+
+
+as
+dynamic
+in
+switch
+
+
+assert
+else
+interface
+sync
+
+
+enum
+implements
+is
+this
+
+
+async
+export
+library
+throw
+
+
+await
+external
+mixin
+true
+
+
+break
+extends
+new
+try
+
+
+case
+factory
+null
+typedef
+
+
+catch
+false
+operator
+var
+
+
+class
+final
+part
+void
+
+
+const
+finally
+rethrow
+while
+
+
+continue
+for
+return
+with
+
+
+covariant
+get
+set
+yield
+
+
+default
+if
+static
+deferred
+
+
+
+变量与常量
+
+变量声明与初始化
+
+
+调用的变量name包含对String值为“张三” 的对象的引用,name推断变量的类型是String,但可以通过指定它来更改该类型,如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字。
+
+
+ var name = ‘Bob’;
+ Object name = '张三' ;
+ dynamic name = '李四' ;
+
+
+ String name = 'Bob' ;
+
+
+默认值
+
+
+未初始化的变量的初始值为null(包括数字),因此数字、字符串都可以调用各种方法
+
+
+ int lineCount;
+
+ assert (lineCount == null );
+ print (lineCount);
+
+
+
+final and const
+
+
+ final String outSideFinalName
+
+
+
+被final或者const修饰的变量,变量类型可以省略,建议指定数据类型。
+
+
+final name = "Bob" ;
+final String name1 = "张三" ;
+
+const name2 = "alex" ;
+const String name3 = "李四" ;
+
+
+被 final 或 const 修饰的变量无法再去修改其值。
+
+final String outSideFinalName = "Alex" ;
+
+
+outSideFinalName = "Bill" ;
+
+const String outSideName = 'Bill' ;
+
+
+
+
+
+flnal 或者 const 不能和 var 同时使用
+
+
+const var String outSideName = 'Bill' ;
+
+
+final var String name = 'Lili' ;
+
+
+常量如果是类级别的,请使用 static const
+
+
+static const String name3 = 'Tom' ;
+
+
+
+
+
+
+
+const speed = 100 ;
+const double distance = 2.5 * speed;
+
+final speed2 = 100 ;
+final double distance2 = 2.5 * speed2;
+
+
+
+const关键字不只是声明常数变量,您也可以使用它来创建常量值,以及声明创建常量值的构造函数,任何变量都可以有一个常量值。
+
+
+
+var varList = const [];
+final finalList = const [];
+const constList = const [];
+
+
+
+varList = ["haha" ];
+
+
+
+
+
+
+
+
+
+在常量表达式中,该运算符的操作数必须为'bool'、'num'、'String'或'null', const常量必须用conat类型的值初始化。
+
+const String outSideName = 'Bill' ;
+final String outSideFinalName = 'Alex' ;
+const String outSideName2 = 'Tom' ;
+
+const aConstList = const ['1' , '2' , '3' ];
+
+
+
+const validConstString = '$outSideName $outSideName2 $aConstList ' ;
+
+
+
+const validConstString = '$outSideName $outSideName2 $outSideFinalName ' ;
+
+var outSideVarName='Cathy' ;
+
+
+const validConstString = '$outSideName $outSideName2 $outSideVarName ' ;
+
+
+const String outSideConstName = 'Joy' ;
+const validConstString = '$outSideName $outSideName2 $outSideConstName ' ;
+
+
+
+
+数据类型
+
+
+num
+
+
+num 是数字类型的父类,有两个子类 int 和 double。
+
+
+int 根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。
+
+
+double 64位(双精度)浮点数,如IEEE 754标准所规定。
+
+
+int a = 1 ;
+print (a);
+
+double b = 1.12 ;
+print (b);
+
+
+int one = int .parse('1' );
+
+print (one + 2 );
+
+
+var onePointOne = double .parse('1.1' );
+
+print (onePointOne + 2 );
+
+
+String oneAsString = 1. toString();
+
+
+
+print ('$oneAsString + 2' );
+
+print ('$oneAsString 2' );
+
+
+String piAsString = 3.14159 .toStringAsFixed(2 );
+
+print (piAsString);
+
+String aString = 1.12618 .toStringAsFixed(2 );
+
+print (aString);
+
+
+
+String
+
+
+Dart里面的String是一系列 UTF-16 代码单元。
+
+
+Dart里面的String是一系列 UTF-16 代码单元。
+
+
+单引号或者双引号里面嵌套使用引号。
+
+
+用 或{} 来计算字符串中变量的值,需要注意的是如果是表达式需要${表达式}
+
+
+String singleString = 'abcdddd' ;
+String doubleString = "abcsdfafd" ;
+
+String sdString = '$singleString a "bcsd" ${singleString} ' ;
+String dsString = "abc 'aaa' $sdString " ;
+print (sdString);
+print (dsString);
+
+
+String singleString = 'aaa' ;
+String doubleString = "bbb" ;
+
+String sdString = '$singleString a "bbb" ${doubleString} ' ;
+
+print (sdString);
+
+
+String dsString = "${singleString.toUpperCase()} abc 'aaa' $doubleString .toUpperCase()" ;
+
+可以看出 ”$doubleString.toUpperCase()“ 没有加“{}“,导致输出结果是”bbb.toUpperCase()“
+print (dsString);
+
+
+
+bool
+
+Dart 是强 bool 类型检查,只有bool 类型的值是true 才被认为是true。
+只有两个对象具有bool类型:true和false,它们都是编译时常量。
+Dart的类型安全意味着您不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代码, 相反Dart使用的是显式的检查值。
+assert 是语言内置的断言函数,仅在检查模式下有效在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。
+
+
+var fullName = '' ;
+assert (fullName.isEmpty);
+
+
+var hitPoints = 0 ;
+assert (hitPoints <= 0 );
+
+
+var unicorn;
+assert (unicorn == null );
+
+
+var iMeantToDoThis = 0 / 0 ;
+assert (iMeantToDoThis.isNaN);
+
+
+
+
+List集合
+
+在Dart中,数组是List对象,因此大多数人只是将它们称为List。Dart list文字看起来像JavaScript数组文字
+
+
+List list = [10 , 7 , 23 ];
+
+print (list);
+
+
+var fruits = new List ();
+
+
+fruits.add('apples' );
+
+
+fruits.addAll(['oranges' , 'bananas' ]);
+
+List subFruits = ['apples' , 'oranges' , 'banans' ];
+
+fruits.addAll(subFruits);
+
+
+print (fruits);
+
+
+print (fruits.length);
+
+
+print (fruits.first);
+
+
+print (fruits.last);
+
+
+print (fruits[0 ]);
+
+
+print (fruits.indexOf('apples' ));
+
+
+print (fruits.removeAt(0 ));
+
+
+
+fruits.remove('apples' );
+
+
+fruits.removeLast();
+
+
+fruits.removeRange(start,end);
+
+
+fruits.removeWhere((item) => item.length >6 );
+
+
+fruits.clear();
+
+
+
+注意事项:
+
+可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,直接打印是地址值。
+和java一样list里面的元素必须保持类型一致,不一致就会报错。
+和java一样list的角标从0开始。
+如果集合里面有多个相同的元素“X”, 只会删除集合中第一个改元素
+
+
+
+
+
+Map集合
+
+一般来说,map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。Dart支持map由map文字和map类型提供。
+初始化Map方式一: 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。
+
+
+
+
+Map companys = {'Alibaba' : '阿里巴巴' , 'Tencent' : '腾讯' , 'baidu' : '百度' };
+
+print (companys);
+
+
+
+Map schoolsMap = new Map ();
+schoolsMap['first' ] = '清华' ;
+schoolsMap['second' ] = '北大' ;
+schoolsMap['third' ] = '复旦' ;
+
+print (schoolsMap);
+
+var fruits = new Map ();
+fruits["first" ] = "apple" ;
+fruits["second" ] = "banana" ;
+fruits["fifth" ] = "orange" ;
+
+print (fruits);
+
+
+
+var aMap = new Map <int , String >();
+
+
+aMap[1 ] = '小米' ;
+
+
+
+aMap[1 ] = 'alibaba' ;
+
+
+aMap[2 ] = 'alibaba' ;
+
+
+aMap[3 ] = '' ;
+
+
+aMap[4 ] = null ;
+
+print (aMap);
+
+
+assert (aMap.containsKey(1 ));
+
+
+aMap.remove(1 );
+
+print (aMap);
+
+
+注意事项
+
+map的key类型不一致也不会报错。
+添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key,比如分别是 1,2,4,看起来有间隔,事实上添加到map的时候是{1:value,2:value,4:value} 这种形式。
+map里面的key不能相同。但是value可以相同,value可以为空字符串或者为null。
+
+
+
+
+
+运算符
+
+
+
+描述
+操作符
+
+
+
+
+一元后置操作符
+expr++ expr-- () [] . ?.
+
+
+一元前置操作符
+expr !expr ~expr ++expr --expr
+
+
+乘除
+* / % ~/
+
+
+加减
++ -
+
+
+位移
+<< >>
+
+
+按位与
+&
+
+
+按位或
+
+
+
+按位异或
+^
+
+
+逻辑与
+&&
+
+
+逻辑或
+
+
+
+关系和类型判断
+>= > <= < as is is!
+
+
+等
+== !=
+
+
+如果为空
+??
+
+
+条件表达式
+expr1 ? expr2 : expr3
+
+
+赋值
+= *= /= ~/= %= += -= <<= >>= &= ^= = ??=
+
+
+级联
+..
+
+
+
+流程控制语句(Control flow statements)
+
+if...else
+for
+while do-whild
+break continue
+break continue
+assert(仅在checked模式有效)
+
+异常(Exceptions)
+
+
+throw
+
+throw new FormatException('Expected at least 1 section' );
+
+
+ throw 'Out of llamas!' ;
+
+
+因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方
+
+distanceTo(Point other) =>
+throw new UnimplementedError();
+
+
+
+catch
+
+将可能出现异常的代码放置到try语句中,可以通过 on语句来指定需要捕获的异常类型,使用catch来处理异常。
+
+ try {
+ breedMoreLlamas();
+} on OutOfLlamasException {
+
+ buyMoreLlamas();
+} on Exception catch (e) {
+
+ print ('Unknown exception: $e ' );
+} catch (e, s) {
+ print ('Exception details:\n $e ' );
+ print ('Stack trace:\n $s ' );
+}
+
+
+
+rethrow
+
+rethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用。
+
+final foo = '' ;
+
+void misbehave() {
+ try {
+ foo = "1" ;
+ } catch (e) {
+ print ('2' );
+ rethrow ;
+ }
+}
+
+void main() {
+ try {
+ misbehave();
+ } catch (e) {
+ print ('3' );
+ }
+}
+
+
+
+finally
+
+Dart的finally用来执行那些无论异常是否发生都执行的操作。
+
+
+final foo = '' ;
+
+void misbehave() {
+ try {
+ foo = "1" ;
+ } catch (e) {
+ print ('2' );
+ }
+}
+
+void main() {
+ try {
+ misbehave();
+ } catch (e) {
+ print ('3' );
+ } finally {
+ print ('4' );
+ }
+}
+
+
+
+
+函数 Function
+
+ bool isNoble(int atomicNumber) {
+ return _nobleGases[atomicNumber] != null ;
+ }
+
+
+
+main()函数
+
+每个应用程序都必须有一个顶层main()函数,它可以作为应用程序的入口点。该main()函数返回void并具有List参数的可选参数。
+
+void main() {
+querySelector ('#sample_text_id' )
+ ..text = 'Click me!'
+ ..onClick.listen(reverseText);
+}
+
+
+
+级联符号..允许您在同一个对象上进行一系列操作。除了函数调用之外,还可以访问同一对象上的字段。这通常会为您节省创建临时变量的步骤,并允许您编写更流畅的代码。
+
+querySelector ('#confirm' )
+ ..text = 'Confirm'
+ ..classes.add('important' )
+ ..onClick.listen((e) => window .alert('Confirmed!' ));
+
+
+
+var button = querySelector ('#confirm' );
+button.text = 'Confirm' ;
+button.classes.add('important' );
+button.onClick.listen((e) => window .alert('Confirmed!' ));
+
+
+
+final addressBook = (AddressBookBuilder()
+..name = 'jenny'
+..email = 'jenny@example.com'
+..phone = (PhoneNumberBuilder()
+ ..number = '415-555-0100'
+ ..label = 'home' )
+ .build())
+.build();
+
+
+
+当返回值是void时不能构建级联。 例如,以下代码失败:
+
+var sb = StringBuffer ();
+sb.write('foo' )
+ ..write('bar' );
+
+
+
+注意: 严格地说,级联的..符号不是操作符。它只是Dart语法的一部分。
+
+
+
+可选参数
+
+可选的命名参数, 定义函数时,使用{param1, param2, …},用于指定命名参数。例如:
+
+
+void enableFlags({bool bold, bool hidden}) {
+
+}
+
+enableFlags(bold: true , hidden: false );
+
+
+可选的位置参数,用[]它们标记为可选的位置参数:
+
+String say(String from, String msg, [String device]) {
+ var result = '$from says $msg ' ;
+ if (device != null ) {
+ result = '$result with a $device ' ;
+ }
+ return result;
+}
+
+
+ say('Bob' , 'Howdy' );
+
+
+ say('Bob' , 'Howdy' , 'smoke signal' );
+
+
+
+默认参数
+
+函数可以使用=为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。
+下面是为命名参数设置默认值的示例:
+
+
+void enableFlags2({bool bold = false , bool hidden = false }) {
+
+}
+
+
+enableFlags2(bold: true );
+
+
+ String say(String from, String msg,
+ [String device = 'carrier pigeon' , String mood]) {
+ var result = '$from says $msg ' ;
+ if (device != null ) {
+ result = '$result with a $device ' ;
+ }
+ if (mood != null ) {
+ result = '$result (in a $mood mood)' ;
+ }
+ return result;
+}
+
+
+say('Bob' , 'Howdy' );
+
+
+您还可以将list或map作为默认值传递。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认list和gifts参数的默认map。
+
+
+void doStuff(
+ {List <int > list = const [1 , 2 , 3 ],
+ Map <String , String > gifts = const {'first' : 'paper' ,
+ 'second' : 'cotton' , 'third' : 'leather'
+ }}) {
+ print ('list: $list ' );
+ print ('gifts: $gifts ' );
+}
+
+
+
+作为一个类对象的功能
+
+void printElement(int element) {
+ print (element);
+}
+
+var list = [1 , 2 , 3 ];
+
+
+list.forEach(printElement);
+
+
+ var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!' ;
+assert (loudify('hello' ) == '!!! HELLO !!!' );
+
+
+
+匿名函数
+
+大多数函数都能被命名为匿名函数,如 main() 或 printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建lambda或闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。
+一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。
+下面的代码块包含函数的主体:
+
+([[Type ] param1[, …]]) {
+ codeBlock;
+};
+
+
+下面的示例定义了一个具有无类型参数的匿名函数item,该函数被list中的每个item调用,输出一个字符串,该字符串包含指定索引处的值。
+
+ var list = ['apples' , 'bananas' , 'oranges' ];
+list.forEach((item) {
+ print ('${list.indexOf(item)} : $item ' );
+});
+
+
+如果函数只包含一条语句,可以使用箭头符号=>来缩短它, 比如上面的例2可以简写成:
+
+list.forEach((item) => print ('${list.indexOf(item)} : $item ' ));
+
+
+
+返回值
+
+所有函数都返回一个值,如果没有指定返回值,则语句return null,隐式地附加到函数体。
+
+foo() {}
+assert (foo() == null );
+
+
+
+类(Classes)
+
+
+对象
+
+Dart 是一种面向对象的语言,并且支持基于mixin的继承方式。
+Dart 语言中所有的对象都是某一个类的实例,所有的类有同一个基类--Object。
+基于mixin的继承方式具体是指:一个类可以继承自多个父类。
+使用new语句来构造一个类,构造函数的名字可能是ClassName,也可以是ClassName.identifier, 例如:
+
+ var jsonData = JSON.decode('{"x":1, "y":2}' );
+
+
+var p1 = new Point(2 , 2 );
+
+
+var p2 = new Point.fromJson(jsonData);
+
+
+
+var p = new Point(2 , 2 );
+
+
+p.y = 3 ;
+
+
+assert (p.y == 3 );
+
+
+num distance = p.distanceTo(new Point(4 , 4 ));
+
+
+使用?.来确认前操作数不为空, 常用来替代. , 避免左边操作数为null引发异常。
+
+
+p?.y = 4 ;
+
+
+使用const替代new来创建编译时的常量构造函数。
+
+ var p = const ImmutablePoint(2 , 2 );
+
+
+使用runtimeType方法,在运行中获取对象的类型。该方法将返回Type 类型的变量。
+
+ print ('The type of a is ${a.runtimeType} ' );
+
+
+
+实例化变量(Instance variables)
+
+在类定义中,所有没有初始化的变量都会被初始化为null。
+
+ class Point {
+ num x;
+ num y;
+ num z = 0 ;
+}
+
+
+类定义中所有的变量, Dart语言都会隐式的定义 setter 方法,针对非空的变量会额外增加 getter 方法。
+
+class Point {
+num x;
+num y;
+}
+
+main() {
+ var point = new Point();
+ point.x = 4 ;
+ assert (point.x == 4 );
+ assert (point.y == null );
+}
+
+
+
+构造函数(Constructors)
+
+声明一个和类名相同的函数,来作为类的构造函数。
+
+ class Point {
+ num x;
+ num y;
+
+ Point(num x, num y) {
+
+ this .x = x;
+ this .y = y;
+ }
+}
+
+
+this关键字指向了当前类的实例, 上面的代码可以简化为:
+
+ class Point {
+ num x;
+ num y;
+
+
+
+ Point(this .x, this .y);
+}
+
+
+
+构造函数不能继承(Constructors aren’t inherited)
+
+Dart 语言中,子类不会继承父类的命名构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。
+
+
+
+命名的构造函数(Named constructors)
+
+使用命名构造函数从另一类或现有的数据中快速实现构造函数。
+
+class Point {
+ num x;
+ num y;
+
+ Point(this .x, this .y);
+
+
+ Point.fromJson(Map json) {
+ x = json['x' ];
+ y = json['y' ];
+ }
+}
+
+
+构造函数不能被继承,父类中的命名构造函数不能被子类继承。如果想要子类也拥有一个父类一样名字的构造函数,必须在子类是实现这个构造函数。
+
+
+
+调用父类的非默认构造函数
+
+
+默认情况下,子类只能调用父类的无名,无参数的构造函数; 父类的无名构造函数会在子类的构造函数前调用; 如果initializer list 也同时定义了,则会先执行initializer list 中的内容,然后在执行父类的无名无参数构造函数,最后调用子类自己的无名无参数构造函数。即下面的顺序:
+
+initializer list(初始化列表)
+super class’s no-arg constructor(父类无参数构造函数)
+main class’s no-arg constructor (主类无参数构造函数)
+
+
+
+如果父类不显示提供无名无参数构造函数的构造函数,在子类中必须手打调用父类的一个构造函数。这种情况下,调用父类的构造函数的代码放在子类构造函数名后,子类构造函数体前,中间使用:(colon) 分割。
+
+
+class Person {
+ String firstName;
+
+Person.fromJson(Map data) {
+ print ('in Person' );
+}
+}
+
+class Employee extends Person {
+
+super .fromJson(data)
+Employee.fromJson(Map data) : super .fromJson(data) {
+ print ('in Employee' );
+}
+}
+
+main() {
+var emp = new Employee.fromJson({});
+
+
+
+
+if (emp is Person) {
+
+ emp.firstName = 'Bob' ;
+}
+(emp as Person).firstName = 'Bob' ;
+}
+
+
+
+
+初始化列表
+
+除了调用父类的构造函数,也可以通过初始化列表在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示:
+
+class Point {
+num x;
+num y;
+
+Point(this .x, this .y);
+
+
+Point.fromJson(Map jsonMap)
+: x = jsonMap['x' ],
+ y = jsonMap['y' ] {
+ print ('In Point.fromJson(): ($x , $y )' );
+}
+}
+
+注意:上述代码,初始化程序无法访问 this 关键字。
+
+
+静态构造函数
+
+如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。为此,需要定义一个 const 构造函数并确保所有的实例变量都是 final 的。
+
+class ImmutablePoint {
+ final num x;
+ final num y;
+ const ImmutablePoint(this .x, this .y);
+ static final ImmutablePoint origin = const ImmutablePoint(0 , 0 );
+}
+
+
+
+
+重定向构造函数
+
+有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:分隔。
+
+class Point {
+ num x;
+ num y;
+
+
+ Point(this .x, this .y) {
+ print ("Point($x , $y )" );
+ }
+
+
+ Point.alongXAxis(num x) : this (x, 0 );
+}
+
+void main() {
+ var p1 = new Point(1 , 2 );
+ var p2 = new Point.alongXAxis(4 );
+}
+
+
+
+常量构造函数
+
+
+
+工厂构造函数
+
+当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,工厂构造函数可能从缓存返回实例,或者它可能返回子类型的实例。 下面的示例演示一个工厂构造函数从缓存返回的对象:
+
+class Logger {
+final String name;
+bool mute = false ;
+
+
+static final Map <String , Logger> _cache = <String , Logger>{};
+
+factory Logger(String name) {
+ if (_cache.containsKey(name)) {
+ return _cache[name];
+ } else {
+ final logger = new Logger._internal(name);
+ _cache[name] = logger;
+ return logger;
+ }
+ }
+
+ Logger._internal(this .name);
+
+ void log(String msg) {
+ if (!mute) {
+ print (msg);
+ }
+ }
+
+}
+
+注意:工厂构造函数不能用 this。
+
+
+方法
+
+
+
+实例方法
+
+对象的实例方法可以访问实例变量和 this 。以下示例中的 distanceTo() 方法是实例方法的一个例子:
+
+import 'dart:math' ;
+
+class Point {
+ num x;
+ num y;
+ Point(this .x, this .y);
+
+ num distanceTo(Point other) {
+ var dx = x - other.x;
+ var dy = y - other.y;
+ return sqrt(dx * dx + dy * dy);
+ }
+}
+
+
+
+setters 和 Getters
+
+是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的 getter 方法,合适的话可能还会有 setter 方法。你可以通过实现 getters 和 setters 来创建附加属性,也就是直接使用 get 和 set 关键词:
+
+class Rectangle {
+num left;
+num top;
+num width;
+num height;
+
+Rectangle(this .left, this .top, this .width, this .height);
+
+
+num get right => left + width;
+set right(num value) => left = value - width;
+num get bottom => top + height;
+set bottom(num value) => top = value - height;
+}
+
+main() {
+var rect = new Rectangle(3 , 4 , 20 , 15 );
+assert (rect.left == 3 );
+rect.right = 12 ;
+assert (rect.left == -8 );
+}
+
+
+借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。
+注: 不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。
+
+
+
+抽象方法
+
+Instance , getter 和 setter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。要创建一个抽象方法,使用分号(;)代替方法体:
+
+abstract class Doer {
+
+ void doSomething();
+}
+
+class EffectiveDoer extends Doer {
+ void doSomething() {
+
+ }
+}
+
+
+
+
+枚举类型
+
+枚举类型,通常被称为 enumerations 或 enums ,是一种用来代表一个固定数量的常量的特殊类。
+声明一个枚举类型需要使用关键字 enum :
+
+enum Color {
+ red,
+ green,
+ blue
+}
+
+
+
+在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。
+
+assert (Color.red.index == 0 );
+assert (Color.green.index == 1 );
+assert (Color.blue.index == 2 );
+
+
+要得到枚举列表的所有值,可使用枚举的 values 常量。
+
+List <Color> colors = Color.values;
+assert (colors[2 ] == Color.blue);
+
+
+
+
+
+你可以在 switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告:
+
+ ***枚举类型有以下限制***
+ * 你不能在子类中混合或实现一个枚举。
+ * 你不能显式实例化一个枚举。
+ enum Color {
+ red,
+ green,
+ blue
+ }
+
+ Color aColor = Color.blue;
+ switch (aColor) {
+ case Color.red:
+ print ('Red as roses!' );
+ break ;
+
+ case Color.green:
+ print ('Green as grass!' );
+ break ;
+
+ default :
+ print (aColor);
+ }
+
+
+
+
+为类添加特征:mixins
+
+mixins 是一种多类层次结构的类的代码重用。
+要使用 mixins ,在 with 关键字后面跟一个或多个 mixin 的名字。下面的例子显示了两个使用mixins的类:
+
+ class Musician extends Performer with Musical {
+
+}
+
+class Maestro extends Person with Musical ,
+ Aggressive , Demented {
+
+ Maestro(String maestroName) {
+ name = maestroName;
+ canConduct = true ;
+ }
+}
+
+
+要实现 mixin ,就创建一个继承 Object 类的子类,不声明任何构造函数,不调用 super 。例如:
+
+abstract class Musical {
+bool canPlayPiano = false ;
+bool canCompose = false ;
+bool canConduct = false ;
+
+ void entertainMe() {
+ if (canPlayPiano) {
+ print ('Playing piano' );
+ } else if (canConduct) {
+ print ('Waving hands' );
+ } else {
+ print ('Humming to self' );
+ }
+ }
+}
+
+
+
+类的变量和方法
+
+使用 static 关键字来实现类变量和类方法。
+只有当静态变量被使用时才被初始化。
+静态变量, 静态变量(类变量)对于类状态和常数是有用的:
+
+class Color {
+ static const red = const Color('red' );
+ final String name;
+ const Color(this .name);
+}
+
+main() {
+ assert (Color.red.name == 'red' );
+}
+
+
+静态方法, 静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如:
+
+ import 'dart:math' ;
+
+class Point {
+ num x;
+ num y;
+ Point(this .x, this .y);
+
+ static num distanceBetween(Point a, Point b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return sqrt(dx * dx + dy * dy);
+ }
+}
+
+main() {
+ var a = new Point(2 , 2 );
+ var b = new Point(4 , 4 );
+ var distance = Point.distanceBetween(a, b);
+ assert (distance < 2.9 && distance > 2.8 );
+}
+
+
+
+注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。
+你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。
+
+抽象类
+
+使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象中也包含一些实现。如果你想让你的抽象类被实例化,请定义一个 工厂构造函数 。
+抽象类通常包含 抽象方法。下面是声明一个含有抽象方法的抽象类的例子:
+
+
+abstract class AbstractContainer {
+
+
+void updateChildren();
+}
+
+
+下面的类不是抽象类,因此它可以被实例化,即使定义了一个抽象方法:
+
+ class SpecializedContainer extends AbstractContainer {
+
+
+ void updateChildren() {
+
+ }
+
+
+void doSomething();
+}
+
+类-隐式接口
+
+
+
+class Person {
+
+ final _name;
+
+
+ Person(this ._name);
+
+
+ String greet(who) => 'Hello, $who . I am $_name .' ;
+}
+
+
+class Imposter implements Person {
+
+ final _name = "" ;
+
+ String greet(who) => 'Hi $who . Do you know who I am?' ;
+}
+
+greetBob(Person person) => person.greet('bob' );
+
+main() {
+ print (greetBob(new Person('kathy' )));
+ print (greetBob(new Imposter()));
+}
+
+
+
+class Point implements Comparable , Location {
+
+}
+
+
+
+类-扩展一个类
+
+使用 extends 创建一个子类,同时 supper 将指向父类:
+
+ class Television {
+ void turnOn() {
+ _illuminateDisplay();
+ _activateIrSensor();
+ }
+
+ }
+
+ class SmartTelevision extends Television {
+
+ void turnOn() {
+ super .turnOn();
+ _bootNetworkInterface();
+ _initializeMemory();
+ _upgradeApps();
+ }
+
+ }
+
+
+子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法 noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。
+
+ class A {
+
+ void noSuchMethod(Invocation mirror) {
+ print ('You tried to use a non-existent member:' +
+ '${mirror.memberName} ' );
+ }
+ }
+
+
+
+你可以使用 @override 注释来表明你重写了一个成员。
+
+ class A {
+ @override
+ void noSuchMethod(Invocation mirror) {
+
+ }
+ }
+
+
+
+如果你用 noSuchMethod() 实现每一个可能的 getter 方法,setter 方法和类的方法,那么你可以使用 @proxy 标注来避免警告。
+
+@proxy
+ class A {
+ void noSuchMethod(Invocation mirror) {
+
+ }
+ }
+
+库和可见性
+
+
+import,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。
+
+
+库可以分布式使用包。见 Pub Package and Asset Manager 中有关pub(SDK中的一个包管理器)。
+
+
+使用库
+
+使用 import 来指定如何从一个库命名空间用于其他库的范围。
+使用 import 来指定如何从一个库命名空间用于其他库的范围。
+
+ import 'dart:html' ;
+
+
+唯一需要 import 的参数是一个指向库的 URI。对于内置库,URI中具有特殊dart:scheme。对于其他库,你可以使用文件系统路径或package:scheme。包 package:scheme specifies libraries ,如pub工具提供的软件包管理器库。例如:
+
+import 'dart:io' ;
+import 'package:mylib/mylib.dart' ;
+import 'package:utils/utils.dart' ;
+
+
+
+指定库前缀
+
+如果导入两个库是有冲突的标识符,那么你可以指定一个或两个库的前缀。例如,如果 library1 和 library2 都有一个元素类,那么你可能有这样的代码:
+
+import 'package:lib1/lib1.dart' ;
+import 'package:lib2/lib2.dart' as lib2;
+
+var element1 = new Element ();
+var element2 =
+new lib2.Element ();
+
+
+
+导入部分库
+
+如果想使用的库一部分,你可以选择性导入库。例如:
+
+
+import 'package:lib1/lib1.dart' show foo;
+
+
+import 'package:lib2/lib2.dart' hide foo;
+
+
+
+
+延迟加载库
+
+
+* 延迟(deferred)加载(也称为延迟(lazy)加载)允许应用程序按需加载库。下面是当你可能会使用延迟加载某些情况:
+
+ * 为了减少应用程序的初始启动时间;
+ * 执行A / B测试-尝试的算法的替代实施方式中;
+ * 加载很少使用的功能,例如可选的屏幕和对话框。
+
+
+
+* 为了延迟加载一个库,你必须使用 deferred as 先导入它。
+
+``` dart
+import 'package:deferred/hello.dart' deferred as hello;
+```
+
+* 当需要库时,使用该库的调用标识符调用 LoadLibrary()。
+``` dart
+greet() async {
+await hello.loadLibrary();
+hello.printGreeting();
+}
+```
+* 在前面的代码,在库加载好之前,await关键字都是暂停执行的。有关 async 和 await 见 asynchrony support 的更多信息。
+您可以在一个库调用 LoadLibrary() 多次都没有问题。该库也只被加载一次。
+
+* 当您使用延迟加载,请记住以下内容:
+
+ * 延迟库的常量在其作为导入文件时不是常量。记住,这些常量不存在,直到迟库被加载完成。
+ * 你不能在导入文件中使用延迟库常量的类型。相反,考虑将接口类型移到同时由延迟库和导入文件导入的库。
+ * Dart隐含调用LoadLibrary()插入到定义deferred as namespace。在调用LoadLibrary()函数返回一个Future。
+
+
+
+库的实现
+
+用 library 来来命名库,用part来指定库中的其他文件。 注意:不必在应用程序中(具有顶级main()函数的文件)使用library,但这样做可以让你在多个文件中执行应用程序。
+
+
+
+声明库
+
+利用library identifier(库标识符)指定当前库的名称:
+
+
+library ballgame;
+
+
+import 'dart:html' ;
+
+
+
+
+
+关联文件与库
+
+library ballgame;
+
+import 'dart:html' ;
+
+
+part 'ball.dart' ;
+part 'util.dart' ;
+
+
+
+
+第二个文件ball.dart,实现了球赛库的一部分:
+
+part of ballgame;
+
+
+
+
+第三个文件,util.dart,实现了球赛库的其余部分:
+
+part of ballgame;
+
+
+
+
+
+重新导出库(Re-exporting libraries)
+*可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集。
+
+library french;
+
+hello() => print ('Bonjour!' );
+goodbye() => print ('Au Revoir!' );
+
+
+library togo;
+
+import 'french.dart' ;
+export 'french.dart' show hello;
+
+
+import 'togo.dart' ;
+
+void main() {
+ hello();
+ goodbye();
+}
+
+
+
+
+异步的支持
+
+
+Dart 添加了一些新的语言特性用于支持异步编程。最通常使用的特性是 async 方法和 await 表达式。Dart 库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成
+
+
+当你需要使用 Future 来表示一个值时,你有两个选择。
+
+使用 async 和 await
+使用 Future API
+
+
+
+同样的,当你需要从 Stream 获取值的时候,你有两个选择。
+
+
+使用 async 和一个异步的 for 循环 (await for)
+使用 Stream API
+
+使用 async 和 await 的代码是异步的,不过它看起来很像同步的代码。比如这里有一段使用 await 等待一个异步函数结果的代码:
+
+await lookUpVersion()
+
+
+要使用 await,代码必须用 await 标记
+
+ checkVersion() async {
+ var version = await lookUpVersion();
+ if (version == expectedVersion) {
+
+ } else {
+
+ }
+ }
+
+
+你可以使用 try, catch, 和 finally 来处理错误并精简使用了 await 的代码。
+
+ try {
+ server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044 );
+ } catch (e) {
+
+ }
+
+
+
+声明异步函数
+
+一个异步函数是一个由 async 修饰符标记的函数。虽然一个异步函数可能在操作上比较耗时,但是它可以立即返回-在任何方法体执行之前。
+
+checkVersion() async {
+
+}
+
+lookUpVersion() async => ;
+
+
+在函数中添加关键字 async 使得它返回一个 Future,比如,考虑一下这个同步函数,它将返回一个字符串。
+String lookUpVersionSync() => '1.0.0';
+如果你想更改它成为异步方法-因为在以后的实现中将会非常耗时-它的返回值是一个 Future 。
+Future lookUpVersion() async => '1.0.0';
+请注意函数体不需要使用 Future API,如果必要的话 Dart 将会自己创建 Future 对象
+
+
+
+使用带 future 的 await 表达式
+
+await expression
+
+
+在异步方法中你可以使用 await 多次。比如,下列代码为了得到函数的结果一共等待了三次。
+
+var entrypoint = await findEntrypoint();
+var exitCode = await runExecutable(entrypoint, args);
+await flushThenExit(exitCode);
+
+
+
+在 await 表达式中, 表达式 的值通常是一个 Future 对象;如果不是,那么这个值会自动转为 Future。这个 Future 对象表明了表达式应该返回一个对象。await 表达式 的值就是返回的一个对象。在对象可用之前,await 表达式将会一直处于暂停状态。
+
+
+如果 await 没有起作用,请确认它是一个异步方法。比如,在你的 main() 函数里面使用await,main() 的函数体必须被 async 标记:
+
+
+main() async {
+ checkVersion();
+ print ('In main: version is ${await lookUpVersion()} ' );
+}
+
+
+
+
+结合 streams 使用异步循环
+
+await for (variable declaration in expression) {
+
+}
+
+
+
+表达式 的值必须有Stream 类型(流类型)。执行过程如下:
+
+在 stream 发出一个值之前等待
+执行 for 循环的主体,把变量设置为发出的值。
+重复 1 和 2,直到 Stream 关闭
+
+
+
+如果要停止监听 stream ,你可以使用 break 或者 return 语句,跳出循环并取消来自 stream 的订阅 。
+
+
+如果一个异步 for 循环没有正常运行,请确认它是一个异步方法。 比如,在应用的 main() 方法中使用异步的 for 循环时,main() 的方法体必须被 async 标记。
+
+
+main() async {
+ ...
+
+ await for (var request in requestServer) {
+ handleRequest(request);
+ }
+
+ ...
+}
+
+
+
+更多关于异步编程的信息,请看 dart:async 库部分的介绍。你也可以看文章 Dart Language Asynchrony Support: Phase 1
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/dart/syntax.json b/en-us/blog/dart/syntax.json
new file mode 100644
index 0000000..4418c0f
--- /dev/null
+++ b/en-us/blog/dart/syntax.json
@@ -0,0 +1,6 @@
+{
+ "filename": "syntax.md",
+ "__html": "Dart语法学习 \n目录 \n\n参考资料 \n语言特性 \n关键字 \n变量与常量 \n数据类型 \n运算符 operators \n控制流程语句 \n异常 Exceptions \n函数 Function \n类 Class \n类-方法 \n类-抽象类 \n类-隐式接口 \n类-扩展一个类(重写) \n库和可见性 \n异步支持 \n \n参考资料 \n\n语言特性 \n\n\nDart所有的东西都是对象, 即使是数字numbers、函数function、null也都是对象,所有的对象都继承自Object类。
\n \n\nDart动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)。
\n \n\nDart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
\n \n\nDart中的类和接口是统一的,类即接口,你可以继承一个类,也可以实现一个类(接口),自然也包含了良好的面向对象和并发编程的支持。
\n \n\nDart 提供了顶级函数(如:main())。
\n \n\nDart 没有 public、private、protected 这些关键字,变量名以"_"开头意味着对它的 lib 是私有的。
\n \n\n没有初始化的变量都会被赋予默认值 null。
\n \n\nfinal的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const。
\n \n\n编程语言并不是孤立存在的,Dart也是这样,他由语言规范、虚拟机、类库和工具等组成:
\n\nSDK:SDK 包含 Dart VM、dart2js、Pub、库和工具。 \nDartium:内嵌 Dart VM 的 Chromium ,可以在浏览器中直接执行 dart 代码。 \nDart2js:将 Dart 代码编译为 JavaScript 的工具。 \nDart Editor:基于 Eclipse 的全功能 IDE,并包含以上所有工具。支持代码补全、代码导航、快速修正、重构、调试等功能。 \n \n \n \n关键字(56个) \n\n\n\n关键字 \n- \n- \n- \n \n \n\n\nabstract \ndo \nimport \nsuper \n \n\nas \ndynamic \nin \nswitch \n \n\nassert \nelse \ninterface \nsync \n \n\nenum \nimplements \nis \nthis \n \n\nasync \nexport \nlibrary \nthrow \n \n\nawait \nexternal \nmixin \ntrue \n \n\nbreak \nextends \nnew \ntry \n \n\ncase \nfactory \nnull \ntypedef \n \n\ncatch \nfalse \noperator \nvar \n \n\nclass \nfinal \npart \nvoid \n \n\nconst \nfinally \nrethrow \nwhile \n \n\ncontinue \nfor \nreturn \nwith \n \n\ncovariant \nget \nset \nyield \n \n\ndefault \nif \nstatic \ndeferred \n \n \n
\n变量与常量 \n\n变量声明与初始化 \n \n\n调用的变量name包含对String值为“张三” 的对象的引用,name推断变量的类型是String,但可以通过指定它来更改该类型,如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字。 \n \n \n var name = ‘Bob’; \n Object name = '张三' ;\n dynamic name = '李四' ;\n\n \n String name = 'Bob' ;\n
\n\n默认值 \n \n\n未初始化的变量的初始值为null(包括数字),因此数字、字符串都可以调用各种方法 \n \n \n int lineCount;\n \n assert (lineCount == null );\n print (lineCount); \n
\n\n\nfinal and const
\n\n \n final String outSideFinalName\n\n
\n\n被final或者const修饰的变量,变量类型可以省略,建议指定数据类型。 \n \n\nfinal name = \"Bob\" ;\nfinal String name1 = \"张三\" ;\n\nconst name2 = \"alex\" ;\nconst String name3 = \"李四\" ;\n
\n\n被 final 或 const 修饰的变量无法再去修改其值。 \n \nfinal String outSideFinalName = \"Alex\" ;\n\n\noutSideFinalName = \"Bill\" ;\n\nconst String outSideName = 'Bill' ;\n\n\n\n
\n\nflnal 或者 const 不能和 var 同时使用 \n \n\nconst var String outSideName = 'Bill' ;\n\n\nfinal var String name = 'Lili' ; \n
\n\n常量如果是类级别的,请使用 static const \n \n\nstatic const String name3 = 'Tom' ;\n\n\n\n\n\n
\n\nconst speed = 100 ; \nconst double distance = 2.5 * speed; \n\nfinal speed2 = 100 ; \nfinal double distance2 = 2.5 * speed2; \n\n
\n\nconst关键字不只是声明常数变量,您也可以使用它来创建常量值,以及声明创建常量值的构造函数,任何变量都可以有一个常量值。 \n \n\n\nvar varList = const []; \nfinal finalList = const []; \nconst constList = const []; \n\n\n\nvarList = [\"haha\" ];\n\n\n\n\n\n\n\n
\n\n在常量表达式中,该运算符的操作数必须为'bool'、'num'、'String'或'null', const常量必须用conat类型的值初始化。 \n \nconst String outSideName = 'Bill' ;\nfinal String outSideFinalName = 'Alex' ;\nconst String outSideName2 = 'Tom' ;\n\nconst aConstList = const ['1' , '2' , '3' ];\n\n\n\nconst validConstString = '$outSideName $outSideName2 $aConstList ' ;\n\n\n\nconst validConstString = '$outSideName $outSideName2 $outSideFinalName ' ;\n\nvar outSideVarName='Cathy' ;\n\n\nconst validConstString = '$outSideName $outSideName2 $outSideVarName ' ;\n\n\nconst String outSideConstName = 'Joy' ;\nconst validConstString = '$outSideName $outSideName2 $outSideConstName ' ;\n\n
\n \n \n数据类型 \n\n\nnum
\n\n\nnum 是数字类型的父类,有两个子类 int 和 double。
\n \n\nint 根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。
\n \n\ndouble 64位(双精度)浮点数,如IEEE 754标准所规定。
\n \n \nint a = 1 ;\nprint (a);\n\ndouble b = 1.12 ;\nprint (b);\n\n\nint one = int .parse('1' );\n\nprint (one + 2 );\n\n\nvar onePointOne = double .parse('1.1' );\n\nprint (onePointOne + 2 );\n\n\nString oneAsString = 1. toString();\n\n\n\nprint ('$oneAsString + 2' );\n\nprint ('$oneAsString 2' );\n\n\nString piAsString = 3.14159 .toStringAsFixed(2 );\n\nprint (piAsString);\n\nString aString = 1.12618 .toStringAsFixed(2 );\n\nprint (aString);\n
\n \n\nString
\n\n\nDart里面的String是一系列 UTF-16 代码单元。
\n \n\nDart里面的String是一系列 UTF-16 代码单元。
\n \n\n单引号或者双引号里面嵌套使用引号。
\n \n\n用 或{} 来计算字符串中变量的值,需要注意的是如果是表达式需要${表达式}
\n \n \nString singleString = 'abcdddd' ;\nString doubleString = \"abcsdfafd\" ;\n\nString sdString = '$singleString a \"bcsd\" ${singleString} ' ;\nString dsString = \"abc 'aaa' $sdString \" ;\nprint (sdString);\nprint (dsString);\n\n\nString singleString = 'aaa' ;\nString doubleString = \"bbb\" ;\n\nString sdString = '$singleString a \"bbb\" ${doubleString} ' ;\n\nprint (sdString);\n\n\nString dsString = \"${singleString.toUpperCase()} abc 'aaa' $doubleString .toUpperCase()\" ;\n\n可以看出 ”$doubleString.toUpperCase()“ 没有加“{}“,导致输出结果是”bbb.toUpperCase()“\nprint (dsString);\n
\n \n\nbool
\n\nDart 是强 bool 类型检查,只有bool 类型的值是true 才被认为是true。 \n只有两个对象具有bool类型:true和false,它们都是编译时常量。 \nDart的类型安全意味着您不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代码, 相反Dart使用的是显式的检查值。 \nassert 是语言内置的断言函数,仅在检查模式下有效在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。 \n \n \nvar fullName = '' ;\nassert (fullName.isEmpty);\n\n\nvar hitPoints = 0 ;\nassert (hitPoints <= 0 );\n\n\nvar unicorn;\nassert (unicorn == null );\n\n\nvar iMeantToDoThis = 0 / 0 ;\nassert (iMeantToDoThis.isNaN);\n\n
\n \n\nList集合
\n\n在Dart中,数组是List对象,因此大多数人只是将它们称为List。Dart list文字看起来像JavaScript数组文字 \n \n \nList list = [10 , 7 , 23 ];\n\nprint (list);\n\n\nvar fruits = new List ();\n\n\nfruits.add('apples' );\n\n\nfruits.addAll(['oranges' , 'bananas' ]);\n\nList subFruits = ['apples' , 'oranges' , 'banans' ];\n\nfruits.addAll(subFruits);\n\n\nprint (fruits);\n\n\nprint (fruits.length);\n\n\nprint (fruits.first);\n\n\nprint (fruits.last);\n\n\nprint (fruits[0 ]);\n\n\nprint (fruits.indexOf('apples' ));\n\n\nprint (fruits.removeAt(0 ));\n\n\n\nfruits.remove('apples' );\n\n\nfruits.removeLast();\n\n\nfruits.removeRange(start,end);\n\n\nfruits.removeWhere((item) => item.length >6 );\n\n\nfruits.clear();\n
\n\n\n注意事项:
\n\n可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,直接打印是地址值。 \n和java一样list里面的元素必须保持类型一致,不一致就会报错。 \n和java一样list的角标从0开始。 \n如果集合里面有多个相同的元素“X”, 只会删除集合中第一个改元素 \n \n \n \n \n\nMap集合
\n\n一般来说,map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。Dart支持map由map文字和map类型提供。 \n初始化Map方式一: 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。 \n \n\n\n\nMap companys = {'Alibaba' : '阿里巴巴' , 'Tencent' : '腾讯' , 'baidu' : '百度' };\n\nprint (companys);\n\n
\n\nMap schoolsMap = new Map ();\nschoolsMap['first' ] = '清华' ;\nschoolsMap['second' ] = '北大' ;\nschoolsMap['third' ] = '复旦' ;\n\nprint (schoolsMap);\n\nvar fruits = new Map ();\nfruits[\"first\" ] = \"apple\" ;\nfruits[\"second\" ] = \"banana\" ;\nfruits[\"fifth\" ] = \"orange\" ;\n\nprint (fruits);\n
\n\n\nvar aMap = new Map <int , String >();\n\n\naMap[1 ] = '小米' ;\n\n\n\naMap[1 ] = 'alibaba' ;\n\n\naMap[2 ] = 'alibaba' ;\n\n\naMap[3 ] = '' ;\n\n\naMap[4 ] = null ;\n\nprint (aMap);\n\n\nassert (aMap.containsKey(1 ));\n\n\naMap.remove(1 ); \n\nprint (aMap); \n
\n\n注意事项\n\nmap的key类型不一致也不会报错。 \n添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key,比如分别是 1,2,4,看起来有间隔,事实上添加到map的时候是{1:value,2:value,4:value} 这种形式。 \nmap里面的key不能相同。但是value可以相同,value可以为空字符串或者为null。 \n \n \n \n \n \n运算符 \n\n\n\n描述 \n操作符 \n \n \n\n\n一元后置操作符 \nexpr++ expr-- () [] . ?. \n \n\n一元前置操作符 \nexpr !expr ~expr ++expr --expr \n \n\n乘除 \n* / % ~/ \n \n\n加减 \n+ - \n \n\n位移 \n<< >> \n \n\n按位与 \n& \n \n\n按位或 \n \n \n\n按位异或 \n^ \n \n\n逻辑与 \n&& \n \n\n逻辑或 \n \n \n\n关系和类型判断 \n>= > <= < as is is! \n \n\n等 \n== != \n \n\n如果为空 \n?? \n \n\n条件表达式 \nexpr1 ? expr2 : expr3 \n \n\n赋值 \n= *= /= ~/= %= += -= <<= >>= &= ^= = ??= \n \n\n级联 \n.. \n \n \n
\n流程控制语句(Control flow statements) \n\nif...else \nfor \nwhile do-whild \nbreak continue \nbreak continue \nassert(仅在checked模式有效) \n \n异常(Exceptions) \n\n\nthrow
\n\nthrow new FormatException('Expected at least 1 section' );\n
\n\n throw 'Out of llamas!' ;\n
\n\n因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方 \n \ndistanceTo(Point other) =>\nthrow new UnimplementedError();\n
\n \n\ncatch
\n\n将可能出现异常的代码放置到try语句中,可以通过 on语句来指定需要捕获的异常类型,使用catch来处理异常。 \n \n try {\n breedMoreLlamas();\n} on OutOfLlamasException {\n \n buyMoreLlamas();\n} on Exception catch (e) {\n \n print ('Unknown exception: $e ' );\n} catch (e, s) {\n print ('Exception details:\\n $e ' );\n print ('Stack trace:\\n $s ' );\n}\n
\n \n\nrethrow
\n\nrethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用。 \n \nfinal foo = '' ;\n\nvoid misbehave() {\n try {\n foo = \"1\" ;\n } catch (e) {\n print ('2' );\n rethrow ;\n }\n}\n\nvoid main() {\n try {\n misbehave();\n } catch (e) {\n print ('3' );\n }\n}\n
\n \n\nfinally
\n\nDart的finally用来执行那些无论异常是否发生都执行的操作。 \n \n\nfinal foo = '' ;\n\nvoid misbehave() {\n try {\n foo = \"1\" ;\n } catch (e) {\n print ('2' );\n }\n}\n\nvoid main() {\n try {\n misbehave();\n } catch (e) {\n print ('3' );\n } finally {\n print ('4' ); \n }\n}\n\n
\n \n \n函数 Function \n\n bool isNoble(int atomicNumber) {\n return _nobleGases[atomicNumber] != null ;\n } \n
\n\n\nmain()函数
\n\n每个应用程序都必须有一个顶层main()函数,它可以作为应用程序的入口点。该main()函数返回void并具有List参数的可选参数。 \n \nvoid main() {\nquerySelector ('#sample_text_id' )\n ..text = 'Click me!' \n ..onClick.listen(reverseText);\n}\n\n
\n\n级联符号..允许您在同一个对象上进行一系列操作。除了函数调用之外,还可以访问同一对象上的字段。这通常会为您节省创建临时变量的步骤,并允许您编写更流畅的代码。 \n \nquerySelector ('#confirm' ) \n ..text = 'Confirm' \n ..classes.add('important' )\n ..onClick.listen((e) => window .alert('Confirmed!' ));\n\n
\n\nvar button = querySelector ('#confirm' );\nbutton.text = 'Confirm' ;\nbutton.classes.add('important' );\nbutton.onClick.listen((e) => window .alert('Confirmed!' ));\n\n
\n\nfinal addressBook = (AddressBookBuilder()\n..name = 'jenny' \n..email = 'jenny@example.com' \n..phone = (PhoneNumberBuilder()\n ..number = '415-555-0100' \n ..label = 'home' )\n .build())\n.build();\n\n
\n\n当返回值是void时不能构建级联。 例如,以下代码失败: \n \nvar sb = StringBuffer ();\nsb.write('foo' ) \n ..write('bar' ); \n\n
\n\n注意: 严格地说,级联的..符号不是操作符。它只是Dart语法的一部分。 \n \n \n\n可选参数
\n\n可选的命名参数, 定义函数时,使用{param1, param2, …},用于指定命名参数。例如: \n \n \nvoid enableFlags({bool bold, bool hidden}) {\n \n} \n\nenableFlags(bold: true , hidden: false );\n
\n\n可选的位置参数,用[]它们标记为可选的位置参数: \n \nString say(String from, String msg, [String device]) {\n var result = '$from says $msg ' ;\n if (device != null ) {\n result = '$result with a $device ' ;\n }\n return result;\n}\n
\n\n下面是一个不带可选参数调用这个函数的例子: \n \n say('Bob' , 'Howdy' ); \n
\n\n say('Bob' , 'Howdy' , 'smoke signal' ); \n
\n \n\n默认参数
\n\n函数可以使用=为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。 \n下面是为命名参数设置默认值的示例: \n \n\nvoid enableFlags2({bool bold = false , bool hidden = false }) {\n \n}\n\n\nenableFlags2(bold: true );\n
\n\n String say(String from, String msg,\n [String device = 'carrier pigeon' , String mood]) {\n var result = '$from says $msg ' ;\n if (device != null ) {\n result = '$result with a $device ' ;\n }\n if (mood != null ) {\n result = '$result (in a $mood mood)' ;\n }\n return result;\n}\n\n\nsay('Bob' , 'Howdy' ); \n
\n\n您还可以将list或map作为默认值传递。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认list和gifts参数的默认map。 \n \n \nvoid doStuff(\n {List <int > list = const [1 , 2 , 3 ],\n Map <String , String > gifts = const {'first' : 'paper' , \n 'second' : 'cotton' , 'third' : 'leather' \n }}) {\n print ('list: $list ' );\n print ('gifts: $gifts ' );\n}\n
\n \n\n作为一个类对象的功能
\n\n您可以将一个函数作为参数传递给另一个函数。 \n \nvoid printElement(int element) {\n print (element);\n}\n\nvar list = [1 , 2 , 3 ];\n\n\nlist.forEach(printElement);\n
\n\n var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!' ;\nassert (loudify('hello' ) == '!!! HELLO !!!' );\n
\n \n\n匿名函数
\n\n大多数函数都能被命名为匿名函数,如 main() 或 printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建lambda或闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。 \n一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。 \n下面的代码块包含函数的主体: \n \n([[Type ] param1[, …]]) { \n codeBlock; \n}; \n
\n\n下面的示例定义了一个具有无类型参数的匿名函数item,该函数被list中的每个item调用,输出一个字符串,该字符串包含指定索引处的值。 \n \n var list = ['apples' , 'bananas' , 'oranges' ];\nlist.forEach((item) {\n print ('${list.indexOf(item)} : $item ' );\n});\n
\n\n如果函数只包含一条语句,可以使用箭头符号=>来缩短它, 比如上面的例2可以简写成: \n \nlist.forEach((item) => print ('${list.indexOf(item)} : $item ' ));\n
\n \n\n返回值
\n\n所有函数都返回一个值,如果没有指定返回值,则语句return null,隐式地附加到函数体。 \n \nfoo() {}\nassert (foo() == null );\n
\n \n \n类(Classes) \n\n\n对象
\n\nDart 是一种面向对象的语言,并且支持基于mixin的继承方式。 \nDart 语言中所有的对象都是某一个类的实例,所有的类有同一个基类--Object。 \n基于mixin的继承方式具体是指:一个类可以继承自多个父类。 \n使用new语句来构造一个类,构造函数的名字可能是ClassName,也可以是ClassName.identifier, 例如: \n \n var jsonData = JSON.decode('{\"x\":1, \"y\":2}' );\n\n\nvar p1 = new Point(2 , 2 );\n\n\nvar p2 = new Point.fromJson(jsonData);\n\n
\n\n使用.(dot)来调用实例的变量或者方法。 \n \nvar p = new Point(2 , 2 );\n\n\np.y = 3 ;\n\n\nassert (p.y == 3 );\n\n\nnum distance = p.distanceTo(new Point(4 , 4 ));\n
\n\n使用?.来确认前操作数不为空, 常用来替代. , 避免左边操作数为null引发异常。 \n \n \np?.y = 4 ; \n
\n\n使用const替代new来创建编译时的常量构造函数。 \n \n var p = const ImmutablePoint(2 , 2 );\n
\n\n使用runtimeType方法,在运行中获取对象的类型。该方法将返回Type 类型的变量。 \n \n print ('The type of a is ${a.runtimeType} ' );\n
\n \n\n实例化变量(Instance variables)
\n\n在类定义中,所有没有初始化的变量都会被初始化为null。 \n \n class Point {\n num x; \n num y; \n num z = 0 ; \n}\n
\n\n类定义中所有的变量, Dart语言都会隐式的定义 setter 方法,针对非空的变量会额外增加 getter 方法。 \n \nclass Point {\nnum x;\nnum y;\n}\n\nmain() {\n var point = new Point();\n point.x = 4 ; \n assert (point.x == 4 ); \n assert (point.y == null ); \n}\n
\n \n\n构造函数(Constructors)
\n\n声明一个和类名相同的函数,来作为类的构造函数。 \n \n class Point {\n num x;\n num y;\n\n Point(num x, num y) {\n \n this .x = x;\n this .y = y;\n }\n}\n
\n\nthis关键字指向了当前类的实例, 上面的代码可以简化为: \n \n class Point {\n num x;\n num y;\n\n \n \n Point(this .x, this .y);\n}\n
\n \n\n构造函数不能继承(Constructors aren’t inherited)
\n\nDart 语言中,子类不会继承父类的命名构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。 \n \n \n\n命名的构造函数(Named constructors)
\n\n使用命名构造函数从另一类或现有的数据中快速实现构造函数。 \n \nclass Point {\n num x;\n num y;\n\n Point(this .x, this .y);\n\n \n Point.fromJson(Map json) {\n x = json['x' ];\n y = json['y' ];\n }\n}\n
\n\n构造函数不能被继承,父类中的命名构造函数不能被子类继承。如果想要子类也拥有一个父类一样名字的构造函数,必须在子类是实现这个构造函数。 \n \n \n\n调用父类的非默认构造函数
\n\n\n默认情况下,子类只能调用父类的无名,无参数的构造函数; 父类的无名构造函数会在子类的构造函数前调用; 如果initializer list 也同时定义了,则会先执行initializer list 中的内容,然后在执行父类的无名无参数构造函数,最后调用子类自己的无名无参数构造函数。即下面的顺序:
\n\ninitializer list(初始化列表) \nsuper class’s no-arg constructor(父类无参数构造函数) \nmain class’s no-arg constructor (主类无参数构造函数) \n \n \n\n如果父类不显示提供无名无参数构造函数的构造函数,在子类中必须手打调用父类的一个构造函数。这种情况下,调用父类的构造函数的代码放在子类构造函数名后,子类构造函数体前,中间使用:(colon) 分割。
\n \n \nclass Person {\n String firstName;\n\nPerson.fromJson(Map data) {\n print ('in Person' );\n}\n}\n\nclass Employee extends Person {\n\nsuper .fromJson(data)\nEmployee.fromJson(Map data) : super .fromJson(data) {\n print ('in Employee' );\n}\n}\n\nmain() {\nvar emp = new Employee.fromJson({});\n\n\n\n\nif (emp is Person) {\n \n emp.firstName = 'Bob' ;\n}\n(emp as Person).firstName = 'Bob' ;\n}\n\n
\n \n\n初始化列表
\n\n除了调用父类的构造函数,也可以通过初始化列表在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示: \n \nclass Point {\nnum x;\nnum y;\n\nPoint(this .x, this .y);\n\n\nPoint.fromJson(Map jsonMap)\n: x = jsonMap['x' ],\n y = jsonMap['y' ] {\n print ('In Point.fromJson(): ($x , $y )' );\n}\n}\n
\n注意:上述代码,初始化程序无法访问 this 关键字。
\n \n\n静态构造函数
\n\n如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。为此,需要定义一个 const 构造函数并确保所有的实例变量都是 final 的。 \n \nclass ImmutablePoint {\n final num x;\n final num y;\n const ImmutablePoint(this .x, this .y);\n static final ImmutablePoint origin = const ImmutablePoint(0 , 0 );\n}\n\n
\n \n\n重定向构造函数
\n\n有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:分隔。 \n \nclass Point {\n num x;\n num y;\n\n \n Point(this .x, this .y) {\n print (\"Point($x , $y )\" );\n }\n\n \n Point.alongXAxis(num x) : this (x, 0 );\n}\n\nvoid main() {\n var p1 = new Point(1 , 2 );\n var p2 = new Point.alongXAxis(4 );\n} \n
\n \n\n常量构造函数
\n\n \n\n工厂构造函数
\n\n当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,工厂构造函数可能从缓存返回实例,或者它可能返回子类型的实例。 下面的示例演示一个工厂构造函数从缓存返回的对象: \n \nclass Logger {\nfinal String name;\nbool mute = false ;\n\n\nstatic final Map <String , Logger> _cache = <String , Logger>{};\n\nfactory Logger(String name) {\n if (_cache.containsKey(name)) {\n return _cache[name];\n } else {\n final logger = new Logger._internal(name);\n _cache[name] = logger;\n return logger;\n }\n }\n\n Logger._internal(this .name);\n\n void log(String msg) {\n if (!mute) {\n print (msg);\n }\n }\n \n}\n
\n注意:工厂构造函数不能用 this。
\n \n \n方法 \n\n\n\n实例方法
\n\n对象的实例方法可以访问实例变量和 this 。以下示例中的 distanceTo() 方法是实例方法的一个例子: \n \nimport 'dart:math' ;\n\nclass Point {\n num x;\n num y;\n Point(this .x, this .y);\n\n num distanceTo(Point other) {\n var dx = x - other.x;\n var dy = y - other.y;\n return sqrt(dx * dx + dy * dy);\n }\n}\n
\n \n\nsetters 和 Getters
\n\n是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的 getter 方法,合适的话可能还会有 setter 方法。你可以通过实现 getters 和 setters 来创建附加属性,也就是直接使用 get 和 set 关键词: \n \nclass Rectangle {\nnum left;\nnum top;\nnum width;\nnum height;\n\nRectangle(this .left, this .top, this .width, this .height);\n\n\nnum get right => left + width;\nset right(num value) => left = value - width;\nnum get bottom => top + height;\nset bottom(num value) => top = value - height;\n}\n\nmain() {\nvar rect = new Rectangle(3 , 4 , 20 , 15 );\nassert (rect.left == 3 );\nrect.right = 12 ;\nassert (rect.left == -8 );\n}\n
\n\n借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。 \n注: 不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。 \n \n \n\n抽象方法
\n\nInstance , getter 和 setter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。要创建一个抽象方法,使用分号(;)代替方法体: \n \nabstract class Doer {\n \n void doSomething(); \n}\n\nclass EffectiveDoer extends Doer {\n void doSomething() {\n \n }\n}\n\n
\n \n\n枚举类型
\n\n枚举类型,通常被称为 enumerations 或 enums ,是一种用来代表一个固定数量的常量的特殊类。 \n声明一个枚举类型需要使用关键字 enum : \n \nenum Color {\n red,\n green,\n blue\n}\n\n
\n\n在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。 \n \nassert (Color.red.index == 0 );\nassert (Color.green.index == 1 );\nassert (Color.blue.index == 2 );\n
\n\n要得到枚举列表的所有值,可使用枚举的 values 常量。 \n \nList <Color> colors = Color.values;\nassert (colors[2 ] == Color.blue); \n\n
\n \n \n\n你可以在 switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告: \n \n ***枚举类型有以下限制***\n * 你不能在子类中混合或实现一个枚举。\n * 你不能显式实例化一个枚举。\n enum Color {\n red,\n green,\n blue\n }\n \n Color aColor = Color.blue;\n switch (aColor) {\n case Color.red:\n print ('Red as roses!' );\n break ;\n \n case Color.green:\n print ('Green as grass!' );\n break ;\n \n default : \n print (aColor); \n }\n\n
\n\n\n为类添加特征:mixins
\n\nmixins 是一种多类层次结构的类的代码重用。 \n要使用 mixins ,在 with 关键字后面跟一个或多个 mixin 的名字。下面的例子显示了两个使用mixins的类: \n \n class Musician extends Performer with Musical {\n \n}\n\nclass Maestro extends Person with Musical , \n Aggressive , Demented {\n\n Maestro(String maestroName) {\n name = maestroName;\n canConduct = true ;\n }\n}\n
\n\n要实现 mixin ,就创建一个继承 Object 类的子类,不声明任何构造函数,不调用 super 。例如: \n \nabstract class Musical {\nbool canPlayPiano = false ;\nbool canCompose = false ;\nbool canConduct = false ;\n\n void entertainMe() {\n if (canPlayPiano) {\n print ('Playing piano' );\n } else if (canConduct) {\n print ('Waving hands' );\n } else {\n print ('Humming to self' );\n }\n }\n}\n
\n \n\n类的变量和方法
\n\n使用 static 关键字来实现类变量和类方法。 \n只有当静态变量被使用时才被初始化。 \n静态变量, 静态变量(类变量)对于类状态和常数是有用的: \n \nclass Color {\n static const red = const Color('red' ); \n final String name; \n const Color(this .name); \n}\n\nmain() {\n assert (Color.red.name == 'red' );\n}\n
\n\n静态方法, 静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如: \n \n import 'dart:math' ;\n\nclass Point {\n num x;\n num y;\n Point(this .x, this .y);\n\n static num distanceBetween(Point a, Point b) {\n var dx = a.x - b.x;\n var dy = a.y - b.y;\n return sqrt(dx * dx + dy * dy);\n }\n}\n\nmain() {\n var a = new Point(2 , 2 );\n var b = new Point(4 , 4 );\n var distance = Point.distanceBetween(a, b);\n assert (distance < 2.9 && distance > 2.8 );\n}\n\n
\n\n注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。 \n你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。 \n \n抽象类 \n\n使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象中也包含一些实现。如果你想让你的抽象类被实例化,请定义一个 工厂构造函数 。 \n抽象类通常包含 抽象方法。下面是声明一个含有抽象方法的抽象类的例子: \n \n \nabstract class AbstractContainer {\n\n\nvoid updateChildren(); \n}\n
\n\n下面的类不是抽象类,因此它可以被实例化,即使定义了一个抽象方法: \n \n class SpecializedContainer extends AbstractContainer {\n \n\n void updateChildren() {\n \n }\n\n\nvoid doSomething();\n}\n
\n类-隐式接口 \n\n\n\nclass Person {\n \n final _name;\n\n \n Person(this ._name);\n\n \n String greet(who) => 'Hello, $who . I am $_name .' ;\n}\n\n\nclass Imposter implements Person {\n \n final _name = \"\" ;\n\n String greet(who) => 'Hi $who . Do you know who I am?' ;\n}\n\ngreetBob(Person person) => person.greet('bob' );\n\nmain() {\n print (greetBob(new Person('kathy' )));\n print (greetBob(new Imposter()));\n}\n\n
\n\nclass Point implements Comparable , Location {\n\n}\n
\n \n \n类-扩展一个类 \n\n使用 extends 创建一个子类,同时 supper 将指向父类: \n \n class Television {\n void turnOn() {\n _illuminateDisplay();\n _activateIrSensor();\n }\n \n }\n\n class SmartTelevision extends Television {\n \n void turnOn() {\n super .turnOn();\n _bootNetworkInterface();\n _initializeMemory();\n _upgradeApps();\n }\n \n }\n
\n\n子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法 noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。 \n \n class A {\n \n void noSuchMethod(Invocation mirror) {\n print ('You tried to use a non-existent member:' + \n '${mirror.memberName} ' );\n }\n }\n\n
\n\n你可以使用 @override 注释来表明你重写了一个成员。 \n \n class A {\n @override \n void noSuchMethod(Invocation mirror) {\n \n }\n }\n\n
\n\n如果你用 noSuchMethod() 实现每一个可能的 getter 方法,setter 方法和类的方法,那么你可以使用 @proxy 标注来避免警告。 \n \n@proxy \n class A {\n void noSuchMethod(Invocation mirror) {\n \n }\n }\n
\n库和可见性 \n\n\nimport,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。
\n \n\n库可以分布式使用包。见 Pub Package and Asset Manager 中有关pub(SDK中的一个包管理器)。
\n \n\n使用库
\n\n使用 import 来指定如何从一个库命名空间用于其他库的范围。 \n使用 import 来指定如何从一个库命名空间用于其他库的范围。 \n \n import 'dart:html' ;\n
\n\n唯一需要 import 的参数是一个指向库的 URI。对于内置库,URI中具有特殊dart:scheme。对于其他库,你可以使用文件系统路径或package:scheme。包 package:scheme specifies libraries ,如pub工具提供的软件包管理器库。例如: \n \nimport 'dart:io' ;\nimport 'package:mylib/mylib.dart' ;\nimport 'package:utils/utils.dart' ;\n
\n \n\n指定库前缀
\n\n如果导入两个库是有冲突的标识符,那么你可以指定一个或两个库的前缀。例如,如果 library1 和 library2 都有一个元素类,那么你可能有这样的代码: \n \nimport 'package:lib1/lib1.dart' ;\nimport 'package:lib2/lib2.dart' as lib2;\n\nvar element1 = new Element (); \nvar element2 =\nnew lib2.Element (); \n
\n \n\n导入部分库
\n\n如果想使用的库一部分,你可以选择性导入库。例如: \n \n \nimport 'package:lib1/lib1.dart' show foo;\n\n\nimport 'package:lib2/lib2.dart' hide foo;\n\n
\n \n\n延迟加载库
\n \n \n* 延迟(deferred)加载(也称为延迟(lazy)加载)允许应用程序按需加载库。下面是当你可能会使用延迟加载某些情况:\n\n * 为了减少应用程序的初始启动时间;\n * 执行A / B测试-尝试的算法的替代实施方式中;\n * 加载很少使用的功能,例如可选的屏幕和对话框。\n\n\n\n* 为了延迟加载一个库,你必须使用 deferred as 先导入它。\n\n``` dart\nimport 'package:deferred/hello.dart' deferred as hello;\n```\n\n* 当需要库时,使用该库的调用标识符调用 LoadLibrary()。\n``` dart\ngreet() async {\nawait hello.loadLibrary();\nhello.printGreeting();\n}\n```\n* 在前面的代码,在库加载好之前,await关键字都是暂停执行的。有关 async 和 await 见 asynchrony support 的更多信息。\n您可以在一个库调用 LoadLibrary() 多次都没有问题。该库也只被加载一次。\n\n* 当您使用延迟加载,请记住以下内容:\n\n * 延迟库的常量在其作为导入文件时不是常量。记住,这些常量不存在,直到迟库被加载完成。\n * 你不能在导入文件中使用延迟库常量的类型。相反,考虑将接口类型移到同时由延迟库和导入文件导入的库。\n * Dart隐含调用LoadLibrary()插入到定义deferred as namespace。在调用LoadLibrary()函数返回一个Future。\n
\n\n\n库的实现
\n\n用 library 来来命名库,用part来指定库中的其他文件。 注意:不必在应用程序中(具有顶级main()函数的文件)使用library,但这样做可以让你在多个文件中执行应用程序。 \n \n \n\n声明库
\n\n利用library identifier(库标识符)指定当前库的名称: \n \n\nlibrary ballgame;\n\n\nimport 'dart:html' ;\n\n\n
\n \n\n关联文件与库
\n\nlibrary ballgame;\n\nimport 'dart:html' ;\n\n\npart 'ball.dart' ;\npart 'util.dart' ;\n\n\n
\n\n第二个文件ball.dart,实现了球赛库的一部分: \n \npart of ballgame;\n\n\n
\n\n第三个文件,util.dart,实现了球赛库的其余部分: \n \npart of ballgame;\n\n\n
\n \n\n重新导出库(Re-exporting libraries)
\n*可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集。
\n\nlibrary french;\n\nhello() => print ('Bonjour!' );\ngoodbye() => print ('Au Revoir!' );\n\n\nlibrary togo;\n\nimport 'french.dart' ;\nexport 'french.dart' show hello;\n\n\nimport 'togo.dart' ;\n\nvoid main() {\n hello(); \n goodbye(); \n}\n\n
\n \n \n异步的支持 \n\n\nDart 添加了一些新的语言特性用于支持异步编程。最通常使用的特性是 async 方法和 await 表达式。Dart 库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成
\n \n\n当你需要使用 Future 来表示一个值时,你有两个选择。
\n\n使用 async 和 await \n使用 Future API \n \n \n\n同样的,当你需要从 Stream 获取值的时候,你有两个选择。
\n \n \n使用 async 和一个异步的 for 循环 (await for)\n使用 Stream API
\n\n使用 async 和 await 的代码是异步的,不过它看起来很像同步的代码。比如这里有一段使用 await 等待一个异步函数结果的代码: \n \nawait lookUpVersion()\n
\n\n要使用 await,代码必须用 await 标记 \n \n checkVersion() async {\n var version = await lookUpVersion();\n if (version == expectedVersion) {\n \n } else {\n \n }\n }\n
\n\n你可以使用 try, catch, 和 finally 来处理错误并精简使用了 await 的代码。 \n \n try {\n server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044 );\n } catch (e) {\n \n }\n
\n\n\n声明异步函数
\n\n一个异步函数是一个由 async 修饰符标记的函数。虽然一个异步函数可能在操作上比较耗时,但是它可以立即返回-在任何方法体执行之前。 \n \ncheckVersion() async {\n \n}\n\nlookUpVersion() async => ;\n
\n\n在函数中添加关键字 async 使得它返回一个 Future,比如,考虑一下这个同步函数,它将返回一个字符串。 \nString lookUpVersionSync() => '1.0.0'; \n如果你想更改它成为异步方法-因为在以后的实现中将会非常耗时-它的返回值是一个 Future 。 \nFuture lookUpVersion() async => '1.0.0'; \n请注意函数体不需要使用 Future API,如果必要的话 Dart 将会自己创建 Future 对象 \n \n \n\n使用带 future 的 await 表达式
\n\nawait expression\n
\n\n在异步方法中你可以使用 await 多次。比如,下列代码为了得到函数的结果一共等待了三次。 \n \nvar entrypoint = await findEntrypoint();\nvar exitCode = await runExecutable(entrypoint, args);\nawait flushThenExit(exitCode);\n
\n\n\n在 await 表达式中, 表达式 的值通常是一个 Future 对象;如果不是,那么这个值会自动转为 Future。这个 Future 对象表明了表达式应该返回一个对象。await 表达式 的值就是返回的一个对象。在对象可用之前,await 表达式将会一直处于暂停状态。
\n \n\n如果 await 没有起作用,请确认它是一个异步方法。比如,在你的 main() 函数里面使用await,main() 的函数体必须被 async 标记:
\n \n \nmain() async {\n checkVersion();\n print ('In main: version is ${await lookUpVersion()} ' );\n}\n\n
\n \n\n结合 streams 使用异步循环
\n\nawait for (variable declaration in expression) {\n \n}\n
\n\n\n表达式 的值必须有Stream 类型(流类型)。执行过程如下:
\n\n在 stream 发出一个值之前等待 \n执行 for 循环的主体,把变量设置为发出的值。 \n重复 1 和 2,直到 Stream 关闭 \n \n \n\n如果要停止监听 stream ,你可以使用 break 或者 return 语句,跳出循环并取消来自 stream 的订阅 。
\n \n\n如果一个异步 for 循环没有正常运行,请确认它是一个异步方法。 比如,在应用的 main() 方法中使用异步的 for 循环时,main() 的方法体必须被 async 标记。
\n \n \nmain() async {\n ...\n \n await for (var request in requestServer) {\n handleRequest(request);\n }\n\n ...\n}\n
\n \n \n更多关于异步编程的信息,请看 dart:async 库部分的介绍。你也可以看文章 Dart Language Asynchrony Support: Phase 1
\n",
+ "link": "\\en-us\\blog\\dart\\syntax.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/docker/docker-compose.html b/en-us/blog/docker/docker-compose.html
new file mode 100644
index 0000000..2207aef
--- /dev/null
+++ b/en-us/blog/docker/docker-compose.html
@@ -0,0 +1,474 @@
+
+
+
+
+
+
+
+
+
+ docker-compose
+
+
+
+
+ docker和docker-compose 配置 mysql mssql mongodb redis nginx jenkins 环境
+磁盘挂载
+fdisk -l #查看磁盘列表
+mkfs.ext4 /dev/vdb #格式化磁盘
+mount /dev/vdb /data #挂载磁盘在/data
+echo '/dev/vdb /data ext4 defaults,nofail 0 1'>> /etc/fstab # 启动服务器自动挂载
+mount -a #校验自动挂载脚本
+df -h #查看磁盘挂载后信息
+
+docker
+安装 docker
+yum update #更新系统包
+yum install -y yum-utils device-mapper-persistent-data lvm2 #安装yum-utils
+yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #为yum源添加docker仓库位置
+yum install docker-ce #安装docker
+systemctl enable docker #设置开机自动启动
+systemctl start docker #启动docker
+systemctl stop docker #暂停docker
+mv /var/lib/docker /data/docker # 修改Docker镜像的存放位置
+ln -s /data/docker /var/lib/docker #建立软连接
+echo '{
+ "registry-mirrors": [
+ "https://dockerhub.azk8s.cn",
+ "https://hub-mirror.c.163.com",
+ "https://registry.docker-cn.com"
+ ]
+}
+'>> /etc/docker/daemon.json # 镜像下载代理
+
+拉取 Java 镜像
+docker pull java
+
+拉取SqlServer镜像
+docker pull microsoft/mssql-server-linux # 拉取SqlServer镜像
+docker run -p 1433:1433 --name mssql \ # run 运行容器 -p 将容器的1433端口映射到主机的1433端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /data/sqlserver:/var/opt/mssql \ # 挂载mssql文件夹到主机
+-e ACCEPT_EULA=Y \ # 同意协议
+-e MSSQL_SA_PASSWORD=mssql-MSSQL \ # 初始化sa密码
+-u root \ # 指定容器为root运行
+-d microsoft/mssql-server-linux # -d 后台运行
+
+拉取 MySql 镜像
+docker pull mysql #拉取 MySql
+docker run -p 3306:3306 --name mysql \ # run 运行容器 -p 将容器的3306端口映射到主机的3306端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/mysql/log:/var/log/mysql \ # 将日志文件夹挂载到主机
+-v /data/mysql/data:/var/lib/mysql \ # 将数据文件夹挂载到主机
+-v /data/mysql/mysql-files:/var/lib/mysql-files \ # 将数据文件夹挂载到主机
+-v /data/mysql/conf:/etc/mysql \ # 将配置文件夹挂在到主机
+-e MYSQL_ROOT_PASSWORD=xiujingmysql. \ # 初始化root用户的密码
+-d mysql # -d 后台运行
+docker exec -it mysql /bin/bash # 进入Docker容器内部的bash
+
+拉取 Mongodb 镜像
+docker pull mongo #拉取 mongodb
+docker run -p 27017:27017 --name mongo \ # run 运行容器 -p 将容器的27017端口映射到主机的27017端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/mongodb/db:/data/db \ # 将数据文件夹挂载到主机
+-v /data/mongodb/configdb:/data/configdb \ # 将数据库配置文件挂载到主机
+-v /data/mongodb/initdb:/docker-entrypoint-initdb.d # 通过/docker-entrypoint-initdb.d/将更复杂的用户设置显式留给用户 当容器首次启动时它会执行/docker-entrypoint-initdb.d 目录下的sh 和js脚本 。 以脚本字母顺序执行
+-e MONGO_INITDB_ROOT_USERNAME=admin \ # 设置admin数据库账户名称 如果使用了此项,则不需要 --auth 参数
+-e MONGO_INITDB_ROOT_PASSWORD=admin \ # 设置admin数据库账户密码 如果使用了此项,则不需要 --auth 参数
+-d mongo \ # -d 后台运行
+--auth # --auth 需要密码才能访问容器服务
+
+docker exec -it mongo mongo admin # 进入mongo
+db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'}]}); #创建一个名为 admin,密码为 123456 的用户。
+db.auth('admin', '123456') # 尝试使用上面创建的用户信息进行连接。
+
+拉取 Redis 镜像
+docker pull redis #拉取 redis
+docker run -p 6379:6379 --name redis \ # run 运行容器 -p 将容器的6379端口映射到主机的6379端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/redis:/data \ # 将数据文件夹挂载到主机
+-d redis \ # -d 后台运行
+redis-server --appendonly yes \ # 在容器执行redis-server启动命令,并打开redis持久化配置
+--requirepass "123456" # 设置密码123456
+
+拉取 Nginx 镜像
+docker pull nginx #拉取 nginx
+docker run -p 80:80 -p 443:443 --name nginx -d nginx # 运行容器
+docker container cp nginx:/etc/nginx /data/nginx/ #拷贝容器配置
+docker rm -f nginx # 删除容器
+
+nginx 配置文件
+
+user nginx;
+worker_processes 1;
+
+error_log /var/log/nginx/error_log.log warn;
+pid /var/run/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ gzip on; #开启gzip
+ gzip_disable "msie6"; #IE6不使用gzip
+ gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"
+ gzip_proxied any; #代理结果数据的压缩
+ gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值
+ gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果
+ gzip_http_version 1.1; #识别http协议的版本
+ gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩
+ gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩
+
+ include /etc/nginx/conf.d/*.conf;
+
+ server {
+ #nginx同时开启http和https
+ listen 80 default backlog=2048;
+ listen 443 ssl;
+ server_name ysf.djtlpay.com;
+
+ ssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;
+ ssl_certificate_key /ssl/2_ysf.djtlpay.com.key;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+ }
+}
+
+
+运行 nginx
+docker run -p 80:80 -p 443:443 --name nginx \ # run 运行容器 -p 将容器的80,443端口映射到主机的80,443端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/nginx/html:/usr/share/nginx/html \ # nginx 静态资源
+-v /data/nginx/logs:/var/log/nginx \ # 将日志文件夹挂载到主机
+-v /data/nginx/conf:/etc/nginx \ # 将配置文件夹挂在到主机
+-v /data/nginx/conf/ssl:/ssl \ # 将证书文件夹挂在到主机
+-d nginx #
+
+拉取Jenkins镜像:
+docker pull jenkins/jenkins:lts # 拉取 jenkins
+docker run -p 8080:8080 -p 50000:50000 --name jenkins \ # run 运行容器 -p 将容器的8080,50000端口映射到主机的8080,50000端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-u root \ # 运行的用户为root
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/jenkins_home:/var/jenkins_home \ # 将jenkins_home文件夹挂在到主机
+-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \ #设置jenkins运行环境时区
+-d jenkins/jenkins:lts # -d 后台运行
+
+拉取MinIO镜像
+docker pull minio/minio # 拉取MinIO镜像
+docker run -p 9000:9000 --name minio \ # run 运行容器 -p 将容器的9000,9000端口映射到主机的9000,9000端口 --name 容器运行的名
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/minio/data:/data \ # 将data文件夹挂在到主机
+-v /data/minio/config:/root/.minio \ # 将配置文件夹挂在到主机
+-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \ # 设置MINIO_ACCESS_KEY的值
+-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \ # 设置MINIO_SECRET_KEY值
+-d minio/minio server /data # -d 后台运行 server /data 导出/data目录
+
+
+拉取Portainer镜像
+docker pull portainer/portainer # 拉取MinIO镜像
+docker run -p 8001:8000 -p 9001:9000 --name portainer \ # run 运行容器 -p 将容器的8000,9000端口映射到主机的8000,9000端口 --name 容器运行的名
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /var/run/docker.sock:/var/run/docker.sock \ # 将docker.sock文件夹挂在到主机
+-v /data/portainer/data:/data \ # 将配置文件夹挂在到主机
+-d portainer/portainer portainer # -d 后台运行
+
+Docker 开启远程API
+
+用vi编辑器修改docker.service文件
+
+vi /usr/lib/systemd/system/docker.service
+# 需要修改的部分:
+ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
+# 修改后的部分:
+ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock
+
+Docker 常用命令
+systemctl start docker #启动docker
+systemctl enable docker #将docker服务设为开机启动
+systemctl stop docker #停止容器
+systemctl restart docker #重启docker服务
+docker images # 列出镜像
+docker rmi --name # 删除镜像 -f 强制删除
+docker ps # 列出容器 -a 所有
+docker start --name # 启动容器
+docker stop --name # 停止容器
+docker restart --name # 重启docker容器
+docker rm --name # 删除容器 -f 强制删除
+docker stats -a # 查看所有容器情况
+docker system df # 查看Docker磁盘使用情况
+docker exec -it --name /bin/bash #进入Docker容器内部的bash
+docker cp 主机文件 容器名称:容器路径 #复制文件到docker容器中
+docker logs --name #查看docker镜像日志
+docker rm $(docker ps -a -q) # 删除所有容器 -f 强制删除
+docker rmi $(docker images -a -q) # 删除所有镜像 -f 强制删除
+docker rm -f `docker ps -a | grep -vE 'mysql|nginx|redis|jenkins' | awk '{print $1}'` # 删除mysql|nginx|redis|jenkins非容器 -f 强制删除
+docker rmi -f `docker images | grep none | awk '{print $3}'` # 删除镜像none镜像 -f 强制删除
+
+docker-compose
+安装 docker-compose
+# 下载Docker Compose
+curl -L https://get.daocloud.io/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
+# 修改该文件的权限为可执行
+chmod +x /usr/local/bin/docker-compose
+# 查看是否已经安装成功
+docker-compose --version
+
+使用Docker Compose的步骤
+
+使用Dockerfile定义应用程序环境,一般需要修改初始镜像行为时才需要使用;
+使用docker-compose.yml定义需要部署的应用程序服务,以便执行脚本一次性部署;
+使用docker-compose up命令将所有应用服务一次性部署起来。
+
+docker-compose.yml常用命令
+# 指定运行的镜像名称
+image: name:version
+# 配置容器名称
+container_name: name
+# 指定宿主机和容器的端口映射
+ports:
+ - 3306:3306
+# 将宿主机的文件或目录挂载到容器中
+volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mysql/log:/var/log/mysql
+ - /data/mysql/data:/var/lib/mysql
+ - /data/mysql/conf:/etc/mysql
+ - /data/mysql/mysql-files:/var/lib/mysql-files
+# 配置环境变量
+environment:
+ - MYSQL_ROOT_PASSWORD=xiujingmysql.
+# 连接其他容器的服务
+links:
+ - db:database #可以以database为域名访问服务名称为db的容器
+# 挂断自动重新启动
+restart: always
+# 指定容器执行命令
+command: redis-server --requirepass xiujingredis.
+
+Docker Compose常用命令
+# 构建、创建、启动相关容器
+docker-compose up -d # -d表示在后台运行
+# 停止所有相关容器
+docker-compose stop
+# 删除容器文件
+docker-compose rm -f # -f 强制删除
+# 重启容器
+docker-compose restart
+# 列出所有容器信息
+docker-compose ps
+# 查看容器日志
+docker-compose logs
+
+使用Docker Compose 部署应用
+编写docker-compose.yml文件
+version: '3'
+services:
+
+ nginx:
+
+ image: nginx
+
+ container_name: nginx
+
+ ports:
+ - 80 :80
+ - 443 :443
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+
+ sqlserver:
+
+ image: mcr.microsoft.com/mssql/server
+
+ container_name: sqlserver
+
+ ports:
+ - "1433"
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/sqlserver:/var/opt/mssql
+
+ restart: always
+ environment:
+ - TZ=Asia/Shanghai
+ - SA_PASSWORD=mssql-MSSQL
+ - ACCEPT_EULA=Y
+
+ user:
+ root
+
+ mysql:
+
+ image: mysql
+
+ container_name: mysql
+
+ ports:
+ - 3306 :3306
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mysql/log:/var/log/mysql
+ - /data/mysql/data:/var/lib/mysql
+ - /data/mysql/mysql-files:/var/lib/mysql-files
+ - /data/mysql/conf:/etc/mysql
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+ - MYSQL_ROOT_PASSWORD=xiujingmysql.
+
+ user:
+ root
+
+ redis:
+
+ image: redis
+
+ container_name: redis
+
+ ports:
+ - 6379 :6379
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/redis:/data
+ - /data/redis/redis.conf:/etc/redis.conf
+
+ restart: always
+
+ command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes
+
+ environment:
+ - TZ=Asia/Shanghai
+
+ mongo:
+
+ image: mongo
+
+ container_name: mongo
+
+ ports:
+ - 27017 :27017
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mongodb/db:/data/db
+ - /data/mongodb/configdb:/data/configdb
+ - /data/mongodb/initdb:/docker-entrypoint-initdb.d
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+ - AUTH=yes
+ - MONGO_INITDB_ROOT_USERNAME=admin
+ - MONGO_INITDB_ROOT_PASSWORD=admin
+
+ jenkins:
+
+ image: jenkins
+
+ container_name: jenkins
+
+ ports:
+ - 8080 :8080
+ - 50000 :50000
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/jenkins_home:/var/jenkins_home
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+ - JAVA_OPTS=-Duser.timezone=Asia/Shanghai
+
+ user:
+ root
+
+ minio:
+
+ image: minio
+
+ container_name: minio
+
+ ports:
+ - 9000 :9000
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/minio/data:/data
+ - /data/minio/config:/root/.minio
+
+ restart: always
+
+ command: server /data
+
+ portainer :
+
+ image: portainer
+
+ container_name: portainer
+
+ ports:
+ - 8001 :8000
+ - 9001 :9000
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /data/portainer/data:/data
+
+ restart: always
+
+运行Docker Compose命令启动所有服务
+docker-compose up -d
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/docker/docker-compose.json b/en-us/blog/docker/docker-compose.json
new file mode 100644
index 0000000..c2a7ed0
--- /dev/null
+++ b/en-us/blog/docker/docker-compose.json
@@ -0,0 +1,6 @@
+{
+ "filename": "docker-compose.md",
+ "__html": "docker和docker-compose 配置 mysql mssql mongodb redis nginx jenkins 环境 \n磁盘挂载 \nfdisk -l #查看磁盘列表\nmkfs.ext4 /dev/vdb #格式化磁盘\nmount /dev/vdb /data #挂载磁盘在/data\necho '/dev/vdb /data ext4 defaults,nofail 0 1'>> /etc/fstab # 启动服务器自动挂载\nmount -a #校验自动挂载脚本\ndf -h #查看磁盘挂载后信息\n
\ndocker \n安装 docker \nyum update #更新系统包\nyum install -y yum-utils device-mapper-persistent-data lvm2 #安装yum-utils\nyum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #为yum源添加docker仓库位置\nyum install docker-ce #安装docker\nsystemctl enable docker #设置开机自动启动\nsystemctl start docker #启动docker\nsystemctl stop docker #暂停docker\nmv /var/lib/docker /data/docker # 修改Docker镜像的存放位置\nln -s /data/docker /var/lib/docker #建立软连接\necho '{\n \"registry-mirrors\": [\n \"https://dockerhub.azk8s.cn\",\n \"https://hub-mirror.c.163.com\",\n \"https://registry.docker-cn.com\"\n ]\n}\n'>> /etc/docker/daemon.json # 镜像下载代理\n
\n拉取 Java 镜像 \ndocker pull java\n
\n拉取SqlServer镜像 \ndocker pull microsoft/mssql-server-linux # 拉取SqlServer镜像\ndocker run -p 1433:1433 --name mssql \\ # run 运行容器 -p 将容器的1433端口映射到主机的1433端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /data/sqlserver:/var/opt/mssql \\ # 挂载mssql文件夹到主机\n-e ACCEPT_EULA=Y \\ # 同意协议\n-e MSSQL_SA_PASSWORD=mssql-MSSQL \\ # 初始化sa密码\n-u root \\ # 指定容器为root运行\n-d microsoft/mssql-server-linux # -d 后台运行\n
\n拉取 MySql 镜像 \ndocker pull mysql #拉取 MySql\ndocker run -p 3306:3306 --name mysql \\ # run 运行容器 -p 将容器的3306端口映射到主机的3306端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/mysql/log:/var/log/mysql \\ # 将日志文件夹挂载到主机\n-v /data/mysql/data:/var/lib/mysql \\ # 将数据文件夹挂载到主机\n-v /data/mysql/mysql-files:/var/lib/mysql-files \\ # 将数据文件夹挂载到主机\n-v /data/mysql/conf:/etc/mysql \\ # 将配置文件夹挂在到主机\n-e MYSQL_ROOT_PASSWORD=xiujingmysql. \\ # 初始化root用户的密码\n-d mysql # -d 后台运行\ndocker exec -it mysql /bin/bash # 进入Docker容器内部的bash\n
\n拉取 Mongodb 镜像 \ndocker pull mongo #拉取 mongodb\ndocker run -p 27017:27017 --name mongo \\ # run 运行容器 -p 将容器的27017端口映射到主机的27017端口 --name 容器运行的名字 \n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/mongodb/db:/data/db \\ # 将数据文件夹挂载到主机\n-v /data/mongodb/configdb:/data/configdb \\ # 将数据库配置文件挂载到主机\n-v /data/mongodb/initdb:/docker-entrypoint-initdb.d # 通过/docker-entrypoint-initdb.d/将更复杂的用户设置显式留给用户 当容器首次启动时它会执行/docker-entrypoint-initdb.d 目录下的sh 和js脚本 。 以脚本字母顺序执行\n-e MONGO_INITDB_ROOT_USERNAME=admin \\ # 设置admin数据库账户名称 如果使用了此项,则不需要 --auth 参数\n-e MONGO_INITDB_ROOT_PASSWORD=admin \\ # 设置admin数据库账户密码 如果使用了此项,则不需要 --auth 参数\n-d mongo \\ # -d 后台运行\n--auth # --auth 需要密码才能访问容器服务\n\ndocker exec -it mongo mongo admin # 进入mongo\ndb.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'}]}); #创建一个名为 admin,密码为 123456 的用户。\ndb.auth('admin', '123456') # 尝试使用上面创建的用户信息进行连接。\n
\n拉取 Redis 镜像 \ndocker pull redis #拉取 redis\ndocker run -p 6379:6379 --name redis \\ # run 运行容器 -p 将容器的6379端口映射到主机的6379端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/redis:/data \\ # 将数据文件夹挂载到主机\n-d redis \\ # -d 后台运行\nredis-server --appendonly yes \\ # 在容器执行redis-server启动命令,并打开redis持久化配置\n--requirepass \"123456\" # 设置密码123456\n
\n拉取 Nginx 镜像 \ndocker pull nginx #拉取 nginx\ndocker run -p 80:80 -p 443:443 --name nginx -d nginx # 运行容器\ndocker container cp nginx:/etc/nginx /data/nginx/ #拷贝容器配置\ndocker rm -f nginx # 删除容器\n
\nnginx 配置文件
\n\nuser nginx;\nworker_processes 1;\n\nerror_log /var/log/nginx/error_log.log warn;\npid /var/run/nginx.pid;\n\n\nevents {\n worker_connections 1024;\n}\n\n\nhttp {\n include /etc/nginx/mime.types;\n default_type application/octet-stream;\n\n log_format main '$remote_addr - $remote_user [$time_local] "$request" '\n '$status $body_bytes_sent "$http_referer" '\n '"$http_user_agent" "$http_x_forwarded_for"';\n\n access_log /var/log/nginx/access.log main;\n\n sendfile on;\n #tcp_nopush on;\n\n keepalive_timeout 65;\n\n #gzip on;\n\n gzip on; #开启gzip\n gzip_disable "msie6"; #IE6不使用gzip\n gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"\n gzip_proxied any; #代理结果数据的压缩\n gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值\n gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果\n gzip_http_version 1.1; #识别http协议的版本\n gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩\n gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩\n\n include /etc/nginx/conf.d/*.conf;\n\n server {\n #nginx同时开启http和https\n \tlisten 80 default backlog=2048;\n \tlisten 443 ssl;\n \tserver_name ysf.djtlpay.com;\n \t\n \tssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;\n \tssl_certificate_key /ssl/2_ysf.djtlpay.com.key;\n\t\n location / {\n root /usr/share/nginx/html;\n index index.html index.htm;\n }\n }\t\t\n}\n\n
\n运行 nginx
\ndocker run -p 80:80 -p 443:443 --name nginx \\ # run 运行容器 -p 将容器的80,443端口映射到主机的80,443端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/nginx/html:/usr/share/nginx/html \\ # nginx 静态资源\n-v /data/nginx/logs:/var/log/nginx \\ # 将日志文件夹挂载到主机\n-v /data/nginx/conf:/etc/nginx \\ # 将配置文件夹挂在到主机\n-v /data/nginx/conf/ssl:/ssl \\ # 将证书文件夹挂在到主机\n-d nginx #\n
\n拉取Jenkins镜像: \ndocker pull jenkins/jenkins:lts # 拉取 jenkins\ndocker run -p 8080:8080 -p 50000:50000 --name jenkins \\ # run 运行容器 -p 将容器的8080,50000端口映射到主机的8080,50000端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-u root \\ # 运行的用户为root\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/jenkins_home:/var/jenkins_home \\ # 将jenkins_home文件夹挂在到主机\n-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \\ #设置jenkins运行环境时区\n-d jenkins/jenkins:lts # -d 后台运行\n
\n拉取MinIO镜像 \ndocker pull minio/minio # 拉取MinIO镜像\ndocker run -p 9000:9000 --name minio \\ # run 运行容器 -p 将容器的9000,9000端口映射到主机的9000,9000端口 --name 容器运行的名\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/minio/data:/data \\ # 将data文件夹挂在到主机\n-v /data/minio/config:/root/.minio \\ # 将配置文件夹挂在到主机\n-e \"MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE\" \\ # 设置MINIO_ACCESS_KEY的值\n-e \"MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\" \\ # 设置MINIO_SECRET_KEY值\n-d minio/minio server /data # -d 后台运行 server /data 导出/data目录\n\n
\n拉取Portainer镜像 \ndocker pull portainer/portainer # 拉取MinIO镜像\ndocker run -p 8001:8000 -p 9001:9000 --name portainer \\ # run 运行容器 -p 将容器的8000,9000端口映射到主机的8000,9000端口 --name 容器运行的名\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /var/run/docker.sock:/var/run/docker.sock \\ # 将docker.sock文件夹挂在到主机\n-v /data/portainer/data:/data \\ # 将配置文件夹挂在到主机\n-d portainer/portainer portainer # -d 后台运行\n
\nDocker 开启远程API \n\n用vi编辑器修改docker.service文件 \n \nvi /usr/lib/systemd/system/docker.service\n# 需要修改的部分: \nExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock\n# 修改后的部分: \nExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock\n
\nDocker 常用命令 \nsystemctl start docker #启动docker\nsystemctl enable docker #将docker服务设为开机启动\nsystemctl stop docker #停止容器\nsystemctl restart docker #重启docker服务\ndocker images # 列出镜像\ndocker rmi --name # 删除镜像 -f 强制删除\ndocker ps # 列出容器 -a 所有\ndocker start --name # 启动容器\ndocker stop --name # 停止容器\ndocker restart --name # 重启docker容器\ndocker rm --name # 删除容器 -f 强制删除\ndocker stats -a # 查看所有容器情况\ndocker system df # 查看Docker磁盘使用情况\ndocker exec -it --name /bin/bash #进入Docker容器内部的bash\ndocker cp 主机文件 容器名称:容器路径 #复制文件到docker容器中\ndocker logs --name #查看docker镜像日志\ndocker rm $(docker ps -a -q) # 删除所有容器 -f 强制删除\ndocker rmi $(docker images -a -q) # 删除所有镜像 -f 强制删除\ndocker rm -f `docker ps -a | grep -vE 'mysql|nginx|redis|jenkins' | awk '{print $1}'` # 删除mysql|nginx|redis|jenkins非容器 -f 强制删除\ndocker rmi -f `docker images | grep none | awk '{print $3}'` # 删除镜像none镜像 -f 强制删除\n
\ndocker-compose \n安装 docker-compose \n# 下载Docker Compose \ncurl -L https://get.daocloud.io/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose\n# 修改该文件的权限为可执行 \nchmod +x /usr/local/bin/docker-compose\n# 查看是否已经安装成功 \ndocker-compose --version\n
\n使用Docker Compose的步骤 \n\n使用Dockerfile定义应用程序环境,一般需要修改初始镜像行为时才需要使用; \n使用docker-compose.yml定义需要部署的应用程序服务,以便执行脚本一次性部署; \n使用docker-compose up命令将所有应用服务一次性部署起来。 \n \ndocker-compose.yml常用命令 \n# 指定运行的镜像名称 \nimage: name:version\n# 配置容器名称 \ncontainer_name: name\n# 指定宿主机和容器的端口映射 \nports:\n - 3306:3306\n# 将宿主机的文件或目录挂载到容器中 \nvolumes:\n - /etc/localtime:/etc/localtime\n - /data/mysql/log:/var/log/mysql\n - /data/mysql/data:/var/lib/mysql\n - /data/mysql/conf:/etc/mysql\n - /data/mysql/mysql-files:/var/lib/mysql-files\n# 配置环境变量 \nenvironment:\n - MYSQL_ROOT_PASSWORD=xiujingmysql.\n# 连接其他容器的服务 \nlinks:\n - db:database #可以以database为域名访问服务名称为db的容器\n# 挂断自动重新启动 \nrestart: always\n# 指定容器执行命令 \ncommand: redis-server --requirepass xiujingredis.\n
\nDocker Compose常用命令 \n# 构建、创建、启动相关容器 \ndocker-compose up -d # -d表示在后台运行\n# 停止所有相关容器 \ndocker-compose stop\n# 删除容器文件 \ndocker-compose rm -f # -f 强制删除\n# 重启容器 \ndocker-compose restart\n# 列出所有容器信息 \ndocker-compose ps\n# 查看容器日志 \ndocker-compose logs\n
\n使用Docker Compose 部署应用 \n编写docker-compose.yml文件
\nversion: '3' \nservices: \n \n nginx: \n \n image: nginx \n \n container_name: nginx \n \n ports: \n - 80 :80 \n - 443 :443 \n \n volumes: \n - /etc/localtime:/etc/localtime \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n \n sqlserver: \n \n image: mcr.microsoft.com/mssql/server \n \n container_name: sqlserver \n \n ports: \n - \"1433\" \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/sqlserver:/var/opt/mssql \n \n restart: always \n environment: \n - TZ=Asia/Shanghai \n - SA_PASSWORD=mssql-MSSQL \n - ACCEPT_EULA=Y \n \n user: \n root \n \n mysql: \n \n image: mysql \n \n container_name: mysql \n \n ports: \n - 3306 :3306 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/mysql/log:/var/log/mysql \n - /data/mysql/data:/var/lib/mysql \n - /data/mysql/mysql-files:/var/lib/mysql-files \n - /data/mysql/conf:/etc/mysql \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n - MYSQL_ROOT_PASSWORD=xiujingmysql. \n \n user: \n root \n \n redis: \n \n image: redis \n \n container_name: redis \n \n ports: \n - 6379 :6379 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/redis:/data \n - /data/redis/redis.conf:/etc/redis.conf \n \n restart: always \n \n command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes \n \n environment: \n - TZ=Asia/Shanghai \n \n mongo: \n \n image: mongo \n \n container_name: mongo \n \n ports: \n - 27017 :27017 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/mongodb/db:/data/db \n - /data/mongodb/configdb:/data/configdb \n - /data/mongodb/initdb:/docker-entrypoint-initdb.d \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n - AUTH=yes \n - MONGO_INITDB_ROOT_USERNAME=admin \n - MONGO_INITDB_ROOT_PASSWORD=admin \n \n jenkins: \n \n image: jenkins \n \n container_name: jenkins \n \n ports: \n - 8080 :8080 \n - 50000 :50000 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/jenkins_home:/var/jenkins_home \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n - JAVA_OPTS=-Duser.timezone=Asia/Shanghai \n \n user: \n root \n \n minio: \n \n image: minio \n \n container_name: minio \n \n ports: \n - 9000 :9000 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/minio/data:/data \n - /data/minio/config:/root/.minio \n \n restart: always \n \n command: server /data \n \n portainer : \n \n image: portainer \n \n container_name: portainer \n \n ports: \n - 8001 :8000 \n - 9001 :9000 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /var/run/docker.sock:/var/run/docker.sock \n - /data/portainer/data:/data \n \n restart: always \n
\n运行Docker Compose命令启动所有服务
\ndocker-compose up -d\n
\n",
+ "link": "\\en-us\\blog\\docker\\docker-compose.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/docker/docker-jenkins.html b/en-us/blog/docker/docker-jenkins.html
new file mode 100644
index 0000000..0e5d535
--- /dev/null
+++ b/en-us/blog/docker/docker-jenkins.html
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+ docker-jenkins
+
+
+
+
+ Docker部署Jenkins
+Jenkins简介
+Jenkins是开源CI&CD软件领导者,提供超过1000个插件来支持构建、部署、自动化,满足任何项目的需要。我们可以用Jenkins来构建和部署我们的项目,比如说从我们的代码仓库获取代码,然后将我们的代码打包成可执行的文件,之后通过远程的ssh工具执行脚本来运行我们的项目。
+Jenkins的安装及配置
+Docker环境下的安装
+
+docker pull jenkins/jenkins:lts
+
+
+docker run -p 8080:8080 -p 50000:50000 --name jenkins \
+-u root \
+-v /etc/localtime:/etc/localtime \
+-v /data/jenkins_home:/var/jenkins_home \
+-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \
+-d jenkins/jenkins:lts
+
+
+echo 'Asia/Shanghai' >/etc/timezone
+
+Jenkins的配置
+
+
+
+使用管理员密码进行登录,可以使用以下命令从容器启动日志中获取管理密码:
+
+docker logs jenkins
+
+
+
+选择安装插件方式,这里我们直接安装推荐的插件:
+
+
+
+
+
+
+
+
+
+点击系统管理->插件管理,进行一些自定义的插件安装:
+
+
+
+
+
+
+
+在系统管理->系统配置中添加全局ssh的配置,这样Jenkins使用ssh就可以执行远程的linux脚本了:
+
+
+角色权限管理
+我们可以使用Jenkins的角色管理插件来管理Jenkins的用户,比如我们可以给管理员赋予所有权限,运维人员赋予执行任务的相关权限,其他人员只赋予查看权限。
+
+在系统管理->全局安全配置中启用基于角色的权限管理:
+
+
+
+进入系统管理->Manage and Assign Roles界面:
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/docker/docker-jenkins.json b/en-us/blog/docker/docker-jenkins.json
new file mode 100644
index 0000000..b37ff9b
--- /dev/null
+++ b/en-us/blog/docker/docker-jenkins.json
@@ -0,0 +1,6 @@
+{
+ "filename": "docker-jenkins.md",
+ "__html": "Docker部署Jenkins \nJenkins简介 \nJenkins是开源CI&CD软件领导者,提供超过1000个插件来支持构建、部署、自动化,满足任何项目的需要。我们可以用Jenkins来构建和部署我们的项目,比如说从我们的代码仓库获取代码,然后将我们的代码打包成可执行的文件,之后通过远程的ssh工具执行脚本来运行我们的项目。
\nJenkins的安装及配置 \nDocker环境下的安装 \n\ndocker pull jenkins/jenkins:lts\n
\n\ndocker run -p 8080:8080 -p 50000:50000 --name jenkins \\\n-u root \\\n-v /etc/localtime:/etc/localtime \\\n-v /data/jenkins_home:/var/jenkins_home \\\n-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \\\n-d jenkins/jenkins:lts\n
\n\necho 'Asia/Shanghai' >/etc/timezone \n \nJenkins的配置 \n\n
\n\n使用管理员密码进行登录,可以使用以下命令从容器启动日志中获取管理密码: \n \ndocker logs jenkins\n
\n
\n\n选择安装插件方式,这里我们直接安装推荐的插件: \n \n
\n\n
\n\n
\n\n进行实例配置,配置Jenkins的URL: \n \n
\n\n点击系统管理->插件管理,进行一些自定义的插件安装: \n \n
\n\n
\n\n
\n\n在系统管理->系统配置中添加全局ssh的配置,这样Jenkins使用ssh就可以执行远程的linux脚本了: \n \n
\n角色权限管理 \n我们可以使用Jenkins的角色管理插件来管理Jenkins的用户,比如我们可以给管理员赋予所有权限,运维人员赋予执行任务的相关权限,其他人员只赋予查看权限。
\n\n在系统管理->全局安全配置中启用基于角色的权限管理: \n \n
\n\n进入系统管理->Manage and Assign Roles界面: \n \n
\n\n
\n\n
\n",
+ "link": "\\en-us\\blog\\docker\\docker-jenkins.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/docker/docker.html b/en-us/blog/docker/docker.html
index d6719aa..06faca6 100644
--- a/en-us/blog/docker/docker.html
+++ b/en-us/blog/docker/docker.html
@@ -375,6 +375,81 @@ 其他有用的命令
docker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
$ docker container cp [containID]:[/path/to/file] .
+Docker命令详解(run篇)
+命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+Usage: Run a command in a new container
+中文意思为:通过run命令创建一个新的容器(container)
+常用选项说明
+-d, --detach=false, 指定容器运行于前台还是后台,默认为false
+-i, --interactive=false, 打开STDIN,用于控制台交互
+-t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false
+-u, --user="", 指定容器的用户
+-a, --attach=[], 登录容器(必须是以docker run -d启动的容器)
+-w, --workdir="", 指定容器的工作目录
+-c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用
+-e, --env=[], 指定环境变量,容器中可以使用该环境变量
+-m, --memory="", 指定容器的内存上限
+-P, --publish-all=false, 指定容器暴露的端口
+-p, --publish=[], 指定容器暴露的端口
+-h, --hostname="", 指定容器的主机名
+-v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录
+--volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录
+--cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
+--cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
+--cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
+--cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
+--device=[], 添加主机设备给容器,相当于设备直通
+--dns=[], 指定容器的dns服务器
+--dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
+--entrypoint="", 覆盖image的入口点
+--env-file=[], 指定环境变量文件,文件格式为每行一个环境变量
+--expose=[], 指定容器暴露的端口,即修改镜像的暴露端口
+--link=[], 指定容器间的关联,使用其他容器的IP、env等信息
+--lxc-conf=[], 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
+--name="", 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
+--net="bridge", 容器网络设置:
+bridge 使用docker daemon指定的网桥
+host //容器使用主机的网络
+container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
+none 容器使用自己的网络(类似--net=bridge),但是不进行配置
+--privileged=false, 指定容器是否为特权容器,特权容器拥有所有的capabilities
+--restart="no", 指定容器停止后的重启策略:
+no:容器退出时不重启
+on-failure:容器故障退出(返回值非零)时重启
+always:容器退出时总是重启
+--rm=false, 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
+--sig-proxy=true, 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
+示例
+
+运行一个在后台执行的容器,同时,还能用控制台管理:
+
+docker run -i -t -d ubuntu:latest
+
+
+运行一个带命令在后台不断执行的容器,不直接展示容器内部信息:
+
+docker run -d ubuntu:latest ping www.docker.com
+
+
+运行一个在后台不断执行的容器,同时带有命令,程序被终止后还能重启继续跑,还能用控制台管理,
+
+docker run -d --restart=always ubuntu:latest ping www.docker.com
+
+
+docker run -d --name=ubuntu_server ubuntu:latest
+
+
+容器暴露80端口,并指定宿主机80端口与其通信(: 之前是宿主机端口,之后是容器需暴露的端口),
+
+docker run -d --name=ubuntu_server -p 80:80 ubuntu:latest
+
+
+指定容器内目录与宿主机目录共享(: 之前是宿主机文件夹,之后是容器需共享的文件夹),
+
+docker run -d --name=ubuntu_server -v /etc/www:/var/www ubuntu:latest
+
diff --git a/en-us/blog/docker/docker.json b/en-us/blog/docker/docker.json
index 4451a95..06fad67 100644
--- a/en-us/blog/docker/docker.json
+++ b/en-us/blog/docker/docker.json
@@ -1,6 +1,6 @@
{
"filename": "docker.md",
- "__html": "Docker \nDocker简介 \n\nDocker是开源应用容器引擎,轻量级容器技术。 \n基于Go语言,并遵循Apache2.0协议开源 \nDocker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux系统上,也可以实现虚拟化 \n容器完全使用沙箱技术,相互之间不会有任何接口 \n类似于虚拟机技术(vmware、vitural),但docker直接运行在操作系统(Linux)上,而不是运行在虚拟机中,速度快,性能开销极低 \n \n白话文,简介就是:
\n\nDocker支持将软件编译成一个镜像,然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像。\n运行中的这个镜像称为容器,容器启动是非常快速的。类似windows里面的ghost操 作系统,安装好后什么都有了。
\n \n什么是Docker \nDocker 是一个开源的应用容器引擎,基于Go语言,诞生于2013年初,最初发起者是dotCloud公司,开发者可以打包应用到一个轻量级、可移植的容器中,然后发布到主流Linux系统上运行。\n为什么用Docker
\n\n持续交付和部署:使用Docker可以通过定制应用镜像来实现持续集成,持续交付,部署。开发人员构建后的镜像,结合持续集成系统进行集成测试,而运维人员则可以在生产环境中快速部署该镜像,也可以结合持续部署系统进行自动部署。 \n更高效的资源利用:Docker是基于内核级的虚拟化,可以实现更高效的性能,同时对资源的额外需求很低,相比传统虚拟机方式,相同配置的主机能够运行更多的应用。\n更轻松的迁移和扩展:Docker容器几乎可以在任何平台上运行,同时支持主流的操作系统发行版本。 \n更快速的启动时间:传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到妙级,甚至毫秒级的启动时间,大大的节约了开发,测试,部署的时间。\nDocker与传统虚拟机差异 \n \n传统虚拟化方式
\n \nDocker虚拟化方式
\n
\n传统虚拟化是在硬件层面实现虚拟化,需要有额外的虚拟机管理应用和虚拟机操作系统层,而Docker容器是在操作系统层面实现虚拟化,直接复用本地主机操作系统,更加轻量级。
\n核心概念 \n\nDocker镜像:类似于虚拟机里的镜像,是一个只读的模板,一个独立的文件系统,使用镜像可以创建容器,可以理解为镜像是容器的基石。 \nDocker容器:是由Docker镜像创建的运行实例,类似于轻量级的沙箱,每个容器之间都是相互隔离的。支持的操作有启动,停止,删除等。 \ndocker客户端(Client):客户端通过命令行或其他工具使用Docker API(https://docs.docker.com/reference/api/docker_remote_api )与Docker的守护进程进行通信 \ndocker主机(Host):一个物理或虚拟的机器用来执行Docker守护进程和容器 \ndocker仓库(Registry):Docker仓库用来存储镜像,可以理解为代码控制中的代码仓库,Docker Hub(https://hub.docker.com ) 提供了庞大的镜像集合供使用 \n \nDocker安装及启停 \n\n查看centos版本 \n \nDocker 要求 CentOS 系统的内核版本高于 3 .10 \n通过命令:uname -r\n查看当前centos版本,如版本不符,需升级系统版本\n
\n\n升级软件包及内核(可选) \n \nyum update\n
\n\n安装docker \n \nyum install docker\n
\n\n启动docker \n \nsystemctl start docker\n
\n\n将docker服务设为开机启动 \n \nsystemtctl enable docker\n
\n\n停止docker \n \nsystemtctl stop docker\n
\nDocker常用命令及操作 \ndocker镜像命令
\ndocker search mysql ## 搜索镜像\ndocker pull mysql ## 下载镜像 下载命名为:docker pull 镜像名:tag,其中tag多为系统的版本,可选的,默认为least\ndocker images ## 镜像列表 RESPOSITORY为镜像名 TAG为镜像版本,least代表最新 IMAGE_ID 为该镜像唯一ID CREATED 为该镜像创建时间 SIZE 为该镜像大小\ndocker rmi image-id ##删除指定镜像\ndocker rmi $(docker images -q) ##删除所有镜像\ndocker run --name container-name -d image-name ##根据镜像启动容器 -- name:为容器起一个名称\n-d:detached,执行完这句命令后,控制台将不会阻塞,可以继续输入命令操作\nimage-name:要运行的镜像名称\ndocker ps ##查看运行中容器 CONTAINER ID:启动时生成的ID\nIMAGE:该容器使用的镜像\nCOMMAND:容器启动时执行的命令\nCREATED:容器创建时间\nSTATUS:当前容器状态\nPORTS:当前容器所使用的默认端口号\nNAMES:启动时给容器设置的名称\ndocker stop container-name/container-id ## 停止运行中容器\ndocker ps -a ##查看所有的容器\ndocker start container-name/container-id ##启动容器\ndocker rm container-id ## 删除单个容器\ndocker rm $(docker ps -a -q ) ## 删除所有容器\ndocker run --name tomcat2 -d -p 8888:8080 tomcat ## 启动做端口映射的容器\ndocker logs container-id/container-name ##查看容器日志\ndocker port container-id ## 查看端口映射\ndocker exec -it container-id/container-name bash ##容器登录命令为\nexit ##容器退出命令\n
\n更多命令可以参考
\n镜像操作指令 \n\n获取镜像:\ndocker pull centos (默认获取centos最新的镜像)\ndocker pull centos:7 (获取指定标签镜像) \n查看本地镜像:\ndocker images \n查看镜像详细信息:\ndocker inspect centos:7 \n查看镜像历史:\ndocker history centos:7 \n删除镜像:\nA:使用标签删除:docker rmi centos\nB:使用ID删除:docker rimi \n构建镜像:\nA:使用docker commit命令\nB:使用Dockerfile构建 \n \n使用docker commit \n例:构建一个带有jdk的镜像
\n按照如下步骤操作
\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@060793baf536 /]# yum install wget\n[root@060793baf536 /]# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm\n\n[root@060793baf536 /]# rpm -ivh jdk-8u131-linux-x64.rpm\nPreparing... ################################# [100%]\nUpdating / installing...\n 1:jdk1.8.0_131-2000:1.8.0_131-fcs ################################# [100%]\nUnpacking JAR files...\n tools.jar...\n plugin.jar...\n javaws.jar...\n deploy.jar...\n rt.jar...\n jsse.jar...\n charsets.jar...\n localedata.jar...\n[root@060793baf536 /]# exit\n[root@localhost ~]# docker commit 060793baf536 centos/jdk:2.0\n
\n通过docker images命令可以看到新增了centos/jdk 标签为2.0的镜像
\n使用Dockerfile构建 \n实际使用中不推荐使用docker commit构建,应使用更灵活和强大的Dockerfile构建docker镜像,直接举例来认识Dockerfile。
\n例:构建一个带有jdk的centos7镜像
\n[root@localhost Dockerfile]# mkdir Dockerfile\n[root@localhost Dockerfile]# cd Dockerfile\n编写Dockerfile:\nFROM centos:7\nMAINTAINER Java-Road "Java-Road@qq.com"\n\nRUN mkdir /usr/local/jdk\nCOPY jdk-8u171-linux-x64.rpm /usr/local/jdk/\nRUN rpm -ivh /usr/local/jdk/jdk-8u171-linux-x64.rpm\n执行如下指令:\n[root@localhost Dockerfile]# docker build -t centos/jdk .\n
\n运行结果如下:\n \ndocker images可以看到新生成的centos/jdk镜像
\n容器操作指令 \n\n[root@localhost ~]# docker run centos:7 /bin/echo'hello world'\n 容器运行完后直接退出\n
\n\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@802e3623e566 /]# ps\n PID TTY TIME CMD\n 1 ? 00:00:00 bash\n 13 ? 00:00:00 ps\n[root@802e3623e566 /]# exit\n执行exit才能退出容器\n
\n\n[root@localhost ~]# docker run -d centos:7 /bin/sh -c "while true; do echo hello world; sleep 1; done"\n
\n\n* docker start 容器ID\n# 例:\n[root@localhost ~]# docker start 802e3623e566\n
\n\n* docker stop 容器ID\n# 例:\n[root@localhost ~]# docker stop 802e3623e566\n
\n\n[root@localhost ~]# docker stop 89566e38c7fb\n[root@localhost ~]# docker rm 89566e38c7fb\n
\n\n[root@localhost ~]# docker exec -it cbd8b1f35dcc /bin/bash\n
\n\n# 导出容器cbd8b1f35dcc到centos_test.tar文件\n[root@localhost ~]# docker export -o centos_test.tar cbd8b1f35dcc\n# 导出的tar文件可以在其他机器上,通过导入来重新运行\n
\n\n# 把导出的文件centos_test.tar通过docker import导入变成镜像\n[root@localhost ~]# docker import centos_test.tar test/centos\n# 通过docker images命令可以看到增加了个test/centos镜像\n
\n实例:制作自己的 Docker 容器 \n\n下面我以 koa-demos 项目为例,介绍怎么写 Dockerfile 文件,实现让用户在 Docker 容器里面运行 Koa 框架。\n作为准备工作,请先下载源码。
\n \n$ git clone https://github.com/ruanyf/koa-demos.git\n$ cd koa-demos\n
\nDockerfile 文件 \n首先,在项目的根目录下,新建一个文本文件.dockerignore,写入下面的内容。
\n.git\nnode_modules\nnpm-debug.log\n
\n上面代码表示,这三个路径要排除,不要打包进入 image 文件。如果你没有路径要排除,这个文件可以不新建。
\n然后,在项目的根目录下,新建一个文本文件 Dockerfile,写入下面的内容。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\n
\n上面代码一共五行,含义如下。
\n\nFROM node:8.4:该 image 文件继承官方的 node image,冒号表示标签,这里标签是8.4,即8.4版本的 node。 \nCOPY . /app:将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入 image 文件的/app目录。 \nWORKDIR /app:指定接下来的工作路径为/app。 \nRUN npm install:在/app目录下,运行npm install命令安装依赖。注意,安装后所有的依赖,都将打包进入 image 文件。 \nEXPOSE 3000:将容器 3000 端口暴露出来, 允许外部连接这个端口。 \n \n创建 image 文件 \n有了 Dockerfile 文件以后,就可以使用docker image build命令创建 image 文件了。
\n$ docker image build -t koa-demo .\n# 或者\n$ docker image build -t koa-demo:0.0.1 .\n``` s\n上面代码中,-t参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。\n\n如果运行成功,就可以看到新生成的 image 文件koa-demo了。\n``` s\n$ docker image ls\n
\n生成容器 \ndocker container run命令会从 image 文件生成容器。\n$ docker container run -p 8000:3000 -it koa-demo /bin/bash\n# 或者\n$ docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash\n
\n上面命令的各个参数含义如下:
\n\n-p参数:容器的 3000 端口映射到本机的 8000 端口。 \n-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。 \nkoa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。 \n/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。 \n \n如果一切正常,运行上面的命令以后,就会返回一个命令行提示符。
\nroot@66d80f4aaf1e:/app#\n
\n这表示你已经在容器里面了,返回的提示符就是容器内部的 Shell 提示符。执行下面的命令。
\nroot@66d80f4aaf1e:/app# node demos/01.js\n
\n这时,Koa 框架已经运行起来了。打开本机的浏览器,访问 http://127.0.0.1:8000 ,网页显示"Not Found",这是因为这个 demo 没有写路由。
\n这个例子中,Node 进程运行在 Docker 容器的虚拟环境里面,进程接触到的文件系统和网络接口都是虚拟的,与本机的文件系统和网络接口是隔离的,因此需要定义容器与物理机的端口映射(map)。
\n现在,在容器的命令行,按下 Ctrl + c 停止 Node 进程,然后按下 Ctrl + d (或者输入 exit)退出容器。此外,也可以用docker container kill终止容器运行。
\n# 在本机的另一个终端窗口,查出容器的 ID\n$ docker container ls\n\n# 停止指定的容器运行\n$ docker container kill [containerID]\n\n# 容器停止运行之后,并不会消失,用下面的命令删除容器文件。\n\n# 查出容器的 ID\n$ docker container ls --all\n\n# 删除指定的容器文件\n$ docker container rm [containerID]\n\n#也可以使用docker container run命令的--rm参数,在容器终止运行后自动删除容器文件。\n$ docker container run --rm -p 8000:3000 -it koa-demo /bin/bash\n
\nCMD 命令 \n上一节的例子里面,容器启动以后,需要手动输入命令node demos/01.js。我们可以把这个命令写在 Dockerfile 里面,这样容器启动以后,这个命令就已经执行了,不用再手动输入了。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\nCMD node demos/01.js\n
\n上面的 Dockerfile 里面,多了最后一行CMD node demos/01.js,它表示容器启动后自动执行node demos/01.js。
\n你可能会问,RUN命令与CMD命令的区别在哪里?简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。
\n注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。现在,启动容器可以使用下面的命令。
\n$ docker container run --rm -p 8000:3000 -it koa-demo:0.0.1\n
\n发布 image 文件 \n容器运行成功后,就确认了 image 文件的有效性。这时,我们就可以考虑把 image 文件分享到网上,让其他人使用。\n首先,去 hub.docker.com 或 cloud.docker.com 注册一个账户。然后,用下面的命令登录。
\n$ docker login\n# 接着,为本地的 image 标注用户名和版本。\n$ docker image tag [imageName] [username]/[repository]:[tag]\n# 实例\n$ docker image tag koa-demos:0.0.1 ruanyf/koa-demos:0.0.1\n#也可以不标注用户名,重新构建一下 image 文件。\n$ docker image build -t [username]/[repository]:[tag] .\n# 最后,发布 image 文件。\n$ docker image push [username]/[repository]:[tag]\n
\n发布成功以后,登录 hub.docker.com ,就可以看到已经发布的 image 文件。
\n其他有用的命令 \ndocker 的主要用法就是上面这些,此外还有几个命令,也非常有用。
\n\ndocker container start \n \n前面的docker container run命令是新建容器,每运行一次,就会新建一个容器。同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器,就要使用docker container start命令,它用来启动已经生成、已经停止运行的容器文件。
\n$ docker container start [containerID]\n
\n\ndocker container stop \n \n前面的docker container kill命令终止容器运行,相当于向容器里面的主进程发出 SIGKILL 信号。而docker container stop命令也是用来终止容器运行,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号。
\n$ bash container stop [containerID]\n
\n这两个信号的差别是,应用程序收到 SIGTERM 信号以后,可以自行进行收尾清理工作,但也可以不理会这个信号。如果收到 SIGKILL 信号,就会强行立即终止,那些正在进行中的操作会全部丢失。
\n\ndocker container logs \n \ndocker container logs命令用来查看 docker 容器的输出,即容器里面 Shell 的标准输出。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令查看输出。
\n$ docker container logs [containerID]\n
\n\ndocker container exec \n \ndocker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令了。
\n$ docker container exec -it [containerID] /bin/bash\n
\n\ndocker container cp \n \ndocker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
\n$ docker container cp [containID]:[/path/to/file] .\n
\n",
+ "__html": "Docker \nDocker简介 \n\nDocker是开源应用容器引擎,轻量级容器技术。 \n基于Go语言,并遵循Apache2.0协议开源 \nDocker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux系统上,也可以实现虚拟化 \n容器完全使用沙箱技术,相互之间不会有任何接口 \n类似于虚拟机技术(vmware、vitural),但docker直接运行在操作系统(Linux)上,而不是运行在虚拟机中,速度快,性能开销极低 \n \n白话文,简介就是:
\n\nDocker支持将软件编译成一个镜像,然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像。\n运行中的这个镜像称为容器,容器启动是非常快速的。类似windows里面的ghost操 作系统,安装好后什么都有了。
\n \n什么是Docker \nDocker 是一个开源的应用容器引擎,基于Go语言,诞生于2013年初,最初发起者是dotCloud公司,开发者可以打包应用到一个轻量级、可移植的容器中,然后发布到主流Linux系统上运行。\n为什么用Docker
\n\n持续交付和部署:使用Docker可以通过定制应用镜像来实现持续集成,持续交付,部署。开发人员构建后的镜像,结合持续集成系统进行集成测试,而运维人员则可以在生产环境中快速部署该镜像,也可以结合持续部署系统进行自动部署。 \n更高效的资源利用:Docker是基于内核级的虚拟化,可以实现更高效的性能,同时对资源的额外需求很低,相比传统虚拟机方式,相同配置的主机能够运行更多的应用。\n更轻松的迁移和扩展:Docker容器几乎可以在任何平台上运行,同时支持主流的操作系统发行版本。 \n更快速的启动时间:传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到妙级,甚至毫秒级的启动时间,大大的节约了开发,测试,部署的时间。\nDocker与传统虚拟机差异 \n \n传统虚拟化方式
\n \nDocker虚拟化方式
\n
\n传统虚拟化是在硬件层面实现虚拟化,需要有额外的虚拟机管理应用和虚拟机操作系统层,而Docker容器是在操作系统层面实现虚拟化,直接复用本地主机操作系统,更加轻量级。
\n核心概念 \n\nDocker镜像:类似于虚拟机里的镜像,是一个只读的模板,一个独立的文件系统,使用镜像可以创建容器,可以理解为镜像是容器的基石。 \nDocker容器:是由Docker镜像创建的运行实例,类似于轻量级的沙箱,每个容器之间都是相互隔离的。支持的操作有启动,停止,删除等。 \ndocker客户端(Client):客户端通过命令行或其他工具使用Docker API(https://docs.docker.com/reference/api/docker_remote_api )与Docker的守护进程进行通信 \ndocker主机(Host):一个物理或虚拟的机器用来执行Docker守护进程和容器 \ndocker仓库(Registry):Docker仓库用来存储镜像,可以理解为代码控制中的代码仓库,Docker Hub(https://hub.docker.com ) 提供了庞大的镜像集合供使用 \n \nDocker安装及启停 \n\n查看centos版本 \n \nDocker 要求 CentOS 系统的内核版本高于 3 .10 \n通过命令:uname -r\n查看当前centos版本,如版本不符,需升级系统版本\n
\n\n升级软件包及内核(可选) \n \nyum update\n
\n\n安装docker \n \nyum install docker\n
\n\n启动docker \n \nsystemctl start docker\n
\n\n将docker服务设为开机启动 \n \nsystemtctl enable docker\n
\n\n停止docker \n \nsystemtctl stop docker\n
\nDocker常用命令及操作 \ndocker镜像命令
\ndocker search mysql ## 搜索镜像\ndocker pull mysql ## 下载镜像 下载命名为:docker pull 镜像名:tag,其中tag多为系统的版本,可选的,默认为least\ndocker images ## 镜像列表 RESPOSITORY为镜像名 TAG为镜像版本,least代表最新 IMAGE_ID 为该镜像唯一ID CREATED 为该镜像创建时间 SIZE 为该镜像大小\ndocker rmi image-id ##删除指定镜像\ndocker rmi $(docker images -q) ##删除所有镜像\ndocker run --name container-name -d image-name ##根据镜像启动容器 -- name:为容器起一个名称\n-d:detached,执行完这句命令后,控制台将不会阻塞,可以继续输入命令操作\nimage-name:要运行的镜像名称\ndocker ps ##查看运行中容器 CONTAINER ID:启动时生成的ID\nIMAGE:该容器使用的镜像\nCOMMAND:容器启动时执行的命令\nCREATED:容器创建时间\nSTATUS:当前容器状态\nPORTS:当前容器所使用的默认端口号\nNAMES:启动时给容器设置的名称\ndocker stop container-name/container-id ## 停止运行中容器\ndocker ps -a ##查看所有的容器\ndocker start container-name/container-id ##启动容器\ndocker rm container-id ## 删除单个容器\ndocker rm $(docker ps -a -q ) ## 删除所有容器\ndocker run --name tomcat2 -d -p 8888:8080 tomcat ## 启动做端口映射的容器\ndocker logs container-id/container-name ##查看容器日志\ndocker port container-id ## 查看端口映射\ndocker exec -it container-id/container-name bash ##容器登录命令为\nexit ##容器退出命令\n
\n更多命令可以参考
\n镜像操作指令 \n\n获取镜像:\ndocker pull centos (默认获取centos最新的镜像)\ndocker pull centos:7 (获取指定标签镜像) \n查看本地镜像:\ndocker images \n查看镜像详细信息:\ndocker inspect centos:7 \n查看镜像历史:\ndocker history centos:7 \n删除镜像:\nA:使用标签删除:docker rmi centos\nB:使用ID删除:docker rimi \n构建镜像:\nA:使用docker commit命令\nB:使用Dockerfile构建 \n \n使用docker commit \n例:构建一个带有jdk的镜像
\n按照如下步骤操作
\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@060793baf536 /]# yum install wget\n[root@060793baf536 /]# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm\n\n[root@060793baf536 /]# rpm -ivh jdk-8u131-linux-x64.rpm\nPreparing... ################################# [100%]\nUpdating / installing...\n 1:jdk1.8.0_131-2000:1.8.0_131-fcs ################################# [100%]\nUnpacking JAR files...\n tools.jar...\n plugin.jar...\n javaws.jar...\n deploy.jar...\n rt.jar...\n jsse.jar...\n charsets.jar...\n localedata.jar...\n[root@060793baf536 /]# exit\n[root@localhost ~]# docker commit 060793baf536 centos/jdk:2.0\n
\n通过docker images命令可以看到新增了centos/jdk 标签为2.0的镜像
\n使用Dockerfile构建 \n实际使用中不推荐使用docker commit构建,应使用更灵活和强大的Dockerfile构建docker镜像,直接举例来认识Dockerfile。
\n例:构建一个带有jdk的centos7镜像
\n[root@localhost Dockerfile]# mkdir Dockerfile\n[root@localhost Dockerfile]# cd Dockerfile\n编写Dockerfile:\nFROM centos:7\nMAINTAINER Java-Road "Java-Road@qq.com"\n\nRUN mkdir /usr/local/jdk\nCOPY jdk-8u171-linux-x64.rpm /usr/local/jdk/\nRUN rpm -ivh /usr/local/jdk/jdk-8u171-linux-x64.rpm\n执行如下指令:\n[root@localhost Dockerfile]# docker build -t centos/jdk .\n
\n运行结果如下:\n \ndocker images可以看到新生成的centos/jdk镜像
\n容器操作指令 \n\n[root@localhost ~]# docker run centos:7 /bin/echo'hello world'\n 容器运行完后直接退出\n
\n\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@802e3623e566 /]# ps\n PID TTY TIME CMD\n 1 ? 00:00:00 bash\n 13 ? 00:00:00 ps\n[root@802e3623e566 /]# exit\n执行exit才能退出容器\n
\n\n[root@localhost ~]# docker run -d centos:7 /bin/sh -c "while true; do echo hello world; sleep 1; done"\n
\n\n* docker start 容器ID\n# 例:\n[root@localhost ~]# docker start 802e3623e566\n
\n\n* docker stop 容器ID\n# 例:\n[root@localhost ~]# docker stop 802e3623e566\n
\n\n[root@localhost ~]# docker stop 89566e38c7fb\n[root@localhost ~]# docker rm 89566e38c7fb\n
\n\n[root@localhost ~]# docker exec -it cbd8b1f35dcc /bin/bash\n
\n\n# 导出容器cbd8b1f35dcc到centos_test.tar文件\n[root@localhost ~]# docker export -o centos_test.tar cbd8b1f35dcc\n# 导出的tar文件可以在其他机器上,通过导入来重新运行\n
\n\n# 把导出的文件centos_test.tar通过docker import导入变成镜像\n[root@localhost ~]# docker import centos_test.tar test/centos\n# 通过docker images命令可以看到增加了个test/centos镜像\n
\n实例:制作自己的 Docker 容器 \n\n下面我以 koa-demos 项目为例,介绍怎么写 Dockerfile 文件,实现让用户在 Docker 容器里面运行 Koa 框架。\n作为准备工作,请先下载源码。
\n \n$ git clone https://github.com/ruanyf/koa-demos.git\n$ cd koa-demos\n
\nDockerfile 文件 \n首先,在项目的根目录下,新建一个文本文件.dockerignore,写入下面的内容。
\n.git\nnode_modules\nnpm-debug.log\n
\n上面代码表示,这三个路径要排除,不要打包进入 image 文件。如果你没有路径要排除,这个文件可以不新建。
\n然后,在项目的根目录下,新建一个文本文件 Dockerfile,写入下面的内容。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\n
\n上面代码一共五行,含义如下。
\n\nFROM node:8.4:该 image 文件继承官方的 node image,冒号表示标签,这里标签是8.4,即8.4版本的 node。 \nCOPY . /app:将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入 image 文件的/app目录。 \nWORKDIR /app:指定接下来的工作路径为/app。 \nRUN npm install:在/app目录下,运行npm install命令安装依赖。注意,安装后所有的依赖,都将打包进入 image 文件。 \nEXPOSE 3000:将容器 3000 端口暴露出来, 允许外部连接这个端口。 \n \n创建 image 文件 \n有了 Dockerfile 文件以后,就可以使用docker image build命令创建 image 文件了。
\n$ docker image build -t koa-demo .\n# 或者\n$ docker image build -t koa-demo:0.0.1 .\n``` s\n上面代码中,-t参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。\n\n如果运行成功,就可以看到新生成的 image 文件koa-demo了。\n``` s\n$ docker image ls\n
\n生成容器 \ndocker container run命令会从 image 文件生成容器。\n$ docker container run -p 8000:3000 -it koa-demo /bin/bash\n# 或者\n$ docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash\n
\n上面命令的各个参数含义如下:
\n\n-p参数:容器的 3000 端口映射到本机的 8000 端口。 \n-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。 \nkoa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。 \n/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。 \n \n如果一切正常,运行上面的命令以后,就会返回一个命令行提示符。
\nroot@66d80f4aaf1e:/app#\n
\n这表示你已经在容器里面了,返回的提示符就是容器内部的 Shell 提示符。执行下面的命令。
\nroot@66d80f4aaf1e:/app# node demos/01.js\n
\n这时,Koa 框架已经运行起来了。打开本机的浏览器,访问 http://127.0.0.1:8000 ,网页显示"Not Found",这是因为这个 demo 没有写路由。
\n这个例子中,Node 进程运行在 Docker 容器的虚拟环境里面,进程接触到的文件系统和网络接口都是虚拟的,与本机的文件系统和网络接口是隔离的,因此需要定义容器与物理机的端口映射(map)。
\n现在,在容器的命令行,按下 Ctrl + c 停止 Node 进程,然后按下 Ctrl + d (或者输入 exit)退出容器。此外,也可以用docker container kill终止容器运行。
\n# 在本机的另一个终端窗口,查出容器的 ID\n$ docker container ls\n\n# 停止指定的容器运行\n$ docker container kill [containerID]\n\n# 容器停止运行之后,并不会消失,用下面的命令删除容器文件。\n\n# 查出容器的 ID\n$ docker container ls --all\n\n# 删除指定的容器文件\n$ docker container rm [containerID]\n\n#也可以使用docker container run命令的--rm参数,在容器终止运行后自动删除容器文件。\n$ docker container run --rm -p 8000:3000 -it koa-demo /bin/bash\n
\nCMD 命令 \n上一节的例子里面,容器启动以后,需要手动输入命令node demos/01.js。我们可以把这个命令写在 Dockerfile 里面,这样容器启动以后,这个命令就已经执行了,不用再手动输入了。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\nCMD node demos/01.js\n
\n上面的 Dockerfile 里面,多了最后一行CMD node demos/01.js,它表示容器启动后自动执行node demos/01.js。
\n你可能会问,RUN命令与CMD命令的区别在哪里?简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。
\n注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。现在,启动容器可以使用下面的命令。
\n$ docker container run --rm -p 8000:3000 -it koa-demo:0.0.1\n
\n发布 image 文件 \n容器运行成功后,就确认了 image 文件的有效性。这时,我们就可以考虑把 image 文件分享到网上,让其他人使用。\n首先,去 hub.docker.com 或 cloud.docker.com 注册一个账户。然后,用下面的命令登录。
\n$ docker login\n# 接着,为本地的 image 标注用户名和版本。\n$ docker image tag [imageName] [username]/[repository]:[tag]\n# 实例\n$ docker image tag koa-demos:0.0.1 ruanyf/koa-demos:0.0.1\n#也可以不标注用户名,重新构建一下 image 文件。\n$ docker image build -t [username]/[repository]:[tag] .\n# 最后,发布 image 文件。\n$ docker image push [username]/[repository]:[tag]\n
\n发布成功以后,登录 hub.docker.com ,就可以看到已经发布的 image 文件。
\n其他有用的命令 \ndocker 的主要用法就是上面这些,此外还有几个命令,也非常有用。
\n\ndocker container start \n \n前面的docker container run命令是新建容器,每运行一次,就会新建一个容器。同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器,就要使用docker container start命令,它用来启动已经生成、已经停止运行的容器文件。
\n$ docker container start [containerID]\n
\n\ndocker container stop \n \n前面的docker container kill命令终止容器运行,相当于向容器里面的主进程发出 SIGKILL 信号。而docker container stop命令也是用来终止容器运行,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号。
\n$ bash container stop [containerID]\n
\n这两个信号的差别是,应用程序收到 SIGTERM 信号以后,可以自行进行收尾清理工作,但也可以不理会这个信号。如果收到 SIGKILL 信号,就会强行立即终止,那些正在进行中的操作会全部丢失。
\n\ndocker container logs \n \ndocker container logs命令用来查看 docker 容器的输出,即容器里面 Shell 的标准输出。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令查看输出。
\n$ docker container logs [containerID]\n
\n\ndocker container exec \n \ndocker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令了。
\n$ docker container exec -it [containerID] /bin/bash\n
\n\ndocker container cp \n \ndocker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
\n$ docker container cp [containID]:[/path/to/file] .\n
\nDocker命令详解(run篇) \n命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]\nUsage: Run a command in a new container\n中文意思为:通过run命令创建一个新的容器(container)
\n常用选项说明 \n-d, --detach=false, 指定容器运行于前台还是后台,默认为false\n-i, --interactive=false, 打开STDIN,用于控制台交互\n-t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false\n-u, --user="", 指定容器的用户\n-a, --attach=[], 登录容器(必须是以docker run -d启动的容器)\n-w, --workdir="", 指定容器的工作目录\n-c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用\n-e, --env=[], 指定环境变量,容器中可以使用该环境变量\n-m, --memory="", 指定容器的内存上限\n-P, --publish-all=false, 指定容器暴露的端口\n-p, --publish=[], 指定容器暴露的端口\n-h, --hostname="", 指定容器的主机名\n-v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录\n--volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录\n--cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities \n--cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities \n--cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法\n--cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU\n--device=[], 添加主机设备给容器,相当于设备直通\n--dns=[], 指定容器的dns服务器\n--dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件\n--entrypoint="", 覆盖image的入口点\n--env-file=[], 指定环境变量文件,文件格式为每行一个环境变量\n--expose=[], 指定容器暴露的端口,即修改镜像的暴露端口\n--link=[], 指定容器间的关联,使用其他容器的IP、env等信息\n--lxc-conf=[], 指定容器的配置文件,只有在指定--exec-driver=lxc时使用\n--name="", 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字\n--net="bridge", 容器网络设置:\nbridge 使用docker daemon指定的网桥\nhost //容器使用主机的网络\ncontainer:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源\nnone 容器使用自己的网络(类似--net=bridge),但是不进行配置\n--privileged=false, 指定容器是否为特权容器,特权容器拥有所有的capabilities\n--restart="no", 指定容器停止后的重启策略:\nno:容器退出时不重启\non-failure:容器故障退出(返回值非零)时重启\nalways:容器退出时总是重启\n--rm=false, 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)\n--sig-proxy=true, 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
\n示例 \n\n运行一个在后台执行的容器,同时,还能用控制台管理: \n \ndocker run -i -t -d ubuntu:latest\n
\n\n运行一个带命令在后台不断执行的容器,不直接展示容器内部信息: \n \ndocker run -d ubuntu:latest ping www.docker.com\n
\n\n运行一个在后台不断执行的容器,同时带有命令,程序被终止后还能重启继续跑,还能用控制台管理, \n \ndocker run -d --restart=always ubuntu:latest ping www.docker.com\n
\n\ndocker run -d --name=ubuntu_server ubuntu:latest\n
\n\n容器暴露80端口,并指定宿主机80端口与其通信(: 之前是宿主机端口,之后是容器需暴露的端口), \n \ndocker run -d --name=ubuntu_server -p 80:80 ubuntu:latest\n
\n\n指定容器内目录与宿主机目录共享(: 之前是宿主机文件夹,之后是容器需共享的文件夹), \n \ndocker run -d --name=ubuntu_server -v /etc/www:/var/www ubuntu:latest\n
\n",
"link": "\\en-us\\blog\\docker\\docker.html",
"meta": {}
}
\ No newline at end of file
diff --git a/en-us/blog/exp/cto.html b/en-us/blog/exp/cto.html
new file mode 100644
index 0000000..139ad04
--- /dev/null
+++ b/en-us/blog/exp/cto.html
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+ cto
+
+
+
+
+ CTO 技能图谱
+岗位职责
+
+建立技术团队文化
+规划技术发展路线
+落地产品研发成果
+宣传公司技术品牌
+吸引优秀技术人才
+
+基本素质
+
+正直诚实的道德修养
+谦虚谨慎的工作态度
+随机应变的处事风格
+统领全局的战略思维
+
+硬技能
+技术能力
+
+具备一定的技术深度
+具备较强的技术广度
+追求技术选型合理性
+对技术发展嗅觉敏锐
+
+业务能力
+
+能深度理解业务本质
+能用技术来帮助业务
+让技术驱动业务发展
+
+架构能力
+
+能站在业务上设计架构
+架构规范合理且易落地
+能为架构设计提出意见
+
+产品能力
+
+具备一定的产品价值观
+能准确地抓住用户刚需
+能为产品设计提出意见
+
+管理能力
+
+团队管理
+
+招人:知道如何吸引所需的人?
+识人:知道如何识别已有队员?
+育人:知道如何让队员们成长?
+开人:知道如何请人愉快离开?
+留人:知道如何留住该留的人?
+挖人:知道如何挖到需要的人?
+
+
+项目管理
+
+估算:知道项目成本如何估算?
+分工:知道工作任务如何分配?
+排期:知道项目计划如何制定?
+实施:知道项目实施如何开展?
+发布:知道项目发布如何执行?
+回顾:知道项目回顾如何进行?
+
+
+绩效管理
+
+制定:知道如何制定考核标准?
+执行:知道如何执行绩效考核?
+优化:知道如何提升团队绩效?
+
+
+时间管理
+
+制定:知道如何制定团队计划?
+管理:知道如何管理团队任务?
+权衡:知道如何权衡优先级别?
+
+
+情绪管理
+
+控制:知道如何控制自我情绪?
+转化:知道如何化悲观为乐观?
+使用:知道如何善用自我情绪?
+
+
+
+软技能
+领导能力
+
+决策能力
+
+不要害怕做决定
+做出正确的决定
+敢于为决定负责
+
+
+影响能力
+
+不要改变别人而是激发别人
+用自己的行为和态度去激发
+持续不断提高自己的影响力
+
+
+沟通能力
+
+向上沟通(与公司创始人)
+
+领会老板真实意图
+站在老板角度思考
+不要强迫改变老板
+
+
+向下沟通(与本部门同事)
+
+是沟通而不是命令
+站在下属立场沟通
+不要吝啬夸赞队员
+
+
+
+
+横向沟通(与跨部门同事)
+
+突出对方的重要性
+先要共识才能共赢
+懂得圆满大于完美
+
+
+
+执行能力
+
+理解执行目标
+提高执行效率
+确保执行效果
+
+学习能力
+
+对新知识充满好奇心
+能够快速学习新技能
+拥有触类旁通的能力
+
+组织能力
+
+积极主动并有活力
+做事情敢于放得开
+能够调用所需资源
+
+洞察能力
+
+善于抓住事物本质
+善于观察人性心理
+善于预见未来变化
+
+抗压能力
+
+学会释放压力
+化压力为动力
+化消极为积极
+
+自省能力
+
+能够不断自我反省
+能够从失败中总结
+能够在总结中提高
+
+战略能力
+
+能够深刻理解公司战略
+能够站在更高层次思考
+结合战略形成技术壁垒
+
+社交能力
+
+善于表达自己
+善于结交朋友
+善于公众演讲
+
+谈判能力
+
+能够通过谈判得到对方认同
+能够从谈判中找到共赢方式
+能够在谈判场合中保持镇定
+
+政治能力
+
+能够对政治持有敏感度
+能够处理办公室小政治
+能够主动避免政治风险
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/exp/cto.json b/en-us/blog/exp/cto.json
new file mode 100644
index 0000000..d0963fc
--- /dev/null
+++ b/en-us/blog/exp/cto.json
@@ -0,0 +1,6 @@
+{
+ "filename": "cto.md",
+ "__html": "CTO 技能图谱 \n岗位职责 \n\n建立技术团队文化 \n规划技术发展路线 \n落地产品研发成果 \n宣传公司技术品牌 \n吸引优秀技术人才 \n \n基本素质 \n\n正直诚实的道德修养 \n谦虚谨慎的工作态度 \n随机应变的处事风格 \n统领全局的战略思维 \n \n硬技能 \n技术能力 \n\n具备一定的技术深度 \n具备较强的技术广度 \n追求技术选型合理性 \n对技术发展嗅觉敏锐 \n \n业务能力 \n\n能深度理解业务本质 \n能用技术来帮助业务 \n让技术驱动业务发展 \n \n架构能力 \n\n能站在业务上设计架构 \n架构规范合理且易落地 \n能为架构设计提出意见 \n \n产品能力 \n\n具备一定的产品价值观 \n能准确地抓住用户刚需 \n能为产品设计提出意见 \n \n管理能力 \n\n团队管理\n\n招人:知道如何吸引所需的人? \n识人:知道如何识别已有队员? \n育人:知道如何让队员们成长? \n开人:知道如何请人愉快离开? \n留人:知道如何留住该留的人? \n挖人:知道如何挖到需要的人? \n \n \n项目管理\n\n估算:知道项目成本如何估算? \n分工:知道工作任务如何分配? \n排期:知道项目计划如何制定? \n实施:知道项目实施如何开展? \n发布:知道项目发布如何执行? \n回顾:知道项目回顾如何进行? \n \n \n绩效管理\n\n制定:知道如何制定考核标准? \n执行:知道如何执行绩效考核? \n优化:知道如何提升团队绩效? \n \n \n时间管理\n\n制定:知道如何制定团队计划? \n管理:知道如何管理团队任务? \n权衡:知道如何权衡优先级别? \n \n \n情绪管理\n\n控制:知道如何控制自我情绪? \n转化:知道如何化悲观为乐观? \n使用:知道如何善用自我情绪? \n \n \n \n软技能 \n领导能力 \n\n决策能力\n\n不要害怕做决定 \n做出正确的决定 \n敢于为决定负责 \n \n \n影响能力\n\n不要改变别人而是激发别人 \n用自己的行为和态度去激发 \n持续不断提高自己的影响力 \n \n \n沟通能力\n\n向上沟通(与公司创始人)\n\n领会老板真实意图 \n站在老板角度思考 \n不要强迫改变老板 \n \n \n向下沟通(与本部门同事)\n\n是沟通而不是命令 \n站在下属立场沟通 \n不要吝啬夸赞队员 \n \n \n \n \n横向沟通(与跨部门同事)\n\n突出对方的重要性 \n先要共识才能共赢 \n懂得圆满大于完美 \n \n \n \n执行能力 \n\n理解执行目标 \n提高执行效率 \n确保执行效果 \n \n学习能力 \n\n对新知识充满好奇心 \n能够快速学习新技能 \n拥有触类旁通的能力 \n \n组织能力 \n\n积极主动并有活力 \n做事情敢于放得开 \n能够调用所需资源 \n \n洞察能力 \n\n善于抓住事物本质 \n善于观察人性心理 \n善于预见未来变化 \n \n抗压能力 \n\n学会释放压力 \n化压力为动力 \n化消极为积极 \n \n自省能力 \n\n能够不断自我反省 \n能够从失败中总结 \n能够在总结中提高 \n \n战略能力 \n\n能够深刻理解公司战略 \n能够站在更高层次思考 \n结合战略形成技术壁垒 \n \n社交能力 \n\n善于表达自己 \n善于结交朋友 \n善于公众演讲 \n \n谈判能力 \n\n能够通过谈判得到对方认同 \n能够从谈判中找到共赢方式 \n能够在谈判场合中保持镇定 \n \n政治能力 \n\n能够对政治持有敏感度 \n能够处理办公室小政治 \n能够主动避免政治风险 \n \n",
+ "link": "\\en-us\\blog\\exp\\cto.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/java/feature.html b/en-us/blog/java/feature.html
index b7ad01c..ba1ec83 100644
--- a/en-us/blog/java/feature.html
+++ b/en-us/blog/java/feature.html
@@ -23,6 +23,7 @@
JAVA7: <>符号、ARM支持、支持多catch
JAVA8:Lamda表达式,类型注解等
JAVA9: 模块化、接口中的私有方法等
+JAVA10: 局部变量推断、整个JDK代码仓库、统一的垃圾回收接口、并行垃圾回收器G1、线程局部管控
Java5 新特性总结
泛型 Generics
@@ -1463,6 +1464,75 @@ 新的工厂方法
* <U> CompletionStage<U> completedStage(U value): 返回一个新的以指定 value 完成的CompletionStage ,并且只支持 CompletionStage 里的接口。
* <U> CompletionStage<U> failedStage(Throwable ex): 返回一个新的以指定异常完成的CompletionStage ,并且只支持 CompletionStage 里的接口。
+Java 10 新特性
+
+局部变量推断
+整个JDK代码仓库
+统一的垃圾回收接口
+并行垃圾回收器G1
+线程局部管控
+
+局部变量推断
+它向 Java 中引入在其他语言中很常见的 var ,比如 JavaScript 。只要编译器可以推断此种类型,你不再需要专门声明一个局部变量的类型。
+开发者将能够声明变量而不必指定关联的类型。比如:
+List <String> list = new ArrayList <String>();
+Stream <String> stream = getStream();
+
+它可以简化为:
+var list = new ArrayList();
+var stream = getStream();
+
+局部变量类型推断将引入“ var ”关键字的使用,而不是要求明确指定变量的类型,我们俗称“语法糖”。
+这就消除了我们之前必须执行的 ArrayList 类型定义的重复。
+其实我们在JDK7,我们需要:
+List <String> list = new ArrayList <String>();
+
+但是在JDK8,我们只需要:
+List <String> list = new ArrayList <>();
+
+所以这是一个逐步的升级。也是人性化的表现与提升。
+有趣的是,需要注意 var 不能成为一个关键字,而是一个保留字。这意味着你仍然可以使用 var 作为一个变量,方法或包名,但是现在(尽管我确定你绝不会)你不能再有一个类被调用。
+局部变量类型推荐仅限于如下使用场景:
+
+局部变量初始化
+for循环内部索引变量
+传统的for循环声明变量
+Java官方表示,它不能用于以下几个地方:
+方法参数
+构造函数参数
+方法返回类型
+字段
+捕获表达式(或任何其他类型的变量声明)
+
+注意:
+Java的var和JavaScript的完全不同,不要这样去类比。Java的var是用于局部类型推断的,而不是JS那样的动态类型,所以下面这个样子是不行的:
+var a = 10 ;
+a = "abc" ;
+
+其次,这个var只能用于局部变量声明,在其他地方使用都是错误的。
+class C {
+ public var a = 10 ;
+ public var f () {
+ return 10 ;
+ }
+}
+
+整合 JDK 代码仓库
+为了简化开发流程,Java 10 中会将多个代码库合并到一个代码仓库中。
+在已发布的 Java 版本中,JDK 的整套代码根据不同功能已被分别存储在多个 Mercurial 存储库,这八个 Mercurial 存储库分别是:root、corba、hotspot、jaxp、jaxws、jdk、langtools、nashorn。
+虽然以上八个存储库之间相互独立以保持各组件代码清晰分离,但同时管理这些存储库存在许多缺点,并且无法进行相关联源代码的管理操作。其中最重要的一点是,涉及多个存储库的变更集无法进行原子提交 (atomic commit)。例如,如果一个 bug 修复时需要对独立存储两个不同代码库的代码进行更改,那么必须创建两个提交:每个存储库中各一个。这种不连续性很容易降低项目和源代码管理工具的可跟踪性和加大复杂性。特别是,不可能跨越相互依赖的变更集的存储库执行原子提交这种多次跨仓库的变化是常见现象。
+为了解决这个问题,JDK 10 中将所有现有存储库合并到一个 Mercurial 存储库中。这种合并的一次生效应,单一的 Mercurial 存储库比现有的八个存储库要更容易地被镜像(作为一个 Git 存储库),并且使得跨越相互依赖的变更集的存储库运行原子提交成为可能,从而简化开发和管理过程。虽然在整合过程中,外部开发人员有一些阻力,但是 JDK 开发团队已经使这一更改成为 JDK 10 的一部分。
+统一的垃圾回收接口
+在当前的 Java 结构中,组成垃圾回收器(GC)实现的组件分散在代码库的各个部分。尽管这些惯例对于使用 GC 计划的 JDK 开发者来说比较熟悉,但对新的开发人员来说,对于在哪里查找特定 GC 的源代码,或者实现一个新的垃圾收集器常常会感到困惑。更重要的是,随着 Java modules 的出现,我们希望在构建过程中排除不需要的 GC,但是当前 GC 接口的横向结构会给排除、定位问题带来困难。
+为解决此问题,需要整合并清理 GC 接口,以便更容易地实现新的 GC,并更好地维护现有的 GC。Java 10 中,hotspot/gc 代码实现方面,引入一个干净的 GC 接口,改进不同 GC 源代码的隔离性,多个 GC 之间共享的实现细节代码应该存在于辅助类中。这种方式提供了足够的灵活性来实现全新 GC 接口,同时允许以混合搭配方式重复使用现有代码,并且能够保持代码更加干净、整洁,便于排查收集器问题。
+并行垃圾回收器 G1
+大家如果接触过 Java 性能调优工作,应该会知道,调优的最终目标是通过参数设置来达到快速、低延时的内存垃圾回收以提高应用吞吐量,尽可能的避免因内存回收不及时而触发的完整 GC(Full GC 会带来应用出现卡顿)。
+G1 垃圾回收器是 Java 9 中 Hotspot 的默认垃圾回收器,是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是当并发收集无法快速回收内存时,会触发垃圾回收器回退进行 Full GC。之前 Java 版本中的 G1 垃圾回收器执行 GC 时采用的是基于单线程标记扫描压缩算法(mark-sweep-compact)。为了最大限度地减少 Full GC 造成的应用停顿的影响,Java 10 中将为 G1 引入多线程并行 GC,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
+Java 10 中将采用并行化 mark-sweep-compact 算法,并使用与年轻代回收和混合回收相同数量的线程。具体并行 GC 线程数量可以通过:-XX:ParallelGCThreads 参数来调节,但这也会影响用于年轻代和混合收集的工作线程数。
+线程局部管控
+在已有的 Java 版本中,JVM 线程只能全部启用或者停止,没法做到对单独某个线程的操作。为了能够对单独的某个线程进行操作,Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程。通过这种方式显著地提高了现有 JVM 功能的性能开销,并且改变了到达 JVM 全局安全点的现有时间语义。
+增加的参数为:-XX:ThreadLocalHandshakes (默认为开启),将允许用户在支持的平台上选择安全点。
+参考
diff --git a/en-us/blog/java/feature.json b/en-us/blog/java/feature.json
index 1ea4849..6b73244 100644
--- a/en-us/blog/java/feature.json
+++ b/en-us/blog/java/feature.json
@@ -1,6 +1,6 @@
{
"filename": "feature.md",
- "__html": "Java 新特性总结 \n总结的这些新特性,都是自己觉得在开发中实际用得上的。\n简单概括下就是:
\n\nJAVA1.3:普通的原始的JAVA,基本语法相信大家都见过了 \nJAVA1.4:assert关键字 \nJAVA5:枚举类型、泛型、自动拆装箱 \nJAVA6: @Override注解 \nJAVA7: <>符号、ARM支持、支持多catch \nJAVA8:Lamda表达式,类型注解等 \nJAVA9: 模块化、接口中的私有方法等 \n \nJava5 新特性总结 \n泛型 Generics \n引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。Parameterized Type作为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。
\n泛型可以带来如下的好处总结如下:
\n\n类型安全:抛弃List、Map,使用List、Map给它们添加元素或者使用Iterator遍历时,编译期就可以给你检查出类型错误 \n方法参数和返回值加上了Type: 抛弃List、Map,使用List、Map \n不需要类型转换:List list=new ArrayList(); \n类型通配符“?”: 假设一个打印List中元素的方法printList,我们希望任何类型T的List都可以被打印 \n \n枚举类型 \n引入了枚举类型
\n自动装箱拆箱(自动类型包装和解包)autoboxing & unboxing \n简单的说是类型自动转换。自动装包:基本类型自动转为包装类(int ——Integer)自动拆包:包装类自动转为基本类型(Integer——int)
\n可变参数varargs(varargs number of arguments) \n参数类型相同时,把重载函数合并到一起了。如:
\npublic void test (object... objs) {\n for (Object obj:objs){\n System.out.println(obj);\n }\n}\n
\nAnnotations(重要) 它是java中的metadata(注释) \n注解在JAVA5中就引入了。这是非常重要的特性。现在注解的应用已经随处可见。不过JAVA5的注解还不成熟,没法自定义注解。
\n新的迭代语句 \nfor (int n:numbers){\n\n}\n
\n静态导入(import static ) \n导入静态对象,可以省略些代码。不过这个也不常用。
\nimport static java.lang.System.out;\npublic class HelloWorld {\n public static void main (String[] args) {\n out.print(\"Hello World!\" );\n }\n}\n
\n新的格式化方法java.util.Formatter) \nformatter.format(\"Remaining account balance: $%.2f\" , balance);\n
\n新的线程模型和并发库Thread Framework(重要) \n最主要的就是引入了java.util.concurrent包,这个都是需要重点掌握的。
\nHashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore
\nJava6 新特性总结 \nWeb Services \n优先支持编写 XML web service 客户端程序。你可以用过简单的annotaion将你的API发布成.NET交互的web services. Mustang 添加了新的解析和 XML 在 Java object-mapping APIs中, 之前只在Java EE平台实现或者Java Web Services Pack中提供.
\nScripting \n现在你可以在Java源代码中混入JavaScript了,这对开发原型很有有用,你也可以插入自己的脚本引擎。
\nJDBC4.0 \nJAVA6将联合绑定 Java DB (Apache Derby). JDBC 4.0 增加了许多特性例如支持XML作为SQL数据类型,更好的集成Binary Large OBjects (BLOBs) 和 Character Large OBjects (CLOBs) .
\nUI优化 \n\nGUI 开发者可以有更多的技巧来使用 SwingWorker utility ,以帮助GUI应用中的多线程。, JTable 分类和过滤,以及添加splash闪屏。 \nSwing拥有更好的 look-and-feel , LCD 文本呈现, 整体GUI性能的提升。Java应用程序可以和本地平台更好的集成,例如访问平台的系统托盘和开始菜单。Mustang将Java插件技术和Java Web Start引擎统一了起来。 \n \n监控管理增强 \n添加更多的诊断信息,绑定了不是很知名的 memory-heap 分析工具Jhat 来查看内核导出。
\n编译API \ncompiler API提供编程访问javac,可以实现进程内编译,动态产生Java代码
\n自定义注解 \nJava tool和framework 提供商可以定义自己的 annotations ,并且内核支持自定义annotation的插件和执行处理器
\n安全性 \nXML-数字签名(XML-DSIG) APIs 用于创建和操纵数字签名); 新的方法来访问本地平台的安全服务,例如本地Microsoft Windows for secure authentication and communicationnative 的Public Key Infrastructure (PKI) 和 cryptographic services, Java Generic Security Services (Java GSS) 和 Kerberos services for authentication, 以及访问 LDAP servers 来认证用户.
\nJava7 新特性总结 \nswitch中使用String \njava7以前在switch中只能使用number或enum,现在可以使用string了。
\n示例:
\nString s = \"a\" ;\nswitch (s) {\n case \"a\" :\n System.out.println(\"is a\" );\n break ;\n case \"b\" :\n System.out.println(\"is b\" );\n break ;\n default :\n System.out.println(\"is c\" );\n break ;\n}\n
\n异常处理 \n\nThrowable类增加addSuppressed方法和getSuppressed方法,支持原始异常中加入被抑制的异常。 \n异常抑制:在try和finally中同时抛出异常时,finally中抛出的异常会在异常栈中向上传递,而try中产生的原始异常会消失。 \n在Java7之前的版本,可以将原始异常保存,在finally中产生异常时抛出原始异常: \n \n\npublic void read (String filename) throws BaseException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException == null ) { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw new BaseException(readException); \n } \n } \n}\n\n\npublic void read (String filename) throws IOException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException != null ) { \n readException.addSuppressed(ex); \n } \n else { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw readException; \n } \n } \n} \n
\ntry-with-resources \njava7以前对某些资源的操作是需要手动关闭,如InputStream,Writes,Sockets,Sql等,需要在finally中进行关闭资源的操作,现在不需要使用finally来保证打开的流被正确关闭,现在是自动完成的,会自动释放资源,确保每一个资源在处理完成后都会关闭,就不需要我们代码去close();
\n\n在采用try-with-resources方式后,不需要再次声明流的关闭。 \n可以使用try-with-resources的资源有:任何实现了java.lang.AutoCloseable接口和java.io.Closeable接口的对象。为了支持这个行为,所有可关闭的类将被修改为可以实现一个Closable(可关闭的)接口。 \n \npublic interface Closeable extends AutoCloseable {}\npublic abstract class Reader implements Readable , Closeable {}\n
\n如果在try语句中写入了没有实现该接口的类,会提示:
\n\nThe resource type File does not implement java.lang.AutoCloseable
\n \n示例:
\n\nOutputStream fos = null ;\ntry {\n fos = new FileOutputStream(\"D:/file\" );\n} finally {\n fos.close();\n}\n\ntry (OutputStream fos = new FileOutputStream(\"D:/file\" );){\n \n}\n\npublic void copyFile (String fromPath, String toPath) throws IOException { \ntry ( InputStream input = new FileInputStream(fromPath); \n OutputStream output = new FileOutputStream(toPath) ) { \n byte [] buffer = new byte [8192 ]; \n int len = -1 ; \n while ( (len=input.read(buffer))!=-1 ) { \n output.write(buffer, 0 , len); \n } \n }\n} \n
\n捕获多个异常 \njava7以前在一个方法抛出多个异常时,只能一个个的catch,这样代码会有多个catch,显得很不友好,现在只需一个catch语句,多个异常类型用"|"隔开。\n示例:
\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException e) {\n e.printStackTrace();\n} catch (IllegalAccessException e) {\n e.printStackTrace();\n}\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException | IllegalAccessException e) {\n e.printStackTrace();\n}\n
\n泛型实例化类型自动推断 \n运用泛型实例化类型自动推断,对通用实例创建(diamond)的type引用进行了改进\n示例:
\n\nList<String> list = new ArrayList<String>();\n\nList<String> list = new ArrayList<>();\n
\n增加二进制表示 \nJava7前支持十进制(123)、八进制(0123)、十六进制(0X12AB)
\nJava7增加二进制表示(0B11110001、0b11110001)\n示例:
\nint binary = 0b0001_1001 ;\nSystem.out.println(\"binary is :\" +binary);\n binary is :25 \n
\n数字中可添加分隔符 \nJava7中支持在数字中间增加'_'作为分隔符,分隔长int以及long(也支持double,float),显示更直观,如(12_123_456)。
\n下划线只能在数字中间,编译时编译器自动删除数字中的下划线。
\n示例:
\nint intOne = 1_000_000 ;\nlong longOne = 1_000_000 ;\ndouble doubleOne = 1_000_000 ;\nfloat floatOne = 1_000_000 ;\n
\n变长参数方法的优化 \n参数类型相同时,把重载函数合并到一起了\n使用可变参数时,提升编译器的警告和错误信息
\npublic int sum (int ... args) { \n int result = 0 ; \n for (int value : args) { \n result += value; \n } \n return result; \n} \n
\n集合类的语法支持 \n\nList<String> list = new ArrayList<String>();\n list.add(\"item\" );\n String item = list.get(0 );\n\n Set<String> set = new HashSet<String>();\n set.add(\"item\" );\n Map<String, Integer> map = new HashMap<String, Integer>();\n map.put(\"key\" , 1 );\n int value = map.get(\"key\" );\n\nList<String> list = [\"item\" ];\n String item = list[0 ];\n\n Set<String> set = {\"item\" };\n\n Map<String, Integer> map = {\"key\" : 1 };\n int value = map[\"key\" ]; \n
\n自动资源管理 \nJava中某些资源是需要手动关闭的,如InputStream,Writes,Sockets,Sql classes等。这个新的语言特性允许try语句本身申请更多的资源,这些资源作用于try代码块,并自动关闭。
\n\nBufferedReader br = new BufferedReader(new FileReader(path));\ntry {\nreturn br.readLine();\n } finally {\n br.close();\n}\n\ntry (BufferedReader br = new BufferedReader(new FileReader(path)) {\n return br.readLine();\n}\n
\n新增一些取环境信息的工具方法 \nFile System.getJavaIoTempDir() \nFile System.getJavaHomeDir() \nFile System.getUserHomeDir() \nFile System.getUserDir() \n
\nJava8 新特性总结 \nJava8 新增了非常多的特性,我们主要讨论以下几个:
\n\nLambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 \n方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 \n默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。 \n新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。 \nStream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。 \nDate Time API − 加强对日期与时间的处理。 \nOptional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。 \nNashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。 \n \nLambda表达式和函数式接口 \nLambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
\nLambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> System.out.println( e ) );\n
\n在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( ( String e ) -> System.out.println( e ) );\n
\n如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> {\n System.out.print( e );\n System.out.print( e );\n} );\n
\nLambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:
\nString separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) );\n\nfinal String separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) ); \n
\nLambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );\n\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> {\n int result = e1.compareTo( e2 );\n return result;\n} );\n
\nLambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
\n@FunctionalInterface \npublic interface Functional {\n void method () ;\n}\n
\n不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
\n@FunctionalInterface \npublic interface FunctionalDefaultMethods {\n void method () ;\n\n default void defaultMethod () {\n System.out.print(\"defaultMethod\" );\n }\n static void staticMethod () {\n System.out.print(\"staticMethod\" );\n }\n}\n
\nLambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档 。
\n接口的默认方法和静态方法 \nJava 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
\n默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
\nprivate interface Defaulable {\n \n \n default String notRequired () { \n return \"Default implementation\" ; \n } \n}\n\nprivate static class DefaultableImpl implements Defaulable {\n}\n\nprivate static class OverridableImpl implements Defaulable {\n @Override \n public String notRequired () {\n return \"Overridden implementation\" ;\n }\n}\n
\nDefaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
\nJava 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
\nprivate interface DefaulableFactory {\n \n static Defaulable create ( Supplier< Defaulable > supplier ) {\n return supplier.get();\n }\n}\n
\n下面的代码片段整合了默认方法和静态方法的使用场景:
\npublic static void main ( String[] args ) {\n Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );\n System.out.println( defaulable.notRequired() );\n\n defaulable = DefaulableFactory.create( OverridableImpl::new );\n System.out.println( defaulable.notRequired() );\n}\n
\n这段代码的输出结果如下:
\n\nDefault implementation\nOverridden implementation
\n \n由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
\n尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档 。
\n方法引用 \n方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
\n西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。
\npublic static class Car {\n public static Car create ( final Supplier< Car > supplier ) {\n return supplier.get();\n }\n\n public static void collide ( final Car car ) {\n System.out.println( \"Collided \" + car.toString() );\n }\n\n public void follow ( final Car another ) {\n System.out.println( \"Following the \" + another.toString() );\n }\n\n public void repair () { \n System.out.println( \"Repaired \" + this .toString() );\n }\n}\n
\n第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。
\nfinal Car car = Car.create( Car::new );\nfinal List< Car > cars = Arrays.asList( car );\n
\n第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
\ncars.forEach( Car::collide );\n
\n第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
\ncars.forEach( Car::repair );\n
\n第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
\nfinal Car police = Car.create( Car::new );\ncars.forEach( police::follow );\n
\n运行上述例子,可以在控制台看到如下输出(Car实例可能不同):
\n\nCollided com.javacodegeeks.java8.method.references.MethodReferencesC a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nRepaired com.javacodegeeks.java8.method.references.MethodReferences C a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nFollowing the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n重复注解 \n自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
\n在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:
\npackage com.javacodegeeks.java8.repeatable.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Repeatable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\npublic class RepeatingAnnotations {\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n public @interface Filters {\n Filter[] value();\n }\n\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n @Repeatable ( Filters.class )\n public @interface Filter {\n String value () ;\n };\n\n @Filter ( \"filter1\" )\n @Filter ( \"filter2\" )\n public interface Filterable {\n }\n\n public static void main (String[] args) {\n for ( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {\n System.out.println( filter.value() );\n }\n }\n}\n
\n正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。
\n另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:
\n\nfilter1\nfilter2
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n更好的类型推断 \nJava 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。例子代码如下:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class Value < T > {\n public static < T > T defaultValue () {\n return null ;\n }\n\n public T getOrDefault ( T value, T defaultValue ) {\n return ( value != null ) ? value : defaultValue;\n }\n}\n
\n下列代码是Value类型的应用:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class TypeInference {\n public static void main (String[] args) {\n final Value< String > value = new Value<>();\n value.getOrDefault( \"22\" , Value.defaultValue() );\n }\n}\n
\n参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value.defaultValue()。
\n拓宽注解的应用场景 \nJava 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:
\npackage com.javacodegeeks.java8.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.ArrayList;\nimport java.util.Collection;\n\npublic class Annotations {\n @Retention ( RetentionPolicy.RUNTIME )\n @Target ( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )\n public @interface NonEmpty {\n }\n\n public static class Holder < @NonEmpty T > extends @NonEmpty Object {\n public void method () throws @NonEmpty Exception {\n }\n }\n\n @SuppressWarnings ( \"unused\" )\n public static void main (String[] args) {\n final Holder< String > holder = new @NonEmpty Holder< String >();\n @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();\n }\n}\n
\nElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。
\nJava编译器的新特性 \n为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer library。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。
\npackage com.javacodegeeks.java8.parameter.names;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\n\npublic class ParameterNames {\n public static void main (String[] args) throws Exception {\n Method method = ParameterNames.class.getMethod( \"main\" , String[].class );\n for ( final Parameter parameter: method.getParameters() ) {\n System.out.println( \"Parameter: \" + parameter.getName() );\n }\n }\n}\n
\n在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:
\n\nParameter: arg0\n如果带-parameters参数,则会输出如下结果(正确的结果):\nParameter: args\n如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:
\n \n<plugin> \n <groupId>org.apache.maven.plugins</groupId> \n <artifactId>maven-compiler-plugin</artifactId> \n <version>3.1</version> \n <configuration> \n <compilerArgument>-parameters</compilerArgument> \n <source>1.8</source> \n <target>1.8</target> \n </configuration> \n</plugin> \n
\nOptional \nJava应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。
\nOptional仅仅是一个容器:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。
\n接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:
\nOptional< String > fullName = Optional.ofNullable( null );\nSystem.out.println( \"Full Name is set? \" + fullName.isPresent() );\nSystem.out.println( \"Full Name: \" + fullName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( fullName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\n
\n如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。
\n上述代码的输出结果如下:
\n\nFull Name is set? false\nFull Name: [none]\nHey Stranger!
\n \n再看下另一个简单的例子:
\nOptional< String > firstName = Optional.of( \"Tom\" );\nSystem.out.println( \"First Name is set? \" + firstName.isPresent() );\nSystem.out.println( \"First Name: \" + firstName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( firstName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\nSystem.out.println();\n
\n如果想了解和学习更详细的内容,可以参考官方文档 。
\nStreams \n新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
\nSteam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:
\npublic class Streams {\n private enum Status {\n OPEN, CLOSED\n };\n\n private static final class Task {\n private final Status status;\n private final Integer points;\n\n Task( final Status status, final Integer points ) {\n this .status = status;\n this .points = points;\n }\n\n public Integer getPoints () {\n return points;\n }\n\n public Status getStatus () {\n return status;\n }\n\n @Override \n public String toString () {\n return String.format( \"[%s, %d]\" , status, points );\n }\n }\n}\n
\nTask类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:
\nfinal Collection< Task > tasks = Arrays.asList(\n new Task( Status.OPEN, 5 ),\n new Task( Status.OPEN, 13 ),\n new Task( Status.CLOSED, 8 ) \n);\n
\n首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。
\n\nfinal long totalPointsOfOpenTasks = tasks\n .stream()\n .filter( task -> task.getStatus() == Status.OPEN )\n .mapToInt( Task::getPoints )\n .sum();\n\nSystem.out.println( \"Total points: \" + totalPointsOfOpenTasks );\n
\n运行这个方法的控制台输出是:
\n\nTotal points: 18\n这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。
\n \n在学习下一个例子之前,还需要记住一些steams(点此更多细节 )的知识点。Steam之上的操作可分为中间操作和晚期操作。
\n中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。
\n晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。
\nsteam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:
\n\nfinal double totalPoints = tasks\n .stream()\n .parallel()\n .map( task -> task.getPoints() ) \n .reduce( 0 , Integer::sum );\n\nSystem.out.println( \"Total points (all tasks): \" + totalPoints );\n
\n这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:
\n\nTotal points(all tasks): 26.0
\n \n对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:
\n\nfinal Map< Status, List< Task > > map = tasks\n .stream()\n .collect( Collectors.groupingBy( Task::getStatus ) );\nSystem.out.println( map );\n
\n控制台的输出如下:
\n\n{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
\n \n最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:
\n\nfinal Collection< String > result = tasks\n .stream() \n .mapToInt( Task::getPoints ) \n .asLongStream() \n .mapToDouble( points -> points / totalPoints ) \n .boxed() \n .mapToLong( weigth -> ( long )( weigth * 100 ) ) \n .mapToObj( percentage -> percentage + \"%\" ) \n .collect( Collectors.toList() ); \n\nSystem.out.println( result );\n
\n控制台输出结果如下:
\n\n[19%, 50%, 30%]
\n \n最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:
\nfinal Path path = new File( filename ).toPath();\ntry ( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {\n lines.onClose( () -> System.out.println(\"Done!\" ) ).forEach( System.out::println );\n}\n
\nStream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。
\n点此更多细节
\nDate/Time API(JSR 310) \nJava 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。
\n因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。
\n我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。
\n\nfinal Clock clock = Clock.systemUTC();\nSystem.out.println( clock.instant() );\nSystem.out.println( clock.millis() );\n
\n这个例子的输出结果是:
\n\n2014-04-12T15:19:29.282Z\n1397315969360
\n \n第二,关注下LocalDate和LocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。
\n\nfinal LocalDate date = LocalDate.now();\nfinal LocalDate dateFromClock = LocalDate.now( clock );\n\nSystem.out.println( date );\nSystem.out.println( dateFromClock );\n\n\nfinal LocalTime time = LocalTime.now();\nfinal LocalTime timeFromClock = LocalTime.now( clock );\n\nSystem.out.println( time );\nSystem.out.println( timeFromClock );\n
\n上述例子的输出结果如下:
\n\n2014-04-12\n2014-04-12\n11:25:54.568\n15:25:54.568
\n \nLocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子:
\n\nfinal LocalDateTime datetime = LocalDateTime.now();\nfinal LocalDateTime datetimeFromClock = LocalDateTime.now( clock );\n\nSystem.out.println( datetime );\nSystem.out.println( datetimeFromClock );\n
\n上述这个例子的输出结果如下:
\n\n2014-04-12T11:37:52.309\n2014-04-12T15:37:52.309
\n \n如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:
\n\nfinal ZonedDateTime zonedDatetime = ZonedDateTime.now();\nfinal ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );\nfinal ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( \"America/Los_Angeles\" ) );\n\nSystem.out.println( zonedDatetime );\nSystem.out.println( zonedDatetimeFromClock );\nSystem.out.println( zonedDatetimeFromZone );\n
\n这个例子的输出结果是:
\n\n2014-04-12T11:47:01.017-04:00[America/New_York]\n2014-04-12T15:47:01.017Z\n2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
\n \n最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:
\n\nfinal LocalDateTime from = LocalDateTime.of( 2014 , Month.APRIL, 16 , 0 , 0 , 0 );\nfinal LocalDateTime to = LocalDateTime.of( 2015 , Month.APRIL, 16 , 23 , 59 , 59 );\n\nfinal Duration duration = Duration.between( from, to );\nSystem.out.println( \"Duration in days: \" + duration.toDays() );\nSystem.out.println( \"Duration in hours: \" + duration.toHours() );\n
\n这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:
\n\nDuration in days: 365\nDuration in hours: 8783
\n \n对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果想了解和学习更详细的内容,可以参考官方文档 。
\nNashorn JavaScript引擎 \nJava 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下:
\nScriptEngineManager manager = new ScriptEngineManager();\nScriptEngine engine = manager.getEngineByName( \"JavaScript\" );\n\nSystem.out.println( engine.getClass().getName() );\nSystem.out.println( \"Result:\" + engine.eval( \"function f() { return 1; }; f() + 1;\" ) );\n
\n这个代码的输出结果如下:
\n\njdk.nashorn.api.scripting.NashornScriptEngine\nResult: 2
\n \nBase64 \n对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
\npackage com.javacodegeeks.java8.base64;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\n\npublic class Base64s {\n public static void main (String[] args) {\n final String text = \"Base64 finally in Java 8!\" ;\n\n final String encoded = Base64\n .getEncoder()\n .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );\n System.out.println( encoded );\n\n final String decoded = new String( \n Base64.getDecoder().decode( encoded ),\n StandardCharsets.UTF_8 );\n System.out.println( decoded );\n }\n}\n
\n这个例子的输出结果如下:
\n\nQmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==\nBase64 finally in Java 8!
\n \n新的Base64API也支持URL和MINE的编码解码。\n(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
\n并行数组 \nJava8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:
\npackage com.javacodegeeks.java8.parallel.arrays;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class ParallelArrays {\n public static void main ( String[] args ) {\n long [] arrayOfLong = new long [ 20000 ];\n\n Arrays.parallelSetAll( arrayOfLong,\n index -> ThreadLocalRandom.current().nextInt( 1000000 ) );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n\n Arrays.parallelSort( arrayOfLong );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n }\n}\n
\n上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:
\n\nUnsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378\nSorted: 39 220 263 268 325 607 655 678 723 793
\n \n并发性 \n基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作
\nJava 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。
\n在java.util.concurrent.atomic包中也新增了不少工具类,列举如下:
\n\nDoubleAccumulator \nDoubleAdder \nLongAccumulator \nLongAdder \n \n新的Java工具 \nNashorn引擎:jjs \njjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:
\nfunction f ( ) {\n return 1 ;\n};\n\nprint( f() + 1 );\n
\n可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:
\n\n2
\n \n如果需要了解细节,可以参考官方文档 。
\n类依赖分析器:jdeps \njdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。
\n我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
\njdeps org.springframework.core-3.0 .5.RELEASE.jar\n
\n这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".
\norg.springframework.core-3.0.5.RELEASE.jar -> C:\\Program Files\\Java\\jdk1.8.0\\jre\\lib\\rt.jar\n org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.io\n -> java.lang\n -> java.lang.annotation\n -> java.lang.ref\n -> java.lang.reflect\n -> java.util\n -> java.util.concurrent\n -> org.apache.commons.logging not found\n -> org.springframework.asm not found\n -> org.springframework.asm.commons not found\n org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.lang\n -> java.lang.annotation\n -> java.lang.reflect\n -> java.util\n
\n如果需要了解细节,可以参考官方文档 。
\nJVM的新特性 \n使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
\nJava 9 新特性总结 \n\n模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。 \nREPL (JShell):交互式编程环境。 \nHTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。 \n改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。 \n多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。 \n集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。 \n私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。 \n进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。 \n改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。 \n改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。 \n改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。 \n改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。 \n改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。 \n多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。 \n改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。 \n轻量级的 JSON API:内置了一个轻量级的JSON API \n响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。 \n \nJava9 新特性之---目录结构 \n包含jdk8及以前的jdk版本,所有目录结构以及目录含义如图:\n \n \njdk9之后,目录结构发生变化如图:\n \n这个新特性只要了解下就可以了,这个目录结构是方便为了接下来新特性做保证
\n模块化 \n一个大型的项目,比如淘宝商城等,都会包含多个模块,比如订单模块,前台模块,后台管理模块,广告位模块,会员模块.....等等,各个模块之间会相互调用,不过这种情况下会很少,只针对特殊情况,如果一个项目有30个模块系统进行开发,但是只要某个单独模块运行时,都会带动所有的模块,这样对于jvm来说在内存和性能上会很低,所以,java9提供了这一个特性,某一个模块运行的时候,jvm只会启动和它有依赖的模块,并不会加载所有的模块到内存中,这样性能大大的提高了。写法上如下:
\n \n一个项目中的两个模块,模块之间通过module-info.java来关联,在IDEA编辑器右键创建package-info.java\n \n在这个两个模块java9Demo和java9Test中,java9demo编写一个实体类Person,在java9Test调用这样一个过程
\n这个是java9Demo 将 java9Test 模块需要的文件导出 exports 把它所在的包导出
\nmodule java9Demo{\n requires com.mdxl.layer_cj.entity;\n}\n
\n然后在java9Test模块中创建一个package-info.java,引入java9Demo模块导出包名
\nmodule java9Test{\n requires java9Demo;\n}\n
\n这样就可以直接在java9Test中引入Person实体类了,这只是一个简单的例子。exports 控制着那些包可以被模块访问,所以不被导出的包不能被其他模块访问
\nJShell工具 \n怎么理解,怎么用呢?这个只是针对于java9来说,相当于cmd工具,你可以和cmd一样,直接写方法等等,不过我认为只是适用于初学者做一些最简单的运算和写一些方法:\n在cmd中打开这个工具:
\n$ jshell\n| Welcome to JShell -- Version 9-ea\n| For an introduction type: /help intro\njshell>\n
\n查看 JShell 命令
\n输入 /help 可以查看 JShell相关的命令:
\njshell> /help\n| Type a Java language expression, statement, or declaration.\n| Or type one of the following commands:\n| /list [<name or id>|-all|-start]\n| list the source you have typed\n| /edit <name or id>\n| edit a source entry referenced by name or id\n| /drop <name or id>\n| delete a source entry referenced by name or id\n| /save [-all|-history|-start] <file>\n| Save snippet source to a file.\n| /open <file>\n| open a file as source input\n| /vars [<name or id>|-all|-start]\n| list the declared variables and their values\n| /methods [<name or id>|-all|-start]\n| list the declared methods and their signatures\n| /types [<name or id>|-all|-start]\n| list the declared types\n| /imports \n| list the imported items\n
\n执行 JShell 命令
\n/imports 命令用于查看已导入的包:
\njshell> /imports\n| import java.io.*\n| import java.math.*\n| import java.net.*\n| import java.nio.file.*\n| import java.util.*\n| import java.util.concurrent.*\n| import java.util.function.*\n| import java.util.prefs.*\n| import java.util.regex.*\n| import java.util.stream.*\njshell>\n
\n等等,我认为只适用于初学者学习java不用其他编辑工具就可以学习java
\nHTTP 2 客户端 \nJDK9之前提供HttpURLConnection API来实现Http访问功能,但是这个类基本很少使用,一般都会选择Apache的Http Client,此次在Java 9的版本中引入了一个新的package:java.net.http,里面提供了对Http访问很好的支持,不仅支持Http1.1而且还支持HTTP2(什么是HTTP2?请参见HTTP2的时代来了...),以及WebSocket,据说性能特别好。
\n \n注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。
\n改进 Javadoc \njavadoc 工具可以生成 Java 文档, Java 9 的 javadoc 的输出现在符合兼容 HTML5 标准。
\n//java 9 之前\nC:\\JAVA>javadoc -d C:/JAVA Tester.java\n//java 9 之后\nC:\\JAVA> javadoc -d C:/JAVA -html5 Tester.java\n
\n多版本兼容 jar 包 \n多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
\n通过 --release 参数指定编译版本。
\n具体的变化就是 META-INF 目录下 MANIFEST.MF 文件新增了一个属性:
\n\nMulti-Release: true
\n \n然后 META-INF 目录下还新增了一个 versions 目录,如果是要支持 java9,则在 versions 目录下有 9 的目录。
\nmultirelease.jar\n├── META-INF\n│ └── versions\n│ └── 9\n│ └── multirelease\n│ └── Helper.class\n├── multirelease\n ├── Helper.class\n └── Main.class\n
\n集合工厂方法 \nJava 9 List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
\n这些工厂方法可以以更简洁的方式来创建集合。
\n旧方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n public static void main (String []args) {\n Set<String> set = new HashSet<>();\n set.add(\"A\" );\n set.add(\"B\" );\n set.add(\"C\" );\n set = Collections.unmodifiableSet(set);\n System.out.println(set);\n List<String> list = new ArrayList<>();\n\n list.add(\"A\" );\n list.add(\"B\" );\n list.add(\"C\" );\n list = Collections.unmodifiableList(list);\n System.out.println(list);\n Map<String, String> map = new HashMap<>();\n\n map.put(\"A\" ,\"Apple\" );\n map.put(\"B\" ,\"Boy\" );\n map.put(\"C\" ,\"Cat\" );\n map = Collections.unmodifiableMap(map);\n System.out.println(map);\n }\n}\n
\n执行输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n
\n新方法创建集合:
\nJava 9 中,以下方法被添加到 List,Set 和 Map 接口以及它们的重载对象。
\nstatic <E> List<E> of (E e1, E e2, E e3) ;\nstatic <E> Set<E> of (E e1, E e2, E e3) ;\nstatic <K,V> Map<K,V> of (K k1, V v1, K k2, V v2, K k3, V v3) ;\nstatic <K,V> Map<K,V> ofEntries (Map.Entry<? extends K,? extends V>... entries) \n
\n\nList 和 Set 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口如果超过 10 个参数, 可以使用 ofEntries(...) 方法。 \n \n新方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.AbstractMap;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n\n public static void main (String []args) {\n Set<String> set = Set.of(\"A\" , \"B\" , \"C\" ); \n System.out.println(set);\n List<String> list = List.of(\"A\" , \"B\" , \"C\" );\n System.out.println(list);\n Map<String, String> map = Map.of(\"A\" ,\"Apple\" ,\"B\" ,\"Boy\" ,\"C\" ,\"Cat\" );\n System.out.println(map);\n \n Map<String, String> map1 = Map.ofEntries (\n new AbstractMap.SimpleEntry<>(\"A\" ,\"Apple\" ),\n new AbstractMap.SimpleEntry<>(\"B\" ,\"Boy\" ),\n new AbstractMap.SimpleEntry<>(\"C\" ,\"Cat\" ));\n System.out.println(map1);\n }\n}\n
\n输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n{A=Apple, B=Boy, C=Cat}\n
\n私有接口方法 \n在 Java 8之前,接口可以有常量变量和抽象方法。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\npublic interface Tests {\n \n String str='hello wrold' ;\n void show (T str) ;\n \n default void def () {\n System.out.print(\"default method\" );\n }\n static void sta () {\n System.out.print(\"static method\" );\n }\n \n private void pri () {\n System.out.print(\"private method\" );\n }\n private static void pri_sta () {\n System.out.print(\"private static method\" );\n }\n}\n
\n改进的进程 API \n在 Java 9 之前,Process API 仍然缺乏对使用本地进程的基本支持,例如获取进程的 PID 和所有者,进程的开始时间,进程使用了多少 CPU 时间,多少本地进程正在运行等。
\nJava 9 向 Process API 添加了一个名为 ProcessHandle 的接口来增强 java.lang.Process 类。
\nProcessHandle 接口的实例标识一个本地进程,它允许查询进程状态并管理进程。
\nProcessHandle 嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\nProcessHandle 接口中声明的 onExit() 方法可用于在某个进程终止时触发某些操作。
\nimport java.time.ZoneId;\nimport java.util.stream.Stream;\nimport java.util.stream.Collectors;\nimport java.io.IOException;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n ProcessBuilder pb = new ProcessBuilder(\"notepad.exe\" );\n String np = \"Not Present\" ;\n Process p = pb.start();\n ProcessHandle.Info info = p.info();\n System.out.printf(\"Process ID : %s%n\" , p.pid());\n System.out.printf(\"Command name : %s%n\" , info.command().orElse(np));\n System.out.printf(\"Command line : %s%n\" , info.commandLine().orElse(np));\n\n System.out.printf(\"Start time: %s%n\" ,\n info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())\n .toLocalDateTime().toString()).orElse(np));\n\n System.out.printf(\"Arguments : %s%n\" ,\n info.arguments().map(a -> Stream.of(a).collect(\n Collectors.joining(\" \" ))).orElse(np));\n\n System.out.printf(\"User : %s%n\" , info.user().orElse(np));\n }\n}\n
\n以上实例执行输出结果为:
\nProcess ID : 5800\nCommand name : C:\\Windows\\System32\\notepad.exe\nCommand line : Not Present\nStart time: 2017-11-04T21:35:03.626\nArguments : Not Present\nUser: administrator\n
\n改进的 Stream API \nJava 9 改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
\nJava 9 为 Stream 新增了几个方法:dropWhile、takeWhile、ofNullable,为 iterate 方法新增了一个重载方法。
\ntakeWhile 方法 语法 \ndefault Stream<T> takeWhile (Predicate<? super T> predicate) \n
\ntakeWhile() 方法使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 false。如果第一个值不满足断言条件,将返回一个空的 Stream。
\ntakeWhile() 方法在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素;在无序的 Stream 中,takeWhile 返回从开头开始的符合 Predicate 要求的元素的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).takeWhile(s->!s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 takeWhile 方法在碰到空字符串时停止循环输出,执行输出结果为:
\nabc\n
\ndropWhile 方法 语法: \ndefault Stream<T> dropWhile (Predicate<? super T> predicate) \n
\ndropWhile 方法和 takeWhile 作用相反的,使用一个断言作为参数,直到断言语句第一次返回 true 才返回给定 Stream 的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).dropWhile(s-> !s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 dropWhile 方法在碰到空字符串时开始循环输出,执行输出结果为:
\nef\n
\niterate 方法 语法: \nstatic <T> Stream<T> iterate (T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) \n
\n方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的 hasNext 的 predicate 返回 false 时,迭代停止。
\njava.util.stream.IntStream;\n\npublic class Tester {\n public static void main (String[] args) {\n IntStream.iterate(3 , x -> x < 10 , x -> x+ 3 ).forEach(System.out::println);\n }\n}\n
\n执行输出结果为:
\n3\n6\n9\n
\nofNullable 方法 语法: \nstatic <T> Stream<T> ofNullable (T t) \n
\nofNullable 方法可以预防 NullPointerExceptions 异常, 可以通过检查流来避免 null 值。
\n如果指定元素为非 null,则获取一个元素并生成单个元素流,元素为 null 则返回一个空流。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n long count = Stream.ofNullable(100 ).count();\n System.out.println(count);\n \n count = Stream.ofNullable(null ).count();\n System.out.println(count);\n }\n}\n
\n执行输出结果为:
\n1\n0\n
\n改进的 try-with-resources \ntry-with-resources 是 JDK 7 中一个新的异常处理机制,它能够很容易地关闭在 try-catch 语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources 语句确保了每个资源在语句结束时关闭。所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。
\ntry-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (BufferedReader br1 = br) {\n return br1.readLine();\n }\n }\n}\n
\n输出结果为:
\ntest\n
\n以上实例中我们需要在 try 语句块中声明资源 br1,然后才能使用它。\n在 Java 9 中,我们不需要声明资源 br1 就可以使用它,并得到相同的结果。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (br) {\n return br.readLine();\n }\n }\n}\n
\n执行输出结果为:
\ntest\n
\n在处理必须关闭的资源时,使用try-with-resources语句替代try-finally语句。 生成的代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources语句在编写必须关闭资源的代码时会更容易,也不会出错,而使用try-finally语句实际上是不可能的。
\n改进的 @Deprecated 注解 \n注解 @Deprecated 可以标记 Java API 状态,可以是以下几种:
\n使用它存在风险,可能导致错误\n可能在未来版本中不兼容\n可能在未来版本中删除\n一个更好和更高效的方案已经取代它。\nJava 9 中注解增加了两个新元素:since 和 forRemoval。
\n\nsince: 元素指定已注解的API元素已被弃用的版本。 \nforRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API。 \n \n钻石操作符的升级 \n钻石操作符是在 java 7 中引入的,可以让代码更易读,但它不能用于匿名的内部类。
\n在 java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性。
\n\nMap<String,String> map6=new HashMap<String,String>();\n\nMap<String,String> map6=new HashMap<>();\n\nMap<String,String> map6=new HashMap<>(){};\n
\n改进的 Optional 类 \nOptional 类在 Java 8 中引入,Optional 类的引入很好的解决空指针异常。。在 java 9 中, 添加了三个方法来改进它的功能:
\n\nstream() \nifPresentOrElse() \nor() \n \nstream() 方法 语法: \npublic Stream<T> stream () \n
\nstream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream(Stream.empty())。
\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class Tester {\npublic static void main (String[] args) {\n List<Optional<String>> list = Arrays.asList (\n Optional.empty(), \n Optional.of(\"A\" ), \n Optional.empty(), \n Optional.of(\"B\" ));\n\n \n \n \n List<String> filteredList = list.stream()\n .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())\n .collect(Collectors.toList());\n\n \n \n List<String> filteredListJava9 = list.stream()\n .flatMap(Optional::stream)\n .collect(Collectors.toList());\n\n System.out.println(filteredList);\n System.out.println(filteredListJava9);\n } \n}\n
\n执行输出结果为:
\n[A, B]\n[A, B]\n
\nifPresentOrElse() 方法 语法: \npublic void ifPresentOrElse (Consumer<? super T> action, Runnable emptyAction) \n
\nifPresentOrElse 方法的改进就是有了 else,接受两个参数 Consumer 和 Runnable。
\nifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()。
\nimport java.util.Optional;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<Integer> optional = Optional.of(1 );\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n\n optional = Optional.empty();\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n } \n}\n
\n执行输出结果为:
\nValue: 1\nNot Present.\n
\nor() 方法 语法: \npublic Optional<T> or (Supplier<? extends Optional<? extends T>> supplier) \n
\n如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<String> optional1 = Optional.of(\"Mahesh\" );\n Supplier<Optional<String>> supplierString = () -> Optional.of(\"Not Present\" );\n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x));\n optional1 = Optional.empty(); \n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x)); \n } \n}\n
\n执行输出结果为:
\nValue: Mahesh\nValue: Not Present\n
\n多分辨率图像 API \nJava 9 定义多分辨率图像 API,开发者可以很容易的操作和展示不同分辨率的图像了。
\n以下是多分辨率图像的主要操作方法:
\n\n\nImage getResolutionVariant(double destImageWidth, double destImageHeight) − 获取特定分辨率的图像变体-表示一张已知分辨率单位为DPI的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。。
\n \n\nList getResolutionVariants() − 返回可读的分辨率的图像变体列表。
\n \n \n改进的 CompletableFuture API \nJava 8 引入了 CompletableFuture 类,可能是 java.util.concurrent.Future 明确的完成版(设置了它的值和状态),也可能被用作java.util.concurrent.CompleteStage 。支持 future 完成时触发一些依赖的函数和动作。Java 9 引入了一些CompletableFuture 的改进:
\nJava 9 对 CompletableFuture 做了改进:
\n\n支持 delays 和 timeouts \n提升了对子类化的支持 \n新的工厂方法 \n \n支持 delays 和 timeouts \npublic CompletableFuture<T> completeOnTimeout (T value, long timeout, TimeUnit unit) \n
\n在 timeout(单位在 java.util.concurrent.Timeunits units 中,比如 MILLISECONDS )前以给定的 value 完成这个 CompletableFutrue。返回这个 CompletableFutrue。
\npublic CompletableFuture<T> orTimeout (long timeout, TimeUnit unit) \n
\n如果没有在给定的 timeout 内完成,就以 java.util.concurrent.TimeoutException 完成这个 CompletableFutrue,并返回这个 CompletableFutrue
\n增强了对子类化的支持 \n做了许多改进使得 CompletableFuture 可以被更简单的继承。比如,你也许想重写新的 public Executor defaultExecutor() 方法来代替默认的 executor。
\n另一个新的使子类化更容易的方法是:
\npublic <U> CompletableFuture<U> newIncompleteFuture () \n
\n新的工厂方法 \nJava 8引入了 <U> CompletableFuture<U> completedFuture(U value) 工厂方法来返回一个已经以给定 value 完成了的 CompletableFuture。Java 9以 一个新的 <U> CompletableFuture <U> failedFuture(Throwable ex) 来补充了这个方法,可以返回一个以给定异常完成的 CompletableFuture。\n\n除此以外,Java 9 引入了下面这对 stage-oriented 工厂方法,返回完成的或异常完成的 completion stages:\n\n* <U> CompletionStage<U> completedStage(U value): 返回一个新的以指定 value 完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n* <U> CompletionStage<U> failedStage(Throwable ex): 返回一个新的以指定异常完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n
\n",
+ "__html": "Java 新特性总结 \n总结的这些新特性,都是自己觉得在开发中实际用得上的。\n简单概括下就是:
\n\nJAVA1.3:普通的原始的JAVA,基本语法相信大家都见过了 \nJAVA1.4:assert关键字 \nJAVA5:枚举类型、泛型、自动拆装箱 \nJAVA6: @Override注解 \nJAVA7: <>符号、ARM支持、支持多catch \nJAVA8:Lamda表达式,类型注解等 \nJAVA9: 模块化、接口中的私有方法等 \nJAVA10: 局部变量推断、整个JDK代码仓库、统一的垃圾回收接口、并行垃圾回收器G1、线程局部管控 \n \nJava5 新特性总结 \n泛型 Generics \n引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。Parameterized Type作为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。
\n泛型可以带来如下的好处总结如下:
\n\n类型安全:抛弃List、Map,使用List、Map给它们添加元素或者使用Iterator遍历时,编译期就可以给你检查出类型错误 \n方法参数和返回值加上了Type: 抛弃List、Map,使用List、Map \n不需要类型转换:List list=new ArrayList(); \n类型通配符“?”: 假设一个打印List中元素的方法printList,我们希望任何类型T的List都可以被打印 \n \n枚举类型 \n引入了枚举类型
\n自动装箱拆箱(自动类型包装和解包)autoboxing & unboxing \n简单的说是类型自动转换。自动装包:基本类型自动转为包装类(int ——Integer)自动拆包:包装类自动转为基本类型(Integer——int)
\n可变参数varargs(varargs number of arguments) \n参数类型相同时,把重载函数合并到一起了。如:
\npublic void test (object... objs) {\n for (Object obj:objs){\n System.out.println(obj);\n }\n}\n
\nAnnotations(重要) 它是java中的metadata(注释) \n注解在JAVA5中就引入了。这是非常重要的特性。现在注解的应用已经随处可见。不过JAVA5的注解还不成熟,没法自定义注解。
\n新的迭代语句 \nfor (int n:numbers){\n\n}\n
\n静态导入(import static ) \n导入静态对象,可以省略些代码。不过这个也不常用。
\nimport static java.lang.System.out;\npublic class HelloWorld {\n public static void main (String[] args) {\n out.print(\"Hello World!\" );\n }\n}\n
\n新的格式化方法java.util.Formatter) \nformatter.format(\"Remaining account balance: $%.2f\" , balance);\n
\n新的线程模型和并发库Thread Framework(重要) \n最主要的就是引入了java.util.concurrent包,这个都是需要重点掌握的。
\nHashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore
\nJava6 新特性总结 \nWeb Services \n优先支持编写 XML web service 客户端程序。你可以用过简单的annotaion将你的API发布成.NET交互的web services. Mustang 添加了新的解析和 XML 在 Java object-mapping APIs中, 之前只在Java EE平台实现或者Java Web Services Pack中提供.
\nScripting \n现在你可以在Java源代码中混入JavaScript了,这对开发原型很有有用,你也可以插入自己的脚本引擎。
\nJDBC4.0 \nJAVA6将联合绑定 Java DB (Apache Derby). JDBC 4.0 增加了许多特性例如支持XML作为SQL数据类型,更好的集成Binary Large OBjects (BLOBs) 和 Character Large OBjects (CLOBs) .
\nUI优化 \n\nGUI 开发者可以有更多的技巧来使用 SwingWorker utility ,以帮助GUI应用中的多线程。, JTable 分类和过滤,以及添加splash闪屏。 \nSwing拥有更好的 look-and-feel , LCD 文本呈现, 整体GUI性能的提升。Java应用程序可以和本地平台更好的集成,例如访问平台的系统托盘和开始菜单。Mustang将Java插件技术和Java Web Start引擎统一了起来。 \n \n监控管理增强 \n添加更多的诊断信息,绑定了不是很知名的 memory-heap 分析工具Jhat 来查看内核导出。
\n编译API \ncompiler API提供编程访问javac,可以实现进程内编译,动态产生Java代码
\n自定义注解 \nJava tool和framework 提供商可以定义自己的 annotations ,并且内核支持自定义annotation的插件和执行处理器
\n安全性 \nXML-数字签名(XML-DSIG) APIs 用于创建和操纵数字签名); 新的方法来访问本地平台的安全服务,例如本地Microsoft Windows for secure authentication and communicationnative 的Public Key Infrastructure (PKI) 和 cryptographic services, Java Generic Security Services (Java GSS) 和 Kerberos services for authentication, 以及访问 LDAP servers 来认证用户.
\nJava7 新特性总结 \nswitch中使用String \njava7以前在switch中只能使用number或enum,现在可以使用string了。
\n示例:
\nString s = \"a\" ;\nswitch (s) {\n case \"a\" :\n System.out.println(\"is a\" );\n break ;\n case \"b\" :\n System.out.println(\"is b\" );\n break ;\n default :\n System.out.println(\"is c\" );\n break ;\n}\n
\n异常处理 \n\nThrowable类增加addSuppressed方法和getSuppressed方法,支持原始异常中加入被抑制的异常。 \n异常抑制:在try和finally中同时抛出异常时,finally中抛出的异常会在异常栈中向上传递,而try中产生的原始异常会消失。 \n在Java7之前的版本,可以将原始异常保存,在finally中产生异常时抛出原始异常: \n \n\npublic void read (String filename) throws BaseException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException == null ) { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw new BaseException(readException); \n } \n } \n}\n\n\npublic void read (String filename) throws IOException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException != null ) { \n readException.addSuppressed(ex); \n } \n else { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw readException; \n } \n } \n} \n
\ntry-with-resources \njava7以前对某些资源的操作是需要手动关闭,如InputStream,Writes,Sockets,Sql等,需要在finally中进行关闭资源的操作,现在不需要使用finally来保证打开的流被正确关闭,现在是自动完成的,会自动释放资源,确保每一个资源在处理完成后都会关闭,就不需要我们代码去close();
\n\n在采用try-with-resources方式后,不需要再次声明流的关闭。 \n可以使用try-with-resources的资源有:任何实现了java.lang.AutoCloseable接口和java.io.Closeable接口的对象。为了支持这个行为,所有可关闭的类将被修改为可以实现一个Closable(可关闭的)接口。 \n \npublic interface Closeable extends AutoCloseable {}\npublic abstract class Reader implements Readable , Closeable {}\n
\n如果在try语句中写入了没有实现该接口的类,会提示:
\n\nThe resource type File does not implement java.lang.AutoCloseable
\n \n示例:
\n\nOutputStream fos = null ;\ntry {\n fos = new FileOutputStream(\"D:/file\" );\n} finally {\n fos.close();\n}\n\ntry (OutputStream fos = new FileOutputStream(\"D:/file\" );){\n \n}\n\npublic void copyFile (String fromPath, String toPath) throws IOException { \ntry ( InputStream input = new FileInputStream(fromPath); \n OutputStream output = new FileOutputStream(toPath) ) { \n byte [] buffer = new byte [8192 ]; \n int len = -1 ; \n while ( (len=input.read(buffer))!=-1 ) { \n output.write(buffer, 0 , len); \n } \n }\n} \n
\n捕获多个异常 \njava7以前在一个方法抛出多个异常时,只能一个个的catch,这样代码会有多个catch,显得很不友好,现在只需一个catch语句,多个异常类型用"|"隔开。\n示例:
\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException e) {\n e.printStackTrace();\n} catch (IllegalAccessException e) {\n e.printStackTrace();\n}\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException | IllegalAccessException e) {\n e.printStackTrace();\n}\n
\n泛型实例化类型自动推断 \n运用泛型实例化类型自动推断,对通用实例创建(diamond)的type引用进行了改进\n示例:
\n\nList<String> list = new ArrayList<String>();\n\nList<String> list = new ArrayList<>();\n
\n增加二进制表示 \nJava7前支持十进制(123)、八进制(0123)、十六进制(0X12AB)
\nJava7增加二进制表示(0B11110001、0b11110001)\n示例:
\nint binary = 0b0001_1001 ;\nSystem.out.println(\"binary is :\" +binary);\n binary is :25 \n
\n数字中可添加分隔符 \nJava7中支持在数字中间增加'_'作为分隔符,分隔长int以及long(也支持double,float),显示更直观,如(12_123_456)。
\n下划线只能在数字中间,编译时编译器自动删除数字中的下划线。
\n示例:
\nint intOne = 1_000_000 ;\nlong longOne = 1_000_000 ;\ndouble doubleOne = 1_000_000 ;\nfloat floatOne = 1_000_000 ;\n
\n变长参数方法的优化 \n参数类型相同时,把重载函数合并到一起了\n使用可变参数时,提升编译器的警告和错误信息
\npublic int sum (int ... args) { \n int result = 0 ; \n for (int value : args) { \n result += value; \n } \n return result; \n} \n
\n集合类的语法支持 \n\nList<String> list = new ArrayList<String>();\n list.add(\"item\" );\n String item = list.get(0 );\n\n Set<String> set = new HashSet<String>();\n set.add(\"item\" );\n Map<String, Integer> map = new HashMap<String, Integer>();\n map.put(\"key\" , 1 );\n int value = map.get(\"key\" );\n\nList<String> list = [\"item\" ];\n String item = list[0 ];\n\n Set<String> set = {\"item\" };\n\n Map<String, Integer> map = {\"key\" : 1 };\n int value = map[\"key\" ]; \n
\n自动资源管理 \nJava中某些资源是需要手动关闭的,如InputStream,Writes,Sockets,Sql classes等。这个新的语言特性允许try语句本身申请更多的资源,这些资源作用于try代码块,并自动关闭。
\n\nBufferedReader br = new BufferedReader(new FileReader(path));\ntry {\nreturn br.readLine();\n } finally {\n br.close();\n}\n\ntry (BufferedReader br = new BufferedReader(new FileReader(path)) {\n return br.readLine();\n}\n
\n新增一些取环境信息的工具方法 \nFile System.getJavaIoTempDir() \nFile System.getJavaHomeDir() \nFile System.getUserHomeDir() \nFile System.getUserDir() \n
\nJava8 新特性总结 \nJava8 新增了非常多的特性,我们主要讨论以下几个:
\n\nLambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 \n方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 \n默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。 \n新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。 \nStream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。 \nDate Time API − 加强对日期与时间的处理。 \nOptional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。 \nNashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。 \n \nLambda表达式和函数式接口 \nLambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
\nLambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> System.out.println( e ) );\n
\n在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( ( String e ) -> System.out.println( e ) );\n
\n如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> {\n System.out.print( e );\n System.out.print( e );\n} );\n
\nLambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:
\nString separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) );\n\nfinal String separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) ); \n
\nLambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );\n\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> {\n int result = e1.compareTo( e2 );\n return result;\n} );\n
\nLambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
\n@FunctionalInterface \npublic interface Functional {\n void method () ;\n}\n
\n不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
\n@FunctionalInterface \npublic interface FunctionalDefaultMethods {\n void method () ;\n\n default void defaultMethod () {\n System.out.print(\"defaultMethod\" );\n }\n static void staticMethod () {\n System.out.print(\"staticMethod\" );\n }\n}\n
\nLambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档 。
\n接口的默认方法和静态方法 \nJava 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
\n默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
\nprivate interface Defaulable {\n \n \n default String notRequired () { \n return \"Default implementation\" ; \n } \n}\n\nprivate static class DefaultableImpl implements Defaulable {\n}\n\nprivate static class OverridableImpl implements Defaulable {\n @Override \n public String notRequired () {\n return \"Overridden implementation\" ;\n }\n}\n
\nDefaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
\nJava 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
\nprivate interface DefaulableFactory {\n \n static Defaulable create ( Supplier< Defaulable > supplier ) {\n return supplier.get();\n }\n}\n
\n下面的代码片段整合了默认方法和静态方法的使用场景:
\npublic static void main ( String[] args ) {\n Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );\n System.out.println( defaulable.notRequired() );\n\n defaulable = DefaulableFactory.create( OverridableImpl::new );\n System.out.println( defaulable.notRequired() );\n}\n
\n这段代码的输出结果如下:
\n\nDefault implementation\nOverridden implementation
\n \n由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
\n尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档 。
\n方法引用 \n方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
\n西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。
\npublic static class Car {\n public static Car create ( final Supplier< Car > supplier ) {\n return supplier.get();\n }\n\n public static void collide ( final Car car ) {\n System.out.println( \"Collided \" + car.toString() );\n }\n\n public void follow ( final Car another ) {\n System.out.println( \"Following the \" + another.toString() );\n }\n\n public void repair () { \n System.out.println( \"Repaired \" + this .toString() );\n }\n}\n
\n第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。
\nfinal Car car = Car.create( Car::new );\nfinal List< Car > cars = Arrays.asList( car );\n
\n第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
\ncars.forEach( Car::collide );\n
\n第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
\ncars.forEach( Car::repair );\n
\n第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
\nfinal Car police = Car.create( Car::new );\ncars.forEach( police::follow );\n
\n运行上述例子,可以在控制台看到如下输出(Car实例可能不同):
\n\nCollided com.javacodegeeks.java8.method.references.MethodReferencesC a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nRepaired com.javacodegeeks.java8.method.references.MethodReferences C a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nFollowing the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n重复注解 \n自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
\n在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:
\npackage com.javacodegeeks.java8.repeatable.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Repeatable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\npublic class RepeatingAnnotations {\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n public @interface Filters {\n Filter[] value();\n }\n\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n @Repeatable ( Filters.class )\n public @interface Filter {\n String value () ;\n };\n\n @Filter ( \"filter1\" )\n @Filter ( \"filter2\" )\n public interface Filterable {\n }\n\n public static void main (String[] args) {\n for ( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {\n System.out.println( filter.value() );\n }\n }\n}\n
\n正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。
\n另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:
\n\nfilter1\nfilter2
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n更好的类型推断 \nJava 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。例子代码如下:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class Value < T > {\n public static < T > T defaultValue () {\n return null ;\n }\n\n public T getOrDefault ( T value, T defaultValue ) {\n return ( value != null ) ? value : defaultValue;\n }\n}\n
\n下列代码是Value类型的应用:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class TypeInference {\n public static void main (String[] args) {\n final Value< String > value = new Value<>();\n value.getOrDefault( \"22\" , Value.defaultValue() );\n }\n}\n
\n参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value.defaultValue()。
\n拓宽注解的应用场景 \nJava 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:
\npackage com.javacodegeeks.java8.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.ArrayList;\nimport java.util.Collection;\n\npublic class Annotations {\n @Retention ( RetentionPolicy.RUNTIME )\n @Target ( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )\n public @interface NonEmpty {\n }\n\n public static class Holder < @NonEmpty T > extends @NonEmpty Object {\n public void method () throws @NonEmpty Exception {\n }\n }\n\n @SuppressWarnings ( \"unused\" )\n public static void main (String[] args) {\n final Holder< String > holder = new @NonEmpty Holder< String >();\n @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();\n }\n}\n
\nElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。
\nJava编译器的新特性 \n为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer library。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。
\npackage com.javacodegeeks.java8.parameter.names;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\n\npublic class ParameterNames {\n public static void main (String[] args) throws Exception {\n Method method = ParameterNames.class.getMethod( \"main\" , String[].class );\n for ( final Parameter parameter: method.getParameters() ) {\n System.out.println( \"Parameter: \" + parameter.getName() );\n }\n }\n}\n
\n在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:
\n\nParameter: arg0\n如果带-parameters参数,则会输出如下结果(正确的结果):\nParameter: args\n如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:
\n \n<plugin> \n <groupId>org.apache.maven.plugins</groupId> \n <artifactId>maven-compiler-plugin</artifactId> \n <version>3.1</version> \n <configuration> \n <compilerArgument>-parameters</compilerArgument> \n <source>1.8</source> \n <target>1.8</target> \n </configuration> \n</plugin> \n
\nOptional \nJava应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。
\nOptional仅仅是一个容器:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。
\n接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:
\nOptional< String > fullName = Optional.ofNullable( null );\nSystem.out.println( \"Full Name is set? \" + fullName.isPresent() );\nSystem.out.println( \"Full Name: \" + fullName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( fullName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\n
\n如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。
\n上述代码的输出结果如下:
\n\nFull Name is set? false\nFull Name: [none]\nHey Stranger!
\n \n再看下另一个简单的例子:
\nOptional< String > firstName = Optional.of( \"Tom\" );\nSystem.out.println( \"First Name is set? \" + firstName.isPresent() );\nSystem.out.println( \"First Name: \" + firstName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( firstName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\nSystem.out.println();\n
\n如果想了解和学习更详细的内容,可以参考官方文档 。
\nStreams \n新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
\nSteam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:
\npublic class Streams {\n private enum Status {\n OPEN, CLOSED\n };\n\n private static final class Task {\n private final Status status;\n private final Integer points;\n\n Task( final Status status, final Integer points ) {\n this .status = status;\n this .points = points;\n }\n\n public Integer getPoints () {\n return points;\n }\n\n public Status getStatus () {\n return status;\n }\n\n @Override \n public String toString () {\n return String.format( \"[%s, %d]\" , status, points );\n }\n }\n}\n
\nTask类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:
\nfinal Collection< Task > tasks = Arrays.asList(\n new Task( Status.OPEN, 5 ),\n new Task( Status.OPEN, 13 ),\n new Task( Status.CLOSED, 8 ) \n);\n
\n首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。
\n\nfinal long totalPointsOfOpenTasks = tasks\n .stream()\n .filter( task -> task.getStatus() == Status.OPEN )\n .mapToInt( Task::getPoints )\n .sum();\n\nSystem.out.println( \"Total points: \" + totalPointsOfOpenTasks );\n
\n运行这个方法的控制台输出是:
\n\nTotal points: 18\n这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。
\n \n在学习下一个例子之前,还需要记住一些steams(点此更多细节 )的知识点。Steam之上的操作可分为中间操作和晚期操作。
\n中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。
\n晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。
\nsteam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:
\n\nfinal double totalPoints = tasks\n .stream()\n .parallel()\n .map( task -> task.getPoints() ) \n .reduce( 0 , Integer::sum );\n\nSystem.out.println( \"Total points (all tasks): \" + totalPoints );\n
\n这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:
\n\nTotal points(all tasks): 26.0
\n \n对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:
\n\nfinal Map< Status, List< Task > > map = tasks\n .stream()\n .collect( Collectors.groupingBy( Task::getStatus ) );\nSystem.out.println( map );\n
\n控制台的输出如下:
\n\n{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
\n \n最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:
\n\nfinal Collection< String > result = tasks\n .stream() \n .mapToInt( Task::getPoints ) \n .asLongStream() \n .mapToDouble( points -> points / totalPoints ) \n .boxed() \n .mapToLong( weigth -> ( long )( weigth * 100 ) ) \n .mapToObj( percentage -> percentage + \"%\" ) \n .collect( Collectors.toList() ); \n\nSystem.out.println( result );\n
\n控制台输出结果如下:
\n\n[19%, 50%, 30%]
\n \n最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:
\nfinal Path path = new File( filename ).toPath();\ntry ( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {\n lines.onClose( () -> System.out.println(\"Done!\" ) ).forEach( System.out::println );\n}\n
\nStream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。
\n点此更多细节
\nDate/Time API(JSR 310) \nJava 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。
\n因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。
\n我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。
\n\nfinal Clock clock = Clock.systemUTC();\nSystem.out.println( clock.instant() );\nSystem.out.println( clock.millis() );\n
\n这个例子的输出结果是:
\n\n2014-04-12T15:19:29.282Z\n1397315969360
\n \n第二,关注下LocalDate和LocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。
\n\nfinal LocalDate date = LocalDate.now();\nfinal LocalDate dateFromClock = LocalDate.now( clock );\n\nSystem.out.println( date );\nSystem.out.println( dateFromClock );\n\n\nfinal LocalTime time = LocalTime.now();\nfinal LocalTime timeFromClock = LocalTime.now( clock );\n\nSystem.out.println( time );\nSystem.out.println( timeFromClock );\n
\n上述例子的输出结果如下:
\n\n2014-04-12\n2014-04-12\n11:25:54.568\n15:25:54.568
\n \nLocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子:
\n\nfinal LocalDateTime datetime = LocalDateTime.now();\nfinal LocalDateTime datetimeFromClock = LocalDateTime.now( clock );\n\nSystem.out.println( datetime );\nSystem.out.println( datetimeFromClock );\n
\n上述这个例子的输出结果如下:
\n\n2014-04-12T11:37:52.309\n2014-04-12T15:37:52.309
\n \n如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:
\n\nfinal ZonedDateTime zonedDatetime = ZonedDateTime.now();\nfinal ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );\nfinal ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( \"America/Los_Angeles\" ) );\n\nSystem.out.println( zonedDatetime );\nSystem.out.println( zonedDatetimeFromClock );\nSystem.out.println( zonedDatetimeFromZone );\n
\n这个例子的输出结果是:
\n\n2014-04-12T11:47:01.017-04:00[America/New_York]\n2014-04-12T15:47:01.017Z\n2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
\n \n最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:
\n\nfinal LocalDateTime from = LocalDateTime.of( 2014 , Month.APRIL, 16 , 0 , 0 , 0 );\nfinal LocalDateTime to = LocalDateTime.of( 2015 , Month.APRIL, 16 , 23 , 59 , 59 );\n\nfinal Duration duration = Duration.between( from, to );\nSystem.out.println( \"Duration in days: \" + duration.toDays() );\nSystem.out.println( \"Duration in hours: \" + duration.toHours() );\n
\n这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:
\n\nDuration in days: 365\nDuration in hours: 8783
\n \n对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果想了解和学习更详细的内容,可以参考官方文档 。
\nNashorn JavaScript引擎 \nJava 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下:
\nScriptEngineManager manager = new ScriptEngineManager();\nScriptEngine engine = manager.getEngineByName( \"JavaScript\" );\n\nSystem.out.println( engine.getClass().getName() );\nSystem.out.println( \"Result:\" + engine.eval( \"function f() { return 1; }; f() + 1;\" ) );\n
\n这个代码的输出结果如下:
\n\njdk.nashorn.api.scripting.NashornScriptEngine\nResult: 2
\n \nBase64 \n对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
\npackage com.javacodegeeks.java8.base64;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\n\npublic class Base64s {\n public static void main (String[] args) {\n final String text = \"Base64 finally in Java 8!\" ;\n\n final String encoded = Base64\n .getEncoder()\n .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );\n System.out.println( encoded );\n\n final String decoded = new String( \n Base64.getDecoder().decode( encoded ),\n StandardCharsets.UTF_8 );\n System.out.println( decoded );\n }\n}\n
\n这个例子的输出结果如下:
\n\nQmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==\nBase64 finally in Java 8!
\n \n新的Base64API也支持URL和MINE的编码解码。\n(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
\n并行数组 \nJava8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:
\npackage com.javacodegeeks.java8.parallel.arrays;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class ParallelArrays {\n public static void main ( String[] args ) {\n long [] arrayOfLong = new long [ 20000 ];\n\n Arrays.parallelSetAll( arrayOfLong,\n index -> ThreadLocalRandom.current().nextInt( 1000000 ) );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n\n Arrays.parallelSort( arrayOfLong );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n }\n}\n
\n上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:
\n\nUnsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378\nSorted: 39 220 263 268 325 607 655 678 723 793
\n \n并发性 \n基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作
\nJava 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。
\n在java.util.concurrent.atomic包中也新增了不少工具类,列举如下:
\n\nDoubleAccumulator \nDoubleAdder \nLongAccumulator \nLongAdder \n \n新的Java工具 \nNashorn引擎:jjs \njjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:
\nfunction f ( ) {\n return 1 ;\n};\n\nprint( f() + 1 );\n
\n可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:
\n\n2
\n \n如果需要了解细节,可以参考官方文档 。
\n类依赖分析器:jdeps \njdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。
\n我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
\njdeps org.springframework.core-3.0 .5.RELEASE.jar\n
\n这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".
\norg.springframework.core-3.0.5.RELEASE.jar -> C:\\Program Files\\Java\\jdk1.8.0\\jre\\lib\\rt.jar\n org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.io\n -> java.lang\n -> java.lang.annotation\n -> java.lang.ref\n -> java.lang.reflect\n -> java.util\n -> java.util.concurrent\n -> org.apache.commons.logging not found\n -> org.springframework.asm not found\n -> org.springframework.asm.commons not found\n org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.lang\n -> java.lang.annotation\n -> java.lang.reflect\n -> java.util\n
\n如果需要了解细节,可以参考官方文档 。
\nJVM的新特性 \n使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
\nJava 9 新特性总结 \n\n模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。 \nREPL (JShell):交互式编程环境。 \nHTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。 \n改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。 \n多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。 \n集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。 \n私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。 \n进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。 \n改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。 \n改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。 \n改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。 \n改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。 \n改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。 \n多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。 \n改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。 \n轻量级的 JSON API:内置了一个轻量级的JSON API \n响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。 \n \nJava9 新特性之---目录结构 \n包含jdk8及以前的jdk版本,所有目录结构以及目录含义如图:\n \n \njdk9之后,目录结构发生变化如图:\n \n这个新特性只要了解下就可以了,这个目录结构是方便为了接下来新特性做保证
\n模块化 \n一个大型的项目,比如淘宝商城等,都会包含多个模块,比如订单模块,前台模块,后台管理模块,广告位模块,会员模块.....等等,各个模块之间会相互调用,不过这种情况下会很少,只针对特殊情况,如果一个项目有30个模块系统进行开发,但是只要某个单独模块运行时,都会带动所有的模块,这样对于jvm来说在内存和性能上会很低,所以,java9提供了这一个特性,某一个模块运行的时候,jvm只会启动和它有依赖的模块,并不会加载所有的模块到内存中,这样性能大大的提高了。写法上如下:
\n \n一个项目中的两个模块,模块之间通过module-info.java来关联,在IDEA编辑器右键创建package-info.java\n \n在这个两个模块java9Demo和java9Test中,java9demo编写一个实体类Person,在java9Test调用这样一个过程
\n这个是java9Demo 将 java9Test 模块需要的文件导出 exports 把它所在的包导出
\nmodule java9Demo{\n requires com.mdxl.layer_cj.entity;\n}\n
\n然后在java9Test模块中创建一个package-info.java,引入java9Demo模块导出包名
\nmodule java9Test{\n requires java9Demo;\n}\n
\n这样就可以直接在java9Test中引入Person实体类了,这只是一个简单的例子。exports 控制着那些包可以被模块访问,所以不被导出的包不能被其他模块访问
\nJShell工具 \n怎么理解,怎么用呢?这个只是针对于java9来说,相当于cmd工具,你可以和cmd一样,直接写方法等等,不过我认为只是适用于初学者做一些最简单的运算和写一些方法:\n在cmd中打开这个工具:
\n$ jshell\n| Welcome to JShell -- Version 9-ea\n| For an introduction type: /help intro\njshell>\n
\n查看 JShell 命令
\n输入 /help 可以查看 JShell相关的命令:
\njshell> /help\n| Type a Java language expression, statement, or declaration.\n| Or type one of the following commands:\n| /list [<name or id>|-all|-start]\n| list the source you have typed\n| /edit <name or id>\n| edit a source entry referenced by name or id\n| /drop <name or id>\n| delete a source entry referenced by name or id\n| /save [-all|-history|-start] <file>\n| Save snippet source to a file.\n| /open <file>\n| open a file as source input\n| /vars [<name or id>|-all|-start]\n| list the declared variables and their values\n| /methods [<name or id>|-all|-start]\n| list the declared methods and their signatures\n| /types [<name or id>|-all|-start]\n| list the declared types\n| /imports \n| list the imported items\n
\n执行 JShell 命令
\n/imports 命令用于查看已导入的包:
\njshell> /imports\n| import java.io.*\n| import java.math.*\n| import java.net.*\n| import java.nio.file.*\n| import java.util.*\n| import java.util.concurrent.*\n| import java.util.function.*\n| import java.util.prefs.*\n| import java.util.regex.*\n| import java.util.stream.*\njshell>\n
\n等等,我认为只适用于初学者学习java不用其他编辑工具就可以学习java
\nHTTP 2 客户端 \nJDK9之前提供HttpURLConnection API来实现Http访问功能,但是这个类基本很少使用,一般都会选择Apache的Http Client,此次在Java 9的版本中引入了一个新的package:java.net.http,里面提供了对Http访问很好的支持,不仅支持Http1.1而且还支持HTTP2(什么是HTTP2?请参见HTTP2的时代来了...),以及WebSocket,据说性能特别好。
\n \n注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。
\n改进 Javadoc \njavadoc 工具可以生成 Java 文档, Java 9 的 javadoc 的输出现在符合兼容 HTML5 标准。
\n//java 9 之前\nC:\\JAVA>javadoc -d C:/JAVA Tester.java\n//java 9 之后\nC:\\JAVA> javadoc -d C:/JAVA -html5 Tester.java\n
\n多版本兼容 jar 包 \n多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
\n通过 --release 参数指定编译版本。
\n具体的变化就是 META-INF 目录下 MANIFEST.MF 文件新增了一个属性:
\n\nMulti-Release: true
\n \n然后 META-INF 目录下还新增了一个 versions 目录,如果是要支持 java9,则在 versions 目录下有 9 的目录。
\nmultirelease.jar\n├── META-INF\n│ └── versions\n│ └── 9\n│ └── multirelease\n│ └── Helper.class\n├── multirelease\n ├── Helper.class\n └── Main.class\n
\n集合工厂方法 \nJava 9 List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
\n这些工厂方法可以以更简洁的方式来创建集合。
\n旧方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n public static void main (String []args) {\n Set<String> set = new HashSet<>();\n set.add(\"A\" );\n set.add(\"B\" );\n set.add(\"C\" );\n set = Collections.unmodifiableSet(set);\n System.out.println(set);\n List<String> list = new ArrayList<>();\n\n list.add(\"A\" );\n list.add(\"B\" );\n list.add(\"C\" );\n list = Collections.unmodifiableList(list);\n System.out.println(list);\n Map<String, String> map = new HashMap<>();\n\n map.put(\"A\" ,\"Apple\" );\n map.put(\"B\" ,\"Boy\" );\n map.put(\"C\" ,\"Cat\" );\n map = Collections.unmodifiableMap(map);\n System.out.println(map);\n }\n}\n
\n执行输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n
\n新方法创建集合:
\nJava 9 中,以下方法被添加到 List,Set 和 Map 接口以及它们的重载对象。
\nstatic <E> List<E> of (E e1, E e2, E e3) ;\nstatic <E> Set<E> of (E e1, E e2, E e3) ;\nstatic <K,V> Map<K,V> of (K k1, V v1, K k2, V v2, K k3, V v3) ;\nstatic <K,V> Map<K,V> ofEntries (Map.Entry<? extends K,? extends V>... entries) \n
\n\nList 和 Set 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口如果超过 10 个参数, 可以使用 ofEntries(...) 方法。 \n \n新方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.AbstractMap;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n\n public static void main (String []args) {\n Set<String> set = Set.of(\"A\" , \"B\" , \"C\" ); \n System.out.println(set);\n List<String> list = List.of(\"A\" , \"B\" , \"C\" );\n System.out.println(list);\n Map<String, String> map = Map.of(\"A\" ,\"Apple\" ,\"B\" ,\"Boy\" ,\"C\" ,\"Cat\" );\n System.out.println(map);\n \n Map<String, String> map1 = Map.ofEntries (\n new AbstractMap.SimpleEntry<>(\"A\" ,\"Apple\" ),\n new AbstractMap.SimpleEntry<>(\"B\" ,\"Boy\" ),\n new AbstractMap.SimpleEntry<>(\"C\" ,\"Cat\" ));\n System.out.println(map1);\n }\n}\n
\n输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n{A=Apple, B=Boy, C=Cat}\n
\n私有接口方法 \n在 Java 8之前,接口可以有常量变量和抽象方法。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\npublic interface Tests {\n \n String str='hello wrold' ;\n void show (T str) ;\n \n default void def () {\n System.out.print(\"default method\" );\n }\n static void sta () {\n System.out.print(\"static method\" );\n }\n \n private void pri () {\n System.out.print(\"private method\" );\n }\n private static void pri_sta () {\n System.out.print(\"private static method\" );\n }\n}\n
\n改进的进程 API \n在 Java 9 之前,Process API 仍然缺乏对使用本地进程的基本支持,例如获取进程的 PID 和所有者,进程的开始时间,进程使用了多少 CPU 时间,多少本地进程正在运行等。
\nJava 9 向 Process API 添加了一个名为 ProcessHandle 的接口来增强 java.lang.Process 类。
\nProcessHandle 接口的实例标识一个本地进程,它允许查询进程状态并管理进程。
\nProcessHandle 嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\nProcessHandle 接口中声明的 onExit() 方法可用于在某个进程终止时触发某些操作。
\nimport java.time.ZoneId;\nimport java.util.stream.Stream;\nimport java.util.stream.Collectors;\nimport java.io.IOException;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n ProcessBuilder pb = new ProcessBuilder(\"notepad.exe\" );\n String np = \"Not Present\" ;\n Process p = pb.start();\n ProcessHandle.Info info = p.info();\n System.out.printf(\"Process ID : %s%n\" , p.pid());\n System.out.printf(\"Command name : %s%n\" , info.command().orElse(np));\n System.out.printf(\"Command line : %s%n\" , info.commandLine().orElse(np));\n\n System.out.printf(\"Start time: %s%n\" ,\n info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())\n .toLocalDateTime().toString()).orElse(np));\n\n System.out.printf(\"Arguments : %s%n\" ,\n info.arguments().map(a -> Stream.of(a).collect(\n Collectors.joining(\" \" ))).orElse(np));\n\n System.out.printf(\"User : %s%n\" , info.user().orElse(np));\n }\n}\n
\n以上实例执行输出结果为:
\nProcess ID : 5800\nCommand name : C:\\Windows\\System32\\notepad.exe\nCommand line : Not Present\nStart time: 2017-11-04T21:35:03.626\nArguments : Not Present\nUser: administrator\n
\n改进的 Stream API \nJava 9 改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
\nJava 9 为 Stream 新增了几个方法:dropWhile、takeWhile、ofNullable,为 iterate 方法新增了一个重载方法。
\ntakeWhile 方法 语法 \ndefault Stream<T> takeWhile (Predicate<? super T> predicate) \n
\ntakeWhile() 方法使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 false。如果第一个值不满足断言条件,将返回一个空的 Stream。
\ntakeWhile() 方法在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素;在无序的 Stream 中,takeWhile 返回从开头开始的符合 Predicate 要求的元素的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).takeWhile(s->!s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 takeWhile 方法在碰到空字符串时停止循环输出,执行输出结果为:
\nabc\n
\ndropWhile 方法 语法: \ndefault Stream<T> dropWhile (Predicate<? super T> predicate) \n
\ndropWhile 方法和 takeWhile 作用相反的,使用一个断言作为参数,直到断言语句第一次返回 true 才返回给定 Stream 的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).dropWhile(s-> !s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 dropWhile 方法在碰到空字符串时开始循环输出,执行输出结果为:
\nef\n
\niterate 方法 语法: \nstatic <T> Stream<T> iterate (T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) \n
\n方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的 hasNext 的 predicate 返回 false 时,迭代停止。
\njava.util.stream.IntStream;\n\npublic class Tester {\n public static void main (String[] args) {\n IntStream.iterate(3 , x -> x < 10 , x -> x+ 3 ).forEach(System.out::println);\n }\n}\n
\n执行输出结果为:
\n3\n6\n9\n
\nofNullable 方法 语法: \nstatic <T> Stream<T> ofNullable (T t) \n
\nofNullable 方法可以预防 NullPointerExceptions 异常, 可以通过检查流来避免 null 值。
\n如果指定元素为非 null,则获取一个元素并生成单个元素流,元素为 null 则返回一个空流。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n long count = Stream.ofNullable(100 ).count();\n System.out.println(count);\n \n count = Stream.ofNullable(null ).count();\n System.out.println(count);\n }\n}\n
\n执行输出结果为:
\n1\n0\n
\n改进的 try-with-resources \ntry-with-resources 是 JDK 7 中一个新的异常处理机制,它能够很容易地关闭在 try-catch 语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources 语句确保了每个资源在语句结束时关闭。所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。
\ntry-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (BufferedReader br1 = br) {\n return br1.readLine();\n }\n }\n}\n
\n输出结果为:
\ntest\n
\n以上实例中我们需要在 try 语句块中声明资源 br1,然后才能使用它。\n在 Java 9 中,我们不需要声明资源 br1 就可以使用它,并得到相同的结果。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (br) {\n return br.readLine();\n }\n }\n}\n
\n执行输出结果为:
\ntest\n
\n在处理必须关闭的资源时,使用try-with-resources语句替代try-finally语句。 生成的代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources语句在编写必须关闭资源的代码时会更容易,也不会出错,而使用try-finally语句实际上是不可能的。
\n改进的 @Deprecated 注解 \n注解 @Deprecated 可以标记 Java API 状态,可以是以下几种:
\n使用它存在风险,可能导致错误\n可能在未来版本中不兼容\n可能在未来版本中删除\n一个更好和更高效的方案已经取代它。\nJava 9 中注解增加了两个新元素:since 和 forRemoval。
\n\nsince: 元素指定已注解的API元素已被弃用的版本。 \nforRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API。 \n \n钻石操作符的升级 \n钻石操作符是在 java 7 中引入的,可以让代码更易读,但它不能用于匿名的内部类。
\n在 java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性。
\n\nMap<String,String> map6=new HashMap<String,String>();\n\nMap<String,String> map6=new HashMap<>();\n\nMap<String,String> map6=new HashMap<>(){};\n
\n改进的 Optional 类 \nOptional 类在 Java 8 中引入,Optional 类的引入很好的解决空指针异常。。在 java 9 中, 添加了三个方法来改进它的功能:
\n\nstream() \nifPresentOrElse() \nor() \n \nstream() 方法 语法: \npublic Stream<T> stream () \n
\nstream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream(Stream.empty())。
\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class Tester {\npublic static void main (String[] args) {\n List<Optional<String>> list = Arrays.asList (\n Optional.empty(), \n Optional.of(\"A\" ), \n Optional.empty(), \n Optional.of(\"B\" ));\n\n \n \n \n List<String> filteredList = list.stream()\n .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())\n .collect(Collectors.toList());\n\n \n \n List<String> filteredListJava9 = list.stream()\n .flatMap(Optional::stream)\n .collect(Collectors.toList());\n\n System.out.println(filteredList);\n System.out.println(filteredListJava9);\n } \n}\n
\n执行输出结果为:
\n[A, B]\n[A, B]\n
\nifPresentOrElse() 方法 语法: \npublic void ifPresentOrElse (Consumer<? super T> action, Runnable emptyAction) \n
\nifPresentOrElse 方法的改进就是有了 else,接受两个参数 Consumer 和 Runnable。
\nifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()。
\nimport java.util.Optional;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<Integer> optional = Optional.of(1 );\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n\n optional = Optional.empty();\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n } \n}\n
\n执行输出结果为:
\nValue: 1\nNot Present.\n
\nor() 方法 语法: \npublic Optional<T> or (Supplier<? extends Optional<? extends T>> supplier) \n
\n如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<String> optional1 = Optional.of(\"Mahesh\" );\n Supplier<Optional<String>> supplierString = () -> Optional.of(\"Not Present\" );\n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x));\n optional1 = Optional.empty(); \n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x)); \n } \n}\n
\n执行输出结果为:
\nValue: Mahesh\nValue: Not Present\n
\n多分辨率图像 API \nJava 9 定义多分辨率图像 API,开发者可以很容易的操作和展示不同分辨率的图像了。
\n以下是多分辨率图像的主要操作方法:
\n\n\nImage getResolutionVariant(double destImageWidth, double destImageHeight) − 获取特定分辨率的图像变体-表示一张已知分辨率单位为DPI的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。。
\n \n\nList getResolutionVariants() − 返回可读的分辨率的图像变体列表。
\n \n \n改进的 CompletableFuture API \nJava 8 引入了 CompletableFuture 类,可能是 java.util.concurrent.Future 明确的完成版(设置了它的值和状态),也可能被用作java.util.concurrent.CompleteStage 。支持 future 完成时触发一些依赖的函数和动作。Java 9 引入了一些CompletableFuture 的改进:
\nJava 9 对 CompletableFuture 做了改进:
\n\n支持 delays 和 timeouts \n提升了对子类化的支持 \n新的工厂方法 \n \n支持 delays 和 timeouts \npublic CompletableFuture<T> completeOnTimeout (T value, long timeout, TimeUnit unit) \n
\n在 timeout(单位在 java.util.concurrent.Timeunits units 中,比如 MILLISECONDS )前以给定的 value 完成这个 CompletableFutrue。返回这个 CompletableFutrue。
\npublic CompletableFuture<T> orTimeout (long timeout, TimeUnit unit) \n
\n如果没有在给定的 timeout 内完成,就以 java.util.concurrent.TimeoutException 完成这个 CompletableFutrue,并返回这个 CompletableFutrue
\n增强了对子类化的支持 \n做了许多改进使得 CompletableFuture 可以被更简单的继承。比如,你也许想重写新的 public Executor defaultExecutor() 方法来代替默认的 executor。
\n另一个新的使子类化更容易的方法是:
\npublic <U> CompletableFuture<U> newIncompleteFuture () \n
\n新的工厂方法 \nJava 8引入了 <U> CompletableFuture<U> completedFuture(U value) 工厂方法来返回一个已经以给定 value 完成了的 CompletableFuture。Java 9以 一个新的 <U> CompletableFuture <U> failedFuture(Throwable ex) 来补充了这个方法,可以返回一个以给定异常完成的 CompletableFuture。\n\n除此以外,Java 9 引入了下面这对 stage-oriented 工厂方法,返回完成的或异常完成的 completion stages:\n\n* <U> CompletionStage<U> completedStage(U value): 返回一个新的以指定 value 完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n* <U> CompletionStage<U> failedStage(Throwable ex): 返回一个新的以指定异常完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n
\nJava 10 新特性 \n\n局部变量推断 \n整个JDK代码仓库 \n统一的垃圾回收接口 \n并行垃圾回收器G1 \n线程局部管控 \n \n局部变量推断 \n它向 Java 中引入在其他语言中很常见的 var ,比如 JavaScript 。只要编译器可以推断此种类型,你不再需要专门声明一个局部变量的类型。
\n开发者将能够声明变量而不必指定关联的类型。比如:
\nList <String> list = new ArrayList <String>();\nStream <String> stream = getStream();\n
\n它可以简化为:
\nvar list = new ArrayList();\nvar stream = getStream();\n
\n局部变量类型推断将引入“ var ”关键字的使用,而不是要求明确指定变量的类型,我们俗称“语法糖”。
\n这就消除了我们之前必须执行的 ArrayList 类型定义的重复。
\n其实我们在JDK7,我们需要:
\nList <String> list = new ArrayList <String>();\n
\n但是在JDK8,我们只需要:
\nList <String> list = new ArrayList <>();\n
\n所以这是一个逐步的升级。也是人性化的表现与提升。
\n有趣的是,需要注意 var 不能成为一个关键字,而是一个保留字。这意味着你仍然可以使用 var 作为一个变量,方法或包名,但是现在(尽管我确定你绝不会)你不能再有一个类被调用。
\n局部变量类型推荐仅限于如下使用场景:
\n\n局部变量初始化 \nfor循环内部索引变量 \n传统的for循环声明变量\nJava官方表示,它不能用于以下几个地方: \n方法参数 \n构造函数参数 \n方法返回类型 \n字段 \n捕获表达式(或任何其他类型的变量声明) \n \n注意:
\nJava的var和JavaScript的完全不同,不要这样去类比。Java的var是用于局部类型推断的,而不是JS那样的动态类型,所以下面这个样子是不行的:
\nvar a = 10 ;\na = \"abc\" ; \n
\n其次,这个var只能用于局部变量声明,在其他地方使用都是错误的。
\nclass C {\n public var a = 10 ; \n public var f () { \n return 10 ;\n }\n}\n
\n整合 JDK 代码仓库 \n为了简化开发流程,Java 10 中会将多个代码库合并到一个代码仓库中。
\n在已发布的 Java 版本中,JDK 的整套代码根据不同功能已被分别存储在多个 Mercurial 存储库,这八个 Mercurial 存储库分别是:root、corba、hotspot、jaxp、jaxws、jdk、langtools、nashorn。
\n虽然以上八个存储库之间相互独立以保持各组件代码清晰分离,但同时管理这些存储库存在许多缺点,并且无法进行相关联源代码的管理操作。其中最重要的一点是,涉及多个存储库的变更集无法进行原子提交 (atomic commit)。例如,如果一个 bug 修复时需要对独立存储两个不同代码库的代码进行更改,那么必须创建两个提交:每个存储库中各一个。这种不连续性很容易降低项目和源代码管理工具的可跟踪性和加大复杂性。特别是,不可能跨越相互依赖的变更集的存储库执行原子提交这种多次跨仓库的变化是常见现象。
\n为了解决这个问题,JDK 10 中将所有现有存储库合并到一个 Mercurial 存储库中。这种合并的一次生效应,单一的 Mercurial 存储库比现有的八个存储库要更容易地被镜像(作为一个 Git 存储库),并且使得跨越相互依赖的变更集的存储库运行原子提交成为可能,从而简化开发和管理过程。虽然在整合过程中,外部开发人员有一些阻力,但是 JDK 开发团队已经使这一更改成为 JDK 10 的一部分。
\n统一的垃圾回收接口 \n在当前的 Java 结构中,组成垃圾回收器(GC)实现的组件分散在代码库的各个部分。尽管这些惯例对于使用 GC 计划的 JDK 开发者来说比较熟悉,但对新的开发人员来说,对于在哪里查找特定 GC 的源代码,或者实现一个新的垃圾收集器常常会感到困惑。更重要的是,随着 Java modules 的出现,我们希望在构建过程中排除不需要的 GC,但是当前 GC 接口的横向结构会给排除、定位问题带来困难。
\n为解决此问题,需要整合并清理 GC 接口,以便更容易地实现新的 GC,并更好地维护现有的 GC。Java 10 中,hotspot/gc 代码实现方面,引入一个干净的 GC 接口,改进不同 GC 源代码的隔离性,多个 GC 之间共享的实现细节代码应该存在于辅助类中。这种方式提供了足够的灵活性来实现全新 GC 接口,同时允许以混合搭配方式重复使用现有代码,并且能够保持代码更加干净、整洁,便于排查收集器问题。
\n并行垃圾回收器 G1 \n大家如果接触过 Java 性能调优工作,应该会知道,调优的最终目标是通过参数设置来达到快速、低延时的内存垃圾回收以提高应用吞吐量,尽可能的避免因内存回收不及时而触发的完整 GC(Full GC 会带来应用出现卡顿)。
\nG1 垃圾回收器是 Java 9 中 Hotspot 的默认垃圾回收器,是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是当并发收集无法快速回收内存时,会触发垃圾回收器回退进行 Full GC。之前 Java 版本中的 G1 垃圾回收器执行 GC 时采用的是基于单线程标记扫描压缩算法(mark-sweep-compact)。为了最大限度地减少 Full GC 造成的应用停顿的影响,Java 10 中将为 G1 引入多线程并行 GC,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
\nJava 10 中将采用并行化 mark-sweep-compact 算法,并使用与年轻代回收和混合回收相同数量的线程。具体并行 GC 线程数量可以通过:-XX:ParallelGCThreads 参数来调节,但这也会影响用于年轻代和混合收集的工作线程数。
\n线程局部管控 \n在已有的 Java 版本中,JVM 线程只能全部启用或者停止,没法做到对单独某个线程的操作。为了能够对单独的某个线程进行操作,Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程。通过这种方式显著地提高了现有 JVM 功能的性能开销,并且改变了到达 JVM 全局安全点的现有时间语义。
\n增加的参数为:-XX:ThreadLocalHandshakes (默认为开启),将允许用户在支持的平台上选择安全点。
\n参考
\n",
"link": "\\en-us\\blog\\java\\feature.html",
"meta": {}
}
\ No newline at end of file
diff --git a/en-us/blog/java/javavm.html b/en-us/blog/java/javavm.html
new file mode 100644
index 0000000..31900e0
--- /dev/null
+++ b/en-us/blog/java/javavm.html
@@ -0,0 +1,466 @@
+
+
+
+
+
+
+
+
+
+ javavm
+
+
+
+
+ Java 虚拟机
+
+一、基本概念
+二、Java 内存区域
+ 2.1 程序计数器
+ 2.2 Java虚拟机栈
+ 2.3 本地方法栈
+ 2.4 Java堆
+ 2.5 方法区
+三、对象
+四、垃圾收集算法
+ 4.1 Java 堆回收
+ 4.2 方法区回收
+ 4.3 垃圾收集算法
+五、经典垃圾收集器
+ 5.1 Serial 收集器
+ 5.2 ParNew 收集器
+ 5.3 Parallel Scavenge 收集器
+ 5.4 Serial Old 收集器
+ 5.5 Paralled Old 收集器
+ 5.6 CMS 收集器
+ 5.7 Garbage First 收集器
+ 5.8 内存分配原则
+六、虚拟机类加载机制
+ 6.1 类加载时机
+ 6.2 类加载过程
+ 6.3 类加载器
+ 6.4 双亲委派模型
+ 6.5 模块化下的类加载器
+七、程序编译
+ 7.1 编译器分类
+ 7.2 解释器与编译器
+ 7.3 分层编译
+ 7.4 热点探测
+八、代码优化
+ 8.1 方法内联
+ 8.2 逃逸分析
+ 8.3 公共子表达式消除
+ 8.4 数组边界检查消除
+
+一、基本概念
+1.1 OpenJDK
+自 1996 年 JDK 1.0
发布以来,Sun 公司在大版本上发行了 JDK 1.1
、JDK 1.2
、JDK 1.3
、JDK 1.4
、JDK 5
,JDK 6
,这些版本的 JDK 都可以统称为 SunJDK 。之后在 2006 年的 JavaOne 大会上,Sun 公司宣布将 Java 开源,在随后的一年多里,它陆续将 JDK 的各个部分在 GPL v2(GNU General Public License,version 2)协议下开源,并建立了 OpenJDK 组织来对这些代码进行独立的管理,这就是 OpenJDK 的来源,此时的 OpenJDK 拥有当时 sunJDK 7 的几乎全部代码。
+1.2 OracleJDK
+在 JDK 7 的开发期间,由于各种原因的影响 Sun 公司市值一路下跌,已无力推进 JDK 7 的开发,JDK 7 的发布一直被推迟。之后在 2009 年 Sun 公司被 Oracle 公司所收购,为解决 JDK 7 长期跳票的问题,Oracle 将 JDK 7 中大部分未能完成的项目推迟到 JDK 8 ,并于 2011 年发布了JDK 7,在这之后由 Oracle 公司正常发行的 JDK 版本就由 SunJDK 改称为 Oracle JDK。
+在 2017 年 JDK 9 发布后,Oracle 公司宣布从此以后 JDK 将会在每年的 3 月和 9 月各发布一个大版本,即半年发行一个大版本,目的是为了避免众多功能被捆绑到一个 JDK 版本上而引发的无法交付的风险。
+在 JDK 11 发布后,Oracle 同步调整了 JDK 的商业授权,宣布从 JDK 11 起将以前的商业特性全部开源给 OpenJDK ,这样 OpenJDK 11 和 OracleJDK 11 的代码和功能,在本质上就完全相同了。同时还宣布以后都会发行两个版本的 JDK :
+
+一个是在 GPLv2 + CE 协议下由 Oracle 开源的 OpenJDK;
+一个是在 OTN 协议下正常发行的 OracleJDK。
+
+两者共享大部分源码,在功能上几乎一致。唯一的区别是 Oracle OpenJDK 可以在开发、测试或者生产环境中使用,但只有半年的更新支持;而 OracleJDK 对个人免费,但在生产环境中商用收费,可以有三年时间的更新支持。
+1.3 HotSpot VM
+它是 Sun/Oracle JDK 和 OpenJDK 中默认的虚拟机,也是目前使用最为广泛的虚拟机。最初由 Longview Technologies 公司设计发明,该公司在 1997 年被 Sun 公司收购,随后 Sun 公司在 2006 年开源 SunJDK 时也将 HotSpot 虚拟机一并进行了开源。之后 Oracle 收购 Sun 以后,建立了 HotRockit 项目,用于将其收购的另外一家公司(BEA)的 JRockit 虚拟机中的优秀特性集成到 HotSpot 中。HotSpot 在这个过程里面移除掉永久代,并吸收了 JRockit 的 Java Mission Control 监控工具等功能。到 JDK 8 发行时,采用的就是集两者之长的 HotSpot VM 。
+我们可以在自己的电脑上使用 java -version
来获得 JDK 的信息:
+C:\Users> java -version
+java version "1.8.0_171" # 如果是openJDK, 则这里会显示:openjdk version
+Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
+Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) # 使用的是HotSpot虚拟机,默认为服务端模式
+
+二、Java 内存区域
+
+2.1 程序计数器
+程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每条线程都拥有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。
+2.2 Java虚拟机栈
+Java 虚拟机栈(Java Virtual Machine Stack)也为线程私有,它描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法从调用到结束就对应着一个栈帧从入栈到出栈的过程。在《Java 虚拟机规范》中,对该内存区域规定了两类异常:
+
+如果线程请求的栈深度大于虚拟机所允许的栈深度,将抛出 StackOverflowError
异常;
+如果 Java 虚拟机栈的容量允许动态扩展,当栈扩展时如果无法申请到足够的内存会抛出 OutOfMemoryError
异常。
+
+2.3 本地方法栈
+本地方法栈(Native Method Stacks)与虚拟机栈类似,其区别在于:Java 虚拟机栈是为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
+2.4 Java堆
+Java 堆(Java Heap)是虚拟机所管理的最大一块的内存空间,它被所有线程所共享,用于存放对象实例。Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为是连续的。Java 堆可以被实现成固定大小的,也可以是可扩展的,当前大多数主流的虚拟机都是按照可扩展来实现的,即可以通过最大值参数 -Xmx
和最小值参数 -Xms
进行设定。如果 Java 堆中没有足够的内存来完成实例分配,并且堆也无法再扩展时,Java 虚拟机将会抛出 OutOfMemoryError
异常。
+2.5 方法区
+方法区(Method Area)也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。方法区也被称为 “非堆”,目的是与 Java 堆进行区分。《Java 虚拟机规范》规定,如果方法区无法满足新的内存分配需求时,将会抛出 OutOfMemoryError
异常。
+运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放常量池表(Constant Pool Table),常量池表中存放了编译期生成的各种符号字面量和符号引用。
+三、对象
+3.1 对象的创建
+当我们在代码中使用 new
关键字创建一个对象时,其在虚拟机中需要经过以下步骤:
+1. 类加载过程
+当虚拟机遇到一条字节码 new
指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。
+2. 分配内存
+在类加载检查通过后,虚拟机需要新生对象分配内存空间。根据 Java 堆是否规整,可以有以下两种分配方案:
+
+指针碰撞 :假设 Java 堆中内存是绝对规整的,所有使用的内存放在一边,所有未被使用的内存放在另外一边,中间以指针作为分界点指示器。此时内存分配只是将指针向空闲方向偏移出对象大小的空间即可,这种方式被称为指针碰撞。
+
+
+
+空闲列表 :如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。
+
+
+注:Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力,后文将会介绍。
+
+除了分配方式外,由于对象创建在虚拟机中是一个非常频繁的行为,此时需要保证在并发环境下的线程安全:如果一个线程给对象 A 分配了内存空间,但指针还没来得及修改,此时就可能出现另外一个线程使用原来的指针来给对象 B 分配内存空间的情况。想要解决这个问题有两个方案:
+
+方式一 :采用同步锁定,或采用 CAS 配上失败重试的方式来保证更新操作的原子性。
+方式二 :为每个线程在 Java 堆中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。线程在进行内存分配时优先使用本地缓冲,当本地缓冲使用完成后,再向 Java 堆申请分配,此时 Java 堆采用同步锁定的方式来保证分配行为的线程安全。
+
+3. 对象头设置
+将对象有关的元数据信息、对象的哈希码、分代年龄等信息存储到对象头中。
+4. 对象初始化
+调用对象的构造函数,即 Class 文件中的 <init>()
来初始化对象,为相关字段赋值。
+3.2 对象的内存布局
+在 HotSpot 虚拟机中,对象在堆内存中的存储布局可以划分为以下三个部分:
+1. 对象头 (Header)
+对象头包括两部分信息:
+
+Mark Word :对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,官方统称为 Mark Word 。
+类型指针 :对象指向它类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的示例。需要说明的是并非所有的虚拟机都必须要在对象数据上保留类型指针,这取决于对象的访问定位方式(详见下文)。
+
+2. 实例数据 (Instance Data)
+即我们在程序代码中定义的各种类型的字段的内容,无论是从父类继承而来,还是子类中定义的都需要记录。
+3. 对其填充 (Padding)
+主要起占位符的作用。HotSpot 虚拟机要求对象起始地址必须是 8 字节的整倍数,即间接要求了任何对象的大小都必须是 8 字节的整倍数。对象头部分在设计上就是 8 字节的整倍数,如果对象的实例数据不是 8 字节的整倍数,则由对齐填充进行补全。
+3.3 对象的访问定位
+对象创建后,Java 程序就可以通过栈上的 reference
来操作堆上的具体对象。《Java 虚拟机规范》规定 reference
是一个指向对象的引用,但并未规定其具体实现方式。主流的方式方式有以下两种:
+
+句柄访问 :Java 堆将划分出一块内存来作为句柄池, reference
中存储的是对象的句柄地址,而句柄则包含了对象实例数据和类型数据的地址信息。
+指针访问 :reference
中存储的直接就是对象地址,而对象的类型数据则由上文介绍的对象头中的类型指针来指定。
+
+通过句柄访问对象:
+
+通过直接指针访问对象:
+
+句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 reference
本生并不需要修改;指针访问则反之,由于其 reference
中存储的直接就是对象地址,所以当对象移动时, reference
需要被修改。但针对只需要访问对象本身的场景,指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作,所以这类减少的效果会非常显著,基于这个原因,HotSpot 主要使用的是指针访问的方式。
+四、垃圾收集算法
+在 Java 虚拟机内存模型中,程序计数器、虚拟机栈、本地方法栈这 3 个区域都是线程私有的,会随着线程的结束而销毁,因此在这 3 个区域当中,无需过多考虑垃圾回收问题。垃圾回收问题主要发生在 Java 堆和方法区上。
+4.1 Java 堆回收
+在 Java 堆上,垃圾回收的主要内容是死亡对象(不可能再被任何途径使用的对象)。而判断对象是否死亡有以下两种方法:
+1. 引用计数法
+在对象中添加一个引用计数器,对象每次被引用时,该计数器加一;当引用失效时,计数器的值减一;只要计数器的值为零,则代表对应的对象不可能再被使用。该方法的缺点在于无法避免相互循环引用的问题:
+objA.instance = objB
+objB.instance = objA
+objA = null ;
+objB = null ;
+System.gc();
+
+如上所示,此时两个对象已经不能再被访问,但其互相持有对对方的引用,如果采用引用计数法,则两个对象都无法被回收。
+2. 可达性分析
+上面的代码在大多数虚拟机中都能被正确的回收,因为大多数主流的虚拟机都是采用的可达性分析方法来判断对象是否死亡。可达性分析是通过一系列被称为 GC Roots
的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链(Reference Chain),如果某个对象到 GC Roots
间没有任何引用链相连,这代表 GC Roots
到该对象不可达, 此时证明此该对象不可能再被使用。
+
+在 Java 语言中,固定可作为 GC Roots
的对象包括以下几种:
+
+在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
+在方法区中类静态属性引用的对象,譬如 Java 类中引用类型的静态变量;
+在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用;
+在本地方法栈中的 JNI(即 Native 方法)引用的对象;
+Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(如 NullPointException,OutOfMemoryError 等)及系统类加载器;
+所有被同步锁(synchronized 关键字)持有的对象;
+反应 Java 虚拟机内部情况的 JMXBean,JVMTI 中注册的回调,本地代码缓存等。
+
+除了这些固定的 GC Roots
集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域的不同,还可能会有其他对象 “临时性” 地加入,共同构成完整的 GC Roots
集合。
+3. 对象引用
+可达性分析是基于引用链进行判断的,在 JDK 1.2 之后,Java 将引用关系分为以下四类:
+
+强引用 (Strongly Reference) :最传统的引用,如 Object obj = new Object()
。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
+软引用 (Soft Reference) :用于描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常之前,会被列入回收范围内进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
+弱引用 (Weak Reference) :用于描述那些非必须的对象,强度比软引用弱。被弱引用关联对象只能生存到下一次垃圾收集发生时,无论当前内存是否足够,弱引用对象都会被回收。
+虚引用 (Phantom Reference) :最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被回收时收到一个系统通知。
+
+4. 对象真正死亡
+要真正宣告一个对象死亡,需要经过至少两次标记过程:
+
+如果对象在进行可达性分析后发现 GC Roots
不可达,将会进行第一次标记;
+随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalized()
方法。如果对象没有覆盖 finalized()
方法,或者 finalized()
已经被虚拟机调用过,这两种情况都会视为没有必要执行。如果判定结果是有必要执行,此时对象会被放入名为 F-Queue
的队列,等待 Finalizer 线程执行其 finalized()
方法。在这个过程中,收集器会进行第二次小规模的标记,如果对象在 finalized()
方法中重新将自己与引用链上的任何一个对象进行了关联,如将自己(this 关键字)赋值给某个类变量或者对象的成员变量,此时它就实现了自我拯救,则第二次标记会将其移除 “即将回收” 的集合,否则该对象就将被真正回收,走向死亡。
+
+4.2 方法区回收
+在 Java 堆上进行对象回收的性价比通常比较高,因为大多数对象都是朝生夕灭的。而方法区由于回收条件比较苛刻,对应的回收性价比通常比较低,主要回收两部分内容:废弃的常量和不再使用的类型。
+4.3 垃圾收集算法
+1. 分代收集理论
+当前大多数虚拟机都遵循 “分代收集” 的理论进行设计,它建立在强弱两个分代假说下:
+
+弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是朝生夕灭的。
+强分代假说 (Strong Generational Hypothesis) :熬过越多次垃圾收集过程的对象就越难以消亡。
+跨带引用假说 (Intergenerational Reference Hypothesis) :基于上面两条假说还可以得出的一条隐含推论:存在相互引用关系的两个对象,应该倾向于同时生存或者同时消亡。
+
+强弱分代假说奠定了垃圾收集器的设计原则:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄就是对象经历垃圾收集的次数)分配到不同的区域中进行存储。之后如果一个区域中的对象都是朝生夕灭的,那么收集器只需要关注少量对象的存活而不是去标记那些大量将要被回收的对象,此时就能以较小的代价获取较大的空间。最后再将难以消亡的对象集中到一块,根据强分代假说,它们是很难消亡的,因此虚拟机可以使用较低的频率进行回收,这就兼顾了时间和内存空间的开销。
+2. 回收类型
+根据分代收集理论,收集范围可以分为以下几种类型:
+
+部分收集 (Partial GC) :具体分为:
+
+新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
+老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
+混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
+
+
+整堆收集 (Full GC) :收集整个 Java 堆和方法区。
+
+3. 标记-清除算法
+它是最基础的垃圾收集算法,收集过程分为两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;也可以反过来,标记存活对象,统一回收所有未被标记的对象。
+
+它主要有以下两个缺点:
+
+执行效率不稳定:如果 Java 堆上包含大量需要回收的对象,则需要进行大量标记和清除动作;
+内存空间碎片化:标记清除后会产生大量不连续的空间,从而可能导致无法为大对象分配足够的连续内存。
+
+4. 标记-复制算法
+标记-复制算法基于 ”半区复制“ 算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存使用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的那块内存空间一次性清理掉。其优点在于避免了内存空间碎片化的问题,其缺点如下:
+
+如果内存中多数对象都是存活的,这种算法将产生大量的复制开销;
+浪费内存空间,内存空间变为了原有的一半。
+
+
+基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 Eden
和 两块较小的 Survivor
空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 Eden
和其中的一块 Survivor
,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 Survivor
上,这样只有 10% 的内存空间会被浪费掉。当 Survivor
空间不足以容纳一次 Minor GC
时,此时由其他内存区域(通常是老年代)来进行分配担保。
+5. 标记-整理算法
+标记-整理算法是在标记完成后,让所有存活对象都向内存的一端移动,然后直接清理掉边界以外的内存。其优点在于可以避免内存空间碎片化的问题,也可以充分利用内存空间;其缺点在于根据所使用的收集器的不同,在移动存活对象时可能要全程暂停用户程序:
+
+五、经典垃圾收集器
+并行与并发是并发编程中的专有名词,在谈论垃圾收集器的上下文语境中,它们的含义如下:
+
+HotSpot 虚拟机中一共存在七款经典的垃圾收集器:
+
+
+注:收集器之间存在连线,则代表它们可以搭配使用。
+
+5.1 Serial 收集器
+Serial 收集器是最基础、历史最悠久的收集器,它是一个单线程收集器,在进行垃圾回收时,必须暂停其他所有的工作线程,直到收集结束,这是其主要缺点。它的优点在于单线程避免了多线程复杂的上下文切换,因此在单线程环境下收集效率非常高,由于这个优点,迄今为止,其仍然是 HotSpot 虚拟机在客户端模式下默认的新生代收集器:
+
+5.2 ParNew 收集器
+他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收:
+
+5.3 Parallel Scavenge 收集器
+Parallel Scavenge 也是新生代收集器,基于 标记-复制 算法进行实现,它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值:
+吞吐量 = \frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间}
+
+Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量:
+
+-XX:MaxGCPauseMillis :控制最大垃圾收集时间,假设需要回收的垃圾总量不变,那么降低垃圾收集的时间就会导致收集频率变高,所以需要将其设置为合适的值,不能一味减小。
+-XX:MaxGCTimeRatio :直接用于设置吞吐量大小,它是一个大于 0 小于 100 的整数。假设把它设置为 19,表示此时允许的最大垃圾收集时间占总时间的 5%(即 1/(1+19) );默认值为 99 ,即允许最大 1%( 1/(1+99) )的垃圾收集时间。
+
+5.4 Serial Old 收集器
+从名字也可以看出来,它是 Serial 收集器的老年代版本,同样是一个单线程收集器,采用 标记-整理 算法,主要用于给客户端模式下的 HotSpot 虚拟机使用:
+
+5.5 Paralled Old 收集器
+Paralled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现:
+
+5.6 CMS 收集器
+CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于 标记-清除 算法实现,整个收集过程分为以下四个阶段:
+
+初始标记 (inital mark) :标记 GC Roots
能直接关联到的对象,耗时短但需要暂停用户线程;
+并发标记 (concurrent mark) :从 GC Roots
能直接关联到的对象开始遍历整个对象图,耗时长但不需要暂停用户线程;
+重新标记 (remark) :采用增量更新算法,对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记,耗时比初始标记稍长且需要暂停用户线程;
+并发清除 (inital sweep) :并发清除掉已经死亡的对象,耗时长但不需要暂停用户线程。
+
+
+其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下:
+
+由于涉及并发操作,因此对处理器资源比较敏感。
+由于是基于 标记-清除 算法实现的,因此会产生大量空间碎片。
+无法处理浮动垃圾(Floating Garbage):由于并发清除时用户线程还是在继续,所以此时仍然会产生垃圾,这些垃圾就被称为浮动垃圾,只能等到下一次垃圾收集时再进行清理。
+
+5.7 Garbage First 收集器
+Garbage First(简称 G1)是一款面向服务端的垃圾收集器,也是 JDK 9 服务端模式下默认的垃圾收集器,它的诞生具有里程碑式的意义。G1 虽然也遵循分代收集理论,但不再以固定大小和固定数量来划分分代区域,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region)。每一个 Region 都可以根据不同的需求来扮演新生代的 Eden
空间、Survivor
空间或者老年代空间,收集器会根据其扮演角色的不同而采用不同的收集策略。
+
+上面还有一些 Region 使用 H 进行标注,它代表 Humongous,表示这些 Region 用于存储大对象(humongous object,H-obj),即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤:
+
+初始标记 (Inital Marking) :标记 GC Roots
能直接关联到的对象,并且修改 TAMS(Top at Mark Start)指针的值,让下一阶段用户线程并发运行时,能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针,新分配的对象必须位于这两个指针位置以上,位于这两个指针位置以上的对象默认被隐式标记为存活的,不会纳入回收范围;
+并发标记 (Concurrent Marking) :从 GC Roots
能直接关联到的对象开始遍历整个对象图。遍历完成后,还需要处理 SATB 记录中变动的对象。SATB(snapshot-at-the-beginning,开始阶段快照)能够有效的解决并发标记阶段因为用户线程运行而导致的对象变动,其效率比 CMS 重新标记阶段所使用的增量更新算法效率更高;
+最终标记 (Final Marking) :对用户线程做一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的少量的 STAB 记录。虽然并发标记阶段会处理 SATB 记录,但由于处理时用户线程依然是运行中的,因此依然会有少量的变动,所以需要最终标记来处理;
+筛选回收 (Live Data Counting and Evacuation) :负责更新 Regin 统计数据,按照各个 Regin 的回收价值和成本进行排序,在根据用户期望的停顿时间进行来指定回收计划,可以选择任意多个 Regin 构成回收集。然后将回收集中 Regin 的存活对象复制到空的 Regin 中,再清理掉整个旧的 Regin 。此时因为涉及到存活对象的移动,所以需要暂停用户线程,并由多个收集线程并行执行。
+
+
+5.8 内存分配原则
+1. 对象优先在 Eden 分配
+大多数情况下,对象在新生代的 Eden
区中进行分配,当 Eden
区没有足够空间时,虚拟机将进行一次 Minor GC。
+2. 大对象直接进入老年代
+大对象就是指需要大量连续内存空间的 Java 对象,最典型的就是超长的字符串或者元素数量很多的数组,它们将直接进入老年代。主要是因为如果在新生代分配,因为其需要大量连续的内存空间,可能会导致提前触发垃圾回收;并且由于新生代的垃圾回收本身就很频繁,此时复制大对象也需要额外的性能开销。
+3. 长期存活的对象将进入老年代
+虚拟机会给每个对象在其对象头中定义一个年龄计数器。对象通常在 Eden
区中诞生,如果经历第一次 Minor GC 后仍然存活,并且能够被 Survivor 容纳的话,该对象就会被移动到 Survivor 中,并将其年龄加 1。对象在 Survivor 中每经过一次 Minor GC,年龄就加 1,当年龄达到一定程度后(由 -XX:MaxTenuringThreshold
设置,默认值为 15)就会进入老年代中。
+4. 动态年龄判断
+如果在 Survivor 空间中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等待年龄到达 -XX:MaxTenuringThreshold
设置的值。
+5. 空间担保分配
+在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果条件成立,那么这一次的 Minor GC 可以确认是安全的。如果不成立,虚拟机会查看 -XX:HandlePromotionFailure
的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于或者 -XX:HandlePromotionFailure
的值设置不允许冒险,那么就要改为进行一次 Full GC 。
+六、虚拟机类加载机制
+Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的类加载机制。
+6.1 类加载时机
+一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、卸载、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接:
+
+《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化:
+
+遇到 new
、 getstatic
、 putstatic
、 invokestatic
这四条字节码指令,如果类型进行过初始化,则需要先触发其进行初始化,能够生成这四条指令码的典型 Java 代码场景有:
+
+使用 new
关键字实例化对象时;
+读取或设置一个类型的静态字段时(被 final 修饰,已在编译期把结果放入常量池的静态字段除外);
+调用一个类型的静态方法时。
+
+
+使用 java.lang.reflect
包的方法对类型进行反射调用时,如果类型没有进行过初始化、则需要触发其初始化;
+当初始化类时,如发现其父类还没有进行过初始化、则需要触发其父类进行初始化;
+当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
+当使用 JDK 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle
实例最后解析的结果为 REF_getStatic
, REF_putStatic
, REF_invokeStatic
, REF_newInvokeSpecial
四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化;
+当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那么该接口要在其之前被初始化。
+
+6.2 类加载过程
+1. 加载
+在加载阶段,虚拟机需要完成以下三件事:
+
+通过一个类的全限定名来获取定义此类的二进制字节流 ;
+将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构;
+在内存中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
+
+《Java 虚拟机规范》并没有限制从何处获取二进制流,因此可以从 JAR 包、WAR 包获取,也可以从 JSP 生成的 Class 文件等处获取。
+2. 验证
+这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,从而保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证阶段大致会完成下面四项验证:
+
+文件格式验证 :验证字节流是否符合 Class 文件格式的规范;
+元数据验证 :对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java 语言规范》的要求(如除了 java.lang.Object
外,所有的类都应该有父类);
+字节码验证 :通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的(如允许把子类对象赋值给父类数据类型,但不能把父类对象赋值给子类数据类型);
+符号引用验证 :验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。如果无法验证通过,则会抛出一个java.lang.IncompatibleClassChangeError
的子类异常,如 java.lang.NoSuchFieldError
、 java.lang.NoSuchMethodError
等。
+
+3. 准备
+准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段。
+4. 解析
+解析是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程:
+
+符号引用 :符号引用用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
+直接引用 :直接引用是指可以直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。
+
+整个解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行解析。
+5. 初始化
+初始化阶段就是执行类构造器的 <clinit>()
方法的过程,该方法具有以下特点:
+
+<clinit>()
方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生,编译器收集顺序由语句在源文件中出现的顺序决定。
+<clinit>()
方法与类的构造器函数(即在虚拟机视角中的实例构造器 <init>()
方法)不同,它不需要显示的调用父类的构造器,Java 虚拟机会保证在子类的 <clinit>()
方法执行前,父类的 <clinit>()
方法已经执行完毕。
+由于父类的 <clinit>()
方法先执行,也就意味着父类中定义的静态语句块要优先于子类变量的赋值操作。
+<clinit>()
方法对于类或者接口不是必须的,如果一个类中没有静态语句块,也没有对变量进行赋值操作,那么编译器可以不为这个类生成 <clinit>()
方法。
+接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>()
方法。
+Java 虚拟机必须保证一个类的 <clinit>()
方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的 <clinit>()
方法,其他线程都需要阻塞等待。
+
+6.3 类加载器
+能够通过一个类的全限定名来获取描述该类的二进制字节流的工具称为类加载器。每一个类加载器都拥有一个独立的类名空间,因此对于任意一个类,都必须由加载它的类加载器和这个类本身来共同确立其在 Java 虚拟机中的唯一性。这意味着要想比较两个类是否相等,必须在同一类加载器加载的前提下;如果两个类的类加载器不同,则它们一定不相等。
+6.4 双亲委派模型
+从 Java 虚拟机角度而言,类加载器可以分为以下两类:
+
+启动类加载器 :启动类加载器(Bootstrap ClassLoader)由 C++ 语言实现(以 HotSpot 为例),它是虚拟机自身的一部分;
+其他所有类的类加载器 :由 Java 语言实现,独立存在于虚拟机外部,并且全部继承自 java.lang.ClassLoader
。
+
+从开发人员角度而言,类加载器可以分为以下三类:
+
+启动类加载器 (Boostrap Class Loader) :负责把存放在 <JAVA_HOME>\lib
目录中,或被 -Xbootclasspath
参数所指定的路径中存放的能被 Java 虚拟机识别的类库加载到虚拟机的内存中;
+扩展类加载器 (Extension Class Loader) :负责加载 <JAVA_HOME>\lib\ext
目录中,或被 java.ext.dirs
系统变量所指定的路径中的所有类库。
+应用程序类加载器 (Application Class Loader) :负责加载用户类路径(ClassPath)上的所有的类库。
+
+JDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成加载:
+
+上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。
+双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类,否则就有可能出现一个程序中存在两个不同的 java.lang.Object
的情况。
+6.5 模块化下的类加载器
+JDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
+
+仍维持三层类加载器和双亲委派的架构,但扩展类加载器被平台类加载器所取代;
+当平台及应用程序类加载器收到类加载请求时,要首先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载;
+启动类加载器、平台类加载器、应用程序类加载器全部继承自 java.internal.loader.BuiltinClassLoader
,BuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。
+
+
+七、程序编译
+7.1 编译器分类
+
+前端编译器 :把 *.java
文件转变成 .class
文件的过程;如 JDK 的 Javac,Eclipse JDT 中的增量式编译器。
+即使编译器 :常称为 JIT 编译器(Just In Time Complier),在运行期把字节码转变成本地机器码的过程;如 HotSpot 虚拟机中的 C1、C2 编译器,Graal 编译器。
+提前编译器 :直接把程序编译成目标机器指令集相关的二进制代码的过程。如 JDK 的 jaotc,GUN Compiler for the Java(GCJ),Excelsior JET 。
+
+7.2 解释器与编译器
+在 HotSpot 虚拟机中,Java 程序最初都是通过解释器(Interpreter)进行解释执行的,其优点在于可以省去编译时间,让程序快速启动。当程序启动后,如果虚拟机发现某个方法或代码块的运行特别频繁,就会使用编译器将其编译为本地机器码,并使用各种手段进行优化,从而提高执行效率,这就是即时编译器。HotSpot 内置了两个(或三个)即时编译器:
+
+客户端编译器 (Client Complier) :简称 C1;
+服务端编译器 (Servier Complier) :简称 C2,在有的资料和 JDK 源码中也称为 Opto 编译器;
+Graal 编译器 :在 JDK 10 时才出现,长期目标是替代 C2。
+
+在分层编译的工作模式出现前,采用客户端编译器还是服务端编译器完全取决于虚拟机是运行在客户端模式还是服务端模式下,可以在启动时通过 -client
或 -server
参数进行指定,也可以让虚拟机根据自身版本和宿主机性能来自主选择。
+7.3 分层编译
+要编译出优化程度越高的代码通常都需要越长的编译时间,为了在程序启动速度与运行效率之间达到最佳平衡,HotSpot 虚拟机在编译子系统中加入了分层编译(Tiered Compilation):
+
+第 0 层 :程序纯解释执行,并且解释器不开启性能监控功能;
+第 1 层 :使用客户端编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启性能监控功能;
+第 2 层 :仍然使用客户端编译执行,仅开启方法及回边次数统计等有限的性能监控;
+第 3 层 :仍然使用客户端编译执行,开启全部性能监控;
+第 4 层 :使用服务端编译器将字节码编译为本地代码,其耗时更长,并且会根据性能监控信息进行一些不可靠的激进优化。
+
+以上层次并不是固定不变的,根据不同的运行参数和版本,虚拟机可以调整分层的数量。各层次编译之间的交互转换关系如下图所示:
+
+实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。
+7.4 热点探测
+即时编译器编译的目标是 “热点代码”,它主要分为以下两类:
+
+被多次调用的方法。
+被多次执行循环体。这里指的是一个方法只被少量调用过,但方法体内部存在循环次数较多的循环体,此时也认为是热点代码。但编译器编译的仍然是循环体所在的方法,而不会单独编译循环体。
+
+判断某段代码是否是热点代码的行为称为 “热点探测” (Hot Spot Code Detection),主流的热点探测方法有以下两种:
+
+基于采样的热点探测 (Sample Based Hot Spot Code Detection) :采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶,如果发现某个(或某些)方法经常出现在栈顶,那么就认为它是 “热点方法”。
+基于计数的热点探测 (Counter Based Hot Spot Code Detection) :采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是 “热点方法”。
+
+八、代码优化
+即时编译器除了将字节码编译为本地机器码外,还会对代码进行一定程度的优化,它包含多达几十种优化技术,这里选取其中代表性的四种进行介绍:
+8.1 方法内联
+最重要的优化手段,它会将目标方法中的代码原封不动地 “复制” 到发起调用的方法之中,避免发生真实的方法调用,并采用名为类型继承关系分析(Class Hierarchy Analysis,CHA)的技术来解决虚方法(Java 语言中默认的实例方法都是虚方法)的内联问题。
+8.2 逃逸分析
+逃逸行为主要分为以下两类:
+
+方法逃逸 :当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,此时称为方法逃逸;
+线程逃逸 :当一个对象在方法里面被定义后,它可能被外部线程所访问,例如赋值给可以在其他线程中访问的实例变量,此时称为线程,其逃逸程度高于方法逃逸。
+
+public static StringBuilder concat (String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for (String string : strings) {
+ sb.append(string);
+ }
+ return sb;
+}
+
+public static String concat (String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for (String string : strings) {
+ sb.append(string);
+ }
+ return sb.toString();
+}
+
+如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可以为这个对象实例采取不同程序的优化:
+
+栈上分配 (Stack Allocations) :如果一个对象不会逃逸到线程外,那么将会在栈上分配内存来创建这个对象,而不是 Java 堆上,此时对象所占用的内存空间就会随着栈帧的出栈而销毁,从而可以减轻垃圾回收的压力。
+标量替换 (Scalar Replacement) :如果一个数据已经无法再分解成为更小的数据类型,那么这些数据就称为标量(如 int、long 等数值类型及 reference 类型等);反之,如果一个数据可以继续分解,那它就被称为聚合量(如对象)。如果一个对象不会逃逸外方法外,那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代,从而减少内存占用。
+同步消除 (Synchronization Elimination) :如果一个变量不会逃逸出线程,那么对这个变量实施的同步措施就可以消除掉。
+
+8.3 公共子表达式消除
+如果一个表达式 E 之前已经被计算过了,并且从先前的计算到现在 E 中所有变量的值都没有发生过变化,那么 E 这次的出现就称为公共子表达式。对于这种表达式,无需再重新进行计算,只需要直接使用前面的计算结果即可。
+8.4 数组边界检查消除
+对于虚拟机执行子系统来说,每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中,并且使用循环变量来访问数据,即循环变量的取值永远在 [0,list.length) 之间,那么此时就可以消除整个循环的数据边界检查,从而避免多次无用的判断。
+参考资料
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/java/javavm.json b/en-us/blog/java/javavm.json
new file mode 100644
index 0000000..0273ded
--- /dev/null
+++ b/en-us/blog/java/javavm.json
@@ -0,0 +1,6 @@
+{
+ "filename": "javavm.md",
+ "__html": "Java 虚拟机 \n\n一、基本概念 \n二、Java 内存区域 \n 2.1 程序计数器 \n 2.2 Java虚拟机栈 \n 2.3 本地方法栈 \n 2.4 Java堆 \n 2.5 方法区 \n三、对象 \n四、垃圾收集算法 \n 4.1 Java 堆回收 \n 4.2 方法区回收 \n 4.3 垃圾收集算法 \n五、经典垃圾收集器 \n 5.1 Serial 收集器 \n 5.2 ParNew 收集器 \n 5.3 Parallel Scavenge 收集器 \n 5.4 Serial Old 收集器 \n 5.5 Paralled Old 收集器 \n 5.6 CMS 收集器 \n 5.7 Garbage First 收集器 \n 5.8 内存分配原则 \n六、虚拟机类加载机制 \n 6.1 类加载时机 \n 6.2 类加载过程 \n 6.3 类加载器 \n 6.4 双亲委派模型 \n 6.5 模块化下的类加载器 \n七、程序编译 \n 7.1 编译器分类 \n 7.2 解释器与编译器 \n 7.3 分层编译 \n 7.4 热点探测 \n八、代码优化 \n 8.1 方法内联 \n 8.2 逃逸分析 \n 8.3 公共子表达式消除 \n 8.4 数组边界检查消除 \n \n一、基本概念 \n1.1 OpenJDK \n自 1996 年 JDK 1.0
发布以来,Sun 公司在大版本上发行了 JDK 1.1
、JDK 1.2
、JDK 1.3
、JDK 1.4
、JDK 5
,JDK 6
,这些版本的 JDK 都可以统称为 SunJDK 。之后在 2006 年的 JavaOne 大会上,Sun 公司宣布将 Java 开源,在随后的一年多里,它陆续将 JDK 的各个部分在 GPL v2(GNU General Public License,version 2)协议下开源,并建立了 OpenJDK 组织来对这些代码进行独立的管理,这就是 OpenJDK 的来源,此时的 OpenJDK 拥有当时 sunJDK 7 的几乎全部代码。
\n1.2 OracleJDK \n在 JDK 7 的开发期间,由于各种原因的影响 Sun 公司市值一路下跌,已无力推进 JDK 7 的开发,JDK 7 的发布一直被推迟。之后在 2009 年 Sun 公司被 Oracle 公司所收购,为解决 JDK 7 长期跳票的问题,Oracle 将 JDK 7 中大部分未能完成的项目推迟到 JDK 8 ,并于 2011 年发布了JDK 7,在这之后由 Oracle 公司正常发行的 JDK 版本就由 SunJDK 改称为 Oracle JDK。
\n在 2017 年 JDK 9 发布后,Oracle 公司宣布从此以后 JDK 将会在每年的 3 月和 9 月各发布一个大版本,即半年发行一个大版本,目的是为了避免众多功能被捆绑到一个 JDK 版本上而引发的无法交付的风险。
\n在 JDK 11 发布后,Oracle 同步调整了 JDK 的商业授权,宣布从 JDK 11 起将以前的商业特性全部开源给 OpenJDK ,这样 OpenJDK 11 和 OracleJDK 11 的代码和功能,在本质上就完全相同了。同时还宣布以后都会发行两个版本的 JDK :
\n\n一个是在 GPLv2 + CE 协议下由 Oracle 开源的 OpenJDK; \n一个是在 OTN 协议下正常发行的 OracleJDK。 \n \n两者共享大部分源码,在功能上几乎一致。唯一的区别是 Oracle OpenJDK 可以在开发、测试或者生产环境中使用,但只有半年的更新支持;而 OracleJDK 对个人免费,但在生产环境中商用收费,可以有三年时间的更新支持。
\n1.3 HotSpot VM \n它是 Sun/Oracle JDK 和 OpenJDK 中默认的虚拟机,也是目前使用最为广泛的虚拟机。最初由 Longview Technologies 公司设计发明,该公司在 1997 年被 Sun 公司收购,随后 Sun 公司在 2006 年开源 SunJDK 时也将 HotSpot 虚拟机一并进行了开源。之后 Oracle 收购 Sun 以后,建立了 HotRockit 项目,用于将其收购的另外一家公司(BEA)的 JRockit 虚拟机中的优秀特性集成到 HotSpot 中。HotSpot 在这个过程里面移除掉永久代,并吸收了 JRockit 的 Java Mission Control 监控工具等功能。到 JDK 8 发行时,采用的就是集两者之长的 HotSpot VM 。
\n我们可以在自己的电脑上使用 java -version
来获得 JDK 的信息:
\nC:\\Users> java -version\njava version \"1.8.0_171\" # 如果是openJDK, 则这里会显示:openjdk version\nJava(TM) SE Runtime Environment (build 1.8.0_171-b11)\nJava HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) # 使用的是HotSpot虚拟机,默认为服务端模式\n
\n二、Java 内存区域 \n\n2.1 程序计数器 \n程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每条线程都拥有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。
\n2.2 Java虚拟机栈 \nJava 虚拟机栈(Java Virtual Machine Stack)也为线程私有,它描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法从调用到结束就对应着一个栈帧从入栈到出栈的过程。在《Java 虚拟机规范》中,对该内存区域规定了两类异常:
\n\n如果线程请求的栈深度大于虚拟机所允许的栈深度,将抛出 StackOverflowError
异常; \n如果 Java 虚拟机栈的容量允许动态扩展,当栈扩展时如果无法申请到足够的内存会抛出 OutOfMemoryError
异常。 \n \n2.3 本地方法栈 \n本地方法栈(Native Method Stacks)与虚拟机栈类似,其区别在于:Java 虚拟机栈是为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
\n2.4 Java堆 \nJava 堆(Java Heap)是虚拟机所管理的最大一块的内存空间,它被所有线程所共享,用于存放对象实例。Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为是连续的。Java 堆可以被实现成固定大小的,也可以是可扩展的,当前大多数主流的虚拟机都是按照可扩展来实现的,即可以通过最大值参数 -Xmx
和最小值参数 -Xms
进行设定。如果 Java 堆中没有足够的内存来完成实例分配,并且堆也无法再扩展时,Java 虚拟机将会抛出 OutOfMemoryError
异常。
\n2.5 方法区 \n方法区(Method Area)也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。方法区也被称为 “非堆”,目的是与 Java 堆进行区分。《Java 虚拟机规范》规定,如果方法区无法满足新的内存分配需求时,将会抛出 OutOfMemoryError
异常。
\n运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放常量池表(Constant Pool Table),常量池表中存放了编译期生成的各种符号字面量和符号引用。
\n三、对象 \n3.1 对象的创建 \n当我们在代码中使用 new
关键字创建一个对象时,其在虚拟机中需要经过以下步骤:
\n1. 类加载过程
\n当虚拟机遇到一条字节码 new
指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。
\n2. 分配内存
\n在类加载检查通过后,虚拟机需要新生对象分配内存空间。根据 Java 堆是否规整,可以有以下两种分配方案:
\n\n指针碰撞 :假设 Java 堆中内存是绝对规整的,所有使用的内存放在一边,所有未被使用的内存放在另外一边,中间以指针作为分界点指示器。此时内存分配只是将指针向空闲方向偏移出对象大小的空间即可,这种方式被称为指针碰撞。 \n \n\n\n空闲列表 :如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。 \n \n\n注:Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力,后文将会介绍。
\n \n除了分配方式外,由于对象创建在虚拟机中是一个非常频繁的行为,此时需要保证在并发环境下的线程安全:如果一个线程给对象 A 分配了内存空间,但指针还没来得及修改,此时就可能出现另外一个线程使用原来的指针来给对象 B 分配内存空间的情况。想要解决这个问题有两个方案:
\n\n方式一 :采用同步锁定,或采用 CAS 配上失败重试的方式来保证更新操作的原子性。 \n方式二 :为每个线程在 Java 堆中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。线程在进行内存分配时优先使用本地缓冲,当本地缓冲使用完成后,再向 Java 堆申请分配,此时 Java 堆采用同步锁定的方式来保证分配行为的线程安全。 \n \n3. 对象头设置
\n将对象有关的元数据信息、对象的哈希码、分代年龄等信息存储到对象头中。
\n4. 对象初始化
\n调用对象的构造函数,即 Class 文件中的 <init>()
来初始化对象,为相关字段赋值。
\n3.2 对象的内存布局 \n在 HotSpot 虚拟机中,对象在堆内存中的存储布局可以划分为以下三个部分:
\n1. 对象头 (Header)
\n对象头包括两部分信息:
\n\nMark Word :对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,官方统称为 Mark Word 。 \n类型指针 :对象指向它类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的示例。需要说明的是并非所有的虚拟机都必须要在对象数据上保留类型指针,这取决于对象的访问定位方式(详见下文)。 \n \n2. 实例数据 (Instance Data)
\n即我们在程序代码中定义的各种类型的字段的内容,无论是从父类继承而来,还是子类中定义的都需要记录。
\n3. 对其填充 (Padding)
\n主要起占位符的作用。HotSpot 虚拟机要求对象起始地址必须是 8 字节的整倍数,即间接要求了任何对象的大小都必须是 8 字节的整倍数。对象头部分在设计上就是 8 字节的整倍数,如果对象的实例数据不是 8 字节的整倍数,则由对齐填充进行补全。
\n3.3 对象的访问定位 \n对象创建后,Java 程序就可以通过栈上的 reference
来操作堆上的具体对象。《Java 虚拟机规范》规定 reference
是一个指向对象的引用,但并未规定其具体实现方式。主流的方式方式有以下两种:
\n\n句柄访问 :Java 堆将划分出一块内存来作为句柄池, reference
中存储的是对象的句柄地址,而句柄则包含了对象实例数据和类型数据的地址信息。 \n指针访问 :reference
中存储的直接就是对象地址,而对象的类型数据则由上文介绍的对象头中的类型指针来指定。 \n \n通过句柄访问对象:
\n\n通过直接指针访问对象:
\n\n句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 reference
本生并不需要修改;指针访问则反之,由于其 reference
中存储的直接就是对象地址,所以当对象移动时, reference
需要被修改。但针对只需要访问对象本身的场景,指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作,所以这类减少的效果会非常显著,基于这个原因,HotSpot 主要使用的是指针访问的方式。
\n四、垃圾收集算法 \n在 Java 虚拟机内存模型中,程序计数器、虚拟机栈、本地方法栈这 3 个区域都是线程私有的,会随着线程的结束而销毁,因此在这 3 个区域当中,无需过多考虑垃圾回收问题。垃圾回收问题主要发生在 Java 堆和方法区上。
\n4.1 Java 堆回收 \n在 Java 堆上,垃圾回收的主要内容是死亡对象(不可能再被任何途径使用的对象)。而判断对象是否死亡有以下两种方法:
\n1. 引用计数法 \n在对象中添加一个引用计数器,对象每次被引用时,该计数器加一;当引用失效时,计数器的值减一;只要计数器的值为零,则代表对应的对象不可能再被使用。该方法的缺点在于无法避免相互循环引用的问题:
\nobjA.instance = objB\nobjB.instance = objA \nobjA = null ;\nobjB = null ;\nSystem.gc();\n
\n如上所示,此时两个对象已经不能再被访问,但其互相持有对对方的引用,如果采用引用计数法,则两个对象都无法被回收。
\n2. 可达性分析 \n上面的代码在大多数虚拟机中都能被正确的回收,因为大多数主流的虚拟机都是采用的可达性分析方法来判断对象是否死亡。可达性分析是通过一系列被称为 GC Roots
的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链(Reference Chain),如果某个对象到 GC Roots
间没有任何引用链相连,这代表 GC Roots
到该对象不可达, 此时证明此该对象不可能再被使用。
\n\n在 Java 语言中,固定可作为 GC Roots
的对象包括以下几种:
\n\n在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等; \n在方法区中类静态属性引用的对象,譬如 Java 类中引用类型的静态变量; \n在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用; \n在本地方法栈中的 JNI(即 Native 方法)引用的对象; \nJava 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(如 NullPointException,OutOfMemoryError 等)及系统类加载器; \n所有被同步锁(synchronized 关键字)持有的对象; \n反应 Java 虚拟机内部情况的 JMXBean,JVMTI 中注册的回调,本地代码缓存等。 \n \n除了这些固定的 GC Roots
集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域的不同,还可能会有其他对象 “临时性” 地加入,共同构成完整的 GC Roots
集合。
\n3. 对象引用 \n可达性分析是基于引用链进行判断的,在 JDK 1.2 之后,Java 将引用关系分为以下四类:
\n\n强引用 (Strongly Reference) :最传统的引用,如 Object obj = new Object()
。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。 \n软引用 (Soft Reference) :用于描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常之前,会被列入回收范围内进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存溢出异常。 \n弱引用 (Weak Reference) :用于描述那些非必须的对象,强度比软引用弱。被弱引用关联对象只能生存到下一次垃圾收集发生时,无论当前内存是否足够,弱引用对象都会被回收。 \n虚引用 (Phantom Reference) :最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被回收时收到一个系统通知。 \n \n4. 对象真正死亡 \n要真正宣告一个对象死亡,需要经过至少两次标记过程:
\n\n如果对象在进行可达性分析后发现 GC Roots
不可达,将会进行第一次标记; \n随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalized()
方法。如果对象没有覆盖 finalized()
方法,或者 finalized()
已经被虚拟机调用过,这两种情况都会视为没有必要执行。如果判定结果是有必要执行,此时对象会被放入名为 F-Queue
的队列,等待 Finalizer 线程执行其 finalized()
方法。在这个过程中,收集器会进行第二次小规模的标记,如果对象在 finalized()
方法中重新将自己与引用链上的任何一个对象进行了关联,如将自己(this 关键字)赋值给某个类变量或者对象的成员变量,此时它就实现了自我拯救,则第二次标记会将其移除 “即将回收” 的集合,否则该对象就将被真正回收,走向死亡。 \n \n4.2 方法区回收 \n在 Java 堆上进行对象回收的性价比通常比较高,因为大多数对象都是朝生夕灭的。而方法区由于回收条件比较苛刻,对应的回收性价比通常比较低,主要回收两部分内容:废弃的常量和不再使用的类型。
\n4.3 垃圾收集算法 \n1. 分代收集理论 \n当前大多数虚拟机都遵循 “分代收集” 的理论进行设计,它建立在强弱两个分代假说下:
\n\n弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是朝生夕灭的。 \n强分代假说 (Strong Generational Hypothesis) :熬过越多次垃圾收集过程的对象就越难以消亡。 \n跨带引用假说 (Intergenerational Reference Hypothesis) :基于上面两条假说还可以得出的一条隐含推论:存在相互引用关系的两个对象,应该倾向于同时生存或者同时消亡。 \n \n强弱分代假说奠定了垃圾收集器的设计原则:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄就是对象经历垃圾收集的次数)分配到不同的区域中进行存储。之后如果一个区域中的对象都是朝生夕灭的,那么收集器只需要关注少量对象的存活而不是去标记那些大量将要被回收的对象,此时就能以较小的代价获取较大的空间。最后再将难以消亡的对象集中到一块,根据强分代假说,它们是很难消亡的,因此虚拟机可以使用较低的频率进行回收,这就兼顾了时间和内存空间的开销。
\n2. 回收类型 \n根据分代收集理论,收集范围可以分为以下几种类型:
\n\n部分收集 (Partial GC) :具体分为:\n\n新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集; \n老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集; \n混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。 \n \n \n整堆收集 (Full GC) :收集整个 Java 堆和方法区。 \n \n3. 标记-清除算法 \n它是最基础的垃圾收集算法,收集过程分为两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;也可以反过来,标记存活对象,统一回收所有未被标记的对象。
\n\n它主要有以下两个缺点:
\n\n执行效率不稳定:如果 Java 堆上包含大量需要回收的对象,则需要进行大量标记和清除动作; \n内存空间碎片化:标记清除后会产生大量不连续的空间,从而可能导致无法为大对象分配足够的连续内存。 \n \n4. 标记-复制算法 \n标记-复制算法基于 ”半区复制“ 算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存使用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的那块内存空间一次性清理掉。其优点在于避免了内存空间碎片化的问题,其缺点如下:
\n\n如果内存中多数对象都是存活的,这种算法将产生大量的复制开销; \n浪费内存空间,内存空间变为了原有的一半。 \n \n\n基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 Eden
和 两块较小的 Survivor
空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 Eden
和其中的一块 Survivor
,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 Survivor
上,这样只有 10% 的内存空间会被浪费掉。当 Survivor
空间不足以容纳一次 Minor GC
时,此时由其他内存区域(通常是老年代)来进行分配担保。
\n5. 标记-整理算法 \n标记-整理算法是在标记完成后,让所有存活对象都向内存的一端移动,然后直接清理掉边界以外的内存。其优点在于可以避免内存空间碎片化的问题,也可以充分利用内存空间;其缺点在于根据所使用的收集器的不同,在移动存活对象时可能要全程暂停用户程序:
\n\n五、经典垃圾收集器 \n并行与并发是并发编程中的专有名词,在谈论垃圾收集器的上下文语境中,它们的含义如下:
\n\nHotSpot 虚拟机中一共存在七款经典的垃圾收集器:
\n\n\n注:收集器之间存在连线,则代表它们可以搭配使用。
\n \n5.1 Serial 收集器 \nSerial 收集器是最基础、历史最悠久的收集器,它是一个单线程收集器,在进行垃圾回收时,必须暂停其他所有的工作线程,直到收集结束,这是其主要缺点。它的优点在于单线程避免了多线程复杂的上下文切换,因此在单线程环境下收集效率非常高,由于这个优点,迄今为止,其仍然是 HotSpot 虚拟机在客户端模式下默认的新生代收集器:
\n\n5.2 ParNew 收集器 \n他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收:
\n\n5.3 Parallel Scavenge 收集器 \nParallel Scavenge 也是新生代收集器,基于 标记-复制 算法进行实现,它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值:
\n吞吐量 = \\frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间}\n\nParallel Scavenge 收集器提供两个参数用于精确控制吞吐量:
\n\n-XX:MaxGCPauseMillis :控制最大垃圾收集时间,假设需要回收的垃圾总量不变,那么降低垃圾收集的时间就会导致收集频率变高,所以需要将其设置为合适的值,不能一味减小。 \n-XX:MaxGCTimeRatio :直接用于设置吞吐量大小,它是一个大于 0 小于 100 的整数。假设把它设置为 19,表示此时允许的最大垃圾收集时间占总时间的 5%(即 1/(1+19) );默认值为 99 ,即允许最大 1%( 1/(1+99) )的垃圾收集时间。 \n \n5.4 Serial Old 收集器 \n从名字也可以看出来,它是 Serial 收集器的老年代版本,同样是一个单线程收集器,采用 标记-整理 算法,主要用于给客户端模式下的 HotSpot 虚拟机使用:
\n\n5.5 Paralled Old 收集器 \nParalled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现:
\n\n5.6 CMS 收集器 \nCMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于 标记-清除 算法实现,整个收集过程分为以下四个阶段:
\n\n初始标记 (inital mark) :标记 GC Roots
能直接关联到的对象,耗时短但需要暂停用户线程; \n并发标记 (concurrent mark) :从 GC Roots
能直接关联到的对象开始遍历整个对象图,耗时长但不需要暂停用户线程; \n重新标记 (remark) :采用增量更新算法,对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记,耗时比初始标记稍长且需要暂停用户线程; \n并发清除 (inital sweep) :并发清除掉已经死亡的对象,耗时长但不需要暂停用户线程。 \n \n\n其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下:
\n\n由于涉及并发操作,因此对处理器资源比较敏感。 \n由于是基于 标记-清除 算法实现的,因此会产生大量空间碎片。 \n无法处理浮动垃圾(Floating Garbage):由于并发清除时用户线程还是在继续,所以此时仍然会产生垃圾,这些垃圾就被称为浮动垃圾,只能等到下一次垃圾收集时再进行清理。 \n \n5.7 Garbage First 收集器 \nGarbage First(简称 G1)是一款面向服务端的垃圾收集器,也是 JDK 9 服务端模式下默认的垃圾收集器,它的诞生具有里程碑式的意义。G1 虽然也遵循分代收集理论,但不再以固定大小和固定数量来划分分代区域,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region)。每一个 Region 都可以根据不同的需求来扮演新生代的 Eden
空间、Survivor
空间或者老年代空间,收集器会根据其扮演角色的不同而采用不同的收集策略。
\n\n上面还有一些 Region 使用 H 进行标注,它代表 Humongous,表示这些 Region 用于存储大对象(humongous object,H-obj),即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤:
\n\n初始标记 (Inital Marking) :标记 GC Roots
能直接关联到的对象,并且修改 TAMS(Top at Mark Start)指针的值,让下一阶段用户线程并发运行时,能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针,新分配的对象必须位于这两个指针位置以上,位于这两个指针位置以上的对象默认被隐式标记为存活的,不会纳入回收范围; \n并发标记 (Concurrent Marking) :从 GC Roots
能直接关联到的对象开始遍历整个对象图。遍历完成后,还需要处理 SATB 记录中变动的对象。SATB(snapshot-at-the-beginning,开始阶段快照)能够有效的解决并发标记阶段因为用户线程运行而导致的对象变动,其效率比 CMS 重新标记阶段所使用的增量更新算法效率更高; \n最终标记 (Final Marking) :对用户线程做一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的少量的 STAB 记录。虽然并发标记阶段会处理 SATB 记录,但由于处理时用户线程依然是运行中的,因此依然会有少量的变动,所以需要最终标记来处理; \n筛选回收 (Live Data Counting and Evacuation) :负责更新 Regin 统计数据,按照各个 Regin 的回收价值和成本进行排序,在根据用户期望的停顿时间进行来指定回收计划,可以选择任意多个 Regin 构成回收集。然后将回收集中 Regin 的存活对象复制到空的 Regin 中,再清理掉整个旧的 Regin 。此时因为涉及到存活对象的移动,所以需要暂停用户线程,并由多个收集线程并行执行。 \n \n\n5.8 内存分配原则 \n1. 对象优先在 Eden 分配 \n大多数情况下,对象在新生代的 Eden
区中进行分配,当 Eden
区没有足够空间时,虚拟机将进行一次 Minor GC。
\n2. 大对象直接进入老年代 \n大对象就是指需要大量连续内存空间的 Java 对象,最典型的就是超长的字符串或者元素数量很多的数组,它们将直接进入老年代。主要是因为如果在新生代分配,因为其需要大量连续的内存空间,可能会导致提前触发垃圾回收;并且由于新生代的垃圾回收本身就很频繁,此时复制大对象也需要额外的性能开销。
\n3. 长期存活的对象将进入老年代 \n虚拟机会给每个对象在其对象头中定义一个年龄计数器。对象通常在 Eden
区中诞生,如果经历第一次 Minor GC 后仍然存活,并且能够被 Survivor 容纳的话,该对象就会被移动到 Survivor 中,并将其年龄加 1。对象在 Survivor 中每经过一次 Minor GC,年龄就加 1,当年龄达到一定程度后(由 -XX:MaxTenuringThreshold
设置,默认值为 15)就会进入老年代中。
\n4. 动态年龄判断 \n如果在 Survivor 空间中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等待年龄到达 -XX:MaxTenuringThreshold
设置的值。
\n5. 空间担保分配 \n在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果条件成立,那么这一次的 Minor GC 可以确认是安全的。如果不成立,虚拟机会查看 -XX:HandlePromotionFailure
的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于或者 -XX:HandlePromotionFailure
的值设置不允许冒险,那么就要改为进行一次 Full GC 。
\n六、虚拟机类加载机制 \nJava 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的类加载机制。
\n6.1 类加载时机 \n一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、卸载、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接:
\n\n《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化:
\n\n遇到 new
、 getstatic
、 putstatic
、 invokestatic
这四条字节码指令,如果类型进行过初始化,则需要先触发其进行初始化,能够生成这四条指令码的典型 Java 代码场景有:\n\n使用 new
关键字实例化对象时; \n读取或设置一个类型的静态字段时(被 final 修饰,已在编译期把结果放入常量池的静态字段除外); \n调用一个类型的静态方法时。 \n \n \n使用 java.lang.reflect
包的方法对类型进行反射调用时,如果类型没有进行过初始化、则需要触发其初始化; \n当初始化类时,如发现其父类还没有进行过初始化、则需要触发其父类进行初始化; \n当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类; \n当使用 JDK 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle
实例最后解析的结果为 REF_getStatic
, REF_putStatic
, REF_invokeStatic
, REF_newInvokeSpecial
四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化; \n当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那么该接口要在其之前被初始化。 \n \n6.2 类加载过程 \n1. 加载 \n在加载阶段,虚拟机需要完成以下三件事:
\n\n通过一个类的全限定名来获取定义此类的二进制字节流 ; \n将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构; \n在内存中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。 \n \n《Java 虚拟机规范》并没有限制从何处获取二进制流,因此可以从 JAR 包、WAR 包获取,也可以从 JSP 生成的 Class 文件等处获取。
\n2. 验证 \n这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,从而保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证阶段大致会完成下面四项验证:
\n\n文件格式验证 :验证字节流是否符合 Class 文件格式的规范; \n元数据验证 :对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java 语言规范》的要求(如除了 java.lang.Object
外,所有的类都应该有父类); \n字节码验证 :通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的(如允许把子类对象赋值给父类数据类型,但不能把父类对象赋值给子类数据类型); \n符号引用验证 :验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。如果无法验证通过,则会抛出一个java.lang.IncompatibleClassChangeError
的子类异常,如 java.lang.NoSuchFieldError
、 java.lang.NoSuchMethodError
等。 \n \n3. 准备 \n准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段。
\n4. 解析 \n解析是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程:
\n\n符号引用 :符号引用用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。 \n直接引用 :直接引用是指可以直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。 \n \n整个解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行解析。
\n5. 初始化 \n初始化阶段就是执行类构造器的 <clinit>()
方法的过程,该方法具有以下特点:
\n\n<clinit>()
方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生,编译器收集顺序由语句在源文件中出现的顺序决定。 \n<clinit>()
方法与类的构造器函数(即在虚拟机视角中的实例构造器 <init>()
方法)不同,它不需要显示的调用父类的构造器,Java 虚拟机会保证在子类的 <clinit>()
方法执行前,父类的 <clinit>()
方法已经执行完毕。 \n由于父类的 <clinit>()
方法先执行,也就意味着父类中定义的静态语句块要优先于子类变量的赋值操作。 \n<clinit>()
方法对于类或者接口不是必须的,如果一个类中没有静态语句块,也没有对变量进行赋值操作,那么编译器可以不为这个类生成 <clinit>()
方法。 \n接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>()
方法。 \nJava 虚拟机必须保证一个类的 <clinit>()
方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的 <clinit>()
方法,其他线程都需要阻塞等待。 \n \n6.3 类加载器 \n能够通过一个类的全限定名来获取描述该类的二进制字节流的工具称为类加载器。每一个类加载器都拥有一个独立的类名空间,因此对于任意一个类,都必须由加载它的类加载器和这个类本身来共同确立其在 Java 虚拟机中的唯一性。这意味着要想比较两个类是否相等,必须在同一类加载器加载的前提下;如果两个类的类加载器不同,则它们一定不相等。
\n6.4 双亲委派模型 \n从 Java 虚拟机角度而言,类加载器可以分为以下两类:
\n\n启动类加载器 :启动类加载器(Bootstrap ClassLoader)由 C++ 语言实现(以 HotSpot 为例),它是虚拟机自身的一部分; \n其他所有类的类加载器 :由 Java 语言实现,独立存在于虚拟机外部,并且全部继承自 java.lang.ClassLoader
。 \n \n从开发人员角度而言,类加载器可以分为以下三类:
\n\n启动类加载器 (Boostrap Class Loader) :负责把存放在 <JAVA_HOME>\\lib
目录中,或被 -Xbootclasspath
参数所指定的路径中存放的能被 Java 虚拟机识别的类库加载到虚拟机的内存中; \n扩展类加载器 (Extension Class Loader) :负责加载 <JAVA_HOME>\\lib\\ext
目录中,或被 java.ext.dirs
系统变量所指定的路径中的所有类库。 \n应用程序类加载器 (Application Class Loader) :负责加载用户类路径(ClassPath)上的所有的类库。 \n \nJDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成加载:
\n\n上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。
\n双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类,否则就有可能出现一个程序中存在两个不同的 java.lang.Object
的情况。
\n6.5 模块化下的类加载器 \nJDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
\n\n仍维持三层类加载器和双亲委派的架构,但扩展类加载器被平台类加载器所取代; \n当平台及应用程序类加载器收到类加载请求时,要首先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载; \n启动类加载器、平台类加载器、应用程序类加载器全部继承自 java.internal.loader.BuiltinClassLoader
,BuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。 \n \n\n七、程序编译 \n7.1 编译器分类 \n\n前端编译器 :把 *.java
文件转变成 .class
文件的过程;如 JDK 的 Javac,Eclipse JDT 中的增量式编译器。 \n即使编译器 :常称为 JIT 编译器(Just In Time Complier),在运行期把字节码转变成本地机器码的过程;如 HotSpot 虚拟机中的 C1、C2 编译器,Graal 编译器。 \n提前编译器 :直接把程序编译成目标机器指令集相关的二进制代码的过程。如 JDK 的 jaotc,GUN Compiler for the Java(GCJ),Excelsior JET 。 \n \n7.2 解释器与编译器 \n在 HotSpot 虚拟机中,Java 程序最初都是通过解释器(Interpreter)进行解释执行的,其优点在于可以省去编译时间,让程序快速启动。当程序启动后,如果虚拟机发现某个方法或代码块的运行特别频繁,就会使用编译器将其编译为本地机器码,并使用各种手段进行优化,从而提高执行效率,这就是即时编译器。HotSpot 内置了两个(或三个)即时编译器:
\n\n客户端编译器 (Client Complier) :简称 C1; \n服务端编译器 (Servier Complier) :简称 C2,在有的资料和 JDK 源码中也称为 Opto 编译器; \nGraal 编译器 :在 JDK 10 时才出现,长期目标是替代 C2。 \n \n在分层编译的工作模式出现前,采用客户端编译器还是服务端编译器完全取决于虚拟机是运行在客户端模式还是服务端模式下,可以在启动时通过 -client
或 -server
参数进行指定,也可以让虚拟机根据自身版本和宿主机性能来自主选择。
\n7.3 分层编译 \n要编译出优化程度越高的代码通常都需要越长的编译时间,为了在程序启动速度与运行效率之间达到最佳平衡,HotSpot 虚拟机在编译子系统中加入了分层编译(Tiered Compilation):
\n\n第 0 层 :程序纯解释执行,并且解释器不开启性能监控功能; \n第 1 层 :使用客户端编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启性能监控功能; \n第 2 层 :仍然使用客户端编译执行,仅开启方法及回边次数统计等有限的性能监控; \n第 3 层 :仍然使用客户端编译执行,开启全部性能监控; \n第 4 层 :使用服务端编译器将字节码编译为本地代码,其耗时更长,并且会根据性能监控信息进行一些不可靠的激进优化。 \n \n以上层次并不是固定不变的,根据不同的运行参数和版本,虚拟机可以调整分层的数量。各层次编译之间的交互转换关系如下图所示:
\n\n实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。
\n7.4 热点探测 \n即时编译器编译的目标是 “热点代码”,它主要分为以下两类:
\n\n被多次调用的方法。 \n被多次执行循环体。这里指的是一个方法只被少量调用过,但方法体内部存在循环次数较多的循环体,此时也认为是热点代码。但编译器编译的仍然是循环体所在的方法,而不会单独编译循环体。 \n \n判断某段代码是否是热点代码的行为称为 “热点探测” (Hot Spot Code Detection),主流的热点探测方法有以下两种:
\n\n基于采样的热点探测 (Sample Based Hot Spot Code Detection) :采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶,如果发现某个(或某些)方法经常出现在栈顶,那么就认为它是 “热点方法”。 \n基于计数的热点探测 (Counter Based Hot Spot Code Detection) :采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是 “热点方法”。 \n \n八、代码优化 \n即时编译器除了将字节码编译为本地机器码外,还会对代码进行一定程度的优化,它包含多达几十种优化技术,这里选取其中代表性的四种进行介绍:
\n8.1 方法内联 \n最重要的优化手段,它会将目标方法中的代码原封不动地 “复制” 到发起调用的方法之中,避免发生真实的方法调用,并采用名为类型继承关系分析(Class Hierarchy Analysis,CHA)的技术来解决虚方法(Java 语言中默认的实例方法都是虚方法)的内联问题。
\n8.2 逃逸分析 \n逃逸行为主要分为以下两类:
\n\n方法逃逸 :当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,此时称为方法逃逸; \n线程逃逸 :当一个对象在方法里面被定义后,它可能被外部线程所访问,例如赋值给可以在其他线程中访问的实例变量,此时称为线程,其逃逸程度高于方法逃逸。 \n \npublic static StringBuilder concat (String... strings) {\n StringBuilder sb = new StringBuilder();\n for (String string : strings) {\n sb.append(string);\n }\n return sb; \n}\n\npublic static String concat (String... strings) {\n StringBuilder sb = new StringBuilder();\n for (String string : strings) {\n sb.append(string);\n }\n return sb.toString(); \n}\n
\n如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可以为这个对象实例采取不同程序的优化:
\n\n栈上分配 (Stack Allocations) :如果一个对象不会逃逸到线程外,那么将会在栈上分配内存来创建这个对象,而不是 Java 堆上,此时对象所占用的内存空间就会随着栈帧的出栈而销毁,从而可以减轻垃圾回收的压力。 \n标量替换 (Scalar Replacement) :如果一个数据已经无法再分解成为更小的数据类型,那么这些数据就称为标量(如 int、long 等数值类型及 reference 类型等);反之,如果一个数据可以继续分解,那它就被称为聚合量(如对象)。如果一个对象不会逃逸外方法外,那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代,从而减少内存占用。 \n同步消除 (Synchronization Elimination) :如果一个变量不会逃逸出线程,那么对这个变量实施的同步措施就可以消除掉。 \n \n8.3 公共子表达式消除 \n如果一个表达式 E 之前已经被计算过了,并且从先前的计算到现在 E 中所有变量的值都没有发生过变化,那么 E 这次的出现就称为公共子表达式。对于这种表达式,无需再重新进行计算,只需要直接使用前面的计算结果即可。
\n8.4 数组边界检查消除 \n对于虚拟机执行子系统来说,每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中,并且使用循环变量来访问数据,即循环变量的取值永远在 [0,list.length) 之间,那么此时就可以消除整个循环的数据边界检查,从而避免多次无用的判断。
\n参考资料 \n\n",
+ "link": "\\en-us\\blog\\java\\javavm.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/java/springAnnotation.html b/en-us/blog/java/springAnnotation.html
new file mode 100644
index 0000000..b4f6dd3
--- /dev/null
+++ b/en-us/blog/java/springAnnotation.html
@@ -0,0 +1,220 @@
+
+
+
+
+
+
+
+
+
+ springAnnotation
+
+
+
+
+ Spring 中的 18 个注解
+1 @Controller
+标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.
+@Controller
+public class TestController {
+
+ public String test (Map<String,Object> map) {
+ return "hello" ;
+ }
+}
+
+2 @RestController
+Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
+3 @Service
+用于标注业务层组件,说白了就是加入你有一个用注解的方式把这个类注入到spring配置中
+4 @Autowired
+用来装配bean,都可以写在字段上,或者方法上。
+默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:
+@Autowired (required=false )
+
+5 @RequestMapping
+类定义处: 提供初步的请求映射信息,相对于 WEB 应用的根目录。
+方法处: 提供进一步的细分映射信息,相对于类定义处的 URL。
+6 @RequestParam
+用于将请求参数区数据映射到功能处理方法的参数上
+例如
+public Resp test (@RequestParam Integer id) {
+ return Resp.success(customerInfoService.fetch(id));
+}
+
+这个id就是要接收从接口传递过来的参数id的值的,如果接口传递过来的参数名和你接收的不一致,也可以如下
+public Resp test (@RequestParam(value="course_id" ) Integer id) {
+ return Resp.success(customerInfoService.fetch(id));
+}
+
+其中course_id就是接口传递的参数,id就是映射course_id的参数名
+7 @ModelAttribute
+使用地方如下:
+
+标记在方法上
+
+标记在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中。
+(1). 在有返回的方法上:当ModelAttribute设置了value,方法返回的值会以这个value为key,以参数接受到的值作为value,存入到Model中,如下面的方法执行之后,最终相当于
+model.addAttribute("user_name", name);假如 @ModelAttribute没有自定义value,则相当于
+model.addAttribute("name", name);
+@ModelAttribute (value="user_name" )
+public String before (@RequestParam(required = false ) String Name,Model model) {
+ System.out.println("name is " +name);
+}
+
+(2) 在没返回的方法上:
+需要手动model.add方法
+@ModelAttribute
+public void before (@RequestParam(required = false ) Integer age,Model model) {
+ model.addAttribute("age" ,age);
+ System.out.println("age is " +age);
+}
+
+
+标记在方法的参数上
+
+标记在方法的参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用.我们在上面的类中加入一个方法如下
+
+public Resp model (@ModelAttribute("user_name" ) String user_name,
+ @ModelAttribute ("name" ) String name,
+ @ModelAttribute ("age" ) Integer age,Model model) {
+ System.out.println("user_name=" +user_name+" name=" +name+" age=" +age);
+ System.out.println("model=" +model);
+ }
+
+用在方法参数中的@ModelAttribute注解,实际上是一种接受参数并且自动放入Model对象中,便于使用。
+8 @Cacheable
+用来标记缓存查询。可用用于方法或者类中,当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。
+参数列表
+
+
+
+参数
+解释
+例子
+
+
+
+
+value
+名称
+@Cacheable(value={"c1","c2"})
+
+
+key
+key
+@Cacheable(value="c1",key="#id")
+
+
+condition
+条件
+@Cacheable(value="c1",condition="#id=1")
+
+
+
+比如@Cacheable(value="UserCache") 标识的是当调用了标记了这个注解的方法时,逻辑默认加上从缓存中获取结果的逻辑,如果缓存中没有数据,则执行用户编写查询逻辑,查询成功之后,同时将结果放入缓存中。
+但凡说到缓存,都是key-value的形式的,因此key就是方法中的参数(id),value就是查询的结果,而命名空间UserCache是在spring*.xml中定义。
+@Cacheable (value="UserCache" )
+public int getUserAge (int id) {
+ int age=getAgeById(id);
+ return age;
+}
+
+9 @CacheEvict
+@CacheEvict用来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。
+@CacheEvict(value=”UserCache”)
+参数列表
+
+
+
+参数
+解释
+例子
+
+
+
+
+value
+名称
+@CacheEvict(value={"c1","c2"})
+
+
+key
+key
+@CacheEvict(value="c1",key="#id")
+
+
+condition
+缓存得条件可为空
+
+
+
+allEntries
+是否清空所有内容
+@CacheEvict(value="c1",allEntries=true)
+
+
+beforeInvocation
+是否在方法执行前清空
+@CacheEvict(value="c1",beforeInvocation=true)
+
+
+
+10 @Resource
+@Resource的作用相当于@Autowired
+只不过@Autowired按byType自动注入,
+而@Resource默认按 byName自动注入罢了。
+@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
+@Resource装配顺序:
+1、如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
+2、如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
+3、如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
+4、如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
+11 @PostConstruct
+@PostConstruct用来标记是在项目启动的时候执行这个方法。用来修饰一个非静态的void()方法
+也就是spring容器启动时就执行,多用于一些全局配置、数据字典之类的加载
+被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行之后执行
+12 @PreDestory
+被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前
+13 @Repository
+@Repository用于标注数据访问组件,即DAO组件
+14 @Component
+@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
+15 @Scope
+@Scope用来配置 spring bean 的作用域,它标识 bean 的作用域。
+默认值是单例
+1、singleton:单例模式,全局有且仅有一个实例
+2、prototype:原型模式,每次获取Bean的时候会有一个新的实例
+3、request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
+4、session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
+5、global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。
+16 @SessionAttributes
+默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中
+参数:
+1、names:这是一个字符串数组。里面应写需要存储到session中数据的名称。
+2、types:根据指定参数的类型,将模型中对应类型的参数存储到session中
+3、value:和names是一样的。
+@Controller
+@SessionAttributes (value={"names" },types={Integer.class})
+public class ScopeService {
+ @RequestMapping ("/testSession" )
+ public String test (Map<String,Object> map) {
+ map.put("names" ,Arrays.asList("a" ,"b" ,"c" ));
+ map.put("age" ,12 );
+ return "hello" ;
+ }
+}
+
+17 @Required
+适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。
+18 @Qualifier
+@Qualifier当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/java/springAnnotation.json b/en-us/blog/java/springAnnotation.json
new file mode 100644
index 0000000..bef2061
--- /dev/null
+++ b/en-us/blog/java/springAnnotation.json
@@ -0,0 +1,6 @@
+{
+ "filename": "springAnnotation.md",
+ "__html": "Spring 中的 18 个注解 \n1 @Controller \n标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.
\n@Controller \npublic class TestController {\n\n public String test (Map<String,Object> map) {\n return \"hello\" ;\n }\n}\n
\n2 @RestController \nSpring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
\n3 @Service \n用于标注业务层组件,说白了就是加入你有一个用注解的方式把这个类注入到spring配置中
\n4 @Autowired \n用来装配bean,都可以写在字段上,或者方法上。
\n默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:
\n@Autowired (required=false )\n
\n5 @RequestMapping \n类定义处: 提供初步的请求映射信息,相对于 WEB 应用的根目录。
\n方法处: 提供进一步的细分映射信息,相对于类定义处的 URL。
\n6 @RequestParam \n用于将请求参数区数据映射到功能处理方法的参数上
\n例如
\npublic Resp test (@RequestParam Integer id) {\n return Resp.success(customerInfoService.fetch(id));\n}\n
\n这个id就是要接收从接口传递过来的参数id的值的,如果接口传递过来的参数名和你接收的不一致,也可以如下
\npublic Resp test (@RequestParam(value=\"course_id\" ) Integer id) {\n return Resp.success(customerInfoService.fetch(id));\n}\n
\n其中course_id就是接口传递的参数,id就是映射course_id的参数名
\n7 @ModelAttribute \n使用地方如下:
\n\n标记在方法上 \n \n标记在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中。
\n(1). 在有返回的方法上:当ModelAttribute设置了value,方法返回的值会以这个value为key,以参数接受到的值作为value,存入到Model中,如下面的方法执行之后,最终相当于
\nmodel.addAttribute("user_name", name);假如 @ModelAttribute没有自定义value,则相当于
\nmodel.addAttribute("name", name);
\n@ModelAttribute (value=\"user_name\" )\npublic String before (@RequestParam(required = false ) String Name,Model model) {\n System.out.println(\"name is \" +name);\n}\n
\n(2) 在没返回的方法上:
\n需要手动model.add方法
\n@ModelAttribute \npublic void before (@RequestParam(required = false ) Integer age,Model model) {\n model.addAttribute(\"age\" ,age);\n System.out.println(\"age is \" +age);\n}\n
\n\n标记在方法的参数上 \n \n标记在方法的参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用.我们在上面的类中加入一个方法如下
\n\npublic Resp model (@ModelAttribute(\"user_name\" ) String user_name,\n @ModelAttribute (\"name\" ) String name,\n @ModelAttribute (\"age\" ) Integer age,Model model) {\n System.out.println(\"user_name=\" +user_name+\" name=\" +name+\" age=\" +age);\n System.out.println(\"model=\" +model);\n }\n
\n用在方法参数中的@ModelAttribute注解,实际上是一种接受参数并且自动放入Model对象中,便于使用。
\n8 @Cacheable \n用来标记缓存查询。可用用于方法或者类中,当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。
\n参数列表
\n\n\n\n参数 \n解释 \n例子 \n \n \n\n\nvalue \n名称 \n@Cacheable(value={"c1","c2"}) \n \n\nkey \nkey \n@Cacheable(value="c1",key="#id") \n \n\ncondition \n条件 \n@Cacheable(value="c1",condition="#id=1") \n \n \n
\n比如@Cacheable(value="UserCache") 标识的是当调用了标记了这个注解的方法时,逻辑默认加上从缓存中获取结果的逻辑,如果缓存中没有数据,则执行用户编写查询逻辑,查询成功之后,同时将结果放入缓存中。
\n但凡说到缓存,都是key-value的形式的,因此key就是方法中的参数(id),value就是查询的结果,而命名空间UserCache是在spring*.xml中定义。
\n@Cacheable (value=\"UserCache\" )\npublic int getUserAge (int id) {\n int age=getAgeById(id);\n return age;\n}\n
\n9 @CacheEvict \n@CacheEvict用来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。
\n@CacheEvict(value=”UserCache”)
\n参数列表
\n\n\n\n参数 \n解释 \n例子 \n \n \n\n\nvalue \n名称 \n@CacheEvict(value={"c1","c2"}) \n \n\nkey \nkey \n@CacheEvict(value="c1",key="#id") \n \n\ncondition \n缓存得条件可为空 \n \n \n\nallEntries \n是否清空所有内容 \n@CacheEvict(value="c1",allEntries=true) \n \n\nbeforeInvocation \n是否在方法执行前清空 \n@CacheEvict(value="c1",beforeInvocation=true) \n \n \n
\n10 @Resource \n@Resource的作用相当于@Autowired
\n只不过@Autowired按byType自动注入,
\n而@Resource默认按 byName自动注入罢了。
\n@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
\n@Resource装配顺序:
\n1、如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
\n2、如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
\n3、如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
\n4、如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
\n11 @PostConstruct \n@PostConstruct用来标记是在项目启动的时候执行这个方法。用来修饰一个非静态的void()方法
\n也就是spring容器启动时就执行,多用于一些全局配置、数据字典之类的加载
\n被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行之后执行
\n12 @PreDestory \n被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前
\n13 @Repository \n@Repository用于标注数据访问组件,即DAO组件
\n14 @Component \n@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
\n15 @Scope \n@Scope用来配置 spring bean 的作用域,它标识 bean 的作用域。
\n默认值是单例
\n1、singleton:单例模式,全局有且仅有一个实例
\n2、prototype:原型模式,每次获取Bean的时候会有一个新的实例
\n3、request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
\n4、session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
\n5、global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。
\n16 @SessionAttributes \n默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中
\n参数:
\n1、names:这是一个字符串数组。里面应写需要存储到session中数据的名称。
\n2、types:根据指定参数的类型,将模型中对应类型的参数存储到session中
\n3、value:和names是一样的。
\n@Controller \n@SessionAttributes (value={\"names\" },types={Integer.class})\npublic class ScopeService {\n @RequestMapping (\"/testSession\" )\n public String test (Map<String,Object> map) {\n map.put(\"names\" ,Arrays.asList(\"a\" ,\"b\" ,\"c\" ));\n map.put(\"age\" ,12 );\n return \"hello\" ;\n }\n}\n
\n17 @Required \n适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。
\n18 @Qualifier \n@Qualifier当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
\n",
+ "link": "\\en-us\\blog\\java\\springAnnotation.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/linux/ope.html b/en-us/blog/linux/ope.html
index 7609767..5ff9786 100644
--- a/en-us/blog/linux/ope.html
+++ b/en-us/blog/linux/ope.html
@@ -79,6 +79,84 @@ ssh实现端口转发
记住:前提是先进行秘钥传输。
命令执行完后,访问192.168.1.15:9200端口则真实是访问192.168.1.19:9200端口。
+grep命令进阶
+grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来
+grep搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。
+
+^ #锚定行的开始 如:'^grep'匹配所有以grep开头的行。
+$
+. #匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。
+* #匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。
+.* #一起用代表任意字符。
+[] #匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。
+[^] #匹配一个不在指定范围内的字符
+\(..\) #标记匹配字符,如'\(love\)',love被标记为1。
+\< #锚定单词的开始,如:'\<grep'匹配包含以grep开头的单词的行。
+\> #锚定单词的结束,如'grep\>'匹配包含以grep结尾的单词的行。
+x\{m\} #重复字符x,m次,如:'0\{5\}'匹配包含5个o的行。
+x\{m,\} #重复字符x,至少m次,如:'o\{5,\}'匹配至少有5个o的行。
+x\{m,n\}#重复字符x,至少m次,不多于n次,如:'o\{5,10\}'匹配5--10个o的行。
+\w #匹配文字和数字字符,也就是[A-Za-z0-9],
+\W #\w的反置形式,匹配一个或多个非单词字符,如点号句号等。
+\b #单词锁定符,如: '\bgrep\b'只匹配grep。
+
+
+-n 打印行号
+ grep -n ".*" h.txt 所有打印行号
+ grep -n "root" h.txt 匹配的内容显示行号
+-v 不包括
+-E 表示过滤 多个参数
+ grep -Ev "sshd|network|crond|sysstat|"
+-o:仅打印你需要的东西,默认打印正行
+ grep -o "hello" h.txt
+-i:忽略大小写
+ grep -i "hello" h.txt
+-c: 用于统计文中出现的次数
+--color=auto 过滤字段添加颜色
+ 利用正则打印特定字符
+\b:作为边界符,边界只包含特定字符的行
+ grep "\boldboy\b" /etc/passwd -->只过滤包含oldboy的行
+
+
+egrep: == grep -E 用于显示文件中符合条件的字符
+ env|egrep "USER|MAIL|PWD|LOGNAME"
+ 用的表达式不一样 ,egerp更加规范
+egrep -o "oldboy|hello" h.txt -->仅仅输出 oldboy 和 hello
+
+
+# 查找指定关键字个数
+grep '\bboot\b' logs_bak.txt 【\b单词锁定符,只匹配boot】
+# 输出logs_bak.txt 文件中含有从logs.txt文件中读取出的关键词的内容行
+cat logs_bak.txt
+ cat logs.txt
+ cat logs.txt | grep -nf logs_bak.txt
+# 从多个文件中查找关键词
+grep "omc" /etc/passwd /etc/shadow 【多文件查询时,会用冒号前添加文件名】
+# 打印IP信息
+ifconfig eth0|grep -E "([0-9]{1,3}\.){3}" 【-E 表达式匹配,用小括号括起来表示一个整体】
+# 同时过滤多个关键字
+cat /etc/passwd|grep -E "boy|omc"
+ ==> cat /etc/passwd|egrep "omc|boy" 【用 | 划分多个关键字】
+# 显示当前目录下面以.txt 结尾的文件中的所有包含每个字符串至少有7个连续小写字符的字符串的行
+grep '\w\{7\}' *.txt
+ ==> grep '[a-z]\{7\}' *.txt 【注意特殊字符的转义】
+
+
+# A 查询匹配内容的一行之外,后n行的显示
+ # B 查询匹配内容的一行之外,前n行的显示
+ # C 查询匹配内容的一行之外,显示上下n行
+grep -n 'yum' -A 3 logs_bak.txt
+
diff --git a/en-us/blog/linux/ope.json b/en-us/blog/linux/ope.json
index 1951b1d..6941721 100644
--- a/en-us/blog/linux/ope.json
+++ b/en-us/blog/linux/ope.json
@@ -1,6 +1,6 @@
{
"filename": "ope.md",
- "__html": "#逼格高又实用的 Linux 命令,开发、运维一定要懂!
\n实用的 xargs 命令 \n在平时的使用中,我认为xargs这个命令还是较为重要和方便的。我们可以通过使用这个命令,将命令输出的结果作为参数传递给另一个命令。
\n比如说我们想找出某个路径下以 .conf 结尾的文件,并将这些文件进行分类,那么普通的做法就是先将以 .conf 结尾的文件先找出来,然后输出到一个文件中,接着cat这个文件,并使用file文件分类命令去对输出的文件进行分类。这个普通的方法还的确是略显麻烦,那么这个时候xargs命令就派上用场了。
\n例1:找出 / 目录下以.conf 结尾的文件,并进行文件分类
\n命令:
\nfind / -name *.conf -type f -print | xargs file\n
\n输出结果如下所示:
\n
\n命令或脚本后台运行 \n有时候我们进行一些操作的时候,不希望我们的操作在终端会话断了之后就跟着断了,特别是一些数据库导入导出操作,如果涉及到大数据量的操作,我们不可能保证我们的网络在我们的操作期间不出问题,所以后台运行脚本或者命令对我们来说是一大保障。
\n比如说我们想把数据库的导出操作后台运行,并且将命令的操作输出记录到文件,那么我们可以这么做:
\nnohup mysqldump -uroot -pxxxxx --all-databases > ./alldatabases.sql &(xxxxx是密码)\n
\n当然如果你不想密码明文,你还可以这么做:
\nnohup mysqldump -uroot -p --all-databases > ./alldatabases.sql (后面不加&符号)\n
\n执行了上述命令后,会提示叫你输入密码,输入密码后,该命令还在前台运行,但是我们的目的是后天运行该命令,这个时候你可以按下Ctrl+Z,然后在输入bg就可以达到第一个命令的效果,让该命令后台运行,同时也可以让密码隐蔽输入。
\n命令后台执行的结果会在命令执行的当前目录下留下一个 nohup.out 文件,查看这个文件就知道命令有没有执行报错等信息。
\n找出当前系统内存使用量较高的进程 \n在很多运维的时候,我们发现内存耗用较为严重,那么怎么样才能找出内存消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 4 | head -20\n
\n \n输出的第4列就是内存的耗用百分比。最后一列就是相对应的进程。
\n找出当前系统CPU使用量较高的进程 \n在很多运维的时候,我们发现CPU耗用较为严重,那么怎么样才能找出CPU消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 3 | head -20\n
\n
\n输出的第3列为CPU的耗用百分比,最后一列就是对应的进程。
\n我想大家应该也发现了,sort 命令后的3、4其实就是代表着第3列进行排序、第4列进行排序。
\n同时查看多个日志或数据文件 \n在日常工作中,我们查看日志文件的方式可能是使用tail命令在一个个的终端查看日志文件,一个终端就看一个日志文件。包括我在内也是,但是有时候也会觉得这种方式略显麻烦,其实有个工具叫做 multitail 可以在同一个终端同时查看多个日志文件。
\n首先安装 multitail:
\n# wget ftp://ftp.is.co.za/mirror/ftp.rpmforge.net/redhat/el6/en/x86_64/dag/RPMS/multitail-5.2.9-1.el6.rf.x86_64.rpm\n# yum -y localinstall multitail-5.2.9-1.el6.rf.x86_64.rpm\n
\nmultitail 工具支持文本的高亮显示,内容过滤以及更多你可能需要的功能。
\n如下就来一个有用的例子:
\n此时我们既想查看secure的日志指定过滤关键字输出,又想查看实时的网络ping情况:
\n命令如下:
\n# multitail -e "Accepted" /var/log/secure -l "ping baidu.com"\n
\n
\n是不是很方便?如果平时我们想查看两个日志之间的关联性,可以观察日志输出是否有触发等。如果分开两个终端可能来回进行切换有点浪费时间,这个multitail工具查看未尝不是一个好方法。
\n持续 ping 并将结果记录到日志 \n这个时候你去ping几个包把结果丢出来,人家会反驳你,刚刚那段时间有问题而已,现在业务都恢复正常了,网络肯定正常啊,这个时候估计你要气死。
\n你要是再拿出zabbix等网络监控的数据,这个时候就不太妥当了,zabbix的采集数据间隔你不可能设置成1秒钟1次吧?小编就遇到过这样的问题,结果我通过以下的命令进行了ping监控采集。然后再有人让我背锅的时候,我把出问题时间段的ping数据库截取出来,大家公开谈,结果那次被我叼杠回去了,以后他们都不敢轻易甩锅了,这个感觉好啊。
\n命令:
\nping api.jpush.cn | awk '{ print $0"\\t" strftime("%Y-%m-%d %H:%M:%S",systime()) } ' >> /tmp/jiguang.log &`\n
\n输出的结果会记录到/tmp/jiguang.log 中,每秒钟新增一条ping记录,如下:
\n
\nssh实现端口转发 \n可能很多的朋友都听说过ssh是linux下的远程登录安全协议,就是通俗的远程登录管理服务器。但是应该很少朋友会听说过ssh还可以做端口转发。其实ssh用来做端口转发的功能还是很强大的,下面就来做示范。
\n实例背景:我们公司是有堡垒机的,任何操作均需要在堡垒机上进行,有写开发人员需要访问ELasticSearch的head面板查看集群状态,但是我们并不想将ElasticSearch的9200端口映射出去,依然想通过堡垒机进行访问。
\n所以才会将通往堡垒机(192.168.1.15)的请求转发到服务器ElasticSearch(192.168.1.19)的9200上。
\n例子:\n将发往本机(192.168.1.15)的9200端口访问转发到192.168.1.19的9200端口
\nssh -p 22 -C -f -N -g -L 9200:192.168.1.19:9200 ihavecar@192.168.1.19`\n
\n记住:前提是先进行秘钥传输。
\n命令执行完后,访问192.168.1.15:9200端口则真实是访问192.168.1.19:9200端口。
\n",
+ "__html": "#逼格高又实用的 Linux 命令,开发、运维一定要懂!
\n实用的 xargs 命令 \n在平时的使用中,我认为xargs这个命令还是较为重要和方便的。我们可以通过使用这个命令,将命令输出的结果作为参数传递给另一个命令。
\n比如说我们想找出某个路径下以 .conf 结尾的文件,并将这些文件进行分类,那么普通的做法就是先将以 .conf 结尾的文件先找出来,然后输出到一个文件中,接着cat这个文件,并使用file文件分类命令去对输出的文件进行分类。这个普通的方法还的确是略显麻烦,那么这个时候xargs命令就派上用场了。
\n例1:找出 / 目录下以.conf 结尾的文件,并进行文件分类
\n命令:
\nfind / -name *.conf -type f -print | xargs file\n
\n输出结果如下所示:
\n
\n命令或脚本后台运行 \n有时候我们进行一些操作的时候,不希望我们的操作在终端会话断了之后就跟着断了,特别是一些数据库导入导出操作,如果涉及到大数据量的操作,我们不可能保证我们的网络在我们的操作期间不出问题,所以后台运行脚本或者命令对我们来说是一大保障。
\n比如说我们想把数据库的导出操作后台运行,并且将命令的操作输出记录到文件,那么我们可以这么做:
\nnohup mysqldump -uroot -pxxxxx --all-databases > ./alldatabases.sql &(xxxxx是密码)\n
\n当然如果你不想密码明文,你还可以这么做:
\nnohup mysqldump -uroot -p --all-databases > ./alldatabases.sql (后面不加&符号)\n
\n执行了上述命令后,会提示叫你输入密码,输入密码后,该命令还在前台运行,但是我们的目的是后天运行该命令,这个时候你可以按下Ctrl+Z,然后在输入bg就可以达到第一个命令的效果,让该命令后台运行,同时也可以让密码隐蔽输入。
\n命令后台执行的结果会在命令执行的当前目录下留下一个 nohup.out 文件,查看这个文件就知道命令有没有执行报错等信息。
\n找出当前系统内存使用量较高的进程 \n在很多运维的时候,我们发现内存耗用较为严重,那么怎么样才能找出内存消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 4 | head -20\n
\n \n输出的第4列就是内存的耗用百分比。最后一列就是相对应的进程。
\n找出当前系统CPU使用量较高的进程 \n在很多运维的时候,我们发现CPU耗用较为严重,那么怎么样才能找出CPU消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 3 | head -20\n
\n
\n输出的第3列为CPU的耗用百分比,最后一列就是对应的进程。
\n我想大家应该也发现了,sort 命令后的3、4其实就是代表着第3列进行排序、第4列进行排序。
\n同时查看多个日志或数据文件 \n在日常工作中,我们查看日志文件的方式可能是使用tail命令在一个个的终端查看日志文件,一个终端就看一个日志文件。包括我在内也是,但是有时候也会觉得这种方式略显麻烦,其实有个工具叫做 multitail 可以在同一个终端同时查看多个日志文件。
\n首先安装 multitail:
\n# wget ftp://ftp.is.co.za/mirror/ftp.rpmforge.net/redhat/el6/en/x86_64/dag/RPMS/multitail-5.2.9-1.el6.rf.x86_64.rpm\n# yum -y localinstall multitail-5.2.9-1.el6.rf.x86_64.rpm\n
\nmultitail 工具支持文本的高亮显示,内容过滤以及更多你可能需要的功能。
\n如下就来一个有用的例子:
\n此时我们既想查看secure的日志指定过滤关键字输出,又想查看实时的网络ping情况:
\n命令如下:
\n# multitail -e "Accepted" /var/log/secure -l "ping baidu.com"\n
\n
\n是不是很方便?如果平时我们想查看两个日志之间的关联性,可以观察日志输出是否有触发等。如果分开两个终端可能来回进行切换有点浪费时间,这个multitail工具查看未尝不是一个好方法。
\n持续 ping 并将结果记录到日志 \n这个时候你去ping几个包把结果丢出来,人家会反驳你,刚刚那段时间有问题而已,现在业务都恢复正常了,网络肯定正常啊,这个时候估计你要气死。
\n你要是再拿出zabbix等网络监控的数据,这个时候就不太妥当了,zabbix的采集数据间隔你不可能设置成1秒钟1次吧?小编就遇到过这样的问题,结果我通过以下的命令进行了ping监控采集。然后再有人让我背锅的时候,我把出问题时间段的ping数据库截取出来,大家公开谈,结果那次被我叼杠回去了,以后他们都不敢轻易甩锅了,这个感觉好啊。
\n命令:
\nping api.jpush.cn | awk '{ print $0"\\t" strftime("%Y-%m-%d %H:%M:%S",systime()) } ' >> /tmp/jiguang.log &`\n
\n输出的结果会记录到/tmp/jiguang.log 中,每秒钟新增一条ping记录,如下:
\n
\nssh实现端口转发 \n可能很多的朋友都听说过ssh是linux下的远程登录安全协议,就是通俗的远程登录管理服务器。但是应该很少朋友会听说过ssh还可以做端口转发。其实ssh用来做端口转发的功能还是很强大的,下面就来做示范。
\n实例背景:我们公司是有堡垒机的,任何操作均需要在堡垒机上进行,有写开发人员需要访问ELasticSearch的head面板查看集群状态,但是我们并不想将ElasticSearch的9200端口映射出去,依然想通过堡垒机进行访问。
\n所以才会将通往堡垒机(192.168.1.15)的请求转发到服务器ElasticSearch(192.168.1.19)的9200上。
\n例子:\n将发往本机(192.168.1.15)的9200端口访问转发到192.168.1.19的9200端口
\nssh -p 22 -C -f -N -g -L 9200:192.168.1.19:9200 ihavecar@192.168.1.19`\n
\n记住:前提是先进行秘钥传输。
\n命令执行完后,访问192.168.1.15:9200端口则真实是访问192.168.1.19:9200端口。
\ngrep命令进阶 \ngrep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来
\ngrep搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。
\n\n^ #锚定行的开始 如:'^grep'匹配所有以grep开头的行。 \n$ \n. #匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。\n* #匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。 \n.* #一起用代表任意字符。 \n[] #匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。 \n[^] #匹配一个不在指定范围内的字符\n\\(..\\) #标记匹配字符,如'\\(love\\)',love被标记为1。 \n\\< #锚定单词的开始,如:'\\<grep'匹配包含以grep开头的单词的行。 \n\\> #锚定单词的结束,如'grep\\>'匹配包含以grep结尾的单词的行。 \nx\\{m\\} #重复字符x,m次,如:'0\\{5\\}'匹配包含5个o的行。 \nx\\{m,\\} #重复字符x,至少m次,如:'o\\{5,\\}'匹配至少有5个o的行。 \nx\\{m,n\\}#重复字符x,至少m次,不多于n次,如:'o\\{5,10\\}'匹配5--10个o的行。 \n\\w #匹配文字和数字字符,也就是[A-Za-z0-9],\n\\W #\\w的反置形式,匹配一个或多个非单词字符,如点号句号等。 \n\\b #单词锁定符,如: '\\bgrep\\b'只匹配grep。 \n
\n\n-n 打印行号\n grep -n \".*\" h.txt 所有打印行号\n grep -n \"root\" h.txt 匹配的内容显示行号\n-v 不包括\n-E 表示过滤 多个参数\n grep -Ev \"sshd|network|crond|sysstat|\"\n-o:仅打印你需要的东西,默认打印正行\n grep -o \"hello\" h.txt\n-i:忽略大小写\n grep -i \"hello\" h.txt\n-c: 用于统计文中出现的次数\n--color=auto 过滤字段添加颜色\n 利用正则打印特定字符\n\\b:作为边界符,边界只包含特定字符的行\n grep \"\\boldboy\\b\" /etc/passwd -->只过滤包含oldboy的行\n
\n\negrep: == grep -E 用于显示文件中符合条件的字符\n env|egrep \"USER|MAIL|PWD|LOGNAME\"\n 用的表达式不一样 ,egerp更加规范\negrep -o \"oldboy|hello\" h.txt -->仅仅输出 oldboy 和 hello\n
\n\n# 查找指定关键字个数 \ngrep '\\bboot\\b' logs_bak.txt 【\\b单词锁定符,只匹配boot】\n# 输出logs_bak.txt 文件中含有从logs.txt文件中读取出的关键词的内容行 \ncat logs_bak.txt \n cat logs.txt \n cat logs.txt | grep -nf logs_bak.txt\n# 从多个文件中查找关键词 \ngrep \"omc\" /etc/passwd /etc/shadow 【多文件查询时,会用冒号前添加文件名】\n# 打印IP信息 \nifconfig eth0|grep -E \"([0-9]{1,3}\\.){3}\" 【-E 表达式匹配,用小括号括起来表示一个整体】\n# 同时过滤多个关键字 \ncat /etc/passwd|grep -E \"boy|omc\"\n ==> cat /etc/passwd|egrep \"omc|boy\" 【用 | 划分多个关键字】\n# 显示当前目录下面以.txt 结尾的文件中的所有包含每个字符串至少有7个连续小写字符的字符串的行 \ngrep '\\w\\{7\\}' *.txt\n ==> grep '[a-z]\\{7\\}' *.txt 【注意特殊字符的转义】 \n
\n\n# A 查询匹配内容的一行之外,后n行的显示 \n # B 查询匹配内容的一行之外,前n行的显示\n # C 查询匹配内容的一行之外,显示上下n行\ngrep -n 'yum' -A 3 logs_bak.txt\n
\n",
"link": "\\en-us\\blog\\linux\\ope.html",
"meta": {}
}
\ No newline at end of file
diff --git a/en-us/blog/net/c_sqlserver_nginx.html b/en-us/blog/net/c_sqlserver_nginx.html
new file mode 100644
index 0000000..301a4e1
--- /dev/null
+++ b/en-us/blog/net/c_sqlserver_nginx.html
@@ -0,0 +1,337 @@
+
+
+
+
+
+
+
+
+
+ c_sqlserver_nginx
+
+
+
+
+ ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + sqlserver + Nginx
+一、前言
+在之前的文章(ASP.NET Core 实战:Linux 小白的 .NET Core 部署之路)中,我介绍了如何在 Linux 环境中安装 .NET Core SDK / .NET Core Runtime、Nginx、sqlserver,以及如何将我们的 ASP.NET Core MVC 程序部署到 Linux 上,同时,使用 supervisor 守护程序守护我们的 .NET Core 程序。如果,你有看过那篇文章,并且和我一样是个 Linux 小白用户的话,可能第一感觉就是,把 .NET Core 项目部署在 IIS 上也挺好。
+将 .NET Core 项目部署到 Linux 上如此复杂,就没有简单的部署方式吗?
+你好,有的,Docker 了解一下~~~
+PS:这里的示例代码还是采用之前的毕业设计项目,在这篇文章发布的时候,我已经在程序的仓库中添加了对于 Docker 的支持,你可以下载下来,自己尝试一下,毕竟,实践出真知。
+代码仓储:https://github.com/burningmyself/micro
+二、Step by Step
+1、安装 Docker & Docker Compose
+在代码交付的过程中,偶尔会遇到这样的问题,在本地测试是好的,但是部署到测试环境、生产环境时就出这样那样的问题,同时,因为本地与测试环境、生产环境之间存在差异,我们可能无法在本地复现这些问题,那么,有没有一种工具可以很好的解决这一问题呢?随着历史的车轮不断前行,容器技术诞生了。
+Docker,作为最近几年兴起的一种虚拟化容器技术,他可以将我们的运行程序与操作系统做一个隔离,例如这里我们需要运行 .NET Core 程序,我们不再需要关心底层的操作系统是什么,不需要在每台需要需要运行程序的机器上安装程序运行的各种依赖,我们可以通过程序打包成镜像的方式,将应用程序和该程序的依赖全部置于一个镜像文件中,这时,只要别的机器上有安装 Docker,就可以通过我们打包的这个镜像来运行这个程序。
+1.1、卸载 Docker
+在安装 Docker 之前,我们应该确定当前的机器上是否已经安装好了 Docker,为了防止与现在安装的 Docker CE 发生冲突,这里我们先卸载掉以前版本的 Docker,如果你确定你的机器上并没有安装 Docker 的话此步可以跳过。
+在 Linux 中可以使用 \ 加 Enter 在输入很长很长的语句时进行换行,这里和后面的命令都是采用这样的方式。
+sudo yum remove docker
+docker-client
+docker-client-latest
+docker-common
+docker-latest
+docker-latest-logrotate
+docker-logrotate
+docker-engine
+1.2、添加 yum 源
+在安装 Docker CE 的方式上,我是采用将 Docker CE 的源添加到 yum 源中,之后我们就可以直接使用 yum install 安装 Docker CE,整个的安装过程如下。
+安装工具包从而可以让我们在 yum 中添加别的仓储源
+sudo yum install -y yum-utils \
+ device-mapper-persistent-data \
+ lvm2
+
+设置 docker ce 的稳定库地址
+sudo yum-config-manager \
+ --add-repo \
+ https://download.docker.com/linux/centos/docker-ce.repo
+
+安装 docker ce
+sudo yum install docker-ce docker-ce-cli containerd.io
+
+当我们安装好 Docker 之后,我们就可以使用 docker 命令验证我们是否在机器上成功安装了 Docker,同时,也可以使用 docker --version 命令查看我们安装的 Docker CE 版本。
+
+1.3、设置开机自启
+当 Docker 已经在我们的机器上安装完成后,我们就可以将 Docker 设置成机器的自启服务,这样,如果出现服务器重启的情况下,我们的 Docker 也可以随服务器的重启自动启动 Docker 服务。
+启动 Docker 服务并允许开机自启
+sudo systemctl start docker
+
+查看当前 dokcer 的运行情况
+sudo systemctl status docker
+
+1.4、Hello World
+就像我们在学习一门新的语言时,运行的第一句代码,几乎都是打印出 Hello World,而在 Docker Hub 中,也有这么一个镜像,在无数的 Docker 教程中,安装完 Docker 后,第一件事就是拉取这个镜像文件,“告诉” Docker,我来了。
+Docker Hub 是存放镜像的仓库,里面包含了许多的镜像文件,因为服务器在国外的原因,下载的速度可能不理想,像国内的阿里云、腾讯云也有提供对于 Docker 镜像的加速器服务,你可以按需使用,当然,你也可以创建属于你的私有镜像仓库。
+docker run 命令,它会在我们的本地镜像库中先寻找这个镜像,然后运行。如果在本地没有找到的话,则会自动使用 docker pull 从 Docker Hub 中寻找,能找到的话,则会自动下载到本地,然后运行,找不到的话,这条命令也就运行失败了。
+
+1.5、安装 Docker Compose
+在实际的项目开发中,我们可能会有多个应用镜像,例如在本篇文章的示例中,为了在 Docker 中运行我们的程序,我们需要三个镜像:应用程序自身镜像、sqlserver Server 镜像、以及 Nginx 镜像,为了将我们的程序启动起来,我们需要手敲各个容器的启动参数,环境变量,容器命名,指定不同容器的链接参数等等一系列的操作,又多又烦,可能某一步操作失败后程序就无法正常运行。而当我们使用了 Docker Compose 之后,我们就可以把这些命令一次性写在 docker-compose.yml 配置文件中,以后每次启动我们的应用程序时,只需要通过 docker compose 命令就可以自动帮我们完成这些操作。
+从 github 下载 docker compose 二进制文件
+sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
+
+对下载的二进制文件应用可执行权限
+sudo chmod +x /usr/local/bin/docker-compose
+
+查看 docker compose 版本
+docker-compose --version
+
+
+2、构建程序镜像
+当我们在服务器上安装好 docker 和 docker compose 之后,就可以开始构建我们的程序镜像了。首先我们需要对我们的运行程序添加对于 Docker 的支持。你可以自己手动在 MVC 项目中添加 Dockerfile 文件,或是通过右键添加 Docker 支持。
+
+Dockerfile 就像一个执行的清单,它告诉 Docker,我们这个镜像在构建和运行时需要按照什么样的命令运行。打开 VS 为我们自动创建的 Dockerfile,可以看到清晰的分成了四块的内容。
+
+我们知道,.NET Core 程序的运行需要依赖于 .NET Core Runtime(CoreCLR),因此,为了使我们的程序可以运行起来,我们需要从 hub 中拉取 runtime ,并在 此基础上构建我们的应用镜像。同时,为了避免因为基础的环境的不同造成对程序的影响,这里的 Runtime 需要同程序开发时的 .NET Core SDK 版本保持一致,所以这里我使用的是 .NET Core 3.0 Runtime。
+一个镜像中包含了应用程序及其所有的依赖,与虚拟机不同的是,容器中的每个镜像最终是共享了宿主机的操作系统资源,容器作为用户空间中的独立进程运行在主机操作系统上。
+
+PS:图片版权归属于微软的技术文档,如有侵权,请联系我删除,源文件地址:什么是 Docker?
+镜像可以看成一个个小型的“虚拟主机”,这里我们在镜像中创建了一个 /app 路径作为我们程序在镜像中的工作目录,同时,将 80,443 端口暴露给 Docker,从而可以使我们在镜像外面通过端口访问到当前镜像中的运行的程序。
+FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+因为我们的应用是一个微服架构的应用,最终的项目依赖于解决方案中的各个类库以及我们从 Nuget 中下载的各种第三方组件,在部署时,需要将这些组件打包成 dll 引用。所以,这里我们需要使用 .NET Core SDK 中包含的 .NET Core CLI 进行还原和构建。
+就像在下面的代码中,我们在镜像的内部创建了一个 /src 的路径,将当前解决方案下的类库都复制到这个目录下,之后通过 dotnet restore 命令还原我们的主程序所依赖的各个组件。当我们还原好依赖的组件后,就可以使用 dotnet build 命令生成 Release版本的 dll 文件,同时输出到之前创建的 /app 路径下。
+FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
+WORKDIR /src
+COPY . .
+WORKDIR /src/templates/service/host/Base.IdentityServer
+RUN dotnet restore -nowarn:msb3202,nu1503
+RUN dotnet build --no-restore -c Release -o /app
+
+
+上面一步可以看成我们在使用 VS 生成 Release 版本的解决方案,当生成没有出错之后,我们就可以进行程序的发布。
+FROM build AS publish
+RUN dotnet publish --no-restore -c Release -o /app
+
+当已经生成发布文件之后,按照我们平时部署在 Windows 上的过程,这时就可以通过 IIS 部署运行了,因此,构建我们应用镜像的最后一步就是通过 dotnet 命令执行我们的程序。
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app .
+ENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]
+
+似乎到这一步构建程序镜像就结束了,按照这样流程做的话,就需要我们将整个的解决方案上传到服务器上了,可是,很多时候,我们仅仅是把我们在本地发布好的项目上传到服务器上,这与我们现在的构建流程具有很大的不同,所以这里我们来修改 Dockerfile 文件,从而符合我们的发布流程。
+从上面分析 Dockerfile 的过程中不难看出,在服务器上构建镜像的第二步、第三步就是我们现在在开发环境中手动完成的部分,所以这里,我们只需要对这部分进行删除即可,修改后的 Dockerfile 如下。
+FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster
+WORKDIR /app
+COPY . /app
+EXPOSE 80
+EXPOSE 443
+ENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]
+
+在修改后的 Dockerfile 中,可以看到,我们删去了 build 和 release 的过程,选择直接将我们 Dockerfile 路径下的文件拷贝到镜像中的 /app 路径下,然后直接执行 dotnet 命令,运行我们的程序。
+为了确保 Dockerfile 与发布后的文件处于同一路径下,这里我们需要使用 VS 修改 Dockerfile 的属性值,确保会复制到输出的目录下,这里选择如果较新则复制即可。
+
+3、编写 docker-compose.yml
+当我们构建好应用的镜像,对于 Nginx 和 sqlserver 我们完全可以从 hub 中拉取下来,再执行一些配置即可。所以,我们现在就可以编写 docker compose 文件,来定义我们的应用镜像运行时需要包含的依赖以及每个镜像的启动顺序。
+右键选中 MVC 项目,添加一个 docker-compose.yml 文件,同样的,需要修改该文件的属性,以便于该文件可以复制到输出目录下。注意,这里的文件名和上文的 Dockerfile 都是特定的,你不能做任何的修改。如果你的电脑上已经安装了 Docker for Windows,你也可以使用 VS,右键添加,选中容器业务流程协调程序支持自动对 docker compose 进行配置。
+
+在 yml 文件中,我定义了三个镜像:AdminApiGateway.Host、Base.IdentityServer、Base.HttpApi.Host。三个镜像的定义中有许多相同的地方,都设置了自动重启(restart),以及都处于同一个桥接网络下(psu-net)从而达到镜像间的通信。
+sqlserver 是 SqlServer 的镜像,我们通过环境变量 SA_PASSWORD 设置了 SqlServer 的数据库连接密码,并通过挂载卷的方式将镜像中的数据库文件持久化到我们的服务器本地路径中。同时,将镜像的 1433 端口映射到服务器的 1433 端口上。
+AdminApiGateway.Host 则是我们的程序后台网关镜像,采用位于 /data/dotnet/AdminApiGateway/ 路径下的 Dockerfile 文件进行构建的,因为主程序的运行需要依赖于数据库,所以这里采用 depends_on 属性,使我们的应用镜像依赖于 sqlserver 镜像,即,在 sqlserver 启动后才会启动应用镜像。
+nginx 则是我们的 nginx 镜像,这里将镜像中的 80 端口和 443 端口都映射到服务器 IP 上,因为我们需要配置 Nginx 从而监听我们的程序,所以通过挂载卷的方式,将本地的 nginx.conf 配置文件用配置映射到镜像中。同时,因为我们在构建应用镜像的 Dockerfile 文件时,对外暴露了 80,443 端口,所以这里就可以通过 links 属性进行监听(如果构建时未暴露端口,你可以在 docker compose 文件中通过 Expose 属性暴露镜像中的端口)。
+Nginx 的配置文件如下,这里特别需要注意文件的格式,缩进,一点小错误都可能导致镜像无法正常运行。如果你和我一样将 nginx.conf 放到程序运行路径下的,别忘了修改文件的属性。
+user nginx;
+worker_processes 1;
+
+error_log /var/log/nginx/error_log.log warn;
+pid /var/run/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ gzip on; #开启gzip
+ gzip_disable "msie6"; #IE6不使用gzip
+ gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"
+ gzip_proxied any; #代理结果数据的压缩
+ gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值
+ gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果
+ gzip_http_version 1.1; #识别http协议的版本
+ gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩
+ gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩
+
+ include /etc/nginx/conf.d/*.conf;
+
+ server {
+ #nginx同时开启http和https
+ listen 80 default backlog=2048;
+ listen 443 ssl;
+ server_name ysf.djtlpay.com;
+
+ ssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;
+ ssl_certificate_key /ssl/2_ysf.djtlpay.com.key;
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Host $http_host;
+ proxy_cache_bypass $http_upgrade;
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+ }
+}
+
+一个完整的 docker compose 文件如下,包含了三个镜像以及一个桥接网络。
+version: '3.0'
+
+services:
+ # 指定服务名称
+ sqlserver:
+ # 指定服务使用的镜像
+ image: mcr.microsoft.com/mssql/server
+ # 指定容器名称
+ container_name: sqlserver
+ # 指定服务运行的端口
+ ports:
+ - "1433"
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /data/sqlserver:/var/opt/mssql
+ # 挂断自动重新启动
+ restart: always
+ environment:
+ - TZ=Asia/Shanghai
+ - SA_PASSWORD=mssql-MSSQL
+ - ACCEPT_EULA=Y
+ # 指定容器运行的用户为root
+ user:
+ root
+ # 指定服务名称
+ redis:
+ # 指定服务使用的镜像
+ image: redis
+ # 指定容器名称
+ container_name: redis
+ # 指定服务运行的端口
+ ports:
+ - 6379:6379
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/redis:/data
+ - /data/redis/redis.conf:/etc/redis.conf
+ # 挂断自动重新启动
+ restart: always
+ # 指定容器执行命令
+ command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes
+ # 指定容器的环境变量
+ environment:
+ - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致
+ # 指定服务名称
+ mongo:
+ # 指定服务使用的镜像
+ image: mongo
+ # 指定容器名称
+ container_name: mongo
+ # 指定服务运行的端口
+ ports:
+ - 27017:27017
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mongodb/db:/data/db
+ - /data/mongodb/configdb:/data/configdb
+ - /data/mongodb/initdb:/docker-entrypoint-initdb.d
+ # 挂断自动重新启动
+ restart: always
+ # 指定容器的环境变量
+ environment:
+ - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致
+ - AUTH=yes
+ - MONGO_INITDB_ROOT_USERNAME=admin
+ - MONGO_INITDB_ROOT_PASSWORD=admin
+ nginx:
+ # 指定服务使用的镜像
+ image: nginx
+ # 指定容器名称
+ container_name: nginx
+ # 指定服务运行的端口
+ ports:
+ - 80:80
+ - 443:443
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /etc/localtime:/etc/localtime
+ # 挂断自动重新启动
+ restart: always
+
+ AdminApiGateway.Host:
+ image: 'volosoft/microservice-demo-public-website-gateway:${TAG:-latest}'
+ build:
+ context: ../
+ dockerfile: micro/gateways/AdminApiGateway.Host/Dockerfile
+ depends_on:
+ - sqlserver
+ - redis
+ - mongo
+
+ Base.IdentityServer:
+ image: 'Base.IdentityServer:${TAG:-latest}'
+ build:
+ context: ../
+ dockerfile: micro/modules/base/host/Base.IdentityServer/Dockerfile
+ depends_on:
+ - sqlserver
+ - redis
+ - mongo
+ - AdminApiGateway.Host
+
+ Base.HttpApi.Host:
+ image: 'Base.HttpApi.Host:${TAG:-latest}'
+ build:
+ context: ../
+ dockerfile: micro/modules/base/host/Base.HttpApi.Host/Dockerfile
+ depends_on:
+ - sqlserver
+ - redis
+ - mongo
+ - AdminApiGateway.Host
+ - Base.IdentityServer
+
+这里需要注意,所有有用到镜像间的通信的地方,我们都需要使用镜像名进行指代,例如上面的 nginx 的配置文件中,我们需要将监听的地址改为镜像名称,以及,我们需要修改程序的数据库访问字符串的服务器地址
+4、发布部署程序
+当我们构建好 docker compose 文件后就可以把整个文件上传到服务器上进行构建 docker 镜像了。这里我将所有的部署文件放在服务器的 /data/wwwroot/micro/ 路径下,这时我们就可以通过 docker compose 命令进行镜像构建。
+定位到部署文件在的位置,我们可以直接使用下面的命令进行镜像的(重新)构建,启动,并链接一个服务相关的容器,整个过程都会在后台运行,如果你希望看到整个过程的话,你可以去掉 -d 参数。
+执行镜像构建,启动
+docker-compose up -d
+
+当 up 命令执行完成后,我们就可以通过 ps 命令查看正在运行的容器,若有的容器并没有运行起来,则可以使用 logs 查看容器的运行日志从而进行排错。
+查看所有正在运行的容器
+docker-compose ps
+
+显示容器运行日志
+docker-compose logs
+
+三、总结
+本章主要是介绍了如何通过 docker 容器,完整的部署一个可实际使用的 .NET Core 的单体应用,相比于之前通过 Linux 部署 .NET Core 应用,可以看到整个步骤少了很多,也简单很多。文中涉及到了一些 docker 的命令,如果你之前并没有接触过 docker 的话,可能需要你进一步的了解。当我们将程序打包成一个镜像之后,你完全可以将镜像上传到私有镜像仓库中,或是直接打包成镜像的压缩文件,这样,当需要切换部署环境时,只需要获取到这个镜像之后即可快速完成部署,相比之前,极大的方便了我们的工作。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/net/c_sqlserver_nginx.json b/en-us/blog/net/c_sqlserver_nginx.json
new file mode 100644
index 0000000..d374695
--- /dev/null
+++ b/en-us/blog/net/c_sqlserver_nginx.json
@@ -0,0 +1,6 @@
+{
+ "filename": "c_sqlserver_nginx.md",
+ "__html": "ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + sqlserver + Nginx \n一、前言 \n在之前的文章(ASP.NET Core 实战:Linux 小白的 .NET Core 部署之路)中,我介绍了如何在 Linux 环境中安装 .NET Core SDK / .NET Core Runtime、Nginx、sqlserver,以及如何将我们的 ASP.NET Core MVC 程序部署到 Linux 上,同时,使用 supervisor 守护程序守护我们的 .NET Core 程序。如果,你有看过那篇文章,并且和我一样是个 Linux 小白用户的话,可能第一感觉就是,把 .NET Core 项目部署在 IIS 上也挺好。
\n将 .NET Core 项目部署到 Linux 上如此复杂,就没有简单的部署方式吗?
\n你好,有的,Docker 了解一下~~~
\nPS:这里的示例代码还是采用之前的毕业设计项目,在这篇文章发布的时候,我已经在程序的仓库中添加了对于 Docker 的支持,你可以下载下来,自己尝试一下,毕竟,实践出真知。
\n代码仓储:https://github.com/burningmyself/micro
\n二、Step by Step \n1、安装 Docker & Docker Compose \n在代码交付的过程中,偶尔会遇到这样的问题,在本地测试是好的,但是部署到测试环境、生产环境时就出这样那样的问题,同时,因为本地与测试环境、生产环境之间存在差异,我们可能无法在本地复现这些问题,那么,有没有一种工具可以很好的解决这一问题呢?随着历史的车轮不断前行,容器技术诞生了。
\nDocker,作为最近几年兴起的一种虚拟化容器技术,他可以将我们的运行程序与操作系统做一个隔离,例如这里我们需要运行 .NET Core 程序,我们不再需要关心底层的操作系统是什么,不需要在每台需要需要运行程序的机器上安装程序运行的各种依赖,我们可以通过程序打包成镜像的方式,将应用程序和该程序的依赖全部置于一个镜像文件中,这时,只要别的机器上有安装 Docker,就可以通过我们打包的这个镜像来运行这个程序。
\n1.1、卸载 Docker \n在安装 Docker 之前,我们应该确定当前的机器上是否已经安装好了 Docker,为了防止与现在安装的 Docker CE 发生冲突,这里我们先卸载掉以前版本的 Docker,如果你确定你的机器上并没有安装 Docker 的话此步可以跳过。
\n在 Linux 中可以使用 \\ 加 Enter 在输入很长很长的语句时进行换行,这里和后面的命令都是采用这样的方式。
\nsudo yum remove docker \ndocker-client \ndocker-client-latest \ndocker-common \ndocker-latest \ndocker-latest-logrotate \ndocker-logrotate \ndocker-engine
\n1.2、添加 yum 源 \n在安装 Docker CE 的方式上,我是采用将 Docker CE 的源添加到 yum 源中,之后我们就可以直接使用 yum install 安装 Docker CE,整个的安装过程如下。
\n安装工具包从而可以让我们在 yum 中添加别的仓储源
\nsudo yum install -y yum-utils \\\n device-mapper-persistent-data \\\n lvm2\n
\n设置 docker ce 的稳定库地址
\nsudo yum-config-manager \\\n --add-repo \\\n https://download.docker.com/linux/centos/docker-ce.repo\n
\n安装 docker ce
\nsudo yum install docker-ce docker-ce-cli containerd.io\n
\n当我们安装好 Docker 之后,我们就可以使用 docker 命令验证我们是否在机器上成功安装了 Docker,同时,也可以使用 docker --version 命令查看我们安装的 Docker CE 版本。
\n
\n1.3、设置开机自启 \n当 Docker 已经在我们的机器上安装完成后,我们就可以将 Docker 设置成机器的自启服务,这样,如果出现服务器重启的情况下,我们的 Docker 也可以随服务器的重启自动启动 Docker 服务。
\n启动 Docker 服务并允许开机自启
\nsudo systemctl start docker\n
\n查看当前 dokcer 的运行情况
\nsudo systemctl status docker\n
\n1.4、Hello World \n就像我们在学习一门新的语言时,运行的第一句代码,几乎都是打印出 Hello World,而在 Docker Hub 中,也有这么一个镜像,在无数的 Docker 教程中,安装完 Docker 后,第一件事就是拉取这个镜像文件,“告诉” Docker,我来了。
\nDocker Hub 是存放镜像的仓库,里面包含了许多的镜像文件,因为服务器在国外的原因,下载的速度可能不理想,像国内的阿里云、腾讯云也有提供对于 Docker 镜像的加速器服务,你可以按需使用,当然,你也可以创建属于你的私有镜像仓库。
\ndocker run 命令,它会在我们的本地镜像库中先寻找这个镜像,然后运行。如果在本地没有找到的话,则会自动使用 docker pull 从 Docker Hub 中寻找,能找到的话,则会自动下载到本地,然后运行,找不到的话,这条命令也就运行失败了。
\n
\n1.5、安装 Docker Compose \n在实际的项目开发中,我们可能会有多个应用镜像,例如在本篇文章的示例中,为了在 Docker 中运行我们的程序,我们需要三个镜像:应用程序自身镜像、sqlserver Server 镜像、以及 Nginx 镜像,为了将我们的程序启动起来,我们需要手敲各个容器的启动参数,环境变量,容器命名,指定不同容器的链接参数等等一系列的操作,又多又烦,可能某一步操作失败后程序就无法正常运行。而当我们使用了 Docker Compose 之后,我们就可以把这些命令一次性写在 docker-compose.yml 配置文件中,以后每次启动我们的应用程序时,只需要通过 docker compose 命令就可以自动帮我们完成这些操作。
\n从 github 下载 docker compose 二进制文件
\nsudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose\n
\n对下载的二进制文件应用可执行权限
\nsudo chmod +x /usr/local/bin/docker-compose\n
\n查看 docker compose 版本
\ndocker-compose --version\n
\n
\n2、构建程序镜像 \n当我们在服务器上安装好 docker 和 docker compose 之后,就可以开始构建我们的程序镜像了。首先我们需要对我们的运行程序添加对于 Docker 的支持。你可以自己手动在 MVC 项目中添加 Dockerfile 文件,或是通过右键添加 Docker 支持。
\n
\nDockerfile 就像一个执行的清单,它告诉 Docker,我们这个镜像在构建和运行时需要按照什么样的命令运行。打开 VS 为我们自动创建的 Dockerfile,可以看到清晰的分成了四块的内容。
\n
\n我们知道,.NET Core 程序的运行需要依赖于 .NET Core Runtime(CoreCLR),因此,为了使我们的程序可以运行起来,我们需要从 hub 中拉取 runtime ,并在 此基础上构建我们的应用镜像。同时,为了避免因为基础的环境的不同造成对程序的影响,这里的 Runtime 需要同程序开发时的 .NET Core SDK 版本保持一致,所以这里我使用的是 .NET Core 3.0 Runtime。
\n一个镜像中包含了应用程序及其所有的依赖,与虚拟机不同的是,容器中的每个镜像最终是共享了宿主机的操作系统资源,容器作为用户空间中的独立进程运行在主机操作系统上。\n
\nPS:图片版权归属于微软的技术文档,如有侵权,请联系我删除,源文件地址:什么是 Docker?
\n镜像可以看成一个个小型的“虚拟主机”,这里我们在镜像中创建了一个 /app 路径作为我们程序在镜像中的工作目录,同时,将 80,443 端口暴露给 Docker,从而可以使我们在镜像外面通过端口访问到当前镜像中的运行的程序。
\nFROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n
\n因为我们的应用是一个微服架构的应用,最终的项目依赖于解决方案中的各个类库以及我们从 Nuget 中下载的各种第三方组件,在部署时,需要将这些组件打包成 dll 引用。所以,这里我们需要使用 .NET Core SDK 中包含的 .NET Core CLI 进行还原和构建。
\n就像在下面的代码中,我们在镜像的内部创建了一个 /src 的路径,将当前解决方案下的类库都复制到这个目录下,之后通过 dotnet restore 命令还原我们的主程序所依赖的各个组件。当我们还原好依赖的组件后,就可以使用 dotnet build 命令生成 Release版本的 dll 文件,同时输出到之前创建的 /app 路径下。
\nFROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build\nWORKDIR /src\nCOPY . .\nWORKDIR /src/templates/service/host/Base.IdentityServer\nRUN dotnet restore -nowarn:msb3202,nu1503\nRUN dotnet build --no-restore -c Release -o /app\n\n
\n上面一步可以看成我们在使用 VS 生成 Release 版本的解决方案,当生成没有出错之后,我们就可以进行程序的发布。
\nFROM build AS publish\nRUN dotnet publish --no-restore -c Release -o /app\n
\n当已经生成发布文件之后,按照我们平时部署在 Windows 上的过程,这时就可以通过 IIS 部署运行了,因此,构建我们应用镜像的最后一步就是通过 dotnet 命令执行我们的程序。
\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app .\nENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]\n
\n似乎到这一步构建程序镜像就结束了,按照这样流程做的话,就需要我们将整个的解决方案上传到服务器上了,可是,很多时候,我们仅仅是把我们在本地发布好的项目上传到服务器上,这与我们现在的构建流程具有很大的不同,所以这里我们来修改 Dockerfile 文件,从而符合我们的发布流程。
\n从上面分析 Dockerfile 的过程中不难看出,在服务器上构建镜像的第二步、第三步就是我们现在在开发环境中手动完成的部分,所以这里,我们只需要对这部分进行删除即可,修改后的 Dockerfile 如下。
\nFROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster\nWORKDIR /app\nCOPY . /app \nEXPOSE 80\nEXPOSE 443\nENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]\n
\n在修改后的 Dockerfile 中,可以看到,我们删去了 build 和 release 的过程,选择直接将我们 Dockerfile 路径下的文件拷贝到镜像中的 /app 路径下,然后直接执行 dotnet 命令,运行我们的程序。
\n为了确保 Dockerfile 与发布后的文件处于同一路径下,这里我们需要使用 VS 修改 Dockerfile 的属性值,确保会复制到输出的目录下,这里选择如果较新则复制即可。
\n
\n3、编写 docker-compose.yml \n当我们构建好应用的镜像,对于 Nginx 和 sqlserver 我们完全可以从 hub 中拉取下来,再执行一些配置即可。所以,我们现在就可以编写 docker compose 文件,来定义我们的应用镜像运行时需要包含的依赖以及每个镜像的启动顺序。
\n右键选中 MVC 项目,添加一个 docker-compose.yml 文件,同样的,需要修改该文件的属性,以便于该文件可以复制到输出目录下。注意,这里的文件名和上文的 Dockerfile 都是特定的,你不能做任何的修改。如果你的电脑上已经安装了 Docker for Windows,你也可以使用 VS,右键添加,选中容器业务流程协调程序支持自动对 docker compose 进行配置。
\n
\n在 yml 文件中,我定义了三个镜像:AdminApiGateway.Host、Base.IdentityServer、Base.HttpApi.Host。三个镜像的定义中有许多相同的地方,都设置了自动重启(restart),以及都处于同一个桥接网络下(psu-net)从而达到镜像间的通信。
\nsqlserver 是 SqlServer 的镜像,我们通过环境变量 SA_PASSWORD 设置了 SqlServer 的数据库连接密码,并通过挂载卷的方式将镜像中的数据库文件持久化到我们的服务器本地路径中。同时,将镜像的 1433 端口映射到服务器的 1433 端口上。
\nAdminApiGateway.Host 则是我们的程序后台网关镜像,采用位于 /data/dotnet/AdminApiGateway/ 路径下的 Dockerfile 文件进行构建的,因为主程序的运行需要依赖于数据库,所以这里采用 depends_on 属性,使我们的应用镜像依赖于 sqlserver 镜像,即,在 sqlserver 启动后才会启动应用镜像。
\nnginx 则是我们的 nginx 镜像,这里将镜像中的 80 端口和 443 端口都映射到服务器 IP 上,因为我们需要配置 Nginx 从而监听我们的程序,所以通过挂载卷的方式,将本地的 nginx.conf 配置文件用配置映射到镜像中。同时,因为我们在构建应用镜像的 Dockerfile 文件时,对外暴露了 80,443 端口,所以这里就可以通过 links 属性进行监听(如果构建时未暴露端口,你可以在 docker compose 文件中通过 Expose 属性暴露镜像中的端口)。
\nNginx 的配置文件如下,这里特别需要注意文件的格式,缩进,一点小错误都可能导致镜像无法正常运行。如果你和我一样将 nginx.conf 放到程序运行路径下的,别忘了修改文件的属性。
\nuser nginx;\nworker_processes 1;\n\nerror_log /var/log/nginx/error_log.log warn;\npid /var/run/nginx.pid;\n\n\nevents {\n worker_connections 1024;\n}\n\n\nhttp {\n include /etc/nginx/mime.types;\n default_type application/octet-stream;\n\n log_format main '$remote_addr - $remote_user [$time_local] "$request" '\n '$status $body_bytes_sent "$http_referer" '\n '"$http_user_agent" "$http_x_forwarded_for"';\n\n access_log /var/log/nginx/access.log main;\n\n sendfile on;\n #tcp_nopush on;\n\n keepalive_timeout 65;\n\n #gzip on;\n\n gzip on; #开启gzip\n gzip_disable "msie6"; #IE6不使用gzip\n gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"\n gzip_proxied any; #代理结果数据的压缩\n gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值\n gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果\n gzip_http_version 1.1; #识别http协议的版本\n gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩\n gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩\n\n include /etc/nginx/conf.d/*.conf;\n\n server {\n #nginx同时开启http和https\n listen 80 default backlog=2048;\n listen 443 ssl;\n server_name ysf.djtlpay.com;\n\n ssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;\n ssl_certificate_key /ssl/2_ysf.djtlpay.com.key;\n\n location / {\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Host $http_host;\n proxy_cache_bypass $http_upgrade;\n root /usr/share/nginx/html;\n index index.html index.htm;\n }\n } \n}\n
\n一个完整的 docker compose 文件如下,包含了三个镜像以及一个桥接网络。
\nversion: '3.0'\n\nservices:\n # 指定服务名称\n sqlserver:\n # 指定服务使用的镜像\n image: mcr.microsoft.com/mssql/server\n # 指定容器名称\n container_name: sqlserver\n # 指定服务运行的端口\n ports:\n - "1433"\n # 指定容器中需要挂载的文件 \n volumes:\n - /data/sqlserver:/var/opt/mssql\n # 挂断自动重新启动 \n restart: always\n environment:\n - TZ=Asia/Shanghai\n - SA_PASSWORD=mssql-MSSQL\n - ACCEPT_EULA=Y\n # 指定容器运行的用户为root\n user:\n root\n # 指定服务名称\n redis:\n # 指定服务使用的镜像\n image: redis\n # 指定容器名称\n container_name: redis\n # 指定服务运行的端口\n ports:\n - 6379:6379\n # 指定容器中需要挂载的文件\n volumes:\n - /etc/localtime:/etc/localtime\n - /data/redis:/data\n - /data/redis/redis.conf:/etc/redis.conf\n # 挂断自动重新启动\n restart: always\n # 指定容器执行命令\n command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes\n # 指定容器的环境变量\n environment:\n - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致\n # 指定服务名称\n mongo:\n # 指定服务使用的镜像\n image: mongo\n # 指定容器名称\n container_name: mongo\n # 指定服务运行的端口\n ports:\n - 27017:27017\n # 指定容器中需要挂载的文件\n volumes:\n - /etc/localtime:/etc/localtime\n - /data/mongodb/db:/data/db\n - /data/mongodb/configdb:/data/configdb\n - /data/mongodb/initdb:/docker-entrypoint-initdb.d \n # 挂断自动重新启动\n restart: always\n # 指定容器的环境变量\n environment:\n - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致\n - AUTH=yes\n - MONGO_INITDB_ROOT_USERNAME=admin\n - MONGO_INITDB_ROOT_PASSWORD=admin\n nginx:\n # 指定服务使用的镜像\n image: nginx\n # 指定容器名称\n container_name: nginx\n # 指定服务运行的端口\n ports:\n - 80:80\n - 443:443\n # 指定容器中需要挂载的文件\n volumes:\n - /etc/localtime:/etc/localtime\n # 挂断自动重新启动\n restart: always\n\n AdminApiGateway.Host:\n image: 'volosoft/microservice-demo-public-website-gateway:${TAG:-latest}'\n build:\n context: ../\n dockerfile: micro/gateways/AdminApiGateway.Host/Dockerfile\n depends_on:\n - sqlserver\n - redis\n - mongo \n\n Base.IdentityServer:\n image: 'Base.IdentityServer:${TAG:-latest}'\n build:\n context: ../\n dockerfile: micro/modules/base/host/Base.IdentityServer/Dockerfile\n depends_on:\n - sqlserver\n - redis\n - mongo\n - AdminApiGateway.Host\n\n Base.HttpApi.Host:\n image: 'Base.HttpApi.Host:${TAG:-latest}'\n build:\n context: ../\n dockerfile: micro/modules/base/host/Base.HttpApi.Host/Dockerfile\n depends_on:\n - sqlserver\n - redis\n - mongo\n - AdminApiGateway.Host\n - Base.IdentityServer \n
\n这里需要注意,所有有用到镜像间的通信的地方,我们都需要使用镜像名进行指代,例如上面的 nginx 的配置文件中,我们需要将监听的地址改为镜像名称,以及,我们需要修改程序的数据库访问字符串的服务器地址
\n4、发布部署程序 \n当我们构建好 docker compose 文件后就可以把整个文件上传到服务器上进行构建 docker 镜像了。这里我将所有的部署文件放在服务器的 /data/wwwroot/micro/ 路径下,这时我们就可以通过 docker compose 命令进行镜像构建。
\n定位到部署文件在的位置,我们可以直接使用下面的命令进行镜像的(重新)构建,启动,并链接一个服务相关的容器,整个过程都会在后台运行,如果你希望看到整个过程的话,你可以去掉 -d 参数。
\n执行镜像构建,启动
\ndocker-compose up -d\n
\n当 up 命令执行完成后,我们就可以通过 ps 命令查看正在运行的容器,若有的容器并没有运行起来,则可以使用 logs 查看容器的运行日志从而进行排错。
\n查看所有正在运行的容器
\ndocker-compose ps\n
\n显示容器运行日志
\ndocker-compose logs\n
\n三、总结 \n本章主要是介绍了如何通过 docker 容器,完整的部署一个可实际使用的 .NET Core 的单体应用,相比于之前通过 Linux 部署 .NET Core 应用,可以看到整个步骤少了很多,也简单很多。文中涉及到了一些 docker 的命令,如果你之前并没有接触过 docker 的话,可能需要你进一步的了解。当我们将程序打包成一个镜像之后,你完全可以将镜像上传到私有镜像仓库中,或是直接打包成镜像的压缩文件,这样,当需要切换部署环境时,只需要获取到这个镜像之后即可快速完成部署,相比之前,极大的方便了我们的工作。
\n",
+ "link": "\\en-us\\blog\\net\\c_sqlserver_nginx.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/sql/mybatis.html b/en-us/blog/sql/mybatis.html
new file mode 100644
index 0000000..3bfd5b3
--- /dev/null
+++ b/en-us/blog/sql/mybatis.html
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+ mybatis
+
+
+
+
+ Mybatis使用心德
+什么是Mybatis?
+
+
+Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
+
+
+MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
+
+
+通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
+
+
+Mybaits的优点:
+
+
+基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
+
+
+与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
+
+
+很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
+
+
+能够与Spring很好的集成;
+
+
+提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
+
+
+MyBatis框架的缺点:
+
+
+SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
+
+
+SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
+
+
+MyBatis框架适用场合:
+
+
+MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
+
+
+对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
+
+
+MyBatis与Hibernate有哪些不同?
+
+
+Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
+
+
+Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
+
+
+Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
+
+
+#{}和${}的区别是什么?
+
+
+#{}是预编译处理,${}是字符串替换。
+
+
+Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
+
+
+Mybatis在处理{}时,就是把{}替换成变量的值。
+
+
+使用#{}可以有效的防止SQL注入,提高系统安全性。
+
+
+当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
+第1种:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
+<select id = ”selectorder” parametertype = ”int” resultetype = ”me.gacl.domain.order” >
+ select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
+</select >
+
+第2种:通过 来映射字段名和实体类属性名的一一对应的关系。
+<select id ="getOrder" parameterType ="int" resultMap ="orderresultmap" >
+select * from orders where order_id=#{id}
+</select >
+<resultMap type =”me.gacl.domain.order” id =”orderresultmap” >
+ <!–用id属性来映射主键字段– >
+ <id property =”id” column =”order_id” >
+ <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性– >
+ <result property = “orderno” column =”order_no”/ >
+ <result property =”price” column =”order_price” />
+</reslutMap >
+
+模糊查询like语句该怎么写?
+第1种:在Java代码中添加sql通配符。
+string wildcardname = “%smi%”;
+list<name > names = mapper.selectlike(wildcardname);
+<select id =”selectlike” >
+select * from foo where bar like #{value}
+</select >
+
+第2种:在sql语句中拼接通配符,会引起sql注入
+string wildcardname = “smi”;
+list<name > names = mapper.selectlike(wildcardname);
+<select id =”selectlike” >
+select * from foo where bar like "%"#{value}"%"
+</select >
+
+通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
+Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
+Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个 <select>、<insert>、<update>、<delete>
标签,都会被解析为一个MapperStatement对象。
+举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为 com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。
+Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
+Mybatis是如何进行分页的?分页插件的原理是什么?
+Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
+分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
+Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
+第一种是使用 标签,逐一定义数据库列名和对象属性名之间的映射关系。
+第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
+有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
+如何执行批量插入?
+首先,创建一个简单的insert语句:
+<insert id =”insertname” >
+insert into names (name) values (#{value})
+</insert >
+
+然后在java代码中像下面这样执行批处理插入:
+list < string > names = new arraylist();
+names.add(“fred”);
+names.add(“barney”);
+names.add(“betty”);
+names.add(“wilma”);
+
+sqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);
+try {
+ namemapper mapper = sqlsession.getmapper(namemapper.class);
+ for (string name: names) {
+ mapper.insertname(name);
+ }
+ sqlsession.commit();
+} catch (Exception e) {
+ e.printStackTrace();
+ sqlSession.rollback();
+ throw e;
+}
+finally {
+ sqlsession.close();
+}
+
+如何获取自动生成的(主)键值?
+insert 方法总是返回一个int值 ,这个值代表的是插入的行数。
+如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
+示例:
+<insert id=”insertname” usegeneratedkeys=”true ” keyproperty=”id”>
+ insert into names (name) values (#{name})
+</insert>
+name name = new name();
+name.setname(“fred”);
+int rows = mapper.insertname(name);
+
+system.out.println(“rows inserted = ” + rows);
+system.out.println(“generated key value = ” + name.getid());
+
+在mapper中如何传递多个参数?
+
+第一种:
+DAO层的函数
+
+
+<select id ="selectUser" resultMap ="BaseResultMap" >
+ select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}
+</select >
+
+
+第二种:使用 @param 注解:
+
+
+public interface usermapper {
+ user selectuser (@param(“username”) string username,@param (“hashedpassword”) string hashedpassword) ;
+}
+
+然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
+<select id =”selectuser” resulttype =”user” >
+ select id, username, hashedpassword
+ from some_table
+ where username = #{username}
+ and hashedpassword = #{hashedpassword}
+</select >
+
+
+第三种:多个参数封装成map
+
+try {
+
+
+ Map < String, Object > map = new HashMap();
+ map.put("start" , start);
+ map.put("end" , end);
+ return sqlSession.selectList("StudentID.pagination" , map);
+} catch (Exception e) {
+ e.printStackTrace();
+ sqlSession.rollback();
+ throw e;
+} finally {
+ MybatisUtil.closeSqlSession();
+
+Mybatis动态sql有什么用?执行原理?有哪些动态sql?
+Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
+Mybatis提供了9种动态sql标签:
+trim|where|set|foreach|if|choose|when|otherwise|bind。
+Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
+<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>
,加上动态sql的9个标签,其中 <sql>
为sql片段标签,通过 <include>
标签引入sql片段, <selectKey>
为不支持自增的主键生成策略标签。
+Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
+不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
+原因就是namespace+id是作为Map <String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
+为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
+Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
+一对一、一对多的关联查询 ?
+<mapper namespace ="com.lcb.mapping.userMapper" >
+
+ <select id ="getClass" parameterType ="int" resultMap ="ClassesResultMap" >
+ select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
+ </select >
+ <resultMap type ="com.lcb.user.Classes" id ="ClassesResultMap" >
+
+ <id property ="id" column ="c_id" />
+ <result property ="name" column ="c_name" />
+ <association property ="teacher" javaType ="com.lcb.user.Teacher" >
+ <id property ="id" column ="t_id" />
+ <result property ="name" column ="t_name" />
+ </association >
+ </resultMap >
+
+ <select id ="getClass2" parameterType ="int" resultMap ="ClassesResultMap2" >
+ select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}
+ </select >
+ <resultMap type ="com.lcb.user.Classes" id ="ClassesResultMap2" >
+ <id property ="id" column ="c_id" />
+ <result property ="name" column ="c_name" />
+ <association property ="teacher" javaType ="com.lcb.user.Teacher" >
+ <id property ="id" column ="t_id" />
+ <result property ="name" column ="t_name" />
+ </association >
+
+
+ <collection property ="student" ofType ="com.lcb.user.Student" >
+ <id property ="id" column ="s_id" />
+ <result property ="name" column ="s_name" />
+ </collection >
+ </resultMap >
+</mapper >
+
+MyBatis实现一对一有几种方式?具体怎么操作的?
+有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
+嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
+MyBatis实现一对多有几种方式,怎么操作的?
+有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
+Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
+Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
+它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
+当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
+Mybatis的一级、二级缓存:
+1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
+2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
+3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
+什么是MyBatis的接口绑定?有哪些实现方式?
+接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
+接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
+使用MyBatis的mapper接口调用时有哪些要求?
+1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
+2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
+3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
+4、Mapper.xml文件中的namespace即是mapper接口的类路径。
+Mapper编写有哪几种方式?
+接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。
+1、在sqlMapConfig.xml中配置mapper.xml的位置
+<mappers >
+ <mapper resource ="mapper.xml文件的地址" />
+ <mapper resource ="mapper.xml文件的地址" />
+</mappers >
+
+2、定义mapper接口
+3、实现类集成SqlSessionDaoSupportmapper方法中可以this.getSqlSession()进行数据增删改查。
+4、spring 配置
+<bean id =" " class ="mapper接口的实现" >
+ <property name ="sqlSessionFactory" ref ="sqlSessionFactory" > </property >
+</bean >
+
+第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:
+1、在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
+<mappers >
+ <mapper resource ="mapper.xml文件的地址" />
+ <mapper resource ="mapper.xml文件的地址" />
+</mappers >
+
+2、定义mapper接口:
+mapper.xml中的namespace为mapper接口的地址
+mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致
+Spring中定义
+<bean id ="" class ="org.mybatis.spring.mapper.MapperFactoryBean" >
+ <property name ="mapperInterface" value ="mapper接口地址" />
+ <property name ="sqlSessionFactory" ref ="sqlSessionFactory" />
+</bean >
+
+第三种:使用mapper扫描器:
+1、mapper.xml文件编写:
+mapper.xml中的namespace为mapper接口的地址;mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。
+2、定义mapper接口:
+注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录
+3、配置mapper扫描器:
+<bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" >
+ <property name ="basePackage" value ="mapper接口包地址" > </property >
+ <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" />
+</bean >
+
+4、使用扫描器后从spring容器中获取mapper的实现对象。
+简述Mybatis的插件运行原理,以及如何编写一个插件。
+Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
+编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/sql/mybatis.json b/en-us/blog/sql/mybatis.json
new file mode 100644
index 0000000..6ed1197
--- /dev/null
+++ b/en-us/blog/sql/mybatis.json
@@ -0,0 +1,6 @@
+{
+ "filename": "mybatis.md",
+ "__html": "Mybatis使用心德 \n什么是Mybatis? \n\n\nMybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
\n \n\nMyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
\n \n\n通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
\n \n \nMybaits的优点: \n\n\n基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
\n \n\n与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
\n \n\n很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
\n \n\n能够与Spring很好的集成;
\n \n\n提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
\n \n \nMyBatis框架的缺点: \n\n\nSQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
\n \n\nSQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
\n \n \nMyBatis框架适用场合: \n\n\nMyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
\n \n\n对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
\n \n \nMyBatis与Hibernate有哪些不同? \n\n\nMybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
\n \n\nMybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
\n \n\nHibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
\n \n \n#{}和${}的区别是什么? \n\n\n#{}是预编译处理,${}是字符串替换。
\n \n\nMybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
\n \n\nMybatis在处理{}时,就是把{}替换成变量的值。
\n \n\n使用#{}可以有效的防止SQL注入,提高系统安全性。
\n \n \n当实体类中的属性名和表中的字段名不一样 ,怎么办 ? \n第1种:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
\n<select id = ”selectorder” parametertype = ”int” resultetype = ”me.gacl.domain.order” > \n select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};\n</select > \n
\n第2种:通过 来映射字段名和实体类属性名的一一对应的关系。
\n<select id =\"getOrder\" parameterType =\"int\" resultMap =\"orderresultmap\" > \nselect * from orders where order_id=#{id}\n</select > \n<resultMap type =”me.gacl.domain.order” id =”orderresultmap” > \n <!–用id属性来映射主键字段– > \n <id property =”id” column =”order_id” > \n <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性– > \n <result property = “orderno” column =”order_no”/ > \n <result property =”price” column =”order_price” /> \n</reslutMap > \n
\n模糊查询like语句该怎么写? \n第1种:在Java代码中添加sql通配符。
\nstring wildcardname = “%smi%”;\nlist<name > names = mapper.selectlike(wildcardname);\n<select id =”selectlike” > \nselect * from foo where bar like #{value}\n</select > \n
\n第2种:在sql语句中拼接通配符,会引起sql注入
\nstring wildcardname = “smi”;\nlist<name > names = mapper.selectlike(wildcardname);\n<select id =”selectlike” > \nselect * from foo where bar like \"%\"#{value}\"%\"\n</select > \n
\n通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗? \nDao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
\nMapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个 <select>、<insert>、<update>、<delete>
标签,都会被解析为一个MapperStatement对象。
\n举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为 com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。
\nMapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
\nMybatis是如何进行分页的?分页插件的原理是什么? \nMybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
\n分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
\nMybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? \n第一种是使用 标签,逐一定义数据库列名和对象属性名之间的映射关系。
\n第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
\n有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
\n如何执行批量插入? \n首先,创建一个简单的insert语句:
\n<insert id =”insertname” > \ninsert into names (name) values (#{value})\n</insert > \n
\n然后在java代码中像下面这样执行批处理插入:
\nlist < string > names = new arraylist();\nnames.add(“fred”);\nnames.add(“barney”);\nnames.add(“betty”);\nnames.add(“wilma”);\n\nsqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);\ntry {\n namemapper mapper = sqlsession.getmapper(namemapper.class);\n for (string name: names) {\n mapper.insertname(name);\n }\n sqlsession.commit();\n} catch (Exception e) {\n e.printStackTrace();\n sqlSession.rollback();\n throw e;\n}\nfinally {\n sqlsession.close();\n}\n
\n如何获取自动生成的(主)键值? \ninsert 方法总是返回一个int值 ,这个值代表的是插入的行数。
\n如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
\n示例:
\n<insert id=”insertname” usegeneratedkeys=”true ” keyproperty=”id”>\n insert into names (name) values (#{name}) \n</insert>\nname name = new name();\nname.setname(“fred”);\nint rows = mapper.insertname(name);\n\nsystem.out.println(“rows inserted = ” + rows);\nsystem.out.println(“generated key value = ” + name.getid());\n
\n在mapper中如何传递多个参数? \n\n第一种:\nDAO层的函数 \n \n\n<select id =\"selectUser\" resultMap =\"BaseResultMap\" > \n select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}\n</select > \n
\n\n第二种:使用 @param 注解: \n \n\npublic interface usermapper {\n user selectuser (@param(“username”) string username,@param (“hashedpassword”) string hashedpassword) ;\n}\n
\n然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
\n<select id =”selectuser” resulttype =”user” > \n select id, username, hashedpassword\n from some_table\n where username = #{username}\n and hashedpassword = #{hashedpassword}\n</select > \n
\n\n第三种:多个参数封装成map \n \ntry {\n \n \n Map < String, Object > map = new HashMap();\n map.put(\"start\" , start);\n map.put(\"end\" , end);\n return sqlSession.selectList(\"StudentID.pagination\" , map);\n} catch (Exception e) {\n e.printStackTrace();\n sqlSession.rollback();\n throw e;\n} finally {\n MybatisUtil.closeSqlSession();\n
\nMybatis动态sql有什么用?执行原理?有哪些动态sql? \nMybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
\nMybatis提供了9种动态sql标签:
\ntrim|where|set|foreach|if|choose|when|otherwise|bind。
\nXml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签? \n<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>
,加上动态sql的9个标签,其中 <sql>
为sql片段标签,通过 <include>
标签引入sql片段, <selectKey>
为不支持自增的主键生成策略标签。
\nMybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复? \n不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
\n原因就是namespace+id是作为Map <String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
\n为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? \nHibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
\n一对一、一对多的关联查询 ? \n<mapper namespace =\"com.lcb.mapping.userMapper\" > \n \n <select id =\"getClass\" parameterType =\"int\" resultMap =\"ClassesResultMap\" > \n select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}\n </select > \n <resultMap type =\"com.lcb.user.Classes\" id =\"ClassesResultMap\" > \n \n <id property =\"id\" column =\"c_id\" /> \n <result property =\"name\" column =\"c_name\" /> \n <association property =\"teacher\" javaType =\"com.lcb.user.Teacher\" > \n <id property =\"id\" column =\"t_id\" /> \n <result property =\"name\" column =\"t_name\" /> \n </association > \n </resultMap > \n \n <select id =\"getClass2\" parameterType =\"int\" resultMap =\"ClassesResultMap2\" > \n select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}\n </select > \n <resultMap type =\"com.lcb.user.Classes\" id =\"ClassesResultMap2\" > \n <id property =\"id\" column =\"c_id\" /> \n <result property =\"name\" column =\"c_name\" /> \n <association property =\"teacher\" javaType =\"com.lcb.user.Teacher\" > \n <id property =\"id\" column =\"t_id\" /> \n <result property =\"name\" column =\"t_name\" /> \n </association > \n\n\n <collection property =\"student\" ofType =\"com.lcb.user.Student\" > \n <id property =\"id\" column =\"s_id\" /> \n <result property =\"name\" column =\"s_name\" /> \n </collection > \n </resultMap > \n</mapper > \n
\nMyBatis实现一对一有几种方式?具体怎么操作的? \n有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
\n嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
\nMyBatis实现一对多有几种方式,怎么操作的? \n有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
\nMybatis是否支持延迟加载?如果支持,它的实现原理是什么? \nMybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
\n它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
\n当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
\nMybatis的一级、二级缓存: \n1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
\n2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
\n3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
\n什么是MyBatis的接口绑定?有哪些实现方式? \n接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
\n接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
\n使用MyBatis的mapper接口调用时有哪些要求? \n1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同;\n2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;\n3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;\n4、Mapper.xml文件中的namespace即是mapper接口的类路径。
\nMapper编写有哪几种方式? \n接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。
\n1、在sqlMapConfig.xml中配置mapper.xml的位置
\n<mappers > \n <mapper resource =\"mapper.xml文件的地址\" /> \n <mapper resource =\"mapper.xml文件的地址\" /> \n</mappers > \n
\n2、定义mapper接口
\n3、实现类集成SqlSessionDaoSupportmapper方法中可以this.getSqlSession()进行数据增删改查。
\n4、spring 配置
\n<bean id =\" \" class =\"mapper接口的实现\" > \n <property name =\"sqlSessionFactory\" ref =\"sqlSessionFactory\" > </property > \n</bean > \n
\n第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:\n1、在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
\n<mappers > \n <mapper resource =\"mapper.xml文件的地址\" /> \n <mapper resource =\"mapper.xml文件的地址\" /> \n</mappers > \n
\n2、定义mapper接口:
\nmapper.xml中的namespace为mapper接口的地址\nmapper接口中的方法名和mapper.xml中的定义的statement的id保持一致\nSpring中定义
\n<bean id =\"\" class =\"org.mybatis.spring.mapper.MapperFactoryBean\" > \n <property name =\"mapperInterface\" value =\"mapper接口地址\" /> \n <property name =\"sqlSessionFactory\" ref =\"sqlSessionFactory\" /> \n</bean > \n
\n第三种:使用mapper扫描器:
\n1、mapper.xml文件编写:
\nmapper.xml中的namespace为mapper接口的地址;mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。
\n2、定义mapper接口:
\n注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录
\n3、配置mapper扫描器:
\n<bean class =\"org.mybatis.spring.mapper.MapperScannerConfigurer\" > \n <property name =\"basePackage\" value =\"mapper接口包地址\" > </property > \n <property name =\"sqlSessionFactoryBeanName\" value =\"sqlSessionFactory\" /> \n</bean > \n
\n4、使用扫描器后从spring容器中获取mapper的实现对象。
\n简述Mybatis的插件运行原理,以及如何编写一个插件。 \nMybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
\n编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
\n",
+ "link": "\\en-us\\blog\\sql\\mybatis.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/sql/mysql_backups.html b/en-us/blog/sql/mysql_backups.html
new file mode 100644
index 0000000..24358f9
--- /dev/null
+++ b/en-us/blog/sql/mysql_backups.html
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
+
+
+ mysql_backups
+
+
+
+
+ 数据备份与恢复
+
+一、备份简介
+ 2.1 备份分类
+ 2.2 备份工具
+二、mysqldump
+ 2.1 常用参数
+ 2.2 全量备份
+ 2.3 增量备份
+三、mysqlpump
+ 3.1 功能优势
+ 3.2 常用参数
+四、Xtrabackup
+ 4.1 在线安装
+ 4.2 全量备份
+ 4.3 增量备份
+五、二进制日志的备份
+
+一、备份简介
+2.1 备份分类
+按照不同的思考维度,通常将数据库的备份分为以下几类:
+物理备份 与 逻辑备份
+
+物理备份:备份的是完整的数据库目录和数据文件。采用该模式会进行大量的 IO 操作,但不含任何逻辑转换,因此备份和恢复速度通常都比较快。
+逻辑备份:通过数据库结构和内容信息来进行备份。因为要执行逻辑转换,因此其速度较慢,并且在以文本格式保存时,其输出文件的大小大于物理备份。逻辑备份的还原的粒度可以从服务器级别(所有数据库)精确到具体表,但备份不会包括日志文件、配置文件等与数据库无关的内容。
+
+全量备份 与 增量备份
+
+全量备份:备份服务器在给定时间点上的所有数据。
+增量备份:备份在给定时间跨度内(从一个时间点到另一个时间点)对数据所做的更改。
+
+在线备份 与 离线备份
+
+在线备份:数据库服务在运行状态下进行备份。此时其他客户端依旧可以连接到数据库,但为了保证数据的一致性,在备份期间可能会对数据进行加锁,此时客户端的访问依然会受限。
+离线备份:在数据库服务停机状态下进行备份。此备份过程简单,但由于无法提供对外服务,通常会对业务造成比较大的影响。
+
+2.2 备份工具
+MySQL 支持的备份工具有很多种,这里列出常用的三种:
+
+mysqldump :这是 MySQL 自带的备份工具,其采用的备份方式是逻辑备份,支持全库备份、单库备份、单表备份。由于其采用的是逻辑备份,所以生成的备份文件比物理备份的大,且所需恢复时间也比较长。
+mysqlpump :这是 MySQL 5.7 之后新增的备份工具,在 mysqldump 的基础上进行了功能的扩展,支持多线程备份,支持对备份文件进行压缩,能够提高备份的速度和降低备份文件所需的储存空间。
+Xtrabackup :这是 Percona 公司开发的实时热备工具,能够在不停机的情况下进行快速可靠的热备份,并且备份期间不会间断数据库事务的处理。它支持数据的全备和增备,并且由于其采用的是物理备份的方式,所以恢复速度比较快。
+
+二、mysqldump
+2.1 常用参数
+mysqldump 的基本语法如下:
+# 备份数据库或数据库中的指定表
+mysqldump [options] db_name [tbl_name ...]
+# 备份多个指定的数据库
+mysqldump [options] --databases db_name ...
+# 备份当前数据库实例中的所有表
+mysqldump [options] --all-databases
+
+options 代表可选操作,常用的可选参数如下:
+
+
+--host=host_name, -h host_name
+指定服务器地址。
+
+
+--user=user_name, -u user_name
+指定用户名。
+
+
+--password[=password], -p[password]
+指定密码。通常无需在命令行中明文指定,按照提示输入即可。
+
+
+--default-character-set=charset_name
+导出文本使用的字符集,默认为 utf8。
+
+
+--events, -E
+备份包含数据库中的事件。
+
+
+--ignore-table=db_name.tbl_name
+不需要进行备份的表,必须使用数据库和表名来共同指定。也可以作用于视图。
+
+
+--routines, -R
+备份包含数据库中的存储过程和自定义函数。
+
+
+--triggers
+备份包含数据库中的触发器。
+
+
+--where='where_condition', -w 'where_condition'
+在对单表进行导出时候,可以指定过滤条件,例如指定用户名 --where="user='jimf'"
或用户范围 -w"userid>1"
。
+
+
+--lock-all-tables, -x
+锁定所有数据库中的所有表,从而保证备份数据的一致性。此选项自动关闭 --single-transaction
和 --lock-tables
。
+
+
+--lock-tables, -l
+锁定当前数据库中所有表,能够保证当前数据库中表的一致性,但不能保证全局的一致性。
+
+
+--single-transaction
+此选项会将事务隔离模式设置为 REPEATABLE READ 并开启一个事务,从而保证备份数据的一致性。主要用于事务表,如 InnoDB 表。 但是此时仍然不能在备份表上执行 ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE, TRUNCATE TABLE 等操作,因为 REPEATABLE READ 并不能隔离这些操作。
+另外需要注意的是 --single-transaction
选项与 --lock-tables
选项是互斥的,因为 LOCK TABLES 会导致任何正在挂起的事务被隐式提交。转储大表时,可以将 --single-transaction
选项与 --quick
选项组合使用 。
+
+
+--quick, -q
+主要用于备份大表。它强制 mysqldump 一次只从服务器检索一行数据,避免一次检索所有行而导致缓存溢出。
+
+
+--flush-logs, -F
+在开始备份前刷新 MySQL 的日志文件。此选项需要 RELOAD 权限。如果此选项与 --all-databases
配合使用,则会在每个数据库开始备份前都刷新一次日志。如果配合 --lock-all-tables
,--master-data
或 --single-transaction
使用,则只会在锁定所有表或者开启事务时刷新一次。
+
+
+--master-data[=value ]
+可以通过配置此参数来控制生成的备份文件是否包含 CHANGE MASTER 语句,该语句中包含了当前时间点二进制日志的信息。该选项有两个可选值:1 和 2 ,设置为 1 时 CHANGE MASTER 语句正常生成,设置为 2 时以注释的方式生成。--master-data
选项还会自动关闭 --lock-tables
选项,而且如果你没有指定 --single-transaction
选项,那么它还会启用 --lock-all-tables
选项,在这种情况下,会在备份开始时短暂内获取全局读锁。
+
+
+2.2 全量备份
+mysqldump 的全量备份与恢复的操作比较简单,示例如下:
+# 备份雇员库
+mysqldump -uroot -p --databases employees > employees_bak.sql
+
+# 恢复雇员库
+mysql -uroot -p < employees_bak.sql
+
+单表备份:
+# 备份雇员库中的职位表
+mysqldump -uroot -p --single-transaction employees titles > titles_bak.sql
+
+# 恢复雇员库中的职位表
+mysql> use employees;
+mysql> source /root/mysqldata/titles_bak.sql;
+
+2.3 增量备份
+mysqldump 本身并不能直接进行增量备份,需要通过分析二进制日志的方式来完成。具体示例如下:
+1. 基础全备
+1.先执行一次全备作为基础,这里以单表备份为例,需要用到上文提到的 --master-data
参数,语句如下:
+mysqldump -uroot -p --master-data=2 --flush-logs employees titles > titles_bak.sql
+
+使用 more 命令查看备份文件,此时可以在文件开头看到 CHANGE MASTER 语句,语句中包含了二进制日志的名称和偏移量信息,具体如下:
+
+
+2. 增量恢复
+对表内容进行任意修改,然后通过分析二进制日志文件来生成增量备份的脚本文件,示例如下:
+mysqlbinlog --start-position=155 \
+--database=employees ${MYSQL_HOME}/data/mysql-bin.000004 > titles_inr_bak_01.sql
+
+需要注意的是,在实际生产环境中,可能在全量备份后与增量备份前的时间间隔里生成了多份二进制文件,此时需要对每一个二进制文件都执行相同的命令:
+mysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000005 > titles_inr_bak_02.sql
+mysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000006 > titles_inr_bak_03.sql
+.....
+
+之后将全备脚本 ( titles_bak.sql ),以及所有的增备脚本 ( inr_01.sql,inr_02.sql .... ) 通过 source 命令导入即可,这样就完成了全量 + 增量的恢复。
+三、mysqlpump
+3.1 功能优势
+mysqlpump 在 mysqldump 的基础上进行了扩展增强,其主要的优点如下:
+
+
+能够并行处理数据库及其中的对象,从而可以加快备份进程;
+
+
+能够更好地控制数据库及数据库对象(表,存储过程,用户帐户等);
+
+
+能够直接对备份文件进行压缩;
+
+
+备份时能够显示进度指标(估计值);
+
+
+备份用户时生成的是 CREATE USER 与 GRANT 语句,而不是像 mysqldump 一样备份成数据,可以方便用户按需恢复。
+
+
+3.2 常用参数
+mysqlpump 的使用和 mysqldump 基本一致,这里不再进行赘述。以下主要介绍部分新增的可选项,具体如下:
+
+
+--default-parallelism=N
+每个并行处理队列的默认线程数。默认值为 2。
+
+
+--parallel-schemas=[N:]db_list
+用于并行备份多个数据库:db_list 是一个或多个以逗号分隔的数据库名称列表;N 为使用的线程数,如果没有设置,则使用 --default-parallelism
参数的值。
+
+
+--users
+将用户信息备份为 CREATE USER 语句和 GRANT 语句 。如果想要只备份用户信息,则可以使用下面的命令:
+mysqlpump --exclude-databases=% --users
+
+
+
+--compress-output=algorithm
+默认情况下,mysqlpump 不对备份文件进行压缩。可以使用该选项指定压缩格式,当前支持 LZ4 和 ZLIB 两种格式。需要注意的是压缩后的文件可以占用更少的存储空间,但是却不能直接用于备份恢复,需要先进行解压,具体如下:
+# 采用lz4算法进行压缩
+mysqlpump --compress-output=LZ4 > dump.lz4
+# 恢复前需要先进行解压
+lz4_decompress input_file output_file
+
+# 采用ZLIB算法进行压缩
+mysqlpump --compress-output=ZLIB > dump.zlib
+zlib_decompress input_file output_file
+
+MySQL 发行版自带了上面两个压缩工具,不需要进行额外安装。以上就是 mysqlpump 新增的部分常用参数,完整参数可以参考官方文档:mysqlpump — A Database Backup Program
+
+
+四、Xtrabackup
+4.1 在线安装
+Xtrabackup 可以直接使用 yum 命令进行安装,这里我的 MySQL 为 8.0 ,对应安装的 Xtrabackup 也为 8.0,命令如下:
+# 安装Percona yum 源
+yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
+
+# 安装
+yum install percona-xtrabackup-80
+
+4.2 全量备份
+全量备份的具体步骤如下:
+1. 创建备份
+Xtrabackup 全量备份的基本语句如下,可以使用 target-dir 指明备份文件的存储位置,parallel 则是指明操作的并行度:
+xtrabackup --backup --user=root --password --parallel=3 --target-dir=/data/backups/
+
+以上进行的是整个数据库实例的备份,如果需要备份指定数据库,则可以使用 --databases 进行指定。
+另外一个容易出现的异常是:Xtrabackup 在进行备份时,默认会去 /var/lib/mysql/mysql.sock
文件里获取数据库的 socket 信息,如果你修改了数据库的 socket 配置,则需要使用 --socket 参数进行重新指定,否则会抛出找不到连接的异常。备份完整后需要立即执行的另外一个操作是 prepare (准备备份)。
+2. 准备备份
+由于备份是将所有物理库表等文件复制到备份目录,而整个过程需要持续一段时间,此时备份的数据中就可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务,最终导致备份结果处于不一致状态。此时需要进行 prepare 操作来回滚未提交的事务及同步已经提交的事务至数据文件,从而使得整体达到一致性状态。命令如下:
+xtrabackup --prepare --target-dir=/data/backups/
+
+需要特别注意的在该阶段不要随意中断 xtrabackup 进程,因为这可能会导致数据文件损坏,备份将无法使用。
+3. 恢复备份
+由于 xtrabackup 执行的是物理备份,所以想要进行恢复,必须先要停止 MySQL 服务。同时这里我们可以删除 MySQL 的数据目录来模拟数据丢失的情况,之后使用以下命令将备份文件拷贝到 MySQL 的数据目录下:
+# 模拟数据异常丢失
+rm -rf /usr/app/mysql-8.0.17/data/*
+
+# 将备份文件拷贝到 data 目录下
+xtrabackup --copy-back --target-dir=/data/backups/
+
+copy-back 命令只需要指定备份文件的位置,不需要指定 MySQL 数据目录的位置,因为 Xtrabackup 会自动从 /etc/my.cnf
上获取 MySQL 的相关信息,包括数据目录的位置。如果不需要保留备份文件,可以直接使用 --move-back
命令,代表直接将备份文件移动到数据目录下。此时数据目录的所有者通常为执行命令的用户,需要更改为 mysql 用户,命令如下:
+chown -R mysql:mysql /usr/app/mysql-8.0.18/data
+
+再次启动即可完成备份恢复。
+4.3 增量备份
+使用 Xtrabackup 进行增量备份时,每一次增量备份都需要以上一次的备份为基础,之后再将增量备份运用到第一次全备之上,从而完成备份。具体操作如下:
+1. 创建备份
+这里首先创建一个全备作为基础:
+xtrabackup --backup --user=root --password=xiujingmysql. --host=172.17.0.4 --port=13306 --datadir=/data/mysql/data --parallel-3 --target-dir=/data/backups
+
+之后修改库中任意数据,然后进行第一次增量备份,此时需要使用 incremental-basedir
指定基础目录为全备目录:
+xtrabackup --user=root --password --backup --target-dir=/data/backups/inc1 \
+--incremental-basedir=/data/backups/base
+
+再修改库中任意数据,然后进行第二次增量备份,此时需要使用 incremental-basedir
指定基础目录为上一次增备目录:
+xtrabackup --user=root --password --backup --target-dir=/data/backups/inc2 \
+--incremental-basedir=/data/backups/inc1
+
+2. 准备备份
+准备基础备份:
+xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base
+
+将第一次备份作用于全备数据:
+xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base \
+--incremental-dir=/data/backups/inc1
+
+将第二次备份作用于全备数据:
+xtrabackup --prepare --target-dir=/data/backups/base \
+--incremental-dir=/data/backups/inc2
+
+在准备备份时候,除了最后一次增备外,其余的准备命令都需要加上 --apply-log-only
选项来阻止事务的回滚,因为备份时未提交的事务可能正在进行,并可能在下一次增量备份中提交,如果不进行阻止,那么增量备份将没有任何意义。
+3. 恢复备份
+恢复备份和全量备份时相同,只需要最终准备好的全备数据复制到 MySQL 的数据目录下即可:
+xtrabackup --copy-back --target-dir=/data/backups/base
+# 必须修改文件权限,否则无法启动
+chown -R mysql:mysql /usr/app/mysql-8.0.17/data
+
+此时增量备份就已经完成。需要说明的是:按照上面的情况,如果第二次备份之后发生了宕机,那么第二次备份后到宕机前的数据依然没法通过 Xtrabackup 进行恢复,此时就只能采用上面介绍的分析二进制日志的恢复方法。由此可以看出,无论是采用何种备份方式,二进制日志都是非常重要的,因此最好对其进行实时备份。
+五、二进制日志的备份
+想要备份二进制日志文件,可以通过定时执行 cp 或 scp 等命令来实现,也可以通过 mysqlbinlog 自带的功能来实现远程备份,将远程服务器上的二进制日志文件复制到本机,命令如下:
+mysqlbinlog --read-from-remote-server --raw --stop-never \
+--host=主机名 --port=3306 \
+--user=用户名 --password=密码 初始复制时的日志文件名
+
+需要注意的是这里的用户必须具有 replication slave 权限,因为上述命令本质上是模拟主从复制架构下,从节点通过 IO 线程不断去获取主节点的二进制日志,从而达到备份的目的。
+参考资料
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/sql/mysql_backups.json b/en-us/blog/sql/mysql_backups.json
new file mode 100644
index 0000000..26f95fa
--- /dev/null
+++ b/en-us/blog/sql/mysql_backups.json
@@ -0,0 +1,6 @@
+{
+ "filename": "mysql_backups.md",
+ "__html": "数据备份与恢复 \n\n一、备份简介 \n 2.1 备份分类 \n 2.2 备份工具 \n二、mysqldump \n 2.1 常用参数 \n 2.2 全量备份 \n 2.3 增量备份 \n三、mysqlpump \n 3.1 功能优势 \n 3.2 常用参数 \n四、Xtrabackup \n 4.1 在线安装 \n 4.2 全量备份 \n 4.3 增量备份 \n五、二进制日志的备份 \n \n一、备份简介 \n2.1 备份分类 \n按照不同的思考维度,通常将数据库的备份分为以下几类:
\n物理备份 与 逻辑备份
\n\n物理备份:备份的是完整的数据库目录和数据文件。采用该模式会进行大量的 IO 操作,但不含任何逻辑转换,因此备份和恢复速度通常都比较快。 \n逻辑备份:通过数据库结构和内容信息来进行备份。因为要执行逻辑转换,因此其速度较慢,并且在以文本格式保存时,其输出文件的大小大于物理备份。逻辑备份的还原的粒度可以从服务器级别(所有数据库)精确到具体表,但备份不会包括日志文件、配置文件等与数据库无关的内容。 \n \n全量备份 与 增量备份
\n\n全量备份:备份服务器在给定时间点上的所有数据。 \n增量备份:备份在给定时间跨度内(从一个时间点到另一个时间点)对数据所做的更改。 \n \n在线备份 与 离线备份
\n\n在线备份:数据库服务在运行状态下进行备份。此时其他客户端依旧可以连接到数据库,但为了保证数据的一致性,在备份期间可能会对数据进行加锁,此时客户端的访问依然会受限。 \n离线备份:在数据库服务停机状态下进行备份。此备份过程简单,但由于无法提供对外服务,通常会对业务造成比较大的影响。 \n \n2.2 备份工具 \nMySQL 支持的备份工具有很多种,这里列出常用的三种:
\n\nmysqldump :这是 MySQL 自带的备份工具,其采用的备份方式是逻辑备份,支持全库备份、单库备份、单表备份。由于其采用的是逻辑备份,所以生成的备份文件比物理备份的大,且所需恢复时间也比较长。 \nmysqlpump :这是 MySQL 5.7 之后新增的备份工具,在 mysqldump 的基础上进行了功能的扩展,支持多线程备份,支持对备份文件进行压缩,能够提高备份的速度和降低备份文件所需的储存空间。 \nXtrabackup :这是 Percona 公司开发的实时热备工具,能够在不停机的情况下进行快速可靠的热备份,并且备份期间不会间断数据库事务的处理。它支持数据的全备和增备,并且由于其采用的是物理备份的方式,所以恢复速度比较快。 \n \n二、mysqldump \n2.1 常用参数 \nmysqldump 的基本语法如下:
\n# 备份数据库或数据库中的指定表 \nmysqldump [options] db_name [tbl_name ...]\n# 备份多个指定的数据库 \nmysqldump [options] --databases db_name ...\n# 备份当前数据库实例中的所有表 \nmysqldump [options] --all-databases\n
\noptions 代表可选操作,常用的可选参数如下:
\n\n\n--host=host_name, -h host_name
\n指定服务器地址。
\n \n\n--user=user_name, -u user_name
\n指定用户名。
\n \n\n--password[=password], -p[password]
\n指定密码。通常无需在命令行中明文指定,按照提示输入即可。
\n \n\n--default-character-set=charset_name
\n导出文本使用的字符集,默认为 utf8。
\n \n\n--events, -E
\n备份包含数据库中的事件。
\n \n\n--ignore-table=db_name.tbl_name
\n不需要进行备份的表,必须使用数据库和表名来共同指定。也可以作用于视图。
\n \n\n--routines, -R
\n备份包含数据库中的存储过程和自定义函数。
\n \n\n--triggers
\n备份包含数据库中的触发器。
\n \n\n--where='where_condition', -w 'where_condition'
\n在对单表进行导出时候,可以指定过滤条件,例如指定用户名 --where="user='jimf'"
或用户范围 -w"userid>1"
。
\n \n\n--lock-all-tables, -x
\n锁定所有数据库中的所有表,从而保证备份数据的一致性。此选项自动关闭 --single-transaction
和 --lock-tables
。
\n \n\n--lock-tables, -l
\n锁定当前数据库中所有表,能够保证当前数据库中表的一致性,但不能保证全局的一致性。
\n \n\n--single-transaction
\n此选项会将事务隔离模式设置为 REPEATABLE READ 并开启一个事务,从而保证备份数据的一致性。主要用于事务表,如 InnoDB 表。 但是此时仍然不能在备份表上执行 ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE, TRUNCATE TABLE 等操作,因为 REPEATABLE READ 并不能隔离这些操作。
\n另外需要注意的是 --single-transaction
选项与 --lock-tables
选项是互斥的,因为 LOCK TABLES 会导致任何正在挂起的事务被隐式提交。转储大表时,可以将 --single-transaction
选项与 --quick
选项组合使用 。
\n \n\n--quick, -q
\n主要用于备份大表。它强制 mysqldump 一次只从服务器检索一行数据,避免一次检索所有行而导致缓存溢出。
\n \n\n--flush-logs, -F
\n在开始备份前刷新 MySQL 的日志文件。此选项需要 RELOAD 权限。如果此选项与 --all-databases
配合使用,则会在每个数据库开始备份前都刷新一次日志。如果配合 --lock-all-tables
,--master-data
或 --single-transaction
使用,则只会在锁定所有表或者开启事务时刷新一次。
\n \n\n--master-data[=value ]
\n可以通过配置此参数来控制生成的备份文件是否包含 CHANGE MASTER 语句,该语句中包含了当前时间点二进制日志的信息。该选项有两个可选值:1 和 2 ,设置为 1 时 CHANGE MASTER 语句正常生成,设置为 2 时以注释的方式生成。--master-data
选项还会自动关闭 --lock-tables
选项,而且如果你没有指定 --single-transaction
选项,那么它还会启用 --lock-all-tables
选项,在这种情况下,会在备份开始时短暂内获取全局读锁。
\n \n \n2.2 全量备份 \nmysqldump 的全量备份与恢复的操作比较简单,示例如下:
\n# 备份雇员库 \nmysqldump -uroot -p --databases employees > employees_bak.sql\n\n# 恢复雇员库 \nmysql -uroot -p < employees_bak.sql\n
\n单表备份:
\n# 备份雇员库中的职位表 \nmysqldump -uroot -p --single-transaction employees titles > titles_bak.sql\n\n# 恢复雇员库中的职位表 \nmysql> use employees; \nmysql> source /root/mysqldata/titles_bak.sql; \n
\n2.3 增量备份 \nmysqldump 本身并不能直接进行增量备份,需要通过分析二进制日志的方式来完成。具体示例如下:
\n1. 基础全备 \n1.先执行一次全备作为基础,这里以单表备份为例,需要用到上文提到的 --master-data
参数,语句如下:
\nmysqldump -uroot -p --master-data=2 --flush-logs employees titles > titles_bak.sql\n
\n使用 more 命令查看备份文件,此时可以在文件开头看到 CHANGE MASTER 语句,语句中包含了二进制日志的名称和偏移量信息,具体如下:
\n\n
\n2. 增量恢复 \n对表内容进行任意修改,然后通过分析二进制日志文件来生成增量备份的脚本文件,示例如下:
\nmysqlbinlog --start-position=155 \\\n--database=employees ${MYSQL_HOME}/data/mysql-bin.000004 > titles_inr_bak_01.sql\n
\n需要注意的是,在实际生产环境中,可能在全量备份后与增量备份前的时间间隔里生成了多份二进制文件,此时需要对每一个二进制文件都执行相同的命令:
\nmysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000005 > titles_inr_bak_02.sql\nmysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000006 > titles_inr_bak_03.sql\n.....\n
\n之后将全备脚本 ( titles_bak.sql ),以及所有的增备脚本 ( inr_01.sql,inr_02.sql .... ) 通过 source 命令导入即可,这样就完成了全量 + 增量的恢复。
\n三、mysqlpump \n3.1 功能优势 \nmysqlpump 在 mysqldump 的基础上进行了扩展增强,其主要的优点如下:
\n\n\n能够并行处理数据库及其中的对象,从而可以加快备份进程;
\n \n\n能够更好地控制数据库及数据库对象(表,存储过程,用户帐户等);
\n \n\n能够直接对备份文件进行压缩;
\n \n\n备份时能够显示进度指标(估计值);
\n \n\n备份用户时生成的是 CREATE USER 与 GRANT 语句,而不是像 mysqldump 一样备份成数据,可以方便用户按需恢复。
\n \n \n3.2 常用参数 \nmysqlpump 的使用和 mysqldump 基本一致,这里不再进行赘述。以下主要介绍部分新增的可选项,具体如下:
\n\n\n--default-parallelism=N
\n每个并行处理队列的默认线程数。默认值为 2。
\n \n\n--parallel-schemas=[N:]db_list
\n用于并行备份多个数据库:db_list 是一个或多个以逗号分隔的数据库名称列表;N 为使用的线程数,如果没有设置,则使用 --default-parallelism
参数的值。
\n \n\n--users
\n将用户信息备份为 CREATE USER 语句和 GRANT 语句 。如果想要只备份用户信息,则可以使用下面的命令:
\nmysqlpump --exclude-databases=% --users\n
\n \n\n--compress-output=algorithm
\n默认情况下,mysqlpump 不对备份文件进行压缩。可以使用该选项指定压缩格式,当前支持 LZ4 和 ZLIB 两种格式。需要注意的是压缩后的文件可以占用更少的存储空间,但是却不能直接用于备份恢复,需要先进行解压,具体如下:
\n# 采用lz4算法进行压缩 \nmysqlpump --compress-output=LZ4 > dump.lz4\n# 恢复前需要先进行解压 \nlz4_decompress input_file output_file\n\n# 采用ZLIB算法进行压缩 \nmysqlpump --compress-output=ZLIB > dump.zlib\nzlib_decompress input_file output_file\n
\nMySQL 发行版自带了上面两个压缩工具,不需要进行额外安装。以上就是 mysqlpump 新增的部分常用参数,完整参数可以参考官方文档:mysqlpump — A Database Backup Program
\n \n \n四、Xtrabackup \n4.1 在线安装 \nXtrabackup 可以直接使用 yum 命令进行安装,这里我的 MySQL 为 8.0 ,对应安装的 Xtrabackup 也为 8.0,命令如下:
\n# 安装Percona yum 源 \nyum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm\n\n# 安装 \nyum install percona-xtrabackup-80\n
\n4.2 全量备份 \n全量备份的具体步骤如下:
\n1. 创建备份 \nXtrabackup 全量备份的基本语句如下,可以使用 target-dir 指明备份文件的存储位置,parallel 则是指明操作的并行度:
\nxtrabackup --backup --user=root --password --parallel=3 --target-dir=/data/backups/\n
\n以上进行的是整个数据库实例的备份,如果需要备份指定数据库,则可以使用 --databases 进行指定。
\n另外一个容易出现的异常是:Xtrabackup 在进行备份时,默认会去 /var/lib/mysql/mysql.sock
文件里获取数据库的 socket 信息,如果你修改了数据库的 socket 配置,则需要使用 --socket 参数进行重新指定,否则会抛出找不到连接的异常。备份完整后需要立即执行的另外一个操作是 prepare (准备备份)。
\n2. 准备备份 \n由于备份是将所有物理库表等文件复制到备份目录,而整个过程需要持续一段时间,此时备份的数据中就可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务,最终导致备份结果处于不一致状态。此时需要进行 prepare 操作来回滚未提交的事务及同步已经提交的事务至数据文件,从而使得整体达到一致性状态。命令如下:
\nxtrabackup --prepare --target-dir=/data/backups/\n
\n需要特别注意的在该阶段不要随意中断 xtrabackup 进程,因为这可能会导致数据文件损坏,备份将无法使用。
\n3. 恢复备份 \n由于 xtrabackup 执行的是物理备份,所以想要进行恢复,必须先要停止 MySQL 服务。同时这里我们可以删除 MySQL 的数据目录来模拟数据丢失的情况,之后使用以下命令将备份文件拷贝到 MySQL 的数据目录下:
\n# 模拟数据异常丢失 \nrm -rf /usr/app/mysql-8.0.17/data/*\n\n# 将备份文件拷贝到 data 目录下 \nxtrabackup --copy-back --target-dir=/data/backups/\n
\ncopy-back 命令只需要指定备份文件的位置,不需要指定 MySQL 数据目录的位置,因为 Xtrabackup 会自动从 /etc/my.cnf
上获取 MySQL 的相关信息,包括数据目录的位置。如果不需要保留备份文件,可以直接使用 --move-back
命令,代表直接将备份文件移动到数据目录下。此时数据目录的所有者通常为执行命令的用户,需要更改为 mysql 用户,命令如下:
\nchown -R mysql:mysql /usr/app/mysql-8.0.18/data\n
\n再次启动即可完成备份恢复。
\n4.3 增量备份 \n使用 Xtrabackup 进行增量备份时,每一次增量备份都需要以上一次的备份为基础,之后再将增量备份运用到第一次全备之上,从而完成备份。具体操作如下:
\n1. 创建备份 \n这里首先创建一个全备作为基础:
\nxtrabackup --backup --user=root --password=xiujingmysql. --host=172.17.0.4 --port=13306 --datadir=/data/mysql/data --parallel-3 --target-dir=/data/backups\n
\n之后修改库中任意数据,然后进行第一次增量备份,此时需要使用 incremental-basedir
指定基础目录为全备目录:
\nxtrabackup --user=root --password --backup --target-dir=/data/backups/inc1 \\\n--incremental-basedir=/data/backups/base\n
\n再修改库中任意数据,然后进行第二次增量备份,此时需要使用 incremental-basedir
指定基础目录为上一次增备目录:
\nxtrabackup --user=root --password --backup --target-dir=/data/backups/inc2 \\\n--incremental-basedir=/data/backups/inc1\n
\n2. 准备备份 \n准备基础备份:
\nxtrabackup --prepare --apply-log-only --target-dir=/data/backups/base\n
\n将第一次备份作用于全备数据:
\nxtrabackup --prepare --apply-log-only --target-dir=/data/backups/base \\\n--incremental-dir=/data/backups/inc1\n
\n将第二次备份作用于全备数据:
\nxtrabackup --prepare --target-dir=/data/backups/base \\\n--incremental-dir=/data/backups/inc2\n
\n在准备备份时候,除了最后一次增备外,其余的准备命令都需要加上 --apply-log-only
选项来阻止事务的回滚,因为备份时未提交的事务可能正在进行,并可能在下一次增量备份中提交,如果不进行阻止,那么增量备份将没有任何意义。
\n3. 恢复备份 \n恢复备份和全量备份时相同,只需要最终准备好的全备数据复制到 MySQL 的数据目录下即可:
\nxtrabackup --copy-back --target-dir=/data/backups/base\n# 必须修改文件权限,否则无法启动 \nchown -R mysql:mysql /usr/app/mysql-8.0.17/data\n
\n此时增量备份就已经完成。需要说明的是:按照上面的情况,如果第二次备份之后发生了宕机,那么第二次备份后到宕机前的数据依然没法通过 Xtrabackup 进行恢复,此时就只能采用上面介绍的分析二进制日志的恢复方法。由此可以看出,无论是采用何种备份方式,二进制日志都是非常重要的,因此最好对其进行实时备份。
\n五、二进制日志的备份 \n想要备份二进制日志文件,可以通过定时执行 cp 或 scp 等命令来实现,也可以通过 mysqlbinlog 自带的功能来实现远程备份,将远程服务器上的二进制日志文件复制到本机,命令如下:
\nmysqlbinlog --read-from-remote-server --raw --stop-never \\\n--host=主机名 --port=3306 \\\n--user=用户名 --password=密码 初始复制时的日志文件名\n
\n需要注意的是这里的用户必须具有 replication slave 权限,因为上述命令本质上是模拟主从复制架构下,从节点通过 IO 线程不断去获取主节点的二进制日志,从而达到备份的目的。
\n参考资料 \n\n",
+ "link": "\\en-us\\blog\\sql\\mysql_backups.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/tool/git.html b/en-us/blog/tool/git.html
index 36c4fc2..d7e48c5 100644
--- a/en-us/blog/tool/git.html
+++ b/en-us/blog/tool/git.html
@@ -197,6 +197,157 @@ 程序员的那些迷之缩写
PRD : Product Requirement Document. 产品需求文档
+git常用命令归纳
+
+
+
+分支命令
+说明
+
+
+
+
+git branch
+列出所有本地分支机构。
+
+
+git branch -a
+列出远程和本地分支。
+
+
+git checkout -b branch_name
+创建一个本地分支并切换到该分支。
+
+
+git checkout branch_name
+切换到现有分支。
+
+
+git push origin branch_name
+将分支推送到远程。
+
+
+git branch -m new_name
+重命名当前分支。
+
+
+git branch -d branch_name
+删除本地分支。
+
+
+git push origin :branch_name
+删除远程分支。
+
+
+
+
+
+
+日志命令
+说明
+
+
+
+
+git log --oneline
+单行显示提交历史记录。
+
+
+git log -2
+显示最近N次提交的提交历史记录。
+
+
+git log -p -2
+用diff显示最近N次提交的提交历史记录。
+
+
+git diff
+在工作树中显示所有本地文件更改。
+
+
+git diff myfile
+显示对文件所做的更改。
+
+
+git blame myfile
+显示谁更改了文件的内容和时间。
+
+
+git remote show origin
+显示远程分支及其到本地的映射。
+
+
+
+
+
+
+清理命令
+说明
+
+
+
+
+git clean -f
+删除所有未跟踪的文件。
+
+
+git clean -df
+删除所有未跟踪的文件和目录。
+
+
+git checkout -- .
+撤消对所有文件的本地修改。
+
+
+git reset HEAD myfile
+取消暂存文件。
+
+
+
+
+
+
+标签命令
+说明
+
+
+
+
+git tag
+列出所有标签。
+
+
+git tag -a tag_name -m "tag message"
+创建一个新标签。
+
+
+git push --tags
+将所有标签推送到远程仓库。
+
+
+
+
+
+
+存放命令
+说明
+
+
+
+
+git stash save "stash name" && git stash
+将更改保存到存储中。
+
+
+git stash list
+列出所有藏匿处。
+
+
+git stash pop
+应用藏匿处。
+
+
+
diff --git a/en-us/blog/tool/git.json b/en-us/blog/tool/git.json
index 50a0784..3e08ee8 100644
--- a/en-us/blog/tool/git.json
+++ b/en-us/blog/tool/git.json
@@ -1,6 +1,6 @@
{
"filename": "git.md",
- "__html": "Git 常用命令速查手册 \n
\n初始化仓库 \ngit init\n
\n设置远程仓库地址后再做push \n''' s\ngit remote add origin https://gitee.com/useraddress/HelloGitee.git \n'''
\n将文件添加到仓库 \ngit add 文件名 # 将工作区的某个文件添加到暂存区\ngit add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件\ngit add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件\ngit add . # 将当前工作区的所有文件都加入暂存区\ngit add -i # 进入交互界面模式,按需添加文件到缓存区\n
\n将暂存区文件提交到本地仓库 \ngit commit -m "提交说明" # 将暂存区内容提交到本地仓库\ngit commit -a -m "提交说明" # 跳过缓存区操作,直接把工作区内容提交到本地仓库\n
\n查看仓库当前状态 \ngit status\n
\n比较文件异同 \ngit diff # 工作区与暂存区的差异\ngit diff 分支名 #工作区与某分支的差异,远程分支这样写:remotes/origin/分支名\ngit diff HEAD # 工作区与HEAD指针指向的内容差异\ngit diff 提交id 文件路径 # 工作区某文件当前版本与历史版本的差异\ngit diff --stage # 工作区文件与上次提交的差异(1.6 版本前用 --cached)\ngit diff 版本TAG # 查看从某个版本后都改动内容\ngit diff 分支A 分支B # 比较从分支A和分支B的差异(也支持比较两个TAG)\ngit diff 分支A...分支B # 比较两分支在分开后各自的改动\n\n# 另外:如果只想统计哪些文件被改动,多少行被改动,可以添加 --stat 参数\n
\n查看历史记录 \ngit log # 查看所有commit记录(SHA-A校验和,作者名称,邮箱,提交时间,提交说明)\ngit log -p -次数 # 查看最近多少次的提交记录\ngit log --stat # 简略显示每次提交的内容更改\ngit log --name-only # 仅显示已修改的文件清单\ngit log --name-status # 显示新增,修改,删除的文件清单\ngit log --oneline # 让提交记录以精简的一行输出\ngit log –graph –all --online # 图形展示分支的合并历史\ngit log --author=作者 # 查询作者的提交记录(和grep同时使用要加一个--all--match参数)\ngit log --grep=过滤信息 # 列出提交信息中包含过滤信息的提交记录\ngit log -S查询内容 # 和--grep类似,S和查询内容间没有空格\ngit log fileName # 查看某文件的修改记录,找背锅专用\n
\n代码回滚 \ngit reset HEAD^ # 恢复成上次提交的版本\ngit reset HEAD^^ # 恢复成上上次提交的版本,就是多个^,以此类推或用~次数\n\ngit reflog\n\ngit reset --hard 版本号\n\n--soft:只是改变HEAD指针指向,缓存区和工作区不变;\n--mixed:修改HEAD指针指向,暂存区内容丢失,工作区不变;\n--hard:修改HEAD指针指向,暂存区内容丢失,工作区恢复以前状态;\n
\n同步远程仓库 \ngit push -u origin master\n
\n删除版本库文件 \ngit rm 文件名\n
\n版本库里的版本替换工作区的版本 \ngit checkout -- test.txt\n
\n本地仓库内容推送到远程仓库 \ngit remote add origin git@github.com:帐号名/仓库名.git\n
\n将本地仓库内容推送到远程仓库 \n''' s\ngit add . #将当前目录所有文件添加到git暂存区\ngit commit -m "my first commit" #提交并备注提交信息\ngit push origin master #将本地提交推送到远程仓库\n'''
\n从远程仓库克隆项目到本地 \ngit clone git@github.com:git帐号名/仓库名.git\n
\n创建分支 \ngit checkout -b dev\n-b表示创建并切换分支\n上面一条命令相当于一面的二条:\ngit branch dev //创建分支\ngit checkout dev //切换分支\n
\n查看分支 \ngit branch\n
\n合并分支 \ngit merge dev\n//用于合并指定分支到当前分支\n\ngit merge --no-ff -m "merge with no-ff" dev\n//加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并\n
\n删除分支 \ngit branch -d dev\n
\n查看分支合并图 \ngit log --graph --pretty=oneline --abbrev-commit\n
\n查看远程库信息 \ngit remote\n// -v 显示更详细的信息\n
\ngit相关配置 \n# 安装完Git后第一件要做的事,设置用户信息(global可换成local在单独项目生效):\ngit config --global user.name "用户名" # 设置用户名\ngit config --global user.email "用户邮箱" #设置邮箱\ngit config --global user.name # 查看用户名是否配置成功\ngit config --global user.email # 查看邮箱是否配置\n\n# 其他查看配置相关\ngit config --global --list # 查看全局设置相关参数列表\ngit config --local --list # 查看本地设置相关参数列表\ngit config --system --list # 查看系统配置参数列表\ngit config --list # 查看所有Git的配置(全局+本地+系统)\ngit config --global color.ui true //显示git相关颜色\n
\n撤消某次提交 \ngit revert HEAD # 撤销最近的一个提交\ngit revert 版本号 # 撤销某次commit\n
\n拉取远程分支到本地仓库 \ngit checkout -b 本地分支 远程分支 # 会在本地新建分支,并自动切换到该分支\ngit fetch origin 远程分支:本地分支 # 会在本地新建分支,但不会自动切换,还需checkout\ngit branch --set-upstream 本地分支 远程分支 # 建立本地分支与远程分支的链接\n
\n标签命令 \ngit tag 标签 //打标签命令,默认为HEAD\ngit tag //显示所有标签\ngit tag 标签 �版本号 //给某个commit版本添加标签\ngit show 标签 //显示某个标签的详细信息\n
\n同步远程仓库更新 \ngit fetch origin master\n //从远程获取最新的到本地,首先从远程的origin的master主分支下载最新的版本到origin/master分支上,然后比较本地的master分支和origin/master分支的差别,最后进行合并。\n\ngit fetch比git pull更加安全\ngit pull origin master\n
\n推送时选择强制推送 \n强制推送需要执行下面的命令(默认不推荐该行为):
\ngit push origin master -f\n
\ngit 提示授权失败解决 \ngit config --system --unset credential.helper # 管理员权限执行命令\n
\ngit 授权永久有效 \ngit config --global credential.helper 'store'\n
\n程序员的那些迷之缩写 \n就像你可能不知道 现充 其实是 现实生活很充实的人生赢家 的缩写一样,我们经常看到 Github 上的码农们在 code review 时,把乱七八糟的缩写写得到处都是——娴熟的司机们都会使用缩写来达到提高逼格的效果——我们第一次看到时还是会出现一脸懵逼的状况,这里整理一下这些缩写都是什么含义,以后我们也可以欢快地装逼了。
\n\n\nPR: Pull Request. 拉取请求,给其他项目提交代码
\n \n\nLGTM: Looks Good To Me. 朕知道了 代码已经过 review,可以合并
\n \n\nSGTM: Sounds Good To Me. 和上面那句意思差不多,也是已经通过了 review 的意思
\n \n\nWIP: Work In Progress. 传说中提 PR 的最佳实践是,如果你有个改动很大的 PR,可以在写了一部分的情况下先提交,但是在标题里写上 WIP,以告诉项目维护者这个功能还未完成,方便维护者提前 review 部分提交的代码。
\n \n\nPTAL: Please Take A Look. 你来瞅瞅?用来提示别人来看一下
\n \n\nTBR: To Be Reviewed. 提示维护者进行 review
\n \n\nTL , DR: Too Long; Didn't Read. 太长懒得看。也有很多文档在做简略描述之前会写这么一句
\n \n\nTBD: To Be Done(or Defined/Discussed/Decided/Determined). 根据语境不同意义有所区别,但一般都是还没搞定的意思
\n \n\nPRD : Product Requirement Document. 产品需求文档
\n \n \n",
+ "__html": "Git 常用命令速查手册 \n
\n初始化仓库 \ngit init\n
\n设置远程仓库地址后再做push \n''' s\ngit remote add origin https://gitee.com/useraddress/HelloGitee.git \n'''
\n将文件添加到仓库 \ngit add 文件名 # 将工作区的某个文件添加到暂存区\ngit add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件\ngit add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件\ngit add . # 将当前工作区的所有文件都加入暂存区\ngit add -i # 进入交互界面模式,按需添加文件到缓存区\n
\n将暂存区文件提交到本地仓库 \ngit commit -m "提交说明" # 将暂存区内容提交到本地仓库\ngit commit -a -m "提交说明" # 跳过缓存区操作,直接把工作区内容提交到本地仓库\n
\n查看仓库当前状态 \ngit status\n
\n比较文件异同 \ngit diff # 工作区与暂存区的差异\ngit diff 分支名 #工作区与某分支的差异,远程分支这样写:remotes/origin/分支名\ngit diff HEAD # 工作区与HEAD指针指向的内容差异\ngit diff 提交id 文件路径 # 工作区某文件当前版本与历史版本的差异\ngit diff --stage # 工作区文件与上次提交的差异(1.6 版本前用 --cached)\ngit diff 版本TAG # 查看从某个版本后都改动内容\ngit diff 分支A 分支B # 比较从分支A和分支B的差异(也支持比较两个TAG)\ngit diff 分支A...分支B # 比较两分支在分开后各自的改动\n\n# 另外:如果只想统计哪些文件被改动,多少行被改动,可以添加 --stat 参数\n
\n查看历史记录 \ngit log # 查看所有commit记录(SHA-A校验和,作者名称,邮箱,提交时间,提交说明)\ngit log -p -次数 # 查看最近多少次的提交记录\ngit log --stat # 简略显示每次提交的内容更改\ngit log --name-only # 仅显示已修改的文件清单\ngit log --name-status # 显示新增,修改,删除的文件清单\ngit log --oneline # 让提交记录以精简的一行输出\ngit log –graph –all --online # 图形展示分支的合并历史\ngit log --author=作者 # 查询作者的提交记录(和grep同时使用要加一个--all--match参数)\ngit log --grep=过滤信息 # 列出提交信息中包含过滤信息的提交记录\ngit log -S查询内容 # 和--grep类似,S和查询内容间没有空格\ngit log fileName # 查看某文件的修改记录,找背锅专用\n
\n代码回滚 \ngit reset HEAD^ # 恢复成上次提交的版本\ngit reset HEAD^^ # 恢复成上上次提交的版本,就是多个^,以此类推或用~次数\n\ngit reflog\n\ngit reset --hard 版本号\n\n--soft:只是改变HEAD指针指向,缓存区和工作区不变;\n--mixed:修改HEAD指针指向,暂存区内容丢失,工作区不变;\n--hard:修改HEAD指针指向,暂存区内容丢失,工作区恢复以前状态;\n
\n同步远程仓库 \ngit push -u origin master\n
\n删除版本库文件 \ngit rm 文件名\n
\n版本库里的版本替换工作区的版本 \ngit checkout -- test.txt\n
\n本地仓库内容推送到远程仓库 \ngit remote add origin git@github.com:帐号名/仓库名.git\n
\n将本地仓库内容推送到远程仓库 \n''' s\ngit add . #将当前目录所有文件添加到git暂存区\ngit commit -m "my first commit" #提交并备注提交信息\ngit push origin master #将本地提交推送到远程仓库\n'''
\n从远程仓库克隆项目到本地 \ngit clone git@github.com:git帐号名/仓库名.git\n
\n创建分支 \ngit checkout -b dev\n-b表示创建并切换分支\n上面一条命令相当于一面的二条:\ngit branch dev //创建分支\ngit checkout dev //切换分支\n
\n查看分支 \ngit branch\n
\n合并分支 \ngit merge dev\n//用于合并指定分支到当前分支\n\ngit merge --no-ff -m "merge with no-ff" dev\n//加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并\n
\n删除分支 \ngit branch -d dev\n
\n查看分支合并图 \ngit log --graph --pretty=oneline --abbrev-commit\n
\n查看远程库信息 \ngit remote\n// -v 显示更详细的信息\n
\ngit相关配置 \n# 安装完Git后第一件要做的事,设置用户信息(global可换成local在单独项目生效):\ngit config --global user.name "用户名" # 设置用户名\ngit config --global user.email "用户邮箱" #设置邮箱\ngit config --global user.name # 查看用户名是否配置成功\ngit config --global user.email # 查看邮箱是否配置\n\n# 其他查看配置相关\ngit config --global --list # 查看全局设置相关参数列表\ngit config --local --list # 查看本地设置相关参数列表\ngit config --system --list # 查看系统配置参数列表\ngit config --list # 查看所有Git的配置(全局+本地+系统)\ngit config --global color.ui true //显示git相关颜色\n
\n撤消某次提交 \ngit revert HEAD # 撤销最近的一个提交\ngit revert 版本号 # 撤销某次commit\n
\n拉取远程分支到本地仓库 \ngit checkout -b 本地分支 远程分支 # 会在本地新建分支,并自动切换到该分支\ngit fetch origin 远程分支:本地分支 # 会在本地新建分支,但不会自动切换,还需checkout\ngit branch --set-upstream 本地分支 远程分支 # 建立本地分支与远程分支的链接\n
\n标签命令 \ngit tag 标签 //打标签命令,默认为HEAD\ngit tag //显示所有标签\ngit tag 标签 �版本号 //给某个commit版本添加标签\ngit show 标签 //显示某个标签的详细信息\n
\n同步远程仓库更新 \ngit fetch origin master\n //从远程获取最新的到本地,首先从远程的origin的master主分支下载最新的版本到origin/master分支上,然后比较本地的master分支和origin/master分支的差别,最后进行合并。\n\ngit fetch比git pull更加安全\ngit pull origin master\n
\n推送时选择强制推送 \n强制推送需要执行下面的命令(默认不推荐该行为):
\ngit push origin master -f\n
\ngit 提示授权失败解决 \ngit config --system --unset credential.helper # 管理员权限执行命令\n
\ngit 授权永久有效 \ngit config --global credential.helper 'store'\n
\n程序员的那些迷之缩写 \n就像你可能不知道 现充 其实是 现实生活很充实的人生赢家 的缩写一样,我们经常看到 Github 上的码农们在 code review 时,把乱七八糟的缩写写得到处都是——娴熟的司机们都会使用缩写来达到提高逼格的效果——我们第一次看到时还是会出现一脸懵逼的状况,这里整理一下这些缩写都是什么含义,以后我们也可以欢快地装逼了。
\n\n\nPR: Pull Request. 拉取请求,给其他项目提交代码
\n \n\nLGTM: Looks Good To Me. 朕知道了 代码已经过 review,可以合并
\n \n\nSGTM: Sounds Good To Me. 和上面那句意思差不多,也是已经通过了 review 的意思
\n \n\nWIP: Work In Progress. 传说中提 PR 的最佳实践是,如果你有个改动很大的 PR,可以在写了一部分的情况下先提交,但是在标题里写上 WIP,以告诉项目维护者这个功能还未完成,方便维护者提前 review 部分提交的代码。
\n \n\nPTAL: Please Take A Look. 你来瞅瞅?用来提示别人来看一下
\n \n\nTBR: To Be Reviewed. 提示维护者进行 review
\n \n\nTL , DR: Too Long; Didn't Read. 太长懒得看。也有很多文档在做简略描述之前会写这么一句
\n \n\nTBD: To Be Done(or Defined/Discussed/Decided/Determined). 根据语境不同意义有所区别,但一般都是还没搞定的意思
\n \n\nPRD : Product Requirement Document. 产品需求文档
\n \n \ngit常用命令归纳 \n\n\n\n分支命令 \n说明 \n \n \n\n\ngit branch \n列出所有本地分支机构。 \n \n\ngit branch -a \n列出远程和本地分支。 \n \n\ngit checkout -b branch_name \n创建一个本地分支并切换到该分支。 \n \n\ngit checkout branch_name \n切换到现有分支。 \n \n\ngit push origin branch_name \n将分支推送到远程。 \n \n\ngit branch -m new_name \n重命名当前分支。 \n \n\ngit branch -d branch_name \n删除本地分支。 \n \n\ngit push origin :branch_name \n删除远程分支。 \n \n \n
\n\n\n\n日志命令 \n说明 \n \n \n\n\ngit log --oneline \n单行显示提交历史记录。 \n \n\ngit log -2 \n显示最近N次提交的提交历史记录。 \n \n\ngit log -p -2 \n用diff显示最近N次提交的提交历史记录。 \n \n\ngit diff \n在工作树中显示所有本地文件更改。 \n \n\ngit diff myfile \n显示对文件所做的更改。 \n \n\ngit blame myfile \n显示谁更改了文件的内容和时间。 \n \n\ngit remote show origin \n显示远程分支及其到本地的映射。 \n \n \n
\n\n\n\n清理命令 \n说明 \n \n \n\n\ngit clean -f \n删除所有未跟踪的文件。 \n \n\ngit clean -df \n删除所有未跟踪的文件和目录。 \n \n\ngit checkout -- . \n撤消对所有文件的本地修改。 \n \n\ngit reset HEAD myfile \n取消暂存文件。 \n \n \n
\n\n\n\n标签命令 \n说明 \n \n \n\n\ngit tag \n列出所有标签。 \n \n\ngit tag -a tag_name -m "tag message" \n创建一个新标签。 \n \n\ngit push --tags \n将所有标签推送到远程仓库。 \n \n \n
\n\n\n\n存放命令 \n说明 \n \n \n\n\ngit stash save "stash name" && git stash \n将更改保存到存储中。 \n \n\ngit stash list \n列出所有藏匿处。 \n \n\ngit stash pop \n应用藏匿处。 \n \n \n
\n",
"link": "\\en-us\\blog\\tool\\git.html",
"meta": {}
}
\ No newline at end of file
diff --git a/en-us/blog/tool/minio.html b/en-us/blog/tool/minio.html
new file mode 100644
index 0000000..a8b19d0
--- /dev/null
+++ b/en-us/blog/tool/minio.html
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+ minio
+
+
+
+
+ MinIO 搭建使用
+MinIO简介
+MinIO 是一款基于Go语言的高性能对象存储服务,在Github上已有19K+Star。它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。 本文将使用 MinIO 来自建一个对象存储服务用于存储图片。
+安装及部署
+
+MinIO的安装方式有很多,这里我们使用它在Docker环境下的安装方式。
+
+
+docker pull minio/minio
+
+
+在Docker容器中运行MinIO,这里我们将MiniIO的数据和配置文件夹挂在到宿主机上:
+
+docker run -p 9000:9000 --name minio \
+ --restart=always \
+ -v /etc/localtime:/etc/localtime \
+ -v /data/minio/data:/data \
+ -v /data/minio/config:/root/.minio \
+ -d minio/minio server /data
+
+
+
+上传文件及使用
+
+通过使用MinIO的网页端即可完成文件的上传下载功能,下面我们以图片上传下载为例来演示下该功能。
+
+
+
+
+存储桶创建完成后,通过上传按钮可以上传文件,这里我们上传一张图片:
+
+
+
+图片上传完成后,我们可以通过拷贝链接按钮来获取图片访问路径,但是这只是个临时的访问路径:
+
+
+
+要想获取一个永久的访问路径,需要修改存储桶的访问策略,我们可以点击存储桶右上角的编辑策略按钮来修改访问策略;
+
+
+
+这里有三种访问策略可以选择,一种只读、一种只写、一种可读可写,这里我们选择只读即可,但是需要注意的是,访问前缀需要设置为*.*,否则会无法访问;
+
+
+
+设置完成后,我们只需要通过拷贝链接中的前一串路径即可永久访问该文件;
+
+MinIO客户端的使用
+
+虽然MinIO的网页端管理已经很方便了,但是官网还是给我们提供了基于命令行的客户端MinIO Client(简称mc),下面我们来讲讲它的使用方法。
+
+常用命令
+
+下面我们先来熟悉下mc的命令,这些命令和Linux中的命令有很多相似之处。
+
+
+
+
+命令
+作用
+
+
+
+
+ls
+列出文件和文件夹
+
+
+mb
+创建一个存储桶或一个文件夹
+
+
+cat
+显示文件和对象内容
+
+
+pipe
+将一个STDIN重定向到一个对象或者文件或者STDOUT
+
+
+share
+生成用于共享的URL
+
+
+cp
+拷贝文件和对象
+
+
+mirror
+给存储桶和文件夹做镜像
+
+
+find
+基于参数查找文件
+
+
+diff
+对两个文件夹或者存储桶比较差异
+
+
+rm
+删除文件和对象
+
+
+events
+管理对象通知
+
+
+watch
+监听文件和对象的事件
+
+
+policy
+管理访问策略
+
+
+session
+为cp命令管理保存的会话
+
+
+config
+管理mc配置文件
+
+
+update
+检查软件更新
+
+
+version
+输出版本信息
+
+
+
+安装及配置
+
+由于MinIO服务端中并没有自带客户端,所以我们需要安装配置完客户端后才能使用,这里以Docker环境下的安装为例。
+
+
+下载MinIO Client 的Docker镜像:
+
+docker pull minio/mc
+
+
+docker run -it --entrypoint=/bin/sh minio/mc
+
+
+运行完成后我们需要进行配置,将我们自己的MinIO服务配置到客户端上去,配置的格式如下:
+
+mc config host add <ALIAS> <YOUR-S3-ENDPOINT> <YOUR-ACCESS-KEY> <YOUR-SECRET-KEY> <API-SIGNATURE>
+
+
+mc config host add minio http://localhost:9000 minioadmin minioadmin S3v4
+
+常用操作
+
+
+mc ls minio
+
+mc ls minio/blog
+
+
+
+mc mb minio/test
+
+
+mc share download minio/blog/avatar.png
+
+
+mc find minio/blog --name "*.png"
+
+
+
+mc policy set download minio/test /
+
+mc policy list minio/test /
+
+参考资料
+详细了解MinIO可以参考官方文档:https://docs.min.io/cn/minio-quickstart-guide.html
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/tool/minio.json b/en-us/blog/tool/minio.json
new file mode 100644
index 0000000..6f2d6ee
--- /dev/null
+++ b/en-us/blog/tool/minio.json
@@ -0,0 +1,6 @@
+{
+ "filename": "minio.md",
+ "__html": "MinIO 搭建使用 \nMinIO简介 \nMinIO 是一款基于Go语言的高性能对象存储服务,在Github上已有19K+Star。它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。 本文将使用 MinIO 来自建一个对象存储服务用于存储图片。
\n安装及部署 \n\nMinIO的安装方式有很多,这里我们使用它在Docker环境下的安装方式。
\n \n\ndocker pull minio/minio\n
\n\n在Docker容器中运行MinIO,这里我们将MiniIO的数据和配置文件夹挂在到宿主机上: \n \ndocker run -p 9000:9000 --name minio \\\n --restart=always \\\n -v /etc/localtime:/etc/localtime \\\n -v /data/minio/data:/data \\\n -v /data/minio/config:/root/.minio \\\n -d minio/minio server /data\n
\n\n
\n上传文件及使用 \n\n通过使用MinIO的网页端即可完成文件的上传下载功能,下面我们以图片上传下载为例来演示下该功能。
\n \n\n
\n\n存储桶创建完成后,通过上传按钮可以上传文件,这里我们上传一张图片: \n \n
\n\n图片上传完成后,我们可以通过拷贝链接按钮来获取图片访问路径,但是这只是个临时的访问路径: \n \n
\n\n要想获取一个永久的访问路径,需要修改存储桶的访问策略,我们可以点击存储桶右上角的编辑策略按钮来修改访问策略; \n \n
\n\n这里有三种访问策略可以选择,一种只读、一种只写、一种可读可写,这里我们选择只读即可,但是需要注意的是,访问前缀需要设置为*.*,否则会无法访问; \n \n
\n\n设置完成后,我们只需要通过拷贝链接中的前一串路径即可永久访问该文件; \n \nMinIO客户端的使用 \n\n虽然MinIO的网页端管理已经很方便了,但是官网还是给我们提供了基于命令行的客户端MinIO Client(简称mc),下面我们来讲讲它的使用方法。
\n \n常用命令 \n\n下面我们先来熟悉下mc的命令,这些命令和Linux中的命令有很多相似之处。
\n \n\n\n\n命令 \n作用 \n \n \n\n\nls \n列出文件和文件夹 \n \n\nmb \n创建一个存储桶或一个文件夹 \n \n\ncat \n显示文件和对象内容 \n \n\npipe \n将一个STDIN重定向到一个对象或者文件或者STDOUT \n \n\nshare \n生成用于共享的URL \n \n\ncp \n拷贝文件和对象 \n \n\nmirror \n给存储桶和文件夹做镜像 \n \n\nfind \n基于参数查找文件 \n \n\ndiff \n对两个文件夹或者存储桶比较差异 \n \n\nrm \n删除文件和对象 \n \n\nevents \n管理对象通知 \n \n\nwatch \n监听文件和对象的事件 \n \n\npolicy \n管理访问策略 \n \n\nsession \n为cp命令管理保存的会话 \n \n\nconfig \n管理mc配置文件 \n \n\nupdate \n检查软件更新 \n \n\nversion \n输出版本信息 \n \n \n
\n安装及配置 \n\n由于MinIO服务端中并没有自带客户端,所以我们需要安装配置完客户端后才能使用,这里以Docker环境下的安装为例。
\n \n\n下载MinIO Client 的Docker镜像: \n \ndocker pull minio/mc\n
\n\ndocker run -it --entrypoint=/bin/sh minio/mc\n
\n\n运行完成后我们需要进行配置,将我们自己的MinIO服务配置到客户端上去,配置的格式如下: \n \nmc config host add <ALIAS> <YOUR-S3-ENDPOINT> <YOUR-ACCESS-KEY> <YOUR-SECRET-KEY> <API-SIGNATURE>\n
\n\nmc config host add minio http://localhost:9000 minioadmin minioadmin S3v4\n
\n常用操作 \n\n\nmc ls minio\n\nmc ls minio/blog\n
\n
\n\nmc mb minio/test \n
\n\nmc share download minio/blog/avatar.png\n
\n\nmc find minio/blog --name \"*.png\" \n
\n\n\nmc policy set download minio/test /\n\nmc policy list minio/test /\n
\n参考资料 \n详细了解MinIO可以参考官方文档:https://docs.min.io/cn/minio-quickstart-guide.html
\n",
+ "link": "\\en-us\\blog\\tool\\minio.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/web/es6.html b/en-us/blog/web/es6.html
index 9c3f32a..495ccc4 100644
--- a/en-us/blog/web/es6.html
+++ b/en-us/blog/web/es6.html
@@ -381,7 +381,8 @@ Generators
总结
以上就是 ES6 最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%
-更多ES6语法点击这里
+更多ES6语法点击这里
+《JavaScript 语言入门教程》
diff --git a/en-us/blog/web/es6.json b/en-us/blog/web/es6.json
index 604f1a9..20d041c 100644
--- a/en-us/blog/web/es6.json
+++ b/en-us/blog/web/es6.json
@@ -1,6 +1,6 @@
{
"filename": "es6.md",
- "__html": "JavaScript ES6 规范 \nES6 简介 \nECMAScript 6 简称 ES6,是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
\nECMAScript 和 JavaScript 的关系:前者是后者的语法规格,后者是前者的一种实现
\nBabel :将ES6代码转为ES5代码
\n新特性 \nlet、const \nlet 定义的变量不会被变量提升,const 定义的常量不能被修改,let 和 const 都是块级作用域
\nES6前,js 是没有块级作用域 {} 的概念的。(有函数作用域、全局作用域、eval作用域)
\nES6后,let 和 const 的出现,js 也有了块级作用域的概念,前端的知识是日新月异的~
\n变量提升:在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部;不在函数内即在全局作用域的最顶部。这样就会引起一些误解。例如:
\nconsole.log(a); // undefined\nvar a = 'hello';\n \n# 上面的代码相当于\nvar a;\nconsole.log(a);\na = 'hello';\n \n# 而 let 就不会被变量提升\nconsole.log(a); // a is not defined\nlet a = 'hello';\n
\nconst 定义的常量不能被修改
\nvar name = \"bai\" ;\nname = \"ming\" ;\nconsole .log(name); \nconst name = \"bai\" ;\nname = \"ming\" ; \nconsole .log(name);\n
\nimport、export \nimport导入模块、export导出模块
\n\nimport people from './example' \n \n\nimport * as example from \"./example.js\" \nconsole .log(example.name)\nconsole .log(example.getName())\n \n\nimport {name, age} from './example' \n \n \n\nexport default App\n \n\nexport class App extend Component {};\n
\nclass、extends、super \nES5中最令人头疼的的几个部分:原型、构造函数,继承,有了ES6我们不再烦恼!
\nES6引入了Class(类)这个概念。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nconsole .log(this .type + ' says ' + say);\n}\n}\n \nlet animal = new Animal();\nanimal.says('hello' ); \n \nclass Cat extends Animal {\nconstructor () {\nsuper ();\nthis .type = 'cat' ;\n}\n}\n \nlet cat = new Cat();\ncat.says('hello' ); \n
\n上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实力对象可以共享的。
\nClass之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
\nsuper关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
\nES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
\n\nvar Shape = function (id, x, y ) {\nthis .id = id,\nthis .move(x, y);\n};\nShape.prototype.move = function (x, y ) {\nthis .x = x;\nthis .y = y;\n};\n \nvar Rectangle = function id (ix, x, y, width, height ) {\nShape.call(this , id, x, y);\nthis .width = width;\nthis .height = height;\n};\nRectangle.prototype = Object .create(Shape.prototype);\nRectangle.prototype.constructor = Rectangle;\n \nvar Circle = function (id, x, y, radius ) {\nShape.call(this , id, x, y);\nthis .radius = radius;\n};\nCircle.prototype = Object .create(Shape.prototype);\nCircle.prototype.constructor = Circle;\n \n\nclass Shape {\nconstructor (id, x, y) {\nthis .id = id this .move(x, y);\n}\nmove(x, y) {\nthis .x = x this .y = y;\n}\n}\n \nclass Rectangle extends Shape {\nconstructor (id, x, y, width, height) {\nsuper (id, x, y) this .width = width this .height = height;\n}\n}\n \nclass Circle extends Shape {\nconstructor (id, x, y, radius) {\nsuper (id, x, y) this .radius = radius;\n}\n}\n
\narrow functions (箭头函数) \n函数的快捷写法。不需要 function 关键字来创建函数,省略 return 关键字,继承当前上下文的 this 关键字
\n\nvar arr1 = [1 , 2 , 3 ];\nvar newArr1 = arr1.map(function (x ) {\nreturn x + 1 ;\n});\n \n\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map((x ) => {\nx + 1 \n});\n
\n箭头函数小细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的;当你函数中有且仅有一个表达式的时候可以省略{}
\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map(x => x + 1 );\n
\nJavaScript语言的this对象一直是一个令人头痛的问题,运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n}\nvar animal = new Animal();\nanimal.says('hi' ); \n
\n解决办法:
\n\nsays(say) {\nvar self = this ;\nsetTimeout(function ( ) {\nconsole .log(self.type + ' says ' + say);\n}, 1000 );\n}\n \n\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}.bind(this ), 1000 );\n}\n \n\n\nsays(say) {\nsetTimeout(() => {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n
\ntemplate string (模板字符串) \n解决了 ES5 在字符串功能上的痛点。
\n第一个用途:字符串拼接。将表达式嵌入字符串中进行拼接,用 和${}
来界定。
\n\nvar name1 = \"bai\" ;\nconsole .log('hello' + name1);\n \n\nconst name2 = \"ming\" ;\nconsole .log(`hello${name2} ` );\n
\n第二个用途:在ES5时我们通过反斜杠来做多行字符串拼接。ES6反引号 `` 直接搞定。
\n\nvar msg = \"Hi \\\nman!\" ;\n \n\nconst template = `<div>\n<span>hello world</span>\n</div>` ;\n
\n另外:includes repeat
\n\nlet str = 'hahah' ;\nconsole .log(str.includes('y' )); \n \n\nlet s = 'he' ;\nconsole .log(s.repeat(3 )); \n
\ndestructuring (解构) \n简化数组和对象中信息的提取。
\nES6前,我们一个一个获取对象信息;
\nES6后,解构能让我们从对象或者数组里取出数据存为变量
\n\nvar people1 = {\nname : 'bai' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n};\n \nvar myName = people1.name;\nvar myAge = people1.age;\nvar myColor = people1.color[0 ];\nconsole .log(myName + '----' + myAge + '----' + myColor);\n \n\nlet people2 = {\nname : 'ming' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n}\n \nlet { name, age } = people2;\nlet [first, second] = people2.color;\nconsole .log(`${name} ----${age} ----${first} ` );\n
\ndefault 函数默认参数 \n\nfunction foo (num ) {\nnum = num || 200 ;\nreturn num;\n}\n \n\nfunction foo (num = 200 ) {\nreturn num;\n}\n
\nrest arguments (rest参数) \n解决了 es5 复杂的 arguments 问题
\nfunction foo (x, y, ...rest ) {\nreturn ((x + y) * rest.length);\n}\nfoo(1 , 2 , 'hello' , true , 7 ); \n
\nSpread Operator (展开运算符) \n第一个用途:组装数组
\nlet color = ['red' , 'yellow' ];\nlet colorful = [...color, 'green' , 'blue' ];\nconsole .log(colorful); \n
\n第二个用途:获取数组除了某几项的其他项
\nlet num = [1 , 3 , 5 , 7 , 9 ];\nlet [first, second, ...rest] = num;\nconsole .log(rest); \n
\n对象 \n对象初始化简写
\n\nfunction people (name, age ) {\nreturn {\nname : name,\nage : age\n};\n}\n \n\nfunction people (name, age ) {\nreturn {\nname,\nage\n};\n}\n
\n对象字面量简写(省略冒号与 function 关键字)
\n\nvar people1 = {\nname : 'bai' ,\ngetName : function ( ) {\nconsole .log(this .name);\n}\n};\n \n\nlet people2 = {\nname : 'bai' ,\ngetName () {\nconsole .log(this .name);\n}\n};\n
\n另外:Object.assign()
\nES6 对象提供了Object.assign()这个方法来实现浅复制。Object.assign()可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}
\nconst obj = Object .assign({}, objA, objB)\n \n\nthis .seller = Object .assign({}, this .seller, response.data)\n
\nPromise \n用同步的方式去写异步代码
\n\nfetch('/api/todos' )\n.then(res => res.json())\n.then(data => ({\ndata\n}))\n.catch(err => ({\nerr\n}));\n
\nGenerators \n生成器( generator)是能返回一个迭代器的函数。
\n生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
\n这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
\n迭代器:当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
\n\nfunction *createIterator ( ) {\nyield 1 ;\nyield 2 ;\nyield 3 ;\n}\n \n\nlet iterator = createIterator();\n \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \n
\n迭代器对异步编程作用很大,异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
\n生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
\n那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的:
\nfunction run (taskDef ) {\n\n\nlet task = taskDef();\n \n\nlet result = task.next();\n \n\nfunction step ( ) {\n\nif (!result.done) {\nresult = task.next();\nstep();\n}\n}\n \n\nstep();\n}\n
\n总结 \n以上就是 ES6 最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%
\n更多ES6语法点击这里
\n",
+ "__html": "JavaScript ES6 规范 \nES6 简介 \nECMAScript 6 简称 ES6,是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
\nECMAScript 和 JavaScript 的关系:前者是后者的语法规格,后者是前者的一种实现
\nBabel :将ES6代码转为ES5代码
\n新特性 \nlet、const \nlet 定义的变量不会被变量提升,const 定义的常量不能被修改,let 和 const 都是块级作用域
\nES6前,js 是没有块级作用域 {} 的概念的。(有函数作用域、全局作用域、eval作用域)
\nES6后,let 和 const 的出现,js 也有了块级作用域的概念,前端的知识是日新月异的~
\n变量提升:在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部;不在函数内即在全局作用域的最顶部。这样就会引起一些误解。例如:
\nconsole.log(a); // undefined\nvar a = 'hello';\n \n# 上面的代码相当于\nvar a;\nconsole.log(a);\na = 'hello';\n \n# 而 let 就不会被变量提升\nconsole.log(a); // a is not defined\nlet a = 'hello';\n
\nconst 定义的常量不能被修改
\nvar name = \"bai\" ;\nname = \"ming\" ;\nconsole .log(name); \nconst name = \"bai\" ;\nname = \"ming\" ; \nconsole .log(name);\n
\nimport、export \nimport导入模块、export导出模块
\n\nimport people from './example' \n \n\nimport * as example from \"./example.js\" \nconsole .log(example.name)\nconsole .log(example.getName())\n \n\nimport {name, age} from './example' \n \n \n\nexport default App\n \n\nexport class App extend Component {};\n
\nclass、extends、super \nES5中最令人头疼的的几个部分:原型、构造函数,继承,有了ES6我们不再烦恼!
\nES6引入了Class(类)这个概念。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nconsole .log(this .type + ' says ' + say);\n}\n}\n \nlet animal = new Animal();\nanimal.says('hello' ); \n \nclass Cat extends Animal {\nconstructor () {\nsuper ();\nthis .type = 'cat' ;\n}\n}\n \nlet cat = new Cat();\ncat.says('hello' ); \n
\n上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实力对象可以共享的。
\nClass之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
\nsuper关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
\nES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
\n\nvar Shape = function (id, x, y ) {\nthis .id = id,\nthis .move(x, y);\n};\nShape.prototype.move = function (x, y ) {\nthis .x = x;\nthis .y = y;\n};\n \nvar Rectangle = function id (ix, x, y, width, height ) {\nShape.call(this , id, x, y);\nthis .width = width;\nthis .height = height;\n};\nRectangle.prototype = Object .create(Shape.prototype);\nRectangle.prototype.constructor = Rectangle;\n \nvar Circle = function (id, x, y, radius ) {\nShape.call(this , id, x, y);\nthis .radius = radius;\n};\nCircle.prototype = Object .create(Shape.prototype);\nCircle.prototype.constructor = Circle;\n \n\nclass Shape {\nconstructor (id, x, y) {\nthis .id = id this .move(x, y);\n}\nmove(x, y) {\nthis .x = x this .y = y;\n}\n}\n \nclass Rectangle extends Shape {\nconstructor (id, x, y, width, height) {\nsuper (id, x, y) this .width = width this .height = height;\n}\n}\n \nclass Circle extends Shape {\nconstructor (id, x, y, radius) {\nsuper (id, x, y) this .radius = radius;\n}\n}\n
\narrow functions (箭头函数) \n函数的快捷写法。不需要 function 关键字来创建函数,省略 return 关键字,继承当前上下文的 this 关键字
\n\nvar arr1 = [1 , 2 , 3 ];\nvar newArr1 = arr1.map(function (x ) {\nreturn x + 1 ;\n});\n \n\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map((x ) => {\nx + 1 \n});\n
\n箭头函数小细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的;当你函数中有且仅有一个表达式的时候可以省略{}
\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map(x => x + 1 );\n
\nJavaScript语言的this对象一直是一个令人头痛的问题,运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n}\nvar animal = new Animal();\nanimal.says('hi' ); \n
\n解决办法:
\n\nsays(say) {\nvar self = this ;\nsetTimeout(function ( ) {\nconsole .log(self.type + ' says ' + say);\n}, 1000 );\n}\n \n\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}.bind(this ), 1000 );\n}\n \n\n\nsays(say) {\nsetTimeout(() => {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n
\ntemplate string (模板字符串) \n解决了 ES5 在字符串功能上的痛点。
\n第一个用途:字符串拼接。将表达式嵌入字符串中进行拼接,用 和${}
来界定。
\n\nvar name1 = \"bai\" ;\nconsole .log('hello' + name1);\n \n\nconst name2 = \"ming\" ;\nconsole .log(`hello${name2} ` );\n
\n第二个用途:在ES5时我们通过反斜杠来做多行字符串拼接。ES6反引号 `` 直接搞定。
\n\nvar msg = \"Hi \\\nman!\" ;\n \n\nconst template = `<div>\n<span>hello world</span>\n</div>` ;\n
\n另外:includes repeat
\n\nlet str = 'hahah' ;\nconsole .log(str.includes('y' )); \n \n\nlet s = 'he' ;\nconsole .log(s.repeat(3 )); \n
\ndestructuring (解构) \n简化数组和对象中信息的提取。
\nES6前,我们一个一个获取对象信息;
\nES6后,解构能让我们从对象或者数组里取出数据存为变量
\n\nvar people1 = {\nname : 'bai' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n};\n \nvar myName = people1.name;\nvar myAge = people1.age;\nvar myColor = people1.color[0 ];\nconsole .log(myName + '----' + myAge + '----' + myColor);\n \n\nlet people2 = {\nname : 'ming' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n}\n \nlet { name, age } = people2;\nlet [first, second] = people2.color;\nconsole .log(`${name} ----${age} ----${first} ` );\n
\ndefault 函数默认参数 \n\nfunction foo (num ) {\nnum = num || 200 ;\nreturn num;\n}\n \n\nfunction foo (num = 200 ) {\nreturn num;\n}\n
\nrest arguments (rest参数) \n解决了 es5 复杂的 arguments 问题
\nfunction foo (x, y, ...rest ) {\nreturn ((x + y) * rest.length);\n}\nfoo(1 , 2 , 'hello' , true , 7 ); \n
\nSpread Operator (展开运算符) \n第一个用途:组装数组
\nlet color = ['red' , 'yellow' ];\nlet colorful = [...color, 'green' , 'blue' ];\nconsole .log(colorful); \n
\n第二个用途:获取数组除了某几项的其他项
\nlet num = [1 , 3 , 5 , 7 , 9 ];\nlet [first, second, ...rest] = num;\nconsole .log(rest); \n
\n对象 \n对象初始化简写
\n\nfunction people (name, age ) {\nreturn {\nname : name,\nage : age\n};\n}\n \n\nfunction people (name, age ) {\nreturn {\nname,\nage\n};\n}\n
\n对象字面量简写(省略冒号与 function 关键字)
\n\nvar people1 = {\nname : 'bai' ,\ngetName : function ( ) {\nconsole .log(this .name);\n}\n};\n \n\nlet people2 = {\nname : 'bai' ,\ngetName () {\nconsole .log(this .name);\n}\n};\n
\n另外:Object.assign()
\nES6 对象提供了Object.assign()这个方法来实现浅复制。Object.assign()可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}
\nconst obj = Object .assign({}, objA, objB)\n \n\nthis .seller = Object .assign({}, this .seller, response.data)\n
\nPromise \n用同步的方式去写异步代码
\n\nfetch('/api/todos' )\n.then(res => res.json())\n.then(data => ({\ndata\n}))\n.catch(err => ({\nerr\n}));\n
\nGenerators \n生成器( generator)是能返回一个迭代器的函数。
\n生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
\n这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
\n迭代器:当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
\n\nfunction *createIterator ( ) {\nyield 1 ;\nyield 2 ;\nyield 3 ;\n}\n \n\nlet iterator = createIterator();\n \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \n
\n迭代器对异步编程作用很大,异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
\n生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
\n那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的:
\nfunction run (taskDef ) {\n\n\nlet task = taskDef();\n \n\nlet result = task.next();\n \n\nfunction step ( ) {\n\nif (!result.done) {\nresult = task.next();\nstep();\n}\n}\n \n\nstep();\n}\n
\n总结 \n以上就是 ES6 最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%
\n更多ES6语法点击这里 \n《JavaScript 语言入门教程》
\n",
"link": "\\en-us\\blog\\web\\es6.html",
"meta": {}
}
\ No newline at end of file
diff --git a/en-us/blog/web/javascript.html b/en-us/blog/web/javascript.html
new file mode 100644
index 0000000..afb3cc0
--- /dev/null
+++ b/en-us/blog/web/javascript.html
@@ -0,0 +1,685 @@
+
+
+
+
+
+
+
+
+
+ javascript
+
+
+
+
+ JavaScript 基础
+
+一、概念简介
+二、基本类型
+ 2.1 数值类型
+ 2.2 字符类型
+ 2.3 基本类型检测
+三、引用类型
+ 3.1 Object 类型
+ 3.2 Array 类型
+ 3.3 Date 类型
+ 3.4 Funcation 类型
+ 3.5 引用类型检测
+四、内置对象
+ 4.1 Global 对象
+ 4.2 window 对象
+五、作用域与闭包
+ 5.1 作用域
+ 5.2 作用域链
+ 5.3 闭包
+六、对象设计
+ 6.1 数据属性
+ 6.2 访问器属性
+ 6.3 读取属性
+ 6.4 创建对象
+
+一、概念简介
+JavaScript 是一种专为与网页交互而设计的脚本语言,由以下三个部分组成:
+
+ECMAScript :由 ECMA-262 定义,提供核心语言功能;
+文档对象模型 (DOM) :提供访问和操作网页内容的方法和接口;
+浏览器对象模型 (BOM) :提供与浏览器交互的方法和接口。
+
+ECMAScript 提供了语言的核心功能,它定义了以下七种数据类型:
+
+六种基本数据类型 :Undefined
,Null
,Boolean
,Number
,String
,Symbol
( ES 6新增 );
+一种引用数据类型 :统称为 Object 类型;具体又细分为 Object
,Array
,Date
,RegExp
,Function
等类型。另外和 Java 语言类似,对于布尔,数值,字符串等基本类型,分别存在其对应的包装类型 Boolean,Number,String,但通常我们并不会使用到这些包装类型,只需要使用其基本类型即可。
+
+二、基本类型
+2.1 数值类型
+1. 进制数值
+ECMAScript 中的 Number 支持以下三种常用进制:
+
+十进制 :正常数值就是十进制;
+八进制 :八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7);
+十六进制 :十六进制字面值的前两位必须是 0x,后跟任意的十六进制数字(0~9 及 A~F)。
+
+console .log(56 );
+console .log(070 );
+console .log(0x38 );
+
+2. 浮点数值
+ECMAScript 的数值类型同样支持浮点数,但是由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会尽量将浮点数值转换为整数值存储:
+var a = 10.0 ;
+console .log(a);
+
+和其他语言类似,浮点数中的数值也是不精准的,示例如下:
+var a = 0.1 ; var b = 0.2 ;
+
+a + b ;
+a+b === 0.3 ;
+
+如果想要对浮点数进行精确计算,可以使用 decimal.js , math.js 等第三方库。
+3. 科学计数法
+ECMAScript 支持使用科学计数法来表达数值:
+8e-2
+8e2
+
+4. parseInt() \ parseFloat()
+parseInt 可以用于解析字符串并返回整数,parseFloat 用于解析字符串并返回浮点数:
+parseInt ("56" );
+parseInt ("0x38" , 16 );
+parseInt ("56.6" );
+
+parseFloat ("12.2" );
+
+parseInt ("blue" );
+
+5. toFixed()
+toFixed 用于保留指定位数的小数,但需要注意的是其四舍五入的行为是不确定的:
+1.35 .toFixed(1 )
+1.335 .toFixed(2 )
+1.3335 .toFixed(3 )
+1.33335 .toFixed(4 )
+1.333335 .toFixed(5 )
+1.3333335 .toFixed(6 )
+
+想要解决这个问题,需要重写 toFixed 方法并通过判断最后一位是否大于或等于5来决定是否需要进位,具体代码如下:
+
+Number .prototype.toFixed = function (len ) {
+ if (len>20 || len<0 ){
+ throw new RangeError ('toFixed() digits argument must be between 0 and 20' );
+ }
+
+ var number = Number (this );
+ if (isNaN (number) || number >= Math .pow(10 , 21 )) {
+ return number.toString();
+ }
+ if (typeof (len) == 'undefined' || len == 0 ) {
+ return (Math .round(number)).toString();
+ }
+ var result = number.toString(),
+ numberArr = result.split('.' );
+
+ if (numberArr.length<2 ){
+
+ return padNum(result);
+ }
+ var intNum = numberArr[0 ],
+ deciNum = numberArr[1 ],
+ lastNum = deciNum.substr(len, 1 );
+
+ if (deciNum.length == len){
+
+ return result;
+ }
+ if (deciNum.length < len){
+
+ return padNum(result)
+ }
+
+ result = intNum + '.' + deciNum.substr(0 , len);
+ if (parseInt (lastNum, 10 )>=5 ){
+
+ var times = Math .pow(10 , len);
+ var changedInt = Number (result.replace('.' ,'' ));
+ changedInt++;
+ changedInt /= times;
+ result = padNum(changedInt+'' );
+ }
+ return result;
+
+ function padNum (num ) {
+ var dotPos = num.indexOf('.' );
+ if (dotPos === -1 ){
+
+ num += '.' ;
+ for (var i = 0 ;i<len;i++){
+ num += '0' ;
+ }
+ return num;
+ } else {
+
+ var need = len - (num.length - dotPos - 1 );
+ for (var j = 0 ;j<need;j++){
+ num += '0' ;
+ }
+ return num;
+ }
+ }
+}
+
+
+参考自:js中小数四舍五入和浮点数的研究
+
+2.2 字符类型
+1. 字符串表示
+ECMAScript 支持使用双引号 "
或单引号 '
来表示字符串,并且 ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,示例如下:
+var lang = "Java" ;
+
+lang = lang + "Script" ;
+
+2. 转换为字符串
+要把一个值转换为一个字符串有两种方式:
+
+使用对象方法 toString() :大多数对象都具有这个方法,但需要注意的是 null 和 undefined 没有;
+使用转型函数 String() :使用该转型函数时,如果传入的值有 toString() 方法,则调用该方法并返回相应的结果;如果传入的值是 null,则返回 "null" ;如果传入值是 undefined,则返回 "undefined" 。 示例如下:
+
+var a = null ;
+a.toString()
+String (a)
+
+3. 常用的字符串操作
+
+concat() :用于拼接一个或多个字符串;
+slice() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置;
+substring() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置;
+substr() :用于截取字符串,接收两个参数,分别代表截取的开始位置和截取的长度;
+indexOf() \ lastIndexOf() :均接收两个参数,分别代表待查找的字符串和查找的开始位置;
+trim() :用于去除字符串前后的空格。
+
+slice,substring,substr 等方法在传入正数参数时,其行为比较好预期,但传递参数是负数时,则具体的行为表现如下:
+
+slice() :会将传入的负值与字符串的长度相加;
+substring() :方法会把所有负值参数都转换为 0 ;
+substr() :会将第一个负值参数加上字符串的长度,如果传递了第二个参数且为负数时候,会将其转换为 0 。
+
+var stringValue = "hello world" ;
+
+
+alert(stringValue.slice(3 ));
+alert(stringValue.substring(3 ));
+alert(stringValue.substr(3 ));
+
+
+alert(stringValue.slice(3 , 7 ));
+alert(stringValue.substring(3 ,7 ));
+alert(stringValue.substr(3 , 7 ));
+
+
+alert(stringValue.slice(-3 ));
+alert(stringValue.substring(-3 ));
+alert(stringValue.substr(-3 ));
+
+
+alert(stringValue.slice(3 , -4 ));
+alert(stringValue.substring(3 , -4 ));
+alert(stringValue.substr(3 , -4 ));
+
+2.3 基本类型检测
+JavaScript 是一种弱类型的语言,在声明变量时候可以不必指明其具体类型,而是由程序进行推断。如果想要知道变量具体属于哪一个基础类型,可以使用 typeof 关键字,它的返回情况如下:
+
+undefined :如果对应的值未定义;
+boolean :如果对应的值是布尔值;
+string :如果对应的值是字符串;
+number :如果对应的值是数值;
+object :如果对应的值是对象或 null;
+function :如果对应的值是函数则返回 function。 函数在本质上也是对象,但是由于其一等公民的特殊地位,所以将其和其他普通对象进行区分是很有必要的,因此 typeof 对其检测时会返回 function ,而不是 object 。
+
+三、引用类型
+3.1 Object 类型
+创建 Object 实例有以下两种方式:
+
+使用 new 操作符后跟着 Object 构造函数;
+使用对象字面量的方式。
+
+
+var user = new Object ();
+user.name = "heibaiying" ;
+user.age = 30 ;
+
+
+var user = {
+ name : "heibaiying" ,
+ age : 30
+};
+
+3.2 Array 类型
+创建数组也有两种方式,基于构造函数的方式和基于对象字面量的方式:
+
+var colors = new Array ();
+var colors = new Array (20 );
+var colors = new Array ("red" , "blue" , "green" );
+
+
+var names = [];
+var colors = ["red" , "blue" , "green" ];
+
+数组的长度保存在其 length 属性中,和其他语言中的 length 属性不同,这个值是不是只读的,可以用其进行数组的截断操作或添加新的数据项,示例如下:
+var colors = ["red" , "blue" , "green" ];
+
+colors.length = 2 ;
+colors[colors.length] = "green" ;
+colors[10 ] = "black" ;
+
+数组的其他常用方法如下:
+1. 检测数组
+colors instanceof Array
+Array .isArray(colors)
+
+2. 转换方法
+var colors = ["red" , "blue" , "green" ];
+
+colors.valueOf();
+colors;
+colors.toString();
+colors.join("|" );
+
+3. 栈方法
+ECMAScript 的数组提供了类似栈的特性,能够实现后进先出:
+var colors = ["red" , "blue" , "green" ];
+
+colors.push("black" );
+colors.pop()
+colors
+
+4. 队列方法
+ECMAScript 的数组提供了类似栈的特性,能够实现先进先出:
+colors.push("black" ,"yellow" );
+colors.shift()
+colors
+
+5. 重排序方法
+var values = [1 , 2 , 3 , 4 , 5 ];
+values.reverse();
+values
+
+
+function compare (value1, value2 ) {
+ if (value1 < value2) {
+ return -1 ;
+ } else if (value1 > value2) {
+ return 1 ;
+ } else {
+ return 0 ;
+ }
+}
+values.sort(compare)
+values
+
+6. 操作方法
+concat() 用于拼接并返回新的数组:
+var colors = ["red" , "green" , "blue" ];
+var colors2 = colors.concat("yellow" , ["black" , "brown" ]);
+
+colors
+colors2
+
+slice() 用于截取数组并返回新的数组,它接收两个参数,分别代表截取的开始位置和结束位置,它是一个前开后闭的区间:
+var colors = ["red" , "green" , "blue" , "yellow" , "purple" ];
+
+var colors2 = colors.slice(1 );
+var colors3 = colors.slice(0 ,2 );
+
+splice() 用于删除并在删除位置新增数据项,它接收任意个参数,其中第一个参数为删除的开始位置,第二个参数为删除多少个数据项,之后可以接任意个参数,用于表示待插入的数据项:
+var colors = ["red" , "green" , "blue" , "yellow" ];
+
+colors.splice(1 ,2 )
+colors
+
+colors.splice(1 ,0 ,"black" ,"green" )
+colors
+
+7. 位置方法
+indexOf() 和 lastIndexOf() 用于查找指定元素的 Index ,它们都接收两个参数:待查找项和查找的起点位置:
+var colors = ["red", "green", "blue", "yellow", "green", "blue"];
+
+colors.indexOf("green"); // 1
+colors.indexOf("green", 3); // 4
+colors.lastIndexOf("green"); // 4
+colors.lastIndexOf("green", 3); // 1
+
+8. 迭代方法
+ECMAScript 5 提供了五个迭代方法:
+
+every() :判断数组中的每个元素是否满足指定条件,如果全部满足则返回 true,否则返回 flase;
+some() :判断数组中的每个元素是否满足指定条件,只要有一个满足则返回 true,否则返回 flase;
+filter() :过滤并返回符合条件的元素组成的数组。
+forEach() :对数组中的每一项运行给定函数。
+map() :对数组中的每一项运行给定函数,并返回每次函数调用结果所组成的数组。
+
+var numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ];
+
+numbers.every(function (value, index, array ) {
+ return value > 3 ;
+});
+
+
+numbers.some(function (value, index, array ) {
+ return value > 3 ;
+});
+
+
+numbers.filter(function (value, index, array ) {
+ return value > 3 ;
+});
+
+
+numbers.forEach(function (value, index, array ) {
+ console .log(value);
+});
+
+numbers.map(function (value, index, array ) {
+ return value * 10 ;
+});
+
+
+9. 归并方法
+ECMAScript 5 提供了两个归并数组的方法: reduce() 和 reduceRight() 。 它们都接收四个参数:前一个值、当前值、当前项的索引 和 数组本身,使用示例如下:
+var values = [1 , 2 , 3 , 4 , 5 ];
+var sum01 = values.reduce(function (prev, cur, index, array ) {
+ return prev + cur;
+});
+
+var sum02 = values.reduceRight(function (prev, cur, index, array ) {
+ return prev + cur;
+});
+
+3.3 Date 类型
+创建一个日期对象,可以使用 new 操作符和 Date 构造函数:
+var now = new Date();
+now.toLocaleString()
+
+var date = new Date(2018 , 7 , 8 , 8 , 30 , 20 );
+date.toLocaleString();
+
+如果你只想知道当前时间的毫秒数,可以直接使用 Date 对象的静态方法:
+Date .now()
+1568426130593
+
+1. 格式转换
+
+toLocaleString() :按照浏览器所在时区返回相应的日期格式;
+toString() :返回日期时间数据和的时区数据;
+valueOf() :返回日期的时间戳格式。
+
+var date = new Date (2018 , 7 , 8 , 8 , 30 , 20 );
+
+console .log(date.toLocaleString());
+console .log(date.toString());
+console .log(date.valueOf());
+
+由于 valueOf() 返回的是日期的时间戳格式,所以对于 date 对象,可以直接使用比较运算符来比较其大小:
+var date01 = new Date (2018 , 7 , 8 , 8 , 30 , 20 );
+var date02 = new Date (2016 , 7 , 8 , 8 , 30 , 20 );
+
+console .log(date01 > date02);
+console .log(date01 < date02);
+
+2. 常用方法
+
+getTime() \ setTime(毫秒) :返回和设置整个日期的所代表的毫秒数;与 valueOf() 方法返回的值相同;
+getFullYear() \ setFullYear(年) :返回和设置4位数的年份;
+getMonth() \ setMonth(月) :返回和设置月份,其中 0 表示一月, 11 表示十二月;
+getDate() \ setDate(日) :返回和设置月份中的天数(1到31);
+getDay() :返回和设置星期几 ( 其中0表示星期日, 6表示星期六);
+getHours() \ setHours(时) :返回和设置小时数(0到23);
+getMinutes() \ setMinutes(分) :返回和设置日期中的分钟数(0到59);
+getSeconds() \ setSeconds(秒) :返回和设置日期中的秒数(0到59);
+getMilliseconds() \ setMilliseconds(毫秒) :返回和设置日期中的毫秒数。
+
+3.4 Funcation 类型
+1. 函数参数
+ECMAScript 使用 function 关键字来声明函数,但和其他语言不同的是,ECMAScript 中函数对于参数的限制是非常宽松的,例如你在定义函数时定义了两个参数,但在调用时可以只传递一个参数、也可以传三个参数,甚至不传递,示例如下:
+function test (first, second) {
+ console.log("first:" + first + ",second:" + second);
+}
+test(1 )
+test(1 ,2 )
+test(1 ,2 ,3 )
+
+之所以能实现这样的效果,是因为 ECMAScript 在函数内部使用了一个数组 arguments 来维护所有参数,函数接收到的始终都是这个数组,而在实际使用时指向的也是这个数组中的具体元素,所以以上的函数等价于下面的函数:
+function test (first, second ) {
+ console .log("first:" + arguments [0 ] + ",second:" + arguments [1 ]);
+}
+
+2. 改变函数作用域
+在 ECMAScript 5 中,每个函数都包含两个非继承而来的方法:apply() 和 call() ,它们都用于在特定的作用域中调用函数。简单来说,可以用这两个方法来改变函数的实际调用对象,从而改变 this 的值,因为 this 总是指向当前函数的实际调用对象:
+window .color = "red" ;
+var o = { color : "blue" };
+function sayColor ( ) {
+ console .log(this .color);
+}
+
+sayColor();
+sayColor.call(this );
+sayColor.call(window );
+sayColor.call(o);
+
+apply() 和 call() 的第一个参数都是指代函数的调用对象,它们的区别主要在于第二个参数:apply() 支持使用数组或 arguments 给调用函数传值,而 call() 给调用函数传值时,必须逐个列举:
+function sum (num1, num2 ) {
+ return num1 + num2;
+}
+
+function callSum1 (num1, num2 ) {
+ return sum.apply(this , arguments );
+}
+
+function callSum2 (num1, num2 ) {
+ return sum.apply(this , [num1, num2]);
+}
+
+function callSum3 (num1, num2 ) {
+ return sum.call(this , num1, num2);
+}
+
+callSum1(10 , 10 );
+callSum2(10 , 10 );
+callSum3(10 , 10 );
+
+3. 绑定函数作用域
+如果想要将函数绑定在某个特定的作用域上,可以使用 bind() 函数:
+window .color = "red" ;
+var o = { color : "blue" };
+function sayColor ( ) {
+ console .log(this .color);
+}
+
+var objectSayColor = sayColor.bind(o);
+objectSayColor();
+
+3.5 引用类型检测
+想要检测某个对象是否属于某个引用类型,可以使用 instanceof 关键字:
+var date = new Date();
+date instanceof Date // true
+
+四、内置对象
+4.1 Global 对象
+ECMAScript 中内置了一个全局对象 Global ,任何不属于任何其他对象的属性和方法,最终都是它的属性和方法。 ES 通过该内置对象,提供了一些可以直接调用的全局方法,常用的如下:
+
+isNaN() :用于确定一个值是否为 NaN;
+isFinite() :用于判断被传入的参数值是否为一个有限数值;
+parseInt() \ parseFloat() :解析并返回一个整数 \ 浮点数;
+encodeURI() :对 URI 进行编码,但不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井号;
+encodeURIComponent() :对 URI 进行编码,会对任何非标准字符进行编码。
+
+4.2 window 对象
+ECMAScript 并没有提供任何直接访问 Global 对象的方法,但是浏览器却基于 Global 扩展实现了 window 对象,可以直接在浏览器环境下使用:
+window .isFinite(12 )
+a = 12 ;
+window .a
+
+五、作用域与闭包
+5.1 作用域
+在 ECMAScript 6 之前,只存在两种作用域,即:全局作用域 和 函数作用域,不存在块级作用域。这意味着在除了函数外的任何代码块中使用 var 关键字声明的变量都会被提升为全局变量,示例如下:
+function test ( ) {
+ var age =12 ;
+}
+age
+
+if (true ) {
+ var name = "heibaiying" ;
+}
+name
+
+这种情况同样适用与 for 循环代码块:
+for (var i = 0 ; i < 10 ; i++) {}
+console .log(i);
+
+5.2 作用域链
+由于函数作用域的存在,函数内的变量不能被外部访问,但是函数内的变量可以被其内部的函数访问,并且函数也可以访问其父级作用域上的变量,从而形成一条从其自身作用域到全局作用域的链条,示例如下:
+var global = "global" ;
+var outer = "outer global" ;
+
+(function outer ( ) {
+ var outer = "outer" ;
+
+ function inner ( ) {
+ console .log(global, outer);
+ }
+
+ inner()
+})();
+
+
+
+5.3 闭包
+由于函数作用域的存在,函数内的变量不能被外部访问,这可以保证变量的私有性。但如果你想允许外部对内部变量进行特定操作,可以通过闭包来实现。闭包是指有权访问另一个函数作用域中的变量的函数。示例如下:
+var contain = function () {
+
+ var arr = [];
+
+ return {
+ push: function () {
+ arr.push(...arguments);
+ },
+
+ get: function () {
+ return arr;
+ }
+ }
+};
+
+var ctn = contain();
+ctn.push(1 , 2 , 3 , 4 );
+ctn.get();
+
+六、对象设计
+ECMAScript 中的对象都有两种基本属性:数据属性和访问器属性。
+6.1 数据属性
+数据属性有以下 4 个描述其行为的特性:
+
+Enumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。
+Writable :表示能否修改属性的值;对于直接在对象上定义的属性, 该值默认为 true。
+Value :对应属性的数据值。默认值为 undefined。
+Configurable :表示能否对属性进行删除,修改等配置操作,对于直接在对象上定义的属性, 该值默认为 true。需要注意的是一旦将该属性的值设置为 false,就不能再将其设置为 true 。即一旦设置为不可配置,就不能再修改为可配置。因为你已经修改为不可配置,此时任何配置操作都无效了,自然修改 Configurable 属性的操作也无效。
+
+var person = {age : 12 };
+Object .defineProperty(person, "name" , {
+ Enumerable : false ,
+ writable : false ,
+ value : "heibai"
+});
+
+console .log(person.name);
+person.name = "ying" ;
+console .log(person.name);
+
+for (var key in person) {
+ console .log("key:" + key + ",value:" + person[key])
+}
+
+6.2 访问器属性
+访问器属性也有以下 4 个描述其行为的特性:
+
+Configurable :表示能否对属性进行删除,修改等配置操作;对于直接在对象上定义的属性, 该值默认为 true。
+Enumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。
+Get :在读取属性时调用的函数。默认值为 undefined。
+Set :在写入属性时调用的函数。默认值为 undefined。
+
+var student = {
+ _age : null ,
+ birthday : new Date (2012 ,7 ,2 )
+};
+Object .defineProperty(student, "age" , {
+
+ get : function ( ) {
+ if (this ._age == null ) {
+
+ return new Date ().getFullYear() - this .birthday.getFullYear()
+ }else {
+ return this ._age;
+ }
+ },
+ set : function (newValue ) {
+ if (newValue < 0 ) {
+ console .log("年龄不能设置为负数" );
+ } else {
+ this ._age = newValue;
+ }
+ }
+});
+
+student.age = -1 ;
+console .log(student.age);
+student.age = 12 ;
+console .log(student.age);
+
+6.3 读取属性
+想要获取一个对象的数据属性和访问器属性,可以使用 Object.getOwnPropertyDescriptor() 方法,类似于其他语言中的反射机制。这个方法接收两个参数:属性所在的对象和要读取属性名称。沿用上面的例子,示例如下:
+var descriptor = Object .getOwnPropertyDescriptor(student, "age" );
+console .log(descriptor.get);
+console .log(descriptor.enumerable);
+
+6.4 创建对象
+在 ECMAScript 中,对象就是一种特殊的函数,想要声明一个对象,可以结合使用构造器模式和原型模式:基本属性可以通过构造器传入;但方法声明需要定义在原型属性上,如果直接定义在构造器上,每个对象实例都会创建该方法函数,即每个对象实例调用的都是自己重复声明的方法函数,示例如下:
+function Person (name, age, job ) {
+ this .name = name;
+ this .age = age;
+ this .job = job;
+ this .friends = ["hei" , "bai" ];
+
+ this .sayAge = function ( ) {
+ console .log(this .age)
+ }
+}
+
+Person.prototype = {
+ constructor : Person,
+
+ sayName: function ( ) {
+ alert(this .name);
+ }
+}
+
+
+var person1 = new Person("user01" , 29 , "Software Engineer" );
+var person2 = new Person("user02" , 27 , "Doctor" );
+
+person1.friends.push("ying" );
+console .log(person1.friends);
+console .log(person2.friends);
+console .log(person1 instanceof Person);
+console .log(person1.constructor === Person);
+console .log(person1.sayName === person2.sayName);
+console .log(person1.sayAge===person2.sayAge);
+
+参考资料
+
+尼古拉斯·泽卡斯 . JavaScript高级程序设计(第3版). 人民邮电出版社 . 2012-3-29
+JS中小数四舍五入和浮点数的研究
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/web/javascript.json b/en-us/blog/web/javascript.json
new file mode 100644
index 0000000..e5ea221
--- /dev/null
+++ b/en-us/blog/web/javascript.json
@@ -0,0 +1,6 @@
+{
+ "filename": "javascript.md",
+ "__html": "JavaScript 基础 \n\n一、概念简介 \n二、基本类型 \n 2.1 数值类型 \n 2.2 字符类型 \n 2.3 基本类型检测 \n三、引用类型 \n 3.1 Object 类型 \n 3.2 Array 类型 \n 3.3 Date 类型 \n 3.4 Funcation 类型 \n 3.5 引用类型检测 \n四、内置对象 \n 4.1 Global 对象 \n 4.2 window 对象 \n五、作用域与闭包 \n 5.1 作用域 \n 5.2 作用域链 \n 5.3 闭包 \n六、对象设计 \n 6.1 数据属性 \n 6.2 访问器属性 \n 6.3 读取属性 \n 6.4 创建对象 \n \n一、概念简介 \nJavaScript 是一种专为与网页交互而设计的脚本语言,由以下三个部分组成:
\n\nECMAScript :由 ECMA-262 定义,提供核心语言功能; \n文档对象模型 (DOM) :提供访问和操作网页内容的方法和接口; \n浏览器对象模型 (BOM) :提供与浏览器交互的方法和接口。 \n \nECMAScript 提供了语言的核心功能,它定义了以下七种数据类型:
\n\n六种基本数据类型 :Undefined
,Null
,Boolean
,Number
,String
,Symbol
( ES 6新增 ); \n一种引用数据类型 :统称为 Object 类型;具体又细分为 Object
,Array
,Date
,RegExp
,Function
等类型。另外和 Java 语言类似,对于布尔,数值,字符串等基本类型,分别存在其对应的包装类型 Boolean,Number,String,但通常我们并不会使用到这些包装类型,只需要使用其基本类型即可。 \n \n二、基本类型 \n2.1 数值类型 \n1. 进制数值
\nECMAScript 中的 Number 支持以下三种常用进制:
\n\n十进制 :正常数值就是十进制; \n八进制 :八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7); \n十六进制 :十六进制字面值的前两位必须是 0x,后跟任意的十六进制数字(0~9 及 A~F)。 \n \nconsole .log(56 ); \nconsole .log(070 ); \nconsole .log(0x38 ); \n
\n2. 浮点数值
\nECMAScript 的数值类型同样支持浮点数,但是由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会尽量将浮点数值转换为整数值存储:
\nvar a = 10.0 ;\nconsole .log(a); \n
\n和其他语言类似,浮点数中的数值也是不精准的,示例如下:
\nvar a = 0.1 ; var b = 0.2 ;\n\na + b ; \na+b === 0.3 ; \n
\n如果想要对浮点数进行精确计算,可以使用 decimal.js , math.js 等第三方库。
\n3. 科学计数法
\nECMAScript 支持使用科学计数法来表达数值:
\n8e-2 \n8e2 \n
\n4. parseInt() \\ parseFloat()
\nparseInt 可以用于解析字符串并返回整数,parseFloat 用于解析字符串并返回浮点数:
\nparseInt (\"56\" ); \nparseInt (\"0x38\" , 16 ); \nparseInt (\"56.6\" ); \n\nparseFloat (\"12.2\" ); \n\nparseInt (\"blue\" ); \n
\n5. toFixed()
\ntoFixed 用于保留指定位数的小数,但需要注意的是其四舍五入的行为是不确定的:
\n1.35 .toFixed(1 ) \n1.335 .toFixed(2 ) \n1.3335 .toFixed(3 ) \n1.33335 .toFixed(4 ) \n1.333335 .toFixed(5 ) \n1.3333335 .toFixed(6 ) \n
\n想要解决这个问题,需要重写 toFixed 方法并通过判断最后一位是否大于或等于5来决定是否需要进位,具体代码如下:
\n\nNumber .prototype.toFixed = function (len ) {\n if (len>20 || len<0 ){\n throw new RangeError ('toFixed() digits argument must be between 0 and 20' );\n }\n \n var number = Number (this );\n if (isNaN (number) || number >= Math .pow(10 , 21 )) {\n return number.toString();\n }\n if (typeof (len) == 'undefined' || len == 0 ) {\n return (Math .round(number)).toString();\n }\n var result = number.toString(),\n numberArr = result.split('.' );\n\n if (numberArr.length<2 ){\n \n return padNum(result);\n }\n var intNum = numberArr[0 ], \n deciNum = numberArr[1 ],\n lastNum = deciNum.substr(len, 1 );\n \n if (deciNum.length == len){\n \n return result;\n }\n if (deciNum.length < len){\n \n return padNum(result)\n }\n \n result = intNum + '.' + deciNum.substr(0 , len);\n if (parseInt (lastNum, 10 )>=5 ){\n \n var times = Math .pow(10 , len); \n var changedInt = Number (result.replace('.' ,'' ));\n changedInt++;\n changedInt /= times;\n result = padNum(changedInt+'' );\n }\n return result;\n \n function padNum (num ) {\n var dotPos = num.indexOf('.' );\n if (dotPos === -1 ){\n \n num += '.' ;\n for (var i = 0 ;i<len;i++){\n num += '0' ;\n }\n return num;\n } else {\n \n var need = len - (num.length - dotPos - 1 );\n for (var j = 0 ;j<need;j++){\n num += '0' ;\n }\n return num;\n }\n }\n}\n
\n\n参考自:js中小数四舍五入和浮点数的研究
\n \n2.2 字符类型 \n1. 字符串表示
\nECMAScript 支持使用双引号 "
或单引号 '
来表示字符串,并且 ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,示例如下:
\nvar lang = \"Java\" ;\n\nlang = lang + \"Script\" ;\n
\n2. 转换为字符串
\n要把一个值转换为一个字符串有两种方式:
\n\n使用对象方法 toString() :大多数对象都具有这个方法,但需要注意的是 null 和 undefined 没有; \n使用转型函数 String() :使用该转型函数时,如果传入的值有 toString() 方法,则调用该方法并返回相应的结果;如果传入的值是 null,则返回 "null" ;如果传入值是 undefined,则返回 "undefined" 。 示例如下: \n \nvar a = null ;\na.toString() \nString (a) \n
\n3. 常用的字符串操作
\n\nconcat() :用于拼接一个或多个字符串; \nslice() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置; \nsubstring() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置; \nsubstr() :用于截取字符串,接收两个参数,分别代表截取的开始位置和截取的长度; \nindexOf() \\ lastIndexOf() :均接收两个参数,分别代表待查找的字符串和查找的开始位置; \ntrim() :用于去除字符串前后的空格。 \n \nslice,substring,substr 等方法在传入正数参数时,其行为比较好预期,但传递参数是负数时,则具体的行为表现如下:
\n\nslice() :会将传入的负值与字符串的长度相加; \nsubstring() :方法会把所有负值参数都转换为 0 ; \nsubstr() :会将第一个负值参数加上字符串的长度,如果传递了第二个参数且为负数时候,会将其转换为 0 。 \n \nvar stringValue = \"hello world\" ;\n\n\nalert(stringValue.slice(3 )); \nalert(stringValue.substring(3 )); \nalert(stringValue.substr(3 )); \n\n\nalert(stringValue.slice(3 , 7 )); \nalert(stringValue.substring(3 ,7 )); \nalert(stringValue.substr(3 , 7 )); \n\n\nalert(stringValue.slice(-3 )); \nalert(stringValue.substring(-3 )); \nalert(stringValue.substr(-3 )); \n\n\nalert(stringValue.slice(3 , -4 )); \nalert(stringValue.substring(3 , -4 )); \nalert(stringValue.substr(3 , -4 )); \n
\n2.3 基本类型检测 \nJavaScript 是一种弱类型的语言,在声明变量时候可以不必指明其具体类型,而是由程序进行推断。如果想要知道变量具体属于哪一个基础类型,可以使用 typeof 关键字,它的返回情况如下:
\n\nundefined :如果对应的值未定义; \nboolean :如果对应的值是布尔值; \nstring :如果对应的值是字符串; \nnumber :如果对应的值是数值; \nobject :如果对应的值是对象或 null; \nfunction :如果对应的值是函数则返回 function。 函数在本质上也是对象,但是由于其一等公民的特殊地位,所以将其和其他普通对象进行区分是很有必要的,因此 typeof 对其检测时会返回 function ,而不是 object 。 \n \n三、引用类型 \n3.1 Object 类型 \n创建 Object 实例有以下两种方式:
\n\n使用 new 操作符后跟着 Object 构造函数; \n使用对象字面量的方式。 \n \n\nvar user = new Object ();\nuser.name = \"heibaiying\" ;\nuser.age = 30 ;\n\n\nvar user = {\n name : \"heibaiying\" ,\n age : 30 \n};\n
\n3.2 Array 类型 \n创建数组也有两种方式,基于构造函数的方式和基于对象字面量的方式:
\n\nvar colors = new Array ();\nvar colors = new Array (20 );\nvar colors = new Array (\"red\" , \"blue\" , \"green\" );\n\n\nvar names = [];\nvar colors = [\"red\" , \"blue\" , \"green\" ];\n
\n数组的长度保存在其 length 属性中,和其他语言中的 length 属性不同,这个值是不是只读的,可以用其进行数组的截断操作或添加新的数据项,示例如下:
\nvar colors = [\"red\" , \"blue\" , \"green\" ]; \n\ncolors.length = 2 ; \ncolors[colors.length] = \"green\" ; \ncolors[10 ] = \"black\" ; \n
\n数组的其他常用方法如下:
\n1. 检测数组
\ncolors instanceof Array \nArray .isArray(colors)\n
\n2. 转换方法
\nvar colors = [\"red\" , \"blue\" , \"green\" ];\n\ncolors.valueOf(); \ncolors; \ncolors.toString(); \ncolors.join(\"|\" ); \n
\n3. 栈方法
\nECMAScript 的数组提供了类似栈的特性,能够实现后进先出:
\nvar colors = [\"red\" , \"blue\" , \"green\" ];\n\ncolors.push(\"black\" ); \ncolors.pop() \ncolors \n
\n4. 队列方法
\nECMAScript 的数组提供了类似栈的特性,能够实现先进先出:
\ncolors.push(\"black\" ,\"yellow\" ); \ncolors.shift() \ncolors \n
\n5. 重排序方法
\nvar values = [1 , 2 , 3 , 4 , 5 ];\nvalues.reverse();\nvalues \n\n\nfunction compare (value1, value2 ) {\n if (value1 < value2) {\n return -1 ;\n } else if (value1 > value2) {\n return 1 ;\n } else {\n return 0 ;\n }\n}\nvalues.sort(compare)\nvalues \n
\n6. 操作方法
\nconcat() 用于拼接并返回新的数组:
\nvar colors = [\"red\" , \"green\" , \"blue\" ];\nvar colors2 = colors.concat(\"yellow\" , [\"black\" , \"brown\" ]);\n\ncolors \ncolors2 \n
\nslice() 用于截取数组并返回新的数组,它接收两个参数,分别代表截取的开始位置和结束位置,它是一个前开后闭的区间:
\nvar colors = [\"red\" , \"green\" , \"blue\" , \"yellow\" , \"purple\" ];\n\nvar colors2 = colors.slice(1 ); \nvar colors3 = colors.slice(0 ,2 ); \n
\nsplice() 用于删除并在删除位置新增数据项,它接收任意个参数,其中第一个参数为删除的开始位置,第二个参数为删除多少个数据项,之后可以接任意个参数,用于表示待插入的数据项:
\nvar colors = [\"red\" , \"green\" , \"blue\" , \"yellow\" ];\n\ncolors.splice(1 ,2 ) \ncolors \n\ncolors.splice(1 ,0 ,\"black\" ,\"green\" ) \ncolors \n
\n7. 位置方法
\nindexOf() 和 lastIndexOf() 用于查找指定元素的 Index ,它们都接收两个参数:待查找项和查找的起点位置:
\nvar colors = [\"red\", \"green\", \"blue\", \"yellow\", \"green\", \"blue\"];\n\ncolors.indexOf(\"green\"); // 1\ncolors.indexOf(\"green\", 3); // 4\ncolors.lastIndexOf(\"green\"); // 4\ncolors.lastIndexOf(\"green\", 3); // 1\n
\n8. 迭代方法
\nECMAScript 5 提供了五个迭代方法:
\n\nevery() :判断数组中的每个元素是否满足指定条件,如果全部满足则返回 true,否则返回 flase; \nsome() :判断数组中的每个元素是否满足指定条件,只要有一个满足则返回 true,否则返回 flase; \nfilter() :过滤并返回符合条件的元素组成的数组。 \nforEach() :对数组中的每一项运行给定函数。 \nmap() :对数组中的每一项运行给定函数,并返回每次函数调用结果所组成的数组。 \n \nvar numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ];\n\nnumbers.every(function (value, index, array ) {\n return value > 3 ;\n}); \n\n\nnumbers.some(function (value, index, array ) {\n return value > 3 ;\n}); \n\n\nnumbers.filter(function (value, index, array ) {\n return value > 3 ;\n}); \n\n\nnumbers.forEach(function (value, index, array ) {\n console .log(value);\n});\n\nnumbers.map(function (value, index, array ) {\n return value * 10 ;\n}); \n\n
\n9. 归并方法
\nECMAScript 5 提供了两个归并数组的方法: reduce() 和 reduceRight() 。 它们都接收四个参数:前一个值、当前值、当前项的索引 和 数组本身,使用示例如下:
\nvar values = [1 , 2 , 3 , 4 , 5 ];\nvar sum01 = values.reduce(function (prev, cur, index, array ) {\n return prev + cur;\n}); \n\nvar sum02 = values.reduceRight(function (prev, cur, index, array ) {\n return prev + cur;\n}); \n
\n3.3 Date 类型 \n创建一个日期对象,可以使用 new 操作符和 Date 构造函数:
\nvar now = new Date();\nnow.toLocaleString() \n \nvar date = new Date(2018 , 7 , 8 , 8 , 30 , 20 );\ndate.toLocaleString(); \n
\n如果你只想知道当前时间的毫秒数,可以直接使用 Date 对象的静态方法:
\nDate .now()\n1568426130593 \n
\n1. 格式转换
\n\ntoLocaleString() :按照浏览器所在时区返回相应的日期格式; \ntoString() :返回日期时间数据和的时区数据; \nvalueOf() :返回日期的时间戳格式。 \n \nvar date = new Date (2018 , 7 , 8 , 8 , 30 , 20 );\n\nconsole .log(date.toLocaleString()); \nconsole .log(date.toString()); \nconsole .log(date.valueOf()); \n
\n由于 valueOf() 返回的是日期的时间戳格式,所以对于 date 对象,可以直接使用比较运算符来比较其大小:
\nvar date01 = new Date (2018 , 7 , 8 , 8 , 30 , 20 );\nvar date02 = new Date (2016 , 7 , 8 , 8 , 30 , 20 );\n\nconsole .log(date01 > date02); \nconsole .log(date01 < date02); \n
\n2. 常用方法
\n\ngetTime() \\ setTime(毫秒) :返回和设置整个日期的所代表的毫秒数;与 valueOf() 方法返回的值相同; \ngetFullYear() \\ setFullYear(年) :返回和设置4位数的年份; \ngetMonth() \\ setMonth(月) :返回和设置月份,其中 0 表示一月, 11 表示十二月; \ngetDate() \\ setDate(日) :返回和设置月份中的天数(1到31); \ngetDay() :返回和设置星期几 ( 其中0表示星期日, 6表示星期六); \ngetHours() \\ setHours(时) :返回和设置小时数(0到23); \ngetMinutes() \\ setMinutes(分) :返回和设置日期中的分钟数(0到59); \ngetSeconds() \\ setSeconds(秒) :返回和设置日期中的秒数(0到59); \ngetMilliseconds() \\ setMilliseconds(毫秒) :返回和设置日期中的毫秒数。 \n \n3.4 Funcation 类型 \n1. 函数参数
\nECMAScript 使用 function 关键字来声明函数,但和其他语言不同的是,ECMAScript 中函数对于参数的限制是非常宽松的,例如你在定义函数时定义了两个参数,但在调用时可以只传递一个参数、也可以传三个参数,甚至不传递,示例如下:
\nfunction test (first, second) {\n console.log(\"first:\" + first + \",second:\" + second);\n}\ntest(1 ) \ntest(1 ,2 ) \ntest(1 ,2 ,3 ) \n
\n之所以能实现这样的效果,是因为 ECMAScript 在函数内部使用了一个数组 arguments 来维护所有参数,函数接收到的始终都是这个数组,而在实际使用时指向的也是这个数组中的具体元素,所以以上的函数等价于下面的函数:
\nfunction test (first, second ) {\n console .log(\"first:\" + arguments [0 ] + \",second:\" + arguments [1 ]);\n}\n
\n2. 改变函数作用域
\n在 ECMAScript 5 中,每个函数都包含两个非继承而来的方法:apply() 和 call() ,它们都用于在特定的作用域中调用函数。简单来说,可以用这两个方法来改变函数的实际调用对象,从而改变 this 的值,因为 this 总是指向当前函数的实际调用对象:
\nwindow .color = \"red\" ;\nvar o = { color : \"blue\" };\nfunction sayColor ( ) {\n console .log(this .color);\n}\n\nsayColor(); \nsayColor.call(this ); \nsayColor.call(window ); \nsayColor.call(o); \n
\napply() 和 call() 的第一个参数都是指代函数的调用对象,它们的区别主要在于第二个参数:apply() 支持使用数组或 arguments 给调用函数传值,而 call() 给调用函数传值时,必须逐个列举:
\nfunction sum (num1, num2 ) {\n return num1 + num2;\n}\n\nfunction callSum1 (num1, num2 ) {\n return sum.apply(this , arguments );\n}\n\nfunction callSum2 (num1, num2 ) {\n return sum.apply(this , [num1, num2]);\n}\n\nfunction callSum3 (num1, num2 ) {\n return sum.call(this , num1, num2);\n}\n\ncallSum1(10 , 10 );\ncallSum2(10 , 10 );\ncallSum3(10 , 10 );\n
\n3. 绑定函数作用域
\n如果想要将函数绑定在某个特定的作用域上,可以使用 bind() 函数:
\nwindow .color = \"red\" ;\nvar o = { color : \"blue\" };\nfunction sayColor ( ) {\n console .log(this .color);\n}\n\nvar objectSayColor = sayColor.bind(o);\nobjectSayColor(); \n
\n3.5 引用类型检测 \n想要检测某个对象是否属于某个引用类型,可以使用 instanceof 关键字:
\nvar date = new Date();\ndate instanceof Date // true\n
\n四、内置对象 \n4.1 Global 对象 \nECMAScript 中内置了一个全局对象 Global ,任何不属于任何其他对象的属性和方法,最终都是它的属性和方法。 ES 通过该内置对象,提供了一些可以直接调用的全局方法,常用的如下:
\n\nisNaN() :用于确定一个值是否为 NaN; \nisFinite() :用于判断被传入的参数值是否为一个有限数值; \nparseInt() \\ parseFloat() :解析并返回一个整数 \\ 浮点数; \nencodeURI() :对 URI 进行编码,但不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井号; \nencodeURIComponent() :对 URI 进行编码,会对任何非标准字符进行编码。 \n \n4.2 window 对象 \nECMAScript 并没有提供任何直接访问 Global 对象的方法,但是浏览器却基于 Global 扩展实现了 window 对象,可以直接在浏览器环境下使用:
\nwindow .isFinite(12 ) \na = 12 ;\nwindow .a \n
\n五、作用域与闭包 \n5.1 作用域 \n在 ECMAScript 6 之前,只存在两种作用域,即:全局作用域 和 函数作用域,不存在块级作用域。这意味着在除了函数外的任何代码块中使用 var 关键字声明的变量都会被提升为全局变量,示例如下:
\nfunction test ( ) {\n var age =12 ;\n}\nage \n\nif (true ) {\n var name = \"heibaiying\" ;\n}\nname \n
\n这种情况同样适用与 for 循环代码块:
\nfor (var i = 0 ; i < 10 ; i++) {}\nconsole .log(i); \n
\n5.2 作用域链 \n由于函数作用域的存在,函数内的变量不能被外部访问,但是函数内的变量可以被其内部的函数访问,并且函数也可以访问其父级作用域上的变量,从而形成一条从其自身作用域到全局作用域的链条,示例如下:
\nvar global = \"global\" ;\nvar outer = \"outer global\" ;\n\n(function outer ( ) {\n var outer = \"outer\" ;\n\n function inner ( ) {\n console .log(global, outer);\n }\n\n inner()\n})();\n\n\n
\n5.3 闭包 \n由于函数作用域的存在,函数内的变量不能被外部访问,这可以保证变量的私有性。但如果你想允许外部对内部变量进行特定操作,可以通过闭包来实现。闭包是指有权访问另一个函数作用域中的变量的函数。示例如下:
\nvar contain = function () {\n \n var arr = [];\n\n return {\n push: function () {\n arr.push(...arguments);\n },\n \n get: function () {\n return arr;\n }\n }\n};\n\nvar ctn = contain();\nctn.push(1 , 2 , 3 , 4 );\nctn.get(); \n
\n六、对象设计 \nECMAScript 中的对象都有两种基本属性:数据属性和访问器属性。
\n6.1 数据属性 \n数据属性有以下 4 个描述其行为的特性:
\n\nEnumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。 \nWritable :表示能否修改属性的值;对于直接在对象上定义的属性, 该值默认为 true。 \nValue :对应属性的数据值。默认值为 undefined。 \nConfigurable :表示能否对属性进行删除,修改等配置操作,对于直接在对象上定义的属性, 该值默认为 true。需要注意的是一旦将该属性的值设置为 false,就不能再将其设置为 true 。即一旦设置为不可配置,就不能再修改为可配置。因为你已经修改为不可配置,此时任何配置操作都无效了,自然修改 Configurable 属性的操作也无效。 \n \nvar person = {age : 12 };\nObject .defineProperty(person, \"name\" , {\n Enumerable : false ,\n writable : false ,\n value : \"heibai\" \n});\n\nconsole .log(person.name); \nperson.name = \"ying\" ;\nconsole .log(person.name); \n\nfor (var key in person) {\n console .log(\"key:\" + key + \",value:\" + person[key]) \n}\n
\n6.2 访问器属性 \n访问器属性也有以下 4 个描述其行为的特性:
\n\nConfigurable :表示能否对属性进行删除,修改等配置操作;对于直接在对象上定义的属性, 该值默认为 true。 \nEnumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。 \nGet :在读取属性时调用的函数。默认值为 undefined。 \nSet :在写入属性时调用的函数。默认值为 undefined。 \n \nvar student = {\n _age : null ,\n birthday : new Date (2012 ,7 ,2 )\n};\nObject .defineProperty(student, \"age\" , {\n \n get : function ( ) {\n if (this ._age == null ) {\n \n return new Date ().getFullYear() - this .birthday.getFullYear()\n }else {\n return this ._age;\n }\n },\n set : function (newValue ) {\n if (newValue < 0 ) {\n console .log(\"年龄不能设置为负数\" );\n } else {\n this ._age = newValue;\n }\n }\n});\n\nstudent.age = -1 ; \nconsole .log(student.age); \nstudent.age = 12 ;\nconsole .log(student.age); \n
\n6.3 读取属性 \n想要获取一个对象的数据属性和访问器属性,可以使用 Object.getOwnPropertyDescriptor() 方法,类似于其他语言中的反射机制。这个方法接收两个参数:属性所在的对象和要读取属性名称。沿用上面的例子,示例如下:
\nvar descriptor = Object .getOwnPropertyDescriptor(student, \"age\" );\nconsole .log(descriptor.get); \nconsole .log(descriptor.enumerable); \n
\n6.4 创建对象 \n在 ECMAScript 中,对象就是一种特殊的函数,想要声明一个对象,可以结合使用构造器模式和原型模式:基本属性可以通过构造器传入;但方法声明需要定义在原型属性上,如果直接定义在构造器上,每个对象实例都会创建该方法函数,即每个对象实例调用的都是自己重复声明的方法函数,示例如下:
\nfunction Person (name, age, job ) {\n this .name = name;\n this .age = age;\n this .job = job;\n this .friends = [\"hei\" , \"bai\" ];\n \n this .sayAge = function ( ) {\n console .log(this .age)\n }\n}\n\nPerson.prototype = {\n constructor : Person,\n \n sayName: function ( ) {\n alert(this .name);\n }\n}\n\n\nvar person1 = new Person(\"user01\" , 29 , \"Software Engineer\" );\nvar person2 = new Person(\"user02\" , 27 , \"Doctor\" );\n\nperson1.friends.push(\"ying\" );\nconsole .log(person1.friends); \nconsole .log(person2.friends); \nconsole .log(person1 instanceof Person); \nconsole .log(person1.constructor === Person); \nconsole .log(person1.sayName === person2.sayName); \nconsole .log(person1.sayAge===person2.sayAge); \n
\n参考资料 \n\n尼古拉斯·泽卡斯 . JavaScript高级程序设计(第3版). 人民邮电出版社 . 2012-3-29 \nJS中小数四舍五入和浮点数的研究 \n \n",
+ "link": "\\en-us\\blog\\web\\javascript.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/web/js_tool_method.html b/en-us/blog/web/js_tool_method.html
new file mode 100644
index 0000000..e42ecb1
--- /dev/null
+++ b/en-us/blog/web/js_tool_method.html
@@ -0,0 +1,978 @@
+
+
+
+
+
+
+
+
+
+ js_tool_method
+
+
+
+
+ JavaScript 工具函数大全
+数组
+
+all:布尔全等判断
+
+const all = (arr, fn = Boolean ) => arr.every(fn);
+
+all([4 , 2 , 3 ], x => x > 1 );
+all([1 , 2 , 3 ]);
+
+
+
+allEqual:检查数组各项相等
+
+const allEqual = arr => arr.every(val => val === arr[0 ]);
+
+allEqual([1 , 2 , 3 , 4 , 5 , 6 ]);
+allEqual([1 , 1 , 1 , 1 ]);
+
+
+
+approximatelyEqual:约等于
+
+const approximatelyEqual = (v1, v2, epsilon = 0.001 ) => Math .abs(v1 - v2) < epsilon;
+
+approximatelyEqual(Math .PI / 2.0 , 1.5708 );
+
+
+
+arrayToCSV:数组转CSV格式(带空格的字符串)
+
+
+const arrayToCSV = (arr, delimiter = ',' ) =>
+ arr.map(v => v.map(x => `"${x} "` ).join(delimiter)).join('\n' );
+
+arrayToCSV([['a' , 'b' ], ['c' , 'd' ]]);
+arrayToCSV([['a' , 'b' ], ['c' , 'd' ]], ';' );
+
+
+
+arrayToHtmlList:数组转li列表
+
+const arrayToHtmlList = (arr, listID ) =>
+ (el => (
+ (el = document .querySelector('#' + listID)),
+ (el.innerHTML += arr.map(item => `<li>${item} </li>` ).join('' ))
+ ))();
+
+arrayToHtmlList(['item 1' , 'item 2' ], 'myListID' );
+
+
+
+average:平均数
+
+const average = (...nums ) => nums.reduce((acc, val ) => acc + val, 0 ) / nums.length;
+average(...[1 , 2 , 3 ]);
+average(1 , 2 , 3 );
+
+
+
+averageBy:数组对象属性平均数
+
+此代码段将获取数组对象属性的平均值
+const averageBy = (arr, fn ) =>
+ arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val ) => acc + val, 0 ) /
+ arr.length;
+
+averageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], o => o.n);
+averageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], 'n' );
+
+
+
+bifurcate:拆分断言后的数组
+
+可以根据每个元素返回的值,使用reduce()和push() 将元素添加到第二次参数fn中 。
+const bifurcate = (arr, filter ) =>
+ arr.reduce((acc, val, i ) => (acc[filter[i] ? 0 : 1 ].push(val), acc), [[], []]);
+bifurcate(['beep' , 'boop' , 'foo' , 'bar' ], [true , true , false , true ]);
+
+
+
+
+castArray:其它类型转数组
+
+const castArray = val => (Array .isArray(val) ? val : [val]);
+
+castArray('foo' );
+castArray([1 ]);
+castArray(1 );
+
+
+
+compact:去除数组中的无效/无用值
+
+const compact = arr => arr.filter(Boolean );
+
+compact([0 , 1 , false , 2 , '' , 3 , 'a' , 'e' * 23 , NaN , 's' , 34 ]);
+
+
+
+
+countOccurrences:检测数值出现次数
+
+const countOccurrences = (arr, val ) => arr.reduce((a, v ) => (v === val ? a + 1 : a), 0 );
+countOccurrences([1 , 1 , 2 , 1 , 2 , 3 ], 1 );
+
+
+
+deepFlatten:递归扁平化数组
+
+const deepFlatten = arr => [].concat(...arr.map(v => (Array .isArray(v) ? deepFlatten(v) : v)));
+
+deepFlatten([1 , [2 ], [[3 ], 4 ], 5 ]);
+
+
+
+difference:寻找差异(并返回第一个数组独有的)
+
+此代码段查找两个数组之间的差异,并返回第一个数组独有的。
+
+const difference = (a, b ) => {
+ const s = new Set (b);
+ return a.filter(x => !s.has(x));
+};
+
+difference([1 , 2 , 3 ], [1 , 2 , 4 ]);
+
+
+
+differenceBy:先执行再寻找差异
+
+在将给定函数应用于两个列表的每个元素之后,此方法返回两个数组之间的差异。
+const differenceBy = (a, b, fn ) => {
+ const s = new Set (b.map(fn));
+ return a.filter(x => !s.has(fn(x)));
+};
+
+differenceBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor);
+differenceBy([{ x : 2 }, { x : 1 }], [{ x : 1 }], v => v.x);
+
+
+
+dropWhile:删除不符合条件的值
+
+此代码段从数组顶部开始删除元素,直到传递的函数返回为true。
+const dropWhile = (arr, func ) => {
+ while (arr.length > 0 && !func(arr[0 ])) arr = arr.slice(1 );
+ return arr;
+};
+
+dropWhile([1 , 2 , 3 , 4 ], n => n >= 3 );
+
+
+
+flatten:指定深度扁平化数组
+
+此代码段第二参数可指定深度。
+const flatten = (arr, depth = 1 ) =>
+ arr.reduce((a, v ) => a.concat(depth > 1 && Array .isArray(v) ? flatten(v, depth - 1 ) : v), []);
+
+flatten([1 , [2 ], 3 , 4 ]);
+flatten([1 , [2 , [3 , [4 , 5 ], 6 ], 7 ], 8 ], 2 );
+
+
+
+indexOfAll:返回数组中某值的所有索引
+
+此代码段可用于获取数组中某个值的所有索引,如果此值中未包含该值,则返回一个空数组。
+const indexOfAll = (arr, val ) => arr.reduce((acc, el, i ) => (el === val ? [...acc, i] : acc), []);
+
+indexOfAll([1 , 2 , 3 , 1 , 2 , 3 ], 1 );
+indexOfAll([1 , 2 , 3 ], 4 );
+
+
+
+intersection:两数组的交集
+
+
+const intersection = (a, b ) => {
+ const s = new Set (b);
+ return a.filter(x => s.has(x));
+};
+
+intersection([1 , 2 , 3 ], [4 , 3 , 2 ]);
+
+
+
+intersectionWith:两数组都符合条件的交集
+
+此片段可用于在对两个数组的每个元素执行了函数之后,返回两个数组中存在的元素列表。
+
+const intersectionBy = (a, b, fn ) => {
+ const s = new Set (b.map(fn));
+ return a.filter(x => s.has(fn(x)));
+};
+
+intersectionBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor);
+
+
+
+intersectionWith:先比较后返回交集
+
+const intersectionWith = (a, b, comp ) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1 );
+
+intersectionWith([1 , 1.2 , 1.5 , 3 , 0 ], [1.9 , 3 , 0 , 3.9 ], (a, b) => Math .round(a) === Math .round(b));
+
+
+
+minN:返回指定长度的升序数组
+
+const minN = (arr, n = 1 ) => [...arr].sort((a, b ) => a - b).slice(0 , n);
+
+minN([1 , 2 , 3 ]);
+minN([1 , 2 , 3 ], 2 );
+
+
+
+negate:根据条件反向筛选
+
+
+const negate = func => (...args ) => !func(...args);
+
+[1 , 2 , 3 , 4 , 5 , 6 ].filter(negate(n => n % 2 === 0 ));
+
+
+
+randomIntArrayInRange:生成两数之间指定长度的随机数组
+
+const randomIntArrayInRange = (min, max, n = 1 ) =>
+ Array .from({ length : n }, () => Math .floor(Math .random() * (max - min + 1 )) + min);
+
+randomIntArrayInRange(12 , 35 , 10 );
+
+
+
+sample:在指定数组中获取随机数
+
+const sample = arr => arr[Math .floor(Math .random() * arr.length)];
+
+sample([3 , 7 , 9 , 11 ]);
+
+
+
+sampleSize:在指定数组中获取指定长度的随机数
+
+此代码段可用于从数组中获取指定长度的随机数,直至穷尽数组。 使用Fisher-Yates算法对数组中的元素进行随机选择。
+const sampleSize = ([...arr], n = 1 ) => {
+ let m = arr.length;
+ while (m) {
+ const i = Math .floor(Math .random() * m--);
+ [arr[m], arr[i]] = [arr[i], arr[m]];
+ }
+ return arr.slice(0 , n);
+};
+
+sampleSize([1 , 2 , 3 ], 2 );
+sampleSize([1 , 2 , 3 ], 4 );
+
+
+
+shuffle:“洗牌” 数组
+
+此代码段使用Fisher-Yates算法随机排序数组的元素。
+
+const shuffle = ([...arr] ) => {
+ let m = arr.length;
+ while (m) {
+ const i = Math .floor(Math .random() * m--);
+ [arr[m], arr[i]] = [arr[i], arr[m]];
+ }
+ return arr;
+};
+
+const foo = [1 , 2 , 3 ];
+shuffle(foo);
+
+
+
+nest:根据parent_id生成树结构(阿里一面真题)
+
+根据每项的parent_id,生成具体树形结构的对象。
+const nest = (items, id = null , link = 'parent_id' ) =>
+ items
+ .filter(item => item[link] === id)
+ .map(item => ({ ...item, children : nest(items, item.id) }));
+
+
+用法:
+ const comments = [
+ { id : 1 , parent_id : null },
+ { id : 2 , parent_id : 1 },
+ { id : 3 , parent_id : 1 },
+ { id : 4 , parent_id : 2 },
+ { id : 5 , parent_id : 4 }
+];
+const nestedComments = nest(comments);
+
+
+函数
+
+attempt:捕获函数运行异常
+
+该代码段执行一个函数,返回结果或捕获的错误对象。
+onst attempt = (fn, ...args ) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return e instanceof Error ? e : new Error (e);
+ }
+};
+var elements = attempt(function (selector ) {
+ return document .querySelectorAll(selector);
+}, '>_>' );
+if (elements instanceof Error ) elements = [];
+
+
+
+defer:推迟执行
+
+const defer = (fn, ...args ) => setTimeout(fn, 1 , ...args);
+
+defer(console .log, 'a' ), console .log('b' );
+
+
+
+runPromisesInSeries:运行多个Promises
+
+const runPromisesInSeries = ps => ps.reduce((p, next ) => p.then(next), Promise .resolve());
+const delay = d => new Promise (r => setTimeout(r, d));
+
+runPromisesInSeries([() => delay(1000 ), () => delay(2000 )]);
+
+
+
+
+timeTaken:计算函数执行时间
+
+
+const timeTaken = callback => {
+ console .time('timeTaken' );
+ const r = callback();
+ console .timeEnd('timeTaken' );
+ return r;
+};
+
+timeTaken(() => Math .pow(2 , 10 ));
+
+
+
+createEventHub:简单的发布/订阅模式
+
+创建一个发布/订阅(发布-订阅)事件集线,有emit,on和off方法。
+
+使用Object.create(null)创建一个空的hub对象。
+emit,根据event参数解析处理程序数组,然后.forEach()通过传入数据作为参数来运行每个处理程序。
+on,为事件创建一个数组(若不存在则为空数组),然后.push()将处理程序添加到该数组。
+off,用.findIndex()在事件数组中查找处理程序的索引,并使用.splice()删除。
+
+const createEventHub = () => ({
+ hub : Object .create(null ),
+ emit(event, data) {
+ (this .hub[event] || []).forEach(handler => handler(data));
+ },
+ on(event, handler) {
+ if (!this .hub[event]) this .hub[event] = [];
+ this .hub[event].push(handler);
+ },
+ off(event, handler) {
+ const i = (this .hub[event] || []).findIndex(h => h === handler);
+ if (i > -1 ) this .hub[event].splice(i, 1 );
+ if (this .hub[event].length === 0 ) delete this .hub[event];
+ }
+});
+
+
+用法:
+const handler = data => console .log(data);
+const hub = createEventHub();
+let increment = 0 ;
+
+
+hub.on('message' , handler);
+hub.on('message' , () => console .log('Message event fired' ));
+hub.on('increment' , () => increment++);
+
+
+hub.emit('message' , 'hello world' );
+hub.emit('message' , { hello : 'world' });
+hub.emit('increment' );
+
+
+hub.off('message' , handler);
+
+
+
+memoize:缓存函数
+
+通过实例化一个Map对象来创建一个空的缓存。
+通过检查输入值的函数输出是否已缓存,返回存储一个参数的函数,该参数将被提供给已记忆的函数;如果没有,则存储并返回它。
+const memoize = fn => {
+ const cache = new Map ();
+ const cached = function (val ) {
+ return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this , val)) && cache.get(val);
+ };
+ cached.cache = cache;
+ return cached;
+};
+
+
+
+Ps: 这个版本可能不是很清晰,还有Vue源码版的:
+
+export function cached <F : Function > (fn: F ): F {
+ const cache = Object .create(null )
+ return (function cachedFn (str: string ) {
+ const hit = cache[str]
+ return hit || (cache[str] = fn(str))
+ }: any)
+}
+
+
+
+once:只调用一次的函数
+
+const once = fn => {
+ let called = false
+ return function ( ) {
+ if (!called) {
+ called = true
+ fn.apply(this , arguments )
+ }
+ }
+};
+
+
+
+flattenObject:以键的路径扁平化对象
+
+使用递归。
+
+利用Object.keys(obj)联合Array.prototype.reduce(),以每片叶子节点转换为扁平的路径节点。
+如果键的值是一个对象,则函数使用调用适当的自身prefix以创建路径Object.assign()。
+否则,它将适当的前缀键值对添加到累加器对象。
+prefix除非您希望每个键都有一个前缀,否则应始终省略第二个参数。
+
+const flattenObject = (obj, prefix = '' ) =>
+ Object .keys(obj).reduce((acc, k ) => {
+ const pre = prefix.length ? prefix + '.' : '' ;
+ if (typeof obj[k] === 'object' ) Object .assign(acc, flattenObject(obj[k], pre + k));
+ else acc[pre + k] = obj[k];
+ return acc;
+ }, {});
+
+flattenObject({ a : { b : { c : 1 } }, d : 1 });
+
+
+
+unflattenObject:以键的路径展开对象
+
+与上面的相反,展开对象。
+const unflattenObject = obj =>
+ Object .keys(obj).reduce((acc, k ) => {
+ if (k.indexOf('.' ) !== -1 ) {
+ const keys = k.split('.' );
+ Object .assign(
+ acc,
+ JSON .parse(
+ '{' +
+ keys.map((v, i ) => (i !== keys.length - 1 ? `"${v} ":{` : `"${v} ":` )).join('' ) +
+ obj[k] +
+ '}' .repeat(keys.length)
+ )
+ );
+ } else acc[k] = obj[k];
+ return acc;
+ }, {});
+
+unflattenObject({ 'a.b.c' : 1 , d : 1 });
+
+
+这个的用途,在做Tree组件或复杂表单时取值非常舒服。
+字符串
+
+byteSize:返回字符串的字节长度
+
+const byteSize = str => new Blob([str]).size;
+
+byteSize('😀' );
+byteSize('Hello World' );
+
+
+
+capitalize:首字母大写
+
+const capitalize = ([first, ...rest] ) =>
+ first.toUpperCase() + rest.join('' );
+
+capitalize('fooBar' );
+capitalize('fooBar' , true );
+
+
+
+capitalizeEveryWord:每个单词首字母大写
+
+const capitalizeEveryWord = str => str.replace(/\b[a-z]/g , char => char.toUpperCase());
+
+capitalizeEveryWord('hello world!' );
+
+
+
+decapitalize:首字母小写
+
+const decapitalize = ([first, ...rest] ) =>
+ first.toLowerCase() + rest.join('' )
+
+decapitalize('FooBar' );
+decapitalize('FooBar' );
+
+
+
+luhnCheck:银行卡号码校验(luhn算法)
+
+Luhn算法的实现,用于验证各种标识号,例如信用卡号,IMEI号,国家提供商标识号等。
+与String.prototype.split('')结合使用,以获取数字数组。获得最后一个数字。实施luhn算法。如果被整除,则返回,否则返回。
+const luhnCheck = num => {
+ let arr = (num + '' )
+ .split('' )
+ .reverse()
+ .map(x => parseInt (x));
+ let lastDigit = arr.splice(0 , 1 )[0 ];
+ let sum = arr.reduce((acc, val, i ) => (i % 2 !== 0 ? acc + val : acc + ((val * 2 ) % 9 ) || 9 ), 0 );
+ sum += lastDigit;
+ return sum % 10 === 0 ;
+};
+
+
+用例:
+luhnCheck('4485275742308327' );
+luhnCheck(6011329933655299 );
+luhnCheck(123456789 );
+
+补充:银行卡号码的校验规则:
+银行卡号码的校验采用Luhn算法,校验过程大致如下:
+
+从右到左给卡号字符串编号,最右边第一位是1,最右边第二位是2,最右边第三位是3….
+从右向左遍历,对每一位字符t执行第三个步骤,并将每一位的计算结果相加得到一个数s。
+对每一位的计算规则:如果这一位是奇数位,则返回t本身,如果是偶数位,则先将t乘以2得到一个数n,如果n是一位数(小于10),直接返回n,否则将n的个位数和十位数相加返回。
+如果s能够整除10,则此号码有效,否则号码无效。
+
+因为最终的结果会对10取余来判断是否能够整除10,所以又叫做模10算法。
+当然,还是库比较香: bankcardinfo
+
+splitLines:将多行字符串拆分为行数组。
+
+使用String.prototype.split()和正则表达式匹配换行符并创建一个数组。
+const splitLines = str => str.split(/\r?\n/ );
+
+splitLines('This\nis a\nmultiline\nstring.\n' );
+
+
+
+stripHTMLTags:删除字符串中的HTMl标签
+
+从字符串中删除HTML / XML标签。
+使用正则表达式从字符串中删除HTML / XML 标记。
+const stripHTMLTags = str => str.replace(/<[^>]*>/g , '' );
+
+stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>' );
+
+
+对象
+
+dayOfYear:当前日期天数
+
+const dayOfYear = date =>
+ Math .floor((date - new Date (date.getFullYear(), 0 , 0 )) / 1000 / 60 / 60 / 24 );
+
+dayOfYear(new Date ());
+
+
+
+forOwn:迭代属性并执行回调
+
+const forOwn = (obj, fn ) => Object .keys(obj).forEach(key => fn(obj[key], key, obj));
+forOwn({ foo : 'bar' , a : 1 }, v => console .log(v));
+
+
+
+Get Time From Date:返回当前24小时制时间的字符串
+
+const getColonTimeFromDate = date => date.toTimeString().slice(0 , 8 );
+
+getColonTimeFromDate(new Date ());
+
+
+
+Get Days Between Dates:返回日期间的天数
+
+const getDaysDiffBetweenDates = (dateInitial, dateFinal ) =>
+ (dateFinal - dateInitial) / (1000 * 3600 * 24 );
+
+getDaysDiffBetweenDates(new Date ('2019-01-01' ), new Date ('2019-10-14' ));
+
+
+
+is:检查值是否为特定类型。
+
+const is = (type, val ) => ![, null ].includes(val) && val.constructor === type;
+
+is(Array , [1 ]);
+is(ArrayBuffer , new ArrayBuffer ());
+is(Map , new Map ());
+is(RegExp , /./g);
+is(Set , new Set ());
+is(WeakMap , new WeakMap ());
+is(WeakSet , new WeakSet ());
+is(String , '' );
+is(String , new String ('' ));
+is(Number , 1 );
+is(Number , new Number (1 ));
+is(Boolean , true );
+is(Boolean , new Boolean (true ));
+
+
+
+isAfterDate:检查是否在某日期后
+
+const isAfterDate = (dateA, dateB ) => dateA > dateB;
+
+isAfterDate(new Date (2010 , 10 , 21 ), new Date (2010 , 10 , 20 ));
+
+
+
+isBeforeDate:检查是否在某日期前
+
+const isBeforeDate = (dateA, dateB ) => dateA < dateB;
+
+isBeforeDate(new Date (2010 , 10 , 20 ), new Date (2010 , 10 , 21 ));
+
+
+
+tomorrow:获取明天的字符串格式时间
+
+
+const tomorrow = () => {
+ let t = new Date ();
+ t.setDate(t.getDate() + 1 );
+ return t.toISOString().split('T' )[0 ];
+};
+
+tomorrow();
+
+
+
+equals:全等判断
+
+在两个变量之间进行深度比较以确定它们是否全等。
+此代码段精简的核心在于Array.prototype.every()的使用。
+const equals = (a, b ) => {
+ if (a === b) return true ;
+ if (a instanceof Date && b instanceof Date ) return a.getTime() === b.getTime();
+ if (!a || !b || (typeof a !== 'object' && typeof b !== 'object' )) return a === b;
+ if (a.prototype !== b.prototype) return false ;
+ let keys = Object .keys(a);
+ if (keys.length !== Object .keys(b).length) return false ;
+ return keys.every(k => equals(a[k], b[k]));
+};
+
+
+用法:
+equals({ a : [2 , { e : 3 }], b : [4 ], c : 'foo' }, { a : [2 , { e : 3 }], b : [4 ], c : 'foo' });
+
+
+数字
+
+randomIntegerInRange:生成指定范围的随机整数
+
+const randomIntegerInRange = (min, max ) => Math .floor(Math .random() * (max - min + 1 )) + min;
+
+randomIntegerInRange(0 , 5 );
+
+
+
+randomNumberInRange:生成指定范围的随机小数
+
+const randomNumberInRange = (min, max ) => Math .random() * (max - min) + min;
+
+randomNumberInRange(2 , 10 );
+
+
+
+round:四舍五入到指定位数
+
+const round = (n, decimals = 0 ) => Number (`${Math .round(`${n} e${decimals} ` )} e-${decimals} ` );
+
+round(1.005 , 2 );
+
+
+
+sum:计算数组或多个数字的总和
+
+
+const sum = (...arr ) => [...arr].reduce((acc, val ) => acc + val, 0 );
+
+sum(1 , 2 , 3 , 4 );
+sum(...[1 , 2 , 3 , 4 ]);
+
+
+
+toCurrency:简单的货币单位转换
+
+const toCurrency = (n, curr, LanguageFormat = undefined ) =>
+ Intl .NumberFormat(LanguageFormat, { style : 'currency' , currency : curr }).format(n);
+
+toCurrency(123456.789 , 'EUR' );
+toCurrency(123456.789 , 'USD' , 'en-us' );
+toCurrency(123456.789 , 'USD' , 'fa' );
+toCurrency(322342436423.2435 , 'JPY' );
+
+
+浏览器操作及其它
+
+bottomVisible:检查页面底部是否可见
+
+const bottomVisible = () =>
+ document .documentElement.clientHeight + window .scrollY >=
+ (document .documentElement.scrollHeight || document .documentElement.clientHeight);
+
+bottomVisible();
+
+
+
+Create Directory:检查创建目录
+
+此代码段调用fs模块的existsSync()检查目录是否存在,如果不存在,则mkdirSync()创建该目录。
+const fs = require ('fs' );
+const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined );
+createDirIfNotExists('test' );
+
+
+
+currentURL:返回当前链接url
+
+const currentURL = () => window .location.href;
+
+currentURL();
+
+
+
+distance:返回两点间的距离
+
+该代码段通过计算欧几里得距离来返回两点之间的距离。
+const distance = (x0, y0, x1, y1 ) => Math .hypot(x1 - x0, y1 - y0);
+
+distance(1 , 1 , 2 , 3 );
+
+
+
+elementContains:检查是否包含子元素
+此代码段检查父元素是否包含子元素。
+
+const elementContains = (parent, child ) => parent !== child && parent.contains(child);
+
+elementContains(document .querySelector('head' ), document .querySelector('title' ));
+elementContains(document .querySelector('body' ), document .querySelector('body' ));
+
+
+
+getStyle:返回指定元素的生效样式
+
+const getStyle = (el, ruleName ) => getComputedStyle(el)[ruleName];
+
+getStyle(document .querySelector('p' ), 'font-size' );
+
+
+
+getType:返回值或变量的类型名
+
+const getType = v =>
+ v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
+
+getType(new Set ([1 , 2 , 3 ]));
+getType([1 , 2 , 3 ]);
+
+
+
+hasClass:校验指定元素的类名
+
+const hasClass = (el, className ) => el.classList.contains(className);
+hasClass(document .querySelector('p.special' ), 'special' );
+
+
+
+hide:隐藏所有的指定标签
+
+const hide = (...el ) => [...el].forEach(e => (e.style.display = 'none' ));
+
+hide(document .querySelectorAll('img' ));
+
+
+
+httpsRedirect:HTTP 跳转 HTTPS
+
+const httpsRedirect = () => {
+ if (location.protocol !== 'https:' ) location.replace('https://' + location.href.split('//' )[1 ]);
+};
+
+httpsRedirect();
+
+
+
+insertAfter:在指定元素之后插入新元素
+
+const insertAfter = (el, htmlString ) => el.insertAdjacentHTML('afterend' , htmlString);
+
+
+insertAfter(document .getElementById('myId' ), '<p>after</p>' );
+
+
+
+insertBefore:在指定元素之前插入新元素
+
+const insertBefore = (el, htmlString ) => el.insertAdjacentHTML('beforebegin' , htmlString);
+
+insertBefore(document .getElementById('myId' ), '<p>before</p>' );
+
+
+
+isBrowser:检查是否为浏览器环境
+
+此代码段可用于确定当前运行时环境是否为浏览器。这有助于避免在服务器(节点)上运行前端模块时出错。
+const isBrowser = () => ![typeof window , typeof document ].includes('undefined' );
+
+isBrowser();
+isBrowser();
+
+
+
+isBrowserTab:检查当前标签页是否活动
+
+const isBrowserTabFocused = () => !document .hidden;
+
+isBrowserTabFocused();
+
+
+
+nodeListToArray:转换nodeList为数组
+
+const nodeListToArray = nodeList => [...nodeList];
+
+nodeListToArray(document .childNodes);
+
+
+
+Random Hexadecimal Color Code:随机十六进制颜色
+
+
+const randomHexColorCode = () => {
+ let n = (Math .random() * 0xfffff * 1000000 ).toString(16 );
+ return '#' + n.slice(0 , 6 );
+};
+
+randomHexColorCode();
+
+
+
+scrollToTop:平滑滚动至顶部
+
+const scrollToTop = () => {
+ const c = document .documentElement.scrollTop || document .body.scrollTop;
+ if (c > 0 ) {
+ window .requestAnimationFrame(scrollToTop);
+ window .scrollTo(0 , c - c / 8 );
+ }
+};
+
+scrollToTop();
+
+
+
+smoothScroll:滚动到指定元素区域
+
+该代码段可将指定元素平滑滚动到浏览器窗口的可见区域。
+const smoothScroll = element =>
+ document .querySelector(element).scrollIntoView({
+ behavior : 'smooth'
+ });
+
+smoothScroll('#fooBar' );
+smoothScroll('.fooBar' );
+
+
+
+detectDeviceType:检测移动/PC设备
+
+const detectDeviceType = () =>
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i .test(navigator.userAgent)
+ ? 'Mobile'
+ : 'Desktop' ;
+
+
+
+getScrollPosition:返回当前的滚动位置
+
+默认参数为window ,pageXOffset(pageYOffset)为第一选择,没有则用scrollLeft(scrollTop)
+const getScrollPosition = (el = window ) => ({
+ x : el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
+ y : el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
+});
+
+getScrollPosition();
+
+
+
+size:获取不同类型变量的字节长度
+
+这个的实现非常巧妙,利用Blob类文件对象的特性,获取对象的长度。
+另外,多重三元运算符,是真香。
+const size = val =>
+ Array .isArray(val)
+ ? val.length
+ : val && typeof val === 'object'
+ ? val.size || val.length || Object .keys(val).length
+ : typeof val === 'string'
+ ? new Blob([val]).size
+ : 0 ;
+
+size([1 , 2 , 3 , 4 , 5 ]);
+size('size' );
+size({ one : 1 , two : 2 , three : 3 });
+
+
+
+
+escapeHTML:转义HTML
+
+当然是用来防XSS攻击啦。
+const escapeHTML = str =>
+ str.replace(
+ /[&<>'"]/g ,
+ tag =>
+ ({
+ '&' : '&' ,
+ '<' : '<' ,
+ '>' : '>' ,
+ "'" : ''' ,
+ '"' : '"'
+ }[tag] || tag)
+ );
+
+escapeHTML('<a href="#">Me & you</a>' );
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/web/js_tool_method.json b/en-us/blog/web/js_tool_method.json
new file mode 100644
index 0000000..b358b16
--- /dev/null
+++ b/en-us/blog/web/js_tool_method.json
@@ -0,0 +1,6 @@
+{
+ "filename": "js_tool_method.md",
+ "__html": "JavaScript 工具函数大全 \n数组 \n\nall:布尔全等判断 \n \nconst all = (arr, fn = Boolean ) => arr.every(fn);\n\nall([4 , 2 , 3 ], x => x > 1 ); \nall([1 , 2 , 3 ]); \n\n
\n\nallEqual:检查数组各项相等 \n \nconst allEqual = arr => arr.every(val => val === arr[0 ]);\n\nallEqual([1 , 2 , 3 , 4 , 5 , 6 ]); \nallEqual([1 , 1 , 1 , 1 ]); \n\n
\n\napproximatelyEqual:约等于 \n \nconst approximatelyEqual = (v1, v2, epsilon = 0.001 ) => Math .abs(v1 - v2) < epsilon;\n\napproximatelyEqual(Math .PI / 2.0 , 1.5708 ); \n\n
\n\narrayToCSV:数组转CSV格式(带空格的字符串) \n \n\nconst arrayToCSV = (arr, delimiter = ',' ) => \n arr.map(v => v.map(x => `\"${x} \"` ).join(delimiter)).join('\\n' );\n \narrayToCSV([['a' , 'b' ], ['c' , 'd' ]]); \narrayToCSV([['a' , 'b' ], ['c' , 'd' ]], ';' ); \n\n
\n\narrayToHtmlList:数组转li列表 \n \nconst arrayToHtmlList = (arr, listID ) => \n (el => (\n (el = document .querySelector('#' + listID)),\n (el.innerHTML += arr.map(item => `<li>${item} </li>` ).join('' ))\n ))();\n \narrayToHtmlList(['item 1' , 'item 2' ], 'myListID' );\n\n
\n\naverage:平均数 \n \nconst average = (...nums ) => nums.reduce((acc, val ) => acc + val, 0 ) / nums.length;\naverage(...[1 , 2 , 3 ]); \naverage(1 , 2 , 3 ); \n\n
\n\naverageBy:数组对象属性平均数 \n \n此代码段将获取数组对象属性的平均值
\nconst averageBy = (arr, fn ) => \n arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val ) => acc + val, 0 ) /\n arr.length;\n \naverageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], o => o.n); \naverageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], 'n' ); \n\n
\n\nbifurcate:拆分断言后的数组 \n \n可以根据每个元素返回的值,使用reduce()和push() 将元素添加到第二次参数fn中 。
\nconst bifurcate = (arr, filter ) => \n arr.reduce((acc, val, i ) => (acc[filter[i] ? 0 : 1 ].push(val), acc), [[], []]);\nbifurcate(['beep' , 'boop' , 'foo' , 'bar' ], [true , true , false , true ]); \n\n\n
\n\ncastArray:其它类型转数组 \n \nconst castArray = val => (Array .isArray(val) ? val : [val]);\n\ncastArray('foo' ); \ncastArray([1 ]); \ncastArray(1 ); \n\n
\n\ncompact:去除数组中的无效/无用值 \n \nconst compact = arr => arr.filter(Boolean );\n\ncompact([0 , 1 , false , 2 , '' , 3 , 'a' , 'e' * 23 , NaN , 's' , 34 ]); \n\n\n
\n\ncountOccurrences:检测数值出现次数 \n \nconst countOccurrences = (arr, val ) => arr.reduce((a, v ) => (v === val ? a + 1 : a), 0 );\ncountOccurrences([1 , 1 , 2 , 1 , 2 , 3 ], 1 ); \n\n
\n\ndeepFlatten:递归扁平化数组 \n \nconst deepFlatten = arr => [].concat(...arr.map(v => (Array .isArray(v) ? deepFlatten(v) : v)));\n\ndeepFlatten([1 , [2 ], [[3 ], 4 ], 5 ]); \n\n
\n\ndifference:寻找差异(并返回第一个数组独有的) \n \n此代码段查找两个数组之间的差异,并返回第一个数组独有的。
\n\nconst difference = (a, b ) => {\n const s = new Set (b);\n return a.filter(x => !s.has(x));\n};\n\ndifference([1 , 2 , 3 ], [1 , 2 , 4 ]); \n\n
\n\ndifferenceBy:先执行再寻找差异 \n \n在将给定函数应用于两个列表的每个元素之后,此方法返回两个数组之间的差异。
\nconst differenceBy = (a, b, fn ) => {\n const s = new Set (b.map(fn));\n return a.filter(x => !s.has(fn(x)));\n};\n\ndifferenceBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor); \ndifferenceBy([{ x : 2 }, { x : 1 }], [{ x : 1 }], v => v.x); \n\n
\n\ndropWhile:删除不符合条件的值 \n \n此代码段从数组顶部开始删除元素,直到传递的函数返回为true。
\nconst dropWhile = (arr, func ) => {\n while (arr.length > 0 && !func(arr[0 ])) arr = arr.slice(1 );\n return arr;\n};\n\ndropWhile([1 , 2 , 3 , 4 ], n => n >= 3 ); \n\n
\n\nflatten:指定深度扁平化数组 \n \n此代码段第二参数可指定深度。
\nconst flatten = (arr, depth = 1 ) => \n arr.reduce((a, v ) => a.concat(depth > 1 && Array .isArray(v) ? flatten(v, depth - 1 ) : v), []);\n\nflatten([1 , [2 ], 3 , 4 ]); \nflatten([1 , [2 , [3 , [4 , 5 ], 6 ], 7 ], 8 ], 2 ); \n\n
\n\nindexOfAll:返回数组中某值的所有索引 \n \n此代码段可用于获取数组中某个值的所有索引,如果此值中未包含该值,则返回一个空数组。
\nconst indexOfAll = (arr, val ) => arr.reduce((acc, el, i ) => (el === val ? [...acc, i] : acc), []);\n\nindexOfAll([1 , 2 , 3 , 1 , 2 , 3 ], 1 ); \nindexOfAll([1 , 2 , 3 ], 4 ); \n\n
\n\nintersection:两数组的交集 \n \n\nconst intersection = (a, b ) => {\n const s = new Set (b);\n return a.filter(x => s.has(x));\n};\n\nintersection([1 , 2 , 3 ], [4 , 3 , 2 ]); \n\n
\n\nintersectionWith:两数组都符合条件的交集 \n \n此片段可用于在对两个数组的每个元素执行了函数之后,返回两个数组中存在的元素列表。
\n\nconst intersectionBy = (a, b, fn ) => {\n const s = new Set (b.map(fn));\n return a.filter(x => s.has(fn(x)));\n};\n\nintersectionBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor); \n\n
\n\nintersectionWith:先比较后返回交集 \n \nconst intersectionWith = (a, b, comp ) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1 );\n\nintersectionWith([1 , 1.2 , 1.5 , 3 , 0 ], [1.9 , 3 , 0 , 3.9 ], (a, b) => Math .round(a) === Math .round(b)); \n\n
\n\nminN:返回指定长度的升序数组 \n \nconst minN = (arr, n = 1 ) => [...arr].sort((a, b ) => a - b).slice(0 , n);\n\nminN([1 , 2 , 3 ]); \nminN([1 , 2 , 3 ], 2 ); \n\n
\n\nnegate:根据条件反向筛选 \n \n\nconst negate = func => (...args ) => !func(...args);\n\n[1 , 2 , 3 , 4 , 5 , 6 ].filter(negate(n => n % 2 === 0 )); \n\n
\n\nrandomIntArrayInRange:生成两数之间指定长度的随机数组 \n \nconst randomIntArrayInRange = (min, max, n = 1 ) => \n Array .from({ length : n }, () => Math .floor(Math .random() * (max - min + 1 )) + min);\n \nrandomIntArrayInRange(12 , 35 , 10 ); \n\n
\n\nsample:在指定数组中获取随机数 \n \nconst sample = arr => arr[Math .floor(Math .random() * arr.length)];\n\nsample([3 , 7 , 9 , 11 ]); \n\n
\n\nsampleSize:在指定数组中获取指定长度的随机数 \n \n此代码段可用于从数组中获取指定长度的随机数,直至穷尽数组。 使用Fisher-Yates算法对数组中的元素进行随机选择。
\nconst sampleSize = ([...arr], n = 1 ) => {\n let m = arr.length;\n while (m) {\n const i = Math .floor(Math .random() * m--);\n [arr[m], arr[i]] = [arr[i], arr[m]];\n }\n return arr.slice(0 , n);\n};\n\nsampleSize([1 , 2 , 3 ], 2 ); \nsampleSize([1 , 2 , 3 ], 4 ); \n\n
\n\nshuffle:“洗牌” 数组 \n \n此代码段使用Fisher-Yates算法随机排序数组的元素。
\n\nconst shuffle = ([...arr] ) => {\n let m = arr.length;\n while (m) {\n const i = Math .floor(Math .random() * m--);\n [arr[m], arr[i]] = [arr[i], arr[m]];\n }\n return arr;\n};\n\nconst foo = [1 , 2 , 3 ];\nshuffle(foo); \n\n
\n\nnest:根据parent_id生成树结构(阿里一面真题) \n \n根据每项的parent_id,生成具体树形结构的对象。
\nconst nest = (items, id = null , link = 'parent_id' ) => \n items\n .filter(item => item[link] === id)\n .map(item => ({ ...item, children : nest(items, item.id) }));\n\n
\n用法:
\n const comments = [\n { id : 1 , parent_id : null },\n { id : 2 , parent_id : 1 },\n { id : 3 , parent_id : 1 },\n { id : 4 , parent_id : 2 },\n { id : 5 , parent_id : 4 }\n];\nconst nestedComments = nest(comments); \n\n
\n函数 \n\nattempt:捕获函数运行异常 \n \n该代码段执行一个函数,返回结果或捕获的错误对象。
\nonst attempt = (fn, ...args ) => {\n try {\n return fn(...args);\n } catch (e) {\n return e instanceof Error ? e : new Error (e);\n }\n};\nvar elements = attempt(function (selector ) {\n return document .querySelectorAll(selector);\n}, '>_>' );\nif (elements instanceof Error ) elements = []; \n\n
\n\ndefer:推迟执行 \n \nconst defer = (fn, ...args ) => setTimeout(fn, 1 , ...args);\n\ndefer(console .log, 'a' ), console .log('b' ); \n\n
\n\nrunPromisesInSeries:运行多个Promises \n \nconst runPromisesInSeries = ps => ps.reduce((p, next ) => p.then(next), Promise .resolve());\nconst delay = d => new Promise (r => setTimeout(r, d));\n\nrunPromisesInSeries([() => delay(1000 ), () => delay(2000 )]);\n\n\n
\n\ntimeTaken:计算函数执行时间 \n \n\nconst timeTaken = callback => {\n console .time('timeTaken' );\n const r = callback();\n console .timeEnd('timeTaken' );\n return r;\n};\n\ntimeTaken(() => Math .pow(2 , 10 )); \n\n
\n\ncreateEventHub:简单的发布/订阅模式 \n \n创建一个发布/订阅(发布-订阅)事件集线,有emit,on和off方法。
\n\n使用Object.create(null)创建一个空的hub对象。 \nemit,根据event参数解析处理程序数组,然后.forEach()通过传入数据作为参数来运行每个处理程序。 \non,为事件创建一个数组(若不存在则为空数组),然后.push()将处理程序添加到该数组。 \noff,用.findIndex()在事件数组中查找处理程序的索引,并使用.splice()删除。 \n \nconst createEventHub = () => ({\n hub : Object .create(null ),\n emit(event, data) {\n (this .hub[event] || []).forEach(handler => handler(data));\n },\n on(event, handler) {\n if (!this .hub[event]) this .hub[event] = [];\n this .hub[event].push(handler);\n },\n off(event, handler) {\n const i = (this .hub[event] || []).findIndex(h => h === handler);\n if (i > -1 ) this .hub[event].splice(i, 1 );\n if (this .hub[event].length === 0 ) delete this .hub[event];\n }\n});\n\n
\n用法:
\nconst handler = data => console .log(data);\nconst hub = createEventHub();\nlet increment = 0 ;\n\n\nhub.on('message' , handler);\nhub.on('message' , () => console .log('Message event fired' ));\nhub.on('increment' , () => increment++);\n\n\nhub.emit('message' , 'hello world' ); \nhub.emit('message' , { hello : 'world' }); \nhub.emit('increment' ); \n\n\nhub.off('message' , handler);\n\n
\n\nmemoize:缓存函数 \n \n通过实例化一个Map对象来创建一个空的缓存。
\n通过检查输入值的函数输出是否已缓存,返回存储一个参数的函数,该参数将被提供给已记忆的函数;如果没有,则存储并返回它。
\nconst memoize = fn => {\n const cache = new Map ();\n const cached = function (val ) {\n return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this , val)) && cache.get(val);\n };\n cached.cache = cache;\n return cached;\n};\n\n\n
\nPs: 这个版本可能不是很清晰,还有Vue源码版的:
\n\nexport function cached <F : Function > (fn: F ): F {\n const cache = Object .create(null )\n return (function cachedFn (str: string ) {\n const hit = cache[str]\n return hit || (cache[str] = fn(str))\n }: any)\n}\n\n
\n\nonce:只调用一次的函数 \n \nconst once = fn => {\n let called = false \n return function ( ) {\n if (!called) {\n called = true \n fn.apply(this , arguments )\n }\n }\n};\n\n
\n\nflattenObject:以键的路径扁平化对象 \n \n使用递归。
\n\n利用Object.keys(obj)联合Array.prototype.reduce(),以每片叶子节点转换为扁平的路径节点。 \n如果键的值是一个对象,则函数使用调用适当的自身prefix以创建路径Object.assign()。 \n否则,它将适当的前缀键值对添加到累加器对象。 \nprefix除非您希望每个键都有一个前缀,否则应始终省略第二个参数。 \n \nconst flattenObject = (obj, prefix = '' ) => \n Object .keys(obj).reduce((acc, k ) => {\n const pre = prefix.length ? prefix + '.' : '' ;\n if (typeof obj[k] === 'object' ) Object .assign(acc, flattenObject(obj[k], pre + k));\n else acc[pre + k] = obj[k];\n return acc;\n }, {});\n \nflattenObject({ a : { b : { c : 1 } }, d : 1 }); \n\n
\n\nunflattenObject:以键的路径展开对象 \n \n与上面的相反,展开对象。
\nconst unflattenObject = obj => \n Object .keys(obj).reduce((acc, k ) => {\n if (k.indexOf('.' ) !== -1 ) {\n const keys = k.split('.' );\n Object .assign(\n acc,\n JSON .parse(\n '{' +\n keys.map((v, i ) => (i !== keys.length - 1 ? `\"${v} \":{` : `\"${v} \":` )).join('' ) +\n obj[k] +\n '}' .repeat(keys.length)\n )\n );\n } else acc[k] = obj[k];\n return acc;\n }, {});\n \nunflattenObject({ 'a.b.c' : 1 , d : 1 }); \n\n
\n这个的用途,在做Tree组件或复杂表单时取值非常舒服。
\n字符串 \n\nbyteSize:返回字符串的字节长度 \n \nconst byteSize = str => new Blob([str]).size;\n\nbyteSize('😀' ); \nbyteSize('Hello World' ); \n\n
\n\ncapitalize:首字母大写 \n \nconst capitalize = ([first, ...rest] ) => \n first.toUpperCase() + rest.join('' );\n \ncapitalize('fooBar' ); \ncapitalize('fooBar' , true ); \n\n
\n\ncapitalizeEveryWord:每个单词首字母大写 \n \nconst capitalizeEveryWord = str => str.replace(/\\b[a-z]/g , char => char.toUpperCase());\n\ncapitalizeEveryWord('hello world!' ); \n\n
\n\ndecapitalize:首字母小写 \n \nconst decapitalize = ([first, ...rest] ) => \n first.toLowerCase() + rest.join('' )\n\ndecapitalize('FooBar' ); \ndecapitalize('FooBar' ); \n\n
\n\nluhnCheck:银行卡号码校验(luhn算法) \n \nLuhn算法的实现,用于验证各种标识号,例如信用卡号,IMEI号,国家提供商标识号等。
\n与String.prototype.split('')结合使用,以获取数字数组。获得最后一个数字。实施luhn算法。如果被整除,则返回,否则返回。
\nconst luhnCheck = num => {\n let arr = (num + '' )\n .split('' )\n .reverse()\n .map(x => parseInt (x));\n let lastDigit = arr.splice(0 , 1 )[0 ];\n let sum = arr.reduce((acc, val, i ) => (i % 2 !== 0 ? acc + val : acc + ((val * 2 ) % 9 ) || 9 ), 0 );\n sum += lastDigit;\n return sum % 10 === 0 ;\n};\n\n
\n用例:
\nluhnCheck('4485275742308327' ); \nluhnCheck(6011329933655299 ); \nluhnCheck(123456789 ); \n
\n补充:银行卡号码的校验规则:
\n银行卡号码的校验采用Luhn算法,校验过程大致如下:
\n\n从右到左给卡号字符串编号,最右边第一位是1,最右边第二位是2,最右边第三位是3…. \n从右向左遍历,对每一位字符t执行第三个步骤,并将每一位的计算结果相加得到一个数s。 \n对每一位的计算规则:如果这一位是奇数位,则返回t本身,如果是偶数位,则先将t乘以2得到一个数n,如果n是一位数(小于10),直接返回n,否则将n的个位数和十位数相加返回。 \n如果s能够整除10,则此号码有效,否则号码无效。 \n \n因为最终的结果会对10取余来判断是否能够整除10,所以又叫做模10算法。\n当然,还是库比较香: bankcardinfo
\n\nsplitLines:将多行字符串拆分为行数组。 \n \n使用String.prototype.split()和正则表达式匹配换行符并创建一个数组。
\nconst splitLines = str => str.split(/\\r?\\n/ );\n\nsplitLines('This\\nis a\\nmultiline\\nstring.\\n' ); \n\n
\n\nstripHTMLTags:删除字符串中的HTMl标签 \n \n从字符串中删除HTML / XML标签。
\n使用正则表达式从字符串中删除HTML / XML 标记。
\nconst stripHTMLTags = str => str.replace(/<[^>]*>/g , '' );\n\nstripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>' ); \n\n
\n对象 \n\ndayOfYear:当前日期天数 \n \nconst dayOfYear = date => \n Math .floor((date - new Date (date.getFullYear(), 0 , 0 )) / 1000 / 60 / 60 / 24 );\n\ndayOfYear(new Date ()); \n\n
\n\nforOwn:迭代属性并执行回调 \n \nconst forOwn = (obj, fn ) => Object .keys(obj).forEach(key => fn(obj[key], key, obj));\nforOwn({ foo : 'bar' , a : 1 }, v => console .log(v)); \n\n
\n\nGet Time From Date:返回当前24小时制时间的字符串 \n \nconst getColonTimeFromDate = date => date.toTimeString().slice(0 , 8 );\n\ngetColonTimeFromDate(new Date ()); \n\n
\n\nGet Days Between Dates:返回日期间的天数 \n \nconst getDaysDiffBetweenDates = (dateInitial, dateFinal ) => \n (dateFinal - dateInitial) / (1000 * 3600 * 24 );\n \ngetDaysDiffBetweenDates(new Date ('2019-01-01' ), new Date ('2019-10-14' )); \n\n
\n\nis:检查值是否为特定类型。 \n \nconst is = (type, val ) => ![, null ].includes(val) && val.constructor === type;\n\nis(Array , [1 ]); \nis(ArrayBuffer , new ArrayBuffer ()); \nis(Map , new Map ()); \nis(RegExp , /./g); \nis(Set , new Set ()); \nis(WeakMap , new WeakMap ()); \nis(WeakSet , new WeakSet ()); \nis(String , '' ); \nis(String , new String ('' )); \nis(Number , 1 ); \nis(Number , new Number (1 )); \nis(Boolean , true ); \nis(Boolean , new Boolean (true )); \n\n
\n\nisAfterDate:检查是否在某日期后 \n \nconst isAfterDate = (dateA, dateB ) => dateA > dateB;\n\nisAfterDate(new Date (2010 , 10 , 21 ), new Date (2010 , 10 , 20 )); \n\n
\n\nisBeforeDate:检查是否在某日期前 \n \nconst isBeforeDate = (dateA, dateB ) => dateA < dateB;\n\nisBeforeDate(new Date (2010 , 10 , 20 ), new Date (2010 , 10 , 21 )); \n\n
\n\ntomorrow:获取明天的字符串格式时间 \n \n\nconst tomorrow = () => {\n let t = new Date ();\n t.setDate(t.getDate() + 1 );\n return t.toISOString().split('T' )[0 ];\n};\n\ntomorrow(); \n\n
\n\nequals:全等判断 \n \n在两个变量之间进行深度比较以确定它们是否全等。
\n此代码段精简的核心在于Array.prototype.every()的使用。
\nconst equals = (a, b ) => {\n if (a === b) return true ;\n if (a instanceof Date && b instanceof Date ) return a.getTime() === b.getTime();\n if (!a || !b || (typeof a !== 'object' && typeof b !== 'object' )) return a === b;\n if (a.prototype !== b.prototype) return false ;\n let keys = Object .keys(a);\n if (keys.length !== Object .keys(b).length) return false ;\n return keys.every(k => equals(a[k], b[k]));\n};\n\n
\n用法:
\nequals({ a : [2 , { e : 3 }], b : [4 ], c : 'foo' }, { a : [2 , { e : 3 }], b : [4 ], c : 'foo' }); \n\n
\n数字 \n\nrandomIntegerInRange:生成指定范围的随机整数 \n \nconst randomIntegerInRange = (min, max ) => Math .floor(Math .random() * (max - min + 1 )) + min;\n\nrandomIntegerInRange(0 , 5 ); \n\n
\n\nrandomNumberInRange:生成指定范围的随机小数 \n \nconst randomNumberInRange = (min, max ) => Math .random() * (max - min) + min;\n\nrandomNumberInRange(2 , 10 ); \n\n
\n\nround:四舍五入到指定位数 \n \nconst round = (n, decimals = 0 ) => Number (`${Math .round(`${n} e${decimals} ` )} e-${decimals} ` );\n\nround(1.005 , 2 ); \n\n
\n\nsum:计算数组或多个数字的总和 \n \n\nconst sum = (...arr ) => [...arr].reduce((acc, val ) => acc + val, 0 );\n\nsum(1 , 2 , 3 , 4 ); \nsum(...[1 , 2 , 3 , 4 ]); \n\n
\n\ntoCurrency:简单的货币单位转换 \n \nconst toCurrency = (n, curr, LanguageFormat = undefined ) => \n Intl .NumberFormat(LanguageFormat, { style : 'currency' , currency : curr }).format(n);\n \ntoCurrency(123456.789 , 'EUR' ); \ntoCurrency(123456.789 , 'USD' , 'en-us' ); \ntoCurrency(123456.789 , 'USD' , 'fa' ); \ntoCurrency(322342436423.2435 , 'JPY' ); \n\n
\n浏览器操作及其它 \n\nbottomVisible:检查页面底部是否可见 \n \nconst bottomVisible = () => \n document .documentElement.clientHeight + window .scrollY >=\n (document .documentElement.scrollHeight || document .documentElement.clientHeight);\n\nbottomVisible(); \n\n
\n\nCreate Directory:检查创建目录 \n \n此代码段调用fs模块的existsSync()检查目录是否存在,如果不存在,则mkdirSync()创建该目录。
\nconst fs = require ('fs' );\nconst createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined );\ncreateDirIfNotExists('test' ); \n\n
\n\ncurrentURL:返回当前链接url \n \nconst currentURL = () => window .location.href;\n\ncurrentURL(); \n\n
\n\ndistance:返回两点间的距离 \n \n该代码段通过计算欧几里得距离来返回两点之间的距离。
\nconst distance = (x0, y0, x1, y1 ) => Math .hypot(x1 - x0, y1 - y0);\n\ndistance(1 , 1 , 2 , 3 ); \n\n
\n\nelementContains:检查是否包含子元素\n此代码段检查父元素是否包含子元素。 \n \nconst elementContains = (parent, child ) => parent !== child && parent.contains(child);\n\nelementContains(document .querySelector('head' ), document .querySelector('title' )); \nelementContains(document .querySelector('body' ), document .querySelector('body' )); \n\n
\n\ngetStyle:返回指定元素的生效样式 \n \nconst getStyle = (el, ruleName ) => getComputedStyle(el)[ruleName];\n\ngetStyle(document .querySelector('p' ), 'font-size' ); \n\n
\n\ngetType:返回值或变量的类型名 \n \nconst getType = v => \n v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();\n \ngetType(new Set ([1 , 2 , 3 ])); \ngetType([1 , 2 , 3 ]); \n\n
\n\nhasClass:校验指定元素的类名 \n \nconst hasClass = (el, className ) => el.classList.contains(className);\nhasClass(document .querySelector('p.special' ), 'special' ); \n\n
\n\nhide:隐藏所有的指定标签 \n \nconst hide = (...el ) => [...el].forEach(e => (e.style.display = 'none' ));\n\nhide(document .querySelectorAll('img' )); \n\n
\n\nhttpsRedirect:HTTP 跳转 HTTPS \n \nconst httpsRedirect = () => {\n if (location.protocol !== 'https:' ) location.replace('https://' + location.href.split('//' )[1 ]);\n};\n\nhttpsRedirect(); \n\n
\n\ninsertAfter:在指定元素之后插入新元素 \n \nconst insertAfter = (el, htmlString ) => el.insertAdjacentHTML('afterend' , htmlString);\n\n\ninsertAfter(document .getElementById('myId' ), '<p>after</p>' ); \n\n
\n\ninsertBefore:在指定元素之前插入新元素 \n \nconst insertBefore = (el, htmlString ) => el.insertAdjacentHTML('beforebegin' , htmlString);\n\ninsertBefore(document .getElementById('myId' ), '<p>before</p>' ); \n\n
\n\nisBrowser:检查是否为浏览器环境 \n \n此代码段可用于确定当前运行时环境是否为浏览器。这有助于避免在服务器(节点)上运行前端模块时出错。
\nconst isBrowser = () => ![typeof window , typeof document ].includes('undefined' );\n\nisBrowser(); \nisBrowser(); \n\n
\n\nisBrowserTab:检查当前标签页是否活动 \n \nconst isBrowserTabFocused = () => !document .hidden;\n\nisBrowserTabFocused(); \n\n
\n\nnodeListToArray:转换nodeList为数组 \n \nconst nodeListToArray = nodeList => [...nodeList];\n\nnodeListToArray(document .childNodes); \n\n
\n\nRandom Hexadecimal Color Code:随机十六进制颜色 \n \n\nconst randomHexColorCode = () => {\n let n = (Math .random() * 0xfffff * 1000000 ).toString(16 );\n return '#' + n.slice(0 , 6 );\n};\n\nrandomHexColorCode(); \n\n
\n\nscrollToTop:平滑滚动至顶部 \n \nconst scrollToTop = () => {\n const c = document .documentElement.scrollTop || document .body.scrollTop;\n if (c > 0 ) {\n window .requestAnimationFrame(scrollToTop);\n window .scrollTo(0 , c - c / 8 );\n }\n};\n\nscrollToTop();\n\n
\n\nsmoothScroll:滚动到指定元素区域 \n \n该代码段可将指定元素平滑滚动到浏览器窗口的可见区域。
\nconst smoothScroll = element => \n document .querySelector(element).scrollIntoView({\n behavior : 'smooth' \n });\n \nsmoothScroll('#fooBar' ); \nsmoothScroll('.fooBar' ); \n\n
\n\ndetectDeviceType:检测移动/PC设备 \n \nconst detectDeviceType = () => \n /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i .test(navigator.userAgent)\n ? 'Mobile' \n : 'Desktop' ;\n\n
\n\ngetScrollPosition:返回当前的滚动位置 \n \n默认参数为window ,pageXOffset(pageYOffset)为第一选择,没有则用scrollLeft(scrollTop)
\nconst getScrollPosition = (el = window ) => ({\n x : el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,\n y : el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop\n});\n\ngetScrollPosition(); \n\n
\n\nsize:获取不同类型变量的字节长度 \n \n这个的实现非常巧妙,利用Blob类文件对象的特性,获取对象的长度。
\n另外,多重三元运算符,是真香。
\nconst size = val => \n Array .isArray(val)\n ? val.length\n : val && typeof val === 'object' \n ? val.size || val.length || Object .keys(val).length\n : typeof val === 'string' \n ? new Blob([val]).size\n : 0 ;\n\nsize([1 , 2 , 3 , 4 , 5 ]); \nsize('size' ); \nsize({ one : 1 , two : 2 , three : 3 }); \n\n\n
\n\nescapeHTML:转义HTML \n \n当然是用来防XSS攻击啦。
\nconst escapeHTML = str => \n str.replace(\n /[&<>'\"]/g ,\n tag =>\n ({\n '&' : '&' ,\n '<' : '<' ,\n '>' : '>' ,\n \"'\" : ''' ,\n '\"' : '"' \n }[tag] || tag)\n );\n\nescapeHTML('<a href=\"#\">Me & you</a>' ); \n\n
\n",
+ "link": "\\en-us\\blog\\web\\js_tool_method.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/web/react_interview.html b/en-us/blog/web/react_interview.html
new file mode 100644
index 0000000..72f2369
--- /dev/null
+++ b/en-us/blog/web/react_interview.html
@@ -0,0 +1,845 @@
+
+
+
+
+
+
+
+
+
+ react_interview
+
+
+
+
+ React 面试问题
+
+如果你是一位有理想的前端开发人员,并且正在准备面试,那么这篇文章就是为你准备的。本文收集了 React 面试中最常见的 50 大问题,这是一份理想的指南,让你为 React 相关的面试做好充分的准备工作。首先我们快速了解一下 React 在市场上的需求和现状,然后再开始讨论 React 面试问题。
+
+JavaScript 工具的市场地位正在缓慢而稳定地上升当中,而对 React 认证的需求正在飞速增长。选择正确的技术来开发应用程序或网站变得愈加艰难。React 被认为是 Javascript 语言中增长最快的框架。
+虚拟 DOM 和可复用部件等独特特性吸引了前端开发人员的注意。尽管成熟的框架(如 Angular、Meteor 和 Vue 等)在 MVC(模型 - 视图 - 控制器)中只是一个“视图”库,但它们都有很强的竞争力。下图显示了常见 JS 框架的趋势:
+
+以下是面试官最有可能提出的 50 个面试问题和答案。
+React 面试问题——常规知识
+
+
+真实 DOM 和虚拟 DOM 的区别
+
+
+
+真实 DOM
+虚拟 DOM
+
+
+
+
+1.更新较慢
+1.更新较快
+
+
+2.可以直接更新 HTML
+2.不能直接更新 HTML
+
+
+3.元素更新时创建一个新 DOM
+3.元素更新时更新 JSX
+
+
+4.DOM 操作开销较大
+4.DOM 操作非常容易
+
+
+5.内存浪费严重
+5.没有内存浪费
+
+
+
+
+
+什么是 React?
+
+React 是 2011 年由 Facebook 开发的前端 JavaScript 库。
+它遵循基于组件的方法,这种方法可以用来构建可复用的 UI 组件。
+它用于复杂的交互式 Web 端和移动端用户界面开发。
+尽管它在 2015 年才开源,但得到了一家巨头的支持。
+
+
+
+React 的特点是什么?
+
+轻量级 DOM,以获得更好的性能。
+在 React 中,一切都被视为组件。
+React 使用 JSX(JavaScript eXtension),使我们可以编写类似于 HTML 的 JavaScript。
+React 不是直接运行在浏览器的文档对象模型(DOM)上,而是运行在虚拟 DOM 上。
+ReactJS 遵循单向数据流或单向数据绑定。
+
+
+
+列出 React 的一些主要优势。
+
+可以提高应用程序的性能。
+可以方便地用在客户端和服务端。
+由于有了 JSX,代码的可读性提高了。
+使用 React 后,编写 UI 测试用例变得非常容易。
+
+
+
+React 有哪些局限?
+
+React 只是一个库,而不是一个成熟的框架。
+它的库很大,需要花费一些时间来理解。
+新手程序员可能很难入门。
+由于它使用了内联模板和 JSX,编码也比较复杂。
+
+
+
+什么是 JSX?
+
+
+JSX 是 JavaScript XML 的简写。这是 React 使用的一种文件类型,具备 JavaScript 的表现力,并使用 HTML 作为模板语法。这样一来 HTML 文件理解起来就非常简单。这种文件可以创造稳健的应用程序并提高其效率。下面是一个 JSX 实例:
+render(){
+ return (
+ <div >
+ <h1 > Hello World from Codersera!!</h1 >
+ </div >
+ );
+ }
+
+
+你对虚拟 DOM 有什么了解?解释其工作机制。
+
+虚拟 DOM 是轻量级的 JavaScript 对象,一开始只是真实 DOM 的一个副本。它是一个节点树,将组件列为对象及其属性和内容的列表。React 的渲染功能从 React 的各个部分生成一个节点树。然后,它会根据由不同用户或系统行为引起的信息模型突变来更新此树。
+虚拟 DOM 的工作机制只有简单的三步组成。
+1. 每当任何基础信息更改时,整个 UI 就会以虚拟 DOM 的表示形式重新渲染。
+
+
+2. 然后计算先前的 DOM 表示和新的 DOM 表示之间的区别。
+
+
+3. 计算完成后,只有实际更改的内容才会更新到真实 DOM。
+
+
+
+为什么浏览器无法读取 JSX?
+
+React 使用 JSX(JavaScript eXtension),我们可以用它编写类似于 HTML 的 JavaScript。但由于 JSX 不是合法的 JavaScript,因此浏览器无法直接读取它。如果 JavaScript 文件包含 JSX,则必须将其转换。你需要一个转换器将 JSX 转换为浏览器可以理解的常规 Javascript。目前最常用的转换器是 Babel。
+
+与 ES5 相比,React 的 ES6 语法有何不同?
+
+ES5 和 ES6 的语法区别如下:
+
+var React = require ('react' );
+
+import React from 'react' ;
+
+******** export vs exports *********
+
+
+module .exports = Component;
+
+export default Component;
+
+****** function *****
+// ES5
+
+var MyComponent = React .createClass ({
+ render: function( ) {
+ return <h3 > Hello CoderSera! </h3 >
+ },
+});
+
+
+class MyComponent extends React .Component {
+ render() {
+ return <h3 > Hello CoderSera! </h3 >
+ }
+}
+
+ ******* props ******
+
+
+var App = React.createClass({
+ propTypes : { name : React.PropTypes.string },
+ render : function ( ) {
+ return <h3 > Hello, { this.props.name }! < /h3 >
+ },
+});
+
+
+class App extends React .Component {
+ render() {
+ return <h3 > Hello, { this.props.name }! </h3 >
+ }
+}
+
+ ****** state *****
+
+
+var App = React.createClass({
+ getInitialState : function ( ) {
+ return { name : 'world' };
+ }
+
+ render: function ( ) {
+ return <h3 > Hello, { this.state.name }! < /h3 > ;
+ },
+});
+
+
+class App extends React .Component {
+ constructor () {
+ super ();
+ this .state = { name : 'world' };
+ }
+
+ render() {
+ return <h3 > Hello, { this.state.name }! < /h3 >
+ }
+ render() {
+ return ;
+ <h3 > Hello, { this.state.name }! < /h3 >
+ }
+
+
+
+React 和 Angular 有何不同?
+
+
+
+
+React 对比 Angular
+React
+Angular
+
+
+
+
+架构
+使用虚拟 DOM
+使用真实 DOM
+
+
+渲染
+服务端渲染
+客户端渲染
+
+
+DOM
+使用虚拟 DOM
+使用真实 DOM
+
+
+数据绑定
+单向数据绑定
+双向数据绑定
+
+
+调试
+编译时调试
+运行时调试
+
+
+开发者
+Facebook
+谷歌
+
+
+
+
+如何理解“在 React 中,一切都是组件”。
+
+React 应用程序的 UI 构建块都是组件。这些部分将整个 UI 划分为许多可自治和可复用的微小部分。然后独立的某个部分发生变化就不会影响 UI 的其余部分。
+
+解释 React 中 render() 的目的。
+
+它被视为普通函数,但 render() 函数必须返回某些值,无论值是否为空。调用组件文件时默认会调用 render() 方法,因为组件需要显示 HTML 标记,或者我们可以说 JSX 语法。每个 React 组件必须有一个 render() 函数,它返回单个 React 元素,该元素代表原生 DOM 组件。如果需要渲染多个 HTML 元素,则必须将它们分组在一个封闭的标签内,如form、group和div等。此函数必须保持纯净,就是说它在每次调用时必须返回相同的结果。
+import React, { Component } from 'react' ;
+
+class App extends Component {
+ render() {
+ return (<div > <h1 className ='App-title' > hello CoderSera </h1 > </div > )
+ }
+}
+
+export default App;
+
+
+
+什么是 Hooks?
+
+Hooks 是一项新功能,使你无需编写类即可使用状态等 React 功能。来看一个 useState hook 示例。
+<em>import </em>{useState} <em>from </ em>'react' ;<br><br><em>function </em>Example() {<br> <em>// Declare a new
+
+
+什么是 props?
+
+
+
+React 中的状态是什么,如何使用?
+
+组件可以通过状态来跟踪其执行的任何渲染之间的信息。
+状态用于可变数据或将要更改的数据。这对于用户输入尤其方便。以搜索栏为例,用户输入数据时他们看到的内容也会更新。
+
+状态和 props 的区别。
+
+
+
+
+条件
+状态
+Props
+
+
+
+
+从父组件接收初始值
+是
+是
+
+
+父组件可以更改值
+否
+是
+
+
+在组件内设置默认值
+是
+是
+
+
+在组件内更改
+是
+否
+
+
+为子组件设置初始值
+是
+是
+
+
+在子组件内更改
+否
+是
+
+
+
+
+如何更新组件的状态?
+
+可以使用 this.setState() 更新组件的状态。
+class MyComponent extends React .Component {
+ constructor () {
+ super ();
+ this .state = {
+ name : 'Maxx' ,
+ id : '101'
+ }
+ }
+ render()
+ {
+ setTimeout(() => {this .setState({name :'Jaeha' , id :'222' })},2000 )
+ return (
+ <div >
+ <h1 > Hello {this.state.name}</h1 >
+ <h2 > Your Id is {this.state.id}</h2 >
+ </div >
+ );
+ }
+ }
+ ReactDOM.render(
+ <MyComponent /> , document .getElementById('content' )
+);
+
+
+18.React 中的箭头函数是什么?如何使用?
+粗箭头 => 用于定义匿名函数,这通常是将参数传递给回调函数的最简单方法。但是你需要在使用它时优化性能。注意:每次渲染组件时,在 render 方法中使用箭头函数都会创建一个新函数,这可能会影响性能。
+
+render() {
+ return (
+ <MyInput onChange={this.handleChange.bind(this) } />
+ );
+}
+//With Arrow Function
+render() {
+ return(
+ <MyInput onChange={ (e) => this.handleOnChange(e) } />
+ );
+}
+
+
+
+有状态和无状态组件的区别。
+
+
+
+
+有状态组件
+无状态组件
+
+
+
+
+在内存中存储组件状态更改的信息
+计算组件的内部状态
+
+
+有权更改状态
+无权更改状态
+
+
+包含过去、现在和可能的未来状态更改的信息
+没有包含关于状态更改的信息
+
+
+无状态组件通知它们关于状态更改的需求,然后它们将 props 传递给前者
+它们从有状态组件接收 props,将其视为回调函数
+
+
+
+
+React 组件的生命周期有哪些阶段?
+
+React 组件的生命周期分为三个不同阶段:
+
+
+详细解释 React 组件的生命周期技术。
+
+一些最重要的生命周期方法包括:
+
+
+componentWillMount()——在初始渲染发生之前,即在 React 将组件插入 DOM 之前立即调用一次。请务必注意,在此方法中调用 this.setState() 不会触发重新渲染。
+
+
+componentDidMount()——在渲染函数之后触发此方法。现在可以访问更新的 DOM,这意味着该方法是初始化其他需要访问 DOM 的 Javascript 库以及数据提取操作的最佳选择。
+
+
+componentWillReceiveProps()——componentWillReceiveProps() 在组件接收新 props 时调用。在调用 render() 方法之前,我们可以用这个方法对 prop 过渡做出反应。在此函数中调用 this.setState() 不会触发额外的重新渲染,我们可以通过 this.props 访问旧的 props。
+
+
+shouldComponentUpdate()——我们可以用它来决定下一个组件的状态是否应触发重新渲染。此方法返回一个布尔值,默认为 true。但是我们可以返回 false,并且不会调用以下方法:
+
+
+componentWillUpdate()——当接收到新的 props 或状态时,在渲染(更新)之前立即调用此方法。我们可以用它在更新之前做准备,但是不允许使用 this.setState()。
+
+
+componentDidUpdate()——React 更新 DOM 后立即调用此方法。我们可以使用此方法与更新后的 DOM 交互,或执行任何渲染后操作。
+
+
+componentWillUnmount()——从 DOM 卸载组件之前立即调用此方法。我们可以用它执行可能需要的任何清理工作。
+
+
+
+React 中的事件是什么?
+
+在 React 中,事件是对特定动作(如鼠标悬停、鼠标单击和按键等)触发的反应。处理这些事件类似于处理 DOM 元素上的事件。但是在语法上存在一些差异,例如:
+
+
+事件命名使用驼峰式大小写,而不是仅使用小写字母。
+
+
+事件作为函数而不是字符串传递。
+
+
+事件参数包含一组特定于事件的属性。每个事件类型都包含它自己的属性和行为,这些属性和行为只能通过它的事件处理程序访问。
+
+如何在 React 中创建事件?
+
+class Display extends React .Component ( {
+ show(evt) {
+
+ },
+ render() {
+
+ return (
+ <div onClick ={this.show} > Click Me!</div >
+ );
+ }
+});
+
+
+
+
+什么是 React 中的合成事件?
+
+合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同的浏览器行为合并为一个 API。这样做是为了确保在各个浏览器的事件中显示一致的特征。
+
+如何理解 React 中的引用?
+
+Ref 是 React 引用的简写。它是一个属性,帮助存储对特定元素或组件的引用,由组件渲染的配置函数返回。它用于返回对渲染返回的特定元素或组件的引用。当我们需要 DOM 测量或向组件添加方法时,它们会派上用场。
+class ReferenceDemo extends React .Component {
+ display() {
+ const name = this .inputDemo.value;
+ document .getElementById('disp' ).innerHTML = name;
+ }
+ render() {
+ return (
+ <div >
+ Name: <input type ="text" ref ={input => this.inputDemo = input} />
+ <button name ="Click" onClick ={this.display} > Click</button >
+
+ <h2 > Hello <span id ="disp" > </span > !!!</h2 >
+ </div >
+ );
+ }
+ }
+
+
+
+列出一些应该使用引用的情况?
+
+以下是应使用 ref 的情况:
+
+
+当你需要管理焦点、选择文本或媒体播放时。
+
+
+触发命令式动画。
+
+
+与第三方 DOM 库集成。
+
+
+
+如何模块化 React 代码?
+
+可以使用 export 和 import 属性来模块化软件。它们有助于在不同的文档中单独编写组件。
+
+export default class ChildComponent extends React .Component {
+ render() {
+ return ( <div >
+ <h1 > This is a child component</h1 >
+ </div > )
+ }
+}
+
+
+import ChildComponent from './childcomponent.js' ;
+class ParentComponent extends React .Component {
+ render() {
+ return (
+ <div >
+ <App />
+ </div >
+ );
+ }
+}
+
+
+
+在 React 中如何创建表单?
+
+React 提供了一种有状态的,响应式的方法来构建表单。与其他 DOM 元素不同,HTML 表单元素在 React 中的工作机制有所不同。例如,表单数据通常由组件而不是 DOM 处理,并且通常使用受控组件来实现。
+区别在于可以使用回调函数来处理表单事件,然后使用容器的状态存储表单数据。这使你的组件可以更好地控制表单控制元素和表单数据。
+回调函数是在发生事件(包括更改表单控制值或表单提交)时触发的。
+handleSubmit(event) {
+ alert('A name was submitted: ' + this .state.value);
+ event.preventDefault();
+}
+
+render() {
+ return (
+
+
+
+<form onSubmit={this.handleSubmit}>
+ <label>
+ Name:
+ <input type="text" value={this.state.value} onChange={this.handleSubmit} />
+ </label>
+ <input type="submit" value="Submit" />
+ </form>
+
+
+
+ );
+}
+
+
+
+如何理解受控和非受控组件?
+
+
+
+
+受控组件
+非受控组件
+
+
+
+
+它们不维护自己的状态
+它们维护自己的状态
+
+
+数据由父组件控制
+数据由 DOM 控制
+
+
+它们通过 props 获得当前值,然后通过回调通知更改
+使用引用来获得它们的当前值
+
+
+
+
+高阶组件(HOC)是什么意思?
+
+React 中的高阶组件是一种在组件之间共享通用功能而无需重复代码的模式。
+高阶组件实际上不是组件,它是一个接受组件并返回新组件的函数。它将一个组件转换为另一个组件,并添加其他数据或功能
+
+HOC 可以做什么?
+
+HOC 可用于许多任务,例如:
+
+
+代码复用,逻辑和引导抽象。
+
+
+渲染劫持。
+
+
+状态抽象和控制。
+
+
+Props 操控。
+
+
+
+什么是纯组件?
+
+React 并没有在我们的组件中编写 shouldComponent 方法,而是引入了一个带有内置 shouldComponentUpdate 实现的新组件,它是 React.PureComponent 组件。
+React.PureComponent 通过浅层 prop 和状态比较来实现它。在某些情况下,你可以使用 React.PureComponent 来提高性能。
+
+React 中键有什么用途?
+
+键可帮助 React 识别哪些项目已更改、添加或删除。应该为数组内的元素提供键,以赋予元素稳定的身份。键必须是唯一的。
+当使用动态创建的组件或用户更改列表时,React 键非常有用。设置键值后,更改后的组件就能保持唯一标识。
+
+MVC 框架的主要问题有哪些?
+
+以下是 MVC 框架的一些主要问题:
+
+MVC 不能解决代码复杂性问题。它也不能解决代码复用或灵活性问题。
+它不保证解耦代码。
+
+
+你对 Flux 有什么了解?
+
+Flux 是 Facebook 内部与 React 搭配使用的架构。它不是框架或库。只是一种新型的体系结构,是对 React 和单向数据流概念的补充:
+Flux 的各个组成部分如下:
+
+动作(Actions)——帮助数据传递到调度器的辅助方法。
+调度器(Dispatcher)——接收动作并将负载广播到已注册的回调。
+存储(Stores)——具有已注册到调度器的回调的应用程序状态和逻辑的容器。
+控制器视图(Controller Views)——从组件中获取状态并通过 props 传递给子组件的 React 组件。
+
+
+
+什么是 Redux?
+
+Redux 是一种状态管理工具。尽管它主要与 React 搭配使用,但也可以与其他任何 JavaScript 框架或库搭配。
+Redux 允许你在一个称为存储(Store)的对象中管理整个应用程序状态。
+对存储的更新将触发与存储的已更新部分连接的组件的重新渲染。当我们想要更新某些东西时,我们称之为动作(Action)。我们还创建函数来处理这些动作并返回更新的存储。这些函数称为 Reducer。
+
+Redux 遵循的三大原则是什么?
+
+
+
+单一可信来源:整个应用程序的状态存储在单个存储区中的对象 / 状态树中。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
+
+
+状态是只读的:更改状态的唯一方法是触发动作。动作是描述更改的普通 JS 对象。就像状态是数据的最小表示一样,动作是数据更改的最小表示。
+
+
+使用纯函数更改:为了确认动作是如何转换状态树的,你需要纯函数。纯函数是返回值仅取决于其参数值的函数。
+
+
+
+如何理解“单一可信源”?
+
+单一可信源(SSOT)是构造信息模型和相关数据模式的实践,其中每个数据元素都只能在一个地方掌握(或编辑)
+Redux 使用“存储”将应用程序的整个状态存储在一个位置。因此,组件的所有状态都存储在存储中,并且存储本身会接收更新。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
+
+列出 Redux 的组件。
+
+Redux 由以下组件组成:
+
+动作——这是一个描述发生了什么的对象。
+Reducer——确定状态如何变化的地方。
+存储——整个应用程序的状态 / 对象树保存在存储中。
+视图——仅显示存储提供的数据。
+
+
+数据在 Redux 中是如何流动的?
+
+
+
+在 Redux 中如何定义动作?
+
+React 中的动作必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,你也可以为其添加更多属性。在 Redux 中使用称为“动作创建者”的函数来创建动作。以下是动作和动作创建者的示例:
+function addTodo (text ) {
+ return {
+ type : ADD_TODO,
+ text
+ }
+}
+
+
+
+说明 Reducer 的作用。
+
+Reducer 是用于指示 ACTION 反应中应用程序状态变化的简单功能。它接收先前的状态和动作,然后返回新的状态。它根据动作类型确定需要哪种更新,然后返回新值。如果没有要完成的工作,它将按原样返回先前状态。
+
+在 Redux 中存储的用途是什么?
+
+存储是一个 JavaScript 对象,可以保存应用程序的状态,并提供一些辅助方法来访问状态、调度动作并记录侦听器。应用程序的整个状态 / 对象树存储在单个存储中。因此 Redux 非常容易理解且可预测。我们可以将中间件转移到存储,以管理数据处理任务,并维护更改存储状态的各种活动的日志。通过 Reducer,所有活动都返回新的状态。
+
+Redux 与 Flux 有何不同?
+
+
+
+
+Flux
+Redux
+
+
+
+
+存储包括状态和更改逻辑
+存储和更改逻辑是分离的
+
+
+有多个存储
+只有一个存储
+
+
+所有存储不互通,是平行的
+带有分层 Reducer 的单个存储
+
+
+有单个调度器
+没有调度器的概念
+
+
+React 组件订阅到存储
+容器组件是有联系的
+
+
+状态是可变的
+状态是不可变的
+
+
+
+
+Redux 有哪些优势?
+
+Redux 的优点如下:
+
+结果的可预测性——由于总是有单一可信源,比如存储,因此当前状态与动作及应用程序的其他部分同步时不会出现混乱。
+可维护性——代码易于维护,具有可预测的结果和严格的结构。
+服务端渲染——你只需将在服务器上创建的存储传递给客户端即可。这对于初始渲染非常有用,并优化了应用程序性能,提供了更好的用户体验。
+开发人员工具——从动作到状态更改,开发人员可以利用这些工具实时跟踪应用程序中发生的所有事情。
+社区和生态系统——Redux 背后拥有巨大的社区,用起来更加便利。大批优秀的开发者为库的发展做出了贡献,并开发了很多应用程序。
+易于测试——Redux 的代码主要是较小的、纯净的和孤立的函数。这使代码可测试且独立。
+组织——Redux 精确地规定了代码的组织方式,这使得团队合作时代码更加一致,更容易理解。
+
+
+什么是 React Router?
+
+React Router 是建立在 React 之上的功能强大的路由库。它使 URL 与网页上显示的数据保持同步。它保持标准化的结构和行为,可用于开发单页 Web 应用程序。React Router 有一个简单的 API。React Router 提供了一种方法,只会显示你的应用中路由匹配你的定义的那些组件 。
+
+为什么在 React Router v4 中使用 switch 关键字?
+
+在 Switch 组件内,Route 和Redirect 组件嵌套在内部。从 Switch 顶部的 Route/Redirect 组件开始到底部的 Route/Redirect,根据浏览器中当前的 URL 是否与 Route/Redirect 组件的 prop/ 路径匹配,将每个组件评估为 true 或 false。
+Switch 只会渲染第一个匹配的子级。当我们嵌套了下面这样的路由时真的很方便:
+<Switch>
+ <Route path ="/accounts/new" component ={AddForm} />
+ <Route path ={ `/accounts /:accountId `} component ={Profile} />
+</Switch >
+
+
+
+为什么我们在 React 中需要一个路由器?
+
+路由器用于定义多个路由,并且当用户键入特定的 URL 时,如果该 URL 与路由器内部定义的任何“路由”的路径匹配,则该用户将被重定向到该路由。因此我们需要在应用程序中添加一个路由器库,以允许创建多个路由,每个路由都为我们指向一个独特的视图。
+从 React Router 包导入的组件有两个属性,一个是将用户引导到指定路径的 path,另一个是用于定义所述路径中内容的 component。
+<switch >
+ <route exact path=’/’ component={Home}/>
+ <route path=’/posts/:id’ component={Newpost}/>
+ <route path=’/posts’ component={Post}/>
+</switch>
+
+
+
+列出 React Router 的优点。
+
+几个优点是:
+
+
+就像 React 基于组件的理念一样,在 React Router v4 中 API 是“完全组件化的”。路由器可以可视化为单个根组件(),其中包含特定的子路由()。
+
+
+无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在组件中。
+
+
+包是拆分的:三个包分别用于 Web、Native 和 Core。这使我们的应用更加紧凑。它们的编码样式类似,所以很容易来回切换。
+
+
+React Router 与传统路由有何不同?
+
+
+
+
+
+~
+传统路由
+React 路由
+
+
+
+
+参与的页面
+每个视图对应一个新页面
+只涉及单个 HTML 页面
+
+
+URL 更改
+向服务器发送一个 HTTP 请求并接收对应的 HTML 页面
+只有历史属性被更改
+
+
+体验
+用户其实是在每个视图的不同页面间切换
+用户以为自己正在不同的页面间切换
+
+
+
+原文链接: https://codersera.com/blog/top-50-react-questions-you-need-to-prepare-for-the-interview-in-2019/
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/web/react_interview.json b/en-us/blog/web/react_interview.json
new file mode 100644
index 0000000..abf9b35
--- /dev/null
+++ b/en-us/blog/web/react_interview.json
@@ -0,0 +1,6 @@
+{
+ "filename": "react_interview.md",
+ "__html": "React 面试问题 \n\n如果你是一位有理想的前端开发人员,并且正在准备面试,那么这篇文章就是为你准备的。本文收集了 React 面试中最常见的 50 大问题,这是一份理想的指南,让你为 React 相关的面试做好充分的准备工作。首先我们快速了解一下 React 在市场上的需求和现状,然后再开始讨论 React 面试问题。
\n \nJavaScript 工具的市场地位正在缓慢而稳定地上升当中,而对 React 认证的需求正在飞速增长。选择正确的技术来开发应用程序或网站变得愈加艰难。React 被认为是 Javascript 语言中增长最快的框架。
\n虚拟 DOM 和可复用部件等独特特性吸引了前端开发人员的注意。尽管成熟的框架(如 Angular、Meteor 和 Vue 等)在 MVC(模型 - 视图 - 控制器)中只是一个“视图”库,但它们都有很强的竞争力。下图显示了常见 JS 框架的趋势:
\n
\n以下是面试官最有可能提出的 50 个面试问题和答案。
\nReact 面试问题——常规知识 \n\n\n真实 DOM 和虚拟 DOM 的区别
\n\n\n\n真实 DOM \n虚拟 DOM \n \n \n\n\n1.更新较慢 \n1.更新较快 \n \n\n2.可以直接更新 HTML \n2.不能直接更新 HTML \n \n\n3.元素更新时创建一个新 DOM \n3.元素更新时更新 JSX \n \n\n4.DOM 操作开销较大 \n4.DOM 操作非常容易 \n \n\n5.内存浪费严重 \n5.没有内存浪费 \n \n \n
\n \n\n什么是 React?
\n\nReact 是 2011 年由 Facebook 开发的前端 JavaScript 库。 \n它遵循基于组件的方法,这种方法可以用来构建可复用的 UI 组件。 \n它用于复杂的交互式 Web 端和移动端用户界面开发。 \n尽管它在 2015 年才开源,但得到了一家巨头的支持。 \n \n \n\nReact 的特点是什么?
\n\n轻量级 DOM,以获得更好的性能。 \n在 React 中,一切都被视为组件。 \nReact 使用 JSX(JavaScript eXtension),使我们可以编写类似于 HTML 的 JavaScript。 \nReact 不是直接运行在浏览器的文档对象模型(DOM)上,而是运行在虚拟 DOM 上。 \nReactJS 遵循单向数据流或单向数据绑定。 \n \n \n\n列出 React 的一些主要优势。
\n\n可以提高应用程序的性能。 \n可以方便地用在客户端和服务端。 \n由于有了 JSX,代码的可读性提高了。 \n使用 React 后,编写 UI 测试用例变得非常容易。 \n \n \n\nReact 有哪些局限?
\n\nReact 只是一个库,而不是一个成熟的框架。 \n它的库很大,需要花费一些时间来理解。 \n新手程序员可能很难入门。 \n由于它使用了内联模板和 JSX,编码也比较复杂。 \n \n \n\n什么是 JSX?
\n \n \nJSX 是 JavaScript XML 的简写。这是 React 使用的一种文件类型,具备 JavaScript 的表现力,并使用 HTML 作为模板语法。这样一来 HTML 文件理解起来就非常简单。这种文件可以创造稳健的应用程序并提高其效率。下面是一个 JSX 实例:
\nrender(){\n return ( \n <div > \n <h1 > Hello World from Codersera!!</h1 > \n </div > \n );\n }\n
\n\n你对虚拟 DOM 有什么了解?解释其工作机制。 \n \n虚拟 DOM 是轻量级的 JavaScript 对象,一开始只是真实 DOM 的一个副本。它是一个节点树,将组件列为对象及其属性和内容的列表。React 的渲染功能从 React 的各个部分生成一个节点树。然后,它会根据由不同用户或系统行为引起的信息模型突变来更新此树。\n虚拟 DOM 的工作机制只有简单的三步组成。
\n1. 每当任何基础信息更改时,整个 UI 就会以虚拟 DOM 的表示形式重新渲染。\n
\n
\n2. 然后计算先前的 DOM 表示和新的 DOM 表示之间的区别。\n
\n
\n3. 计算完成后,只有实际更改的内容才会更新到真实 DOM。\n
\n
\n\n为什么浏览器无法读取 JSX? \n \nReact 使用 JSX(JavaScript eXtension),我们可以用它编写类似于 HTML 的 JavaScript。但由于 JSX 不是合法的 JavaScript,因此浏览器无法直接读取它。如果 JavaScript 文件包含 JSX,则必须将其转换。你需要一个转换器将 JSX 转换为浏览器可以理解的常规 Javascript。目前最常用的转换器是 Babel。
\n\n与 ES5 相比,React 的 ES6 语法有何不同? \n \nES5 和 ES6 的语法区别如下:
\n\nvar React = require ('react' );\n\nimport React from 'react' ;\n \n******** export vs exports *********\n \n\nmodule .exports = Component;\n\nexport default Component;\n \n****** function *****\n// ES5 \n \nvar MyComponent = React .createClass ({\n render: function( ) {\n return <h3 > Hello CoderSera! </h3 > \n },\n});\n \n\nclass MyComponent extends React .Component {\n render() {\n return <h3 > Hello CoderSera! </h3 > \n }\n}\n \n ******* props ******\n \n\nvar App = React.createClass({\n propTypes : { name : React.PropTypes.string },\n render : function ( ) {\n return <h3 > Hello, { this.props.name }! < /h3 > \n },\n});\n \n\nclass App extends React .Component {\n render() {\n return <h3 > Hello, { this.props.name }! </h3 > \n }\n}\n \n ****** state *****\n \n\nvar App = React.createClass({\n getInitialState : function ( ) {\n return { name : 'world' };\n } \n \n render: function ( ) {\n return <h3 > Hello, { this.state.name }! < /h3 > ;\n },\n});\n \n\nclass App extends React .Component {\n constructor () {\n super ();\n this .state = { name : 'world' };\n }\n \n render() {\n return <h3 > Hello, { this.state.name }! < /h3 > \n }\n render() {\n return ;\n <h3 > Hello, { this.state.name }! < /h3 > \n }\n\n
\n\nReact 和 Angular 有何不同? \n \n\n\n\nReact 对比 Angular \nReact \nAngular \n \n \n\n\n架构 \n使用虚拟 DOM \n使用真实 DOM \n \n\n渲染 \n服务端渲染 \n客户端渲染 \n \n\nDOM \n使用虚拟 DOM \n使用真实 DOM \n \n\n数据绑定 \n单向数据绑定 \n双向数据绑定 \n \n\n调试 \n编译时调试 \n运行时调试 \n \n\n开发者 \nFacebook \n谷歌 \n \n \n
\n\n如何理解“在 React 中,一切都是组件”。 \n \nReact 应用程序的 UI 构建块都是组件。这些部分将整个 UI 划分为许多可自治和可复用的微小部分。然后独立的某个部分发生变化就不会影响 UI 的其余部分。
\n\n解释 React 中 render() 的目的。 \n \n它被视为普通函数,但 render() 函数必须返回某些值,无论值是否为空。调用组件文件时默认会调用 render() 方法,因为组件需要显示 HTML 标记,或者我们可以说 JSX 语法。每个 React 组件必须有一个 render() 函数,它返回单个 React 元素,该元素代表原生 DOM 组件。如果需要渲染多个 HTML 元素,则必须将它们分组在一个封闭的标签内,如form、group和div等。此函数必须保持纯净,就是说它在每次调用时必须返回相同的结果。
\nimport React, { Component } from 'react' ;\n \nclass App extends Component {\n render() {\n return (<div > <h1 className ='App-title' > hello CoderSera </h1 > </div > )\n }\n}\n \nexport default App;\n\n
\n\n什么是 Hooks? \n \nHooks 是一项新功能,使你无需编写类即可使用状态等 React 功能。来看一个 useState hook 示例。
\n<em>import </em>{useState} <em>from </ em>'react' ;<br><br><em>function </em>Example() {<br> <em>// Declare a new\n
\n\n什么是 props? \n \n\n\nReact 中的状态是什么,如何使用? \n \n组件可以通过状态来跟踪其执行的任何渲染之间的信息。
\n状态用于可变数据或将要更改的数据。这对于用户输入尤其方便。以搜索栏为例,用户输入数据时他们看到的内容也会更新。
\n\n状态和 props 的区别。 \n \n\n\n\n条件 \n状态 \nProps \n \n \n\n\n从父组件接收初始值 \n是 \n是 \n \n\n父组件可以更改值 \n否 \n是 \n \n\n在组件内设置默认值 \n是 \n是 \n \n\n在组件内更改 \n是 \n否 \n \n\n为子组件设置初始值 \n是 \n是 \n \n\n在子组件内更改 \n否 \n是 \n \n \n
\n\n如何更新组件的状态? \n \n可以使用 this.setState() 更新组件的状态。
\nclass MyComponent extends React .Component {\n constructor () {\n super ();\n this .state = {\n name : 'Maxx' ,\n id : '101' \n }\n }\n render()\n {\n setTimeout(() => {this .setState({name :'Jaeha' , id :'222' })},2000 )\n return ( \n <div > \n <h1 > Hello {this.state.name}</h1 > \n <h2 > Your Id is {this.state.id}</h2 > \n </div > \n );\n }\n }\n ReactDOM.render(\n <MyComponent /> , document .getElementById('content' )\n);\n\n
\n18.React 中的箭头函数是什么?如何使用?
\n粗箭头 => 用于定义匿名函数,这通常是将参数传递给回调函数的最简单方法。但是你需要在使用它时优化性能。注意:每次渲染组件时,在 render 方法中使用箭头函数都会创建一个新函数,这可能会影响性能。
\n\nrender() { \n return (\n <MyInput onChange={this.handleChange.bind(this) } />\n );\n}\n//With Arrow Function\nrender() { \n return(\n <MyInput onChange={ (e) => this.handleOnChange(e) } />\n );\n}\n\n
\n\n有状态和无状态组件的区别。 \n \n\n\n\n有状态组件 \n无状态组件 \n \n \n\n\n在内存中存储组件状态更改的信息 \n计算组件的内部状态 \n \n\n有权更改状态 \n无权更改状态 \n \n\n包含过去、现在和可能的未来状态更改的信息 \n没有包含关于状态更改的信息 \n \n\n无状态组件通知它们关于状态更改的需求,然后它们将 props 传递给前者 \n它们从有状态组件接收 props,将其视为回调函数 \n \n \n
\n\nReact 组件的生命周期有哪些阶段? \n \nReact 组件的生命周期分为三个不同阶段:
\n\n\n详细解释 React 组件的生命周期技术。 \n \n一些最重要的生命周期方法包括:
\n\n\ncomponentWillMount()——在初始渲染发生之前,即在 React 将组件插入 DOM 之前立即调用一次。请务必注意,在此方法中调用 this.setState() 不会触发重新渲染。
\n \n\ncomponentDidMount()——在渲染函数之后触发此方法。现在可以访问更新的 DOM,这意味着该方法是初始化其他需要访问 DOM 的 Javascript 库以及数据提取操作的最佳选择。
\n \n\ncomponentWillReceiveProps()——componentWillReceiveProps() 在组件接收新 props 时调用。在调用 render() 方法之前,我们可以用这个方法对 prop 过渡做出反应。在此函数中调用 this.setState() 不会触发额外的重新渲染,我们可以通过 this.props 访问旧的 props。
\n \n\nshouldComponentUpdate()——我们可以用它来决定下一个组件的状态是否应触发重新渲染。此方法返回一个布尔值,默认为 true。但是我们可以返回 false,并且不会调用以下方法:
\n \n\ncomponentWillUpdate()——当接收到新的 props 或状态时,在渲染(更新)之前立即调用此方法。我们可以用它在更新之前做准备,但是不允许使用 this.setState()。
\n \n\ncomponentDidUpdate()——React 更新 DOM 后立即调用此方法。我们可以使用此方法与更新后的 DOM 交互,或执行任何渲染后操作。
\n \n\ncomponentWillUnmount()——从 DOM 卸载组件之前立即调用此方法。我们可以用它执行可能需要的任何清理工作。
\n \n \n\nReact 中的事件是什么? \n \n在 React 中,事件是对特定动作(如鼠标悬停、鼠标单击和按键等)触发的反应。处理这些事件类似于处理 DOM 元素上的事件。但是在语法上存在一些差异,例如:
\n\n\n事件命名使用驼峰式大小写,而不是仅使用小写字母。
\n \n\n事件作为函数而不是字符串传递。
\n \n \n事件参数包含一组特定于事件的属性。每个事件类型都包含它自己的属性和行为,这些属性和行为只能通过它的事件处理程序访问。
\n\n如何在 React 中创建事件? \n \nclass Display extends React .Component ( { \n show(evt) {\n \n }, \n render() { \n \n return ( \n <div onClick ={this.show} > Click Me!</div > \n ); \n }\n});\n\n\n
\n\n什么是 React 中的合成事件? \n \n合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同的浏览器行为合并为一个 API。这样做是为了确保在各个浏览器的事件中显示一致的特征。
\n\n如何理解 React 中的引用? \n \nRef 是 React 引用的简写。它是一个属性,帮助存储对特定元素或组件的引用,由组件渲染的配置函数返回。它用于返回对渲染返回的特定元素或组件的引用。当我们需要 DOM 测量或向组件添加方法时,它们会派上用场。
\nclass ReferenceDemo extends React .Component {\n display() {\n const name = this .inputDemo.value;\n document .getElementById('disp' ).innerHTML = name;\n }\n render() {\n return ( \n <div > \n Name: <input type =\"text\" ref ={input => this.inputDemo = input} />\n <button name =\"Click\" onClick ={this.display} > Click</button > \n \n <h2 > Hello <span id =\"disp\" > </span > !!!</h2 > \n </div > \n );\n }\n }\n\n
\n\n列出一些应该使用引用的情况? \n \n以下是应使用 ref 的情况:
\n\n\n当你需要管理焦点、选择文本或媒体播放时。
\n \n\n触发命令式动画。
\n \n\n与第三方 DOM 库集成。
\n \n \n\n如何模块化 React 代码? \n \n可以使用 export 和 import 属性来模块化软件。它们有助于在不同的文档中单独编写组件。
\n\nexport default class ChildComponent extends React .Component {\n render() {\n return ( <div > \n <h1 > This is a child component</h1 > \n </div > )\n }\n}\n \n\nimport ChildComponent from './childcomponent.js' ;\nclass ParentComponent extends React .Component { \n render() { \n return ( \n <div > \n <App /> \n </div > \n ); \n }\n}\n\n
\n\n在 React 中如何创建表单? \n \nReact 提供了一种有状态的,响应式的方法来构建表单。与其他 DOM 元素不同,HTML 表单元素在 React 中的工作机制有所不同。例如,表单数据通常由组件而不是 DOM 处理,并且通常使用受控组件来实现。\n区别在于可以使用回调函数来处理表单事件,然后使用容器的状态存储表单数据。这使你的组件可以更好地控制表单控制元素和表单数据。\n回调函数是在发生事件(包括更改表单控制值或表单提交)时触发的。
\nhandleSubmit(event) {\n alert('A name was submitted: ' + this .state.value);\n event.preventDefault();\n}\n \nrender() {\n return ( \n \n \n \n<form onSubmit={this.handleSubmit}>\n <label>\n Name:\n <input type=\"text\" value={this.state.value} onChange={this.handleSubmit} />\n </label>\n <input type=\"submit\" value=\"Submit\" />\n </form>\n \n \n \n );\n}\n\n
\n\n如何理解受控和非受控组件? \n \n\n\n\n受控组件 \n非受控组件 \n \n \n\n\n它们不维护自己的状态 \n它们维护自己的状态 \n \n\n数据由父组件控制 \n数据由 DOM 控制 \n \n\n它们通过 props 获得当前值,然后通过回调通知更改 \n使用引用来获得它们的当前值 \n \n \n
\n\n高阶组件(HOC)是什么意思? \n \nReact 中的高阶组件是一种在组件之间共享通用功能而无需重复代码的模式。\n高阶组件实际上不是组件,它是一个接受组件并返回新组件的函数。它将一个组件转换为另一个组件,并添加其他数据或功能
\n\nHOC 可以做什么? \n \nHOC 可用于许多任务,例如:
\n\n\n代码复用,逻辑和引导抽象。
\n \n\n渲染劫持。
\n \n\n状态抽象和控制。
\n \n\nProps 操控。
\n \n \n\n什么是纯组件? \n \nReact 并没有在我们的组件中编写 shouldComponent 方法,而是引入了一个带有内置 shouldComponentUpdate 实现的新组件,它是 React.PureComponent 组件。\nReact.PureComponent 通过浅层 prop 和状态比较来实现它。在某些情况下,你可以使用 React.PureComponent 来提高性能。
\n\nReact 中键有什么用途? \n \n键可帮助 React 识别哪些项目已更改、添加或删除。应该为数组内的元素提供键,以赋予元素稳定的身份。键必须是唯一的。\n当使用动态创建的组件或用户更改列表时,React 键非常有用。设置键值后,更改后的组件就能保持唯一标识。
\n\nMVC 框架的主要问题有哪些? \n \n以下是 MVC 框架的一些主要问题:
\n\nMVC 不能解决代码复杂性问题。它也不能解决代码复用或灵活性问题。 \n它不保证解耦代码。 \n \n\n你对 Flux 有什么了解? \n \nFlux 是 Facebook 内部与 React 搭配使用的架构。它不是框架或库。只是一种新型的体系结构,是对 React 和单向数据流概念的补充:
\nFlux 的各个组成部分如下:
\n\n动作(Actions)——帮助数据传递到调度器的辅助方法。 \n调度器(Dispatcher)——接收动作并将负载广播到已注册的回调。 \n存储(Stores)——具有已注册到调度器的回调的应用程序状态和逻辑的容器。 \n控制器视图(Controller Views)——从组件中获取状态并通过 props 传递给子组件的 React 组件。 \n \n
\n\n什么是 Redux? \n \nRedux 是一种状态管理工具。尽管它主要与 React 搭配使用,但也可以与其他任何 JavaScript 框架或库搭配。
\nRedux 允许你在一个称为存储(Store)的对象中管理整个应用程序状态。
\n对存储的更新将触发与存储的已更新部分连接的组件的重新渲染。当我们想要更新某些东西时,我们称之为动作(Action)。我们还创建函数来处理这些动作并返回更新的存储。这些函数称为 Reducer。
\n\nRedux 遵循的三大原则是什么? \n \n\n\n单一可信来源:整个应用程序的状态存储在单个存储区中的对象 / 状态树中。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
\n \n\n状态是只读的:更改状态的唯一方法是触发动作。动作是描述更改的普通 JS 对象。就像状态是数据的最小表示一样,动作是数据更改的最小表示。
\n \n\n使用纯函数更改:为了确认动作是如何转换状态树的,你需要纯函数。纯函数是返回值仅取决于其参数值的函数。
\n \n \n\n如何理解“单一可信源”? \n \n单一可信源(SSOT)是构造信息模型和相关数据模式的实践,其中每个数据元素都只能在一个地方掌握(或编辑)\nRedux 使用“存储”将应用程序的整个状态存储在一个位置。因此,组件的所有状态都存储在存储中,并且存储本身会接收更新。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
\n\n列出 Redux 的组件。 \n \nRedux 由以下组件组成:
\n\n动作——这是一个描述发生了什么的对象。 \nReducer——确定状态如何变化的地方。 \n存储——整个应用程序的状态 / 对象树保存在存储中。 \n视图——仅显示存储提供的数据。 \n \n\n数据在 Redux 中是如何流动的? \n \n
\n\n在 Redux 中如何定义动作? \n \nReact 中的动作必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,你也可以为其添加更多属性。在 Redux 中使用称为“动作创建者”的函数来创建动作。以下是动作和动作创建者的示例:
\nfunction addTodo (text ) {\n return {\n type : ADD_TODO, \n text \n }\n}\n\n
\n\n说明 Reducer 的作用。 \n \nReducer 是用于指示 ACTION 反应中应用程序状态变化的简单功能。它接收先前的状态和动作,然后返回新的状态。它根据动作类型确定需要哪种更新,然后返回新值。如果没有要完成的工作,它将按原样返回先前状态。
\n\n在 Redux 中存储的用途是什么? \n \n存储是一个 JavaScript 对象,可以保存应用程序的状态,并提供一些辅助方法来访问状态、调度动作并记录侦听器。应用程序的整个状态 / 对象树存储在单个存储中。因此 Redux 非常容易理解且可预测。我们可以将中间件转移到存储,以管理数据处理任务,并维护更改存储状态的各种活动的日志。通过 Reducer,所有活动都返回新的状态。
\n\nRedux 与 Flux 有何不同? \n \n\n\n\nFlux \nRedux \n \n \n\n\n存储包括状态和更改逻辑 \n存储和更改逻辑是分离的 \n \n\n有多个存储 \n只有一个存储 \n \n\n所有存储不互通,是平行的 \n带有分层 Reducer 的单个存储 \n \n\n有单个调度器 \n没有调度器的概念 \n \n\nReact 组件订阅到存储 \n容器组件是有联系的 \n \n\n状态是可变的 \n状态是不可变的 \n \n \n
\n\nRedux 有哪些优势? \n \nRedux 的优点如下:
\n\n结果的可预测性——由于总是有单一可信源,比如存储,因此当前状态与动作及应用程序的其他部分同步时不会出现混乱。 \n可维护性——代码易于维护,具有可预测的结果和严格的结构。 \n服务端渲染——你只需将在服务器上创建的存储传递给客户端即可。这对于初始渲染非常有用,并优化了应用程序性能,提供了更好的用户体验。 \n开发人员工具——从动作到状态更改,开发人员可以利用这些工具实时跟踪应用程序中发生的所有事情。 \n社区和生态系统——Redux 背后拥有巨大的社区,用起来更加便利。大批优秀的开发者为库的发展做出了贡献,并开发了很多应用程序。 \n易于测试——Redux 的代码主要是较小的、纯净的和孤立的函数。这使代码可测试且独立。 \n组织——Redux 精确地规定了代码的组织方式,这使得团队合作时代码更加一致,更容易理解。 \n \n\n什么是 React Router? \n \nReact Router 是建立在 React 之上的功能强大的路由库。它使 URL 与网页上显示的数据保持同步。它保持标准化的结构和行为,可用于开发单页 Web 应用程序。React Router 有一个简单的 API。React Router 提供了一种方法,只会显示你的应用中路由匹配你的定义的那些组件 。
\n\n为什么在 React Router v4 中使用 switch 关键字? \n \n在 Switch 组件内,Route 和Redirect 组件嵌套在内部。从 Switch 顶部的 Route/Redirect 组件开始到底部的 Route/Redirect,根据浏览器中当前的 URL 是否与 Route/Redirect 组件的 prop/ 路径匹配,将每个组件评估为 true 或 false。\nSwitch 只会渲染第一个匹配的子级。当我们嵌套了下面这样的路由时真的很方便:
\n<Switch>\n <Route path =\"/accounts/new\" component ={AddForm} /> \n <Route path ={ `/accounts /:accountId `} component ={Profile} /> \n</Switch > \n\n
\n\n为什么我们在 React 中需要一个路由器? \n \n路由器用于定义多个路由,并且当用户键入特定的 URL 时,如果该 URL 与路由器内部定义的任何“路由”的路径匹配,则该用户将被重定向到该路由。因此我们需要在应用程序中添加一个路由器库,以允许创建多个路由,每个路由都为我们指向一个独特的视图。
\n从 React Router 包导入的组件有两个属性,一个是将用户引导到指定路径的 path,另一个是用于定义所述路径中内容的 component。
\n<switch >\n <route exact path=’/’ component={Home}/>\n <route path=’/posts/:id’ component={Newpost}/>\n <route path=’/posts’ component={Post}/>\n</switch>\n\n
\n\n列出 React Router 的优点。 \n \n几个优点是:
\n\n\n就像 React 基于组件的理念一样,在 React Router v4 中 API 是“完全组件化的”。路由器可以可视化为单个根组件(),其中包含特定的子路由()。
\n \n\n无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在组件中。
\n \n\n包是拆分的:三个包分别用于 Web、Native 和 Core。这使我们的应用更加紧凑。它们的编码样式类似,所以很容易来回切换。
\n \n\nReact Router 与传统路由有何不同?
\n \n \n\n\n\n~ \n传统路由 \nReact 路由 \n \n \n\n\n参与的页面 \n每个视图对应一个新页面 \n只涉及单个 HTML 页面 \n \n\nURL 更改 \n向服务器发送一个 HTTP 请求并接收对应的 HTML 页面 \n只有历史属性被更改 \n \n\n体验 \n用户其实是在每个视图的不同页面间切换 \n用户以为自己正在不同的页面间切换 \n \n \n
\n原文链接: https://codersera.com/blog/top-50-react-questions-you-need-to-prepare-for-the-interview-in-2019/
\n",
+ "link": "\\en-us\\blog\\web\\react_interview.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/en-us/blog/web/vue_cp_react.html b/en-us/blog/web/vue_cp_react.html
new file mode 100644
index 0000000..59df615
--- /dev/null
+++ b/en-us/blog/web/vue_cp_react.html
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+ vue_cp_react
+
+
+
+
+ 前端框架用vue还是react?清晰对比两者差异
+前言
+近两年前端技术层出不穷,目前市面上已经有了很多供前端人员使用的开发框架,转眼19年已过大半,前端框架领域日趋成熟,实现了三足鼎立的局面,截止到10月22日,Angular,react和vue数据统计如下图所示:
+
+最近在学习使用框架的时候,分别使用vue和react开发了两个移动端产品,对这两个框架的学习曲线有了一些感悟,这两个都是现在比较热门的js框架,它俩在使用方式上和学习复杂度上还是有很大区别的,这里简单总结下两者的差异。
+主要从以下几个方面入手方面展开:
+
+框架的诞生
+设计思想
+编写语法
+脚手架构建工具
+数据绑定
+虚拟DOM
+指令
+性能优化
+原生渲染native
+ssr服务端渲染
+生命周期函数
+销毁组件
+状态集管理工具
+
+诞生
+vue
+vue由尤雨溪开发,由独立团队维护,现在大部分的子项目都交给团队成员打理,Vue核心库依然主要由尤雨溪亲自维护。vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它有一个特性是虚拟DOM,从性能上碾轧angularJS,这个时候,vue1.0悄悄的问世了,它的优雅,轻便也吸引了一部分用户,开始受到关注,16年中旬,VUE2.0问世,不管从性能上,还是从成本上都隐隐超过了react,火的一塌糊涂,这个时候,angular开发团队也开发了angular2.0版本,并且更名为angular,吸收了react、vue的优点,加上angular本身的特点,也吸引到很多用户,目前已经迭代到8.0了。友情提示注意下vue的诞生时间,如果正好有小伙伴在面试,被问到你是从什么时候开始接触并且使用vue的,你要是回答用了5、6年了那场面就十分尴尬了。
+react
+起初facebook在建设instagram(图片分享)的时候,因为牵扯到一个东西叫数据流,那为了处理数据流并且还要考虑好性能方面的问题,Facebook开始对市场上的各种前端MVC框架去进行一个研究,然而并没有看上眼的,于是Facebook觉得,还是自己开发一个才是最棒的,那么他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,他们就自己开发了一套,果然大牛创造力还是很强大的。
+React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
+设计思想
+vue
+vue的官网中说它是一款渐进式框架,采用自底向上增量开发的设计。这里我们需要明确一个概念,什么是渐进式框架。在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统(components)、客户端路由(vue-router)、大规模状态管理(vuex)来构建一个完整的框架。Vue从设计角度来讲,虽然能够涵盖所有这些内容,但是你并不需要一上手就把所有东西全用上,因为没有必要。无论从学习角度,还是实际情况,这都是可选的。声明式渲染和组建系统是Vue的核心库所包含内容,而客户端路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。
+react
+react主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以手动实现,比如借助 onChange 和 setState 来实现一个双向的数据流。而vue是基于可变数据的,支持双向绑定,它提供了v-model这样的指令来实现文本框的数据流双向绑定。
+编写语法
+vue
+vue推荐的做法是webpack+vue-loader的单文件组件格式,vue保留了html、css、js分离的写法,使得现有的前端开发者在开发的时候能保持原有的习惯,更接近常用的web开发方式,模板就是普通的html,数据绑定使用mustache风格,样式直接使用css。其中style 标签还提供了一个可选的scoped属性,它会为组件内 CSS 指定作用域,用它来控制仅对当前组件有效还是全局生效。
+模板和JSX是各有利弊的东西。模板更贴近我们的HTML,可以让我们更直观地思考语义结构,更好地结合CSS的书写。
+同时vue也支持JSX语法,因为是真正的JavaScript,拥有这个语言本身的所有的能力,可以进行复杂的逻辑判断,进行选择性的返回最终要返回的DOM结构,能够实现一些在模板的语法限制下,很难做到的一些事情。
+react
+用过react的开发者可能知道,react是没有模板的,直接就是一个渲染函数,它中间返回的就是一个虚拟DOM树,React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'。JSX实际就是一套使用XML语法,用于让我们更简单地去描述树状结构的语法糖。在react中,所有的组件的渲染功能都依靠JSX。你可以在render()中编写类似XML的语法,它最终会被编译成原生JavaScript。不仅仅是 HTML 可以用 JSX 来表达,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理。JSX是基于 JS 之上的一套额外语法,学习使用起来有一定的成本。
+构建工具
+vue
+vue提供了CLI 脚手架,可以帮助你非常容易地构建项目。全局安装之后,我们就可以用 vue create命令创建一个新的项目,vue 的 CLI 跟其他 CLI不同之处在于,有多个可选模板,有简单的也有复杂的,可以让用户自定义选择需要安装的模块,还可以将你的选择保存成模板,便于后续使用。
+极简的配置,更快的安装,可以更快的上手。它也有一个更完整的模板,包括单元测试在内的各种内容都涵盖,但是,它的复杂度也更高,这又涉及到根据用例来选择恰当复杂度的问题。
+react
+React 在这方面也提供了 create-react-app,但是现在还存在一些局限性:
+
+它不允许在项目生成时进行任何配置,而 Vue CLI 运行于可升级的运行时依赖之上,该运行时可以通过插件进行扩展。
+它只提供一个构建单页面应用的默认选项,而 Vue 提供了各种用途的模板。
+它不能用用户自建的预设配置构建项目,这对企业环境下预先建立约定是特别有用的。
+
+而要注意的是这些限制是故意设计的,这有它的优势。例如,如果你的项目需求非常简单,你就不需要自定义生成过程。你能把它作为一个依赖来更新。
+数据绑定
+vue
+vue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。
+Vue采用数据劫持&发布-订阅模式的方式,vue在创建vm的时候,会将数据配置在实例当中,然后通过Object.defineProperty对数据进行操作,为数据动态添加了getter与setter方法,当获取数据的时候会触发对应的getter方法,当设置数据的时候会触发对应的setter方法,从而进一步触发vm的watcher方法,然后数据更改,vm则会进一步触发视图更新操作。
+react
+react是单向数据流,react中属性是不允许更改的,状态是允许更改的。react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。在setState中,传入一个对象,就会将组件的状态中键值对的部分更改,还可以传入一个函数,这个回调函数必须向上面方式一样的一个对象函数可以接受prevState和props。通过调用this.setState去更新this.state,不能直接操作this.state,请把它当成不可变的。
+调用setState更新this.state,它不是马上就会生效的,它是异步的。所以不要认为调用完setState后可以立马获取到最新的值。多个顺序执行的setState不是同步的一个接着一个的执行,会加入一个异步队列,然后最后一起执行,即批处理。
+setState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。
+diff算法
+vue
+vue中diff算法实现流程
+在内存中构建虚拟dom树
+将内存中虚拟dom树渲染成真实dom结构
+数据改变的时候,将之前的虚拟dom树结合新的数据生成新的虚拟dom树
+将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对),来更新只需要被替换的DOM,而不是全部重绘。在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。
+会将对比出来的差异进行重新渲染
+react
+react中diff算法实现流程
+DOM结构发生改变-----直接卸载并重新create
+DOM结构一样-----不会卸载,但是会update变化的内容
+所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点
+(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)
+React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点。
+vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。
+指令
+指令 (Directives) 是带有
+v- 前缀的特殊特性,指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
+vue
+vue中提供很多内部指令供我们使用,它可以让我们进行一些模板的操作,例如有时候,我们的data中的存放的数据不是个简单的数字或者字符串,而是数组Array类型,这个时候,我们要把数组的元素展示在视图上,就需要用到vue提供的 v-for 指令,来实现列表的渲染。
+react
+因为react中没有v-for指令,所以循环渲染的时候需要用到map()方法来渲染视图,并且将符合条件的元素放入一个新数组返回。
+性能优化
+vue
+vue中的每个组件内部自动实现了
+shouldComponentUpdate的优化,在vue里面由于依赖追踪系统的存在,当任意数据变动的时,Vue的每一个组件都精确地知道自己是否需要重绘,所以并不需要手动优化。用vue渲染这些组件的时候,数据变了,对应的组件基本上去除了手动优化的必要性。而在react中我们需要手动去优化其性能,但是当数据特别多的时候vue中的watcher也会特别多,从而造成页面卡顿,所以一般数据比较多的大型项目会倾向于使用react。在react官网中,官方也建议我们使用React来构建快速响应的大型 Web 应用程序。
+react
+当props或state发生改变的时候会触发
+shouldComponentUpdate生命周期函数,它是用来控制组件是否被重新渲染的,如果它返回true,则执行render函数,更新组件;如果它返回false,则不会触发重新渲染的过程。
+有的时候我们希望它在更新之前,和之前的状态进行一个对比,这个时候我们就需要重写
+shouldComponentUpdate来避免不必要的dom操作,对比当前的props或state和更新之后的nextProps或nextState,返回true时 ,组件更新;返回false,则不会更新,节省性能。
+shouldComponentUpdate(nextProps, nextState) {
+ if (this .props.a !== nextProps.a) {
+ return true ;
+ }
+ if (this .state.b !== nextState.b) {
+ return true ;
+ }
+ return false ;
+}
+
+我们也可以创建一个继承React.PureComponent的React组件,它自带
+shouldComponentUpdate,可以对props进行浅比较,发现更新之后的props与当前的props一样,就不会进行render了。
+classTestextendsReact.PureComponent{constructor(props){super(props);}render(){return
hello...{this.props.a}
}}
+由于React.PureComponent进行的是浅比较,也就是说它只会对比原对象的值是否相同,当我们的props或state为数组或者对象这种引用类型的时候,我们修改它的数值,由于数据引用指针没有发生改变,所以组件也是不会重新渲染的。这个时候我们就需要进行深拷贝,创建一个新的对象或数组,将原对象的各项属性的"值"(数组的所有元素)拷贝过来,是"值"而不仅仅是"引用地址"。我们可以使用slice()方法:
+ew_state.todos = new_state.todos.slice();
+
+或者引入immutable库来实现数据不可变。
+原生渲染native
+native指的是使用原生API来开发App,比如ios使用OC语言,android使用java。
+vue
+vue和Weex进行官方合作,weex是阿里巴巴发起的跨平台用户界面开发框架,它的思想是多个平台,只写一套代码,weex允许你使用 vue 语法开发不仅仅可以运行在浏览器端,还能被用于开发 iOS 和 Android 上的原生应用的组件。即只需要编写一份代码,即可运行在Web、iOS、Android上。
+weex相对来说上手比较简单,安装vue-cli之后就可以使用,学习门槛低,但是它的社区目前还处于成长期,react native的社区非常成熟活跃,有非常丰富的组件可供扩展。
+react
+react native是Facebook在2015年3月在F8开发者大会上开源的跨平台UI框架,需针对iOS、Android不同编写2份代码,使用react native需要按照文档安装配置很多依赖的工具,相对比较麻烦。weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架。
+weex的目标在于抹平各个平台的差异性,从而简化应用开发。而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题。
+ssr服务端渲染
+服务端渲染核心在于方便seo优化,后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完整的html页面,再直接返回给浏览器,以便用户浏览。
+vue
+2016 年 10 月 25 日,zeit.co背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js。
+服务端渲染支持流式渲染,因为HTTP请求也是流式,Vue 的服务端渲染结果可以直接 pipe 到返回的请求里面。这样一来,就可以更早地在浏览器中呈现给用户内容,通过合理的缓存策略,可以有效地提升服务端渲染的性能。
+
+基于 Vue.js
+自动代码分层
+服务端渲染
+强大的路由功能,支持异步数据
+静态文件服务
+ES2015+ 语法支持
+打包和压缩 JS 和 CSS
+HTML 头部标签管理
+本地开发支持热加载
+集成 ESLint
+支持各种样式预处理器: SASS、LESS、 Stylus 等等
+支持 HTTP/2 推送
+
+react
+Next是一个React框架,允许使用React构建SSR和静态web应用
+
+服务器渲染,获取数据非常简单
+无需学习新框架,支持静态导出。
+支持CSS-in-JS库
+自动代码拆分,加快页面加载速度,不加载不必要的代码
+基于Webpack的开发环境,支持模块热更新(HMR)
+支持Babel和Webpack自定义配置服务器、路由和next插件。
+能够部署在任何能运行node的平台
+内置页面搜索引擎优化(SEO)处理
+在生产环境下,打包文件体积更小,运行速度更快
+
+生命周期
+vue
+【初始化阶段(4个)】
+(1)beforeCreate
+此钩子函数不能获取到数据,dom元素也没有渲染出来,此钩子函数不会用来做什么事情。
+(2)created
+此钩子函数,数据已经挂载了,但是dom节点还是没有渲染出来,在这个钩子函数里面,如果同步更改数据的话,不会影响运行中钩子函数的执行。可以用来发送ajax请求,也可以做一些初始化事件的相关操作。
+(3)beforeMount
+代表dom节点马上要被渲染出来了,但是还没有真正的渲染出来,此钩子函数跟created钩子函数基本一样,也可以做一些初始化数据的配置。
+(4)mounted
+是生命周期初始化阶段的最后一个钩子函数,数据已经挂载完毕了,真实dom也可以获取到了。
+【运行中阶段(2个)】
+(5)beforeUpdate
+运行中钩子函数beforeUpdate默认是不会执行的,当数据更改的时候,才会执行。数据更新的时候,先调用beforeUpdate,然后数据更新引发视图渲染完成之后,再会执行updated。运行时beforeUpdate这个钩子函数获取的数据还是更新之前的数据(获取的是更新前的dom内容),在这个钩子函数里面,千万不能对数据进行更改,会造成死循环。
+(6)updated
+这个钩子函数获取的数据是更新后的数据,生成新的虚拟dom,跟上一次的虚拟dom结构进行比较,比较出来差异(diff算法)后再渲染真实dom,当数据引发dom重新渲染的时候,在updated钩子函数里面就可以获取最新的真实dom了。
+【销毁阶段(2个)】
+(7)beforeDestroy
+切换路由的时候,组件就会被销毁了,销毁之前执行beforeDestroy。在这个钩子函数里面,我们可以做一些善后的操作,例如可以清空一下全局的定时器(created钩子函数绑定的初始化阶段的事件)、清除事件绑定。
+(8)destoryed
+组件销毁后执行destroyed,销毁后组件的双向数据绑定、事件监听watcher相关的都被移除掉了,但是组件的真实dom结构还是存在在页面中的。
+添加keep-alive标签后会增加active和deactive这两个生命周期函数,初始化操作放在actived里面,一旦切换组件,因为组件没有被销毁,所以它不会执行销毁阶段的钩子函数,所以移除操作需要放在deactived里面,在里面进行一些善后操作,这个时候created钩子函数只会执行一次,销毁的钩子函数一直没有执行。
+
+react
+【初始化阶段(5个)】:
+(1)getDefaultProps:实例化组件之后,组件的getDefaultProps钩子函数会执行
+这个钩子函数的目的是为组件的实例挂载默认的属性
+这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,创建出所有实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性
+理论上来说,写成函数返回对象的方式,是为了防止实例共享,但是react专门为了让实例共享,只能让这个函数只执行一次
+组件间共享默认属性会减少内存空间的浪费,而且也不需要担心某一个实例更改属性后其他的实例也会更改的问题,因为组件不能自己更改属性,而且默认属性的优先级低。
+(2)getInitialState:为实例挂载初始状态,且每次实例化都会执行,也就是说,每一个组件实例都拥有自己独立的状态。
+(3)componentWillMount:执行componentWillMount,相当于Vue里的created+beforeMount,这里是在渲染之前最后一次更改数据的机会,在这里更改的话是不会触发render的重新执行。
+(4)render:渲染dom
+render()方法必须是一个纯函数,他不应该改变
+state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果
+shouldComponentUpdate()返回
+false,
+render()不会被调用。
+(5)componentDidMount:相当于Vue里的mounted,多用于操作真实dom
+【运行中阶段(5个)】
+当组件mount到页面中之后,就进入了运行中阶段,在这里有5个钩子函数,但是这5个函数只有在数据(属性、状态)发送改变的时候才会执行
+(1)componentWillReceiveProps(nextProps,nextState)
+当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行。初始化props时候不会主动执行
+当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数还没有同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态。
+(2)接下来就会执行shouldComponentUpdate(nextProps,nextState),这个函数的作用:当属性或状态发生改变后控制组件是否要更新,提高性能,返回true就更新,否则不更新,默认返回true。
+接收nextProp、nextState,根据根据新属性状态和原属性状态作出对比、判断后控制是否更新
+如果
+shouldComponentUpdate()返回
+false,
+componentWillUpdate,
+render和
+componentDidUpdate不会被调用。
+(3)componentWillUpdate,在这里,组件马上就要重新render了,多做一些准备工作,千万千万,不要在这里修改状态,否则会死循环 相当于Vue中的beforeUpdate
+(4)render,重新渲染dom
+(5)componentDidUpdate,在这里,新的dom结构已经诞生了,相当于Vue里的updated
+【销毁阶段】
+当组件被销毁之前的一刹那,会触发componentWillUnmount,临死前的挣扎
+相当于Vue里的beforeDestroy,所以说一般会做一些善后的事情,例如使定时器无效,取消网络请求或清理在
+componentDidMount中创建的任何监听。
+
+销毁组件
+vue
+vue在调用$destroy方法的时候就会执行beforeDestroy生命周期函数,然后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说如果想要对残留的dom结构进行处理必须在destroyed生命周期函数中处理。
+react
+react执行完componentWillUnmount之后把事件、数据、dom都全部处理掉了,也就是说当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件相当于被销毁,所以根本不需要其他的钩子函数了。react销毁组件的时候,会将组件的dom结构也移除,vue则不然,在调用destory方法销毁组件的时候,组件的dom结构还是存在于页面中的,this.$destory组件结构还是存在的,只是移除了事件监听,所以这就是为什么vue中有destroyed,而react却没有componentDidUnmount。
+状态集管理工具
+vue
+vuex是一个专门为vue构建的状态集管理工具,vue和react都是基于组件化开发的,项目中包含很多的组件,组件都会有组件嵌套,想让组件中的数据被其他组件也可以访问到就需要使用到Vuex。
+vuex的流程
+将需要共享的状态挂载到state上:this.$store.state来调用
+创建store,将状态挂载到state上,在根实例里面配置store,之后我们在组件中就可以通过this.$store.state来使用state中管理的数据,但是这样使用时,当state的数据更改的时候,vue组件并不会重新渲染,所以我们要通过计算属性computed来使用,但是当我们使用多个数据的时候这种写法比较麻烦,vuex提供了mapState辅助函数,帮助我们在组件中获取并使用vuex的store中保存的状态。
+我们通过getters来创建状态:通过this.$store.getters来调用
+可以根据某一个状态派生出一个新状态,vuex也提供了mapGetters辅助函数来帮助我们在组件中使用getters里的状态。
+使用mutations来更改state:通过this.$store.commit来调用
+我们不能直接在组件中更改state,而是需要使用mutations来更改,mutations也是一个纯对象,里面包含很多更改state的方法,这些方法的形参接收到state,在函数体里更改,这时,组件用到的数据也会更改,实现响应式。vuex提供了mapMutations方法来帮助我们在组件中调用mutations 的方法。
+使用actions来处理异步操作:this.$store.dispatch来调用
+Actions类似于mutations,不同在于:Actions提交的是mutations,而不是直接变更状态。Actions可以包含任意异步操作。也就是说,如果有这样的需求:在一个异步操作处理之后,更改状态,我们在组件中应该先调用actions,来进行异步动作,然后由actions调用mutations来更改数据。在组件中通过this.$store.dispatch方法调用actions的方法,当然也可以使用mapMutations来辅助使用。
+react
+2015年Redux出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。它的出现主要是为解决react中组件之间的通信问题。建议把数据放入到redux中管理,目的就是方便数据统一,好管理。项目一旦出现问题,可以直接定位问题点。组件扩展的时候,后续涉及到传递的问题。本来的话,组件使用自己的数据,但是后来公用组件,还需要考虑如何值传递,在redux中可以存储至少5G以上的数据。
+redux的流程
+
+
+创建store: 从redux工具中取出createStore去生成一个store。
+创建一个reducer,然后将其传入到createStore中辅助store的创建。 reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就3是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态。 想要给store创建默认状态其实就是给reducer一个参数创建默认值。
+组件通过调用store.getState方法来使用store中的state,挂载在了自己的状态上。
+组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer
+reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变, reducer返回什么状态,store.getState就可以获取什么状态。
+我们可以在组件中,利用store.subscribe方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态。
+
+小结
+vue和react的核心都是专注于轻量级的视图层,虽然只是解决一个很小的问题,但是它们庞大的生态圈提供了丰富的配套工具,一开始它并不会给你提供全套的配置方案,将所有的功能都一次性给你打包好,它只会给你提供一些简单的核心功能,当你需要做一个更复杂的应用时,再增添相应的工具。例如做一个单页应用的时候才需要用路由;做一个相当庞大的应用,涉及到多组件状态共享以及多个开发者共同协作时,才可能需要大规模状态管理方案。
+框架的存在就是为了帮助我们应对不同的项目复杂度,当我们面对一个大型、复杂的开发项目时,使用太简陋的工具会极大的降低开发人员的生产力,影响工作效率,框架的诞生就是在这些工程中提取一些重复的并且已经受过验证的模式,抽象到一个已经帮你设计好的API封装当中,帮助我们去应对不同复杂度的问题。所以在开发的过程中,选择一个合适的框架就会事半功倍。但是,框架本身也有复杂度,有些框架会让人一时不知如何上手。当你接到一个并不复杂的需求,却使用了很复杂的框架,那么就相当于杀鸡用牛刀,会遇到工具复杂度所带来的副作用,不仅会失去工具本身所带来优势,还会增加各种问题,例如学习成本、上手成本,以及实际开发效率等。
+所以并不是说做得少的框架就不如做的做的框架,每个框架都有各自的优势和劣势,并不能找到完全符合需求的框架,最重要的适合当前项目,目前两大框架的生态圈一片繁荣,react社区是当前最活跃的,最快的时候三天更新一个版本,一个问题可能存在几十种不同的解决方案,这就需要我们前端人员去在不同的功能之间做取舍,以后前端框架的发展方向应该是小而精、灵活以及开放的,核心功能+生态附加库可以帮我们更加灵活的构建项目,为了跟上前进的脚步,就需要不停的吸收最新的内容,这也是从事前端开发领域的一大乐趣,希望大家都能在学习中获得长足的进步。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/en-us/blog/web/vue_cp_react.json b/en-us/blog/web/vue_cp_react.json
new file mode 100644
index 0000000..6a5e36d
--- /dev/null
+++ b/en-us/blog/web/vue_cp_react.json
@@ -0,0 +1,6 @@
+{
+ "filename": "vue_cp_react.md",
+ "__html": "前端框架用vue还是react?清晰对比两者差异 \n前言 \n近两年前端技术层出不穷,目前市面上已经有了很多供前端人员使用的开发框架,转眼19年已过大半,前端框架领域日趋成熟,实现了三足鼎立的局面,截止到10月22日,Angular,react和vue数据统计如下图所示:
\n
\n最近在学习使用框架的时候,分别使用vue和react开发了两个移动端产品,对这两个框架的学习曲线有了一些感悟,这两个都是现在比较热门的js框架,它俩在使用方式上和学习复杂度上还是有很大区别的,这里简单总结下两者的差异。\n主要从以下几个方面入手方面展开:
\n\n框架的诞生 \n设计思想 \n编写语法 \n脚手架构建工具 \n数据绑定 \n虚拟DOM \n指令 \n性能优化 \n原生渲染native \nssr服务端渲染 \n生命周期函数 \n销毁组件 \n状态集管理工具 \n \n诞生 \nvue \nvue由尤雨溪开发,由独立团队维护,现在大部分的子项目都交给团队成员打理,Vue核心库依然主要由尤雨溪亲自维护。vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它有一个特性是虚拟DOM,从性能上碾轧angularJS,这个时候,vue1.0悄悄的问世了,它的优雅,轻便也吸引了一部分用户,开始受到关注,16年中旬,VUE2.0问世,不管从性能上,还是从成本上都隐隐超过了react,火的一塌糊涂,这个时候,angular开发团队也开发了angular2.0版本,并且更名为angular,吸收了react、vue的优点,加上angular本身的特点,也吸引到很多用户,目前已经迭代到8.0了。友情提示注意下vue的诞生时间,如果正好有小伙伴在面试,被问到你是从什么时候开始接触并且使用vue的,你要是回答用了5、6年了那场面就十分尴尬了。
\nreact \n起初facebook在建设instagram(图片分享)的时候,因为牵扯到一个东西叫数据流,那为了处理数据流并且还要考虑好性能方面的问题,Facebook开始对市场上的各种前端MVC框架去进行一个研究,然而并没有看上眼的,于是Facebook觉得,还是自己开发一个才是最棒的,那么他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,他们就自己开发了一套,果然大牛创造力还是很强大的。\nReact 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
\n设计思想 \nvue \nvue的官网中说它是一款渐进式框架,采用自底向上增量开发的设计。这里我们需要明确一个概念,什么是渐进式框架。在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统(components)、客户端路由(vue-router)、大规模状态管理(vuex)来构建一个完整的框架。Vue从设计角度来讲,虽然能够涵盖所有这些内容,但是你并不需要一上手就把所有东西全用上,因为没有必要。无论从学习角度,还是实际情况,这都是可选的。声明式渲染和组建系统是Vue的核心库所包含内容,而客户端路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。
\nreact \nreact主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以手动实现,比如借助 onChange 和 setState 来实现一个双向的数据流。而vue是基于可变数据的,支持双向绑定,它提供了v-model这样的指令来实现文本框的数据流双向绑定。
\n编写语法 \nvue \nvue推荐的做法是webpack+vue-loader的单文件组件格式,vue保留了html、css、js分离的写法,使得现有的前端开发者在开发的时候能保持原有的习惯,更接近常用的web开发方式,模板就是普通的html,数据绑定使用mustache风格,样式直接使用css。其中style 标签还提供了一个可选的scoped属性,它会为组件内 CSS 指定作用域,用它来控制仅对当前组件有效还是全局生效。\n模板和JSX是各有利弊的东西。模板更贴近我们的HTML,可以让我们更直观地思考语义结构,更好地结合CSS的书写。\n同时vue也支持JSX语法,因为是真正的JavaScript,拥有这个语言本身的所有的能力,可以进行复杂的逻辑判断,进行选择性的返回最终要返回的DOM结构,能够实现一些在模板的语法限制下,很难做到的一些事情。
\nreact \n用过react的开发者可能知道,react是没有模板的,直接就是一个渲染函数,它中间返回的就是一个虚拟DOM树,React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'。JSX实际就是一套使用XML语法,用于让我们更简单地去描述树状结构的语法糖。在react中,所有的组件的渲染功能都依靠JSX。你可以在render()中编写类似XML的语法,它最终会被编译成原生JavaScript。不仅仅是 HTML 可以用 JSX 来表达,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理。JSX是基于 JS 之上的一套额外语法,学习使用起来有一定的成本。
\n构建工具 \nvue \nvue提供了CLI 脚手架,可以帮助你非常容易地构建项目。全局安装之后,我们就可以用 vue create命令创建一个新的项目,vue 的 CLI 跟其他 CLI不同之处在于,有多个可选模板,有简单的也有复杂的,可以让用户自定义选择需要安装的模块,还可以将你的选择保存成模板,便于后续使用。\n极简的配置,更快的安装,可以更快的上手。它也有一个更完整的模板,包括单元测试在内的各种内容都涵盖,但是,它的复杂度也更高,这又涉及到根据用例来选择恰当复杂度的问题。
\nreact \nReact 在这方面也提供了 create-react-app,但是现在还存在一些局限性:
\n\n它不允许在项目生成时进行任何配置,而 Vue CLI 运行于可升级的运行时依赖之上,该运行时可以通过插件进行扩展。 \n它只提供一个构建单页面应用的默认选项,而 Vue 提供了各种用途的模板。 \n它不能用用户自建的预设配置构建项目,这对企业环境下预先建立约定是特别有用的。 \n \n而要注意的是这些限制是故意设计的,这有它的优势。例如,如果你的项目需求非常简单,你就不需要自定义生成过程。你能把它作为一个依赖来更新。
\n数据绑定 \nvue \nvue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。\nVue采用数据劫持&发布-订阅模式的方式,vue在创建vm的时候,会将数据配置在实例当中,然后通过Object.defineProperty对数据进行操作,为数据动态添加了getter与setter方法,当获取数据的时候会触发对应的getter方法,当设置数据的时候会触发对应的setter方法,从而进一步触发vm的watcher方法,然后数据更改,vm则会进一步触发视图更新操作。
\nreact \nreact是单向数据流,react中属性是不允许更改的,状态是允许更改的。react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。在setState中,传入一个对象,就会将组件的状态中键值对的部分更改,还可以传入一个函数,这个回调函数必须向上面方式一样的一个对象函数可以接受prevState和props。通过调用this.setState去更新this.state,不能直接操作this.state,请把它当成不可变的。\n调用setState更新this.state,它不是马上就会生效的,它是异步的。所以不要认为调用完setState后可以立马获取到最新的值。多个顺序执行的setState不是同步的一个接着一个的执行,会加入一个异步队列,然后最后一起执行,即批处理。\nsetState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。
\ndiff算法 \nvue \nvue中diff算法实现流程
\n在内存中构建虚拟dom树\n将内存中虚拟dom树渲染成真实dom结构\n数据改变的时候,将之前的虚拟dom树结合新的数据生成新的虚拟dom树\n将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对),来更新只需要被替换的DOM,而不是全部重绘。在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。\n会将对比出来的差异进行重新渲染
\nreact \nreact中diff算法实现流程
\nDOM结构发生改变-----直接卸载并重新create\nDOM结构一样-----不会卸载,但是会update变化的内容\n所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点\n(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)
\nReact会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点。\nvue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。
\n指令 \n指令 (Directives) 是带有\nv- 前缀的特殊特性,指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
\nvue \nvue中提供很多内部指令供我们使用,它可以让我们进行一些模板的操作,例如有时候,我们的data中的存放的数据不是个简单的数字或者字符串,而是数组Array类型,这个时候,我们要把数组的元素展示在视图上,就需要用到vue提供的 v-for 指令,来实现列表的渲染。
\nreact \n因为react中没有v-for指令,所以循环渲染的时候需要用到map()方法来渲染视图,并且将符合条件的元素放入一个新数组返回。
\n性能优化 \nvue \nvue中的每个组件内部自动实现了\nshouldComponentUpdate的优化,在vue里面由于依赖追踪系统的存在,当任意数据变动的时,Vue的每一个组件都精确地知道自己是否需要重绘,所以并不需要手动优化。用vue渲染这些组件的时候,数据变了,对应的组件基本上去除了手动优化的必要性。而在react中我们需要手动去优化其性能,但是当数据特别多的时候vue中的watcher也会特别多,从而造成页面卡顿,所以一般数据比较多的大型项目会倾向于使用react。在react官网中,官方也建议我们使用React来构建快速响应的大型 Web 应用程序。
\nreact \n当props或state发生改变的时候会触发\nshouldComponentUpdate生命周期函数,它是用来控制组件是否被重新渲染的,如果它返回true,则执行render函数,更新组件;如果它返回false,则不会触发重新渲染的过程。\n有的时候我们希望它在更新之前,和之前的状态进行一个对比,这个时候我们就需要重写\nshouldComponentUpdate来避免不必要的dom操作,对比当前的props或state和更新之后的nextProps或nextState,返回true时 ,组件更新;返回false,则不会更新,节省性能。
\nshouldComponentUpdate(nextProps, nextState) {\n if (this .props.a !== nextProps.a) {\n return true ;\n }\n if (this .state.b !== nextState.b) {\n return true ;\n }\n return false ;\n}\n
\n我们也可以创建一个继承React.PureComponent的React组件,它自带\nshouldComponentUpdate,可以对props进行浅比较,发现更新之后的props与当前的props一样,就不会进行render了。\nclassTestextendsReact.PureComponent{constructor(props){super(props);}render(){return
hello...{this.props.a}
}}\n由于React.PureComponent进行的是浅比较,也就是说它只会对比原对象的值是否相同,当我们的props或state为数组或者对象这种引用类型的时候,我们修改它的数值,由于数据引用指针没有发生改变,所以组件也是不会重新渲染的。这个时候我们就需要进行深拷贝,创建一个新的对象或数组,将原对象的各项属性的"值"(数组的所有元素)拷贝过来,是"值"而不仅仅是"引用地址"。我们可以使用slice()方法:\new_state.todos = new_state.todos.slice();\n
\n或者引入immutable库来实现数据不可变。
\n原生渲染native \nnative指的是使用原生API来开发App,比如ios使用OC语言,android使用java。
\nvue \nvue和Weex进行官方合作,weex是阿里巴巴发起的跨平台用户界面开发框架,它的思想是多个平台,只写一套代码,weex允许你使用 vue 语法开发不仅仅可以运行在浏览器端,还能被用于开发 iOS 和 Android 上的原生应用的组件。即只需要编写一份代码,即可运行在Web、iOS、Android上。\nweex相对来说上手比较简单,安装vue-cli之后就可以使用,学习门槛低,但是它的社区目前还处于成长期,react native的社区非常成熟活跃,有非常丰富的组件可供扩展。
\nreact \nreact native是Facebook在2015年3月在F8开发者大会上开源的跨平台UI框架,需针对iOS、Android不同编写2份代码,使用react native需要按照文档安装配置很多依赖的工具,相对比较麻烦。weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架。\nweex的目标在于抹平各个平台的差异性,从而简化应用开发。而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题。
\nssr服务端渲染 \n服务端渲染核心在于方便seo优化,后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完整的html页面,再直接返回给浏览器,以便用户浏览。
\nvue \n2016 年 10 月 25 日,zeit.co背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js。\n服务端渲染支持流式渲染,因为HTTP请求也是流式,Vue 的服务端渲染结果可以直接 pipe 到返回的请求里面。这样一来,就可以更早地在浏览器中呈现给用户内容,通过合理的缓存策略,可以有效地提升服务端渲染的性能。
\n\n基于 Vue.js \n自动代码分层 \n服务端渲染 \n强大的路由功能,支持异步数据 \n静态文件服务 \nES2015+ 语法支持 \n打包和压缩 JS 和 CSS \nHTML 头部标签管理 \n本地开发支持热加载 \n集成 ESLint \n支持各种样式预处理器: SASS、LESS、 Stylus 等等 \n支持 HTTP/2 推送 \n \nreact \nNext是一个React框架,允许使用React构建SSR和静态web应用
\n\n服务器渲染,获取数据非常简单 \n无需学习新框架,支持静态导出。 \n支持CSS-in-JS库 \n自动代码拆分,加快页面加载速度,不加载不必要的代码 \n基于Webpack的开发环境,支持模块热更新(HMR) \n支持Babel和Webpack自定义配置服务器、路由和next插件。 \n能够部署在任何能运行node的平台 \n内置页面搜索引擎优化(SEO)处理 \n在生产环境下,打包文件体积更小,运行速度更快 \n \n生命周期 \nvue \n【初始化阶段(4个)】\n(1)beforeCreate\n此钩子函数不能获取到数据,dom元素也没有渲染出来,此钩子函数不会用来做什么事情。\n(2)created\n此钩子函数,数据已经挂载了,但是dom节点还是没有渲染出来,在这个钩子函数里面,如果同步更改数据的话,不会影响运行中钩子函数的执行。可以用来发送ajax请求,也可以做一些初始化事件的相关操作。\n(3)beforeMount\n代表dom节点马上要被渲染出来了,但是还没有真正的渲染出来,此钩子函数跟created钩子函数基本一样,也可以做一些初始化数据的配置。\n(4)mounted\n是生命周期初始化阶段的最后一个钩子函数,数据已经挂载完毕了,真实dom也可以获取到了。\n【运行中阶段(2个)】\n(5)beforeUpdate\n运行中钩子函数beforeUpdate默认是不会执行的,当数据更改的时候,才会执行。数据更新的时候,先调用beforeUpdate,然后数据更新引发视图渲染完成之后,再会执行updated。运行时beforeUpdate这个钩子函数获取的数据还是更新之前的数据(获取的是更新前的dom内容),在这个钩子函数里面,千万不能对数据进行更改,会造成死循环。\n(6)updated\n这个钩子函数获取的数据是更新后的数据,生成新的虚拟dom,跟上一次的虚拟dom结构进行比较,比较出来差异(diff算法)后再渲染真实dom,当数据引发dom重新渲染的时候,在updated钩子函数里面就可以获取最新的真实dom了。\n【销毁阶段(2个)】\n(7)beforeDestroy\n切换路由的时候,组件就会被销毁了,销毁之前执行beforeDestroy。在这个钩子函数里面,我们可以做一些善后的操作,例如可以清空一下全局的定时器(created钩子函数绑定的初始化阶段的事件)、清除事件绑定。\n(8)destoryed\n组件销毁后执行destroyed,销毁后组件的双向数据绑定、事件监听watcher相关的都被移除掉了,但是组件的真实dom结构还是存在在页面中的。\n添加keep-alive标签后会增加active和deactive这两个生命周期函数,初始化操作放在actived里面,一旦切换组件,因为组件没有被销毁,所以它不会执行销毁阶段的钩子函数,所以移除操作需要放在deactived里面,在里面进行一些善后操作,这个时候created钩子函数只会执行一次,销毁的钩子函数一直没有执行。
\n
\nreact \n【初始化阶段(5个)】:\n(1)getDefaultProps:实例化组件之后,组件的getDefaultProps钩子函数会执行\n这个钩子函数的目的是为组件的实例挂载默认的属性\n这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,创建出所有实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性\n理论上来说,写成函数返回对象的方式,是为了防止实例共享,但是react专门为了让实例共享,只能让这个函数只执行一次\n组件间共享默认属性会减少内存空间的浪费,而且也不需要担心某一个实例更改属性后其他的实例也会更改的问题,因为组件不能自己更改属性,而且默认属性的优先级低。\n(2)getInitialState:为实例挂载初始状态,且每次实例化都会执行,也就是说,每一个组件实例都拥有自己独立的状态。\n(3)componentWillMount:执行componentWillMount,相当于Vue里的created+beforeMount,这里是在渲染之前最后一次更改数据的机会,在这里更改的话是不会触发render的重新执行。\n(4)render:渲染dom\nrender()方法必须是一个纯函数,他不应该改变\nstate,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果\nshouldComponentUpdate()返回\nfalse,\nrender()不会被调用。\n(5)componentDidMount:相当于Vue里的mounted,多用于操作真实dom\n【运行中阶段(5个)】\n当组件mount到页面中之后,就进入了运行中阶段,在这里有5个钩子函数,但是这5个函数只有在数据(属性、状态)发送改变的时候才会执行\n(1)componentWillReceiveProps(nextProps,nextState)\n当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行。初始化props时候不会主动执行\n当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数还没有同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态。\n(2)接下来就会执行shouldComponentUpdate(nextProps,nextState),这个函数的作用:当属性或状态发生改变后控制组件是否要更新,提高性能,返回true就更新,否则不更新,默认返回true。\n接收nextProp、nextState,根据根据新属性状态和原属性状态作出对比、判断后控制是否更新\n如果\nshouldComponentUpdate()返回\nfalse,\ncomponentWillUpdate,\nrender和\ncomponentDidUpdate不会被调用。\n(3)componentWillUpdate,在这里,组件马上就要重新render了,多做一些准备工作,千万千万,不要在这里修改状态,否则会死循环 相当于Vue中的beforeUpdate\n(4)render,重新渲染dom\n(5)componentDidUpdate,在这里,新的dom结构已经诞生了,相当于Vue里的updated\n【销毁阶段】\n当组件被销毁之前的一刹那,会触发componentWillUnmount,临死前的挣扎\n相当于Vue里的beforeDestroy,所以说一般会做一些善后的事情,例如使定时器无效,取消网络请求或清理在\ncomponentDidMount中创建的任何监听。
\n
\n销毁组件 \nvue \nvue在调用$destroy方法的时候就会执行beforeDestroy生命周期函数,然后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说如果想要对残留的dom结构进行处理必须在destroyed生命周期函数中处理。
\nreact \nreact执行完componentWillUnmount之后把事件、数据、dom都全部处理掉了,也就是说当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件相当于被销毁,所以根本不需要其他的钩子函数了。react销毁组件的时候,会将组件的dom结构也移除,vue则不然,在调用destory方法销毁组件的时候,组件的dom结构还是存在于页面中的,this.$destory组件结构还是存在的,只是移除了事件监听,所以这就是为什么vue中有destroyed,而react却没有componentDidUnmount。
\n状态集管理工具 \nvue \nvuex是一个专门为vue构建的状态集管理工具,vue和react都是基于组件化开发的,项目中包含很多的组件,组件都会有组件嵌套,想让组件中的数据被其他组件也可以访问到就需要使用到Vuex。\nvuex的流程
\n将需要共享的状态挂载到state上:this.$store.state来调用
\n创建store,将状态挂载到state上,在根实例里面配置store,之后我们在组件中就可以通过this.$store.state来使用state中管理的数据,但是这样使用时,当state的数据更改的时候,vue组件并不会重新渲染,所以我们要通过计算属性computed来使用,但是当我们使用多个数据的时候这种写法比较麻烦,vuex提供了mapState辅助函数,帮助我们在组件中获取并使用vuex的store中保存的状态。
\n我们通过getters来创建状态:通过this.$store.getters来调用
\n可以根据某一个状态派生出一个新状态,vuex也提供了mapGetters辅助函数来帮助我们在组件中使用getters里的状态。
\n使用mutations来更改state:通过this.$store.commit来调用
\n我们不能直接在组件中更改state,而是需要使用mutations来更改,mutations也是一个纯对象,里面包含很多更改state的方法,这些方法的形参接收到state,在函数体里更改,这时,组件用到的数据也会更改,实现响应式。vuex提供了mapMutations方法来帮助我们在组件中调用mutations 的方法。
\n使用actions来处理异步操作:this.$store.dispatch来调用
\nActions类似于mutations,不同在于:Actions提交的是mutations,而不是直接变更状态。Actions可以包含任意异步操作。也就是说,如果有这样的需求:在一个异步操作处理之后,更改状态,我们在组件中应该先调用actions,来进行异步动作,然后由actions调用mutations来更改数据。在组件中通过this.$store.dispatch方法调用actions的方法,当然也可以使用mapMutations来辅助使用。
\nreact \n2015年Redux出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。它的出现主要是为解决react中组件之间的通信问题。建议把数据放入到redux中管理,目的就是方便数据统一,好管理。项目一旦出现问题,可以直接定位问题点。组件扩展的时候,后续涉及到传递的问题。本来的话,组件使用自己的数据,但是后来公用组件,还需要考虑如何值传递,在redux中可以存储至少5G以上的数据。\nredux的流程
\n
\n\n创建store: 从redux工具中取出createStore去生成一个store。 \n创建一个reducer,然后将其传入到createStore中辅助store的创建。 reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就3是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态。 想要给store创建默认状态其实就是给reducer一个参数创建默认值。 \n组件通过调用store.getState方法来使用store中的state,挂载在了自己的状态上。 \n组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer \nreducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变, reducer返回什么状态,store.getState就可以获取什么状态。 \n我们可以在组件中,利用store.subscribe方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态。 \n \n小结 \nvue和react的核心都是专注于轻量级的视图层,虽然只是解决一个很小的问题,但是它们庞大的生态圈提供了丰富的配套工具,一开始它并不会给你提供全套的配置方案,将所有的功能都一次性给你打包好,它只会给你提供一些简单的核心功能,当你需要做一个更复杂的应用时,再增添相应的工具。例如做一个单页应用的时候才需要用路由;做一个相当庞大的应用,涉及到多组件状态共享以及多个开发者共同协作时,才可能需要大规模状态管理方案。\n框架的存在就是为了帮助我们应对不同的项目复杂度,当我们面对一个大型、复杂的开发项目时,使用太简陋的工具会极大的降低开发人员的生产力,影响工作效率,框架的诞生就是在这些工程中提取一些重复的并且已经受过验证的模式,抽象到一个已经帮你设计好的API封装当中,帮助我们去应对不同复杂度的问题。所以在开发的过程中,选择一个合适的框架就会事半功倍。但是,框架本身也有复杂度,有些框架会让人一时不知如何上手。当你接到一个并不复杂的需求,却使用了很复杂的框架,那么就相当于杀鸡用牛刀,会遇到工具复杂度所带来的副作用,不仅会失去工具本身所带来优势,还会增加各种问题,例如学习成本、上手成本,以及实际开发效率等。\n所以并不是说做得少的框架就不如做的做的框架,每个框架都有各自的优势和劣势,并不能找到完全符合需求的框架,最重要的适合当前项目,目前两大框架的生态圈一片繁荣,react社区是当前最活跃的,最快的时候三天更新一个版本,一个问题可能存在几十种不同的解决方案,这就需要我们前端人员去在不同的功能之间做取舍,以后前端框架的发展方向应该是小而精、灵活以及开放的,核心功能+生态附加库可以帮我们更加灵活的构建项目,为了跟上前进的脚步,就需要不停的吸收最新的内容,这也是从事前端开发领域的一大乐趣,希望大家都能在学习中获得长足的进步。
\n",
+ "link": "\\en-us\\blog\\web\\vue_cp_react.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/md_json/blog.json b/md_json/blog.json
index a8d5d0a..ef1a566 100644
--- a/md_json/blog.json
+++ b/md_json/blog.json
@@ -1,5 +1,20 @@
{
"en-us": [
+ {
+ "filename": "syntax.md",
+ "link": "\\en-us\\blog\\dart\\syntax.html",
+ "meta": {}
+ },
+ {
+ "filename": "docker-compose.md",
+ "link": "\\en-us\\blog\\docker\\docker-compose.html",
+ "meta": {}
+ },
+ {
+ "filename": "docker-jenkins.md",
+ "link": "\\en-us\\blog\\docker\\docker-jenkins.html",
+ "meta": {}
+ },
{
"filename": "docker.md",
"link": "\\en-us\\blog\\docker\\docker.html",
@@ -165,6 +180,11 @@
"link": "\\en-us\\blog\\exp\\code-principle.html",
"meta": {}
},
+ {
+ "filename": "cto.md",
+ "link": "\\en-us\\blog\\exp\\cto.html",
+ "meta": {}
+ },
{
"filename": "devops.md",
"link": "\\en-us\\blog\\exp\\devops.html",
@@ -240,6 +260,11 @@
"link": "\\en-us\\blog\\java\\feature.html",
"meta": {}
},
+ {
+ "filename": "javavm.md",
+ "link": "\\en-us\\blog\\java\\javavm.html",
+ "meta": {}
+ },
{
"filename": "load-class.md",
"link": "\\en-us\\blog\\java\\load-class.html",
@@ -255,6 +280,11 @@
"link": "\\en-us\\blog\\java\\rocketmq\\rmq-1.html",
"meta": {}
},
+ {
+ "filename": "springAnnotation.md",
+ "link": "\\en-us\\blog\\java\\springAnnotation.html",
+ "meta": {}
+ },
{
"filename": "often.md",
"link": "\\en-us\\blog\\linux\\often.html",
@@ -315,6 +345,11 @@
"link": "\\en-us\\blog\\net\\c_sharp.html",
"meta": {}
},
+ {
+ "filename": "c_sqlserver_nginx.md",
+ "link": "\\en-us\\blog\\net\\c_sqlserver_nginx.html",
+ "meta": {}
+ },
{
"filename": "kj.md",
"link": "\\en-us\\blog\\php\\kj.html",
@@ -345,6 +380,16 @@
"link": "\\en-us\\blog\\sql\\data_split.html",
"meta": {}
},
+ {
+ "filename": "mybatis.md",
+ "link": "\\en-us\\blog\\sql\\mybatis.html",
+ "meta": {}
+ },
+ {
+ "filename": "mysql_backups.md",
+ "link": "\\en-us\\blog\\sql\\mysql_backups.html",
+ "meta": {}
+ },
{
"filename": "mysql_index.md",
"link": "\\en-us\\blog\\sql\\mysql_index.html",
@@ -380,16 +425,6 @@
"link": "\\en-us\\blog\\sql\\sql_server_master.html",
"meta": {}
},
- {
- "filename": "20190706.md",
- "link": "\\en-us\\blog\\summary\\20190706.html",
- "meta": {}
- },
- {
- "filename": "20190715.md",
- "link": "\\en-us\\blog\\summary\\20190715.html",
- "meta": {}
- },
{
"filename": "cicd.md",
"link": "\\en-us\\blog\\tool\\cicd.html",
@@ -430,6 +465,11 @@
"link": "\\en-us\\blog\\tool\\markdown.html",
"meta": {}
},
+ {
+ "filename": "minio.md",
+ "link": "\\en-us\\blog\\tool\\minio.html",
+ "meta": {}
+ },
{
"filename": "mkdocs.md",
"link": "\\en-us\\blog\\tool\\mkdocs.html",
@@ -445,6 +485,16 @@
"link": "\\en-us\\blog\\web\\es6.html",
"meta": {}
},
+ {
+ "filename": "javascript.md",
+ "link": "\\en-us\\blog\\web\\javascript.html",
+ "meta": {}
+ },
+ {
+ "filename": "js_tool_method.md",
+ "link": "\\en-us\\blog\\web\\js_tool_method.html",
+ "meta": {}
+ },
{
"filename": "node.js.md",
"link": "\\en-us\\blog\\web\\node.js.html",
@@ -454,9 +504,34 @@
"filename": "react.md",
"link": "\\en-us\\blog\\web\\react.html",
"meta": {}
+ },
+ {
+ "filename": "react_interview.md",
+ "link": "\\en-us\\blog\\web\\react_interview.html",
+ "meta": {}
+ },
+ {
+ "filename": "vue_cp_react.md",
+ "link": "\\en-us\\blog\\web\\vue_cp_react.html",
+ "meta": {}
}
],
"zh-cn": [
+ {
+ "filename": "syntax.md",
+ "link": "\\zh-cn\\blog\\dart\\syntax.html",
+ "meta": {}
+ },
+ {
+ "filename": "docker-compose.md",
+ "link": "\\zh-cn\\blog\\docker\\docker-compose.html",
+ "meta": {}
+ },
+ {
+ "filename": "docker-jenkins.md",
+ "link": "\\zh-cn\\blog\\docker\\docker-jenkins.html",
+ "meta": {}
+ },
{
"filename": "docker.md",
"link": "\\zh-cn\\blog\\docker\\docker.html",
@@ -622,6 +697,11 @@
"link": "\\zh-cn\\blog\\exp\\code-principle.html",
"meta": {}
},
+ {
+ "filename": "cto.md",
+ "link": "\\zh-cn\\blog\\exp\\cto.html",
+ "meta": {}
+ },
{
"filename": "devops.md",
"link": "\\zh-cn\\blog\\exp\\devops.html",
@@ -697,6 +777,11 @@
"link": "\\zh-cn\\blog\\java\\feature.html",
"meta": {}
},
+ {
+ "filename": "javavm.md",
+ "link": "\\zh-cn\\blog\\java\\javavm.html",
+ "meta": {}
+ },
{
"filename": "load-class.md",
"link": "\\zh-cn\\blog\\java\\load-class.html",
@@ -712,6 +797,11 @@
"link": "\\zh-cn\\blog\\java\\rocketmq\\rmq-1.html",
"meta": {}
},
+ {
+ "filename": "springAnnotation.md",
+ "link": "\\zh-cn\\blog\\java\\springAnnotation.html",
+ "meta": {}
+ },
{
"filename": "often.md",
"link": "\\zh-cn\\blog\\linux\\often.html",
@@ -772,6 +862,11 @@
"link": "\\zh-cn\\blog\\net\\c_sharp.html",
"meta": {}
},
+ {
+ "filename": "c_sqlserver_nginx.md",
+ "link": "\\zh-cn\\blog\\net\\c_sqlserver_nginx.html",
+ "meta": {}
+ },
{
"filename": "kj.md",
"link": "\\zh-cn\\blog\\php\\kj.html",
@@ -802,6 +897,16 @@
"link": "\\zh-cn\\blog\\sql\\data_split.html",
"meta": {}
},
+ {
+ "filename": "mybatis.md",
+ "link": "\\zh-cn\\blog\\sql\\mybatis.html",
+ "meta": {}
+ },
+ {
+ "filename": "mysql_backups.md",
+ "link": "\\zh-cn\\blog\\sql\\mysql_backups.html",
+ "meta": {}
+ },
{
"filename": "mysql_index.md",
"link": "\\zh-cn\\blog\\sql\\mysql_index.html",
@@ -837,16 +942,6 @@
"link": "\\zh-cn\\blog\\sql\\sql_server_master.html",
"meta": {}
},
- {
- "filename": "20190706.md",
- "link": "\\zh-cn\\blog\\summary\\20190706.html",
- "meta": {}
- },
- {
- "filename": "20190715.md",
- "link": "\\zh-cn\\blog\\summary\\20190715.html",
- "meta": {}
- },
{
"filename": "cicd.md",
"link": "\\zh-cn\\blog\\tool\\cicd.html",
@@ -887,6 +982,11 @@
"link": "\\zh-cn\\blog\\tool\\markdown.html",
"meta": {}
},
+ {
+ "filename": "minio.md",
+ "link": "\\zh-cn\\blog\\tool\\minio.html",
+ "meta": {}
+ },
{
"filename": "mkdocs.md",
"link": "\\zh-cn\\blog\\tool\\mkdocs.html",
@@ -902,6 +1002,16 @@
"link": "\\zh-cn\\blog\\web\\es6.html",
"meta": {}
},
+ {
+ "filename": "javascript.md",
+ "link": "\\zh-cn\\blog\\web\\javascript.html",
+ "meta": {}
+ },
+ {
+ "filename": "js_tool_method.md",
+ "link": "\\zh-cn\\blog\\web\\js_tool_method.html",
+ "meta": {}
+ },
{
"filename": "node.js.md",
"link": "\\zh-cn\\blog\\web\\node.js.html",
@@ -911,6 +1021,16 @@
"filename": "react.md",
"link": "\\zh-cn\\blog\\web\\react.html",
"meta": {}
+ },
+ {
+ "filename": "react_interview.md",
+ "link": "\\zh-cn\\blog\\web\\react_interview.html",
+ "meta": {}
+ },
+ {
+ "filename": "vue_cp_react.md",
+ "link": "\\zh-cn\\blog\\web\\vue_cp_react.html",
+ "meta": {}
}
]
}
\ No newline at end of file
diff --git a/zh-cn/blog/dart/syntax.html b/zh-cn/blog/dart/syntax.html
new file mode 100644
index 0000000..47a7043
--- /dev/null
+++ b/zh-cn/blog/dart/syntax.html
@@ -0,0 +1,1905 @@
+
+
+
+
+
+
+
+
+
+ syntax
+
+
+
+
+ Dart语法学习
+目录
+
+参考资料
+语言特性
+关键字
+变量与常量
+数据类型
+运算符 operators
+控制流程语句
+异常 Exceptions
+函数 Function
+类 Class
+类-方法
+类-抽象类
+类-隐式接口
+类-扩展一个类(重写)
+库和可见性
+异步支持
+
+参考资料
+
+语言特性
+
+
+Dart所有的东西都是对象, 即使是数字numbers、函数function、null也都是对象,所有的对象都继承自Object类。
+
+
+Dart动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)。
+
+
+Dart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
+
+
+Dart中的类和接口是统一的,类即接口,你可以继承一个类,也可以实现一个类(接口),自然也包含了良好的面向对象和并发编程的支持。
+
+
+Dart 提供了顶级函数(如:main())。
+
+
+Dart 没有 public、private、protected 这些关键字,变量名以"_"开头意味着对它的 lib 是私有的。
+
+
+没有初始化的变量都会被赋予默认值 null。
+
+
+final的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const。
+
+
+编程语言并不是孤立存在的,Dart也是这样,他由语言规范、虚拟机、类库和工具等组成:
+
+SDK:SDK 包含 Dart VM、dart2js、Pub、库和工具。
+Dartium:内嵌 Dart VM 的 Chromium ,可以在浏览器中直接执行 dart 代码。
+Dart2js:将 Dart 代码编译为 JavaScript 的工具。
+Dart Editor:基于 Eclipse 的全功能 IDE,并包含以上所有工具。支持代码补全、代码导航、快速修正、重构、调试等功能。
+
+
+
+关键字(56个)
+
+
+
+关键字
+-
+-
+-
+
+
+
+
+abstract
+do
+import
+super
+
+
+as
+dynamic
+in
+switch
+
+
+assert
+else
+interface
+sync
+
+
+enum
+implements
+is
+this
+
+
+async
+export
+library
+throw
+
+
+await
+external
+mixin
+true
+
+
+break
+extends
+new
+try
+
+
+case
+factory
+null
+typedef
+
+
+catch
+false
+operator
+var
+
+
+class
+final
+part
+void
+
+
+const
+finally
+rethrow
+while
+
+
+continue
+for
+return
+with
+
+
+covariant
+get
+set
+yield
+
+
+default
+if
+static
+deferred
+
+
+
+变量与常量
+
+变量声明与初始化
+
+
+调用的变量name包含对String值为“张三” 的对象的引用,name推断变量的类型是String,但可以通过指定它来更改该类型,如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字。
+
+
+ var name = ‘Bob’;
+ Object name = '张三' ;
+ dynamic name = '李四' ;
+
+
+ String name = 'Bob' ;
+
+
+默认值
+
+
+未初始化的变量的初始值为null(包括数字),因此数字、字符串都可以调用各种方法
+
+
+ int lineCount;
+
+ assert (lineCount == null );
+ print (lineCount);
+
+
+
+final and const
+
+
+ final String outSideFinalName
+
+
+
+被final或者const修饰的变量,变量类型可以省略,建议指定数据类型。
+
+
+final name = "Bob" ;
+final String name1 = "张三" ;
+
+const name2 = "alex" ;
+const String name3 = "李四" ;
+
+
+被 final 或 const 修饰的变量无法再去修改其值。
+
+final String outSideFinalName = "Alex" ;
+
+
+outSideFinalName = "Bill" ;
+
+const String outSideName = 'Bill' ;
+
+
+
+
+
+flnal 或者 const 不能和 var 同时使用
+
+
+const var String outSideName = 'Bill' ;
+
+
+final var String name = 'Lili' ;
+
+
+常量如果是类级别的,请使用 static const
+
+
+static const String name3 = 'Tom' ;
+
+
+
+
+
+
+
+const speed = 100 ;
+const double distance = 2.5 * speed;
+
+final speed2 = 100 ;
+final double distance2 = 2.5 * speed2;
+
+
+
+const关键字不只是声明常数变量,您也可以使用它来创建常量值,以及声明创建常量值的构造函数,任何变量都可以有一个常量值。
+
+
+
+var varList = const [];
+final finalList = const [];
+const constList = const [];
+
+
+
+varList = ["haha" ];
+
+
+
+
+
+
+
+
+
+在常量表达式中,该运算符的操作数必须为'bool'、'num'、'String'或'null', const常量必须用conat类型的值初始化。
+
+const String outSideName = 'Bill' ;
+final String outSideFinalName = 'Alex' ;
+const String outSideName2 = 'Tom' ;
+
+const aConstList = const ['1' , '2' , '3' ];
+
+
+
+const validConstString = '$outSideName $outSideName2 $aConstList ' ;
+
+
+
+const validConstString = '$outSideName $outSideName2 $outSideFinalName ' ;
+
+var outSideVarName='Cathy' ;
+
+
+const validConstString = '$outSideName $outSideName2 $outSideVarName ' ;
+
+
+const String outSideConstName = 'Joy' ;
+const validConstString = '$outSideName $outSideName2 $outSideConstName ' ;
+
+
+
+
+数据类型
+
+
+num
+
+
+num 是数字类型的父类,有两个子类 int 和 double。
+
+
+int 根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。
+
+
+double 64位(双精度)浮点数,如IEEE 754标准所规定。
+
+
+int a = 1 ;
+print (a);
+
+double b = 1.12 ;
+print (b);
+
+
+int one = int .parse('1' );
+
+print (one + 2 );
+
+
+var onePointOne = double .parse('1.1' );
+
+print (onePointOne + 2 );
+
+
+String oneAsString = 1. toString();
+
+
+
+print ('$oneAsString + 2' );
+
+print ('$oneAsString 2' );
+
+
+String piAsString = 3.14159 .toStringAsFixed(2 );
+
+print (piAsString);
+
+String aString = 1.12618 .toStringAsFixed(2 );
+
+print (aString);
+
+
+
+String
+
+
+Dart里面的String是一系列 UTF-16 代码单元。
+
+
+Dart里面的String是一系列 UTF-16 代码单元。
+
+
+单引号或者双引号里面嵌套使用引号。
+
+
+用 或{} 来计算字符串中变量的值,需要注意的是如果是表达式需要${表达式}
+
+
+String singleString = 'abcdddd' ;
+String doubleString = "abcsdfafd" ;
+
+String sdString = '$singleString a "bcsd" ${singleString} ' ;
+String dsString = "abc 'aaa' $sdString " ;
+print (sdString);
+print (dsString);
+
+
+String singleString = 'aaa' ;
+String doubleString = "bbb" ;
+
+String sdString = '$singleString a "bbb" ${doubleString} ' ;
+
+print (sdString);
+
+
+String dsString = "${singleString.toUpperCase()} abc 'aaa' $doubleString .toUpperCase()" ;
+
+可以看出 ”$doubleString.toUpperCase()“ 没有加“{}“,导致输出结果是”bbb.toUpperCase()“
+print (dsString);
+
+
+
+bool
+
+Dart 是强 bool 类型检查,只有bool 类型的值是true 才被认为是true。
+只有两个对象具有bool类型:true和false,它们都是编译时常量。
+Dart的类型安全意味着您不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代码, 相反Dart使用的是显式的检查值。
+assert 是语言内置的断言函数,仅在检查模式下有效在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。
+
+
+var fullName = '' ;
+assert (fullName.isEmpty);
+
+
+var hitPoints = 0 ;
+assert (hitPoints <= 0 );
+
+
+var unicorn;
+assert (unicorn == null );
+
+
+var iMeantToDoThis = 0 / 0 ;
+assert (iMeantToDoThis.isNaN);
+
+
+
+
+List集合
+
+在Dart中,数组是List对象,因此大多数人只是将它们称为List。Dart list文字看起来像JavaScript数组文字
+
+
+List list = [10 , 7 , 23 ];
+
+print (list);
+
+
+var fruits = new List ();
+
+
+fruits.add('apples' );
+
+
+fruits.addAll(['oranges' , 'bananas' ]);
+
+List subFruits = ['apples' , 'oranges' , 'banans' ];
+
+fruits.addAll(subFruits);
+
+
+print (fruits);
+
+
+print (fruits.length);
+
+
+print (fruits.first);
+
+
+print (fruits.last);
+
+
+print (fruits[0 ]);
+
+
+print (fruits.indexOf('apples' ));
+
+
+print (fruits.removeAt(0 ));
+
+
+
+fruits.remove('apples' );
+
+
+fruits.removeLast();
+
+
+fruits.removeRange(start,end);
+
+
+fruits.removeWhere((item) => item.length >6 );
+
+
+fruits.clear();
+
+
+
+注意事项:
+
+可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,直接打印是地址值。
+和java一样list里面的元素必须保持类型一致,不一致就会报错。
+和java一样list的角标从0开始。
+如果集合里面有多个相同的元素“X”, 只会删除集合中第一个改元素
+
+
+
+
+
+Map集合
+
+一般来说,map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。Dart支持map由map文字和map类型提供。
+初始化Map方式一: 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。
+
+
+
+
+Map companys = {'Alibaba' : '阿里巴巴' , 'Tencent' : '腾讯' , 'baidu' : '百度' };
+
+print (companys);
+
+
+
+Map schoolsMap = new Map ();
+schoolsMap['first' ] = '清华' ;
+schoolsMap['second' ] = '北大' ;
+schoolsMap['third' ] = '复旦' ;
+
+print (schoolsMap);
+
+var fruits = new Map ();
+fruits["first" ] = "apple" ;
+fruits["second" ] = "banana" ;
+fruits["fifth" ] = "orange" ;
+
+print (fruits);
+
+
+
+var aMap = new Map <int , String >();
+
+
+aMap[1 ] = '小米' ;
+
+
+
+aMap[1 ] = 'alibaba' ;
+
+
+aMap[2 ] = 'alibaba' ;
+
+
+aMap[3 ] = '' ;
+
+
+aMap[4 ] = null ;
+
+print (aMap);
+
+
+assert (aMap.containsKey(1 ));
+
+
+aMap.remove(1 );
+
+print (aMap);
+
+
+注意事项
+
+map的key类型不一致也不会报错。
+添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key,比如分别是 1,2,4,看起来有间隔,事实上添加到map的时候是{1:value,2:value,4:value} 这种形式。
+map里面的key不能相同。但是value可以相同,value可以为空字符串或者为null。
+
+
+
+
+
+运算符
+
+
+
+描述
+操作符
+
+
+
+
+一元后置操作符
+expr++ expr-- () [] . ?.
+
+
+一元前置操作符
+expr !expr ~expr ++expr --expr
+
+
+乘除
+* / % ~/
+
+
+加减
++ -
+
+
+位移
+<< >>
+
+
+按位与
+&
+
+
+按位或
+
+
+
+按位异或
+^
+
+
+逻辑与
+&&
+
+
+逻辑或
+
+
+
+关系和类型判断
+>= > <= < as is is!
+
+
+等
+== !=
+
+
+如果为空
+??
+
+
+条件表达式
+expr1 ? expr2 : expr3
+
+
+赋值
+= *= /= ~/= %= += -= <<= >>= &= ^= = ??=
+
+
+级联
+..
+
+
+
+流程控制语句(Control flow statements)
+
+if...else
+for
+while do-whild
+break continue
+break continue
+assert(仅在checked模式有效)
+
+异常(Exceptions)
+
+
+throw
+
+throw new FormatException('Expected at least 1 section' );
+
+
+ throw 'Out of llamas!' ;
+
+
+因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方
+
+distanceTo(Point other) =>
+throw new UnimplementedError();
+
+
+
+catch
+
+将可能出现异常的代码放置到try语句中,可以通过 on语句来指定需要捕获的异常类型,使用catch来处理异常。
+
+ try {
+ breedMoreLlamas();
+} on OutOfLlamasException {
+
+ buyMoreLlamas();
+} on Exception catch (e) {
+
+ print ('Unknown exception: $e ' );
+} catch (e, s) {
+ print ('Exception details:\n $e ' );
+ print ('Stack trace:\n $s ' );
+}
+
+
+
+rethrow
+
+rethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用。
+
+final foo = '' ;
+
+void misbehave() {
+ try {
+ foo = "1" ;
+ } catch (e) {
+ print ('2' );
+ rethrow ;
+ }
+}
+
+void main() {
+ try {
+ misbehave();
+ } catch (e) {
+ print ('3' );
+ }
+}
+
+
+
+finally
+
+Dart的finally用来执行那些无论异常是否发生都执行的操作。
+
+
+final foo = '' ;
+
+void misbehave() {
+ try {
+ foo = "1" ;
+ } catch (e) {
+ print ('2' );
+ }
+}
+
+void main() {
+ try {
+ misbehave();
+ } catch (e) {
+ print ('3' );
+ } finally {
+ print ('4' );
+ }
+}
+
+
+
+
+函数 Function
+
+ bool isNoble(int atomicNumber) {
+ return _nobleGases[atomicNumber] != null ;
+ }
+
+
+
+main()函数
+
+每个应用程序都必须有一个顶层main()函数,它可以作为应用程序的入口点。该main()函数返回void并具有List参数的可选参数。
+
+void main() {
+querySelector ('#sample_text_id' )
+ ..text = 'Click me!'
+ ..onClick.listen(reverseText);
+}
+
+
+
+级联符号..允许您在同一个对象上进行一系列操作。除了函数调用之外,还可以访问同一对象上的字段。这通常会为您节省创建临时变量的步骤,并允许您编写更流畅的代码。
+
+querySelector ('#confirm' )
+ ..text = 'Confirm'
+ ..classes.add('important' )
+ ..onClick.listen((e) => window .alert('Confirmed!' ));
+
+
+
+var button = querySelector ('#confirm' );
+button.text = 'Confirm' ;
+button.classes.add('important' );
+button.onClick.listen((e) => window .alert('Confirmed!' ));
+
+
+
+final addressBook = (AddressBookBuilder()
+..name = 'jenny'
+..email = 'jenny@example.com'
+..phone = (PhoneNumberBuilder()
+ ..number = '415-555-0100'
+ ..label = 'home' )
+ .build())
+.build();
+
+
+
+当返回值是void时不能构建级联。 例如,以下代码失败:
+
+var sb = StringBuffer ();
+sb.write('foo' )
+ ..write('bar' );
+
+
+
+注意: 严格地说,级联的..符号不是操作符。它只是Dart语法的一部分。
+
+
+
+可选参数
+
+可选的命名参数, 定义函数时,使用{param1, param2, …},用于指定命名参数。例如:
+
+
+void enableFlags({bool bold, bool hidden}) {
+
+}
+
+enableFlags(bold: true , hidden: false );
+
+
+可选的位置参数,用[]它们标记为可选的位置参数:
+
+String say(String from, String msg, [String device]) {
+ var result = '$from says $msg ' ;
+ if (device != null ) {
+ result = '$result with a $device ' ;
+ }
+ return result;
+}
+
+
+ say('Bob' , 'Howdy' );
+
+
+ say('Bob' , 'Howdy' , 'smoke signal' );
+
+
+
+默认参数
+
+函数可以使用=为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。
+下面是为命名参数设置默认值的示例:
+
+
+void enableFlags2({bool bold = false , bool hidden = false }) {
+
+}
+
+
+enableFlags2(bold: true );
+
+
+ String say(String from, String msg,
+ [String device = 'carrier pigeon' , String mood]) {
+ var result = '$from says $msg ' ;
+ if (device != null ) {
+ result = '$result with a $device ' ;
+ }
+ if (mood != null ) {
+ result = '$result (in a $mood mood)' ;
+ }
+ return result;
+}
+
+
+say('Bob' , 'Howdy' );
+
+
+您还可以将list或map作为默认值传递。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认list和gifts参数的默认map。
+
+
+void doStuff(
+ {List <int > list = const [1 , 2 , 3 ],
+ Map <String , String > gifts = const {'first' : 'paper' ,
+ 'second' : 'cotton' , 'third' : 'leather'
+ }}) {
+ print ('list: $list ' );
+ print ('gifts: $gifts ' );
+}
+
+
+
+作为一个类对象的功能
+
+void printElement(int element) {
+ print (element);
+}
+
+var list = [1 , 2 , 3 ];
+
+
+list.forEach(printElement);
+
+
+ var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!' ;
+assert (loudify('hello' ) == '!!! HELLO !!!' );
+
+
+
+匿名函数
+
+大多数函数都能被命名为匿名函数,如 main() 或 printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建lambda或闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。
+一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。
+下面的代码块包含函数的主体:
+
+([[Type ] param1[, …]]) {
+ codeBlock;
+};
+
+
+下面的示例定义了一个具有无类型参数的匿名函数item,该函数被list中的每个item调用,输出一个字符串,该字符串包含指定索引处的值。
+
+ var list = ['apples' , 'bananas' , 'oranges' ];
+list.forEach((item) {
+ print ('${list.indexOf(item)} : $item ' );
+});
+
+
+如果函数只包含一条语句,可以使用箭头符号=>来缩短它, 比如上面的例2可以简写成:
+
+list.forEach((item) => print ('${list.indexOf(item)} : $item ' ));
+
+
+
+返回值
+
+所有函数都返回一个值,如果没有指定返回值,则语句return null,隐式地附加到函数体。
+
+foo() {}
+assert (foo() == null );
+
+
+
+类(Classes)
+
+
+对象
+
+Dart 是一种面向对象的语言,并且支持基于mixin的继承方式。
+Dart 语言中所有的对象都是某一个类的实例,所有的类有同一个基类--Object。
+基于mixin的继承方式具体是指:一个类可以继承自多个父类。
+使用new语句来构造一个类,构造函数的名字可能是ClassName,也可以是ClassName.identifier, 例如:
+
+ var jsonData = JSON.decode('{"x":1, "y":2}' );
+
+
+var p1 = new Point(2 , 2 );
+
+
+var p2 = new Point.fromJson(jsonData);
+
+
+
+var p = new Point(2 , 2 );
+
+
+p.y = 3 ;
+
+
+assert (p.y == 3 );
+
+
+num distance = p.distanceTo(new Point(4 , 4 ));
+
+
+使用?.来确认前操作数不为空, 常用来替代. , 避免左边操作数为null引发异常。
+
+
+p?.y = 4 ;
+
+
+使用const替代new来创建编译时的常量构造函数。
+
+ var p = const ImmutablePoint(2 , 2 );
+
+
+使用runtimeType方法,在运行中获取对象的类型。该方法将返回Type 类型的变量。
+
+ print ('The type of a is ${a.runtimeType} ' );
+
+
+
+实例化变量(Instance variables)
+
+在类定义中,所有没有初始化的变量都会被初始化为null。
+
+ class Point {
+ num x;
+ num y;
+ num z = 0 ;
+}
+
+
+类定义中所有的变量, Dart语言都会隐式的定义 setter 方法,针对非空的变量会额外增加 getter 方法。
+
+class Point {
+num x;
+num y;
+}
+
+main() {
+ var point = new Point();
+ point.x = 4 ;
+ assert (point.x == 4 );
+ assert (point.y == null );
+}
+
+
+
+构造函数(Constructors)
+
+声明一个和类名相同的函数,来作为类的构造函数。
+
+ class Point {
+ num x;
+ num y;
+
+ Point(num x, num y) {
+
+ this .x = x;
+ this .y = y;
+ }
+}
+
+
+this关键字指向了当前类的实例, 上面的代码可以简化为:
+
+ class Point {
+ num x;
+ num y;
+
+
+
+ Point(this .x, this .y);
+}
+
+
+
+构造函数不能继承(Constructors aren’t inherited)
+
+Dart 语言中,子类不会继承父类的命名构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。
+
+
+
+命名的构造函数(Named constructors)
+
+使用命名构造函数从另一类或现有的数据中快速实现构造函数。
+
+class Point {
+ num x;
+ num y;
+
+ Point(this .x, this .y);
+
+
+ Point.fromJson(Map json) {
+ x = json['x' ];
+ y = json['y' ];
+ }
+}
+
+
+构造函数不能被继承,父类中的命名构造函数不能被子类继承。如果想要子类也拥有一个父类一样名字的构造函数,必须在子类是实现这个构造函数。
+
+
+
+调用父类的非默认构造函数
+
+
+默认情况下,子类只能调用父类的无名,无参数的构造函数; 父类的无名构造函数会在子类的构造函数前调用; 如果initializer list 也同时定义了,则会先执行initializer list 中的内容,然后在执行父类的无名无参数构造函数,最后调用子类自己的无名无参数构造函数。即下面的顺序:
+
+initializer list(初始化列表)
+super class’s no-arg constructor(父类无参数构造函数)
+main class’s no-arg constructor (主类无参数构造函数)
+
+
+
+如果父类不显示提供无名无参数构造函数的构造函数,在子类中必须手打调用父类的一个构造函数。这种情况下,调用父类的构造函数的代码放在子类构造函数名后,子类构造函数体前,中间使用:(colon) 分割。
+
+
+class Person {
+ String firstName;
+
+Person.fromJson(Map data) {
+ print ('in Person' );
+}
+}
+
+class Employee extends Person {
+
+super .fromJson(data)
+Employee.fromJson(Map data) : super .fromJson(data) {
+ print ('in Employee' );
+}
+}
+
+main() {
+var emp = new Employee.fromJson({});
+
+
+
+
+if (emp is Person) {
+
+ emp.firstName = 'Bob' ;
+}
+(emp as Person).firstName = 'Bob' ;
+}
+
+
+
+
+初始化列表
+
+除了调用父类的构造函数,也可以通过初始化列表在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示:
+
+class Point {
+num x;
+num y;
+
+Point(this .x, this .y);
+
+
+Point.fromJson(Map jsonMap)
+: x = jsonMap['x' ],
+ y = jsonMap['y' ] {
+ print ('In Point.fromJson(): ($x , $y )' );
+}
+}
+
+注意:上述代码,初始化程序无法访问 this 关键字。
+
+
+静态构造函数
+
+如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。为此,需要定义一个 const 构造函数并确保所有的实例变量都是 final 的。
+
+class ImmutablePoint {
+ final num x;
+ final num y;
+ const ImmutablePoint(this .x, this .y);
+ static final ImmutablePoint origin = const ImmutablePoint(0 , 0 );
+}
+
+
+
+
+重定向构造函数
+
+有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:分隔。
+
+class Point {
+ num x;
+ num y;
+
+
+ Point(this .x, this .y) {
+ print ("Point($x , $y )" );
+ }
+
+
+ Point.alongXAxis(num x) : this (x, 0 );
+}
+
+void main() {
+ var p1 = new Point(1 , 2 );
+ var p2 = new Point.alongXAxis(4 );
+}
+
+
+
+常量构造函数
+
+
+
+工厂构造函数
+
+当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,工厂构造函数可能从缓存返回实例,或者它可能返回子类型的实例。 下面的示例演示一个工厂构造函数从缓存返回的对象:
+
+class Logger {
+final String name;
+bool mute = false ;
+
+
+static final Map <String , Logger> _cache = <String , Logger>{};
+
+factory Logger(String name) {
+ if (_cache.containsKey(name)) {
+ return _cache[name];
+ } else {
+ final logger = new Logger._internal(name);
+ _cache[name] = logger;
+ return logger;
+ }
+ }
+
+ Logger._internal(this .name);
+
+ void log(String msg) {
+ if (!mute) {
+ print (msg);
+ }
+ }
+
+}
+
+注意:工厂构造函数不能用 this。
+
+
+方法
+
+
+
+实例方法
+
+对象的实例方法可以访问实例变量和 this 。以下示例中的 distanceTo() 方法是实例方法的一个例子:
+
+import 'dart:math' ;
+
+class Point {
+ num x;
+ num y;
+ Point(this .x, this .y);
+
+ num distanceTo(Point other) {
+ var dx = x - other.x;
+ var dy = y - other.y;
+ return sqrt(dx * dx + dy * dy);
+ }
+}
+
+
+
+setters 和 Getters
+
+是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的 getter 方法,合适的话可能还会有 setter 方法。你可以通过实现 getters 和 setters 来创建附加属性,也就是直接使用 get 和 set 关键词:
+
+class Rectangle {
+num left;
+num top;
+num width;
+num height;
+
+Rectangle(this .left, this .top, this .width, this .height);
+
+
+num get right => left + width;
+set right(num value) => left = value - width;
+num get bottom => top + height;
+set bottom(num value) => top = value - height;
+}
+
+main() {
+var rect = new Rectangle(3 , 4 , 20 , 15 );
+assert (rect.left == 3 );
+rect.right = 12 ;
+assert (rect.left == -8 );
+}
+
+
+借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。
+注: 不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。
+
+
+
+抽象方法
+
+Instance , getter 和 setter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。要创建一个抽象方法,使用分号(;)代替方法体:
+
+abstract class Doer {
+
+ void doSomething();
+}
+
+class EffectiveDoer extends Doer {
+ void doSomething() {
+
+ }
+}
+
+
+
+
+枚举类型
+
+枚举类型,通常被称为 enumerations 或 enums ,是一种用来代表一个固定数量的常量的特殊类。
+声明一个枚举类型需要使用关键字 enum :
+
+enum Color {
+ red,
+ green,
+ blue
+}
+
+
+
+在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。
+
+assert (Color.red.index == 0 );
+assert (Color.green.index == 1 );
+assert (Color.blue.index == 2 );
+
+
+要得到枚举列表的所有值,可使用枚举的 values 常量。
+
+List <Color> colors = Color.values;
+assert (colors[2 ] == Color.blue);
+
+
+
+
+
+你可以在 switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告:
+
+ ***枚举类型有以下限制***
+ * 你不能在子类中混合或实现一个枚举。
+ * 你不能显式实例化一个枚举。
+ enum Color {
+ red,
+ green,
+ blue
+ }
+
+ Color aColor = Color.blue;
+ switch (aColor) {
+ case Color.red:
+ print ('Red as roses!' );
+ break ;
+
+ case Color.green:
+ print ('Green as grass!' );
+ break ;
+
+ default :
+ print (aColor);
+ }
+
+
+
+
+为类添加特征:mixins
+
+mixins 是一种多类层次结构的类的代码重用。
+要使用 mixins ,在 with 关键字后面跟一个或多个 mixin 的名字。下面的例子显示了两个使用mixins的类:
+
+ class Musician extends Performer with Musical {
+
+}
+
+class Maestro extends Person with Musical ,
+ Aggressive , Demented {
+
+ Maestro(String maestroName) {
+ name = maestroName;
+ canConduct = true ;
+ }
+}
+
+
+要实现 mixin ,就创建一个继承 Object 类的子类,不声明任何构造函数,不调用 super 。例如:
+
+abstract class Musical {
+bool canPlayPiano = false ;
+bool canCompose = false ;
+bool canConduct = false ;
+
+ void entertainMe() {
+ if (canPlayPiano) {
+ print ('Playing piano' );
+ } else if (canConduct) {
+ print ('Waving hands' );
+ } else {
+ print ('Humming to self' );
+ }
+ }
+}
+
+
+
+类的变量和方法
+
+使用 static 关键字来实现类变量和类方法。
+只有当静态变量被使用时才被初始化。
+静态变量, 静态变量(类变量)对于类状态和常数是有用的:
+
+class Color {
+ static const red = const Color('red' );
+ final String name;
+ const Color(this .name);
+}
+
+main() {
+ assert (Color.red.name == 'red' );
+}
+
+
+静态方法, 静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如:
+
+ import 'dart:math' ;
+
+class Point {
+ num x;
+ num y;
+ Point(this .x, this .y);
+
+ static num distanceBetween(Point a, Point b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ return sqrt(dx * dx + dy * dy);
+ }
+}
+
+main() {
+ var a = new Point(2 , 2 );
+ var b = new Point(4 , 4 );
+ var distance = Point.distanceBetween(a, b);
+ assert (distance < 2.9 && distance > 2.8 );
+}
+
+
+
+注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。
+你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。
+
+抽象类
+
+使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象中也包含一些实现。如果你想让你的抽象类被实例化,请定义一个 工厂构造函数 。
+抽象类通常包含 抽象方法。下面是声明一个含有抽象方法的抽象类的例子:
+
+
+abstract class AbstractContainer {
+
+
+void updateChildren();
+}
+
+
+下面的类不是抽象类,因此它可以被实例化,即使定义了一个抽象方法:
+
+ class SpecializedContainer extends AbstractContainer {
+
+
+ void updateChildren() {
+
+ }
+
+
+void doSomething();
+}
+
+类-隐式接口
+
+
+
+class Person {
+
+ final _name;
+
+
+ Person(this ._name);
+
+
+ String greet(who) => 'Hello, $who . I am $_name .' ;
+}
+
+
+class Imposter implements Person {
+
+ final _name = "" ;
+
+ String greet(who) => 'Hi $who . Do you know who I am?' ;
+}
+
+greetBob(Person person) => person.greet('bob' );
+
+main() {
+ print (greetBob(new Person('kathy' )));
+ print (greetBob(new Imposter()));
+}
+
+
+
+class Point implements Comparable , Location {
+
+}
+
+
+
+类-扩展一个类
+
+使用 extends 创建一个子类,同时 supper 将指向父类:
+
+ class Television {
+ void turnOn() {
+ _illuminateDisplay();
+ _activateIrSensor();
+ }
+
+ }
+
+ class SmartTelevision extends Television {
+
+ void turnOn() {
+ super .turnOn();
+ _bootNetworkInterface();
+ _initializeMemory();
+ _upgradeApps();
+ }
+
+ }
+
+
+子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法 noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。
+
+ class A {
+
+ void noSuchMethod(Invocation mirror) {
+ print ('You tried to use a non-existent member:' +
+ '${mirror.memberName} ' );
+ }
+ }
+
+
+
+你可以使用 @override 注释来表明你重写了一个成员。
+
+ class A {
+ @override
+ void noSuchMethod(Invocation mirror) {
+
+ }
+ }
+
+
+
+如果你用 noSuchMethod() 实现每一个可能的 getter 方法,setter 方法和类的方法,那么你可以使用 @proxy 标注来避免警告。
+
+@proxy
+ class A {
+ void noSuchMethod(Invocation mirror) {
+
+ }
+ }
+
+库和可见性
+
+
+import,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。
+
+
+库可以分布式使用包。见 Pub Package and Asset Manager 中有关pub(SDK中的一个包管理器)。
+
+
+使用库
+
+使用 import 来指定如何从一个库命名空间用于其他库的范围。
+使用 import 来指定如何从一个库命名空间用于其他库的范围。
+
+ import 'dart:html' ;
+
+
+唯一需要 import 的参数是一个指向库的 URI。对于内置库,URI中具有特殊dart:scheme。对于其他库,你可以使用文件系统路径或package:scheme。包 package:scheme specifies libraries ,如pub工具提供的软件包管理器库。例如:
+
+import 'dart:io' ;
+import 'package:mylib/mylib.dart' ;
+import 'package:utils/utils.dart' ;
+
+
+
+指定库前缀
+
+如果导入两个库是有冲突的标识符,那么你可以指定一个或两个库的前缀。例如,如果 library1 和 library2 都有一个元素类,那么你可能有这样的代码:
+
+import 'package:lib1/lib1.dart' ;
+import 'package:lib2/lib2.dart' as lib2;
+
+var element1 = new Element ();
+var element2 =
+new lib2.Element ();
+
+
+
+导入部分库
+
+如果想使用的库一部分,你可以选择性导入库。例如:
+
+
+import 'package:lib1/lib1.dart' show foo;
+
+
+import 'package:lib2/lib2.dart' hide foo;
+
+
+
+
+延迟加载库
+
+
+* 延迟(deferred)加载(也称为延迟(lazy)加载)允许应用程序按需加载库。下面是当你可能会使用延迟加载某些情况:
+
+ * 为了减少应用程序的初始启动时间;
+ * 执行A / B测试-尝试的算法的替代实施方式中;
+ * 加载很少使用的功能,例如可选的屏幕和对话框。
+
+
+
+* 为了延迟加载一个库,你必须使用 deferred as 先导入它。
+
+``` dart
+import 'package:deferred/hello.dart' deferred as hello;
+```
+
+* 当需要库时,使用该库的调用标识符调用 LoadLibrary()。
+``` dart
+greet() async {
+await hello.loadLibrary();
+hello.printGreeting();
+}
+```
+* 在前面的代码,在库加载好之前,await关键字都是暂停执行的。有关 async 和 await 见 asynchrony support 的更多信息。
+您可以在一个库调用 LoadLibrary() 多次都没有问题。该库也只被加载一次。
+
+* 当您使用延迟加载,请记住以下内容:
+
+ * 延迟库的常量在其作为导入文件时不是常量。记住,这些常量不存在,直到迟库被加载完成。
+ * 你不能在导入文件中使用延迟库常量的类型。相反,考虑将接口类型移到同时由延迟库和导入文件导入的库。
+ * Dart隐含调用LoadLibrary()插入到定义deferred as namespace。在调用LoadLibrary()函数返回一个Future。
+
+
+
+库的实现
+
+用 library 来来命名库,用part来指定库中的其他文件。 注意:不必在应用程序中(具有顶级main()函数的文件)使用library,但这样做可以让你在多个文件中执行应用程序。
+
+
+
+声明库
+
+利用library identifier(库标识符)指定当前库的名称:
+
+
+library ballgame;
+
+
+import 'dart:html' ;
+
+
+
+
+
+关联文件与库
+
+library ballgame;
+
+import 'dart:html' ;
+
+
+part 'ball.dart' ;
+part 'util.dart' ;
+
+
+
+
+第二个文件ball.dart,实现了球赛库的一部分:
+
+part of ballgame;
+
+
+
+
+第三个文件,util.dart,实现了球赛库的其余部分:
+
+part of ballgame;
+
+
+
+
+
+重新导出库(Re-exporting libraries)
+*可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集。
+
+library french;
+
+hello() => print ('Bonjour!' );
+goodbye() => print ('Au Revoir!' );
+
+
+library togo;
+
+import 'french.dart' ;
+export 'french.dart' show hello;
+
+
+import 'togo.dart' ;
+
+void main() {
+ hello();
+ goodbye();
+}
+
+
+
+
+异步的支持
+
+
+Dart 添加了一些新的语言特性用于支持异步编程。最通常使用的特性是 async 方法和 await 表达式。Dart 库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成
+
+
+当你需要使用 Future 来表示一个值时,你有两个选择。
+
+使用 async 和 await
+使用 Future API
+
+
+
+同样的,当你需要从 Stream 获取值的时候,你有两个选择。
+
+
+使用 async 和一个异步的 for 循环 (await for)
+使用 Stream API
+
+使用 async 和 await 的代码是异步的,不过它看起来很像同步的代码。比如这里有一段使用 await 等待一个异步函数结果的代码:
+
+await lookUpVersion()
+
+
+要使用 await,代码必须用 await 标记
+
+ checkVersion() async {
+ var version = await lookUpVersion();
+ if (version == expectedVersion) {
+
+ } else {
+
+ }
+ }
+
+
+你可以使用 try, catch, 和 finally 来处理错误并精简使用了 await 的代码。
+
+ try {
+ server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044 );
+ } catch (e) {
+
+ }
+
+
+
+声明异步函数
+
+一个异步函数是一个由 async 修饰符标记的函数。虽然一个异步函数可能在操作上比较耗时,但是它可以立即返回-在任何方法体执行之前。
+
+checkVersion() async {
+
+}
+
+lookUpVersion() async => ;
+
+
+在函数中添加关键字 async 使得它返回一个 Future,比如,考虑一下这个同步函数,它将返回一个字符串。
+String lookUpVersionSync() => '1.0.0';
+如果你想更改它成为异步方法-因为在以后的实现中将会非常耗时-它的返回值是一个 Future 。
+Future lookUpVersion() async => '1.0.0';
+请注意函数体不需要使用 Future API,如果必要的话 Dart 将会自己创建 Future 对象
+
+
+
+使用带 future 的 await 表达式
+
+await expression
+
+
+在异步方法中你可以使用 await 多次。比如,下列代码为了得到函数的结果一共等待了三次。
+
+var entrypoint = await findEntrypoint();
+var exitCode = await runExecutable(entrypoint, args);
+await flushThenExit(exitCode);
+
+
+
+在 await 表达式中, 表达式 的值通常是一个 Future 对象;如果不是,那么这个值会自动转为 Future。这个 Future 对象表明了表达式应该返回一个对象。await 表达式 的值就是返回的一个对象。在对象可用之前,await 表达式将会一直处于暂停状态。
+
+
+如果 await 没有起作用,请确认它是一个异步方法。比如,在你的 main() 函数里面使用await,main() 的函数体必须被 async 标记:
+
+
+main() async {
+ checkVersion();
+ print ('In main: version is ${await lookUpVersion()} ' );
+}
+
+
+
+
+结合 streams 使用异步循环
+
+await for (variable declaration in expression) {
+
+}
+
+
+
+表达式 的值必须有Stream 类型(流类型)。执行过程如下:
+
+在 stream 发出一个值之前等待
+执行 for 循环的主体,把变量设置为发出的值。
+重复 1 和 2,直到 Stream 关闭
+
+
+
+如果要停止监听 stream ,你可以使用 break 或者 return 语句,跳出循环并取消来自 stream 的订阅 。
+
+
+如果一个异步 for 循环没有正常运行,请确认它是一个异步方法。 比如,在应用的 main() 方法中使用异步的 for 循环时,main() 的方法体必须被 async 标记。
+
+
+main() async {
+ ...
+
+ await for (var request in requestServer) {
+ handleRequest(request);
+ }
+
+ ...
+}
+
+
+
+更多关于异步编程的信息,请看 dart:async 库部分的介绍。你也可以看文章 Dart Language Asynchrony Support: Phase 1
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/dart/syntax.json b/zh-cn/blog/dart/syntax.json
new file mode 100644
index 0000000..2fd9202
--- /dev/null
+++ b/zh-cn/blog/dart/syntax.json
@@ -0,0 +1,6 @@
+{
+ "filename": "syntax.md",
+ "__html": "Dart语法学习 \n目录 \n\n参考资料 \n语言特性 \n关键字 \n变量与常量 \n数据类型 \n运算符 operators \n控制流程语句 \n异常 Exceptions \n函数 Function \n类 Class \n类-方法 \n类-抽象类 \n类-隐式接口 \n类-扩展一个类(重写) \n库和可见性 \n异步支持 \n \n参考资料 \n\n语言特性 \n\n\nDart所有的东西都是对象, 即使是数字numbers、函数function、null也都是对象,所有的对象都继承自Object类。
\n \n\nDart动态类型语言, 尽量给变量定义一个类型,会更安全,没有显示定义类型的变量在 debug 模式下会类型会是 dynamic(动态的)。
\n \n\nDart 在 running 之前解析你的所有代码,指定数据类型和编译时的常量,可以提高运行速度。
\n \n\nDart中的类和接口是统一的,类即接口,你可以继承一个类,也可以实现一个类(接口),自然也包含了良好的面向对象和并发编程的支持。
\n \n\nDart 提供了顶级函数(如:main())。
\n \n\nDart 没有 public、private、protected 这些关键字,变量名以"_"开头意味着对它的 lib 是私有的。
\n \n\n没有初始化的变量都会被赋予默认值 null。
\n \n\nfinal的值只能被设定一次。const 是一个编译时的常量,可以通过 const 来创建常量值,var c=const[];,这里 c 还是一个变量,只是被赋值了一个常量值,它还是可以赋其它值。实例变量可以是 final,但不能是 const。
\n \n\n编程语言并不是孤立存在的,Dart也是这样,他由语言规范、虚拟机、类库和工具等组成:
\n\nSDK:SDK 包含 Dart VM、dart2js、Pub、库和工具。 \nDartium:内嵌 Dart VM 的 Chromium ,可以在浏览器中直接执行 dart 代码。 \nDart2js:将 Dart 代码编译为 JavaScript 的工具。 \nDart Editor:基于 Eclipse 的全功能 IDE,并包含以上所有工具。支持代码补全、代码导航、快速修正、重构、调试等功能。 \n \n \n \n关键字(56个) \n\n\n\n关键字 \n- \n- \n- \n \n \n\n\nabstract \ndo \nimport \nsuper \n \n\nas \ndynamic \nin \nswitch \n \n\nassert \nelse \ninterface \nsync \n \n\nenum \nimplements \nis \nthis \n \n\nasync \nexport \nlibrary \nthrow \n \n\nawait \nexternal \nmixin \ntrue \n \n\nbreak \nextends \nnew \ntry \n \n\ncase \nfactory \nnull \ntypedef \n \n\ncatch \nfalse \noperator \nvar \n \n\nclass \nfinal \npart \nvoid \n \n\nconst \nfinally \nrethrow \nwhile \n \n\ncontinue \nfor \nreturn \nwith \n \n\ncovariant \nget \nset \nyield \n \n\ndefault \nif \nstatic \ndeferred \n \n \n
\n变量与常量 \n\n变量声明与初始化 \n \n\n调用的变量name包含对String值为“张三” 的对象的引用,name推断变量的类型是String,但可以通过指定它来更改该类型,如果对象不限于单一类型(没有明确的类型),请使用Object或dynamic关键字。 \n \n \n var name = ‘Bob’; \n Object name = '张三' ;\n dynamic name = '李四' ;\n\n \n String name = 'Bob' ;\n
\n\n默认值 \n \n\n未初始化的变量的初始值为null(包括数字),因此数字、字符串都可以调用各种方法 \n \n \n int lineCount;\n \n assert (lineCount == null );\n print (lineCount); \n
\n\n\nfinal and const
\n\n \n final String outSideFinalName\n\n
\n\n被final或者const修饰的变量,变量类型可以省略,建议指定数据类型。 \n \n\nfinal name = \"Bob\" ;\nfinal String name1 = \"张三\" ;\n\nconst name2 = \"alex\" ;\nconst String name3 = \"李四\" ;\n
\n\n被 final 或 const 修饰的变量无法再去修改其值。 \n \nfinal String outSideFinalName = \"Alex\" ;\n\n\noutSideFinalName = \"Bill\" ;\n\nconst String outSideName = 'Bill' ;\n\n\n\n
\n\nflnal 或者 const 不能和 var 同时使用 \n \n\nconst var String outSideName = 'Bill' ;\n\n\nfinal var String name = 'Lili' ; \n
\n\n常量如果是类级别的,请使用 static const \n \n\nstatic const String name3 = 'Tom' ;\n\n\n\n\n\n
\n\nconst speed = 100 ; \nconst double distance = 2.5 * speed; \n\nfinal speed2 = 100 ; \nfinal double distance2 = 2.5 * speed2; \n\n
\n\nconst关键字不只是声明常数变量,您也可以使用它来创建常量值,以及声明创建常量值的构造函数,任何变量都可以有一个常量值。 \n \n\n\nvar varList = const []; \nfinal finalList = const []; \nconst constList = const []; \n\n\n\nvarList = [\"haha\" ];\n\n\n\n\n\n\n\n
\n\n在常量表达式中,该运算符的操作数必须为'bool'、'num'、'String'或'null', const常量必须用conat类型的值初始化。 \n \nconst String outSideName = 'Bill' ;\nfinal String outSideFinalName = 'Alex' ;\nconst String outSideName2 = 'Tom' ;\n\nconst aConstList = const ['1' , '2' , '3' ];\n\n\n\nconst validConstString = '$outSideName $outSideName2 $aConstList ' ;\n\n\n\nconst validConstString = '$outSideName $outSideName2 $outSideFinalName ' ;\n\nvar outSideVarName='Cathy' ;\n\n\nconst validConstString = '$outSideName $outSideName2 $outSideVarName ' ;\n\n\nconst String outSideConstName = 'Joy' ;\nconst validConstString = '$outSideName $outSideName2 $outSideConstName ' ;\n\n
\n \n \n数据类型 \n\n\nnum
\n\n\nnum 是数字类型的父类,有两个子类 int 和 double。
\n \n\nint 根据平台的不同,整数值不大于64位。在Dart VM上,值可以从-263到263 - 1,编译成JavaScript的Dart使用JavaScript代码,允许值从-253到253 - 1。
\n \n\ndouble 64位(双精度)浮点数,如IEEE 754标准所规定。
\n \n \nint a = 1 ;\nprint (a);\n\ndouble b = 1.12 ;\nprint (b);\n\n\nint one = int .parse('1' );\n\nprint (one + 2 );\n\n\nvar onePointOne = double .parse('1.1' );\n\nprint (onePointOne + 2 );\n\n\nString oneAsString = 1. toString();\n\n\n\nprint ('$oneAsString + 2' );\n\nprint ('$oneAsString 2' );\n\n\nString piAsString = 3.14159 .toStringAsFixed(2 );\n\nprint (piAsString);\n\nString aString = 1.12618 .toStringAsFixed(2 );\n\nprint (aString);\n
\n \n\nString
\n\n\nDart里面的String是一系列 UTF-16 代码单元。
\n \n\nDart里面的String是一系列 UTF-16 代码单元。
\n \n\n单引号或者双引号里面嵌套使用引号。
\n \n\n用 或{} 来计算字符串中变量的值,需要注意的是如果是表达式需要${表达式}
\n \n \nString singleString = 'abcdddd' ;\nString doubleString = \"abcsdfafd\" ;\n\nString sdString = '$singleString a \"bcsd\" ${singleString} ' ;\nString dsString = \"abc 'aaa' $sdString \" ;\nprint (sdString);\nprint (dsString);\n\n\nString singleString = 'aaa' ;\nString doubleString = \"bbb\" ;\n\nString sdString = '$singleString a \"bbb\" ${doubleString} ' ;\n\nprint (sdString);\n\n\nString dsString = \"${singleString.toUpperCase()} abc 'aaa' $doubleString .toUpperCase()\" ;\n\n可以看出 ”$doubleString.toUpperCase()“ 没有加“{}“,导致输出结果是”bbb.toUpperCase()“\nprint (dsString);\n
\n \n\nbool
\n\nDart 是强 bool 类型检查,只有bool 类型的值是true 才被认为是true。 \n只有两个对象具有bool类型:true和false,它们都是编译时常量。 \nDart的类型安全意味着您不能使用 if(nonbooleanValue) 或 assert(nonbooleanValue) 等代码, 相反Dart使用的是显式的检查值。 \nassert 是语言内置的断言函数,仅在检查模式下有效在开发过程中, 除非条件为真,否则会引发异常。(断言失败则程序立刻终止)。 \n \n \nvar fullName = '' ;\nassert (fullName.isEmpty);\n\n\nvar hitPoints = 0 ;\nassert (hitPoints <= 0 );\n\n\nvar unicorn;\nassert (unicorn == null );\n\n\nvar iMeantToDoThis = 0 / 0 ;\nassert (iMeantToDoThis.isNaN);\n\n
\n \n\nList集合
\n\n在Dart中,数组是List对象,因此大多数人只是将它们称为List。Dart list文字看起来像JavaScript数组文字 \n \n \nList list = [10 , 7 , 23 ];\n\nprint (list);\n\n\nvar fruits = new List ();\n\n\nfruits.add('apples' );\n\n\nfruits.addAll(['oranges' , 'bananas' ]);\n\nList subFruits = ['apples' , 'oranges' , 'banans' ];\n\nfruits.addAll(subFruits);\n\n\nprint (fruits);\n\n\nprint (fruits.length);\n\n\nprint (fruits.first);\n\n\nprint (fruits.last);\n\n\nprint (fruits[0 ]);\n\n\nprint (fruits.indexOf('apples' ));\n\n\nprint (fruits.removeAt(0 ));\n\n\n\nfruits.remove('apples' );\n\n\nfruits.removeLast();\n\n\nfruits.removeRange(start,end);\n\n\nfruits.removeWhere((item) => item.length >6 );\n\n\nfruits.clear();\n
\n\n\n注意事项:
\n\n可以直接打印list包括list的元素,list也是一个对象。但是java必须遍历才能打印list,直接打印是地址值。 \n和java一样list里面的元素必须保持类型一致,不一致就会报错。 \n和java一样list的角标从0开始。 \n如果集合里面有多个相同的元素“X”, 只会删除集合中第一个改元素 \n \n \n \n \n\nMap集合
\n\n一般来说,map是将键和值相关联的对象。键和值都可以是任何类型的对象。每个键只出现一次,但您可以多次使用相同的值。Dart支持map由map文字和map类型提供。 \n初始化Map方式一: 直接声明,用{}表示,里面写key和value,每组键值对中间用逗号隔开。 \n \n\n\n\nMap companys = {'Alibaba' : '阿里巴巴' , 'Tencent' : '腾讯' , 'baidu' : '百度' };\n\nprint (companys);\n\n
\n\nMap schoolsMap = new Map ();\nschoolsMap['first' ] = '清华' ;\nschoolsMap['second' ] = '北大' ;\nschoolsMap['third' ] = '复旦' ;\n\nprint (schoolsMap);\n\nvar fruits = new Map ();\nfruits[\"first\" ] = \"apple\" ;\nfruits[\"second\" ] = \"banana\" ;\nfruits[\"fifth\" ] = \"orange\" ;\n\nprint (fruits);\n
\n\n\nvar aMap = new Map <int , String >();\n\n\naMap[1 ] = '小米' ;\n\n\n\naMap[1 ] = 'alibaba' ;\n\n\naMap[2 ] = 'alibaba' ;\n\n\naMap[3 ] = '' ;\n\n\naMap[4 ] = null ;\n\nprint (aMap);\n\n\nassert (aMap.containsKey(1 ));\n\n\naMap.remove(1 ); \n\nprint (aMap); \n
\n\n注意事项\n\nmap的key类型不一致也不会报错。 \n添加元素的时候,会按照你添加元素的顺序逐个加入到map里面,哪怕你的key,比如分别是 1,2,4,看起来有间隔,事实上添加到map的时候是{1:value,2:value,4:value} 这种形式。 \nmap里面的key不能相同。但是value可以相同,value可以为空字符串或者为null。 \n \n \n \n \n \n运算符 \n\n\n\n描述 \n操作符 \n \n \n\n\n一元后置操作符 \nexpr++ expr-- () [] . ?. \n \n\n一元前置操作符 \nexpr !expr ~expr ++expr --expr \n \n\n乘除 \n* / % ~/ \n \n\n加减 \n+ - \n \n\n位移 \n<< >> \n \n\n按位与 \n& \n \n\n按位或 \n \n \n\n按位异或 \n^ \n \n\n逻辑与 \n&& \n \n\n逻辑或 \n \n \n\n关系和类型判断 \n>= > <= < as is is! \n \n\n等 \n== != \n \n\n如果为空 \n?? \n \n\n条件表达式 \nexpr1 ? expr2 : expr3 \n \n\n赋值 \n= *= /= ~/= %= += -= <<= >>= &= ^= = ??= \n \n\n级联 \n.. \n \n \n
\n流程控制语句(Control flow statements) \n\nif...else \nfor \nwhile do-whild \nbreak continue \nbreak continue \nassert(仅在checked模式有效) \n \n异常(Exceptions) \n\n\nthrow
\n\nthrow new FormatException('Expected at least 1 section' );\n
\n\n throw 'Out of llamas!' ;\n
\n\n因为抛出异常属于表达式,可以将throw语句放在=>语句中,或者其它可以出现表达式的地方 \n \ndistanceTo(Point other) =>\nthrow new UnimplementedError();\n
\n \n\ncatch
\n\n将可能出现异常的代码放置到try语句中,可以通过 on语句来指定需要捕获的异常类型,使用catch来处理异常。 \n \n try {\n breedMoreLlamas();\n} on OutOfLlamasException {\n \n buyMoreLlamas();\n} on Exception catch (e) {\n \n print ('Unknown exception: $e ' );\n} catch (e, s) {\n print ('Exception details:\\n $e ' );\n print ('Stack trace:\\n $s ' );\n}\n
\n \n\nrethrow
\n\nrethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用。 \n \nfinal foo = '' ;\n\nvoid misbehave() {\n try {\n foo = \"1\" ;\n } catch (e) {\n print ('2' );\n rethrow ;\n }\n}\n\nvoid main() {\n try {\n misbehave();\n } catch (e) {\n print ('3' );\n }\n}\n
\n \n\nfinally
\n\nDart的finally用来执行那些无论异常是否发生都执行的操作。 \n \n\nfinal foo = '' ;\n\nvoid misbehave() {\n try {\n foo = \"1\" ;\n } catch (e) {\n print ('2' );\n }\n}\n\nvoid main() {\n try {\n misbehave();\n } catch (e) {\n print ('3' );\n } finally {\n print ('4' ); \n }\n}\n\n
\n \n \n函数 Function \n\n bool isNoble(int atomicNumber) {\n return _nobleGases[atomicNumber] != null ;\n } \n
\n\n\nmain()函数
\n\n每个应用程序都必须有一个顶层main()函数,它可以作为应用程序的入口点。该main()函数返回void并具有List参数的可选参数。 \n \nvoid main() {\nquerySelector ('#sample_text_id' )\n ..text = 'Click me!' \n ..onClick.listen(reverseText);\n}\n\n
\n\n级联符号..允许您在同一个对象上进行一系列操作。除了函数调用之外,还可以访问同一对象上的字段。这通常会为您节省创建临时变量的步骤,并允许您编写更流畅的代码。 \n \nquerySelector ('#confirm' ) \n ..text = 'Confirm' \n ..classes.add('important' )\n ..onClick.listen((e) => window .alert('Confirmed!' ));\n\n
\n\nvar button = querySelector ('#confirm' );\nbutton.text = 'Confirm' ;\nbutton.classes.add('important' );\nbutton.onClick.listen((e) => window .alert('Confirmed!' ));\n\n
\n\nfinal addressBook = (AddressBookBuilder()\n..name = 'jenny' \n..email = 'jenny@example.com' \n..phone = (PhoneNumberBuilder()\n ..number = '415-555-0100' \n ..label = 'home' )\n .build())\n.build();\n\n
\n\n当返回值是void时不能构建级联。 例如,以下代码失败: \n \nvar sb = StringBuffer ();\nsb.write('foo' ) \n ..write('bar' ); \n\n
\n\n注意: 严格地说,级联的..符号不是操作符。它只是Dart语法的一部分。 \n \n \n\n可选参数
\n\n可选的命名参数, 定义函数时,使用{param1, param2, …},用于指定命名参数。例如: \n \n \nvoid enableFlags({bool bold, bool hidden}) {\n \n} \n\nenableFlags(bold: true , hidden: false );\n
\n\n可选的位置参数,用[]它们标记为可选的位置参数: \n \nString say(String from, String msg, [String device]) {\n var result = '$from says $msg ' ;\n if (device != null ) {\n result = '$result with a $device ' ;\n }\n return result;\n}\n
\n\n下面是一个不带可选参数调用这个函数的例子: \n \n say('Bob' , 'Howdy' ); \n
\n\n say('Bob' , 'Howdy' , 'smoke signal' ); \n
\n \n\n默认参数
\n\n函数可以使用=为命名参数和位置参数定义默认值。默认值必须是编译时常量。如果没有提供默认值,则默认值为null。 \n下面是为命名参数设置默认值的示例: \n \n\nvoid enableFlags2({bool bold = false , bool hidden = false }) {\n \n}\n\n\nenableFlags2(bold: true );\n
\n\n String say(String from, String msg,\n [String device = 'carrier pigeon' , String mood]) {\n var result = '$from says $msg ' ;\n if (device != null ) {\n result = '$result with a $device ' ;\n }\n if (mood != null ) {\n result = '$result (in a $mood mood)' ;\n }\n return result;\n}\n\n\nsay('Bob' , 'Howdy' ); \n
\n\n您还可以将list或map作为默认值传递。下面的示例定义一个函数doStuff(),该函数指定列表参数的默认list和gifts参数的默认map。 \n \n \nvoid doStuff(\n {List <int > list = const [1 , 2 , 3 ],\n Map <String , String > gifts = const {'first' : 'paper' , \n 'second' : 'cotton' , 'third' : 'leather' \n }}) {\n print ('list: $list ' );\n print ('gifts: $gifts ' );\n}\n
\n \n\n作为一个类对象的功能
\n\n您可以将一个函数作为参数传递给另一个函数。 \n \nvoid printElement(int element) {\n print (element);\n}\n\nvar list = [1 , 2 , 3 ];\n\n\nlist.forEach(printElement);\n
\n\n var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!' ;\nassert (loudify('hello' ) == '!!! HELLO !!!' );\n
\n \n\n匿名函数
\n\n大多数函数都能被命名为匿名函数,如 main() 或 printElement()。您还可以创建一个名为匿名函数的无名函数,有时也可以创建lambda或闭包。您可以为变量分配一个匿名函数,例如,您可以从集合中添加或删除它。 \n一个匿名函数看起来类似于一个命名函数 - 0或更多的参数,在括号之间用逗号和可选类型标注分隔。 \n下面的代码块包含函数的主体: \n \n([[Type ] param1[, …]]) { \n codeBlock; \n}; \n
\n\n下面的示例定义了一个具有无类型参数的匿名函数item,该函数被list中的每个item调用,输出一个字符串,该字符串包含指定索引处的值。 \n \n var list = ['apples' , 'bananas' , 'oranges' ];\nlist.forEach((item) {\n print ('${list.indexOf(item)} : $item ' );\n});\n
\n\n如果函数只包含一条语句,可以使用箭头符号=>来缩短它, 比如上面的例2可以简写成: \n \nlist.forEach((item) => print ('${list.indexOf(item)} : $item ' ));\n
\n \n\n返回值
\n\n所有函数都返回一个值,如果没有指定返回值,则语句return null,隐式地附加到函数体。 \n \nfoo() {}\nassert (foo() == null );\n
\n \n \n类(Classes) \n\n\n对象
\n\nDart 是一种面向对象的语言,并且支持基于mixin的继承方式。 \nDart 语言中所有的对象都是某一个类的实例,所有的类有同一个基类--Object。 \n基于mixin的继承方式具体是指:一个类可以继承自多个父类。 \n使用new语句来构造一个类,构造函数的名字可能是ClassName,也可以是ClassName.identifier, 例如: \n \n var jsonData = JSON.decode('{\"x\":1, \"y\":2}' );\n\n\nvar p1 = new Point(2 , 2 );\n\n\nvar p2 = new Point.fromJson(jsonData);\n\n
\n\n使用.(dot)来调用实例的变量或者方法。 \n \nvar p = new Point(2 , 2 );\n\n\np.y = 3 ;\n\n\nassert (p.y == 3 );\n\n\nnum distance = p.distanceTo(new Point(4 , 4 ));\n
\n\n使用?.来确认前操作数不为空, 常用来替代. , 避免左边操作数为null引发异常。 \n \n \np?.y = 4 ; \n
\n\n使用const替代new来创建编译时的常量构造函数。 \n \n var p = const ImmutablePoint(2 , 2 );\n
\n\n使用runtimeType方法,在运行中获取对象的类型。该方法将返回Type 类型的变量。 \n \n print ('The type of a is ${a.runtimeType} ' );\n
\n \n\n实例化变量(Instance variables)
\n\n在类定义中,所有没有初始化的变量都会被初始化为null。 \n \n class Point {\n num x; \n num y; \n num z = 0 ; \n}\n
\n\n类定义中所有的变量, Dart语言都会隐式的定义 setter 方法,针对非空的变量会额外增加 getter 方法。 \n \nclass Point {\nnum x;\nnum y;\n}\n\nmain() {\n var point = new Point();\n point.x = 4 ; \n assert (point.x == 4 ); \n assert (point.y == null ); \n}\n
\n \n\n构造函数(Constructors)
\n\n声明一个和类名相同的函数,来作为类的构造函数。 \n \n class Point {\n num x;\n num y;\n\n Point(num x, num y) {\n \n this .x = x;\n this .y = y;\n }\n}\n
\n\nthis关键字指向了当前类的实例, 上面的代码可以简化为: \n \n class Point {\n num x;\n num y;\n\n \n \n Point(this .x, this .y);\n}\n
\n \n\n构造函数不能继承(Constructors aren’t inherited)
\n\nDart 语言中,子类不会继承父类的命名构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。 \n \n \n\n命名的构造函数(Named constructors)
\n\n使用命名构造函数从另一类或现有的数据中快速实现构造函数。 \n \nclass Point {\n num x;\n num y;\n\n Point(this .x, this .y);\n\n \n Point.fromJson(Map json) {\n x = json['x' ];\n y = json['y' ];\n }\n}\n
\n\n构造函数不能被继承,父类中的命名构造函数不能被子类继承。如果想要子类也拥有一个父类一样名字的构造函数,必须在子类是实现这个构造函数。 \n \n \n\n调用父类的非默认构造函数
\n\n\n默认情况下,子类只能调用父类的无名,无参数的构造函数; 父类的无名构造函数会在子类的构造函数前调用; 如果initializer list 也同时定义了,则会先执行initializer list 中的内容,然后在执行父类的无名无参数构造函数,最后调用子类自己的无名无参数构造函数。即下面的顺序:
\n\ninitializer list(初始化列表) \nsuper class’s no-arg constructor(父类无参数构造函数) \nmain class’s no-arg constructor (主类无参数构造函数) \n \n \n\n如果父类不显示提供无名无参数构造函数的构造函数,在子类中必须手打调用父类的一个构造函数。这种情况下,调用父类的构造函数的代码放在子类构造函数名后,子类构造函数体前,中间使用:(colon) 分割。
\n \n \nclass Person {\n String firstName;\n\nPerson.fromJson(Map data) {\n print ('in Person' );\n}\n}\n\nclass Employee extends Person {\n\nsuper .fromJson(data)\nEmployee.fromJson(Map data) : super .fromJson(data) {\n print ('in Employee' );\n}\n}\n\nmain() {\nvar emp = new Employee.fromJson({});\n\n\n\n\nif (emp is Person) {\n \n emp.firstName = 'Bob' ;\n}\n(emp as Person).firstName = 'Bob' ;\n}\n\n
\n \n\n初始化列表
\n\n除了调用父类的构造函数,也可以通过初始化列表在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示: \n \nclass Point {\nnum x;\nnum y;\n\nPoint(this .x, this .y);\n\n\nPoint.fromJson(Map jsonMap)\n: x = jsonMap['x' ],\n y = jsonMap['y' ] {\n print ('In Point.fromJson(): ($x , $y )' );\n}\n}\n
\n注意:上述代码,初始化程序无法访问 this 关键字。
\n \n\n静态构造函数
\n\n如果你的类产生的对象永远不会改变,你可以让这些对象成为编译时常量。为此,需要定义一个 const 构造函数并确保所有的实例变量都是 final 的。 \n \nclass ImmutablePoint {\n final num x;\n final num y;\n const ImmutablePoint(this .x, this .y);\n static final ImmutablePoint origin = const ImmutablePoint(0 , 0 );\n}\n\n
\n \n\n重定向构造函数
\n\n有时候构造函数的目的只是重定向到该类的另一个构造函数。重定向构造函数没有函数体,使用冒号:分隔。 \n \nclass Point {\n num x;\n num y;\n\n \n Point(this .x, this .y) {\n print (\"Point($x , $y )\" );\n }\n\n \n Point.alongXAxis(num x) : this (x, 0 );\n}\n\nvoid main() {\n var p1 = new Point(1 , 2 );\n var p2 = new Point.alongXAxis(4 );\n} \n
\n \n\n常量构造函数
\n\n \n\n工厂构造函数
\n\n当实现一个使用 factory 关键词修饰的构造函数时,这个构造函数不必创建类的新实例。例如,工厂构造函数可能从缓存返回实例,或者它可能返回子类型的实例。 下面的示例演示一个工厂构造函数从缓存返回的对象: \n \nclass Logger {\nfinal String name;\nbool mute = false ;\n\n\nstatic final Map <String , Logger> _cache = <String , Logger>{};\n\nfactory Logger(String name) {\n if (_cache.containsKey(name)) {\n return _cache[name];\n } else {\n final logger = new Logger._internal(name);\n _cache[name] = logger;\n return logger;\n }\n }\n\n Logger._internal(this .name);\n\n void log(String msg) {\n if (!mute) {\n print (msg);\n }\n }\n \n}\n
\n注意:工厂构造函数不能用 this。
\n \n \n方法 \n\n\n\n实例方法
\n\n对象的实例方法可以访问实例变量和 this 。以下示例中的 distanceTo() 方法是实例方法的一个例子: \n \nimport 'dart:math' ;\n\nclass Point {\n num x;\n num y;\n Point(this .x, this .y);\n\n num distanceTo(Point other) {\n var dx = x - other.x;\n var dy = y - other.y;\n return sqrt(dx * dx + dy * dy);\n }\n}\n
\n \n\nsetters 和 Getters
\n\n是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的 getter 方法,合适的话可能还会有 setter 方法。你可以通过实现 getters 和 setters 来创建附加属性,也就是直接使用 get 和 set 关键词: \n \nclass Rectangle {\nnum left;\nnum top;\nnum width;\nnum height;\n\nRectangle(this .left, this .top, this .width, this .height);\n\n\nnum get right => left + width;\nset right(num value) => left = value - width;\nnum get bottom => top + height;\nset bottom(num value) => top = value - height;\n}\n\nmain() {\nvar rect = new Rectangle(3 , 4 , 20 , 15 );\nassert (rect.left == 3 );\nrect.right = 12 ;\nassert (rect.left == -8 );\n}\n
\n\n借助于 getter 和 setter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。 \n注: 不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。 \n \n \n\n抽象方法
\n\nInstance , getter 和 setter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。要创建一个抽象方法,使用分号(;)代替方法体: \n \nabstract class Doer {\n \n void doSomething(); \n}\n\nclass EffectiveDoer extends Doer {\n void doSomething() {\n \n }\n}\n\n
\n \n\n枚举类型
\n\n枚举类型,通常被称为 enumerations 或 enums ,是一种用来代表一个固定数量的常量的特殊类。 \n声明一个枚举类型需要使用关键字 enum : \n \nenum Color {\n red,\n green,\n blue\n}\n\n
\n\n在枚举中每个值都有一个 index getter 方法,它返回一个在枚举声明中从 0 开始的位置。例如,第一个值索引值为 0 ,第二个值索引值为 1 。 \n \nassert (Color.red.index == 0 );\nassert (Color.green.index == 1 );\nassert (Color.blue.index == 2 );\n
\n\n要得到枚举列表的所有值,可使用枚举的 values 常量。 \n \nList <Color> colors = Color.values;\nassert (colors[2 ] == Color.blue); \n\n
\n \n \n\n你可以在 switch 语句 中使用枚举。如果 e 在 switch (e) 是显式类型的枚举,那么如果你不处理所有的枚举值将会弹出警告: \n \n ***枚举类型有以下限制***\n * 你不能在子类中混合或实现一个枚举。\n * 你不能显式实例化一个枚举。\n enum Color {\n red,\n green,\n blue\n }\n \n Color aColor = Color.blue;\n switch (aColor) {\n case Color.red:\n print ('Red as roses!' );\n break ;\n \n case Color.green:\n print ('Green as grass!' );\n break ;\n \n default : \n print (aColor); \n }\n\n
\n\n\n为类添加特征:mixins
\n\nmixins 是一种多类层次结构的类的代码重用。 \n要使用 mixins ,在 with 关键字后面跟一个或多个 mixin 的名字。下面的例子显示了两个使用mixins的类: \n \n class Musician extends Performer with Musical {\n \n}\n\nclass Maestro extends Person with Musical , \n Aggressive , Demented {\n\n Maestro(String maestroName) {\n name = maestroName;\n canConduct = true ;\n }\n}\n
\n\n要实现 mixin ,就创建一个继承 Object 类的子类,不声明任何构造函数,不调用 super 。例如: \n \nabstract class Musical {\nbool canPlayPiano = false ;\nbool canCompose = false ;\nbool canConduct = false ;\n\n void entertainMe() {\n if (canPlayPiano) {\n print ('Playing piano' );\n } else if (canConduct) {\n print ('Waving hands' );\n } else {\n print ('Humming to self' );\n }\n }\n}\n
\n \n\n类的变量和方法
\n\n使用 static 关键字来实现类变量和类方法。 \n只有当静态变量被使用时才被初始化。 \n静态变量, 静态变量(类变量)对于类状态和常数是有用的: \n \nclass Color {\n static const red = const Color('red' ); \n final String name; \n const Color(this .name); \n}\n\nmain() {\n assert (Color.red.name == 'red' );\n}\n
\n\n静态方法, 静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如: \n \n import 'dart:math' ;\n\nclass Point {\n num x;\n num y;\n Point(this .x, this .y);\n\n static num distanceBetween(Point a, Point b) {\n var dx = a.x - b.x;\n var dy = a.y - b.y;\n return sqrt(dx * dx + dy * dy);\n }\n}\n\nmain() {\n var a = new Point(2 , 2 );\n var b = new Point(4 , 4 );\n var distance = Point.distanceBetween(a, b);\n assert (distance < 2.9 && distance > 2.8 );\n}\n\n
\n\n注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。 \n你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。 \n \n抽象类 \n\n使用 abstract 修饰符来定义一个抽象类,该类不能被实例化。抽象类在定义接口的时候非常有用,实际上抽象中也包含一些实现。如果你想让你的抽象类被实例化,请定义一个 工厂构造函数 。 \n抽象类通常包含 抽象方法。下面是声明一个含有抽象方法的抽象类的例子: \n \n \nabstract class AbstractContainer {\n\n\nvoid updateChildren(); \n}\n
\n\n下面的类不是抽象类,因此它可以被实例化,即使定义了一个抽象方法: \n \n class SpecializedContainer extends AbstractContainer {\n \n\n void updateChildren() {\n \n }\n\n\nvoid doSomething();\n}\n
\n类-隐式接口 \n\n\n\nclass Person {\n \n final _name;\n\n \n Person(this ._name);\n\n \n String greet(who) => 'Hello, $who . I am $_name .' ;\n}\n\n\nclass Imposter implements Person {\n \n final _name = \"\" ;\n\n String greet(who) => 'Hi $who . Do you know who I am?' ;\n}\n\ngreetBob(Person person) => person.greet('bob' );\n\nmain() {\n print (greetBob(new Person('kathy' )));\n print (greetBob(new Imposter()));\n}\n\n
\n\nclass Point implements Comparable , Location {\n\n}\n
\n \n \n类-扩展一个类 \n\n使用 extends 创建一个子类,同时 supper 将指向父类: \n \n class Television {\n void turnOn() {\n _illuminateDisplay();\n _activateIrSensor();\n }\n \n }\n\n class SmartTelevision extends Television {\n \n void turnOn() {\n super .turnOn();\n _bootNetworkInterface();\n _initializeMemory();\n _upgradeApps();\n }\n \n }\n
\n\n子类可以重载实例方法, getters 方法, setters 方法。下面是个关于重写 Object 类的方法 noSuchMethod() 的例子,当代码企图用不存在的方法或实例变量时,这个方法会被调用。 \n \n class A {\n \n void noSuchMethod(Invocation mirror) {\n print ('You tried to use a non-existent member:' + \n '${mirror.memberName} ' );\n }\n }\n\n
\n\n你可以使用 @override 注释来表明你重写了一个成员。 \n \n class A {\n @override \n void noSuchMethod(Invocation mirror) {\n \n }\n }\n\n
\n\n如果你用 noSuchMethod() 实现每一个可能的 getter 方法,setter 方法和类的方法,那么你可以使用 @proxy 标注来避免警告。 \n \n@proxy \n class A {\n void noSuchMethod(Invocation mirror) {\n \n }\n }\n
\n库和可见性 \n\n\nimport,part,library指令可以帮助创建一个模块化的,可共享的代码库。库不仅提供了API,还提供隐私单元:以下划线(_)开头的标识符只对内部库可见。每个Dartapp就是一个库,即使它不使用库指令。
\n \n\n库可以分布式使用包。见 Pub Package and Asset Manager 中有关pub(SDK中的一个包管理器)。
\n \n\n使用库
\n\n使用 import 来指定如何从一个库命名空间用于其他库的范围。 \n使用 import 来指定如何从一个库命名空间用于其他库的范围。 \n \n import 'dart:html' ;\n
\n\n唯一需要 import 的参数是一个指向库的 URI。对于内置库,URI中具有特殊dart:scheme。对于其他库,你可以使用文件系统路径或package:scheme。包 package:scheme specifies libraries ,如pub工具提供的软件包管理器库。例如: \n \nimport 'dart:io' ;\nimport 'package:mylib/mylib.dart' ;\nimport 'package:utils/utils.dart' ;\n
\n \n\n指定库前缀
\n\n如果导入两个库是有冲突的标识符,那么你可以指定一个或两个库的前缀。例如,如果 library1 和 library2 都有一个元素类,那么你可能有这样的代码: \n \nimport 'package:lib1/lib1.dart' ;\nimport 'package:lib2/lib2.dart' as lib2;\n\nvar element1 = new Element (); \nvar element2 =\nnew lib2.Element (); \n
\n \n\n导入部分库
\n\n如果想使用的库一部分,你可以选择性导入库。例如: \n \n \nimport 'package:lib1/lib1.dart' show foo;\n\n\nimport 'package:lib2/lib2.dart' hide foo;\n\n
\n \n\n延迟加载库
\n \n \n* 延迟(deferred)加载(也称为延迟(lazy)加载)允许应用程序按需加载库。下面是当你可能会使用延迟加载某些情况:\n\n * 为了减少应用程序的初始启动时间;\n * 执行A / B测试-尝试的算法的替代实施方式中;\n * 加载很少使用的功能,例如可选的屏幕和对话框。\n\n\n\n* 为了延迟加载一个库,你必须使用 deferred as 先导入它。\n\n``` dart\nimport 'package:deferred/hello.dart' deferred as hello;\n```\n\n* 当需要库时,使用该库的调用标识符调用 LoadLibrary()。\n``` dart\ngreet() async {\nawait hello.loadLibrary();\nhello.printGreeting();\n}\n```\n* 在前面的代码,在库加载好之前,await关键字都是暂停执行的。有关 async 和 await 见 asynchrony support 的更多信息。\n您可以在一个库调用 LoadLibrary() 多次都没有问题。该库也只被加载一次。\n\n* 当您使用延迟加载,请记住以下内容:\n\n * 延迟库的常量在其作为导入文件时不是常量。记住,这些常量不存在,直到迟库被加载完成。\n * 你不能在导入文件中使用延迟库常量的类型。相反,考虑将接口类型移到同时由延迟库和导入文件导入的库。\n * Dart隐含调用LoadLibrary()插入到定义deferred as namespace。在调用LoadLibrary()函数返回一个Future。\n
\n\n\n库的实现
\n\n用 library 来来命名库,用part来指定库中的其他文件。 注意:不必在应用程序中(具有顶级main()函数的文件)使用library,但这样做可以让你在多个文件中执行应用程序。 \n \n \n\n声明库
\n\n利用library identifier(库标识符)指定当前库的名称: \n \n\nlibrary ballgame;\n\n\nimport 'dart:html' ;\n\n\n
\n \n\n关联文件与库
\n\nlibrary ballgame;\n\nimport 'dart:html' ;\n\n\npart 'ball.dart' ;\npart 'util.dart' ;\n\n\n
\n\n第二个文件ball.dart,实现了球赛库的一部分: \n \npart of ballgame;\n\n\n
\n\n第三个文件,util.dart,实现了球赛库的其余部分: \n \npart of ballgame;\n\n\n
\n \n\n重新导出库(Re-exporting libraries)
\n*可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集。
\n\nlibrary french;\n\nhello() => print ('Bonjour!' );\ngoodbye() => print ('Au Revoir!' );\n\n\nlibrary togo;\n\nimport 'french.dart' ;\nexport 'french.dart' show hello;\n\n\nimport 'togo.dart' ;\n\nvoid main() {\n hello(); \n goodbye(); \n}\n\n
\n \n \n异步的支持 \n\n\nDart 添加了一些新的语言特性用于支持异步编程。最通常使用的特性是 async 方法和 await 表达式。Dart 库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成
\n \n\n当你需要使用 Future 来表示一个值时,你有两个选择。
\n\n使用 async 和 await \n使用 Future API \n \n \n\n同样的,当你需要从 Stream 获取值的时候,你有两个选择。
\n \n \n使用 async 和一个异步的 for 循环 (await for)\n使用 Stream API
\n\n使用 async 和 await 的代码是异步的,不过它看起来很像同步的代码。比如这里有一段使用 await 等待一个异步函数结果的代码: \n \nawait lookUpVersion()\n
\n\n要使用 await,代码必须用 await 标记 \n \n checkVersion() async {\n var version = await lookUpVersion();\n if (version == expectedVersion) {\n \n } else {\n \n }\n }\n
\n\n你可以使用 try, catch, 和 finally 来处理错误并精简使用了 await 的代码。 \n \n try {\n server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044 );\n } catch (e) {\n \n }\n
\n\n\n声明异步函数
\n\n一个异步函数是一个由 async 修饰符标记的函数。虽然一个异步函数可能在操作上比较耗时,但是它可以立即返回-在任何方法体执行之前。 \n \ncheckVersion() async {\n \n}\n\nlookUpVersion() async => ;\n
\n\n在函数中添加关键字 async 使得它返回一个 Future,比如,考虑一下这个同步函数,它将返回一个字符串。 \nString lookUpVersionSync() => '1.0.0'; \n如果你想更改它成为异步方法-因为在以后的实现中将会非常耗时-它的返回值是一个 Future 。 \nFuture lookUpVersion() async => '1.0.0'; \n请注意函数体不需要使用 Future API,如果必要的话 Dart 将会自己创建 Future 对象 \n \n \n\n使用带 future 的 await 表达式
\n\nawait expression\n
\n\n在异步方法中你可以使用 await 多次。比如,下列代码为了得到函数的结果一共等待了三次。 \n \nvar entrypoint = await findEntrypoint();\nvar exitCode = await runExecutable(entrypoint, args);\nawait flushThenExit(exitCode);\n
\n\n\n在 await 表达式中, 表达式 的值通常是一个 Future 对象;如果不是,那么这个值会自动转为 Future。这个 Future 对象表明了表达式应该返回一个对象。await 表达式 的值就是返回的一个对象。在对象可用之前,await 表达式将会一直处于暂停状态。
\n \n\n如果 await 没有起作用,请确认它是一个异步方法。比如,在你的 main() 函数里面使用await,main() 的函数体必须被 async 标记:
\n \n \nmain() async {\n checkVersion();\n print ('In main: version is ${await lookUpVersion()} ' );\n}\n\n
\n \n\n结合 streams 使用异步循环
\n\nawait for (variable declaration in expression) {\n \n}\n
\n\n\n表达式 的值必须有Stream 类型(流类型)。执行过程如下:
\n\n在 stream 发出一个值之前等待 \n执行 for 循环的主体,把变量设置为发出的值。 \n重复 1 和 2,直到 Stream 关闭 \n \n \n\n如果要停止监听 stream ,你可以使用 break 或者 return 语句,跳出循环并取消来自 stream 的订阅 。
\n \n\n如果一个异步 for 循环没有正常运行,请确认它是一个异步方法。 比如,在应用的 main() 方法中使用异步的 for 循环时,main() 的方法体必须被 async 标记。
\n \n \nmain() async {\n ...\n \n await for (var request in requestServer) {\n handleRequest(request);\n }\n\n ...\n}\n
\n \n \n更多关于异步编程的信息,请看 dart:async 库部分的介绍。你也可以看文章 Dart Language Asynchrony Support: Phase 1
\n",
+ "link": "\\zh-cn\\blog\\dart\\syntax.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/docker/docker-compose.html b/zh-cn/blog/docker/docker-compose.html
new file mode 100644
index 0000000..4e3288b
--- /dev/null
+++ b/zh-cn/blog/docker/docker-compose.html
@@ -0,0 +1,474 @@
+
+
+
+
+
+
+
+
+
+ docker-compose
+
+
+
+
+ docker和docker-compose 配置 mysql mssql mongodb redis nginx jenkins 环境
+磁盘挂载
+fdisk -l #查看磁盘列表
+mkfs.ext4 /dev/vdb #格式化磁盘
+mount /dev/vdb /data #挂载磁盘在/data
+echo '/dev/vdb /data ext4 defaults,nofail 0 1'>> /etc/fstab # 启动服务器自动挂载
+mount -a #校验自动挂载脚本
+df -h #查看磁盘挂载后信息
+
+docker
+安装 docker
+yum update #更新系统包
+yum install -y yum-utils device-mapper-persistent-data lvm2 #安装yum-utils
+yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #为yum源添加docker仓库位置
+yum install docker-ce #安装docker
+systemctl enable docker #设置开机自动启动
+systemctl start docker #启动docker
+systemctl stop docker #暂停docker
+mv /var/lib/docker /data/docker # 修改Docker镜像的存放位置
+ln -s /data/docker /var/lib/docker #建立软连接
+echo '{
+ "registry-mirrors": [
+ "https://dockerhub.azk8s.cn",
+ "https://hub-mirror.c.163.com",
+ "https://registry.docker-cn.com"
+ ]
+}
+'>> /etc/docker/daemon.json # 镜像下载代理
+
+拉取 Java 镜像
+docker pull java
+
+拉取SqlServer镜像
+docker pull microsoft/mssql-server-linux # 拉取SqlServer镜像
+docker run -p 1433:1433 --name mssql \ # run 运行容器 -p 将容器的1433端口映射到主机的1433端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /data/sqlserver:/var/opt/mssql \ # 挂载mssql文件夹到主机
+-e ACCEPT_EULA=Y \ # 同意协议
+-e MSSQL_SA_PASSWORD=mssql-MSSQL \ # 初始化sa密码
+-u root \ # 指定容器为root运行
+-d microsoft/mssql-server-linux # -d 后台运行
+
+拉取 MySql 镜像
+docker pull mysql #拉取 MySql
+docker run -p 3306:3306 --name mysql \ # run 运行容器 -p 将容器的3306端口映射到主机的3306端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/mysql/log:/var/log/mysql \ # 将日志文件夹挂载到主机
+-v /data/mysql/data:/var/lib/mysql \ # 将数据文件夹挂载到主机
+-v /data/mysql/mysql-files:/var/lib/mysql-files \ # 将数据文件夹挂载到主机
+-v /data/mysql/conf:/etc/mysql \ # 将配置文件夹挂在到主机
+-e MYSQL_ROOT_PASSWORD=xiujingmysql. \ # 初始化root用户的密码
+-d mysql # -d 后台运行
+docker exec -it mysql /bin/bash # 进入Docker容器内部的bash
+
+拉取 Mongodb 镜像
+docker pull mongo #拉取 mongodb
+docker run -p 27017:27017 --name mongo \ # run 运行容器 -p 将容器的27017端口映射到主机的27017端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/mongodb/db:/data/db \ # 将数据文件夹挂载到主机
+-v /data/mongodb/configdb:/data/configdb \ # 将数据库配置文件挂载到主机
+-v /data/mongodb/initdb:/docker-entrypoint-initdb.d # 通过/docker-entrypoint-initdb.d/将更复杂的用户设置显式留给用户 当容器首次启动时它会执行/docker-entrypoint-initdb.d 目录下的sh 和js脚本 。 以脚本字母顺序执行
+-e MONGO_INITDB_ROOT_USERNAME=admin \ # 设置admin数据库账户名称 如果使用了此项,则不需要 --auth 参数
+-e MONGO_INITDB_ROOT_PASSWORD=admin \ # 设置admin数据库账户密码 如果使用了此项,则不需要 --auth 参数
+-d mongo \ # -d 后台运行
+--auth # --auth 需要密码才能访问容器服务
+
+docker exec -it mongo mongo admin # 进入mongo
+db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'}]}); #创建一个名为 admin,密码为 123456 的用户。
+db.auth('admin', '123456') # 尝试使用上面创建的用户信息进行连接。
+
+拉取 Redis 镜像
+docker pull redis #拉取 redis
+docker run -p 6379:6379 --name redis \ # run 运行容器 -p 将容器的6379端口映射到主机的6379端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/redis:/data \ # 将数据文件夹挂载到主机
+-d redis \ # -d 后台运行
+redis-server --appendonly yes \ # 在容器执行redis-server启动命令,并打开redis持久化配置
+--requirepass "123456" # 设置密码123456
+
+拉取 Nginx 镜像
+docker pull nginx #拉取 nginx
+docker run -p 80:80 -p 443:443 --name nginx -d nginx # 运行容器
+docker container cp nginx:/etc/nginx /data/nginx/ #拷贝容器配置
+docker rm -f nginx # 删除容器
+
+nginx 配置文件
+
+user nginx;
+worker_processes 1;
+
+error_log /var/log/nginx/error_log.log warn;
+pid /var/run/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ gzip on; #开启gzip
+ gzip_disable "msie6"; #IE6不使用gzip
+ gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"
+ gzip_proxied any; #代理结果数据的压缩
+ gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值
+ gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果
+ gzip_http_version 1.1; #识别http协议的版本
+ gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩
+ gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩
+
+ include /etc/nginx/conf.d/*.conf;
+
+ server {
+ #nginx同时开启http和https
+ listen 80 default backlog=2048;
+ listen 443 ssl;
+ server_name ysf.djtlpay.com;
+
+ ssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;
+ ssl_certificate_key /ssl/2_ysf.djtlpay.com.key;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+ }
+}
+
+
+运行 nginx
+docker run -p 80:80 -p 443:443 --name nginx \ # run 运行容器 -p 将容器的80,443端口映射到主机的80,443端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/nginx/html:/usr/share/nginx/html \ # nginx 静态资源
+-v /data/nginx/logs:/var/log/nginx \ # 将日志文件夹挂载到主机
+-v /data/nginx/conf:/etc/nginx \ # 将配置文件夹挂在到主机
+-v /data/nginx/conf/ssl:/ssl \ # 将证书文件夹挂在到主机
+-d nginx #
+
+拉取Jenkins镜像:
+docker pull jenkins/jenkins:lts # 拉取 jenkins
+docker run -p 8080:8080 -p 50000:50000 --name jenkins \ # run 运行容器 -p 将容器的8080,50000端口映射到主机的8080,50000端口 --name 容器运行的名字
+--restart=always \ # 挂断自动重新启动
+-u root \ # 运行的用户为root
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/jenkins_home:/var/jenkins_home \ # 将jenkins_home文件夹挂在到主机
+-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \ #设置jenkins运行环境时区
+-d jenkins/jenkins:lts # -d 后台运行
+
+拉取MinIO镜像
+docker pull minio/minio # 拉取MinIO镜像
+docker run -p 9000:9000 --name minio \ # run 运行容器 -p 将容器的9000,9000端口映射到主机的9000,9000端口 --name 容器运行的名
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /data/minio/data:/data \ # 将data文件夹挂在到主机
+-v /data/minio/config:/root/.minio \ # 将配置文件夹挂在到主机
+-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \ # 设置MINIO_ACCESS_KEY的值
+-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \ # 设置MINIO_SECRET_KEY值
+-d minio/minio server /data # -d 后台运行 server /data 导出/data目录
+
+
+拉取Portainer镜像
+docker pull portainer/portainer # 拉取MinIO镜像
+docker run -p 8001:8000 -p 9001:9000 --name portainer \ # run 运行容器 -p 将容器的8000,9000端口映射到主机的8000,9000端口 --name 容器运行的名
+--restart=always \ # 挂断自动重新启动
+-v /etc/localtime:/etc/localtime \ # 将主机本地时间夹挂在到容器
+-v /var/run/docker.sock:/var/run/docker.sock \ # 将docker.sock文件夹挂在到主机
+-v /data/portainer/data:/data \ # 将配置文件夹挂在到主机
+-d portainer/portainer portainer # -d 后台运行
+
+Docker 开启远程API
+
+用vi编辑器修改docker.service文件
+
+vi /usr/lib/systemd/system/docker.service
+# 需要修改的部分:
+ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
+# 修改后的部分:
+ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock
+
+Docker 常用命令
+systemctl start docker #启动docker
+systemctl enable docker #将docker服务设为开机启动
+systemctl stop docker #停止容器
+systemctl restart docker #重启docker服务
+docker images # 列出镜像
+docker rmi --name # 删除镜像 -f 强制删除
+docker ps # 列出容器 -a 所有
+docker start --name # 启动容器
+docker stop --name # 停止容器
+docker restart --name # 重启docker容器
+docker rm --name # 删除容器 -f 强制删除
+docker stats -a # 查看所有容器情况
+docker system df # 查看Docker磁盘使用情况
+docker exec -it --name /bin/bash #进入Docker容器内部的bash
+docker cp 主机文件 容器名称:容器路径 #复制文件到docker容器中
+docker logs --name #查看docker镜像日志
+docker rm $(docker ps -a -q) # 删除所有容器 -f 强制删除
+docker rmi $(docker images -a -q) # 删除所有镜像 -f 强制删除
+docker rm -f `docker ps -a | grep -vE 'mysql|nginx|redis|jenkins' | awk '{print $1}'` # 删除mysql|nginx|redis|jenkins非容器 -f 强制删除
+docker rmi -f `docker images | grep none | awk '{print $3}'` # 删除镜像none镜像 -f 强制删除
+
+docker-compose
+安装 docker-compose
+# 下载Docker Compose
+curl -L https://get.daocloud.io/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
+# 修改该文件的权限为可执行
+chmod +x /usr/local/bin/docker-compose
+# 查看是否已经安装成功
+docker-compose --version
+
+使用Docker Compose的步骤
+
+使用Dockerfile定义应用程序环境,一般需要修改初始镜像行为时才需要使用;
+使用docker-compose.yml定义需要部署的应用程序服务,以便执行脚本一次性部署;
+使用docker-compose up命令将所有应用服务一次性部署起来。
+
+docker-compose.yml常用命令
+# 指定运行的镜像名称
+image: name:version
+# 配置容器名称
+container_name: name
+# 指定宿主机和容器的端口映射
+ports:
+ - 3306:3306
+# 将宿主机的文件或目录挂载到容器中
+volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mysql/log:/var/log/mysql
+ - /data/mysql/data:/var/lib/mysql
+ - /data/mysql/conf:/etc/mysql
+ - /data/mysql/mysql-files:/var/lib/mysql-files
+# 配置环境变量
+environment:
+ - MYSQL_ROOT_PASSWORD=xiujingmysql.
+# 连接其他容器的服务
+links:
+ - db:database #可以以database为域名访问服务名称为db的容器
+# 挂断自动重新启动
+restart: always
+# 指定容器执行命令
+command: redis-server --requirepass xiujingredis.
+
+Docker Compose常用命令
+# 构建、创建、启动相关容器
+docker-compose up -d # -d表示在后台运行
+# 停止所有相关容器
+docker-compose stop
+# 删除容器文件
+docker-compose rm -f # -f 强制删除
+# 重启容器
+docker-compose restart
+# 列出所有容器信息
+docker-compose ps
+# 查看容器日志
+docker-compose logs
+
+使用Docker Compose 部署应用
+编写docker-compose.yml文件
+version: '3'
+services:
+
+ nginx:
+
+ image: nginx
+
+ container_name: nginx
+
+ ports:
+ - 80 :80
+ - 443 :443
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+
+ sqlserver:
+
+ image: mcr.microsoft.com/mssql/server
+
+ container_name: sqlserver
+
+ ports:
+ - "1433"
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/sqlserver:/var/opt/mssql
+
+ restart: always
+ environment:
+ - TZ=Asia/Shanghai
+ - SA_PASSWORD=mssql-MSSQL
+ - ACCEPT_EULA=Y
+
+ user:
+ root
+
+ mysql:
+
+ image: mysql
+
+ container_name: mysql
+
+ ports:
+ - 3306 :3306
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mysql/log:/var/log/mysql
+ - /data/mysql/data:/var/lib/mysql
+ - /data/mysql/mysql-files:/var/lib/mysql-files
+ - /data/mysql/conf:/etc/mysql
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+ - MYSQL_ROOT_PASSWORD=xiujingmysql.
+
+ user:
+ root
+
+ redis:
+
+ image: redis
+
+ container_name: redis
+
+ ports:
+ - 6379 :6379
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/redis:/data
+ - /data/redis/redis.conf:/etc/redis.conf
+
+ restart: always
+
+ command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes
+
+ environment:
+ - TZ=Asia/Shanghai
+
+ mongo:
+
+ image: mongo
+
+ container_name: mongo
+
+ ports:
+ - 27017 :27017
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mongodb/db:/data/db
+ - /data/mongodb/configdb:/data/configdb
+ - /data/mongodb/initdb:/docker-entrypoint-initdb.d
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+ - AUTH=yes
+ - MONGO_INITDB_ROOT_USERNAME=admin
+ - MONGO_INITDB_ROOT_PASSWORD=admin
+
+ jenkins:
+
+ image: jenkins
+
+ container_name: jenkins
+
+ ports:
+ - 8080 :8080
+ - 50000 :50000
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/jenkins_home:/var/jenkins_home
+
+ restart: always
+
+ environment:
+ - TZ=Asia/Shanghai
+ - JAVA_OPTS=-Duser.timezone=Asia/Shanghai
+
+ user:
+ root
+
+ minio:
+
+ image: minio
+
+ container_name: minio
+
+ ports:
+ - 9000 :9000
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/minio/data:/data
+ - /data/minio/config:/root/.minio
+
+ restart: always
+
+ command: server /data
+
+ portainer :
+
+ image: portainer
+
+ container_name: portainer
+
+ ports:
+ - 8001 :8000
+ - 9001 :9000
+
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /var/run/docker.sock:/var/run/docker.sock
+ - /data/portainer/data:/data
+
+ restart: always
+
+运行Docker Compose命令启动所有服务
+docker-compose up -d
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/docker/docker-compose.json b/zh-cn/blog/docker/docker-compose.json
new file mode 100644
index 0000000..e68c23f
--- /dev/null
+++ b/zh-cn/blog/docker/docker-compose.json
@@ -0,0 +1,6 @@
+{
+ "filename": "docker-compose.md",
+ "__html": "docker和docker-compose 配置 mysql mssql mongodb redis nginx jenkins 环境 \n磁盘挂载 \nfdisk -l #查看磁盘列表\nmkfs.ext4 /dev/vdb #格式化磁盘\nmount /dev/vdb /data #挂载磁盘在/data\necho '/dev/vdb /data ext4 defaults,nofail 0 1'>> /etc/fstab # 启动服务器自动挂载\nmount -a #校验自动挂载脚本\ndf -h #查看磁盘挂载后信息\n
\ndocker \n安装 docker \nyum update #更新系统包\nyum install -y yum-utils device-mapper-persistent-data lvm2 #安装yum-utils\nyum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #为yum源添加docker仓库位置\nyum install docker-ce #安装docker\nsystemctl enable docker #设置开机自动启动\nsystemctl start docker #启动docker\nsystemctl stop docker #暂停docker\nmv /var/lib/docker /data/docker # 修改Docker镜像的存放位置\nln -s /data/docker /var/lib/docker #建立软连接\necho '{\n \"registry-mirrors\": [\n \"https://dockerhub.azk8s.cn\",\n \"https://hub-mirror.c.163.com\",\n \"https://registry.docker-cn.com\"\n ]\n}\n'>> /etc/docker/daemon.json # 镜像下载代理\n
\n拉取 Java 镜像 \ndocker pull java\n
\n拉取SqlServer镜像 \ndocker pull microsoft/mssql-server-linux # 拉取SqlServer镜像\ndocker run -p 1433:1433 --name mssql \\ # run 运行容器 -p 将容器的1433端口映射到主机的1433端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /data/sqlserver:/var/opt/mssql \\ # 挂载mssql文件夹到主机\n-e ACCEPT_EULA=Y \\ # 同意协议\n-e MSSQL_SA_PASSWORD=mssql-MSSQL \\ # 初始化sa密码\n-u root \\ # 指定容器为root运行\n-d microsoft/mssql-server-linux # -d 后台运行\n
\n拉取 MySql 镜像 \ndocker pull mysql #拉取 MySql\ndocker run -p 3306:3306 --name mysql \\ # run 运行容器 -p 将容器的3306端口映射到主机的3306端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/mysql/log:/var/log/mysql \\ # 将日志文件夹挂载到主机\n-v /data/mysql/data:/var/lib/mysql \\ # 将数据文件夹挂载到主机\n-v /data/mysql/mysql-files:/var/lib/mysql-files \\ # 将数据文件夹挂载到主机\n-v /data/mysql/conf:/etc/mysql \\ # 将配置文件夹挂在到主机\n-e MYSQL_ROOT_PASSWORD=xiujingmysql. \\ # 初始化root用户的密码\n-d mysql # -d 后台运行\ndocker exec -it mysql /bin/bash # 进入Docker容器内部的bash\n
\n拉取 Mongodb 镜像 \ndocker pull mongo #拉取 mongodb\ndocker run -p 27017:27017 --name mongo \\ # run 运行容器 -p 将容器的27017端口映射到主机的27017端口 --name 容器运行的名字 \n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/mongodb/db:/data/db \\ # 将数据文件夹挂载到主机\n-v /data/mongodb/configdb:/data/configdb \\ # 将数据库配置文件挂载到主机\n-v /data/mongodb/initdb:/docker-entrypoint-initdb.d # 通过/docker-entrypoint-initdb.d/将更复杂的用户设置显式留给用户 当容器首次启动时它会执行/docker-entrypoint-initdb.d 目录下的sh 和js脚本 。 以脚本字母顺序执行\n-e MONGO_INITDB_ROOT_USERNAME=admin \\ # 设置admin数据库账户名称 如果使用了此项,则不需要 --auth 参数\n-e MONGO_INITDB_ROOT_PASSWORD=admin \\ # 设置admin数据库账户密码 如果使用了此项,则不需要 --auth 参数\n-d mongo \\ # -d 后台运行\n--auth # --auth 需要密码才能访问容器服务\n\ndocker exec -it mongo mongo admin # 进入mongo\ndb.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'}]}); #创建一个名为 admin,密码为 123456 的用户。\ndb.auth('admin', '123456') # 尝试使用上面创建的用户信息进行连接。\n
\n拉取 Redis 镜像 \ndocker pull redis #拉取 redis\ndocker run -p 6379:6379 --name redis \\ # run 运行容器 -p 将容器的6379端口映射到主机的6379端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/redis:/data \\ # 将数据文件夹挂载到主机\n-d redis \\ # -d 后台运行\nredis-server --appendonly yes \\ # 在容器执行redis-server启动命令,并打开redis持久化配置\n--requirepass \"123456\" # 设置密码123456\n
\n拉取 Nginx 镜像 \ndocker pull nginx #拉取 nginx\ndocker run -p 80:80 -p 443:443 --name nginx -d nginx # 运行容器\ndocker container cp nginx:/etc/nginx /data/nginx/ #拷贝容器配置\ndocker rm -f nginx # 删除容器\n
\nnginx 配置文件
\n\nuser nginx;\nworker_processes 1;\n\nerror_log /var/log/nginx/error_log.log warn;\npid /var/run/nginx.pid;\n\n\nevents {\n worker_connections 1024;\n}\n\n\nhttp {\n include /etc/nginx/mime.types;\n default_type application/octet-stream;\n\n log_format main '$remote_addr - $remote_user [$time_local] "$request" '\n '$status $body_bytes_sent "$http_referer" '\n '"$http_user_agent" "$http_x_forwarded_for"';\n\n access_log /var/log/nginx/access.log main;\n\n sendfile on;\n #tcp_nopush on;\n\n keepalive_timeout 65;\n\n #gzip on;\n\n gzip on; #开启gzip\n gzip_disable "msie6"; #IE6不使用gzip\n gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"\n gzip_proxied any; #代理结果数据的压缩\n gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值\n gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果\n gzip_http_version 1.1; #识别http协议的版本\n gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩\n gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩\n\n include /etc/nginx/conf.d/*.conf;\n\n server {\n #nginx同时开启http和https\n \tlisten 80 default backlog=2048;\n \tlisten 443 ssl;\n \tserver_name ysf.djtlpay.com;\n \t\n \tssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;\n \tssl_certificate_key /ssl/2_ysf.djtlpay.com.key;\n\t\n location / {\n root /usr/share/nginx/html;\n index index.html index.htm;\n }\n }\t\t\n}\n\n
\n运行 nginx
\ndocker run -p 80:80 -p 443:443 --name nginx \\ # run 运行容器 -p 将容器的80,443端口映射到主机的80,443端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/nginx/html:/usr/share/nginx/html \\ # nginx 静态资源\n-v /data/nginx/logs:/var/log/nginx \\ # 将日志文件夹挂载到主机\n-v /data/nginx/conf:/etc/nginx \\ # 将配置文件夹挂在到主机\n-v /data/nginx/conf/ssl:/ssl \\ # 将证书文件夹挂在到主机\n-d nginx #\n
\n拉取Jenkins镜像: \ndocker pull jenkins/jenkins:lts # 拉取 jenkins\ndocker run -p 8080:8080 -p 50000:50000 --name jenkins \\ # run 运行容器 -p 将容器的8080,50000端口映射到主机的8080,50000端口 --name 容器运行的名字\n--restart=always \\ # 挂断自动重新启动\n-u root \\ # 运行的用户为root\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/jenkins_home:/var/jenkins_home \\ # 将jenkins_home文件夹挂在到主机\n-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \\ #设置jenkins运行环境时区\n-d jenkins/jenkins:lts # -d 后台运行\n
\n拉取MinIO镜像 \ndocker pull minio/minio # 拉取MinIO镜像\ndocker run -p 9000:9000 --name minio \\ # run 运行容器 -p 将容器的9000,9000端口映射到主机的9000,9000端口 --name 容器运行的名\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /data/minio/data:/data \\ # 将data文件夹挂在到主机\n-v /data/minio/config:/root/.minio \\ # 将配置文件夹挂在到主机\n-e \"MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE\" \\ # 设置MINIO_ACCESS_KEY的值\n-e \"MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\" \\ # 设置MINIO_SECRET_KEY值\n-d minio/minio server /data # -d 后台运行 server /data 导出/data目录\n\n
\n拉取Portainer镜像 \ndocker pull portainer/portainer # 拉取MinIO镜像\ndocker run -p 8001:8000 -p 9001:9000 --name portainer \\ # run 运行容器 -p 将容器的8000,9000端口映射到主机的8000,9000端口 --name 容器运行的名\n--restart=always \\ # 挂断自动重新启动\n-v /etc/localtime:/etc/localtime \\ # 将主机本地时间夹挂在到容器\n-v /var/run/docker.sock:/var/run/docker.sock \\ # 将docker.sock文件夹挂在到主机\n-v /data/portainer/data:/data \\ # 将配置文件夹挂在到主机\n-d portainer/portainer portainer # -d 后台运行\n
\nDocker 开启远程API \n\n用vi编辑器修改docker.service文件 \n \nvi /usr/lib/systemd/system/docker.service\n# 需要修改的部分: \nExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock\n# 修改后的部分: \nExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock\n
\nDocker 常用命令 \nsystemctl start docker #启动docker\nsystemctl enable docker #将docker服务设为开机启动\nsystemctl stop docker #停止容器\nsystemctl restart docker #重启docker服务\ndocker images # 列出镜像\ndocker rmi --name # 删除镜像 -f 强制删除\ndocker ps # 列出容器 -a 所有\ndocker start --name # 启动容器\ndocker stop --name # 停止容器\ndocker restart --name # 重启docker容器\ndocker rm --name # 删除容器 -f 强制删除\ndocker stats -a # 查看所有容器情况\ndocker system df # 查看Docker磁盘使用情况\ndocker exec -it --name /bin/bash #进入Docker容器内部的bash\ndocker cp 主机文件 容器名称:容器路径 #复制文件到docker容器中\ndocker logs --name #查看docker镜像日志\ndocker rm $(docker ps -a -q) # 删除所有容器 -f 强制删除\ndocker rmi $(docker images -a -q) # 删除所有镜像 -f 强制删除\ndocker rm -f `docker ps -a | grep -vE 'mysql|nginx|redis|jenkins' | awk '{print $1}'` # 删除mysql|nginx|redis|jenkins非容器 -f 强制删除\ndocker rmi -f `docker images | grep none | awk '{print $3}'` # 删除镜像none镜像 -f 强制删除\n
\ndocker-compose \n安装 docker-compose \n# 下载Docker Compose \ncurl -L https://get.daocloud.io/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose\n# 修改该文件的权限为可执行 \nchmod +x /usr/local/bin/docker-compose\n# 查看是否已经安装成功 \ndocker-compose --version\n
\n使用Docker Compose的步骤 \n\n使用Dockerfile定义应用程序环境,一般需要修改初始镜像行为时才需要使用; \n使用docker-compose.yml定义需要部署的应用程序服务,以便执行脚本一次性部署; \n使用docker-compose up命令将所有应用服务一次性部署起来。 \n \ndocker-compose.yml常用命令 \n# 指定运行的镜像名称 \nimage: name:version\n# 配置容器名称 \ncontainer_name: name\n# 指定宿主机和容器的端口映射 \nports:\n - 3306:3306\n# 将宿主机的文件或目录挂载到容器中 \nvolumes:\n - /etc/localtime:/etc/localtime\n - /data/mysql/log:/var/log/mysql\n - /data/mysql/data:/var/lib/mysql\n - /data/mysql/conf:/etc/mysql\n - /data/mysql/mysql-files:/var/lib/mysql-files\n# 配置环境变量 \nenvironment:\n - MYSQL_ROOT_PASSWORD=xiujingmysql.\n# 连接其他容器的服务 \nlinks:\n - db:database #可以以database为域名访问服务名称为db的容器\n# 挂断自动重新启动 \nrestart: always\n# 指定容器执行命令 \ncommand: redis-server --requirepass xiujingredis.\n
\nDocker Compose常用命令 \n# 构建、创建、启动相关容器 \ndocker-compose up -d # -d表示在后台运行\n# 停止所有相关容器 \ndocker-compose stop\n# 删除容器文件 \ndocker-compose rm -f # -f 强制删除\n# 重启容器 \ndocker-compose restart\n# 列出所有容器信息 \ndocker-compose ps\n# 查看容器日志 \ndocker-compose logs\n
\n使用Docker Compose 部署应用 \n编写docker-compose.yml文件
\nversion: '3' \nservices: \n \n nginx: \n \n image: nginx \n \n container_name: nginx \n \n ports: \n - 80 :80 \n - 443 :443 \n \n volumes: \n - /etc/localtime:/etc/localtime \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n \n sqlserver: \n \n image: mcr.microsoft.com/mssql/server \n \n container_name: sqlserver \n \n ports: \n - \"1433\" \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/sqlserver:/var/opt/mssql \n \n restart: always \n environment: \n - TZ=Asia/Shanghai \n - SA_PASSWORD=mssql-MSSQL \n - ACCEPT_EULA=Y \n \n user: \n root \n \n mysql: \n \n image: mysql \n \n container_name: mysql \n \n ports: \n - 3306 :3306 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/mysql/log:/var/log/mysql \n - /data/mysql/data:/var/lib/mysql \n - /data/mysql/mysql-files:/var/lib/mysql-files \n - /data/mysql/conf:/etc/mysql \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n - MYSQL_ROOT_PASSWORD=xiujingmysql. \n \n user: \n root \n \n redis: \n \n image: redis \n \n container_name: redis \n \n ports: \n - 6379 :6379 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/redis:/data \n - /data/redis/redis.conf:/etc/redis.conf \n \n restart: always \n \n command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes \n \n environment: \n - TZ=Asia/Shanghai \n \n mongo: \n \n image: mongo \n \n container_name: mongo \n \n ports: \n - 27017 :27017 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/mongodb/db:/data/db \n - /data/mongodb/configdb:/data/configdb \n - /data/mongodb/initdb:/docker-entrypoint-initdb.d \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n - AUTH=yes \n - MONGO_INITDB_ROOT_USERNAME=admin \n - MONGO_INITDB_ROOT_PASSWORD=admin \n \n jenkins: \n \n image: jenkins \n \n container_name: jenkins \n \n ports: \n - 8080 :8080 \n - 50000 :50000 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/jenkins_home:/var/jenkins_home \n \n restart: always \n \n environment: \n - TZ=Asia/Shanghai \n - JAVA_OPTS=-Duser.timezone=Asia/Shanghai \n \n user: \n root \n \n minio: \n \n image: minio \n \n container_name: minio \n \n ports: \n - 9000 :9000 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /data/minio/data:/data \n - /data/minio/config:/root/.minio \n \n restart: always \n \n command: server /data \n \n portainer : \n \n image: portainer \n \n container_name: portainer \n \n ports: \n - 8001 :8000 \n - 9001 :9000 \n \n volumes: \n - /etc/localtime:/etc/localtime \n - /var/run/docker.sock:/var/run/docker.sock \n - /data/portainer/data:/data \n \n restart: always \n
\n运行Docker Compose命令启动所有服务
\ndocker-compose up -d\n
\n",
+ "link": "\\zh-cn\\blog\\docker\\docker-compose.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/docker/docker-jenkins.html b/zh-cn/blog/docker/docker-jenkins.html
new file mode 100644
index 0000000..a24d649
--- /dev/null
+++ b/zh-cn/blog/docker/docker-jenkins.html
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+ docker-jenkins
+
+
+
+
+ Docker部署Jenkins
+Jenkins简介
+Jenkins是开源CI&CD软件领导者,提供超过1000个插件来支持构建、部署、自动化,满足任何项目的需要。我们可以用Jenkins来构建和部署我们的项目,比如说从我们的代码仓库获取代码,然后将我们的代码打包成可执行的文件,之后通过远程的ssh工具执行脚本来运行我们的项目。
+Jenkins的安装及配置
+Docker环境下的安装
+
+docker pull jenkins/jenkins:lts
+
+
+docker run -p 8080:8080 -p 50000:50000 --name jenkins \
+-u root \
+-v /etc/localtime:/etc/localtime \
+-v /data/jenkins_home:/var/jenkins_home \
+-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \
+-d jenkins/jenkins:lts
+
+
+echo 'Asia/Shanghai' >/etc/timezone
+
+Jenkins的配置
+
+
+
+使用管理员密码进行登录,可以使用以下命令从容器启动日志中获取管理密码:
+
+docker logs jenkins
+
+
+
+选择安装插件方式,这里我们直接安装推荐的插件:
+
+
+
+
+
+
+
+
+
+点击系统管理->插件管理,进行一些自定义的插件安装:
+
+
+
+
+
+
+
+在系统管理->系统配置中添加全局ssh的配置,这样Jenkins使用ssh就可以执行远程的linux脚本了:
+
+
+角色权限管理
+我们可以使用Jenkins的角色管理插件来管理Jenkins的用户,比如我们可以给管理员赋予所有权限,运维人员赋予执行任务的相关权限,其他人员只赋予查看权限。
+
+在系统管理->全局安全配置中启用基于角色的权限管理:
+
+
+
+进入系统管理->Manage and Assign Roles界面:
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/docker/docker-jenkins.json b/zh-cn/blog/docker/docker-jenkins.json
new file mode 100644
index 0000000..4536bc3
--- /dev/null
+++ b/zh-cn/blog/docker/docker-jenkins.json
@@ -0,0 +1,6 @@
+{
+ "filename": "docker-jenkins.md",
+ "__html": "Docker部署Jenkins \nJenkins简介 \nJenkins是开源CI&CD软件领导者,提供超过1000个插件来支持构建、部署、自动化,满足任何项目的需要。我们可以用Jenkins来构建和部署我们的项目,比如说从我们的代码仓库获取代码,然后将我们的代码打包成可执行的文件,之后通过远程的ssh工具执行脚本来运行我们的项目。
\nJenkins的安装及配置 \nDocker环境下的安装 \n\ndocker pull jenkins/jenkins:lts\n
\n\ndocker run -p 8080:8080 -p 50000:50000 --name jenkins \\\n-u root \\\n-v /etc/localtime:/etc/localtime \\\n-v /data/jenkins_home:/var/jenkins_home \\\n-e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \\\n-d jenkins/jenkins:lts\n
\n\necho 'Asia/Shanghai' >/etc/timezone \n \nJenkins的配置 \n\n
\n\n使用管理员密码进行登录,可以使用以下命令从容器启动日志中获取管理密码: \n \ndocker logs jenkins\n
\n
\n\n选择安装插件方式,这里我们直接安装推荐的插件: \n \n
\n\n
\n\n
\n\n进行实例配置,配置Jenkins的URL: \n \n
\n\n点击系统管理->插件管理,进行一些自定义的插件安装: \n \n
\n\n
\n\n
\n\n在系统管理->系统配置中添加全局ssh的配置,这样Jenkins使用ssh就可以执行远程的linux脚本了: \n \n
\n角色权限管理 \n我们可以使用Jenkins的角色管理插件来管理Jenkins的用户,比如我们可以给管理员赋予所有权限,运维人员赋予执行任务的相关权限,其他人员只赋予查看权限。
\n\n在系统管理->全局安全配置中启用基于角色的权限管理: \n \n
\n\n进入系统管理->Manage and Assign Roles界面: \n \n
\n\n
\n\n
\n",
+ "link": "\\zh-cn\\blog\\docker\\docker-jenkins.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/docker/docker.html b/zh-cn/blog/docker/docker.html
index 092d2ce..b612594 100644
--- a/zh-cn/blog/docker/docker.html
+++ b/zh-cn/blog/docker/docker.html
@@ -375,6 +375,81 @@ 其他有用的命令
docker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
$ docker container cp [containID]:[/path/to/file] .
+Docker命令详解(run篇)
+命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
+Usage: Run a command in a new container
+中文意思为:通过run命令创建一个新的容器(container)
+常用选项说明
+-d, --detach=false, 指定容器运行于前台还是后台,默认为false
+-i, --interactive=false, 打开STDIN,用于控制台交互
+-t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false
+-u, --user="", 指定容器的用户
+-a, --attach=[], 登录容器(必须是以docker run -d启动的容器)
+-w, --workdir="", 指定容器的工作目录
+-c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用
+-e, --env=[], 指定环境变量,容器中可以使用该环境变量
+-m, --memory="", 指定容器的内存上限
+-P, --publish-all=false, 指定容器暴露的端口
+-p, --publish=[], 指定容器暴露的端口
+-h, --hostname="", 指定容器的主机名
+-v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录
+--volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录
+--cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
+--cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
+--cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
+--cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
+--device=[], 添加主机设备给容器,相当于设备直通
+--dns=[], 指定容器的dns服务器
+--dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
+--entrypoint="", 覆盖image的入口点
+--env-file=[], 指定环境变量文件,文件格式为每行一个环境变量
+--expose=[], 指定容器暴露的端口,即修改镜像的暴露端口
+--link=[], 指定容器间的关联,使用其他容器的IP、env等信息
+--lxc-conf=[], 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
+--name="", 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
+--net="bridge", 容器网络设置:
+bridge 使用docker daemon指定的网桥
+host //容器使用主机的网络
+container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
+none 容器使用自己的网络(类似--net=bridge),但是不进行配置
+--privileged=false, 指定容器是否为特权容器,特权容器拥有所有的capabilities
+--restart="no", 指定容器停止后的重启策略:
+no:容器退出时不重启
+on-failure:容器故障退出(返回值非零)时重启
+always:容器退出时总是重启
+--rm=false, 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
+--sig-proxy=true, 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
+示例
+
+运行一个在后台执行的容器,同时,还能用控制台管理:
+
+docker run -i -t -d ubuntu:latest
+
+
+运行一个带命令在后台不断执行的容器,不直接展示容器内部信息:
+
+docker run -d ubuntu:latest ping www.docker.com
+
+
+运行一个在后台不断执行的容器,同时带有命令,程序被终止后还能重启继续跑,还能用控制台管理,
+
+docker run -d --restart=always ubuntu:latest ping www.docker.com
+
+
+docker run -d --name=ubuntu_server ubuntu:latest
+
+
+容器暴露80端口,并指定宿主机80端口与其通信(: 之前是宿主机端口,之后是容器需暴露的端口),
+
+docker run -d --name=ubuntu_server -p 80:80 ubuntu:latest
+
+
+指定容器内目录与宿主机目录共享(: 之前是宿主机文件夹,之后是容器需共享的文件夹),
+
+docker run -d --name=ubuntu_server -v /etc/www:/var/www ubuntu:latest
+
diff --git a/zh-cn/blog/docker/docker.json b/zh-cn/blog/docker/docker.json
index aeb94a3..68a96cf 100644
--- a/zh-cn/blog/docker/docker.json
+++ b/zh-cn/blog/docker/docker.json
@@ -1,6 +1,6 @@
{
"filename": "docker.md",
- "__html": "Docker \nDocker简介 \n\nDocker是开源应用容器引擎,轻量级容器技术。 \n基于Go语言,并遵循Apache2.0协议开源 \nDocker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux系统上,也可以实现虚拟化 \n容器完全使用沙箱技术,相互之间不会有任何接口 \n类似于虚拟机技术(vmware、vitural),但docker直接运行在操作系统(Linux)上,而不是运行在虚拟机中,速度快,性能开销极低 \n \n白话文,简介就是:
\n\nDocker支持将软件编译成一个镜像,然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像。\n运行中的这个镜像称为容器,容器启动是非常快速的。类似windows里面的ghost操 作系统,安装好后什么都有了。
\n \n什么是Docker \nDocker 是一个开源的应用容器引擎,基于Go语言,诞生于2013年初,最初发起者是dotCloud公司,开发者可以打包应用到一个轻量级、可移植的容器中,然后发布到主流Linux系统上运行。\n为什么用Docker
\n\n持续交付和部署:使用Docker可以通过定制应用镜像来实现持续集成,持续交付,部署。开发人员构建后的镜像,结合持续集成系统进行集成测试,而运维人员则可以在生产环境中快速部署该镜像,也可以结合持续部署系统进行自动部署。 \n更高效的资源利用:Docker是基于内核级的虚拟化,可以实现更高效的性能,同时对资源的额外需求很低,相比传统虚拟机方式,相同配置的主机能够运行更多的应用。\n更轻松的迁移和扩展:Docker容器几乎可以在任何平台上运行,同时支持主流的操作系统发行版本。 \n更快速的启动时间:传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到妙级,甚至毫秒级的启动时间,大大的节约了开发,测试,部署的时间。\nDocker与传统虚拟机差异 \n \n传统虚拟化方式
\n \nDocker虚拟化方式
\n
\n传统虚拟化是在硬件层面实现虚拟化,需要有额外的虚拟机管理应用和虚拟机操作系统层,而Docker容器是在操作系统层面实现虚拟化,直接复用本地主机操作系统,更加轻量级。
\n核心概念 \n\nDocker镜像:类似于虚拟机里的镜像,是一个只读的模板,一个独立的文件系统,使用镜像可以创建容器,可以理解为镜像是容器的基石。 \nDocker容器:是由Docker镜像创建的运行实例,类似于轻量级的沙箱,每个容器之间都是相互隔离的。支持的操作有启动,停止,删除等。 \ndocker客户端(Client):客户端通过命令行或其他工具使用Docker API(https://docs.docker.com/reference/api/docker_remote_api )与Docker的守护进程进行通信 \ndocker主机(Host):一个物理或虚拟的机器用来执行Docker守护进程和容器 \ndocker仓库(Registry):Docker仓库用来存储镜像,可以理解为代码控制中的代码仓库,Docker Hub(https://hub.docker.com ) 提供了庞大的镜像集合供使用 \n \nDocker安装及启停 \n\n查看centos版本 \n \nDocker 要求 CentOS 系统的内核版本高于 3 .10 \n通过命令:uname -r\n查看当前centos版本,如版本不符,需升级系统版本\n
\n\n升级软件包及内核(可选) \n \nyum update\n
\n\n安装docker \n \nyum install docker\n
\n\n启动docker \n \nsystemctl start docker\n
\n\n将docker服务设为开机启动 \n \nsystemtctl enable docker\n
\n\n停止docker \n \nsystemtctl stop docker\n
\nDocker常用命令及操作 \ndocker镜像命令
\ndocker search mysql ## 搜索镜像\ndocker pull mysql ## 下载镜像 下载命名为:docker pull 镜像名:tag,其中tag多为系统的版本,可选的,默认为least\ndocker images ## 镜像列表 RESPOSITORY为镜像名 TAG为镜像版本,least代表最新 IMAGE_ID 为该镜像唯一ID CREATED 为该镜像创建时间 SIZE 为该镜像大小\ndocker rmi image-id ##删除指定镜像\ndocker rmi $(docker images -q) ##删除所有镜像\ndocker run --name container-name -d image-name ##根据镜像启动容器 -- name:为容器起一个名称\n-d:detached,执行完这句命令后,控制台将不会阻塞,可以继续输入命令操作\nimage-name:要运行的镜像名称\ndocker ps ##查看运行中容器 CONTAINER ID:启动时生成的ID\nIMAGE:该容器使用的镜像\nCOMMAND:容器启动时执行的命令\nCREATED:容器创建时间\nSTATUS:当前容器状态\nPORTS:当前容器所使用的默认端口号\nNAMES:启动时给容器设置的名称\ndocker stop container-name/container-id ## 停止运行中容器\ndocker ps -a ##查看所有的容器\ndocker start container-name/container-id ##启动容器\ndocker rm container-id ## 删除单个容器\ndocker rm $(docker ps -a -q ) ## 删除所有容器\ndocker run --name tomcat2 -d -p 8888:8080 tomcat ## 启动做端口映射的容器\ndocker logs container-id/container-name ##查看容器日志\ndocker port container-id ## 查看端口映射\ndocker exec -it container-id/container-name bash ##容器登录命令为\nexit ##容器退出命令\n
\n更多命令可以参考
\n镜像操作指令 \n\n获取镜像:\ndocker pull centos (默认获取centos最新的镜像)\ndocker pull centos:7 (获取指定标签镜像) \n查看本地镜像:\ndocker images \n查看镜像详细信息:\ndocker inspect centos:7 \n查看镜像历史:\ndocker history centos:7 \n删除镜像:\nA:使用标签删除:docker rmi centos\nB:使用ID删除:docker rimi \n构建镜像:\nA:使用docker commit命令\nB:使用Dockerfile构建 \n \n使用docker commit \n例:构建一个带有jdk的镜像
\n按照如下步骤操作
\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@060793baf536 /]# yum install wget\n[root@060793baf536 /]# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm\n\n[root@060793baf536 /]# rpm -ivh jdk-8u131-linux-x64.rpm\nPreparing... ################################# [100%]\nUpdating / installing...\n 1:jdk1.8.0_131-2000:1.8.0_131-fcs ################################# [100%]\nUnpacking JAR files...\n tools.jar...\n plugin.jar...\n javaws.jar...\n deploy.jar...\n rt.jar...\n jsse.jar...\n charsets.jar...\n localedata.jar...\n[root@060793baf536 /]# exit\n[root@localhost ~]# docker commit 060793baf536 centos/jdk:2.0\n
\n通过docker images命令可以看到新增了centos/jdk 标签为2.0的镜像
\n使用Dockerfile构建 \n实际使用中不推荐使用docker commit构建,应使用更灵活和强大的Dockerfile构建docker镜像,直接举例来认识Dockerfile。
\n例:构建一个带有jdk的centos7镜像
\n[root@localhost Dockerfile]# mkdir Dockerfile\n[root@localhost Dockerfile]# cd Dockerfile\n编写Dockerfile:\nFROM centos:7\nMAINTAINER Java-Road "Java-Road@qq.com"\n\nRUN mkdir /usr/local/jdk\nCOPY jdk-8u171-linux-x64.rpm /usr/local/jdk/\nRUN rpm -ivh /usr/local/jdk/jdk-8u171-linux-x64.rpm\n执行如下指令:\n[root@localhost Dockerfile]# docker build -t centos/jdk .\n
\n运行结果如下:\n \ndocker images可以看到新生成的centos/jdk镜像
\n容器操作指令 \n\n[root@localhost ~]# docker run centos:7 /bin/echo'hello world'\n 容器运行完后直接退出\n
\n\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@802e3623e566 /]# ps\n PID TTY TIME CMD\n 1 ? 00:00:00 bash\n 13 ? 00:00:00 ps\n[root@802e3623e566 /]# exit\n执行exit才能退出容器\n
\n\n[root@localhost ~]# docker run -d centos:7 /bin/sh -c "while true; do echo hello world; sleep 1; done"\n
\n\n* docker start 容器ID\n# 例:\n[root@localhost ~]# docker start 802e3623e566\n
\n\n* docker stop 容器ID\n# 例:\n[root@localhost ~]# docker stop 802e3623e566\n
\n\n[root@localhost ~]# docker stop 89566e38c7fb\n[root@localhost ~]# docker rm 89566e38c7fb\n
\n\n[root@localhost ~]# docker exec -it cbd8b1f35dcc /bin/bash\n
\n\n# 导出容器cbd8b1f35dcc到centos_test.tar文件\n[root@localhost ~]# docker export -o centos_test.tar cbd8b1f35dcc\n# 导出的tar文件可以在其他机器上,通过导入来重新运行\n
\n\n# 把导出的文件centos_test.tar通过docker import导入变成镜像\n[root@localhost ~]# docker import centos_test.tar test/centos\n# 通过docker images命令可以看到增加了个test/centos镜像\n
\n实例:制作自己的 Docker 容器 \n\n下面我以 koa-demos 项目为例,介绍怎么写 Dockerfile 文件,实现让用户在 Docker 容器里面运行 Koa 框架。\n作为准备工作,请先下载源码。
\n \n$ git clone https://github.com/ruanyf/koa-demos.git\n$ cd koa-demos\n
\nDockerfile 文件 \n首先,在项目的根目录下,新建一个文本文件.dockerignore,写入下面的内容。
\n.git\nnode_modules\nnpm-debug.log\n
\n上面代码表示,这三个路径要排除,不要打包进入 image 文件。如果你没有路径要排除,这个文件可以不新建。
\n然后,在项目的根目录下,新建一个文本文件 Dockerfile,写入下面的内容。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\n
\n上面代码一共五行,含义如下。
\n\nFROM node:8.4:该 image 文件继承官方的 node image,冒号表示标签,这里标签是8.4,即8.4版本的 node。 \nCOPY . /app:将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入 image 文件的/app目录。 \nWORKDIR /app:指定接下来的工作路径为/app。 \nRUN npm install:在/app目录下,运行npm install命令安装依赖。注意,安装后所有的依赖,都将打包进入 image 文件。 \nEXPOSE 3000:将容器 3000 端口暴露出来, 允许外部连接这个端口。 \n \n创建 image 文件 \n有了 Dockerfile 文件以后,就可以使用docker image build命令创建 image 文件了。
\n$ docker image build -t koa-demo .\n# 或者\n$ docker image build -t koa-demo:0.0.1 .\n``` s\n上面代码中,-t参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。\n\n如果运行成功,就可以看到新生成的 image 文件koa-demo了。\n``` s\n$ docker image ls\n
\n生成容器 \ndocker container run命令会从 image 文件生成容器。\n$ docker container run -p 8000:3000 -it koa-demo /bin/bash\n# 或者\n$ docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash\n
\n上面命令的各个参数含义如下:
\n\n-p参数:容器的 3000 端口映射到本机的 8000 端口。 \n-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。 \nkoa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。 \n/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。 \n \n如果一切正常,运行上面的命令以后,就会返回一个命令行提示符。
\nroot@66d80f4aaf1e:/app#\n
\n这表示你已经在容器里面了,返回的提示符就是容器内部的 Shell 提示符。执行下面的命令。
\nroot@66d80f4aaf1e:/app# node demos/01.js\n
\n这时,Koa 框架已经运行起来了。打开本机的浏览器,访问 http://127.0.0.1:8000 ,网页显示"Not Found",这是因为这个 demo 没有写路由。
\n这个例子中,Node 进程运行在 Docker 容器的虚拟环境里面,进程接触到的文件系统和网络接口都是虚拟的,与本机的文件系统和网络接口是隔离的,因此需要定义容器与物理机的端口映射(map)。
\n现在,在容器的命令行,按下 Ctrl + c 停止 Node 进程,然后按下 Ctrl + d (或者输入 exit)退出容器。此外,也可以用docker container kill终止容器运行。
\n# 在本机的另一个终端窗口,查出容器的 ID\n$ docker container ls\n\n# 停止指定的容器运行\n$ docker container kill [containerID]\n\n# 容器停止运行之后,并不会消失,用下面的命令删除容器文件。\n\n# 查出容器的 ID\n$ docker container ls --all\n\n# 删除指定的容器文件\n$ docker container rm [containerID]\n\n#也可以使用docker container run命令的--rm参数,在容器终止运行后自动删除容器文件。\n$ docker container run --rm -p 8000:3000 -it koa-demo /bin/bash\n
\nCMD 命令 \n上一节的例子里面,容器启动以后,需要手动输入命令node demos/01.js。我们可以把这个命令写在 Dockerfile 里面,这样容器启动以后,这个命令就已经执行了,不用再手动输入了。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\nCMD node demos/01.js\n
\n上面的 Dockerfile 里面,多了最后一行CMD node demos/01.js,它表示容器启动后自动执行node demos/01.js。
\n你可能会问,RUN命令与CMD命令的区别在哪里?简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。
\n注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。现在,启动容器可以使用下面的命令。
\n$ docker container run --rm -p 8000:3000 -it koa-demo:0.0.1\n
\n发布 image 文件 \n容器运行成功后,就确认了 image 文件的有效性。这时,我们就可以考虑把 image 文件分享到网上,让其他人使用。\n首先,去 hub.docker.com 或 cloud.docker.com 注册一个账户。然后,用下面的命令登录。
\n$ docker login\n# 接着,为本地的 image 标注用户名和版本。\n$ docker image tag [imageName] [username]/[repository]:[tag]\n# 实例\n$ docker image tag koa-demos:0.0.1 ruanyf/koa-demos:0.0.1\n#也可以不标注用户名,重新构建一下 image 文件。\n$ docker image build -t [username]/[repository]:[tag] .\n# 最后,发布 image 文件。\n$ docker image push [username]/[repository]:[tag]\n
\n发布成功以后,登录 hub.docker.com ,就可以看到已经发布的 image 文件。
\n其他有用的命令 \ndocker 的主要用法就是上面这些,此外还有几个命令,也非常有用。
\n\ndocker container start \n \n前面的docker container run命令是新建容器,每运行一次,就会新建一个容器。同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器,就要使用docker container start命令,它用来启动已经生成、已经停止运行的容器文件。
\n$ docker container start [containerID]\n
\n\ndocker container stop \n \n前面的docker container kill命令终止容器运行,相当于向容器里面的主进程发出 SIGKILL 信号。而docker container stop命令也是用来终止容器运行,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号。
\n$ bash container stop [containerID]\n
\n这两个信号的差别是,应用程序收到 SIGTERM 信号以后,可以自行进行收尾清理工作,但也可以不理会这个信号。如果收到 SIGKILL 信号,就会强行立即终止,那些正在进行中的操作会全部丢失。
\n\ndocker container logs \n \ndocker container logs命令用来查看 docker 容器的输出,即容器里面 Shell 的标准输出。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令查看输出。
\n$ docker container logs [containerID]\n
\n\ndocker container exec \n \ndocker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令了。
\n$ docker container exec -it [containerID] /bin/bash\n
\n\ndocker container cp \n \ndocker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
\n$ docker container cp [containID]:[/path/to/file] .\n
\n",
+ "__html": "Docker \nDocker简介 \n\nDocker是开源应用容器引擎,轻量级容器技术。 \n基于Go语言,并遵循Apache2.0协议开源 \nDocker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux系统上,也可以实现虚拟化 \n容器完全使用沙箱技术,相互之间不会有任何接口 \n类似于虚拟机技术(vmware、vitural),但docker直接运行在操作系统(Linux)上,而不是运行在虚拟机中,速度快,性能开销极低 \n \n白话文,简介就是:
\n\nDocker支持将软件编译成一个镜像,然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像。\n运行中的这个镜像称为容器,容器启动是非常快速的。类似windows里面的ghost操 作系统,安装好后什么都有了。
\n \n什么是Docker \nDocker 是一个开源的应用容器引擎,基于Go语言,诞生于2013年初,最初发起者是dotCloud公司,开发者可以打包应用到一个轻量级、可移植的容器中,然后发布到主流Linux系统上运行。\n为什么用Docker
\n\n持续交付和部署:使用Docker可以通过定制应用镜像来实现持续集成,持续交付,部署。开发人员构建后的镜像,结合持续集成系统进行集成测试,而运维人员则可以在生产环境中快速部署该镜像,也可以结合持续部署系统进行自动部署。 \n更高效的资源利用:Docker是基于内核级的虚拟化,可以实现更高效的性能,同时对资源的额外需求很低,相比传统虚拟机方式,相同配置的主机能够运行更多的应用。\n更轻松的迁移和扩展:Docker容器几乎可以在任何平台上运行,同时支持主流的操作系统发行版本。 \n更快速的启动时间:传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到妙级,甚至毫秒级的启动时间,大大的节约了开发,测试,部署的时间。\nDocker与传统虚拟机差异 \n \n传统虚拟化方式
\n \nDocker虚拟化方式
\n
\n传统虚拟化是在硬件层面实现虚拟化,需要有额外的虚拟机管理应用和虚拟机操作系统层,而Docker容器是在操作系统层面实现虚拟化,直接复用本地主机操作系统,更加轻量级。
\n核心概念 \n\nDocker镜像:类似于虚拟机里的镜像,是一个只读的模板,一个独立的文件系统,使用镜像可以创建容器,可以理解为镜像是容器的基石。 \nDocker容器:是由Docker镜像创建的运行实例,类似于轻量级的沙箱,每个容器之间都是相互隔离的。支持的操作有启动,停止,删除等。 \ndocker客户端(Client):客户端通过命令行或其他工具使用Docker API(https://docs.docker.com/reference/api/docker_remote_api )与Docker的守护进程进行通信 \ndocker主机(Host):一个物理或虚拟的机器用来执行Docker守护进程和容器 \ndocker仓库(Registry):Docker仓库用来存储镜像,可以理解为代码控制中的代码仓库,Docker Hub(https://hub.docker.com ) 提供了庞大的镜像集合供使用 \n \nDocker安装及启停 \n\n查看centos版本 \n \nDocker 要求 CentOS 系统的内核版本高于 3 .10 \n通过命令:uname -r\n查看当前centos版本,如版本不符,需升级系统版本\n
\n\n升级软件包及内核(可选) \n \nyum update\n
\n\n安装docker \n \nyum install docker\n
\n\n启动docker \n \nsystemctl start docker\n
\n\n将docker服务设为开机启动 \n \nsystemtctl enable docker\n
\n\n停止docker \n \nsystemtctl stop docker\n
\nDocker常用命令及操作 \ndocker镜像命令
\ndocker search mysql ## 搜索镜像\ndocker pull mysql ## 下载镜像 下载命名为:docker pull 镜像名:tag,其中tag多为系统的版本,可选的,默认为least\ndocker images ## 镜像列表 RESPOSITORY为镜像名 TAG为镜像版本,least代表最新 IMAGE_ID 为该镜像唯一ID CREATED 为该镜像创建时间 SIZE 为该镜像大小\ndocker rmi image-id ##删除指定镜像\ndocker rmi $(docker images -q) ##删除所有镜像\ndocker run --name container-name -d image-name ##根据镜像启动容器 -- name:为容器起一个名称\n-d:detached,执行完这句命令后,控制台将不会阻塞,可以继续输入命令操作\nimage-name:要运行的镜像名称\ndocker ps ##查看运行中容器 CONTAINER ID:启动时生成的ID\nIMAGE:该容器使用的镜像\nCOMMAND:容器启动时执行的命令\nCREATED:容器创建时间\nSTATUS:当前容器状态\nPORTS:当前容器所使用的默认端口号\nNAMES:启动时给容器设置的名称\ndocker stop container-name/container-id ## 停止运行中容器\ndocker ps -a ##查看所有的容器\ndocker start container-name/container-id ##启动容器\ndocker rm container-id ## 删除单个容器\ndocker rm $(docker ps -a -q ) ## 删除所有容器\ndocker run --name tomcat2 -d -p 8888:8080 tomcat ## 启动做端口映射的容器\ndocker logs container-id/container-name ##查看容器日志\ndocker port container-id ## 查看端口映射\ndocker exec -it container-id/container-name bash ##容器登录命令为\nexit ##容器退出命令\n
\n更多命令可以参考
\n镜像操作指令 \n\n获取镜像:\ndocker pull centos (默认获取centos最新的镜像)\ndocker pull centos:7 (获取指定标签镜像) \n查看本地镜像:\ndocker images \n查看镜像详细信息:\ndocker inspect centos:7 \n查看镜像历史:\ndocker history centos:7 \n删除镜像:\nA:使用标签删除:docker rmi centos\nB:使用ID删除:docker rimi \n构建镜像:\nA:使用docker commit命令\nB:使用Dockerfile构建 \n \n使用docker commit \n例:构建一个带有jdk的镜像
\n按照如下步骤操作
\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@060793baf536 /]# yum install wget\n[root@060793baf536 /]# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm\n\n[root@060793baf536 /]# rpm -ivh jdk-8u131-linux-x64.rpm\nPreparing... ################################# [100%]\nUpdating / installing...\n 1:jdk1.8.0_131-2000:1.8.0_131-fcs ################################# [100%]\nUnpacking JAR files...\n tools.jar...\n plugin.jar...\n javaws.jar...\n deploy.jar...\n rt.jar...\n jsse.jar...\n charsets.jar...\n localedata.jar...\n[root@060793baf536 /]# exit\n[root@localhost ~]# docker commit 060793baf536 centos/jdk:2.0\n
\n通过docker images命令可以看到新增了centos/jdk 标签为2.0的镜像
\n使用Dockerfile构建 \n实际使用中不推荐使用docker commit构建,应使用更灵活和强大的Dockerfile构建docker镜像,直接举例来认识Dockerfile。
\n例:构建一个带有jdk的centos7镜像
\n[root@localhost Dockerfile]# mkdir Dockerfile\n[root@localhost Dockerfile]# cd Dockerfile\n编写Dockerfile:\nFROM centos:7\nMAINTAINER Java-Road "Java-Road@qq.com"\n\nRUN mkdir /usr/local/jdk\nCOPY jdk-8u171-linux-x64.rpm /usr/local/jdk/\nRUN rpm -ivh /usr/local/jdk/jdk-8u171-linux-x64.rpm\n执行如下指令:\n[root@localhost Dockerfile]# docker build -t centos/jdk .\n
\n运行结果如下:\n \ndocker images可以看到新生成的centos/jdk镜像
\n容器操作指令 \n\n[root@localhost ~]# docker run centos:7 /bin/echo'hello world'\n 容器运行完后直接退出\n
\n\n[root@localhost ~]# docker run -it centos:7 /bin/bash\n[root@802e3623e566 /]# ps\n PID TTY TIME CMD\n 1 ? 00:00:00 bash\n 13 ? 00:00:00 ps\n[root@802e3623e566 /]# exit\n执行exit才能退出容器\n
\n\n[root@localhost ~]# docker run -d centos:7 /bin/sh -c "while true; do echo hello world; sleep 1; done"\n
\n\n* docker start 容器ID\n# 例:\n[root@localhost ~]# docker start 802e3623e566\n
\n\n* docker stop 容器ID\n# 例:\n[root@localhost ~]# docker stop 802e3623e566\n
\n\n[root@localhost ~]# docker stop 89566e38c7fb\n[root@localhost ~]# docker rm 89566e38c7fb\n
\n\n[root@localhost ~]# docker exec -it cbd8b1f35dcc /bin/bash\n
\n\n# 导出容器cbd8b1f35dcc到centos_test.tar文件\n[root@localhost ~]# docker export -o centos_test.tar cbd8b1f35dcc\n# 导出的tar文件可以在其他机器上,通过导入来重新运行\n
\n\n# 把导出的文件centos_test.tar通过docker import导入变成镜像\n[root@localhost ~]# docker import centos_test.tar test/centos\n# 通过docker images命令可以看到增加了个test/centos镜像\n
\n实例:制作自己的 Docker 容器 \n\n下面我以 koa-demos 项目为例,介绍怎么写 Dockerfile 文件,实现让用户在 Docker 容器里面运行 Koa 框架。\n作为准备工作,请先下载源码。
\n \n$ git clone https://github.com/ruanyf/koa-demos.git\n$ cd koa-demos\n
\nDockerfile 文件 \n首先,在项目的根目录下,新建一个文本文件.dockerignore,写入下面的内容。
\n.git\nnode_modules\nnpm-debug.log\n
\n上面代码表示,这三个路径要排除,不要打包进入 image 文件。如果你没有路径要排除,这个文件可以不新建。
\n然后,在项目的根目录下,新建一个文本文件 Dockerfile,写入下面的内容。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\n
\n上面代码一共五行,含义如下。
\n\nFROM node:8.4:该 image 文件继承官方的 node image,冒号表示标签,这里标签是8.4,即8.4版本的 node。 \nCOPY . /app:将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入 image 文件的/app目录。 \nWORKDIR /app:指定接下来的工作路径为/app。 \nRUN npm install:在/app目录下,运行npm install命令安装依赖。注意,安装后所有的依赖,都将打包进入 image 文件。 \nEXPOSE 3000:将容器 3000 端口暴露出来, 允许外部连接这个端口。 \n \n创建 image 文件 \n有了 Dockerfile 文件以后,就可以使用docker image build命令创建 image 文件了。
\n$ docker image build -t koa-demo .\n# 或者\n$ docker image build -t koa-demo:0.0.1 .\n``` s\n上面代码中,-t参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。\n\n如果运行成功,就可以看到新生成的 image 文件koa-demo了。\n``` s\n$ docker image ls\n
\n生成容器 \ndocker container run命令会从 image 文件生成容器。\n$ docker container run -p 8000:3000 -it koa-demo /bin/bash\n# 或者\n$ docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash\n
\n上面命令的各个参数含义如下:
\n\n-p参数:容器的 3000 端口映射到本机的 8000 端口。 \n-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。 \nkoa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。 \n/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。 \n \n如果一切正常,运行上面的命令以后,就会返回一个命令行提示符。
\nroot@66d80f4aaf1e:/app#\n
\n这表示你已经在容器里面了,返回的提示符就是容器内部的 Shell 提示符。执行下面的命令。
\nroot@66d80f4aaf1e:/app# node demos/01.js\n
\n这时,Koa 框架已经运行起来了。打开本机的浏览器,访问 http://127.0.0.1:8000 ,网页显示"Not Found",这是因为这个 demo 没有写路由。
\n这个例子中,Node 进程运行在 Docker 容器的虚拟环境里面,进程接触到的文件系统和网络接口都是虚拟的,与本机的文件系统和网络接口是隔离的,因此需要定义容器与物理机的端口映射(map)。
\n现在,在容器的命令行,按下 Ctrl + c 停止 Node 进程,然后按下 Ctrl + d (或者输入 exit)退出容器。此外,也可以用docker container kill终止容器运行。
\n# 在本机的另一个终端窗口,查出容器的 ID\n$ docker container ls\n\n# 停止指定的容器运行\n$ docker container kill [containerID]\n\n# 容器停止运行之后,并不会消失,用下面的命令删除容器文件。\n\n# 查出容器的 ID\n$ docker container ls --all\n\n# 删除指定的容器文件\n$ docker container rm [containerID]\n\n#也可以使用docker container run命令的--rm参数,在容器终止运行后自动删除容器文件。\n$ docker container run --rm -p 8000:3000 -it koa-demo /bin/bash\n
\nCMD 命令 \n上一节的例子里面,容器启动以后,需要手动输入命令node demos/01.js。我们可以把这个命令写在 Dockerfile 里面,这样容器启动以后,这个命令就已经执行了,不用再手动输入了。
\nFROM node:8.4\nCOPY . /app\nWORKDIR /app\nRUN npm install --registry=https://registry.npm.taobao.org\nEXPOSE 3000\nCMD node demos/01.js\n
\n上面的 Dockerfile 里面,多了最后一行CMD node demos/01.js,它表示容器启动后自动执行node demos/01.js。
\n你可能会问,RUN命令与CMD命令的区别在哪里?简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。
\n注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。现在,启动容器可以使用下面的命令。
\n$ docker container run --rm -p 8000:3000 -it koa-demo:0.0.1\n
\n发布 image 文件 \n容器运行成功后,就确认了 image 文件的有效性。这时,我们就可以考虑把 image 文件分享到网上,让其他人使用。\n首先,去 hub.docker.com 或 cloud.docker.com 注册一个账户。然后,用下面的命令登录。
\n$ docker login\n# 接着,为本地的 image 标注用户名和版本。\n$ docker image tag [imageName] [username]/[repository]:[tag]\n# 实例\n$ docker image tag koa-demos:0.0.1 ruanyf/koa-demos:0.0.1\n#也可以不标注用户名,重新构建一下 image 文件。\n$ docker image build -t [username]/[repository]:[tag] .\n# 最后,发布 image 文件。\n$ docker image push [username]/[repository]:[tag]\n
\n发布成功以后,登录 hub.docker.com ,就可以看到已经发布的 image 文件。
\n其他有用的命令 \ndocker 的主要用法就是上面这些,此外还有几个命令,也非常有用。
\n\ndocker container start \n \n前面的docker container run命令是新建容器,每运行一次,就会新建一个容器。同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器,就要使用docker container start命令,它用来启动已经生成、已经停止运行的容器文件。
\n$ docker container start [containerID]\n
\n\ndocker container stop \n \n前面的docker container kill命令终止容器运行,相当于向容器里面的主进程发出 SIGKILL 信号。而docker container stop命令也是用来终止容器运行,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号。
\n$ bash container stop [containerID]\n
\n这两个信号的差别是,应用程序收到 SIGTERM 信号以后,可以自行进行收尾清理工作,但也可以不理会这个信号。如果收到 SIGKILL 信号,就会强行立即终止,那些正在进行中的操作会全部丢失。
\n\ndocker container logs \n \ndocker container logs命令用来查看 docker 容器的输出,即容器里面 Shell 的标准输出。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令查看输出。
\n$ docker container logs [containerID]\n
\n\ndocker container exec \n \ndocker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令了。
\n$ docker container exec -it [containerID] /bin/bash\n
\n\ndocker container cp \n \ndocker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
\n$ docker container cp [containID]:[/path/to/file] .\n
\nDocker命令详解(run篇) \n命令格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]\nUsage: Run a command in a new container\n中文意思为:通过run命令创建一个新的容器(container)
\n常用选项说明 \n-d, --detach=false, 指定容器运行于前台还是后台,默认为false\n-i, --interactive=false, 打开STDIN,用于控制台交互\n-t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false\n-u, --user="", 指定容器的用户\n-a, --attach=[], 登录容器(必须是以docker run -d启动的容器)\n-w, --workdir="", 指定容器的工作目录\n-c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用\n-e, --env=[], 指定环境变量,容器中可以使用该环境变量\n-m, --memory="", 指定容器的内存上限\n-P, --publish-all=false, 指定容器暴露的端口\n-p, --publish=[], 指定容器暴露的端口\n-h, --hostname="", 指定容器的主机名\n-v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录\n--volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录\n--cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities \n--cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities \n--cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法\n--cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU\n--device=[], 添加主机设备给容器,相当于设备直通\n--dns=[], 指定容器的dns服务器\n--dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件\n--entrypoint="", 覆盖image的入口点\n--env-file=[], 指定环境变量文件,文件格式为每行一个环境变量\n--expose=[], 指定容器暴露的端口,即修改镜像的暴露端口\n--link=[], 指定容器间的关联,使用其他容器的IP、env等信息\n--lxc-conf=[], 指定容器的配置文件,只有在指定--exec-driver=lxc时使用\n--name="", 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字\n--net="bridge", 容器网络设置:\nbridge 使用docker daemon指定的网桥\nhost //容器使用主机的网络\ncontainer:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源\nnone 容器使用自己的网络(类似--net=bridge),但是不进行配置\n--privileged=false, 指定容器是否为特权容器,特权容器拥有所有的capabilities\n--restart="no", 指定容器停止后的重启策略:\nno:容器退出时不重启\non-failure:容器故障退出(返回值非零)时重启\nalways:容器退出时总是重启\n--rm=false, 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)\n--sig-proxy=true, 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
\n示例 \n\n运行一个在后台执行的容器,同时,还能用控制台管理: \n \ndocker run -i -t -d ubuntu:latest\n
\n\n运行一个带命令在后台不断执行的容器,不直接展示容器内部信息: \n \ndocker run -d ubuntu:latest ping www.docker.com\n
\n\n运行一个在后台不断执行的容器,同时带有命令,程序被终止后还能重启继续跑,还能用控制台管理, \n \ndocker run -d --restart=always ubuntu:latest ping www.docker.com\n
\n\ndocker run -d --name=ubuntu_server ubuntu:latest\n
\n\n容器暴露80端口,并指定宿主机80端口与其通信(: 之前是宿主机端口,之后是容器需暴露的端口), \n \ndocker run -d --name=ubuntu_server -p 80:80 ubuntu:latest\n
\n\n指定容器内目录与宿主机目录共享(: 之前是宿主机文件夹,之后是容器需共享的文件夹), \n \ndocker run -d --name=ubuntu_server -v /etc/www:/var/www ubuntu:latest\n
\n",
"link": "\\zh-cn\\blog\\docker\\docker.html",
"meta": {}
}
\ No newline at end of file
diff --git a/zh-cn/blog/exp/cto.html b/zh-cn/blog/exp/cto.html
new file mode 100644
index 0000000..6fbea36
--- /dev/null
+++ b/zh-cn/blog/exp/cto.html
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+ cto
+
+
+
+
+ CTO 技能图谱
+岗位职责
+
+建立技术团队文化
+规划技术发展路线
+落地产品研发成果
+宣传公司技术品牌
+吸引优秀技术人才
+
+基本素质
+
+正直诚实的道德修养
+谦虚谨慎的工作态度
+随机应变的处事风格
+统领全局的战略思维
+
+硬技能
+技术能力
+
+具备一定的技术深度
+具备较强的技术广度
+追求技术选型合理性
+对技术发展嗅觉敏锐
+
+业务能力
+
+能深度理解业务本质
+能用技术来帮助业务
+让技术驱动业务发展
+
+架构能力
+
+能站在业务上设计架构
+架构规范合理且易落地
+能为架构设计提出意见
+
+产品能力
+
+具备一定的产品价值观
+能准确地抓住用户刚需
+能为产品设计提出意见
+
+管理能力
+
+团队管理
+
+招人:知道如何吸引所需的人?
+识人:知道如何识别已有队员?
+育人:知道如何让队员们成长?
+开人:知道如何请人愉快离开?
+留人:知道如何留住该留的人?
+挖人:知道如何挖到需要的人?
+
+
+项目管理
+
+估算:知道项目成本如何估算?
+分工:知道工作任务如何分配?
+排期:知道项目计划如何制定?
+实施:知道项目实施如何开展?
+发布:知道项目发布如何执行?
+回顾:知道项目回顾如何进行?
+
+
+绩效管理
+
+制定:知道如何制定考核标准?
+执行:知道如何执行绩效考核?
+优化:知道如何提升团队绩效?
+
+
+时间管理
+
+制定:知道如何制定团队计划?
+管理:知道如何管理团队任务?
+权衡:知道如何权衡优先级别?
+
+
+情绪管理
+
+控制:知道如何控制自我情绪?
+转化:知道如何化悲观为乐观?
+使用:知道如何善用自我情绪?
+
+
+
+软技能
+领导能力
+
+决策能力
+
+不要害怕做决定
+做出正确的决定
+敢于为决定负责
+
+
+影响能力
+
+不要改变别人而是激发别人
+用自己的行为和态度去激发
+持续不断提高自己的影响力
+
+
+沟通能力
+
+向上沟通(与公司创始人)
+
+领会老板真实意图
+站在老板角度思考
+不要强迫改变老板
+
+
+向下沟通(与本部门同事)
+
+是沟通而不是命令
+站在下属立场沟通
+不要吝啬夸赞队员
+
+
+
+
+横向沟通(与跨部门同事)
+
+突出对方的重要性
+先要共识才能共赢
+懂得圆满大于完美
+
+
+
+执行能力
+
+理解执行目标
+提高执行效率
+确保执行效果
+
+学习能力
+
+对新知识充满好奇心
+能够快速学习新技能
+拥有触类旁通的能力
+
+组织能力
+
+积极主动并有活力
+做事情敢于放得开
+能够调用所需资源
+
+洞察能力
+
+善于抓住事物本质
+善于观察人性心理
+善于预见未来变化
+
+抗压能力
+
+学会释放压力
+化压力为动力
+化消极为积极
+
+自省能力
+
+能够不断自我反省
+能够从失败中总结
+能够在总结中提高
+
+战略能力
+
+能够深刻理解公司战略
+能够站在更高层次思考
+结合战略形成技术壁垒
+
+社交能力
+
+善于表达自己
+善于结交朋友
+善于公众演讲
+
+谈判能力
+
+能够通过谈判得到对方认同
+能够从谈判中找到共赢方式
+能够在谈判场合中保持镇定
+
+政治能力
+
+能够对政治持有敏感度
+能够处理办公室小政治
+能够主动避免政治风险
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/exp/cto.json b/zh-cn/blog/exp/cto.json
new file mode 100644
index 0000000..d0a3c62
--- /dev/null
+++ b/zh-cn/blog/exp/cto.json
@@ -0,0 +1,6 @@
+{
+ "filename": "cto.md",
+ "__html": "CTO 技能图谱 \n岗位职责 \n\n建立技术团队文化 \n规划技术发展路线 \n落地产品研发成果 \n宣传公司技术品牌 \n吸引优秀技术人才 \n \n基本素质 \n\n正直诚实的道德修养 \n谦虚谨慎的工作态度 \n随机应变的处事风格 \n统领全局的战略思维 \n \n硬技能 \n技术能力 \n\n具备一定的技术深度 \n具备较强的技术广度 \n追求技术选型合理性 \n对技术发展嗅觉敏锐 \n \n业务能力 \n\n能深度理解业务本质 \n能用技术来帮助业务 \n让技术驱动业务发展 \n \n架构能力 \n\n能站在业务上设计架构 \n架构规范合理且易落地 \n能为架构设计提出意见 \n \n产品能力 \n\n具备一定的产品价值观 \n能准确地抓住用户刚需 \n能为产品设计提出意见 \n \n管理能力 \n\n团队管理\n\n招人:知道如何吸引所需的人? \n识人:知道如何识别已有队员? \n育人:知道如何让队员们成长? \n开人:知道如何请人愉快离开? \n留人:知道如何留住该留的人? \n挖人:知道如何挖到需要的人? \n \n \n项目管理\n\n估算:知道项目成本如何估算? \n分工:知道工作任务如何分配? \n排期:知道项目计划如何制定? \n实施:知道项目实施如何开展? \n发布:知道项目发布如何执行? \n回顾:知道项目回顾如何进行? \n \n \n绩效管理\n\n制定:知道如何制定考核标准? \n执行:知道如何执行绩效考核? \n优化:知道如何提升团队绩效? \n \n \n时间管理\n\n制定:知道如何制定团队计划? \n管理:知道如何管理团队任务? \n权衡:知道如何权衡优先级别? \n \n \n情绪管理\n\n控制:知道如何控制自我情绪? \n转化:知道如何化悲观为乐观? \n使用:知道如何善用自我情绪? \n \n \n \n软技能 \n领导能力 \n\n决策能力\n\n不要害怕做决定 \n做出正确的决定 \n敢于为决定负责 \n \n \n影响能力\n\n不要改变别人而是激发别人 \n用自己的行为和态度去激发 \n持续不断提高自己的影响力 \n \n \n沟通能力\n\n向上沟通(与公司创始人)\n\n领会老板真实意图 \n站在老板角度思考 \n不要强迫改变老板 \n \n \n向下沟通(与本部门同事)\n\n是沟通而不是命令 \n站在下属立场沟通 \n不要吝啬夸赞队员 \n \n \n \n \n横向沟通(与跨部门同事)\n\n突出对方的重要性 \n先要共识才能共赢 \n懂得圆满大于完美 \n \n \n \n执行能力 \n\n理解执行目标 \n提高执行效率 \n确保执行效果 \n \n学习能力 \n\n对新知识充满好奇心 \n能够快速学习新技能 \n拥有触类旁通的能力 \n \n组织能力 \n\n积极主动并有活力 \n做事情敢于放得开 \n能够调用所需资源 \n \n洞察能力 \n\n善于抓住事物本质 \n善于观察人性心理 \n善于预见未来变化 \n \n抗压能力 \n\n学会释放压力 \n化压力为动力 \n化消极为积极 \n \n自省能力 \n\n能够不断自我反省 \n能够从失败中总结 \n能够在总结中提高 \n \n战略能力 \n\n能够深刻理解公司战略 \n能够站在更高层次思考 \n结合战略形成技术壁垒 \n \n社交能力 \n\n善于表达自己 \n善于结交朋友 \n善于公众演讲 \n \n谈判能力 \n\n能够通过谈判得到对方认同 \n能够从谈判中找到共赢方式 \n能够在谈判场合中保持镇定 \n \n政治能力 \n\n能够对政治持有敏感度 \n能够处理办公室小政治 \n能够主动避免政治风险 \n \n",
+ "link": "\\zh-cn\\blog\\exp\\cto.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/java/feature.html b/zh-cn/blog/java/feature.html
index a2fad2f..10449b7 100644
--- a/zh-cn/blog/java/feature.html
+++ b/zh-cn/blog/java/feature.html
@@ -23,6 +23,7 @@
JAVA7: <>符号、ARM支持、支持多catch
JAVA8:Lamda表达式,类型注解等
JAVA9: 模块化、接口中的私有方法等
+JAVA10: 局部变量推断、整个JDK代码仓库、统一的垃圾回收接口、并行垃圾回收器G1、线程局部管控
Java5 新特性总结
泛型 Generics
@@ -1463,6 +1464,75 @@ 新的工厂方法
* <U> CompletionStage<U> completedStage(U value): 返回一个新的以指定 value 完成的CompletionStage ,并且只支持 CompletionStage 里的接口。
* <U> CompletionStage<U> failedStage(Throwable ex): 返回一个新的以指定异常完成的CompletionStage ,并且只支持 CompletionStage 里的接口。
+Java 10 新特性
+
+局部变量推断
+整个JDK代码仓库
+统一的垃圾回收接口
+并行垃圾回收器G1
+线程局部管控
+
+局部变量推断
+它向 Java 中引入在其他语言中很常见的 var ,比如 JavaScript 。只要编译器可以推断此种类型,你不再需要专门声明一个局部变量的类型。
+开发者将能够声明变量而不必指定关联的类型。比如:
+List <String> list = new ArrayList <String>();
+Stream <String> stream = getStream();
+
+它可以简化为:
+var list = new ArrayList();
+var stream = getStream();
+
+局部变量类型推断将引入“ var ”关键字的使用,而不是要求明确指定变量的类型,我们俗称“语法糖”。
+这就消除了我们之前必须执行的 ArrayList 类型定义的重复。
+其实我们在JDK7,我们需要:
+List <String> list = new ArrayList <String>();
+
+但是在JDK8,我们只需要:
+List <String> list = new ArrayList <>();
+
+所以这是一个逐步的升级。也是人性化的表现与提升。
+有趣的是,需要注意 var 不能成为一个关键字,而是一个保留字。这意味着你仍然可以使用 var 作为一个变量,方法或包名,但是现在(尽管我确定你绝不会)你不能再有一个类被调用。
+局部变量类型推荐仅限于如下使用场景:
+
+局部变量初始化
+for循环内部索引变量
+传统的for循环声明变量
+Java官方表示,它不能用于以下几个地方:
+方法参数
+构造函数参数
+方法返回类型
+字段
+捕获表达式(或任何其他类型的变量声明)
+
+注意:
+Java的var和JavaScript的完全不同,不要这样去类比。Java的var是用于局部类型推断的,而不是JS那样的动态类型,所以下面这个样子是不行的:
+var a = 10 ;
+a = "abc" ;
+
+其次,这个var只能用于局部变量声明,在其他地方使用都是错误的。
+class C {
+ public var a = 10 ;
+ public var f () {
+ return 10 ;
+ }
+}
+
+整合 JDK 代码仓库
+为了简化开发流程,Java 10 中会将多个代码库合并到一个代码仓库中。
+在已发布的 Java 版本中,JDK 的整套代码根据不同功能已被分别存储在多个 Mercurial 存储库,这八个 Mercurial 存储库分别是:root、corba、hotspot、jaxp、jaxws、jdk、langtools、nashorn。
+虽然以上八个存储库之间相互独立以保持各组件代码清晰分离,但同时管理这些存储库存在许多缺点,并且无法进行相关联源代码的管理操作。其中最重要的一点是,涉及多个存储库的变更集无法进行原子提交 (atomic commit)。例如,如果一个 bug 修复时需要对独立存储两个不同代码库的代码进行更改,那么必须创建两个提交:每个存储库中各一个。这种不连续性很容易降低项目和源代码管理工具的可跟踪性和加大复杂性。特别是,不可能跨越相互依赖的变更集的存储库执行原子提交这种多次跨仓库的变化是常见现象。
+为了解决这个问题,JDK 10 中将所有现有存储库合并到一个 Mercurial 存储库中。这种合并的一次生效应,单一的 Mercurial 存储库比现有的八个存储库要更容易地被镜像(作为一个 Git 存储库),并且使得跨越相互依赖的变更集的存储库运行原子提交成为可能,从而简化开发和管理过程。虽然在整合过程中,外部开发人员有一些阻力,但是 JDK 开发团队已经使这一更改成为 JDK 10 的一部分。
+统一的垃圾回收接口
+在当前的 Java 结构中,组成垃圾回收器(GC)实现的组件分散在代码库的各个部分。尽管这些惯例对于使用 GC 计划的 JDK 开发者来说比较熟悉,但对新的开发人员来说,对于在哪里查找特定 GC 的源代码,或者实现一个新的垃圾收集器常常会感到困惑。更重要的是,随着 Java modules 的出现,我们希望在构建过程中排除不需要的 GC,但是当前 GC 接口的横向结构会给排除、定位问题带来困难。
+为解决此问题,需要整合并清理 GC 接口,以便更容易地实现新的 GC,并更好地维护现有的 GC。Java 10 中,hotspot/gc 代码实现方面,引入一个干净的 GC 接口,改进不同 GC 源代码的隔离性,多个 GC 之间共享的实现细节代码应该存在于辅助类中。这种方式提供了足够的灵活性来实现全新 GC 接口,同时允许以混合搭配方式重复使用现有代码,并且能够保持代码更加干净、整洁,便于排查收集器问题。
+并行垃圾回收器 G1
+大家如果接触过 Java 性能调优工作,应该会知道,调优的最终目标是通过参数设置来达到快速、低延时的内存垃圾回收以提高应用吞吐量,尽可能的避免因内存回收不及时而触发的完整 GC(Full GC 会带来应用出现卡顿)。
+G1 垃圾回收器是 Java 9 中 Hotspot 的默认垃圾回收器,是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是当并发收集无法快速回收内存时,会触发垃圾回收器回退进行 Full GC。之前 Java 版本中的 G1 垃圾回收器执行 GC 时采用的是基于单线程标记扫描压缩算法(mark-sweep-compact)。为了最大限度地减少 Full GC 造成的应用停顿的影响,Java 10 中将为 G1 引入多线程并行 GC,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
+Java 10 中将采用并行化 mark-sweep-compact 算法,并使用与年轻代回收和混合回收相同数量的线程。具体并行 GC 线程数量可以通过:-XX:ParallelGCThreads 参数来调节,但这也会影响用于年轻代和混合收集的工作线程数。
+线程局部管控
+在已有的 Java 版本中,JVM 线程只能全部启用或者停止,没法做到对单独某个线程的操作。为了能够对单独的某个线程进行操作,Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程。通过这种方式显著地提高了现有 JVM 功能的性能开销,并且改变了到达 JVM 全局安全点的现有时间语义。
+增加的参数为:-XX:ThreadLocalHandshakes (默认为开启),将允许用户在支持的平台上选择安全点。
+参考
diff --git a/zh-cn/blog/java/feature.json b/zh-cn/blog/java/feature.json
index f16e299..2f293d5 100644
--- a/zh-cn/blog/java/feature.json
+++ b/zh-cn/blog/java/feature.json
@@ -1,6 +1,6 @@
{
"filename": "feature.md",
- "__html": "Java 新特性总结 \n总结的这些新特性,都是自己觉得在开发中实际用得上的。\n简单概括下就是:
\n\nJAVA1.3:普通的原始的JAVA,基本语法相信大家都见过了 \nJAVA1.4:assert关键字 \nJAVA5:枚举类型、泛型、自动拆装箱 \nJAVA6: @Override注解 \nJAVA7: <>符号、ARM支持、支持多catch \nJAVA8:Lamda表达式,类型注解等 \nJAVA9: 模块化、接口中的私有方法等 \n \nJava5 新特性总结 \n泛型 Generics \n引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。Parameterized Type作为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。
\n泛型可以带来如下的好处总结如下:
\n\n类型安全:抛弃List、Map,使用List、Map给它们添加元素或者使用Iterator遍历时,编译期就可以给你检查出类型错误 \n方法参数和返回值加上了Type: 抛弃List、Map,使用List、Map \n不需要类型转换:List list=new ArrayList(); \n类型通配符“?”: 假设一个打印List中元素的方法printList,我们希望任何类型T的List都可以被打印 \n \n枚举类型 \n引入了枚举类型
\n自动装箱拆箱(自动类型包装和解包)autoboxing & unboxing \n简单的说是类型自动转换。自动装包:基本类型自动转为包装类(int ——Integer)自动拆包:包装类自动转为基本类型(Integer——int)
\n可变参数varargs(varargs number of arguments) \n参数类型相同时,把重载函数合并到一起了。如:
\npublic void test (object... objs) {\n for (Object obj:objs){\n System.out.println(obj);\n }\n}\n
\nAnnotations(重要) 它是java中的metadata(注释) \n注解在JAVA5中就引入了。这是非常重要的特性。现在注解的应用已经随处可见。不过JAVA5的注解还不成熟,没法自定义注解。
\n新的迭代语句 \nfor (int n:numbers){\n\n}\n
\n静态导入(import static ) \n导入静态对象,可以省略些代码。不过这个也不常用。
\nimport static java.lang.System.out;\npublic class HelloWorld {\n public static void main (String[] args) {\n out.print(\"Hello World!\" );\n }\n}\n
\n新的格式化方法java.util.Formatter) \nformatter.format(\"Remaining account balance: $%.2f\" , balance);\n
\n新的线程模型和并发库Thread Framework(重要) \n最主要的就是引入了java.util.concurrent包,这个都是需要重点掌握的。
\nHashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore
\nJava6 新特性总结 \nWeb Services \n优先支持编写 XML web service 客户端程序。你可以用过简单的annotaion将你的API发布成.NET交互的web services. Mustang 添加了新的解析和 XML 在 Java object-mapping APIs中, 之前只在Java EE平台实现或者Java Web Services Pack中提供.
\nScripting \n现在你可以在Java源代码中混入JavaScript了,这对开发原型很有有用,你也可以插入自己的脚本引擎。
\nJDBC4.0 \nJAVA6将联合绑定 Java DB (Apache Derby). JDBC 4.0 增加了许多特性例如支持XML作为SQL数据类型,更好的集成Binary Large OBjects (BLOBs) 和 Character Large OBjects (CLOBs) .
\nUI优化 \n\nGUI 开发者可以有更多的技巧来使用 SwingWorker utility ,以帮助GUI应用中的多线程。, JTable 分类和过滤,以及添加splash闪屏。 \nSwing拥有更好的 look-and-feel , LCD 文本呈现, 整体GUI性能的提升。Java应用程序可以和本地平台更好的集成,例如访问平台的系统托盘和开始菜单。Mustang将Java插件技术和Java Web Start引擎统一了起来。 \n \n监控管理增强 \n添加更多的诊断信息,绑定了不是很知名的 memory-heap 分析工具Jhat 来查看内核导出。
\n编译API \ncompiler API提供编程访问javac,可以实现进程内编译,动态产生Java代码
\n自定义注解 \nJava tool和framework 提供商可以定义自己的 annotations ,并且内核支持自定义annotation的插件和执行处理器
\n安全性 \nXML-数字签名(XML-DSIG) APIs 用于创建和操纵数字签名); 新的方法来访问本地平台的安全服务,例如本地Microsoft Windows for secure authentication and communicationnative 的Public Key Infrastructure (PKI) 和 cryptographic services, Java Generic Security Services (Java GSS) 和 Kerberos services for authentication, 以及访问 LDAP servers 来认证用户.
\nJava7 新特性总结 \nswitch中使用String \njava7以前在switch中只能使用number或enum,现在可以使用string了。
\n示例:
\nString s = \"a\" ;\nswitch (s) {\n case \"a\" :\n System.out.println(\"is a\" );\n break ;\n case \"b\" :\n System.out.println(\"is b\" );\n break ;\n default :\n System.out.println(\"is c\" );\n break ;\n}\n
\n异常处理 \n\nThrowable类增加addSuppressed方法和getSuppressed方法,支持原始异常中加入被抑制的异常。 \n异常抑制:在try和finally中同时抛出异常时,finally中抛出的异常会在异常栈中向上传递,而try中产生的原始异常会消失。 \n在Java7之前的版本,可以将原始异常保存,在finally中产生异常时抛出原始异常: \n \n\npublic void read (String filename) throws BaseException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException == null ) { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw new BaseException(readException); \n } \n } \n}\n\n\npublic void read (String filename) throws IOException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException != null ) { \n readException.addSuppressed(ex); \n } \n else { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw readException; \n } \n } \n} \n
\ntry-with-resources \njava7以前对某些资源的操作是需要手动关闭,如InputStream,Writes,Sockets,Sql等,需要在finally中进行关闭资源的操作,现在不需要使用finally来保证打开的流被正确关闭,现在是自动完成的,会自动释放资源,确保每一个资源在处理完成后都会关闭,就不需要我们代码去close();
\n\n在采用try-with-resources方式后,不需要再次声明流的关闭。 \n可以使用try-with-resources的资源有:任何实现了java.lang.AutoCloseable接口和java.io.Closeable接口的对象。为了支持这个行为,所有可关闭的类将被修改为可以实现一个Closable(可关闭的)接口。 \n \npublic interface Closeable extends AutoCloseable {}\npublic abstract class Reader implements Readable , Closeable {}\n
\n如果在try语句中写入了没有实现该接口的类,会提示:
\n\nThe resource type File does not implement java.lang.AutoCloseable
\n \n示例:
\n\nOutputStream fos = null ;\ntry {\n fos = new FileOutputStream(\"D:/file\" );\n} finally {\n fos.close();\n}\n\ntry (OutputStream fos = new FileOutputStream(\"D:/file\" );){\n \n}\n\npublic void copyFile (String fromPath, String toPath) throws IOException { \ntry ( InputStream input = new FileInputStream(fromPath); \n OutputStream output = new FileOutputStream(toPath) ) { \n byte [] buffer = new byte [8192 ]; \n int len = -1 ; \n while ( (len=input.read(buffer))!=-1 ) { \n output.write(buffer, 0 , len); \n } \n }\n} \n
\n捕获多个异常 \njava7以前在一个方法抛出多个异常时,只能一个个的catch,这样代码会有多个catch,显得很不友好,现在只需一个catch语句,多个异常类型用"|"隔开。\n示例:
\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException e) {\n e.printStackTrace();\n} catch (IllegalAccessException e) {\n e.printStackTrace();\n}\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException | IllegalAccessException e) {\n e.printStackTrace();\n}\n
\n泛型实例化类型自动推断 \n运用泛型实例化类型自动推断,对通用实例创建(diamond)的type引用进行了改进\n示例:
\n\nList<String> list = new ArrayList<String>();\n\nList<String> list = new ArrayList<>();\n
\n增加二进制表示 \nJava7前支持十进制(123)、八进制(0123)、十六进制(0X12AB)
\nJava7增加二进制表示(0B11110001、0b11110001)\n示例:
\nint binary = 0b0001_1001 ;\nSystem.out.println(\"binary is :\" +binary);\n binary is :25 \n
\n数字中可添加分隔符 \nJava7中支持在数字中间增加'_'作为分隔符,分隔长int以及long(也支持double,float),显示更直观,如(12_123_456)。
\n下划线只能在数字中间,编译时编译器自动删除数字中的下划线。
\n示例:
\nint intOne = 1_000_000 ;\nlong longOne = 1_000_000 ;\ndouble doubleOne = 1_000_000 ;\nfloat floatOne = 1_000_000 ;\n
\n变长参数方法的优化 \n参数类型相同时,把重载函数合并到一起了\n使用可变参数时,提升编译器的警告和错误信息
\npublic int sum (int ... args) { \n int result = 0 ; \n for (int value : args) { \n result += value; \n } \n return result; \n} \n
\n集合类的语法支持 \n\nList<String> list = new ArrayList<String>();\n list.add(\"item\" );\n String item = list.get(0 );\n\n Set<String> set = new HashSet<String>();\n set.add(\"item\" );\n Map<String, Integer> map = new HashMap<String, Integer>();\n map.put(\"key\" , 1 );\n int value = map.get(\"key\" );\n\nList<String> list = [\"item\" ];\n String item = list[0 ];\n\n Set<String> set = {\"item\" };\n\n Map<String, Integer> map = {\"key\" : 1 };\n int value = map[\"key\" ]; \n
\n自动资源管理 \nJava中某些资源是需要手动关闭的,如InputStream,Writes,Sockets,Sql classes等。这个新的语言特性允许try语句本身申请更多的资源,这些资源作用于try代码块,并自动关闭。
\n\nBufferedReader br = new BufferedReader(new FileReader(path));\ntry {\nreturn br.readLine();\n } finally {\n br.close();\n}\n\ntry (BufferedReader br = new BufferedReader(new FileReader(path)) {\n return br.readLine();\n}\n
\n新增一些取环境信息的工具方法 \nFile System.getJavaIoTempDir() \nFile System.getJavaHomeDir() \nFile System.getUserHomeDir() \nFile System.getUserDir() \n
\nJava8 新特性总结 \nJava8 新增了非常多的特性,我们主要讨论以下几个:
\n\nLambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 \n方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 \n默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。 \n新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。 \nStream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。 \nDate Time API − 加强对日期与时间的处理。 \nOptional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。 \nNashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。 \n \nLambda表达式和函数式接口 \nLambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
\nLambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> System.out.println( e ) );\n
\n在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( ( String e ) -> System.out.println( e ) );\n
\n如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> {\n System.out.print( e );\n System.out.print( e );\n} );\n
\nLambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:
\nString separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) );\n\nfinal String separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) ); \n
\nLambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );\n\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> {\n int result = e1.compareTo( e2 );\n return result;\n} );\n
\nLambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
\n@FunctionalInterface \npublic interface Functional {\n void method () ;\n}\n
\n不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
\n@FunctionalInterface \npublic interface FunctionalDefaultMethods {\n void method () ;\n\n default void defaultMethod () {\n System.out.print(\"defaultMethod\" );\n }\n static void staticMethod () {\n System.out.print(\"staticMethod\" );\n }\n}\n
\nLambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档 。
\n接口的默认方法和静态方法 \nJava 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
\n默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
\nprivate interface Defaulable {\n \n \n default String notRequired () { \n return \"Default implementation\" ; \n } \n}\n\nprivate static class DefaultableImpl implements Defaulable {\n}\n\nprivate static class OverridableImpl implements Defaulable {\n @Override \n public String notRequired () {\n return \"Overridden implementation\" ;\n }\n}\n
\nDefaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
\nJava 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
\nprivate interface DefaulableFactory {\n \n static Defaulable create ( Supplier< Defaulable > supplier ) {\n return supplier.get();\n }\n}\n
\n下面的代码片段整合了默认方法和静态方法的使用场景:
\npublic static void main ( String[] args ) {\n Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );\n System.out.println( defaulable.notRequired() );\n\n defaulable = DefaulableFactory.create( OverridableImpl::new );\n System.out.println( defaulable.notRequired() );\n}\n
\n这段代码的输出结果如下:
\n\nDefault implementation\nOverridden implementation
\n \n由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
\n尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档 。
\n方法引用 \n方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
\n西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。
\npublic static class Car {\n public static Car create ( final Supplier< Car > supplier ) {\n return supplier.get();\n }\n\n public static void collide ( final Car car ) {\n System.out.println( \"Collided \" + car.toString() );\n }\n\n public void follow ( final Car another ) {\n System.out.println( \"Following the \" + another.toString() );\n }\n\n public void repair () { \n System.out.println( \"Repaired \" + this .toString() );\n }\n}\n
\n第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。
\nfinal Car car = Car.create( Car::new );\nfinal List< Car > cars = Arrays.asList( car );\n
\n第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
\ncars.forEach( Car::collide );\n
\n第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
\ncars.forEach( Car::repair );\n
\n第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
\nfinal Car police = Car.create( Car::new );\ncars.forEach( police::follow );\n
\n运行上述例子,可以在控制台看到如下输出(Car实例可能不同):
\n\nCollided com.javacodegeeks.java8.method.references.MethodReferencesC a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nRepaired com.javacodegeeks.java8.method.references.MethodReferences C a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nFollowing the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n重复注解 \n自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
\n在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:
\npackage com.javacodegeeks.java8.repeatable.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Repeatable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\npublic class RepeatingAnnotations {\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n public @interface Filters {\n Filter[] value();\n }\n\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n @Repeatable ( Filters.class )\n public @interface Filter {\n String value () ;\n };\n\n @Filter ( \"filter1\" )\n @Filter ( \"filter2\" )\n public interface Filterable {\n }\n\n public static void main (String[] args) {\n for ( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {\n System.out.println( filter.value() );\n }\n }\n}\n
\n正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。
\n另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:
\n\nfilter1\nfilter2
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n更好的类型推断 \nJava 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。例子代码如下:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class Value < T > {\n public static < T > T defaultValue () {\n return null ;\n }\n\n public T getOrDefault ( T value, T defaultValue ) {\n return ( value != null ) ? value : defaultValue;\n }\n}\n
\n下列代码是Value类型的应用:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class TypeInference {\n public static void main (String[] args) {\n final Value< String > value = new Value<>();\n value.getOrDefault( \"22\" , Value.defaultValue() );\n }\n}\n
\n参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value.defaultValue()。
\n拓宽注解的应用场景 \nJava 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:
\npackage com.javacodegeeks.java8.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.ArrayList;\nimport java.util.Collection;\n\npublic class Annotations {\n @Retention ( RetentionPolicy.RUNTIME )\n @Target ( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )\n public @interface NonEmpty {\n }\n\n public static class Holder < @NonEmpty T > extends @NonEmpty Object {\n public void method () throws @NonEmpty Exception {\n }\n }\n\n @SuppressWarnings ( \"unused\" )\n public static void main (String[] args) {\n final Holder< String > holder = new @NonEmpty Holder< String >();\n @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();\n }\n}\n
\nElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。
\nJava编译器的新特性 \n为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer library。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。
\npackage com.javacodegeeks.java8.parameter.names;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\n\npublic class ParameterNames {\n public static void main (String[] args) throws Exception {\n Method method = ParameterNames.class.getMethod( \"main\" , String[].class );\n for ( final Parameter parameter: method.getParameters() ) {\n System.out.println( \"Parameter: \" + parameter.getName() );\n }\n }\n}\n
\n在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:
\n\nParameter: arg0\n如果带-parameters参数,则会输出如下结果(正确的结果):\nParameter: args\n如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:
\n \n<plugin> \n <groupId>org.apache.maven.plugins</groupId> \n <artifactId>maven-compiler-plugin</artifactId> \n <version>3.1</version> \n <configuration> \n <compilerArgument>-parameters</compilerArgument> \n <source>1.8</source> \n <target>1.8</target> \n </configuration> \n</plugin> \n
\nOptional \nJava应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。
\nOptional仅仅是一个容器:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。
\n接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:
\nOptional< String > fullName = Optional.ofNullable( null );\nSystem.out.println( \"Full Name is set? \" + fullName.isPresent() );\nSystem.out.println( \"Full Name: \" + fullName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( fullName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\n
\n如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。
\n上述代码的输出结果如下:
\n\nFull Name is set? false\nFull Name: [none]\nHey Stranger!
\n \n再看下另一个简单的例子:
\nOptional< String > firstName = Optional.of( \"Tom\" );\nSystem.out.println( \"First Name is set? \" + firstName.isPresent() );\nSystem.out.println( \"First Name: \" + firstName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( firstName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\nSystem.out.println();\n
\n如果想了解和学习更详细的内容,可以参考官方文档 。
\nStreams \n新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
\nSteam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:
\npublic class Streams {\n private enum Status {\n OPEN, CLOSED\n };\n\n private static final class Task {\n private final Status status;\n private final Integer points;\n\n Task( final Status status, final Integer points ) {\n this .status = status;\n this .points = points;\n }\n\n public Integer getPoints () {\n return points;\n }\n\n public Status getStatus () {\n return status;\n }\n\n @Override \n public String toString () {\n return String.format( \"[%s, %d]\" , status, points );\n }\n }\n}\n
\nTask类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:
\nfinal Collection< Task > tasks = Arrays.asList(\n new Task( Status.OPEN, 5 ),\n new Task( Status.OPEN, 13 ),\n new Task( Status.CLOSED, 8 ) \n);\n
\n首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。
\n\nfinal long totalPointsOfOpenTasks = tasks\n .stream()\n .filter( task -> task.getStatus() == Status.OPEN )\n .mapToInt( Task::getPoints )\n .sum();\n\nSystem.out.println( \"Total points: \" + totalPointsOfOpenTasks );\n
\n运行这个方法的控制台输出是:
\n\nTotal points: 18\n这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。
\n \n在学习下一个例子之前,还需要记住一些steams(点此更多细节 )的知识点。Steam之上的操作可分为中间操作和晚期操作。
\n中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。
\n晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。
\nsteam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:
\n\nfinal double totalPoints = tasks\n .stream()\n .parallel()\n .map( task -> task.getPoints() ) \n .reduce( 0 , Integer::sum );\n\nSystem.out.println( \"Total points (all tasks): \" + totalPoints );\n
\n这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:
\n\nTotal points(all tasks): 26.0
\n \n对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:
\n\nfinal Map< Status, List< Task > > map = tasks\n .stream()\n .collect( Collectors.groupingBy( Task::getStatus ) );\nSystem.out.println( map );\n
\n控制台的输出如下:
\n\n{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
\n \n最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:
\n\nfinal Collection< String > result = tasks\n .stream() \n .mapToInt( Task::getPoints ) \n .asLongStream() \n .mapToDouble( points -> points / totalPoints ) \n .boxed() \n .mapToLong( weigth -> ( long )( weigth * 100 ) ) \n .mapToObj( percentage -> percentage + \"%\" ) \n .collect( Collectors.toList() ); \n\nSystem.out.println( result );\n
\n控制台输出结果如下:
\n\n[19%, 50%, 30%]
\n \n最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:
\nfinal Path path = new File( filename ).toPath();\ntry ( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {\n lines.onClose( () -> System.out.println(\"Done!\" ) ).forEach( System.out::println );\n}\n
\nStream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。
\n点此更多细节
\nDate/Time API(JSR 310) \nJava 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。
\n因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。
\n我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。
\n\nfinal Clock clock = Clock.systemUTC();\nSystem.out.println( clock.instant() );\nSystem.out.println( clock.millis() );\n
\n这个例子的输出结果是:
\n\n2014-04-12T15:19:29.282Z\n1397315969360
\n \n第二,关注下LocalDate和LocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。
\n\nfinal LocalDate date = LocalDate.now();\nfinal LocalDate dateFromClock = LocalDate.now( clock );\n\nSystem.out.println( date );\nSystem.out.println( dateFromClock );\n\n\nfinal LocalTime time = LocalTime.now();\nfinal LocalTime timeFromClock = LocalTime.now( clock );\n\nSystem.out.println( time );\nSystem.out.println( timeFromClock );\n
\n上述例子的输出结果如下:
\n\n2014-04-12\n2014-04-12\n11:25:54.568\n15:25:54.568
\n \nLocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子:
\n\nfinal LocalDateTime datetime = LocalDateTime.now();\nfinal LocalDateTime datetimeFromClock = LocalDateTime.now( clock );\n\nSystem.out.println( datetime );\nSystem.out.println( datetimeFromClock );\n
\n上述这个例子的输出结果如下:
\n\n2014-04-12T11:37:52.309\n2014-04-12T15:37:52.309
\n \n如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:
\n\nfinal ZonedDateTime zonedDatetime = ZonedDateTime.now();\nfinal ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );\nfinal ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( \"America/Los_Angeles\" ) );\n\nSystem.out.println( zonedDatetime );\nSystem.out.println( zonedDatetimeFromClock );\nSystem.out.println( zonedDatetimeFromZone );\n
\n这个例子的输出结果是:
\n\n2014-04-12T11:47:01.017-04:00[America/New_York]\n2014-04-12T15:47:01.017Z\n2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
\n \n最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:
\n\nfinal LocalDateTime from = LocalDateTime.of( 2014 , Month.APRIL, 16 , 0 , 0 , 0 );\nfinal LocalDateTime to = LocalDateTime.of( 2015 , Month.APRIL, 16 , 23 , 59 , 59 );\n\nfinal Duration duration = Duration.between( from, to );\nSystem.out.println( \"Duration in days: \" + duration.toDays() );\nSystem.out.println( \"Duration in hours: \" + duration.toHours() );\n
\n这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:
\n\nDuration in days: 365\nDuration in hours: 8783
\n \n对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果想了解和学习更详细的内容,可以参考官方文档 。
\nNashorn JavaScript引擎 \nJava 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下:
\nScriptEngineManager manager = new ScriptEngineManager();\nScriptEngine engine = manager.getEngineByName( \"JavaScript\" );\n\nSystem.out.println( engine.getClass().getName() );\nSystem.out.println( \"Result:\" + engine.eval( \"function f() { return 1; }; f() + 1;\" ) );\n
\n这个代码的输出结果如下:
\n\njdk.nashorn.api.scripting.NashornScriptEngine\nResult: 2
\n \nBase64 \n对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
\npackage com.javacodegeeks.java8.base64;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\n\npublic class Base64s {\n public static void main (String[] args) {\n final String text = \"Base64 finally in Java 8!\" ;\n\n final String encoded = Base64\n .getEncoder()\n .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );\n System.out.println( encoded );\n\n final String decoded = new String( \n Base64.getDecoder().decode( encoded ),\n StandardCharsets.UTF_8 );\n System.out.println( decoded );\n }\n}\n
\n这个例子的输出结果如下:
\n\nQmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==\nBase64 finally in Java 8!
\n \n新的Base64API也支持URL和MINE的编码解码。\n(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
\n并行数组 \nJava8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:
\npackage com.javacodegeeks.java8.parallel.arrays;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class ParallelArrays {\n public static void main ( String[] args ) {\n long [] arrayOfLong = new long [ 20000 ];\n\n Arrays.parallelSetAll( arrayOfLong,\n index -> ThreadLocalRandom.current().nextInt( 1000000 ) );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n\n Arrays.parallelSort( arrayOfLong );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n }\n}\n
\n上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:
\n\nUnsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378\nSorted: 39 220 263 268 325 607 655 678 723 793
\n \n并发性 \n基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作
\nJava 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。
\n在java.util.concurrent.atomic包中也新增了不少工具类,列举如下:
\n\nDoubleAccumulator \nDoubleAdder \nLongAccumulator \nLongAdder \n \n新的Java工具 \nNashorn引擎:jjs \njjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:
\nfunction f ( ) {\n return 1 ;\n};\n\nprint( f() + 1 );\n
\n可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:
\n\n2
\n \n如果需要了解细节,可以参考官方文档 。
\n类依赖分析器:jdeps \njdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。
\n我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
\njdeps org.springframework.core-3.0 .5.RELEASE.jar\n
\n这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".
\norg.springframework.core-3.0.5.RELEASE.jar -> C:\\Program Files\\Java\\jdk1.8.0\\jre\\lib\\rt.jar\n org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.io\n -> java.lang\n -> java.lang.annotation\n -> java.lang.ref\n -> java.lang.reflect\n -> java.util\n -> java.util.concurrent\n -> org.apache.commons.logging not found\n -> org.springframework.asm not found\n -> org.springframework.asm.commons not found\n org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.lang\n -> java.lang.annotation\n -> java.lang.reflect\n -> java.util\n
\n如果需要了解细节,可以参考官方文档 。
\nJVM的新特性 \n使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
\nJava 9 新特性总结 \n\n模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。 \nREPL (JShell):交互式编程环境。 \nHTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。 \n改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。 \n多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。 \n集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。 \n私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。 \n进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。 \n改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。 \n改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。 \n改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。 \n改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。 \n改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。 \n多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。 \n改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。 \n轻量级的 JSON API:内置了一个轻量级的JSON API \n响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。 \n \nJava9 新特性之---目录结构 \n包含jdk8及以前的jdk版本,所有目录结构以及目录含义如图:\n \n \njdk9之后,目录结构发生变化如图:\n \n这个新特性只要了解下就可以了,这个目录结构是方便为了接下来新特性做保证
\n模块化 \n一个大型的项目,比如淘宝商城等,都会包含多个模块,比如订单模块,前台模块,后台管理模块,广告位模块,会员模块.....等等,各个模块之间会相互调用,不过这种情况下会很少,只针对特殊情况,如果一个项目有30个模块系统进行开发,但是只要某个单独模块运行时,都会带动所有的模块,这样对于jvm来说在内存和性能上会很低,所以,java9提供了这一个特性,某一个模块运行的时候,jvm只会启动和它有依赖的模块,并不会加载所有的模块到内存中,这样性能大大的提高了。写法上如下:
\n \n一个项目中的两个模块,模块之间通过module-info.java来关联,在IDEA编辑器右键创建package-info.java\n \n在这个两个模块java9Demo和java9Test中,java9demo编写一个实体类Person,在java9Test调用这样一个过程
\n这个是java9Demo 将 java9Test 模块需要的文件导出 exports 把它所在的包导出
\nmodule java9Demo{\n requires com.mdxl.layer_cj.entity;\n}\n
\n然后在java9Test模块中创建一个package-info.java,引入java9Demo模块导出包名
\nmodule java9Test{\n requires java9Demo;\n}\n
\n这样就可以直接在java9Test中引入Person实体类了,这只是一个简单的例子。exports 控制着那些包可以被模块访问,所以不被导出的包不能被其他模块访问
\nJShell工具 \n怎么理解,怎么用呢?这个只是针对于java9来说,相当于cmd工具,你可以和cmd一样,直接写方法等等,不过我认为只是适用于初学者做一些最简单的运算和写一些方法:\n在cmd中打开这个工具:
\n$ jshell\n| Welcome to JShell -- Version 9-ea\n| For an introduction type: /help intro\njshell>\n
\n查看 JShell 命令
\n输入 /help 可以查看 JShell相关的命令:
\njshell> /help\n| Type a Java language expression, statement, or declaration.\n| Or type one of the following commands:\n| /list [<name or id>|-all|-start]\n| list the source you have typed\n| /edit <name or id>\n| edit a source entry referenced by name or id\n| /drop <name or id>\n| delete a source entry referenced by name or id\n| /save [-all|-history|-start] <file>\n| Save snippet source to a file.\n| /open <file>\n| open a file as source input\n| /vars [<name or id>|-all|-start]\n| list the declared variables and their values\n| /methods [<name or id>|-all|-start]\n| list the declared methods and their signatures\n| /types [<name or id>|-all|-start]\n| list the declared types\n| /imports \n| list the imported items\n
\n执行 JShell 命令
\n/imports 命令用于查看已导入的包:
\njshell> /imports\n| import java.io.*\n| import java.math.*\n| import java.net.*\n| import java.nio.file.*\n| import java.util.*\n| import java.util.concurrent.*\n| import java.util.function.*\n| import java.util.prefs.*\n| import java.util.regex.*\n| import java.util.stream.*\njshell>\n
\n等等,我认为只适用于初学者学习java不用其他编辑工具就可以学习java
\nHTTP 2 客户端 \nJDK9之前提供HttpURLConnection API来实现Http访问功能,但是这个类基本很少使用,一般都会选择Apache的Http Client,此次在Java 9的版本中引入了一个新的package:java.net.http,里面提供了对Http访问很好的支持,不仅支持Http1.1而且还支持HTTP2(什么是HTTP2?请参见HTTP2的时代来了...),以及WebSocket,据说性能特别好。
\n \n注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。
\n改进 Javadoc \njavadoc 工具可以生成 Java 文档, Java 9 的 javadoc 的输出现在符合兼容 HTML5 标准。
\n//java 9 之前\nC:\\JAVA>javadoc -d C:/JAVA Tester.java\n//java 9 之后\nC:\\JAVA> javadoc -d C:/JAVA -html5 Tester.java\n
\n多版本兼容 jar 包 \n多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
\n通过 --release 参数指定编译版本。
\n具体的变化就是 META-INF 目录下 MANIFEST.MF 文件新增了一个属性:
\n\nMulti-Release: true
\n \n然后 META-INF 目录下还新增了一个 versions 目录,如果是要支持 java9,则在 versions 目录下有 9 的目录。
\nmultirelease.jar\n├── META-INF\n│ └── versions\n│ └── 9\n│ └── multirelease\n│ └── Helper.class\n├── multirelease\n ├── Helper.class\n └── Main.class\n
\n集合工厂方法 \nJava 9 List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
\n这些工厂方法可以以更简洁的方式来创建集合。
\n旧方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n public static void main (String []args) {\n Set<String> set = new HashSet<>();\n set.add(\"A\" );\n set.add(\"B\" );\n set.add(\"C\" );\n set = Collections.unmodifiableSet(set);\n System.out.println(set);\n List<String> list = new ArrayList<>();\n\n list.add(\"A\" );\n list.add(\"B\" );\n list.add(\"C\" );\n list = Collections.unmodifiableList(list);\n System.out.println(list);\n Map<String, String> map = new HashMap<>();\n\n map.put(\"A\" ,\"Apple\" );\n map.put(\"B\" ,\"Boy\" );\n map.put(\"C\" ,\"Cat\" );\n map = Collections.unmodifiableMap(map);\n System.out.println(map);\n }\n}\n
\n执行输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n
\n新方法创建集合:
\nJava 9 中,以下方法被添加到 List,Set 和 Map 接口以及它们的重载对象。
\nstatic <E> List<E> of (E e1, E e2, E e3) ;\nstatic <E> Set<E> of (E e1, E e2, E e3) ;\nstatic <K,V> Map<K,V> of (K k1, V v1, K k2, V v2, K k3, V v3) ;\nstatic <K,V> Map<K,V> ofEntries (Map.Entry<? extends K,? extends V>... entries) \n
\n\nList 和 Set 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口如果超过 10 个参数, 可以使用 ofEntries(...) 方法。 \n \n新方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.AbstractMap;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n\n public static void main (String []args) {\n Set<String> set = Set.of(\"A\" , \"B\" , \"C\" ); \n System.out.println(set);\n List<String> list = List.of(\"A\" , \"B\" , \"C\" );\n System.out.println(list);\n Map<String, String> map = Map.of(\"A\" ,\"Apple\" ,\"B\" ,\"Boy\" ,\"C\" ,\"Cat\" );\n System.out.println(map);\n \n Map<String, String> map1 = Map.ofEntries (\n new AbstractMap.SimpleEntry<>(\"A\" ,\"Apple\" ),\n new AbstractMap.SimpleEntry<>(\"B\" ,\"Boy\" ),\n new AbstractMap.SimpleEntry<>(\"C\" ,\"Cat\" ));\n System.out.println(map1);\n }\n}\n
\n输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n{A=Apple, B=Boy, C=Cat}\n
\n私有接口方法 \n在 Java 8之前,接口可以有常量变量和抽象方法。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\npublic interface Tests {\n \n String str='hello wrold' ;\n void show (T str) ;\n \n default void def () {\n System.out.print(\"default method\" );\n }\n static void sta () {\n System.out.print(\"static method\" );\n }\n \n private void pri () {\n System.out.print(\"private method\" );\n }\n private static void pri_sta () {\n System.out.print(\"private static method\" );\n }\n}\n
\n改进的进程 API \n在 Java 9 之前,Process API 仍然缺乏对使用本地进程的基本支持,例如获取进程的 PID 和所有者,进程的开始时间,进程使用了多少 CPU 时间,多少本地进程正在运行等。
\nJava 9 向 Process API 添加了一个名为 ProcessHandle 的接口来增强 java.lang.Process 类。
\nProcessHandle 接口的实例标识一个本地进程,它允许查询进程状态并管理进程。
\nProcessHandle 嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\nProcessHandle 接口中声明的 onExit() 方法可用于在某个进程终止时触发某些操作。
\nimport java.time.ZoneId;\nimport java.util.stream.Stream;\nimport java.util.stream.Collectors;\nimport java.io.IOException;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n ProcessBuilder pb = new ProcessBuilder(\"notepad.exe\" );\n String np = \"Not Present\" ;\n Process p = pb.start();\n ProcessHandle.Info info = p.info();\n System.out.printf(\"Process ID : %s%n\" , p.pid());\n System.out.printf(\"Command name : %s%n\" , info.command().orElse(np));\n System.out.printf(\"Command line : %s%n\" , info.commandLine().orElse(np));\n\n System.out.printf(\"Start time: %s%n\" ,\n info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())\n .toLocalDateTime().toString()).orElse(np));\n\n System.out.printf(\"Arguments : %s%n\" ,\n info.arguments().map(a -> Stream.of(a).collect(\n Collectors.joining(\" \" ))).orElse(np));\n\n System.out.printf(\"User : %s%n\" , info.user().orElse(np));\n }\n}\n
\n以上实例执行输出结果为:
\nProcess ID : 5800\nCommand name : C:\\Windows\\System32\\notepad.exe\nCommand line : Not Present\nStart time: 2017-11-04T21:35:03.626\nArguments : Not Present\nUser: administrator\n
\n改进的 Stream API \nJava 9 改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
\nJava 9 为 Stream 新增了几个方法:dropWhile、takeWhile、ofNullable,为 iterate 方法新增了一个重载方法。
\ntakeWhile 方法 语法 \ndefault Stream<T> takeWhile (Predicate<? super T> predicate) \n
\ntakeWhile() 方法使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 false。如果第一个值不满足断言条件,将返回一个空的 Stream。
\ntakeWhile() 方法在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素;在无序的 Stream 中,takeWhile 返回从开头开始的符合 Predicate 要求的元素的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).takeWhile(s->!s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 takeWhile 方法在碰到空字符串时停止循环输出,执行输出结果为:
\nabc\n
\ndropWhile 方法 语法: \ndefault Stream<T> dropWhile (Predicate<? super T> predicate) \n
\ndropWhile 方法和 takeWhile 作用相反的,使用一个断言作为参数,直到断言语句第一次返回 true 才返回给定 Stream 的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).dropWhile(s-> !s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 dropWhile 方法在碰到空字符串时开始循环输出,执行输出结果为:
\nef\n
\niterate 方法 语法: \nstatic <T> Stream<T> iterate (T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) \n
\n方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的 hasNext 的 predicate 返回 false 时,迭代停止。
\njava.util.stream.IntStream;\n\npublic class Tester {\n public static void main (String[] args) {\n IntStream.iterate(3 , x -> x < 10 , x -> x+ 3 ).forEach(System.out::println);\n }\n}\n
\n执行输出结果为:
\n3\n6\n9\n
\nofNullable 方法 语法: \nstatic <T> Stream<T> ofNullable (T t) \n
\nofNullable 方法可以预防 NullPointerExceptions 异常, 可以通过检查流来避免 null 值。
\n如果指定元素为非 null,则获取一个元素并生成单个元素流,元素为 null 则返回一个空流。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n long count = Stream.ofNullable(100 ).count();\n System.out.println(count);\n \n count = Stream.ofNullable(null ).count();\n System.out.println(count);\n }\n}\n
\n执行输出结果为:
\n1\n0\n
\n改进的 try-with-resources \ntry-with-resources 是 JDK 7 中一个新的异常处理机制,它能够很容易地关闭在 try-catch 语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources 语句确保了每个资源在语句结束时关闭。所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。
\ntry-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (BufferedReader br1 = br) {\n return br1.readLine();\n }\n }\n}\n
\n输出结果为:
\ntest\n
\n以上实例中我们需要在 try 语句块中声明资源 br1,然后才能使用它。\n在 Java 9 中,我们不需要声明资源 br1 就可以使用它,并得到相同的结果。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (br) {\n return br.readLine();\n }\n }\n}\n
\n执行输出结果为:
\ntest\n
\n在处理必须关闭的资源时,使用try-with-resources语句替代try-finally语句。 生成的代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources语句在编写必须关闭资源的代码时会更容易,也不会出错,而使用try-finally语句实际上是不可能的。
\n改进的 @Deprecated 注解 \n注解 @Deprecated 可以标记 Java API 状态,可以是以下几种:
\n使用它存在风险,可能导致错误\n可能在未来版本中不兼容\n可能在未来版本中删除\n一个更好和更高效的方案已经取代它。\nJava 9 中注解增加了两个新元素:since 和 forRemoval。
\n\nsince: 元素指定已注解的API元素已被弃用的版本。 \nforRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API。 \n \n钻石操作符的升级 \n钻石操作符是在 java 7 中引入的,可以让代码更易读,但它不能用于匿名的内部类。
\n在 java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性。
\n\nMap<String,String> map6=new HashMap<String,String>();\n\nMap<String,String> map6=new HashMap<>();\n\nMap<String,String> map6=new HashMap<>(){};\n
\n改进的 Optional 类 \nOptional 类在 Java 8 中引入,Optional 类的引入很好的解决空指针异常。。在 java 9 中, 添加了三个方法来改进它的功能:
\n\nstream() \nifPresentOrElse() \nor() \n \nstream() 方法 语法: \npublic Stream<T> stream () \n
\nstream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream(Stream.empty())。
\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class Tester {\npublic static void main (String[] args) {\n List<Optional<String>> list = Arrays.asList (\n Optional.empty(), \n Optional.of(\"A\" ), \n Optional.empty(), \n Optional.of(\"B\" ));\n\n \n \n \n List<String> filteredList = list.stream()\n .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())\n .collect(Collectors.toList());\n\n \n \n List<String> filteredListJava9 = list.stream()\n .flatMap(Optional::stream)\n .collect(Collectors.toList());\n\n System.out.println(filteredList);\n System.out.println(filteredListJava9);\n } \n}\n
\n执行输出结果为:
\n[A, B]\n[A, B]\n
\nifPresentOrElse() 方法 语法: \npublic void ifPresentOrElse (Consumer<? super T> action, Runnable emptyAction) \n
\nifPresentOrElse 方法的改进就是有了 else,接受两个参数 Consumer 和 Runnable。
\nifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()。
\nimport java.util.Optional;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<Integer> optional = Optional.of(1 );\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n\n optional = Optional.empty();\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n } \n}\n
\n执行输出结果为:
\nValue: 1\nNot Present.\n
\nor() 方法 语法: \npublic Optional<T> or (Supplier<? extends Optional<? extends T>> supplier) \n
\n如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<String> optional1 = Optional.of(\"Mahesh\" );\n Supplier<Optional<String>> supplierString = () -> Optional.of(\"Not Present\" );\n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x));\n optional1 = Optional.empty(); \n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x)); \n } \n}\n
\n执行输出结果为:
\nValue: Mahesh\nValue: Not Present\n
\n多分辨率图像 API \nJava 9 定义多分辨率图像 API,开发者可以很容易的操作和展示不同分辨率的图像了。
\n以下是多分辨率图像的主要操作方法:
\n\n\nImage getResolutionVariant(double destImageWidth, double destImageHeight) − 获取特定分辨率的图像变体-表示一张已知分辨率单位为DPI的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。。
\n \n\nList getResolutionVariants() − 返回可读的分辨率的图像变体列表。
\n \n \n改进的 CompletableFuture API \nJava 8 引入了 CompletableFuture 类,可能是 java.util.concurrent.Future 明确的完成版(设置了它的值和状态),也可能被用作java.util.concurrent.CompleteStage 。支持 future 完成时触发一些依赖的函数和动作。Java 9 引入了一些CompletableFuture 的改进:
\nJava 9 对 CompletableFuture 做了改进:
\n\n支持 delays 和 timeouts \n提升了对子类化的支持 \n新的工厂方法 \n \n支持 delays 和 timeouts \npublic CompletableFuture<T> completeOnTimeout (T value, long timeout, TimeUnit unit) \n
\n在 timeout(单位在 java.util.concurrent.Timeunits units 中,比如 MILLISECONDS )前以给定的 value 完成这个 CompletableFutrue。返回这个 CompletableFutrue。
\npublic CompletableFuture<T> orTimeout (long timeout, TimeUnit unit) \n
\n如果没有在给定的 timeout 内完成,就以 java.util.concurrent.TimeoutException 完成这个 CompletableFutrue,并返回这个 CompletableFutrue
\n增强了对子类化的支持 \n做了许多改进使得 CompletableFuture 可以被更简单的继承。比如,你也许想重写新的 public Executor defaultExecutor() 方法来代替默认的 executor。
\n另一个新的使子类化更容易的方法是:
\npublic <U> CompletableFuture<U> newIncompleteFuture () \n
\n新的工厂方法 \nJava 8引入了 <U> CompletableFuture<U> completedFuture(U value) 工厂方法来返回一个已经以给定 value 完成了的 CompletableFuture。Java 9以 一个新的 <U> CompletableFuture <U> failedFuture(Throwable ex) 来补充了这个方法,可以返回一个以给定异常完成的 CompletableFuture。\n\n除此以外,Java 9 引入了下面这对 stage-oriented 工厂方法,返回完成的或异常完成的 completion stages:\n\n* <U> CompletionStage<U> completedStage(U value): 返回一个新的以指定 value 完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n* <U> CompletionStage<U> failedStage(Throwable ex): 返回一个新的以指定异常完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n
\n",
+ "__html": "Java 新特性总结 \n总结的这些新特性,都是自己觉得在开发中实际用得上的。\n简单概括下就是:
\n\nJAVA1.3:普通的原始的JAVA,基本语法相信大家都见过了 \nJAVA1.4:assert关键字 \nJAVA5:枚举类型、泛型、自动拆装箱 \nJAVA6: @Override注解 \nJAVA7: <>符号、ARM支持、支持多catch \nJAVA8:Lamda表达式,类型注解等 \nJAVA9: 模块化、接口中的私有方法等 \nJAVA10: 局部变量推断、整个JDK代码仓库、统一的垃圾回收接口、并行垃圾回收器G1、线程局部管控 \n \nJava5 新特性总结 \n泛型 Generics \n引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。Parameterized Type作为参数和返回值,Generic是vararg、annotation、enumeration、collection的基石。
\n泛型可以带来如下的好处总结如下:
\n\n类型安全:抛弃List、Map,使用List、Map给它们添加元素或者使用Iterator遍历时,编译期就可以给你检查出类型错误 \n方法参数和返回值加上了Type: 抛弃List、Map,使用List、Map \n不需要类型转换:List list=new ArrayList(); \n类型通配符“?”: 假设一个打印List中元素的方法printList,我们希望任何类型T的List都可以被打印 \n \n枚举类型 \n引入了枚举类型
\n自动装箱拆箱(自动类型包装和解包)autoboxing & unboxing \n简单的说是类型自动转换。自动装包:基本类型自动转为包装类(int ——Integer)自动拆包:包装类自动转为基本类型(Integer——int)
\n可变参数varargs(varargs number of arguments) \n参数类型相同时,把重载函数合并到一起了。如:
\npublic void test (object... objs) {\n for (Object obj:objs){\n System.out.println(obj);\n }\n}\n
\nAnnotations(重要) 它是java中的metadata(注释) \n注解在JAVA5中就引入了。这是非常重要的特性。现在注解的应用已经随处可见。不过JAVA5的注解还不成熟,没法自定义注解。
\n新的迭代语句 \nfor (int n:numbers){\n\n}\n
\n静态导入(import static ) \n导入静态对象,可以省略些代码。不过这个也不常用。
\nimport static java.lang.System.out;\npublic class HelloWorld {\n public static void main (String[] args) {\n out.print(\"Hello World!\" );\n }\n}\n
\n新的格式化方法java.util.Formatter) \nformatter.format(\"Remaining account balance: $%.2f\" , balance);\n
\n新的线程模型和并发库Thread Framework(重要) \n最主要的就是引入了java.util.concurrent包,这个都是需要重点掌握的。
\nHashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore
\nJava6 新特性总结 \nWeb Services \n优先支持编写 XML web service 客户端程序。你可以用过简单的annotaion将你的API发布成.NET交互的web services. Mustang 添加了新的解析和 XML 在 Java object-mapping APIs中, 之前只在Java EE平台实现或者Java Web Services Pack中提供.
\nScripting \n现在你可以在Java源代码中混入JavaScript了,这对开发原型很有有用,你也可以插入自己的脚本引擎。
\nJDBC4.0 \nJAVA6将联合绑定 Java DB (Apache Derby). JDBC 4.0 增加了许多特性例如支持XML作为SQL数据类型,更好的集成Binary Large OBjects (BLOBs) 和 Character Large OBjects (CLOBs) .
\nUI优化 \n\nGUI 开发者可以有更多的技巧来使用 SwingWorker utility ,以帮助GUI应用中的多线程。, JTable 分类和过滤,以及添加splash闪屏。 \nSwing拥有更好的 look-and-feel , LCD 文本呈现, 整体GUI性能的提升。Java应用程序可以和本地平台更好的集成,例如访问平台的系统托盘和开始菜单。Mustang将Java插件技术和Java Web Start引擎统一了起来。 \n \n监控管理增强 \n添加更多的诊断信息,绑定了不是很知名的 memory-heap 分析工具Jhat 来查看内核导出。
\n编译API \ncompiler API提供编程访问javac,可以实现进程内编译,动态产生Java代码
\n自定义注解 \nJava tool和framework 提供商可以定义自己的 annotations ,并且内核支持自定义annotation的插件和执行处理器
\n安全性 \nXML-数字签名(XML-DSIG) APIs 用于创建和操纵数字签名); 新的方法来访问本地平台的安全服务,例如本地Microsoft Windows for secure authentication and communicationnative 的Public Key Infrastructure (PKI) 和 cryptographic services, Java Generic Security Services (Java GSS) 和 Kerberos services for authentication, 以及访问 LDAP servers 来认证用户.
\nJava7 新特性总结 \nswitch中使用String \njava7以前在switch中只能使用number或enum,现在可以使用string了。
\n示例:
\nString s = \"a\" ;\nswitch (s) {\n case \"a\" :\n System.out.println(\"is a\" );\n break ;\n case \"b\" :\n System.out.println(\"is b\" );\n break ;\n default :\n System.out.println(\"is c\" );\n break ;\n}\n
\n异常处理 \n\nThrowable类增加addSuppressed方法和getSuppressed方法,支持原始异常中加入被抑制的异常。 \n异常抑制:在try和finally中同时抛出异常时,finally中抛出的异常会在异常栈中向上传递,而try中产生的原始异常会消失。 \n在Java7之前的版本,可以将原始异常保存,在finally中产生异常时抛出原始异常: \n \n\npublic void read (String filename) throws BaseException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException == null ) { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw new BaseException(readException); \n } \n } \n}\n\n\npublic void read (String filename) throws IOException { \n FileInputStream input = null ; \n IOException readException = null ; \n try { \n input = new FileInputStream(filename); \n } catch (IOException ex) { \n readException = ex; \n } finally { \n if (input != null ) { \n try { \n input.close(); \n } catch (IOException ex) { \n if (readException != null ) { \n readException.addSuppressed(ex); \n } \n else { \n readException = ex; \n } \n } \n } \n if (readException != null ) { \n throw readException; \n } \n } \n} \n
\ntry-with-resources \njava7以前对某些资源的操作是需要手动关闭,如InputStream,Writes,Sockets,Sql等,需要在finally中进行关闭资源的操作,现在不需要使用finally来保证打开的流被正确关闭,现在是自动完成的,会自动释放资源,确保每一个资源在处理完成后都会关闭,就不需要我们代码去close();
\n\n在采用try-with-resources方式后,不需要再次声明流的关闭。 \n可以使用try-with-resources的资源有:任何实现了java.lang.AutoCloseable接口和java.io.Closeable接口的对象。为了支持这个行为,所有可关闭的类将被修改为可以实现一个Closable(可关闭的)接口。 \n \npublic interface Closeable extends AutoCloseable {}\npublic abstract class Reader implements Readable , Closeable {}\n
\n如果在try语句中写入了没有实现该接口的类,会提示:
\n\nThe resource type File does not implement java.lang.AutoCloseable
\n \n示例:
\n\nOutputStream fos = null ;\ntry {\n fos = new FileOutputStream(\"D:/file\" );\n} finally {\n fos.close();\n}\n\ntry (OutputStream fos = new FileOutputStream(\"D:/file\" );){\n \n}\n\npublic void copyFile (String fromPath, String toPath) throws IOException { \ntry ( InputStream input = new FileInputStream(fromPath); \n OutputStream output = new FileOutputStream(toPath) ) { \n byte [] buffer = new byte [8192 ]; \n int len = -1 ; \n while ( (len=input.read(buffer))!=-1 ) { \n output.write(buffer, 0 , len); \n } \n }\n} \n
\n捕获多个异常 \njava7以前在一个方法抛出多个异常时,只能一个个的catch,这样代码会有多个catch,显得很不友好,现在只需一个catch语句,多个异常类型用"|"隔开。\n示例:
\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException e) {\n e.printStackTrace();\n} catch (IllegalAccessException e) {\n e.printStackTrace();\n}\n\ntry {\n result = field.get(obj);\n} catch (IllegalArgumentException | IllegalAccessException e) {\n e.printStackTrace();\n}\n
\n泛型实例化类型自动推断 \n运用泛型实例化类型自动推断,对通用实例创建(diamond)的type引用进行了改进\n示例:
\n\nList<String> list = new ArrayList<String>();\n\nList<String> list = new ArrayList<>();\n
\n增加二进制表示 \nJava7前支持十进制(123)、八进制(0123)、十六进制(0X12AB)
\nJava7增加二进制表示(0B11110001、0b11110001)\n示例:
\nint binary = 0b0001_1001 ;\nSystem.out.println(\"binary is :\" +binary);\n binary is :25 \n
\n数字中可添加分隔符 \nJava7中支持在数字中间增加'_'作为分隔符,分隔长int以及long(也支持double,float),显示更直观,如(12_123_456)。
\n下划线只能在数字中间,编译时编译器自动删除数字中的下划线。
\n示例:
\nint intOne = 1_000_000 ;\nlong longOne = 1_000_000 ;\ndouble doubleOne = 1_000_000 ;\nfloat floatOne = 1_000_000 ;\n
\n变长参数方法的优化 \n参数类型相同时,把重载函数合并到一起了\n使用可变参数时,提升编译器的警告和错误信息
\npublic int sum (int ... args) { \n int result = 0 ; \n for (int value : args) { \n result += value; \n } \n return result; \n} \n
\n集合类的语法支持 \n\nList<String> list = new ArrayList<String>();\n list.add(\"item\" );\n String item = list.get(0 );\n\n Set<String> set = new HashSet<String>();\n set.add(\"item\" );\n Map<String, Integer> map = new HashMap<String, Integer>();\n map.put(\"key\" , 1 );\n int value = map.get(\"key\" );\n\nList<String> list = [\"item\" ];\n String item = list[0 ];\n\n Set<String> set = {\"item\" };\n\n Map<String, Integer> map = {\"key\" : 1 };\n int value = map[\"key\" ]; \n
\n自动资源管理 \nJava中某些资源是需要手动关闭的,如InputStream,Writes,Sockets,Sql classes等。这个新的语言特性允许try语句本身申请更多的资源,这些资源作用于try代码块,并自动关闭。
\n\nBufferedReader br = new BufferedReader(new FileReader(path));\ntry {\nreturn br.readLine();\n } finally {\n br.close();\n}\n\ntry (BufferedReader br = new BufferedReader(new FileReader(path)) {\n return br.readLine();\n}\n
\n新增一些取环境信息的工具方法 \nFile System.getJavaIoTempDir() \nFile System.getJavaHomeDir() \nFile System.getUserHomeDir() \nFile System.getUserDir() \n
\nJava8 新特性总结 \nJava8 新增了非常多的特性,我们主要讨论以下几个:
\n\nLambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 \n方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 \n默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。 \n新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。 \nStream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。 \nDate Time API − 加强对日期与时间的处理。 \nOptional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。 \nNashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。 \n \nLambda表达式和函数式接口 \nLambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。
\nLambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> System.out.println( e ) );\n
\n在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( ( String e ) -> System.out.println( e ) );\n
\n如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( e -> {\n System.out.print( e );\n System.out.print( e );\n} );\n
\nLambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:
\nString separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) );\n\nfinal String separator = \",\" ;\nArrays.asList( \"a\" , \"b\" , \"d\" ).forEach( \n ( String e ) -> System.out.print( e + separator ) ); \n
\nLambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );\n\nArrays.asList( \"a\" , \"b\" , \"d\" ).sort( ( e1, e2 ) -> {\n int result = e1.compareTo( e2 );\n return result;\n} );\n
\nLambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
\n@FunctionalInterface \npublic interface Functional {\n void method () ;\n}\n
\n不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
\n@FunctionalInterface \npublic interface FunctionalDefaultMethods {\n void method () ;\n\n default void defaultMethod () {\n System.out.print(\"defaultMethod\" );\n }\n static void staticMethod () {\n System.out.print(\"staticMethod\" );\n }\n}\n
\nLambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档 。
\n接口的默认方法和静态方法 \nJava 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
\n默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
\nprivate interface Defaulable {\n \n \n default String notRequired () { \n return \"Default implementation\" ; \n } \n}\n\nprivate static class DefaultableImpl implements Defaulable {\n}\n\nprivate static class OverridableImpl implements Defaulable {\n @Override \n public String notRequired () {\n return \"Overridden implementation\" ;\n }\n}\n
\nDefaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。
\nJava 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
\nprivate interface DefaulableFactory {\n \n static Defaulable create ( Supplier< Defaulable > supplier ) {\n return supplier.get();\n }\n}\n
\n下面的代码片段整合了默认方法和静态方法的使用场景:
\npublic static void main ( String[] args ) {\n Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );\n System.out.println( defaulable.notRequired() );\n\n defaulable = DefaulableFactory.create( OverridableImpl::new );\n System.out.println( defaulable.notRequired() );\n}\n
\n这段代码的输出结果如下:
\n\nDefault implementation\nOverridden implementation
\n \n由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
\n尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档 。
\n方法引用 \n方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
\n西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。
\npublic static class Car {\n public static Car create ( final Supplier< Car > supplier ) {\n return supplier.get();\n }\n\n public static void collide ( final Car car ) {\n System.out.println( \"Collided \" + car.toString() );\n }\n\n public void follow ( final Car another ) {\n System.out.println( \"Following the \" + another.toString() );\n }\n\n public void repair () { \n System.out.println( \"Repaired \" + this .toString() );\n }\n}\n
\n第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。
\nfinal Car car = Car.create( Car::new );\nfinal List< Car > cars = Arrays.asList( car );\n
\n第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
\ncars.forEach( Car::collide );\n
\n第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
\ncars.forEach( Car::repair );\n
\n第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
\nfinal Car police = Car.create( Car::new );\ncars.forEach( police::follow );\n
\n运行上述例子,可以在控制台看到如下输出(Car实例可能不同):
\n\nCollided com.javacodegeeks.java8.method.references.MethodReferencesC a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nRepaired com.javacodegeeks.java8.method.references.MethodReferences C a r @ 7 a 8 1 1 9 7 d R e p a i r e d c o m . j a v a c o d e g e e k s . j a v a 8 . m e t h o d . r e f e r e n c e s . M e t h o d R e f e r e n c e s Car@7a81197d\nFollowing the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n重复注解 \n自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
\n在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:
\npackage com.javacodegeeks.java8.repeatable.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Repeatable;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\npublic class RepeatingAnnotations {\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n public @interface Filters {\n Filter[] value();\n }\n\n @Target ( ElementType.TYPE )\n @Retention ( RetentionPolicy.RUNTIME )\n @Repeatable ( Filters.class )\n public @interface Filter {\n String value () ;\n };\n\n @Filter ( \"filter1\" )\n @Filter ( \"filter2\" )\n public interface Filterable {\n }\n\n public static void main (String[] args) {\n for ( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {\n System.out.println( filter.value() );\n }\n }\n}\n
\n正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。
\n另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:
\n\nfilter1\nfilter2
\n \n如果想了解和学习更详细的内容,可以参考官方文档 。
\n更好的类型推断 \nJava 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。例子代码如下:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class Value < T > {\n public static < T > T defaultValue () {\n return null ;\n }\n\n public T getOrDefault ( T value, T defaultValue ) {\n return ( value != null ) ? value : defaultValue;\n }\n}\n
\n下列代码是Value类型的应用:
\npackage com.javacodegeeks.java8.type.inference;\n\npublic class TypeInference {\n public static void main (String[] args) {\n final Value< String > value = new Value<>();\n value.getOrDefault( \"22\" , Value.defaultValue() );\n }\n}\n
\n参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value.defaultValue()。
\n拓宽注解的应用场景 \nJava 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:
\npackage com.javacodegeeks.java8.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.ArrayList;\nimport java.util.Collection;\n\npublic class Annotations {\n @Retention ( RetentionPolicy.RUNTIME )\n @Target ( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )\n public @interface NonEmpty {\n }\n\n public static class Holder < @NonEmpty T > extends @NonEmpty Object {\n public void method () throws @NonEmpty Exception {\n }\n }\n\n @SuppressWarnings ( \"unused\" )\n public static void main (String[] args) {\n final Holder< String > holder = new @NonEmpty Holder< String >();\n @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();\n }\n}\n
\nElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。
\nJava编译器的新特性 \n为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer library。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。
\npackage com.javacodegeeks.java8.parameter.names;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Parameter;\n\npublic class ParameterNames {\n public static void main (String[] args) throws Exception {\n Method method = ParameterNames.class.getMethod( \"main\" , String[].class );\n for ( final Parameter parameter: method.getParameters() ) {\n System.out.println( \"Parameter: \" + parameter.getName() );\n }\n }\n}\n
\n在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:
\n\nParameter: arg0\n如果带-parameters参数,则会输出如下结果(正确的结果):\nParameter: args\n如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:
\n \n<plugin> \n <groupId>org.apache.maven.plugins</groupId> \n <artifactId>maven-compiler-plugin</artifactId> \n <version>3.1</version> \n <configuration> \n <compilerArgument>-parameters</compilerArgument> \n <source>1.8</source> \n <target>1.8</target> \n </configuration> \n</plugin> \n
\nOptional \nJava应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。
\nOptional仅仅是一个容器:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。
\n接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:
\nOptional< String > fullName = Optional.ofNullable( null );\nSystem.out.println( \"Full Name is set? \" + fullName.isPresent() );\nSystem.out.println( \"Full Name: \" + fullName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( fullName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\n
\n如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。
\n上述代码的输出结果如下:
\n\nFull Name is set? false\nFull Name: [none]\nHey Stranger!
\n \n再看下另一个简单的例子:
\nOptional< String > firstName = Optional.of( \"Tom\" );\nSystem.out.println( \"First Name is set? \" + firstName.isPresent() );\nSystem.out.println( \"First Name: \" + firstName.orElseGet( () -> \"[none]\" ) );\nSystem.out.println( firstName.map( s -> \"Hey \" + s + \"!\" ).orElse( \"Hey Stranger!\" ) );\nSystem.out.println();\n
\n如果想了解和学习更详细的内容,可以参考官方文档 。
\nStreams \n新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
\nSteam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:
\npublic class Streams {\n private enum Status {\n OPEN, CLOSED\n };\n\n private static final class Task {\n private final Status status;\n private final Integer points;\n\n Task( final Status status, final Integer points ) {\n this .status = status;\n this .points = points;\n }\n\n public Integer getPoints () {\n return points;\n }\n\n public Status getStatus () {\n return status;\n }\n\n @Override \n public String toString () {\n return String.format( \"[%s, %d]\" , status, points );\n }\n }\n}\n
\nTask类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:
\nfinal Collection< Task > tasks = Arrays.asList(\n new Task( Status.OPEN, 5 ),\n new Task( Status.OPEN, 13 ),\n new Task( Status.CLOSED, 8 ) \n);\n
\n首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。
\n\nfinal long totalPointsOfOpenTasks = tasks\n .stream()\n .filter( task -> task.getStatus() == Status.OPEN )\n .mapToInt( Task::getPoints )\n .sum();\n\nSystem.out.println( \"Total points: \" + totalPointsOfOpenTasks );\n
\n运行这个方法的控制台输出是:
\n\nTotal points: 18\n这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。
\n \n在学习下一个例子之前,还需要记住一些steams(点此更多细节 )的知识点。Steam之上的操作可分为中间操作和晚期操作。
\n中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。
\n晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。
\nsteam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:
\n\nfinal double totalPoints = tasks\n .stream()\n .parallel()\n .map( task -> task.getPoints() ) \n .reduce( 0 , Integer::sum );\n\nSystem.out.println( \"Total points (all tasks): \" + totalPoints );\n
\n这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:
\n\nTotal points(all tasks): 26.0
\n \n对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:
\n\nfinal Map< Status, List< Task > > map = tasks\n .stream()\n .collect( Collectors.groupingBy( Task::getStatus ) );\nSystem.out.println( map );\n
\n控制台的输出如下:
\n\n{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
\n \n最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:
\n\nfinal Collection< String > result = tasks\n .stream() \n .mapToInt( Task::getPoints ) \n .asLongStream() \n .mapToDouble( points -> points / totalPoints ) \n .boxed() \n .mapToLong( weigth -> ( long )( weigth * 100 ) ) \n .mapToObj( percentage -> percentage + \"%\" ) \n .collect( Collectors.toList() ); \n\nSystem.out.println( result );\n
\n控制台输出结果如下:
\n\n[19%, 50%, 30%]
\n \n最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:
\nfinal Path path = new File( filename ).toPath();\ntry ( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {\n lines.onClose( () -> System.out.println(\"Done!\" ) ).forEach( System.out::println );\n}\n
\nStream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。
\n点此更多细节
\nDate/Time API(JSR 310) \nJava 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。
\n因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。
\n我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。
\n\nfinal Clock clock = Clock.systemUTC();\nSystem.out.println( clock.instant() );\nSystem.out.println( clock.millis() );\n
\n这个例子的输出结果是:
\n\n2014-04-12T15:19:29.282Z\n1397315969360
\n \n第二,关注下LocalDate和LocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。
\n\nfinal LocalDate date = LocalDate.now();\nfinal LocalDate dateFromClock = LocalDate.now( clock );\n\nSystem.out.println( date );\nSystem.out.println( dateFromClock );\n\n\nfinal LocalTime time = LocalTime.now();\nfinal LocalTime timeFromClock = LocalTime.now( clock );\n\nSystem.out.println( time );\nSystem.out.println( timeFromClock );\n
\n上述例子的输出结果如下:
\n\n2014-04-12\n2014-04-12\n11:25:54.568\n15:25:54.568
\n \nLocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子:
\n\nfinal LocalDateTime datetime = LocalDateTime.now();\nfinal LocalDateTime datetimeFromClock = LocalDateTime.now( clock );\n\nSystem.out.println( datetime );\nSystem.out.println( datetimeFromClock );\n
\n上述这个例子的输出结果如下:
\n\n2014-04-12T11:37:52.309\n2014-04-12T15:37:52.309
\n \n如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:
\n\nfinal ZonedDateTime zonedDatetime = ZonedDateTime.now();\nfinal ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );\nfinal ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( \"America/Los_Angeles\" ) );\n\nSystem.out.println( zonedDatetime );\nSystem.out.println( zonedDatetimeFromClock );\nSystem.out.println( zonedDatetimeFromZone );\n
\n这个例子的输出结果是:
\n\n2014-04-12T11:47:01.017-04:00[America/New_York]\n2014-04-12T15:47:01.017Z\n2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
\n \n最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:
\n\nfinal LocalDateTime from = LocalDateTime.of( 2014 , Month.APRIL, 16 , 0 , 0 , 0 );\nfinal LocalDateTime to = LocalDateTime.of( 2015 , Month.APRIL, 16 , 23 , 59 , 59 );\n\nfinal Duration duration = Duration.between( from, to );\nSystem.out.println( \"Duration in days: \" + duration.toDays() );\nSystem.out.println( \"Duration in hours: \" + duration.toHours() );\n
\n这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:
\n\nDuration in days: 365\nDuration in hours: 8783
\n \n对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果想了解和学习更详细的内容,可以参考官方文档 。
\nNashorn JavaScript引擎 \nJava 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下:
\nScriptEngineManager manager = new ScriptEngineManager();\nScriptEngine engine = manager.getEngineByName( \"JavaScript\" );\n\nSystem.out.println( engine.getClass().getName() );\nSystem.out.println( \"Result:\" + engine.eval( \"function f() { return 1; }; f() + 1;\" ) );\n
\n这个代码的输出结果如下:
\n\njdk.nashorn.api.scripting.NashornScriptEngine\nResult: 2
\n \nBase64 \n对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
\npackage com.javacodegeeks.java8.base64;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\n\npublic class Base64s {\n public static void main (String[] args) {\n final String text = \"Base64 finally in Java 8!\" ;\n\n final String encoded = Base64\n .getEncoder()\n .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );\n System.out.println( encoded );\n\n final String decoded = new String( \n Base64.getDecoder().decode( encoded ),\n StandardCharsets.UTF_8 );\n System.out.println( decoded );\n }\n}\n
\n这个例子的输出结果如下:
\n\nQmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==\nBase64 finally in Java 8!
\n \n新的Base64API也支持URL和MINE的编码解码。\n(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
\n并行数组 \nJava8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:
\npackage com.javacodegeeks.java8.parallel.arrays;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class ParallelArrays {\n public static void main ( String[] args ) {\n long [] arrayOfLong = new long [ 20000 ];\n\n Arrays.parallelSetAll( arrayOfLong,\n index -> ThreadLocalRandom.current().nextInt( 1000000 ) );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n\n Arrays.parallelSort( arrayOfLong );\n Arrays.stream( arrayOfLong ).limit( 10 ).forEach(\n i -> System.out.print( i + \" \" ) );\n System.out.println();\n }\n}\n
\n上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:
\n\nUnsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378\nSorted: 39 220 263 268 325 607 655 678 723 793
\n \n并发性 \n基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作
\nJava 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。
\n在java.util.concurrent.atomic包中也新增了不少工具类,列举如下:
\n\nDoubleAccumulator \nDoubleAdder \nLongAccumulator \nLongAdder \n \n新的Java工具 \nNashorn引擎:jjs \njjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:
\nfunction f ( ) {\n return 1 ;\n};\n\nprint( f() + 1 );\n
\n可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:
\n\n2
\n \n如果需要了解细节,可以参考官方文档 。
\n类依赖分析器:jdeps \njdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。
\n我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
\njdeps org.springframework.core-3.0 .5.RELEASE.jar\n
\n这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".
\norg.springframework.core-3.0.5.RELEASE.jar -> C:\\Program Files\\Java\\jdk1.8.0\\jre\\lib\\rt.jar\n org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.io\n -> java.lang\n -> java.lang.annotation\n -> java.lang.ref\n -> java.lang.reflect\n -> java.util\n -> java.util.concurrent\n -> org.apache.commons.logging not found\n -> org.springframework.asm not found\n -> org.springframework.asm.commons not found\n org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)\n -> java.lang\n -> java.lang.annotation\n -> java.lang.reflect\n -> java.util\n
\n如果需要了解细节,可以参考官方文档 。
\nJVM的新特性 \n使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
\nJava 9 新特性总结 \n\n模块系统:模块是一个包的容器,Java 9 最大的变化之一是引入了模块系统(Jigsaw 项目)。 \nREPL (JShell):交互式编程环境。 \nHTTP 2 客户端:HTTP/2标准是HTTP协议的最新版本,新的 HTTPClient API 支持 WebSocket 和 HTTP2 流以及服务器推送特性。 \n改进的 Javadoc:Javadoc 现在支持在 API 文档中的进行搜索。另外,Javadoc 的输出现在符合兼容 HTML5 标准。 \n多版本兼容 JAR 包:多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。 \n集合工厂方法:List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。 \n私有接口方法:在接口中使用private私有方法。我们可以使用 private 访问修饰符在接口中编写私有方法。 \n进程 API: 改进的 API 来控制和管理操作系统进程。引进 java.lang.ProcessHandle 及其嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。 \n改进的 Stream API:改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。 \n改进 try-with-resources:如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。 \n改进的弃用注解 @Deprecated:注解 @Deprecated 可以标记 Java API 状态,可以表示被标记的 API 将会被移除,或者已经破坏。 \n改进钻石操作符(Diamond Operator) :匿名类可以使用钻石操作符(Diamond Operator)。 \n改进 Optional 类:java.util.Optional 添加了很多新的有用方法,Optional 可以直接转为 stream。 \n多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。 \n改进的 CompletableFuture API : CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。 \n轻量级的 JSON API:内置了一个轻量级的JSON API \n响应式流(Reactive Streams) API: Java 9中引入了新的响应式流 API 来支持 Java 9 中的响应式编程。 \n \nJava9 新特性之---目录结构 \n包含jdk8及以前的jdk版本,所有目录结构以及目录含义如图:\n \n \njdk9之后,目录结构发生变化如图:\n \n这个新特性只要了解下就可以了,这个目录结构是方便为了接下来新特性做保证
\n模块化 \n一个大型的项目,比如淘宝商城等,都会包含多个模块,比如订单模块,前台模块,后台管理模块,广告位模块,会员模块.....等等,各个模块之间会相互调用,不过这种情况下会很少,只针对特殊情况,如果一个项目有30个模块系统进行开发,但是只要某个单独模块运行时,都会带动所有的模块,这样对于jvm来说在内存和性能上会很低,所以,java9提供了这一个特性,某一个模块运行的时候,jvm只会启动和它有依赖的模块,并不会加载所有的模块到内存中,这样性能大大的提高了。写法上如下:
\n \n一个项目中的两个模块,模块之间通过module-info.java来关联,在IDEA编辑器右键创建package-info.java\n \n在这个两个模块java9Demo和java9Test中,java9demo编写一个实体类Person,在java9Test调用这样一个过程
\n这个是java9Demo 将 java9Test 模块需要的文件导出 exports 把它所在的包导出
\nmodule java9Demo{\n requires com.mdxl.layer_cj.entity;\n}\n
\n然后在java9Test模块中创建一个package-info.java,引入java9Demo模块导出包名
\nmodule java9Test{\n requires java9Demo;\n}\n
\n这样就可以直接在java9Test中引入Person实体类了,这只是一个简单的例子。exports 控制着那些包可以被模块访问,所以不被导出的包不能被其他模块访问
\nJShell工具 \n怎么理解,怎么用呢?这个只是针对于java9来说,相当于cmd工具,你可以和cmd一样,直接写方法等等,不过我认为只是适用于初学者做一些最简单的运算和写一些方法:\n在cmd中打开这个工具:
\n$ jshell\n| Welcome to JShell -- Version 9-ea\n| For an introduction type: /help intro\njshell>\n
\n查看 JShell 命令
\n输入 /help 可以查看 JShell相关的命令:
\njshell> /help\n| Type a Java language expression, statement, or declaration.\n| Or type one of the following commands:\n| /list [<name or id>|-all|-start]\n| list the source you have typed\n| /edit <name or id>\n| edit a source entry referenced by name or id\n| /drop <name or id>\n| delete a source entry referenced by name or id\n| /save [-all|-history|-start] <file>\n| Save snippet source to a file.\n| /open <file>\n| open a file as source input\n| /vars [<name or id>|-all|-start]\n| list the declared variables and their values\n| /methods [<name or id>|-all|-start]\n| list the declared methods and their signatures\n| /types [<name or id>|-all|-start]\n| list the declared types\n| /imports \n| list the imported items\n
\n执行 JShell 命令
\n/imports 命令用于查看已导入的包:
\njshell> /imports\n| import java.io.*\n| import java.math.*\n| import java.net.*\n| import java.nio.file.*\n| import java.util.*\n| import java.util.concurrent.*\n| import java.util.function.*\n| import java.util.prefs.*\n| import java.util.regex.*\n| import java.util.stream.*\njshell>\n
\n等等,我认为只适用于初学者学习java不用其他编辑工具就可以学习java
\nHTTP 2 客户端 \nJDK9之前提供HttpURLConnection API来实现Http访问功能,但是这个类基本很少使用,一般都会选择Apache的Http Client,此次在Java 9的版本中引入了一个新的package:java.net.http,里面提供了对Http访问很好的支持,不仅支持Http1.1而且还支持HTTP2(什么是HTTP2?请参见HTTP2的时代来了...),以及WebSocket,据说性能特别好。
\n \n注意:新的 HttpClient API 在 Java 9 中以所谓的孵化器模块交付。也就是说,这套 API 不能保证 100% 完成。
\n改进 Javadoc \njavadoc 工具可以生成 Java 文档, Java 9 的 javadoc 的输出现在符合兼容 HTML5 标准。
\n//java 9 之前\nC:\\JAVA>javadoc -d C:/JAVA Tester.java\n//java 9 之后\nC:\\JAVA> javadoc -d C:/JAVA -html5 Tester.java\n
\n多版本兼容 jar 包 \n多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本。
\n通过 --release 参数指定编译版本。
\n具体的变化就是 META-INF 目录下 MANIFEST.MF 文件新增了一个属性:
\n\nMulti-Release: true
\n \n然后 META-INF 目录下还新增了一个 versions 目录,如果是要支持 java9,则在 versions 目录下有 9 的目录。
\nmultirelease.jar\n├── META-INF\n│ └── versions\n│ └── 9\n│ └── multirelease\n│ └── Helper.class\n├── multirelease\n ├── Helper.class\n └── Main.class\n
\n集合工厂方法 \nJava 9 List,Set 和 Map 接口中,新的静态工厂方法可以创建这些集合的不可变实例。
\n这些工厂方法可以以更简洁的方式来创建集合。
\n旧方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n public static void main (String []args) {\n Set<String> set = new HashSet<>();\n set.add(\"A\" );\n set.add(\"B\" );\n set.add(\"C\" );\n set = Collections.unmodifiableSet(set);\n System.out.println(set);\n List<String> list = new ArrayList<>();\n\n list.add(\"A\" );\n list.add(\"B\" );\n list.add(\"C\" );\n list = Collections.unmodifiableList(list);\n System.out.println(list);\n Map<String, String> map = new HashMap<>();\n\n map.put(\"A\" ,\"Apple\" );\n map.put(\"B\" ,\"Boy\" );\n map.put(\"C\" ,\"Cat\" );\n map = Collections.unmodifiableMap(map);\n System.out.println(map);\n }\n}\n
\n执行输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n
\n新方法创建集合:
\nJava 9 中,以下方法被添加到 List,Set 和 Map 接口以及它们的重载对象。
\nstatic <E> List<E> of (E e1, E e2, E e3) ;\nstatic <E> Set<E> of (E e1, E e2, E e3) ;\nstatic <K,V> Map<K,V> of (K k1, V v1, K k2, V v2, K k3, V v3) ;\nstatic <K,V> Map<K,V> ofEntries (Map.Entry<? extends K,? extends V>... entries) \n
\n\nList 和 Set 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口, of(...) 方法重载了 0 ~ 10 个参数的不同方法 。 \nMap 接口如果超过 10 个参数, 可以使用 ofEntries(...) 方法。 \n \n新方法创建集合:
\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.AbstractMap;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class Tester {\n\n public static void main (String []args) {\n Set<String> set = Set.of(\"A\" , \"B\" , \"C\" ); \n System.out.println(set);\n List<String> list = List.of(\"A\" , \"B\" , \"C\" );\n System.out.println(list);\n Map<String, String> map = Map.of(\"A\" ,\"Apple\" ,\"B\" ,\"Boy\" ,\"C\" ,\"Cat\" );\n System.out.println(map);\n \n Map<String, String> map1 = Map.ofEntries (\n new AbstractMap.SimpleEntry<>(\"A\" ,\"Apple\" ),\n new AbstractMap.SimpleEntry<>(\"B\" ,\"Boy\" ),\n new AbstractMap.SimpleEntry<>(\"C\" ,\"Cat\" ));\n System.out.println(map1);\n }\n}\n
\n输出结果为:
\n[A, B, C]\n[A, B, C]\n{A=Apple, B=Boy, C=Cat}\n{A=Apple, B=Boy, C=Cat}\n
\n私有接口方法 \n在 Java 8之前,接口可以有常量变量和抽象方法。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\npublic interface Tests {\n \n String str='hello wrold' ;\n void show (T str) ;\n \n default void def () {\n System.out.print(\"default method\" );\n }\n static void sta () {\n System.out.print(\"static method\" );\n }\n \n private void pri () {\n System.out.print(\"private method\" );\n }\n private static void pri_sta () {\n System.out.print(\"private static method\" );\n }\n}\n
\n改进的进程 API \n在 Java 9 之前,Process API 仍然缺乏对使用本地进程的基本支持,例如获取进程的 PID 和所有者,进程的开始时间,进程使用了多少 CPU 时间,多少本地进程正在运行等。
\nJava 9 向 Process API 添加了一个名为 ProcessHandle 的接口来增强 java.lang.Process 类。
\nProcessHandle 接口的实例标识一个本地进程,它允许查询进程状态并管理进程。
\nProcessHandle 嵌套接口 Info 来让开发者逃离时常因为要获取一个本地进程的 PID 而不得不使用本地代码的窘境。
\n我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
\nProcessHandle 接口中声明的 onExit() 方法可用于在某个进程终止时触发某些操作。
\nimport java.time.ZoneId;\nimport java.util.stream.Stream;\nimport java.util.stream.Collectors;\nimport java.io.IOException;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n ProcessBuilder pb = new ProcessBuilder(\"notepad.exe\" );\n String np = \"Not Present\" ;\n Process p = pb.start();\n ProcessHandle.Info info = p.info();\n System.out.printf(\"Process ID : %s%n\" , p.pid());\n System.out.printf(\"Command name : %s%n\" , info.command().orElse(np));\n System.out.printf(\"Command line : %s%n\" , info.commandLine().orElse(np));\n\n System.out.printf(\"Start time: %s%n\" ,\n info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())\n .toLocalDateTime().toString()).orElse(np));\n\n System.out.printf(\"Arguments : %s%n\" ,\n info.arguments().map(a -> Stream.of(a).collect(\n Collectors.joining(\" \" ))).orElse(np));\n\n System.out.printf(\"User : %s%n\" , info.user().orElse(np));\n }\n}\n
\n以上实例执行输出结果为:
\nProcess ID : 5800\nCommand name : C:\\Windows\\System32\\notepad.exe\nCommand line : Not Present\nStart time: 2017-11-04T21:35:03.626\nArguments : Not Present\nUser: administrator\n
\n改进的 Stream API \nJava 9 改进的 Stream API 添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。
\nJava 9 为 Stream 新增了几个方法:dropWhile、takeWhile、ofNullable,为 iterate 方法新增了一个重载方法。
\ntakeWhile 方法 语法 \ndefault Stream<T> takeWhile (Predicate<? super T> predicate) \n
\ntakeWhile() 方法使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 false。如果第一个值不满足断言条件,将返回一个空的 Stream。
\ntakeWhile() 方法在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素;在无序的 Stream 中,takeWhile 返回从开头开始的符合 Predicate 要求的元素的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).takeWhile(s->!s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 takeWhile 方法在碰到空字符串时停止循环输出,执行输出结果为:
\nabc\n
\ndropWhile 方法 语法: \ndefault Stream<T> dropWhile (Predicate<? super T> predicate) \n
\ndropWhile 方法和 takeWhile 作用相反的,使用一个断言作为参数,直到断言语句第一次返回 true 才返回给定 Stream 的子集。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n Stream.of(\"a\" ,\"b\" ,\"c\" ,\"\" ,\"e\" ,\"f\" ).dropWhile(s-> !s.isEmpty())\n .forEach(System.out::print);\n }\n}\n
\n以上实例 dropWhile 方法在碰到空字符串时开始循环输出,执行输出结果为:
\nef\n
\niterate 方法 语法: \nstatic <T> Stream<T> iterate (T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) \n
\n方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的 hasNext 的 predicate 返回 false 时,迭代停止。
\njava.util.stream.IntStream;\n\npublic class Tester {\n public static void main (String[] args) {\n IntStream.iterate(3 , x -> x < 10 , x -> x+ 3 ).forEach(System.out::println);\n }\n}\n
\n执行输出结果为:
\n3\n6\n9\n
\nofNullable 方法 语法: \nstatic <T> Stream<T> ofNullable (T t) \n
\nofNullable 方法可以预防 NullPointerExceptions 异常, 可以通过检查流来避免 null 值。
\n如果指定元素为非 null,则获取一个元素并生成单个元素流,元素为 null 则返回一个空流。
\nimport java.util.stream.Stream;\n\npublic class Tester {\n public static void main (String[] args) {\n long count = Stream.ofNullable(100 ).count();\n System.out.println(count);\n \n count = Stream.ofNullable(null ).count();\n System.out.println(count);\n }\n}\n
\n执行输出结果为:
\n1\n0\n
\n改进的 try-with-resources \ntry-with-resources 是 JDK 7 中一个新的异常处理机制,它能够很容易地关闭在 try-catch 语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources 语句确保了每个资源在语句结束时关闭。所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。
\ntry-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (BufferedReader br1 = br) {\n return br1.readLine();\n }\n }\n}\n
\n输出结果为:
\ntest\n
\n以上实例中我们需要在 try 语句块中声明资源 br1,然后才能使用它。\n在 Java 9 中,我们不需要声明资源 br1 就可以使用它,并得到相同的结果。
\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\n\npublic class Tester {\n public static void main (String[] args) throws IOException {\n System.out.println(readData(\"test\" ));\n }\n static String readData (String message) throws IOException {\n Reader inputString = new StringReader(message);\n BufferedReader br = new BufferedReader(inputString);\n try (br) {\n return br.readLine();\n }\n }\n}\n
\n执行输出结果为:
\ntest\n
\n在处理必须关闭的资源时,使用try-with-resources语句替代try-finally语句。 生成的代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources语句在编写必须关闭资源的代码时会更容易,也不会出错,而使用try-finally语句实际上是不可能的。
\n改进的 @Deprecated 注解 \n注解 @Deprecated 可以标记 Java API 状态,可以是以下几种:
\n使用它存在风险,可能导致错误\n可能在未来版本中不兼容\n可能在未来版本中删除\n一个更好和更高效的方案已经取代它。\nJava 9 中注解增加了两个新元素:since 和 forRemoval。
\n\nsince: 元素指定已注解的API元素已被弃用的版本。 \nforRemoval: 元素表示注解的 API 元素在将来的版本中被删除,应该迁移 API。 \n \n钻石操作符的升级 \n钻石操作符是在 java 7 中引入的,可以让代码更易读,但它不能用于匿名的内部类。
\n在 java 9 中, 它可以与匿名的内部类一起使用,从而提高代码的可读性。
\n\nMap<String,String> map6=new HashMap<String,String>();\n\nMap<String,String> map6=new HashMap<>();\n\nMap<String,String> map6=new HashMap<>(){};\n
\n改进的 Optional 类 \nOptional 类在 Java 8 中引入,Optional 类的引入很好的解决空指针异常。。在 java 9 中, 添加了三个方法来改进它的功能:
\n\nstream() \nifPresentOrElse() \nor() \n \nstream() 方法 语法: \npublic Stream<T> stream () \n
\nstream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream(Stream.empty())。
\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class Tester {\npublic static void main (String[] args) {\n List<Optional<String>> list = Arrays.asList (\n Optional.empty(), \n Optional.of(\"A\" ), \n Optional.empty(), \n Optional.of(\"B\" ));\n\n \n \n \n List<String> filteredList = list.stream()\n .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())\n .collect(Collectors.toList());\n\n \n \n List<String> filteredListJava9 = list.stream()\n .flatMap(Optional::stream)\n .collect(Collectors.toList());\n\n System.out.println(filteredList);\n System.out.println(filteredListJava9);\n } \n}\n
\n执行输出结果为:
\n[A, B]\n[A, B]\n
\nifPresentOrElse() 方法 语法: \npublic void ifPresentOrElse (Consumer<? super T> action, Runnable emptyAction) \n
\nifPresentOrElse 方法的改进就是有了 else,接受两个参数 Consumer 和 Runnable。
\nifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()。
\nimport java.util.Optional;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<Integer> optional = Optional.of(1 );\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n\n optional = Optional.empty();\n\n optional.ifPresentOrElse( x -> System.out.println(\"Value: \" + x),() -> \n System.out.println(\"Not Present.\" ));\n } \n}\n
\n执行输出结果为:
\nValue: 1\nNot Present.\n
\nor() 方法 语法: \npublic Optional<T> or (Supplier<? extends Optional<? extends T>> supplier) \n
\n如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\npublic class Tester {\n public static void main (String[] args) {\n Optional<String> optional1 = Optional.of(\"Mahesh\" );\n Supplier<Optional<String>> supplierString = () -> Optional.of(\"Not Present\" );\n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x));\n optional1 = Optional.empty(); \n optional1 = optional1.or( supplierString);\n optional1.ifPresent( x -> System.out.println(\"Value: \" + x)); \n } \n}\n
\n执行输出结果为:
\nValue: Mahesh\nValue: Not Present\n
\n多分辨率图像 API \nJava 9 定义多分辨率图像 API,开发者可以很容易的操作和展示不同分辨率的图像了。
\n以下是多分辨率图像的主要操作方法:
\n\n\nImage getResolutionVariant(double destImageWidth, double destImageHeight) − 获取特定分辨率的图像变体-表示一张已知分辨率单位为DPI的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。。
\n \n\nList getResolutionVariants() − 返回可读的分辨率的图像变体列表。
\n \n \n改进的 CompletableFuture API \nJava 8 引入了 CompletableFuture 类,可能是 java.util.concurrent.Future 明确的完成版(设置了它的值和状态),也可能被用作java.util.concurrent.CompleteStage 。支持 future 完成时触发一些依赖的函数和动作。Java 9 引入了一些CompletableFuture 的改进:
\nJava 9 对 CompletableFuture 做了改进:
\n\n支持 delays 和 timeouts \n提升了对子类化的支持 \n新的工厂方法 \n \n支持 delays 和 timeouts \npublic CompletableFuture<T> completeOnTimeout (T value, long timeout, TimeUnit unit) \n
\n在 timeout(单位在 java.util.concurrent.Timeunits units 中,比如 MILLISECONDS )前以给定的 value 完成这个 CompletableFutrue。返回这个 CompletableFutrue。
\npublic CompletableFuture<T> orTimeout (long timeout, TimeUnit unit) \n
\n如果没有在给定的 timeout 内完成,就以 java.util.concurrent.TimeoutException 完成这个 CompletableFutrue,并返回这个 CompletableFutrue
\n增强了对子类化的支持 \n做了许多改进使得 CompletableFuture 可以被更简单的继承。比如,你也许想重写新的 public Executor defaultExecutor() 方法来代替默认的 executor。
\n另一个新的使子类化更容易的方法是:
\npublic <U> CompletableFuture<U> newIncompleteFuture () \n
\n新的工厂方法 \nJava 8引入了 <U> CompletableFuture<U> completedFuture(U value) 工厂方法来返回一个已经以给定 value 完成了的 CompletableFuture。Java 9以 一个新的 <U> CompletableFuture <U> failedFuture(Throwable ex) 来补充了这个方法,可以返回一个以给定异常完成的 CompletableFuture。\n\n除此以外,Java 9 引入了下面这对 stage-oriented 工厂方法,返回完成的或异常完成的 completion stages:\n\n* <U> CompletionStage<U> completedStage(U value): 返回一个新的以指定 value 完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n* <U> CompletionStage<U> failedStage(Throwable ex): 返回一个新的以指定异常完成的CompletionStage ,并且只支持 CompletionStage 里的接口。\n
\nJava 10 新特性 \n\n局部变量推断 \n整个JDK代码仓库 \n统一的垃圾回收接口 \n并行垃圾回收器G1 \n线程局部管控 \n \n局部变量推断 \n它向 Java 中引入在其他语言中很常见的 var ,比如 JavaScript 。只要编译器可以推断此种类型,你不再需要专门声明一个局部变量的类型。
\n开发者将能够声明变量而不必指定关联的类型。比如:
\nList <String> list = new ArrayList <String>();\nStream <String> stream = getStream();\n
\n它可以简化为:
\nvar list = new ArrayList();\nvar stream = getStream();\n
\n局部变量类型推断将引入“ var ”关键字的使用,而不是要求明确指定变量的类型,我们俗称“语法糖”。
\n这就消除了我们之前必须执行的 ArrayList 类型定义的重复。
\n其实我们在JDK7,我们需要:
\nList <String> list = new ArrayList <String>();\n
\n但是在JDK8,我们只需要:
\nList <String> list = new ArrayList <>();\n
\n所以这是一个逐步的升级。也是人性化的表现与提升。
\n有趣的是,需要注意 var 不能成为一个关键字,而是一个保留字。这意味着你仍然可以使用 var 作为一个变量,方法或包名,但是现在(尽管我确定你绝不会)你不能再有一个类被调用。
\n局部变量类型推荐仅限于如下使用场景:
\n\n局部变量初始化 \nfor循环内部索引变量 \n传统的for循环声明变量\nJava官方表示,它不能用于以下几个地方: \n方法参数 \n构造函数参数 \n方法返回类型 \n字段 \n捕获表达式(或任何其他类型的变量声明) \n \n注意:
\nJava的var和JavaScript的完全不同,不要这样去类比。Java的var是用于局部类型推断的,而不是JS那样的动态类型,所以下面这个样子是不行的:
\nvar a = 10 ;\na = \"abc\" ; \n
\n其次,这个var只能用于局部变量声明,在其他地方使用都是错误的。
\nclass C {\n public var a = 10 ; \n public var f () { \n return 10 ;\n }\n}\n
\n整合 JDK 代码仓库 \n为了简化开发流程,Java 10 中会将多个代码库合并到一个代码仓库中。
\n在已发布的 Java 版本中,JDK 的整套代码根据不同功能已被分别存储在多个 Mercurial 存储库,这八个 Mercurial 存储库分别是:root、corba、hotspot、jaxp、jaxws、jdk、langtools、nashorn。
\n虽然以上八个存储库之间相互独立以保持各组件代码清晰分离,但同时管理这些存储库存在许多缺点,并且无法进行相关联源代码的管理操作。其中最重要的一点是,涉及多个存储库的变更集无法进行原子提交 (atomic commit)。例如,如果一个 bug 修复时需要对独立存储两个不同代码库的代码进行更改,那么必须创建两个提交:每个存储库中各一个。这种不连续性很容易降低项目和源代码管理工具的可跟踪性和加大复杂性。特别是,不可能跨越相互依赖的变更集的存储库执行原子提交这种多次跨仓库的变化是常见现象。
\n为了解决这个问题,JDK 10 中将所有现有存储库合并到一个 Mercurial 存储库中。这种合并的一次生效应,单一的 Mercurial 存储库比现有的八个存储库要更容易地被镜像(作为一个 Git 存储库),并且使得跨越相互依赖的变更集的存储库运行原子提交成为可能,从而简化开发和管理过程。虽然在整合过程中,外部开发人员有一些阻力,但是 JDK 开发团队已经使这一更改成为 JDK 10 的一部分。
\n统一的垃圾回收接口 \n在当前的 Java 结构中,组成垃圾回收器(GC)实现的组件分散在代码库的各个部分。尽管这些惯例对于使用 GC 计划的 JDK 开发者来说比较熟悉,但对新的开发人员来说,对于在哪里查找特定 GC 的源代码,或者实现一个新的垃圾收集器常常会感到困惑。更重要的是,随着 Java modules 的出现,我们希望在构建过程中排除不需要的 GC,但是当前 GC 接口的横向结构会给排除、定位问题带来困难。
\n为解决此问题,需要整合并清理 GC 接口,以便更容易地实现新的 GC,并更好地维护现有的 GC。Java 10 中,hotspot/gc 代码实现方面,引入一个干净的 GC 接口,改进不同 GC 源代码的隔离性,多个 GC 之间共享的实现细节代码应该存在于辅助类中。这种方式提供了足够的灵活性来实现全新 GC 接口,同时允许以混合搭配方式重复使用现有代码,并且能够保持代码更加干净、整洁,便于排查收集器问题。
\n并行垃圾回收器 G1 \n大家如果接触过 Java 性能调优工作,应该会知道,调优的最终目标是通过参数设置来达到快速、低延时的内存垃圾回收以提高应用吞吐量,尽可能的避免因内存回收不及时而触发的完整 GC(Full GC 会带来应用出现卡顿)。
\nG1 垃圾回收器是 Java 9 中 Hotspot 的默认垃圾回收器,是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是当并发收集无法快速回收内存时,会触发垃圾回收器回退进行 Full GC。之前 Java 版本中的 G1 垃圾回收器执行 GC 时采用的是基于单线程标记扫描压缩算法(mark-sweep-compact)。为了最大限度地减少 Full GC 造成的应用停顿的影响,Java 10 中将为 G1 引入多线程并行 GC,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
\nJava 10 中将采用并行化 mark-sweep-compact 算法,并使用与年轻代回收和混合回收相同数量的线程。具体并行 GC 线程数量可以通过:-XX:ParallelGCThreads 参数来调节,但这也会影响用于年轻代和混合收集的工作线程数。
\n线程局部管控 \n在已有的 Java 版本中,JVM 线程只能全部启用或者停止,没法做到对单独某个线程的操作。为了能够对单独的某个线程进行操作,Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程。通过这种方式显著地提高了现有 JVM 功能的性能开销,并且改变了到达 JVM 全局安全点的现有时间语义。
\n增加的参数为:-XX:ThreadLocalHandshakes (默认为开启),将允许用户在支持的平台上选择安全点。
\n参考
\n",
"link": "\\zh-cn\\blog\\java\\feature.html",
"meta": {}
}
\ No newline at end of file
diff --git a/zh-cn/blog/java/javavm.html b/zh-cn/blog/java/javavm.html
new file mode 100644
index 0000000..9f8196a
--- /dev/null
+++ b/zh-cn/blog/java/javavm.html
@@ -0,0 +1,466 @@
+
+
+
+
+
+
+
+
+
+ javavm
+
+
+
+
+ Java 虚拟机
+
+一、基本概念
+二、Java 内存区域
+ 2.1 程序计数器
+ 2.2 Java虚拟机栈
+ 2.3 本地方法栈
+ 2.4 Java堆
+ 2.5 方法区
+三、对象
+四、垃圾收集算法
+ 4.1 Java 堆回收
+ 4.2 方法区回收
+ 4.3 垃圾收集算法
+五、经典垃圾收集器
+ 5.1 Serial 收集器
+ 5.2 ParNew 收集器
+ 5.3 Parallel Scavenge 收集器
+ 5.4 Serial Old 收集器
+ 5.5 Paralled Old 收集器
+ 5.6 CMS 收集器
+ 5.7 Garbage First 收集器
+ 5.8 内存分配原则
+六、虚拟机类加载机制
+ 6.1 类加载时机
+ 6.2 类加载过程
+ 6.3 类加载器
+ 6.4 双亲委派模型
+ 6.5 模块化下的类加载器
+七、程序编译
+ 7.1 编译器分类
+ 7.2 解释器与编译器
+ 7.3 分层编译
+ 7.4 热点探测
+八、代码优化
+ 8.1 方法内联
+ 8.2 逃逸分析
+ 8.3 公共子表达式消除
+ 8.4 数组边界检查消除
+
+一、基本概念
+1.1 OpenJDK
+自 1996 年 JDK 1.0
发布以来,Sun 公司在大版本上发行了 JDK 1.1
、JDK 1.2
、JDK 1.3
、JDK 1.4
、JDK 5
,JDK 6
,这些版本的 JDK 都可以统称为 SunJDK 。之后在 2006 年的 JavaOne 大会上,Sun 公司宣布将 Java 开源,在随后的一年多里,它陆续将 JDK 的各个部分在 GPL v2(GNU General Public License,version 2)协议下开源,并建立了 OpenJDK 组织来对这些代码进行独立的管理,这就是 OpenJDK 的来源,此时的 OpenJDK 拥有当时 sunJDK 7 的几乎全部代码。
+1.2 OracleJDK
+在 JDK 7 的开发期间,由于各种原因的影响 Sun 公司市值一路下跌,已无力推进 JDK 7 的开发,JDK 7 的发布一直被推迟。之后在 2009 年 Sun 公司被 Oracle 公司所收购,为解决 JDK 7 长期跳票的问题,Oracle 将 JDK 7 中大部分未能完成的项目推迟到 JDK 8 ,并于 2011 年发布了JDK 7,在这之后由 Oracle 公司正常发行的 JDK 版本就由 SunJDK 改称为 Oracle JDK。
+在 2017 年 JDK 9 发布后,Oracle 公司宣布从此以后 JDK 将会在每年的 3 月和 9 月各发布一个大版本,即半年发行一个大版本,目的是为了避免众多功能被捆绑到一个 JDK 版本上而引发的无法交付的风险。
+在 JDK 11 发布后,Oracle 同步调整了 JDK 的商业授权,宣布从 JDK 11 起将以前的商业特性全部开源给 OpenJDK ,这样 OpenJDK 11 和 OracleJDK 11 的代码和功能,在本质上就完全相同了。同时还宣布以后都会发行两个版本的 JDK :
+
+一个是在 GPLv2 + CE 协议下由 Oracle 开源的 OpenJDK;
+一个是在 OTN 协议下正常发行的 OracleJDK。
+
+两者共享大部分源码,在功能上几乎一致。唯一的区别是 Oracle OpenJDK 可以在开发、测试或者生产环境中使用,但只有半年的更新支持;而 OracleJDK 对个人免费,但在生产环境中商用收费,可以有三年时间的更新支持。
+1.3 HotSpot VM
+它是 Sun/Oracle JDK 和 OpenJDK 中默认的虚拟机,也是目前使用最为广泛的虚拟机。最初由 Longview Technologies 公司设计发明,该公司在 1997 年被 Sun 公司收购,随后 Sun 公司在 2006 年开源 SunJDK 时也将 HotSpot 虚拟机一并进行了开源。之后 Oracle 收购 Sun 以后,建立了 HotRockit 项目,用于将其收购的另外一家公司(BEA)的 JRockit 虚拟机中的优秀特性集成到 HotSpot 中。HotSpot 在这个过程里面移除掉永久代,并吸收了 JRockit 的 Java Mission Control 监控工具等功能。到 JDK 8 发行时,采用的就是集两者之长的 HotSpot VM 。
+我们可以在自己的电脑上使用 java -version
来获得 JDK 的信息:
+C:\Users> java -version
+java version "1.8.0_171" # 如果是openJDK, 则这里会显示:openjdk version
+Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
+Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) # 使用的是HotSpot虚拟机,默认为服务端模式
+
+二、Java 内存区域
+
+2.1 程序计数器
+程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每条线程都拥有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。
+2.2 Java虚拟机栈
+Java 虚拟机栈(Java Virtual Machine Stack)也为线程私有,它描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法从调用到结束就对应着一个栈帧从入栈到出栈的过程。在《Java 虚拟机规范》中,对该内存区域规定了两类异常:
+
+如果线程请求的栈深度大于虚拟机所允许的栈深度,将抛出 StackOverflowError
异常;
+如果 Java 虚拟机栈的容量允许动态扩展,当栈扩展时如果无法申请到足够的内存会抛出 OutOfMemoryError
异常。
+
+2.3 本地方法栈
+本地方法栈(Native Method Stacks)与虚拟机栈类似,其区别在于:Java 虚拟机栈是为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
+2.4 Java堆
+Java 堆(Java Heap)是虚拟机所管理的最大一块的内存空间,它被所有线程所共享,用于存放对象实例。Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为是连续的。Java 堆可以被实现成固定大小的,也可以是可扩展的,当前大多数主流的虚拟机都是按照可扩展来实现的,即可以通过最大值参数 -Xmx
和最小值参数 -Xms
进行设定。如果 Java 堆中没有足够的内存来完成实例分配,并且堆也无法再扩展时,Java 虚拟机将会抛出 OutOfMemoryError
异常。
+2.5 方法区
+方法区(Method Area)也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。方法区也被称为 “非堆”,目的是与 Java 堆进行区分。《Java 虚拟机规范》规定,如果方法区无法满足新的内存分配需求时,将会抛出 OutOfMemoryError
异常。
+运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放常量池表(Constant Pool Table),常量池表中存放了编译期生成的各种符号字面量和符号引用。
+三、对象
+3.1 对象的创建
+当我们在代码中使用 new
关键字创建一个对象时,其在虚拟机中需要经过以下步骤:
+1. 类加载过程
+当虚拟机遇到一条字节码 new
指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。
+2. 分配内存
+在类加载检查通过后,虚拟机需要新生对象分配内存空间。根据 Java 堆是否规整,可以有以下两种分配方案:
+
+指针碰撞 :假设 Java 堆中内存是绝对规整的,所有使用的内存放在一边,所有未被使用的内存放在另外一边,中间以指针作为分界点指示器。此时内存分配只是将指针向空闲方向偏移出对象大小的空间即可,这种方式被称为指针碰撞。
+
+
+
+空闲列表 :如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。
+
+
+注:Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力,后文将会介绍。
+
+除了分配方式外,由于对象创建在虚拟机中是一个非常频繁的行为,此时需要保证在并发环境下的线程安全:如果一个线程给对象 A 分配了内存空间,但指针还没来得及修改,此时就可能出现另外一个线程使用原来的指针来给对象 B 分配内存空间的情况。想要解决这个问题有两个方案:
+
+方式一 :采用同步锁定,或采用 CAS 配上失败重试的方式来保证更新操作的原子性。
+方式二 :为每个线程在 Java 堆中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。线程在进行内存分配时优先使用本地缓冲,当本地缓冲使用完成后,再向 Java 堆申请分配,此时 Java 堆采用同步锁定的方式来保证分配行为的线程安全。
+
+3. 对象头设置
+将对象有关的元数据信息、对象的哈希码、分代年龄等信息存储到对象头中。
+4. 对象初始化
+调用对象的构造函数,即 Class 文件中的 <init>()
来初始化对象,为相关字段赋值。
+3.2 对象的内存布局
+在 HotSpot 虚拟机中,对象在堆内存中的存储布局可以划分为以下三个部分:
+1. 对象头 (Header)
+对象头包括两部分信息:
+
+Mark Word :对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,官方统称为 Mark Word 。
+类型指针 :对象指向它类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的示例。需要说明的是并非所有的虚拟机都必须要在对象数据上保留类型指针,这取决于对象的访问定位方式(详见下文)。
+
+2. 实例数据 (Instance Data)
+即我们在程序代码中定义的各种类型的字段的内容,无论是从父类继承而来,还是子类中定义的都需要记录。
+3. 对其填充 (Padding)
+主要起占位符的作用。HotSpot 虚拟机要求对象起始地址必须是 8 字节的整倍数,即间接要求了任何对象的大小都必须是 8 字节的整倍数。对象头部分在设计上就是 8 字节的整倍数,如果对象的实例数据不是 8 字节的整倍数,则由对齐填充进行补全。
+3.3 对象的访问定位
+对象创建后,Java 程序就可以通过栈上的 reference
来操作堆上的具体对象。《Java 虚拟机规范》规定 reference
是一个指向对象的引用,但并未规定其具体实现方式。主流的方式方式有以下两种:
+
+句柄访问 :Java 堆将划分出一块内存来作为句柄池, reference
中存储的是对象的句柄地址,而句柄则包含了对象实例数据和类型数据的地址信息。
+指针访问 :reference
中存储的直接就是对象地址,而对象的类型数据则由上文介绍的对象头中的类型指针来指定。
+
+通过句柄访问对象:
+
+通过直接指针访问对象:
+
+句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 reference
本生并不需要修改;指针访问则反之,由于其 reference
中存储的直接就是对象地址,所以当对象移动时, reference
需要被修改。但针对只需要访问对象本身的场景,指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作,所以这类减少的效果会非常显著,基于这个原因,HotSpot 主要使用的是指针访问的方式。
+四、垃圾收集算法
+在 Java 虚拟机内存模型中,程序计数器、虚拟机栈、本地方法栈这 3 个区域都是线程私有的,会随着线程的结束而销毁,因此在这 3 个区域当中,无需过多考虑垃圾回收问题。垃圾回收问题主要发生在 Java 堆和方法区上。
+4.1 Java 堆回收
+在 Java 堆上,垃圾回收的主要内容是死亡对象(不可能再被任何途径使用的对象)。而判断对象是否死亡有以下两种方法:
+1. 引用计数法
+在对象中添加一个引用计数器,对象每次被引用时,该计数器加一;当引用失效时,计数器的值减一;只要计数器的值为零,则代表对应的对象不可能再被使用。该方法的缺点在于无法避免相互循环引用的问题:
+objA.instance = objB
+objB.instance = objA
+objA = null ;
+objB = null ;
+System.gc();
+
+如上所示,此时两个对象已经不能再被访问,但其互相持有对对方的引用,如果采用引用计数法,则两个对象都无法被回收。
+2. 可达性分析
+上面的代码在大多数虚拟机中都能被正确的回收,因为大多数主流的虚拟机都是采用的可达性分析方法来判断对象是否死亡。可达性分析是通过一系列被称为 GC Roots
的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链(Reference Chain),如果某个对象到 GC Roots
间没有任何引用链相连,这代表 GC Roots
到该对象不可达, 此时证明此该对象不可能再被使用。
+
+在 Java 语言中,固定可作为 GC Roots
的对象包括以下几种:
+
+在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
+在方法区中类静态属性引用的对象,譬如 Java 类中引用类型的静态变量;
+在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用;
+在本地方法栈中的 JNI(即 Native 方法)引用的对象;
+Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(如 NullPointException,OutOfMemoryError 等)及系统类加载器;
+所有被同步锁(synchronized 关键字)持有的对象;
+反应 Java 虚拟机内部情况的 JMXBean,JVMTI 中注册的回调,本地代码缓存等。
+
+除了这些固定的 GC Roots
集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域的不同,还可能会有其他对象 “临时性” 地加入,共同构成完整的 GC Roots
集合。
+3. 对象引用
+可达性分析是基于引用链进行判断的,在 JDK 1.2 之后,Java 将引用关系分为以下四类:
+
+强引用 (Strongly Reference) :最传统的引用,如 Object obj = new Object()
。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
+软引用 (Soft Reference) :用于描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常之前,会被列入回收范围内进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
+弱引用 (Weak Reference) :用于描述那些非必须的对象,强度比软引用弱。被弱引用关联对象只能生存到下一次垃圾收集发生时,无论当前内存是否足够,弱引用对象都会被回收。
+虚引用 (Phantom Reference) :最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被回收时收到一个系统通知。
+
+4. 对象真正死亡
+要真正宣告一个对象死亡,需要经过至少两次标记过程:
+
+如果对象在进行可达性分析后发现 GC Roots
不可达,将会进行第一次标记;
+随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalized()
方法。如果对象没有覆盖 finalized()
方法,或者 finalized()
已经被虚拟机调用过,这两种情况都会视为没有必要执行。如果判定结果是有必要执行,此时对象会被放入名为 F-Queue
的队列,等待 Finalizer 线程执行其 finalized()
方法。在这个过程中,收集器会进行第二次小规模的标记,如果对象在 finalized()
方法中重新将自己与引用链上的任何一个对象进行了关联,如将自己(this 关键字)赋值给某个类变量或者对象的成员变量,此时它就实现了自我拯救,则第二次标记会将其移除 “即将回收” 的集合,否则该对象就将被真正回收,走向死亡。
+
+4.2 方法区回收
+在 Java 堆上进行对象回收的性价比通常比较高,因为大多数对象都是朝生夕灭的。而方法区由于回收条件比较苛刻,对应的回收性价比通常比较低,主要回收两部分内容:废弃的常量和不再使用的类型。
+4.3 垃圾收集算法
+1. 分代收集理论
+当前大多数虚拟机都遵循 “分代收集” 的理论进行设计,它建立在强弱两个分代假说下:
+
+弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是朝生夕灭的。
+强分代假说 (Strong Generational Hypothesis) :熬过越多次垃圾收集过程的对象就越难以消亡。
+跨带引用假说 (Intergenerational Reference Hypothesis) :基于上面两条假说还可以得出的一条隐含推论:存在相互引用关系的两个对象,应该倾向于同时生存或者同时消亡。
+
+强弱分代假说奠定了垃圾收集器的设计原则:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄就是对象经历垃圾收集的次数)分配到不同的区域中进行存储。之后如果一个区域中的对象都是朝生夕灭的,那么收集器只需要关注少量对象的存活而不是去标记那些大量将要被回收的对象,此时就能以较小的代价获取较大的空间。最后再将难以消亡的对象集中到一块,根据强分代假说,它们是很难消亡的,因此虚拟机可以使用较低的频率进行回收,这就兼顾了时间和内存空间的开销。
+2. 回收类型
+根据分代收集理论,收集范围可以分为以下几种类型:
+
+部分收集 (Partial GC) :具体分为:
+
+新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
+老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
+混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
+
+
+整堆收集 (Full GC) :收集整个 Java 堆和方法区。
+
+3. 标记-清除算法
+它是最基础的垃圾收集算法,收集过程分为两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;也可以反过来,标记存活对象,统一回收所有未被标记的对象。
+
+它主要有以下两个缺点:
+
+执行效率不稳定:如果 Java 堆上包含大量需要回收的对象,则需要进行大量标记和清除动作;
+内存空间碎片化:标记清除后会产生大量不连续的空间,从而可能导致无法为大对象分配足够的连续内存。
+
+4. 标记-复制算法
+标记-复制算法基于 ”半区复制“ 算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存使用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的那块内存空间一次性清理掉。其优点在于避免了内存空间碎片化的问题,其缺点如下:
+
+如果内存中多数对象都是存活的,这种算法将产生大量的复制开销;
+浪费内存空间,内存空间变为了原有的一半。
+
+
+基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 Eden
和 两块较小的 Survivor
空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 Eden
和其中的一块 Survivor
,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 Survivor
上,这样只有 10% 的内存空间会被浪费掉。当 Survivor
空间不足以容纳一次 Minor GC
时,此时由其他内存区域(通常是老年代)来进行分配担保。
+5. 标记-整理算法
+标记-整理算法是在标记完成后,让所有存活对象都向内存的一端移动,然后直接清理掉边界以外的内存。其优点在于可以避免内存空间碎片化的问题,也可以充分利用内存空间;其缺点在于根据所使用的收集器的不同,在移动存活对象时可能要全程暂停用户程序:
+
+五、经典垃圾收集器
+并行与并发是并发编程中的专有名词,在谈论垃圾收集器的上下文语境中,它们的含义如下:
+
+HotSpot 虚拟机中一共存在七款经典的垃圾收集器:
+
+
+注:收集器之间存在连线,则代表它们可以搭配使用。
+
+5.1 Serial 收集器
+Serial 收集器是最基础、历史最悠久的收集器,它是一个单线程收集器,在进行垃圾回收时,必须暂停其他所有的工作线程,直到收集结束,这是其主要缺点。它的优点在于单线程避免了多线程复杂的上下文切换,因此在单线程环境下收集效率非常高,由于这个优点,迄今为止,其仍然是 HotSpot 虚拟机在客户端模式下默认的新生代收集器:
+
+5.2 ParNew 收集器
+他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收:
+
+5.3 Parallel Scavenge 收集器
+Parallel Scavenge 也是新生代收集器,基于 标记-复制 算法进行实现,它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值:
+吞吐量 = \frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间}
+
+Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量:
+
+-XX:MaxGCPauseMillis :控制最大垃圾收集时间,假设需要回收的垃圾总量不变,那么降低垃圾收集的时间就会导致收集频率变高,所以需要将其设置为合适的值,不能一味减小。
+-XX:MaxGCTimeRatio :直接用于设置吞吐量大小,它是一个大于 0 小于 100 的整数。假设把它设置为 19,表示此时允许的最大垃圾收集时间占总时间的 5%(即 1/(1+19) );默认值为 99 ,即允许最大 1%( 1/(1+99) )的垃圾收集时间。
+
+5.4 Serial Old 收集器
+从名字也可以看出来,它是 Serial 收集器的老年代版本,同样是一个单线程收集器,采用 标记-整理 算法,主要用于给客户端模式下的 HotSpot 虚拟机使用:
+
+5.5 Paralled Old 收集器
+Paralled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现:
+
+5.6 CMS 收集器
+CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于 标记-清除 算法实现,整个收集过程分为以下四个阶段:
+
+初始标记 (inital mark) :标记 GC Roots
能直接关联到的对象,耗时短但需要暂停用户线程;
+并发标记 (concurrent mark) :从 GC Roots
能直接关联到的对象开始遍历整个对象图,耗时长但不需要暂停用户线程;
+重新标记 (remark) :采用增量更新算法,对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记,耗时比初始标记稍长且需要暂停用户线程;
+并发清除 (inital sweep) :并发清除掉已经死亡的对象,耗时长但不需要暂停用户线程。
+
+
+其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下:
+
+由于涉及并发操作,因此对处理器资源比较敏感。
+由于是基于 标记-清除 算法实现的,因此会产生大量空间碎片。
+无法处理浮动垃圾(Floating Garbage):由于并发清除时用户线程还是在继续,所以此时仍然会产生垃圾,这些垃圾就被称为浮动垃圾,只能等到下一次垃圾收集时再进行清理。
+
+5.7 Garbage First 收集器
+Garbage First(简称 G1)是一款面向服务端的垃圾收集器,也是 JDK 9 服务端模式下默认的垃圾收集器,它的诞生具有里程碑式的意义。G1 虽然也遵循分代收集理论,但不再以固定大小和固定数量来划分分代区域,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region)。每一个 Region 都可以根据不同的需求来扮演新生代的 Eden
空间、Survivor
空间或者老年代空间,收集器会根据其扮演角色的不同而采用不同的收集策略。
+
+上面还有一些 Region 使用 H 进行标注,它代表 Humongous,表示这些 Region 用于存储大对象(humongous object,H-obj),即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤:
+
+初始标记 (Inital Marking) :标记 GC Roots
能直接关联到的对象,并且修改 TAMS(Top at Mark Start)指针的值,让下一阶段用户线程并发运行时,能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针,新分配的对象必须位于这两个指针位置以上,位于这两个指针位置以上的对象默认被隐式标记为存活的,不会纳入回收范围;
+并发标记 (Concurrent Marking) :从 GC Roots
能直接关联到的对象开始遍历整个对象图。遍历完成后,还需要处理 SATB 记录中变动的对象。SATB(snapshot-at-the-beginning,开始阶段快照)能够有效的解决并发标记阶段因为用户线程运行而导致的对象变动,其效率比 CMS 重新标记阶段所使用的增量更新算法效率更高;
+最终标记 (Final Marking) :对用户线程做一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的少量的 STAB 记录。虽然并发标记阶段会处理 SATB 记录,但由于处理时用户线程依然是运行中的,因此依然会有少量的变动,所以需要最终标记来处理;
+筛选回收 (Live Data Counting and Evacuation) :负责更新 Regin 统计数据,按照各个 Regin 的回收价值和成本进行排序,在根据用户期望的停顿时间进行来指定回收计划,可以选择任意多个 Regin 构成回收集。然后将回收集中 Regin 的存活对象复制到空的 Regin 中,再清理掉整个旧的 Regin 。此时因为涉及到存活对象的移动,所以需要暂停用户线程,并由多个收集线程并行执行。
+
+
+5.8 内存分配原则
+1. 对象优先在 Eden 分配
+大多数情况下,对象在新生代的 Eden
区中进行分配,当 Eden
区没有足够空间时,虚拟机将进行一次 Minor GC。
+2. 大对象直接进入老年代
+大对象就是指需要大量连续内存空间的 Java 对象,最典型的就是超长的字符串或者元素数量很多的数组,它们将直接进入老年代。主要是因为如果在新生代分配,因为其需要大量连续的内存空间,可能会导致提前触发垃圾回收;并且由于新生代的垃圾回收本身就很频繁,此时复制大对象也需要额外的性能开销。
+3. 长期存活的对象将进入老年代
+虚拟机会给每个对象在其对象头中定义一个年龄计数器。对象通常在 Eden
区中诞生,如果经历第一次 Minor GC 后仍然存活,并且能够被 Survivor 容纳的话,该对象就会被移动到 Survivor 中,并将其年龄加 1。对象在 Survivor 中每经过一次 Minor GC,年龄就加 1,当年龄达到一定程度后(由 -XX:MaxTenuringThreshold
设置,默认值为 15)就会进入老年代中。
+4. 动态年龄判断
+如果在 Survivor 空间中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等待年龄到达 -XX:MaxTenuringThreshold
设置的值。
+5. 空间担保分配
+在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果条件成立,那么这一次的 Minor GC 可以确认是安全的。如果不成立,虚拟机会查看 -XX:HandlePromotionFailure
的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于或者 -XX:HandlePromotionFailure
的值设置不允许冒险,那么就要改为进行一次 Full GC 。
+六、虚拟机类加载机制
+Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的类加载机制。
+6.1 类加载时机
+一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、卸载、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接:
+
+《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化:
+
+遇到 new
、 getstatic
、 putstatic
、 invokestatic
这四条字节码指令,如果类型进行过初始化,则需要先触发其进行初始化,能够生成这四条指令码的典型 Java 代码场景有:
+
+使用 new
关键字实例化对象时;
+读取或设置一个类型的静态字段时(被 final 修饰,已在编译期把结果放入常量池的静态字段除外);
+调用一个类型的静态方法时。
+
+
+使用 java.lang.reflect
包的方法对类型进行反射调用时,如果类型没有进行过初始化、则需要触发其初始化;
+当初始化类时,如发现其父类还没有进行过初始化、则需要触发其父类进行初始化;
+当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
+当使用 JDK 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle
实例最后解析的结果为 REF_getStatic
, REF_putStatic
, REF_invokeStatic
, REF_newInvokeSpecial
四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化;
+当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那么该接口要在其之前被初始化。
+
+6.2 类加载过程
+1. 加载
+在加载阶段,虚拟机需要完成以下三件事:
+
+通过一个类的全限定名来获取定义此类的二进制字节流 ;
+将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构;
+在内存中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
+
+《Java 虚拟机规范》并没有限制从何处获取二进制流,因此可以从 JAR 包、WAR 包获取,也可以从 JSP 生成的 Class 文件等处获取。
+2. 验证
+这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,从而保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证阶段大致会完成下面四项验证:
+
+文件格式验证 :验证字节流是否符合 Class 文件格式的规范;
+元数据验证 :对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java 语言规范》的要求(如除了 java.lang.Object
外,所有的类都应该有父类);
+字节码验证 :通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的(如允许把子类对象赋值给父类数据类型,但不能把父类对象赋值给子类数据类型);
+符号引用验证 :验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。如果无法验证通过,则会抛出一个java.lang.IncompatibleClassChangeError
的子类异常,如 java.lang.NoSuchFieldError
、 java.lang.NoSuchMethodError
等。
+
+3. 准备
+准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段。
+4. 解析
+解析是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程:
+
+符号引用 :符号引用用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
+直接引用 :直接引用是指可以直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。
+
+整个解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行解析。
+5. 初始化
+初始化阶段就是执行类构造器的 <clinit>()
方法的过程,该方法具有以下特点:
+
+<clinit>()
方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生,编译器收集顺序由语句在源文件中出现的顺序决定。
+<clinit>()
方法与类的构造器函数(即在虚拟机视角中的实例构造器 <init>()
方法)不同,它不需要显示的调用父类的构造器,Java 虚拟机会保证在子类的 <clinit>()
方法执行前,父类的 <clinit>()
方法已经执行完毕。
+由于父类的 <clinit>()
方法先执行,也就意味着父类中定义的静态语句块要优先于子类变量的赋值操作。
+<clinit>()
方法对于类或者接口不是必须的,如果一个类中没有静态语句块,也没有对变量进行赋值操作,那么编译器可以不为这个类生成 <clinit>()
方法。
+接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>()
方法。
+Java 虚拟机必须保证一个类的 <clinit>()
方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的 <clinit>()
方法,其他线程都需要阻塞等待。
+
+6.3 类加载器
+能够通过一个类的全限定名来获取描述该类的二进制字节流的工具称为类加载器。每一个类加载器都拥有一个独立的类名空间,因此对于任意一个类,都必须由加载它的类加载器和这个类本身来共同确立其在 Java 虚拟机中的唯一性。这意味着要想比较两个类是否相等,必须在同一类加载器加载的前提下;如果两个类的类加载器不同,则它们一定不相等。
+6.4 双亲委派模型
+从 Java 虚拟机角度而言,类加载器可以分为以下两类:
+
+启动类加载器 :启动类加载器(Bootstrap ClassLoader)由 C++ 语言实现(以 HotSpot 为例),它是虚拟机自身的一部分;
+其他所有类的类加载器 :由 Java 语言实现,独立存在于虚拟机外部,并且全部继承自 java.lang.ClassLoader
。
+
+从开发人员角度而言,类加载器可以分为以下三类:
+
+启动类加载器 (Boostrap Class Loader) :负责把存放在 <JAVA_HOME>\lib
目录中,或被 -Xbootclasspath
参数所指定的路径中存放的能被 Java 虚拟机识别的类库加载到虚拟机的内存中;
+扩展类加载器 (Extension Class Loader) :负责加载 <JAVA_HOME>\lib\ext
目录中,或被 java.ext.dirs
系统变量所指定的路径中的所有类库。
+应用程序类加载器 (Application Class Loader) :负责加载用户类路径(ClassPath)上的所有的类库。
+
+JDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成加载:
+
+上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。
+双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类,否则就有可能出现一个程序中存在两个不同的 java.lang.Object
的情况。
+6.5 模块化下的类加载器
+JDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
+
+仍维持三层类加载器和双亲委派的架构,但扩展类加载器被平台类加载器所取代;
+当平台及应用程序类加载器收到类加载请求时,要首先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载;
+启动类加载器、平台类加载器、应用程序类加载器全部继承自 java.internal.loader.BuiltinClassLoader
,BuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。
+
+
+七、程序编译
+7.1 编译器分类
+
+前端编译器 :把 *.java
文件转变成 .class
文件的过程;如 JDK 的 Javac,Eclipse JDT 中的增量式编译器。
+即使编译器 :常称为 JIT 编译器(Just In Time Complier),在运行期把字节码转变成本地机器码的过程;如 HotSpot 虚拟机中的 C1、C2 编译器,Graal 编译器。
+提前编译器 :直接把程序编译成目标机器指令集相关的二进制代码的过程。如 JDK 的 jaotc,GUN Compiler for the Java(GCJ),Excelsior JET 。
+
+7.2 解释器与编译器
+在 HotSpot 虚拟机中,Java 程序最初都是通过解释器(Interpreter)进行解释执行的,其优点在于可以省去编译时间,让程序快速启动。当程序启动后,如果虚拟机发现某个方法或代码块的运行特别频繁,就会使用编译器将其编译为本地机器码,并使用各种手段进行优化,从而提高执行效率,这就是即时编译器。HotSpot 内置了两个(或三个)即时编译器:
+
+客户端编译器 (Client Complier) :简称 C1;
+服务端编译器 (Servier Complier) :简称 C2,在有的资料和 JDK 源码中也称为 Opto 编译器;
+Graal 编译器 :在 JDK 10 时才出现,长期目标是替代 C2。
+
+在分层编译的工作模式出现前,采用客户端编译器还是服务端编译器完全取决于虚拟机是运行在客户端模式还是服务端模式下,可以在启动时通过 -client
或 -server
参数进行指定,也可以让虚拟机根据自身版本和宿主机性能来自主选择。
+7.3 分层编译
+要编译出优化程度越高的代码通常都需要越长的编译时间,为了在程序启动速度与运行效率之间达到最佳平衡,HotSpot 虚拟机在编译子系统中加入了分层编译(Tiered Compilation):
+
+第 0 层 :程序纯解释执行,并且解释器不开启性能监控功能;
+第 1 层 :使用客户端编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启性能监控功能;
+第 2 层 :仍然使用客户端编译执行,仅开启方法及回边次数统计等有限的性能监控;
+第 3 层 :仍然使用客户端编译执行,开启全部性能监控;
+第 4 层 :使用服务端编译器将字节码编译为本地代码,其耗时更长,并且会根据性能监控信息进行一些不可靠的激进优化。
+
+以上层次并不是固定不变的,根据不同的运行参数和版本,虚拟机可以调整分层的数量。各层次编译之间的交互转换关系如下图所示:
+
+实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。
+7.4 热点探测
+即时编译器编译的目标是 “热点代码”,它主要分为以下两类:
+
+被多次调用的方法。
+被多次执行循环体。这里指的是一个方法只被少量调用过,但方法体内部存在循环次数较多的循环体,此时也认为是热点代码。但编译器编译的仍然是循环体所在的方法,而不会单独编译循环体。
+
+判断某段代码是否是热点代码的行为称为 “热点探测” (Hot Spot Code Detection),主流的热点探测方法有以下两种:
+
+基于采样的热点探测 (Sample Based Hot Spot Code Detection) :采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶,如果发现某个(或某些)方法经常出现在栈顶,那么就认为它是 “热点方法”。
+基于计数的热点探测 (Counter Based Hot Spot Code Detection) :采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是 “热点方法”。
+
+八、代码优化
+即时编译器除了将字节码编译为本地机器码外,还会对代码进行一定程度的优化,它包含多达几十种优化技术,这里选取其中代表性的四种进行介绍:
+8.1 方法内联
+最重要的优化手段,它会将目标方法中的代码原封不动地 “复制” 到发起调用的方法之中,避免发生真实的方法调用,并采用名为类型继承关系分析(Class Hierarchy Analysis,CHA)的技术来解决虚方法(Java 语言中默认的实例方法都是虚方法)的内联问题。
+8.2 逃逸分析
+逃逸行为主要分为以下两类:
+
+方法逃逸 :当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,此时称为方法逃逸;
+线程逃逸 :当一个对象在方法里面被定义后,它可能被外部线程所访问,例如赋值给可以在其他线程中访问的实例变量,此时称为线程,其逃逸程度高于方法逃逸。
+
+public static StringBuilder concat (String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for (String string : strings) {
+ sb.append(string);
+ }
+ return sb;
+}
+
+public static String concat (String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for (String string : strings) {
+ sb.append(string);
+ }
+ return sb.toString();
+}
+
+如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可以为这个对象实例采取不同程序的优化:
+
+栈上分配 (Stack Allocations) :如果一个对象不会逃逸到线程外,那么将会在栈上分配内存来创建这个对象,而不是 Java 堆上,此时对象所占用的内存空间就会随着栈帧的出栈而销毁,从而可以减轻垃圾回收的压力。
+标量替换 (Scalar Replacement) :如果一个数据已经无法再分解成为更小的数据类型,那么这些数据就称为标量(如 int、long 等数值类型及 reference 类型等);反之,如果一个数据可以继续分解,那它就被称为聚合量(如对象)。如果一个对象不会逃逸外方法外,那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代,从而减少内存占用。
+同步消除 (Synchronization Elimination) :如果一个变量不会逃逸出线程,那么对这个变量实施的同步措施就可以消除掉。
+
+8.3 公共子表达式消除
+如果一个表达式 E 之前已经被计算过了,并且从先前的计算到现在 E 中所有变量的值都没有发生过变化,那么 E 这次的出现就称为公共子表达式。对于这种表达式,无需再重新进行计算,只需要直接使用前面的计算结果即可。
+8.4 数组边界检查消除
+对于虚拟机执行子系统来说,每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中,并且使用循环变量来访问数据,即循环变量的取值永远在 [0,list.length) 之间,那么此时就可以消除整个循环的数据边界检查,从而避免多次无用的判断。
+参考资料
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/java/javavm.json b/zh-cn/blog/java/javavm.json
new file mode 100644
index 0000000..40f2e77
--- /dev/null
+++ b/zh-cn/blog/java/javavm.json
@@ -0,0 +1,6 @@
+{
+ "filename": "javavm.md",
+ "__html": "Java 虚拟机 \n\n一、基本概念 \n二、Java 内存区域 \n 2.1 程序计数器 \n 2.2 Java虚拟机栈 \n 2.3 本地方法栈 \n 2.4 Java堆 \n 2.5 方法区 \n三、对象 \n四、垃圾收集算法 \n 4.1 Java 堆回收 \n 4.2 方法区回收 \n 4.3 垃圾收集算法 \n五、经典垃圾收集器 \n 5.1 Serial 收集器 \n 5.2 ParNew 收集器 \n 5.3 Parallel Scavenge 收集器 \n 5.4 Serial Old 收集器 \n 5.5 Paralled Old 收集器 \n 5.6 CMS 收集器 \n 5.7 Garbage First 收集器 \n 5.8 内存分配原则 \n六、虚拟机类加载机制 \n 6.1 类加载时机 \n 6.2 类加载过程 \n 6.3 类加载器 \n 6.4 双亲委派模型 \n 6.5 模块化下的类加载器 \n七、程序编译 \n 7.1 编译器分类 \n 7.2 解释器与编译器 \n 7.3 分层编译 \n 7.4 热点探测 \n八、代码优化 \n 8.1 方法内联 \n 8.2 逃逸分析 \n 8.3 公共子表达式消除 \n 8.4 数组边界检查消除 \n \n一、基本概念 \n1.1 OpenJDK \n自 1996 年 JDK 1.0
发布以来,Sun 公司在大版本上发行了 JDK 1.1
、JDK 1.2
、JDK 1.3
、JDK 1.4
、JDK 5
,JDK 6
,这些版本的 JDK 都可以统称为 SunJDK 。之后在 2006 年的 JavaOne 大会上,Sun 公司宣布将 Java 开源,在随后的一年多里,它陆续将 JDK 的各个部分在 GPL v2(GNU General Public License,version 2)协议下开源,并建立了 OpenJDK 组织来对这些代码进行独立的管理,这就是 OpenJDK 的来源,此时的 OpenJDK 拥有当时 sunJDK 7 的几乎全部代码。
\n1.2 OracleJDK \n在 JDK 7 的开发期间,由于各种原因的影响 Sun 公司市值一路下跌,已无力推进 JDK 7 的开发,JDK 7 的发布一直被推迟。之后在 2009 年 Sun 公司被 Oracle 公司所收购,为解决 JDK 7 长期跳票的问题,Oracle 将 JDK 7 中大部分未能完成的项目推迟到 JDK 8 ,并于 2011 年发布了JDK 7,在这之后由 Oracle 公司正常发行的 JDK 版本就由 SunJDK 改称为 Oracle JDK。
\n在 2017 年 JDK 9 发布后,Oracle 公司宣布从此以后 JDK 将会在每年的 3 月和 9 月各发布一个大版本,即半年发行一个大版本,目的是为了避免众多功能被捆绑到一个 JDK 版本上而引发的无法交付的风险。
\n在 JDK 11 发布后,Oracle 同步调整了 JDK 的商业授权,宣布从 JDK 11 起将以前的商业特性全部开源给 OpenJDK ,这样 OpenJDK 11 和 OracleJDK 11 的代码和功能,在本质上就完全相同了。同时还宣布以后都会发行两个版本的 JDK :
\n\n一个是在 GPLv2 + CE 协议下由 Oracle 开源的 OpenJDK; \n一个是在 OTN 协议下正常发行的 OracleJDK。 \n \n两者共享大部分源码,在功能上几乎一致。唯一的区别是 Oracle OpenJDK 可以在开发、测试或者生产环境中使用,但只有半年的更新支持;而 OracleJDK 对个人免费,但在生产环境中商用收费,可以有三年时间的更新支持。
\n1.3 HotSpot VM \n它是 Sun/Oracle JDK 和 OpenJDK 中默认的虚拟机,也是目前使用最为广泛的虚拟机。最初由 Longview Technologies 公司设计发明,该公司在 1997 年被 Sun 公司收购,随后 Sun 公司在 2006 年开源 SunJDK 时也将 HotSpot 虚拟机一并进行了开源。之后 Oracle 收购 Sun 以后,建立了 HotRockit 项目,用于将其收购的另外一家公司(BEA)的 JRockit 虚拟机中的优秀特性集成到 HotSpot 中。HotSpot 在这个过程里面移除掉永久代,并吸收了 JRockit 的 Java Mission Control 监控工具等功能。到 JDK 8 发行时,采用的就是集两者之长的 HotSpot VM 。
\n我们可以在自己的电脑上使用 java -version
来获得 JDK 的信息:
\nC:\\Users> java -version\njava version \"1.8.0_171\" # 如果是openJDK, 则这里会显示:openjdk version\nJava(TM) SE Runtime Environment (build 1.8.0_171-b11)\nJava HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) # 使用的是HotSpot虚拟机,默认为服务端模式\n
\n二、Java 内存区域 \n\n2.1 程序计数器 \n程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每条线程都拥有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。
\n2.2 Java虚拟机栈 \nJava 虚拟机栈(Java Virtual Machine Stack)也为线程私有,它描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法从调用到结束就对应着一个栈帧从入栈到出栈的过程。在《Java 虚拟机规范》中,对该内存区域规定了两类异常:
\n\n如果线程请求的栈深度大于虚拟机所允许的栈深度,将抛出 StackOverflowError
异常; \n如果 Java 虚拟机栈的容量允许动态扩展,当栈扩展时如果无法申请到足够的内存会抛出 OutOfMemoryError
异常。 \n \n2.3 本地方法栈 \n本地方法栈(Native Method Stacks)与虚拟机栈类似,其区别在于:Java 虚拟机栈是为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
\n2.4 Java堆 \nJava 堆(Java Heap)是虚拟机所管理的最大一块的内存空间,它被所有线程所共享,用于存放对象实例。Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为是连续的。Java 堆可以被实现成固定大小的,也可以是可扩展的,当前大多数主流的虚拟机都是按照可扩展来实现的,即可以通过最大值参数 -Xmx
和最小值参数 -Xms
进行设定。如果 Java 堆中没有足够的内存来完成实例分配,并且堆也无法再扩展时,Java 虚拟机将会抛出 OutOfMemoryError
异常。
\n2.5 方法区 \n方法区(Method Area)也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。方法区也被称为 “非堆”,目的是与 Java 堆进行区分。《Java 虚拟机规范》规定,如果方法区无法满足新的内存分配需求时,将会抛出 OutOfMemoryError
异常。
\n运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放常量池表(Constant Pool Table),常量池表中存放了编译期生成的各种符号字面量和符号引用。
\n三、对象 \n3.1 对象的创建 \n当我们在代码中使用 new
关键字创建一个对象时,其在虚拟机中需要经过以下步骤:
\n1. 类加载过程
\n当虚拟机遇到一条字节码 new
指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。
\n2. 分配内存
\n在类加载检查通过后,虚拟机需要新生对象分配内存空间。根据 Java 堆是否规整,可以有以下两种分配方案:
\n\n指针碰撞 :假设 Java 堆中内存是绝对规整的,所有使用的内存放在一边,所有未被使用的内存放在另外一边,中间以指针作为分界点指示器。此时内存分配只是将指针向空闲方向偏移出对象大小的空间即可,这种方式被称为指针碰撞。 \n \n\n\n空闲列表 :如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。 \n \n\n注:Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力,后文将会介绍。
\n \n除了分配方式外,由于对象创建在虚拟机中是一个非常频繁的行为,此时需要保证在并发环境下的线程安全:如果一个线程给对象 A 分配了内存空间,但指针还没来得及修改,此时就可能出现另外一个线程使用原来的指针来给对象 B 分配内存空间的情况。想要解决这个问题有两个方案:
\n\n方式一 :采用同步锁定,或采用 CAS 配上失败重试的方式来保证更新操作的原子性。 \n方式二 :为每个线程在 Java 堆中预先分配一块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。线程在进行内存分配时优先使用本地缓冲,当本地缓冲使用完成后,再向 Java 堆申请分配,此时 Java 堆采用同步锁定的方式来保证分配行为的线程安全。 \n \n3. 对象头设置
\n将对象有关的元数据信息、对象的哈希码、分代年龄等信息存储到对象头中。
\n4. 对象初始化
\n调用对象的构造函数,即 Class 文件中的 <init>()
来初始化对象,为相关字段赋值。
\n3.2 对象的内存布局 \n在 HotSpot 虚拟机中,对象在堆内存中的存储布局可以划分为以下三个部分:
\n1. 对象头 (Header)
\n对象头包括两部分信息:
\n\nMark Word :对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,官方统称为 Mark Word 。 \n类型指针 :对象指向它类型元数据的指针,Java 虚拟机通过这个指针来确定该对象是哪个类的示例。需要说明的是并非所有的虚拟机都必须要在对象数据上保留类型指针,这取决于对象的访问定位方式(详见下文)。 \n \n2. 实例数据 (Instance Data)
\n即我们在程序代码中定义的各种类型的字段的内容,无论是从父类继承而来,还是子类中定义的都需要记录。
\n3. 对其填充 (Padding)
\n主要起占位符的作用。HotSpot 虚拟机要求对象起始地址必须是 8 字节的整倍数,即间接要求了任何对象的大小都必须是 8 字节的整倍数。对象头部分在设计上就是 8 字节的整倍数,如果对象的实例数据不是 8 字节的整倍数,则由对齐填充进行补全。
\n3.3 对象的访问定位 \n对象创建后,Java 程序就可以通过栈上的 reference
来操作堆上的具体对象。《Java 虚拟机规范》规定 reference
是一个指向对象的引用,但并未规定其具体实现方式。主流的方式方式有以下两种:
\n\n句柄访问 :Java 堆将划分出一块内存来作为句柄池, reference
中存储的是对象的句柄地址,而句柄则包含了对象实例数据和类型数据的地址信息。 \n指针访问 :reference
中存储的直接就是对象地址,而对象的类型数据则由上文介绍的对象头中的类型指针来指定。 \n \n通过句柄访问对象:
\n\n通过直接指针访问对象:
\n\n句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 reference
本生并不需要修改;指针访问则反之,由于其 reference
中存储的直接就是对象地址,所以当对象移动时, reference
需要被修改。但针对只需要访问对象本身的场景,指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作,所以这类减少的效果会非常显著,基于这个原因,HotSpot 主要使用的是指针访问的方式。
\n四、垃圾收集算法 \n在 Java 虚拟机内存模型中,程序计数器、虚拟机栈、本地方法栈这 3 个区域都是线程私有的,会随着线程的结束而销毁,因此在这 3 个区域当中,无需过多考虑垃圾回收问题。垃圾回收问题主要发生在 Java 堆和方法区上。
\n4.1 Java 堆回收 \n在 Java 堆上,垃圾回收的主要内容是死亡对象(不可能再被任何途径使用的对象)。而判断对象是否死亡有以下两种方法:
\n1. 引用计数法 \n在对象中添加一个引用计数器,对象每次被引用时,该计数器加一;当引用失效时,计数器的值减一;只要计数器的值为零,则代表对应的对象不可能再被使用。该方法的缺点在于无法避免相互循环引用的问题:
\nobjA.instance = objB\nobjB.instance = objA \nobjA = null ;\nobjB = null ;\nSystem.gc();\n
\n如上所示,此时两个对象已经不能再被访问,但其互相持有对对方的引用,如果采用引用计数法,则两个对象都无法被回收。
\n2. 可达性分析 \n上面的代码在大多数虚拟机中都能被正确的回收,因为大多数主流的虚拟机都是采用的可达性分析方法来判断对象是否死亡。可达性分析是通过一系列被称为 GC Roots
的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链(Reference Chain),如果某个对象到 GC Roots
间没有任何引用链相连,这代表 GC Roots
到该对象不可达, 此时证明此该对象不可能再被使用。
\n\n在 Java 语言中,固定可作为 GC Roots
的对象包括以下几种:
\n\n在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等; \n在方法区中类静态属性引用的对象,譬如 Java 类中引用类型的静态变量; \n在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用; \n在本地方法栈中的 JNI(即 Native 方法)引用的对象; \nJava 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(如 NullPointException,OutOfMemoryError 等)及系统类加载器; \n所有被同步锁(synchronized 关键字)持有的对象; \n反应 Java 虚拟机内部情况的 JMXBean,JVMTI 中注册的回调,本地代码缓存等。 \n \n除了这些固定的 GC Roots
集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域的不同,还可能会有其他对象 “临时性” 地加入,共同构成完整的 GC Roots
集合。
\n3. 对象引用 \n可达性分析是基于引用链进行判断的,在 JDK 1.2 之后,Java 将引用关系分为以下四类:
\n\n强引用 (Strongly Reference) :最传统的引用,如 Object obj = new Object()
。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。 \n软引用 (Soft Reference) :用于描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常之前,会被列入回收范围内进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存溢出异常。 \n弱引用 (Weak Reference) :用于描述那些非必须的对象,强度比软引用弱。被弱引用关联对象只能生存到下一次垃圾收集发生时,无论当前内存是否足够,弱引用对象都会被回收。 \n虚引用 (Phantom Reference) :最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被回收时收到一个系统通知。 \n \n4. 对象真正死亡 \n要真正宣告一个对象死亡,需要经过至少两次标记过程:
\n\n如果对象在进行可达性分析后发现 GC Roots
不可达,将会进行第一次标记; \n随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalized()
方法。如果对象没有覆盖 finalized()
方法,或者 finalized()
已经被虚拟机调用过,这两种情况都会视为没有必要执行。如果判定结果是有必要执行,此时对象会被放入名为 F-Queue
的队列,等待 Finalizer 线程执行其 finalized()
方法。在这个过程中,收集器会进行第二次小规模的标记,如果对象在 finalized()
方法中重新将自己与引用链上的任何一个对象进行了关联,如将自己(this 关键字)赋值给某个类变量或者对象的成员变量,此时它就实现了自我拯救,则第二次标记会将其移除 “即将回收” 的集合,否则该对象就将被真正回收,走向死亡。 \n \n4.2 方法区回收 \n在 Java 堆上进行对象回收的性价比通常比较高,因为大多数对象都是朝生夕灭的。而方法区由于回收条件比较苛刻,对应的回收性价比通常比较低,主要回收两部分内容:废弃的常量和不再使用的类型。
\n4.3 垃圾收集算法 \n1. 分代收集理论 \n当前大多数虚拟机都遵循 “分代收集” 的理论进行设计,它建立在强弱两个分代假说下:
\n\n弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是朝生夕灭的。 \n强分代假说 (Strong Generational Hypothesis) :熬过越多次垃圾收集过程的对象就越难以消亡。 \n跨带引用假说 (Intergenerational Reference Hypothesis) :基于上面两条假说还可以得出的一条隐含推论:存在相互引用关系的两个对象,应该倾向于同时生存或者同时消亡。 \n \n强弱分代假说奠定了垃圾收集器的设计原则:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄就是对象经历垃圾收集的次数)分配到不同的区域中进行存储。之后如果一个区域中的对象都是朝生夕灭的,那么收集器只需要关注少量对象的存活而不是去标记那些大量将要被回收的对象,此时就能以较小的代价获取较大的空间。最后再将难以消亡的对象集中到一块,根据强分代假说,它们是很难消亡的,因此虚拟机可以使用较低的频率进行回收,这就兼顾了时间和内存空间的开销。
\n2. 回收类型 \n根据分代收集理论,收集范围可以分为以下几种类型:
\n\n部分收集 (Partial GC) :具体分为:\n\n新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集; \n老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集; \n混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。 \n \n \n整堆收集 (Full GC) :收集整个 Java 堆和方法区。 \n \n3. 标记-清除算法 \n它是最基础的垃圾收集算法,收集过程分为两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;也可以反过来,标记存活对象,统一回收所有未被标记的对象。
\n\n它主要有以下两个缺点:
\n\n执行效率不稳定:如果 Java 堆上包含大量需要回收的对象,则需要进行大量标记和清除动作; \n内存空间碎片化:标记清除后会产生大量不连续的空间,从而可能导致无法为大对象分配足够的连续内存。 \n \n4. 标记-复制算法 \n标记-复制算法基于 ”半区复制“ 算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存使用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的那块内存空间一次性清理掉。其优点在于避免了内存空间碎片化的问题,其缺点如下:
\n\n如果内存中多数对象都是存活的,这种算法将产生大量的复制开销; \n浪费内存空间,内存空间变为了原有的一半。 \n \n\n基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 Eden
和 两块较小的 Survivor
空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 Eden
和其中的一块 Survivor
,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 Survivor
上,这样只有 10% 的内存空间会被浪费掉。当 Survivor
空间不足以容纳一次 Minor GC
时,此时由其他内存区域(通常是老年代)来进行分配担保。
\n5. 标记-整理算法 \n标记-整理算法是在标记完成后,让所有存活对象都向内存的一端移动,然后直接清理掉边界以外的内存。其优点在于可以避免内存空间碎片化的问题,也可以充分利用内存空间;其缺点在于根据所使用的收集器的不同,在移动存活对象时可能要全程暂停用户程序:
\n\n五、经典垃圾收集器 \n并行与并发是并发编程中的专有名词,在谈论垃圾收集器的上下文语境中,它们的含义如下:
\n\nHotSpot 虚拟机中一共存在七款经典的垃圾收集器:
\n\n\n注:收集器之间存在连线,则代表它们可以搭配使用。
\n \n5.1 Serial 收集器 \nSerial 收集器是最基础、历史最悠久的收集器,它是一个单线程收集器,在进行垃圾回收时,必须暂停其他所有的工作线程,直到收集结束,这是其主要缺点。它的优点在于单线程避免了多线程复杂的上下文切换,因此在单线程环境下收集效率非常高,由于这个优点,迄今为止,其仍然是 HotSpot 虚拟机在客户端模式下默认的新生代收集器:
\n\n5.2 ParNew 收集器 \n他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收:
\n\n5.3 Parallel Scavenge 收集器 \nParallel Scavenge 也是新生代收集器,基于 标记-复制 算法进行实现,它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值:
\n吞吐量 = \\frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间}\n\nParallel Scavenge 收集器提供两个参数用于精确控制吞吐量:
\n\n-XX:MaxGCPauseMillis :控制最大垃圾收集时间,假设需要回收的垃圾总量不变,那么降低垃圾收集的时间就会导致收集频率变高,所以需要将其设置为合适的值,不能一味减小。 \n-XX:MaxGCTimeRatio :直接用于设置吞吐量大小,它是一个大于 0 小于 100 的整数。假设把它设置为 19,表示此时允许的最大垃圾收集时间占总时间的 5%(即 1/(1+19) );默认值为 99 ,即允许最大 1%( 1/(1+99) )的垃圾收集时间。 \n \n5.4 Serial Old 收集器 \n从名字也可以看出来,它是 Serial 收集器的老年代版本,同样是一个单线程收集器,采用 标记-整理 算法,主要用于给客户端模式下的 HotSpot 虚拟机使用:
\n\n5.5 Paralled Old 收集器 \nParalled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现:
\n\n5.6 CMS 收集器 \nCMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于 标记-清除 算法实现,整个收集过程分为以下四个阶段:
\n\n初始标记 (inital mark) :标记 GC Roots
能直接关联到的对象,耗时短但需要暂停用户线程; \n并发标记 (concurrent mark) :从 GC Roots
能直接关联到的对象开始遍历整个对象图,耗时长但不需要暂停用户线程; \n重新标记 (remark) :采用增量更新算法,对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记,耗时比初始标记稍长且需要暂停用户线程; \n并发清除 (inital sweep) :并发清除掉已经死亡的对象,耗时长但不需要暂停用户线程。 \n \n\n其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下:
\n\n由于涉及并发操作,因此对处理器资源比较敏感。 \n由于是基于 标记-清除 算法实现的,因此会产生大量空间碎片。 \n无法处理浮动垃圾(Floating Garbage):由于并发清除时用户线程还是在继续,所以此时仍然会产生垃圾,这些垃圾就被称为浮动垃圾,只能等到下一次垃圾收集时再进行清理。 \n \n5.7 Garbage First 收集器 \nGarbage First(简称 G1)是一款面向服务端的垃圾收集器,也是 JDK 9 服务端模式下默认的垃圾收集器,它的诞生具有里程碑式的意义。G1 虽然也遵循分代收集理论,但不再以固定大小和固定数量来划分分代区域,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region)。每一个 Region 都可以根据不同的需求来扮演新生代的 Eden
空间、Survivor
空间或者老年代空间,收集器会根据其扮演角色的不同而采用不同的收集策略。
\n\n上面还有一些 Region 使用 H 进行标注,它代表 Humongous,表示这些 Region 用于存储大对象(humongous object,H-obj),即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤:
\n\n初始标记 (Inital Marking) :标记 GC Roots
能直接关联到的对象,并且修改 TAMS(Top at Mark Start)指针的值,让下一阶段用户线程并发运行时,能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针,新分配的对象必须位于这两个指针位置以上,位于这两个指针位置以上的对象默认被隐式标记为存活的,不会纳入回收范围; \n并发标记 (Concurrent Marking) :从 GC Roots
能直接关联到的对象开始遍历整个对象图。遍历完成后,还需要处理 SATB 记录中变动的对象。SATB(snapshot-at-the-beginning,开始阶段快照)能够有效的解决并发标记阶段因为用户线程运行而导致的对象变动,其效率比 CMS 重新标记阶段所使用的增量更新算法效率更高; \n最终标记 (Final Marking) :对用户线程做一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的少量的 STAB 记录。虽然并发标记阶段会处理 SATB 记录,但由于处理时用户线程依然是运行中的,因此依然会有少量的变动,所以需要最终标记来处理; \n筛选回收 (Live Data Counting and Evacuation) :负责更新 Regin 统计数据,按照各个 Regin 的回收价值和成本进行排序,在根据用户期望的停顿时间进行来指定回收计划,可以选择任意多个 Regin 构成回收集。然后将回收集中 Regin 的存活对象复制到空的 Regin 中,再清理掉整个旧的 Regin 。此时因为涉及到存活对象的移动,所以需要暂停用户线程,并由多个收集线程并行执行。 \n \n\n5.8 内存分配原则 \n1. 对象优先在 Eden 分配 \n大多数情况下,对象在新生代的 Eden
区中进行分配,当 Eden
区没有足够空间时,虚拟机将进行一次 Minor GC。
\n2. 大对象直接进入老年代 \n大对象就是指需要大量连续内存空间的 Java 对象,最典型的就是超长的字符串或者元素数量很多的数组,它们将直接进入老年代。主要是因为如果在新生代分配,因为其需要大量连续的内存空间,可能会导致提前触发垃圾回收;并且由于新生代的垃圾回收本身就很频繁,此时复制大对象也需要额外的性能开销。
\n3. 长期存活的对象将进入老年代 \n虚拟机会给每个对象在其对象头中定义一个年龄计数器。对象通常在 Eden
区中诞生,如果经历第一次 Minor GC 后仍然存活,并且能够被 Survivor 容纳的话,该对象就会被移动到 Survivor 中,并将其年龄加 1。对象在 Survivor 中每经过一次 Minor GC,年龄就加 1,当年龄达到一定程度后(由 -XX:MaxTenuringThreshold
设置,默认值为 15)就会进入老年代中。
\n4. 动态年龄判断 \n如果在 Survivor 空间中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等待年龄到达 -XX:MaxTenuringThreshold
设置的值。
\n5. 空间担保分配 \n在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果条件成立,那么这一次的 Minor GC 可以确认是安全的。如果不成立,虚拟机会查看 -XX:HandlePromotionFailure
的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于或者 -XX:HandlePromotionFailure
的值设置不允许冒险,那么就要改为进行一次 Full GC 。
\n六、虚拟机类加载机制 \nJava 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的类加载机制。
\n6.1 类加载时机 \n一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、卸载、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接:
\n\n《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化:
\n\n遇到 new
、 getstatic
、 putstatic
、 invokestatic
这四条字节码指令,如果类型进行过初始化,则需要先触发其进行初始化,能够生成这四条指令码的典型 Java 代码场景有:\n\n使用 new
关键字实例化对象时; \n读取或设置一个类型的静态字段时(被 final 修饰,已在编译期把结果放入常量池的静态字段除外); \n调用一个类型的静态方法时。 \n \n \n使用 java.lang.reflect
包的方法对类型进行反射调用时,如果类型没有进行过初始化、则需要触发其初始化; \n当初始化类时,如发现其父类还没有进行过初始化、则需要触发其父类进行初始化; \n当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类; \n当使用 JDK 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle
实例最后解析的结果为 REF_getStatic
, REF_putStatic
, REF_invokeStatic
, REF_newInvokeSpecial
四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化; \n当一个接口中定义了 JDK 8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那么该接口要在其之前被初始化。 \n \n6.2 类加载过程 \n1. 加载 \n在加载阶段,虚拟机需要完成以下三件事:
\n\n通过一个类的全限定名来获取定义此类的二进制字节流 ; \n将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构; \n在内存中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。 \n \n《Java 虚拟机规范》并没有限制从何处获取二进制流,因此可以从 JAR 包、WAR 包获取,也可以从 JSP 生成的 Class 文件等处获取。
\n2. 验证 \n这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,从而保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证阶段大致会完成下面四项验证:
\n\n文件格式验证 :验证字节流是否符合 Class 文件格式的规范; \n元数据验证 :对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java 语言规范》的要求(如除了 java.lang.Object
外,所有的类都应该有父类); \n字节码验证 :通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的(如允许把子类对象赋值给父类数据类型,但不能把父类对象赋值给子类数据类型); \n符号引用验证 :验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。如果无法验证通过,则会抛出一个java.lang.IncompatibleClassChangeError
的子类异常,如 java.lang.NoSuchFieldError
、 java.lang.NoSuchMethodError
等。 \n \n3. 准备 \n准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段。
\n4. 解析 \n解析是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程:
\n\n符号引用 :符号引用用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。 \n直接引用 :直接引用是指可以直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。 \n \n整个解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行解析。
\n5. 初始化 \n初始化阶段就是执行类构造器的 <clinit>()
方法的过程,该方法具有以下特点:
\n\n<clinit>()
方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生,编译器收集顺序由语句在源文件中出现的顺序决定。 \n<clinit>()
方法与类的构造器函数(即在虚拟机视角中的实例构造器 <init>()
方法)不同,它不需要显示的调用父类的构造器,Java 虚拟机会保证在子类的 <clinit>()
方法执行前,父类的 <clinit>()
方法已经执行完毕。 \n由于父类的 <clinit>()
方法先执行,也就意味着父类中定义的静态语句块要优先于子类变量的赋值操作。 \n<clinit>()
方法对于类或者接口不是必须的,如果一个类中没有静态语句块,也没有对变量进行赋值操作,那么编译器可以不为这个类生成 <clinit>()
方法。 \n接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>()
方法。 \nJava 虚拟机必须保证一个类的 <clinit>()
方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的 <clinit>()
方法,其他线程都需要阻塞等待。 \n \n6.3 类加载器 \n能够通过一个类的全限定名来获取描述该类的二进制字节流的工具称为类加载器。每一个类加载器都拥有一个独立的类名空间,因此对于任意一个类,都必须由加载它的类加载器和这个类本身来共同确立其在 Java 虚拟机中的唯一性。这意味着要想比较两个类是否相等,必须在同一类加载器加载的前提下;如果两个类的类加载器不同,则它们一定不相等。
\n6.4 双亲委派模型 \n从 Java 虚拟机角度而言,类加载器可以分为以下两类:
\n\n启动类加载器 :启动类加载器(Bootstrap ClassLoader)由 C++ 语言实现(以 HotSpot 为例),它是虚拟机自身的一部分; \n其他所有类的类加载器 :由 Java 语言实现,独立存在于虚拟机外部,并且全部继承自 java.lang.ClassLoader
。 \n \n从开发人员角度而言,类加载器可以分为以下三类:
\n\n启动类加载器 (Boostrap Class Loader) :负责把存放在 <JAVA_HOME>\\lib
目录中,或被 -Xbootclasspath
参数所指定的路径中存放的能被 Java 虚拟机识别的类库加载到虚拟机的内存中; \n扩展类加载器 (Extension Class Loader) :负责加载 <JAVA_HOME>\\lib\\ext
目录中,或被 java.ext.dirs
系统变量所指定的路径中的所有类库。 \n应用程序类加载器 (Application Class Loader) :负责加载用户类路径(ClassPath)上的所有的类库。 \n \nJDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成加载:
\n\n上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。
\n双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类,否则就有可能出现一个程序中存在两个不同的 java.lang.Object
的情况。
\n6.5 模块化下的类加载器 \nJDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
\n\n仍维持三层类加载器和双亲委派的架构,但扩展类加载器被平台类加载器所取代; \n当平台及应用程序类加载器收到类加载请求时,要首先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载; \n启动类加载器、平台类加载器、应用程序类加载器全部继承自 java.internal.loader.BuiltinClassLoader
,BuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。 \n \n\n七、程序编译 \n7.1 编译器分类 \n\n前端编译器 :把 *.java
文件转变成 .class
文件的过程;如 JDK 的 Javac,Eclipse JDT 中的增量式编译器。 \n即使编译器 :常称为 JIT 编译器(Just In Time Complier),在运行期把字节码转变成本地机器码的过程;如 HotSpot 虚拟机中的 C1、C2 编译器,Graal 编译器。 \n提前编译器 :直接把程序编译成目标机器指令集相关的二进制代码的过程。如 JDK 的 jaotc,GUN Compiler for the Java(GCJ),Excelsior JET 。 \n \n7.2 解释器与编译器 \n在 HotSpot 虚拟机中,Java 程序最初都是通过解释器(Interpreter)进行解释执行的,其优点在于可以省去编译时间,让程序快速启动。当程序启动后,如果虚拟机发现某个方法或代码块的运行特别频繁,就会使用编译器将其编译为本地机器码,并使用各种手段进行优化,从而提高执行效率,这就是即时编译器。HotSpot 内置了两个(或三个)即时编译器:
\n\n客户端编译器 (Client Complier) :简称 C1; \n服务端编译器 (Servier Complier) :简称 C2,在有的资料和 JDK 源码中也称为 Opto 编译器; \nGraal 编译器 :在 JDK 10 时才出现,长期目标是替代 C2。 \n \n在分层编译的工作模式出现前,采用客户端编译器还是服务端编译器完全取决于虚拟机是运行在客户端模式还是服务端模式下,可以在启动时通过 -client
或 -server
参数进行指定,也可以让虚拟机根据自身版本和宿主机性能来自主选择。
\n7.3 分层编译 \n要编译出优化程度越高的代码通常都需要越长的编译时间,为了在程序启动速度与运行效率之间达到最佳平衡,HotSpot 虚拟机在编译子系统中加入了分层编译(Tiered Compilation):
\n\n第 0 层 :程序纯解释执行,并且解释器不开启性能监控功能; \n第 1 层 :使用客户端编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启性能监控功能; \n第 2 层 :仍然使用客户端编译执行,仅开启方法及回边次数统计等有限的性能监控; \n第 3 层 :仍然使用客户端编译执行,开启全部性能监控; \n第 4 层 :使用服务端编译器将字节码编译为本地代码,其耗时更长,并且会根据性能监控信息进行一些不可靠的激进优化。 \n \n以上层次并不是固定不变的,根据不同的运行参数和版本,虚拟机可以调整分层的数量。各层次编译之间的交互转换关系如下图所示:
\n\n实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。
\n7.4 热点探测 \n即时编译器编译的目标是 “热点代码”,它主要分为以下两类:
\n\n被多次调用的方法。 \n被多次执行循环体。这里指的是一个方法只被少量调用过,但方法体内部存在循环次数较多的循环体,此时也认为是热点代码。但编译器编译的仍然是循环体所在的方法,而不会单独编译循环体。 \n \n判断某段代码是否是热点代码的行为称为 “热点探测” (Hot Spot Code Detection),主流的热点探测方法有以下两种:
\n\n基于采样的热点探测 (Sample Based Hot Spot Code Detection) :采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶,如果发现某个(或某些)方法经常出现在栈顶,那么就认为它是 “热点方法”。 \n基于计数的热点探测 (Counter Based Hot Spot Code Detection) :采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是 “热点方法”。 \n \n八、代码优化 \n即时编译器除了将字节码编译为本地机器码外,还会对代码进行一定程度的优化,它包含多达几十种优化技术,这里选取其中代表性的四种进行介绍:
\n8.1 方法内联 \n最重要的优化手段,它会将目标方法中的代码原封不动地 “复制” 到发起调用的方法之中,避免发生真实的方法调用,并采用名为类型继承关系分析(Class Hierarchy Analysis,CHA)的技术来解决虚方法(Java 语言中默认的实例方法都是虚方法)的内联问题。
\n8.2 逃逸分析 \n逃逸行为主要分为以下两类:
\n\n方法逃逸 :当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,此时称为方法逃逸; \n线程逃逸 :当一个对象在方法里面被定义后,它可能被外部线程所访问,例如赋值给可以在其他线程中访问的实例变量,此时称为线程,其逃逸程度高于方法逃逸。 \n \npublic static StringBuilder concat (String... strings) {\n StringBuilder sb = new StringBuilder();\n for (String string : strings) {\n sb.append(string);\n }\n return sb; \n}\n\npublic static String concat (String... strings) {\n StringBuilder sb = new StringBuilder();\n for (String string : strings) {\n sb.append(string);\n }\n return sb.toString(); \n}\n
\n如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可以为这个对象实例采取不同程序的优化:
\n\n栈上分配 (Stack Allocations) :如果一个对象不会逃逸到线程外,那么将会在栈上分配内存来创建这个对象,而不是 Java 堆上,此时对象所占用的内存空间就会随着栈帧的出栈而销毁,从而可以减轻垃圾回收的压力。 \n标量替换 (Scalar Replacement) :如果一个数据已经无法再分解成为更小的数据类型,那么这些数据就称为标量(如 int、long 等数值类型及 reference 类型等);反之,如果一个数据可以继续分解,那它就被称为聚合量(如对象)。如果一个对象不会逃逸外方法外,那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代,从而减少内存占用。 \n同步消除 (Synchronization Elimination) :如果一个变量不会逃逸出线程,那么对这个变量实施的同步措施就可以消除掉。 \n \n8.3 公共子表达式消除 \n如果一个表达式 E 之前已经被计算过了,并且从先前的计算到现在 E 中所有变量的值都没有发生过变化,那么 E 这次的出现就称为公共子表达式。对于这种表达式,无需再重新进行计算,只需要直接使用前面的计算结果即可。
\n8.4 数组边界检查消除 \n对于虚拟机执行子系统来说,每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中,并且使用循环变量来访问数据,即循环变量的取值永远在 [0,list.length) 之间,那么此时就可以消除整个循环的数据边界检查,从而避免多次无用的判断。
\n参考资料 \n\n",
+ "link": "\\zh-cn\\blog\\java\\javavm.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/java/springAnnotation.html b/zh-cn/blog/java/springAnnotation.html
new file mode 100644
index 0000000..d571cd0
--- /dev/null
+++ b/zh-cn/blog/java/springAnnotation.html
@@ -0,0 +1,220 @@
+
+
+
+
+
+
+
+
+
+ springAnnotation
+
+
+
+
+ Spring 中的 18 个注解
+1 @Controller
+标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.
+@Controller
+public class TestController {
+
+ public String test (Map<String,Object> map) {
+ return "hello" ;
+ }
+}
+
+2 @RestController
+Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
+3 @Service
+用于标注业务层组件,说白了就是加入你有一个用注解的方式把这个类注入到spring配置中
+4 @Autowired
+用来装配bean,都可以写在字段上,或者方法上。
+默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:
+@Autowired (required=false )
+
+5 @RequestMapping
+类定义处: 提供初步的请求映射信息,相对于 WEB 应用的根目录。
+方法处: 提供进一步的细分映射信息,相对于类定义处的 URL。
+6 @RequestParam
+用于将请求参数区数据映射到功能处理方法的参数上
+例如
+public Resp test (@RequestParam Integer id) {
+ return Resp.success(customerInfoService.fetch(id));
+}
+
+这个id就是要接收从接口传递过来的参数id的值的,如果接口传递过来的参数名和你接收的不一致,也可以如下
+public Resp test (@RequestParam(value="course_id" ) Integer id) {
+ return Resp.success(customerInfoService.fetch(id));
+}
+
+其中course_id就是接口传递的参数,id就是映射course_id的参数名
+7 @ModelAttribute
+使用地方如下:
+
+标记在方法上
+
+标记在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中。
+(1). 在有返回的方法上:当ModelAttribute设置了value,方法返回的值会以这个value为key,以参数接受到的值作为value,存入到Model中,如下面的方法执行之后,最终相当于
+model.addAttribute("user_name", name);假如 @ModelAttribute没有自定义value,则相当于
+model.addAttribute("name", name);
+@ModelAttribute (value="user_name" )
+public String before (@RequestParam(required = false ) String Name,Model model) {
+ System.out.println("name is " +name);
+}
+
+(2) 在没返回的方法上:
+需要手动model.add方法
+@ModelAttribute
+public void before (@RequestParam(required = false ) Integer age,Model model) {
+ model.addAttribute("age" ,age);
+ System.out.println("age is " +age);
+}
+
+
+标记在方法的参数上
+
+标记在方法的参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用.我们在上面的类中加入一个方法如下
+
+public Resp model (@ModelAttribute("user_name" ) String user_name,
+ @ModelAttribute ("name" ) String name,
+ @ModelAttribute ("age" ) Integer age,Model model) {
+ System.out.println("user_name=" +user_name+" name=" +name+" age=" +age);
+ System.out.println("model=" +model);
+ }
+
+用在方法参数中的@ModelAttribute注解,实际上是一种接受参数并且自动放入Model对象中,便于使用。
+8 @Cacheable
+用来标记缓存查询。可用用于方法或者类中,当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。
+参数列表
+
+
+
+参数
+解释
+例子
+
+
+
+
+value
+名称
+@Cacheable(value={"c1","c2"})
+
+
+key
+key
+@Cacheable(value="c1",key="#id")
+
+
+condition
+条件
+@Cacheable(value="c1",condition="#id=1")
+
+
+
+比如@Cacheable(value="UserCache") 标识的是当调用了标记了这个注解的方法时,逻辑默认加上从缓存中获取结果的逻辑,如果缓存中没有数据,则执行用户编写查询逻辑,查询成功之后,同时将结果放入缓存中。
+但凡说到缓存,都是key-value的形式的,因此key就是方法中的参数(id),value就是查询的结果,而命名空间UserCache是在spring*.xml中定义。
+@Cacheable (value="UserCache" )
+public int getUserAge (int id) {
+ int age=getAgeById(id);
+ return age;
+}
+
+9 @CacheEvict
+@CacheEvict用来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。
+@CacheEvict(value=”UserCache”)
+参数列表
+
+
+
+参数
+解释
+例子
+
+
+
+
+value
+名称
+@CacheEvict(value={"c1","c2"})
+
+
+key
+key
+@CacheEvict(value="c1",key="#id")
+
+
+condition
+缓存得条件可为空
+
+
+
+allEntries
+是否清空所有内容
+@CacheEvict(value="c1",allEntries=true)
+
+
+beforeInvocation
+是否在方法执行前清空
+@CacheEvict(value="c1",beforeInvocation=true)
+
+
+
+10 @Resource
+@Resource的作用相当于@Autowired
+只不过@Autowired按byType自动注入,
+而@Resource默认按 byName自动注入罢了。
+@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
+@Resource装配顺序:
+1、如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
+2、如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
+3、如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
+4、如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
+11 @PostConstruct
+@PostConstruct用来标记是在项目启动的时候执行这个方法。用来修饰一个非静态的void()方法
+也就是spring容器启动时就执行,多用于一些全局配置、数据字典之类的加载
+被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行之后执行
+12 @PreDestory
+被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前
+13 @Repository
+@Repository用于标注数据访问组件,即DAO组件
+14 @Component
+@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
+15 @Scope
+@Scope用来配置 spring bean 的作用域,它标识 bean 的作用域。
+默认值是单例
+1、singleton:单例模式,全局有且仅有一个实例
+2、prototype:原型模式,每次获取Bean的时候会有一个新的实例
+3、request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
+4、session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
+5、global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。
+16 @SessionAttributes
+默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中
+参数:
+1、names:这是一个字符串数组。里面应写需要存储到session中数据的名称。
+2、types:根据指定参数的类型,将模型中对应类型的参数存储到session中
+3、value:和names是一样的。
+@Controller
+@SessionAttributes (value={"names" },types={Integer.class})
+public class ScopeService {
+ @RequestMapping ("/testSession" )
+ public String test (Map<String,Object> map) {
+ map.put("names" ,Arrays.asList("a" ,"b" ,"c" ));
+ map.put("age" ,12 );
+ return "hello" ;
+ }
+}
+
+17 @Required
+适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。
+18 @Qualifier
+@Qualifier当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/java/springAnnotation.json b/zh-cn/blog/java/springAnnotation.json
new file mode 100644
index 0000000..c047fe2
--- /dev/null
+++ b/zh-cn/blog/java/springAnnotation.json
@@ -0,0 +1,6 @@
+{
+ "filename": "springAnnotation.md",
+ "__html": "Spring 中的 18 个注解 \n1 @Controller \n标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.
\n@Controller \npublic class TestController {\n\n public String test (Map<String,Object> map) {\n return \"hello\" ;\n }\n}\n
\n2 @RestController \nSpring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
\n3 @Service \n用于标注业务层组件,说白了就是加入你有一个用注解的方式把这个类注入到spring配置中
\n4 @Autowired \n用来装配bean,都可以写在字段上,或者方法上。
\n默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,例如:
\n@Autowired (required=false )\n
\n5 @RequestMapping \n类定义处: 提供初步的请求映射信息,相对于 WEB 应用的根目录。
\n方法处: 提供进一步的细分映射信息,相对于类定义处的 URL。
\n6 @RequestParam \n用于将请求参数区数据映射到功能处理方法的参数上
\n例如
\npublic Resp test (@RequestParam Integer id) {\n return Resp.success(customerInfoService.fetch(id));\n}\n
\n这个id就是要接收从接口传递过来的参数id的值的,如果接口传递过来的参数名和你接收的不一致,也可以如下
\npublic Resp test (@RequestParam(value=\"course_id\" ) Integer id) {\n return Resp.success(customerInfoService.fetch(id));\n}\n
\n其中course_id就是接口传递的参数,id就是映射course_id的参数名
\n7 @ModelAttribute \n使用地方如下:
\n\n标记在方法上 \n \n标记在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中。
\n(1). 在有返回的方法上:当ModelAttribute设置了value,方法返回的值会以这个value为key,以参数接受到的值作为value,存入到Model中,如下面的方法执行之后,最终相当于
\nmodel.addAttribute("user_name", name);假如 @ModelAttribute没有自定义value,则相当于
\nmodel.addAttribute("name", name);
\n@ModelAttribute (value=\"user_name\" )\npublic String before (@RequestParam(required = false ) String Name,Model model) {\n System.out.println(\"name is \" +name);\n}\n
\n(2) 在没返回的方法上:
\n需要手动model.add方法
\n@ModelAttribute \npublic void before (@RequestParam(required = false ) Integer age,Model model) {\n model.addAttribute(\"age\" ,age);\n System.out.println(\"age is \" +age);\n}\n
\n\n标记在方法的参数上 \n \n标记在方法的参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用.我们在上面的类中加入一个方法如下
\n\npublic Resp model (@ModelAttribute(\"user_name\" ) String user_name,\n @ModelAttribute (\"name\" ) String name,\n @ModelAttribute (\"age\" ) Integer age,Model model) {\n System.out.println(\"user_name=\" +user_name+\" name=\" +name+\" age=\" +age);\n System.out.println(\"model=\" +model);\n }\n
\n用在方法参数中的@ModelAttribute注解,实际上是一种接受参数并且自动放入Model对象中,便于使用。
\n8 @Cacheable \n用来标记缓存查询。可用用于方法或者类中,当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。
\n参数列表
\n\n\n\n参数 \n解释 \n例子 \n \n \n\n\nvalue \n名称 \n@Cacheable(value={"c1","c2"}) \n \n\nkey \nkey \n@Cacheable(value="c1",key="#id") \n \n\ncondition \n条件 \n@Cacheable(value="c1",condition="#id=1") \n \n \n
\n比如@Cacheable(value="UserCache") 标识的是当调用了标记了这个注解的方法时,逻辑默认加上从缓存中获取结果的逻辑,如果缓存中没有数据,则执行用户编写查询逻辑,查询成功之后,同时将结果放入缓存中。
\n但凡说到缓存,都是key-value的形式的,因此key就是方法中的参数(id),value就是查询的结果,而命名空间UserCache是在spring*.xml中定义。
\n@Cacheable (value=\"UserCache\" )\npublic int getUserAge (int id) {\n int age=getAgeById(id);\n return age;\n}\n
\n9 @CacheEvict \n@CacheEvict用来标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。
\n@CacheEvict(value=”UserCache”)
\n参数列表
\n\n\n\n参数 \n解释 \n例子 \n \n \n\n\nvalue \n名称 \n@CacheEvict(value={"c1","c2"}) \n \n\nkey \nkey \n@CacheEvict(value="c1",key="#id") \n \n\ncondition \n缓存得条件可为空 \n \n \n\nallEntries \n是否清空所有内容 \n@CacheEvict(value="c1",allEntries=true) \n \n\nbeforeInvocation \n是否在方法执行前清空 \n@CacheEvict(value="c1",beforeInvocation=true) \n \n \n
\n10 @Resource \n@Resource的作用相当于@Autowired
\n只不过@Autowired按byType自动注入,
\n而@Resource默认按 byName自动注入罢了。
\n@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
\n@Resource装配顺序:
\n1、如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
\n2、如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
\n3、如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
\n4、如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
\n11 @PostConstruct \n@PostConstruct用来标记是在项目启动的时候执行这个方法。用来修饰一个非静态的void()方法
\n也就是spring容器启动时就执行,多用于一些全局配置、数据字典之类的加载
\n被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy()方法在destroy()方法执行之后执行
\n12 @PreDestory \n被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前
\n13 @Repository \n@Repository用于标注数据访问组件,即DAO组件
\n14 @Component \n@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
\n15 @Scope \n@Scope用来配置 spring bean 的作用域,它标识 bean 的作用域。
\n默认值是单例
\n1、singleton:单例模式,全局有且仅有一个实例
\n2、prototype:原型模式,每次获取Bean的时候会有一个新的实例
\n3、request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
\n4、session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
\n5、global session:只在portal应用中有用,给每一个 global http session 新建一个Bean实例。
\n16 @SessionAttributes \n默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中
\n参数:
\n1、names:这是一个字符串数组。里面应写需要存储到session中数据的名称。
\n2、types:根据指定参数的类型,将模型中对应类型的参数存储到session中
\n3、value:和names是一样的。
\n@Controller \n@SessionAttributes (value={\"names\" },types={Integer.class})\npublic class ScopeService {\n @RequestMapping (\"/testSession\" )\n public String test (Map<String,Object> map) {\n map.put(\"names\" ,Arrays.asList(\"a\" ,\"b\" ,\"c\" ));\n map.put(\"age\" ,12 );\n return \"hello\" ;\n }\n}\n
\n17 @Required \n适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。
\n18 @Qualifier \n@Qualifier当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
\n",
+ "link": "\\zh-cn\\blog\\java\\springAnnotation.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/linux/ope.html b/zh-cn/blog/linux/ope.html
index 9337f62..1c42e6c 100644
--- a/zh-cn/blog/linux/ope.html
+++ b/zh-cn/blog/linux/ope.html
@@ -79,6 +79,84 @@ ssh实现端口转发
记住:前提是先进行秘钥传输。
命令执行完后,访问192.168.1.15:9200端口则真实是访问192.168.1.19:9200端口。
+grep命令进阶
+grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来
+grep搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。
+
+^ #锚定行的开始 如:'^grep'匹配所有以grep开头的行。
+$
+. #匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。
+* #匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。
+.* #一起用代表任意字符。
+[] #匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。
+[^] #匹配一个不在指定范围内的字符
+\(..\) #标记匹配字符,如'\(love\)',love被标记为1。
+\< #锚定单词的开始,如:'\<grep'匹配包含以grep开头的单词的行。
+\> #锚定单词的结束,如'grep\>'匹配包含以grep结尾的单词的行。
+x\{m\} #重复字符x,m次,如:'0\{5\}'匹配包含5个o的行。
+x\{m,\} #重复字符x,至少m次,如:'o\{5,\}'匹配至少有5个o的行。
+x\{m,n\}#重复字符x,至少m次,不多于n次,如:'o\{5,10\}'匹配5--10个o的行。
+\w #匹配文字和数字字符,也就是[A-Za-z0-9],
+\W #\w的反置形式,匹配一个或多个非单词字符,如点号句号等。
+\b #单词锁定符,如: '\bgrep\b'只匹配grep。
+
+
+-n 打印行号
+ grep -n ".*" h.txt 所有打印行号
+ grep -n "root" h.txt 匹配的内容显示行号
+-v 不包括
+-E 表示过滤 多个参数
+ grep -Ev "sshd|network|crond|sysstat|"
+-o:仅打印你需要的东西,默认打印正行
+ grep -o "hello" h.txt
+-i:忽略大小写
+ grep -i "hello" h.txt
+-c: 用于统计文中出现的次数
+--color=auto 过滤字段添加颜色
+ 利用正则打印特定字符
+\b:作为边界符,边界只包含特定字符的行
+ grep "\boldboy\b" /etc/passwd -->只过滤包含oldboy的行
+
+
+egrep: == grep -E 用于显示文件中符合条件的字符
+ env|egrep "USER|MAIL|PWD|LOGNAME"
+ 用的表达式不一样 ,egerp更加规范
+egrep -o "oldboy|hello" h.txt -->仅仅输出 oldboy 和 hello
+
+
+# 查找指定关键字个数
+grep '\bboot\b' logs_bak.txt 【\b单词锁定符,只匹配boot】
+# 输出logs_bak.txt 文件中含有从logs.txt文件中读取出的关键词的内容行
+cat logs_bak.txt
+ cat logs.txt
+ cat logs.txt | grep -nf logs_bak.txt
+# 从多个文件中查找关键词
+grep "omc" /etc/passwd /etc/shadow 【多文件查询时,会用冒号前添加文件名】
+# 打印IP信息
+ifconfig eth0|grep -E "([0-9]{1,3}\.){3}" 【-E 表达式匹配,用小括号括起来表示一个整体】
+# 同时过滤多个关键字
+cat /etc/passwd|grep -E "boy|omc"
+ ==> cat /etc/passwd|egrep "omc|boy" 【用 | 划分多个关键字】
+# 显示当前目录下面以.txt 结尾的文件中的所有包含每个字符串至少有7个连续小写字符的字符串的行
+grep '\w\{7\}' *.txt
+ ==> grep '[a-z]\{7\}' *.txt 【注意特殊字符的转义】
+
+
+# A 查询匹配内容的一行之外,后n行的显示
+ # B 查询匹配内容的一行之外,前n行的显示
+ # C 查询匹配内容的一行之外,显示上下n行
+grep -n 'yum' -A 3 logs_bak.txt
+
diff --git a/zh-cn/blog/linux/ope.json b/zh-cn/blog/linux/ope.json
index c392a2a..1ce91e9 100644
--- a/zh-cn/blog/linux/ope.json
+++ b/zh-cn/blog/linux/ope.json
@@ -1,6 +1,6 @@
{
"filename": "ope.md",
- "__html": "#逼格高又实用的 Linux 命令,开发、运维一定要懂!
\n实用的 xargs 命令 \n在平时的使用中,我认为xargs这个命令还是较为重要和方便的。我们可以通过使用这个命令,将命令输出的结果作为参数传递给另一个命令。
\n比如说我们想找出某个路径下以 .conf 结尾的文件,并将这些文件进行分类,那么普通的做法就是先将以 .conf 结尾的文件先找出来,然后输出到一个文件中,接着cat这个文件,并使用file文件分类命令去对输出的文件进行分类。这个普通的方法还的确是略显麻烦,那么这个时候xargs命令就派上用场了。
\n例1:找出 / 目录下以.conf 结尾的文件,并进行文件分类
\n命令:
\nfind / -name *.conf -type f -print | xargs file\n
\n输出结果如下所示:
\n
\n命令或脚本后台运行 \n有时候我们进行一些操作的时候,不希望我们的操作在终端会话断了之后就跟着断了,特别是一些数据库导入导出操作,如果涉及到大数据量的操作,我们不可能保证我们的网络在我们的操作期间不出问题,所以后台运行脚本或者命令对我们来说是一大保障。
\n比如说我们想把数据库的导出操作后台运行,并且将命令的操作输出记录到文件,那么我们可以这么做:
\nnohup mysqldump -uroot -pxxxxx --all-databases > ./alldatabases.sql &(xxxxx是密码)\n
\n当然如果你不想密码明文,你还可以这么做:
\nnohup mysqldump -uroot -p --all-databases > ./alldatabases.sql (后面不加&符号)\n
\n执行了上述命令后,会提示叫你输入密码,输入密码后,该命令还在前台运行,但是我们的目的是后天运行该命令,这个时候你可以按下Ctrl+Z,然后在输入bg就可以达到第一个命令的效果,让该命令后台运行,同时也可以让密码隐蔽输入。
\n命令后台执行的结果会在命令执行的当前目录下留下一个 nohup.out 文件,查看这个文件就知道命令有没有执行报错等信息。
\n找出当前系统内存使用量较高的进程 \n在很多运维的时候,我们发现内存耗用较为严重,那么怎么样才能找出内存消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 4 | head -20\n
\n \n输出的第4列就是内存的耗用百分比。最后一列就是相对应的进程。
\n找出当前系统CPU使用量较高的进程 \n在很多运维的时候,我们发现CPU耗用较为严重,那么怎么样才能找出CPU消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 3 | head -20\n
\n
\n输出的第3列为CPU的耗用百分比,最后一列就是对应的进程。
\n我想大家应该也发现了,sort 命令后的3、4其实就是代表着第3列进行排序、第4列进行排序。
\n同时查看多个日志或数据文件 \n在日常工作中,我们查看日志文件的方式可能是使用tail命令在一个个的终端查看日志文件,一个终端就看一个日志文件。包括我在内也是,但是有时候也会觉得这种方式略显麻烦,其实有个工具叫做 multitail 可以在同一个终端同时查看多个日志文件。
\n首先安装 multitail:
\n# wget ftp://ftp.is.co.za/mirror/ftp.rpmforge.net/redhat/el6/en/x86_64/dag/RPMS/multitail-5.2.9-1.el6.rf.x86_64.rpm\n# yum -y localinstall multitail-5.2.9-1.el6.rf.x86_64.rpm\n
\nmultitail 工具支持文本的高亮显示,内容过滤以及更多你可能需要的功能。
\n如下就来一个有用的例子:
\n此时我们既想查看secure的日志指定过滤关键字输出,又想查看实时的网络ping情况:
\n命令如下:
\n# multitail -e "Accepted" /var/log/secure -l "ping baidu.com"\n
\n
\n是不是很方便?如果平时我们想查看两个日志之间的关联性,可以观察日志输出是否有触发等。如果分开两个终端可能来回进行切换有点浪费时间,这个multitail工具查看未尝不是一个好方法。
\n持续 ping 并将结果记录到日志 \n这个时候你去ping几个包把结果丢出来,人家会反驳你,刚刚那段时间有问题而已,现在业务都恢复正常了,网络肯定正常啊,这个时候估计你要气死。
\n你要是再拿出zabbix等网络监控的数据,这个时候就不太妥当了,zabbix的采集数据间隔你不可能设置成1秒钟1次吧?小编就遇到过这样的问题,结果我通过以下的命令进行了ping监控采集。然后再有人让我背锅的时候,我把出问题时间段的ping数据库截取出来,大家公开谈,结果那次被我叼杠回去了,以后他们都不敢轻易甩锅了,这个感觉好啊。
\n命令:
\nping api.jpush.cn | awk '{ print $0"\\t" strftime("%Y-%m-%d %H:%M:%S",systime()) } ' >> /tmp/jiguang.log &`\n
\n输出的结果会记录到/tmp/jiguang.log 中,每秒钟新增一条ping记录,如下:
\n
\nssh实现端口转发 \n可能很多的朋友都听说过ssh是linux下的远程登录安全协议,就是通俗的远程登录管理服务器。但是应该很少朋友会听说过ssh还可以做端口转发。其实ssh用来做端口转发的功能还是很强大的,下面就来做示范。
\n实例背景:我们公司是有堡垒机的,任何操作均需要在堡垒机上进行,有写开发人员需要访问ELasticSearch的head面板查看集群状态,但是我们并不想将ElasticSearch的9200端口映射出去,依然想通过堡垒机进行访问。
\n所以才会将通往堡垒机(192.168.1.15)的请求转发到服务器ElasticSearch(192.168.1.19)的9200上。
\n例子:\n将发往本机(192.168.1.15)的9200端口访问转发到192.168.1.19的9200端口
\nssh -p 22 -C -f -N -g -L 9200:192.168.1.19:9200 ihavecar@192.168.1.19`\n
\n记住:前提是先进行秘钥传输。
\n命令执行完后,访问192.168.1.15:9200端口则真实是访问192.168.1.19:9200端口。
\n",
+ "__html": "#逼格高又实用的 Linux 命令,开发、运维一定要懂!
\n实用的 xargs 命令 \n在平时的使用中,我认为xargs这个命令还是较为重要和方便的。我们可以通过使用这个命令,将命令输出的结果作为参数传递给另一个命令。
\n比如说我们想找出某个路径下以 .conf 结尾的文件,并将这些文件进行分类,那么普通的做法就是先将以 .conf 结尾的文件先找出来,然后输出到一个文件中,接着cat这个文件,并使用file文件分类命令去对输出的文件进行分类。这个普通的方法还的确是略显麻烦,那么这个时候xargs命令就派上用场了。
\n例1:找出 / 目录下以.conf 结尾的文件,并进行文件分类
\n命令:
\nfind / -name *.conf -type f -print | xargs file\n
\n输出结果如下所示:
\n
\n命令或脚本后台运行 \n有时候我们进行一些操作的时候,不希望我们的操作在终端会话断了之后就跟着断了,特别是一些数据库导入导出操作,如果涉及到大数据量的操作,我们不可能保证我们的网络在我们的操作期间不出问题,所以后台运行脚本或者命令对我们来说是一大保障。
\n比如说我们想把数据库的导出操作后台运行,并且将命令的操作输出记录到文件,那么我们可以这么做:
\nnohup mysqldump -uroot -pxxxxx --all-databases > ./alldatabases.sql &(xxxxx是密码)\n
\n当然如果你不想密码明文,你还可以这么做:
\nnohup mysqldump -uroot -p --all-databases > ./alldatabases.sql (后面不加&符号)\n
\n执行了上述命令后,会提示叫你输入密码,输入密码后,该命令还在前台运行,但是我们的目的是后天运行该命令,这个时候你可以按下Ctrl+Z,然后在输入bg就可以达到第一个命令的效果,让该命令后台运行,同时也可以让密码隐蔽输入。
\n命令后台执行的结果会在命令执行的当前目录下留下一个 nohup.out 文件,查看这个文件就知道命令有没有执行报错等信息。
\n找出当前系统内存使用量较高的进程 \n在很多运维的时候,我们发现内存耗用较为严重,那么怎么样才能找出内存消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 4 | head -20\n
\n \n输出的第4列就是内存的耗用百分比。最后一列就是相对应的进程。
\n找出当前系统CPU使用量较高的进程 \n在很多运维的时候,我们发现CPU耗用较为严重,那么怎么样才能找出CPU消耗的进程排序呢?
\n命令:
\nps -aux | sort -rnk 3 | head -20\n
\n
\n输出的第3列为CPU的耗用百分比,最后一列就是对应的进程。
\n我想大家应该也发现了,sort 命令后的3、4其实就是代表着第3列进行排序、第4列进行排序。
\n同时查看多个日志或数据文件 \n在日常工作中,我们查看日志文件的方式可能是使用tail命令在一个个的终端查看日志文件,一个终端就看一个日志文件。包括我在内也是,但是有时候也会觉得这种方式略显麻烦,其实有个工具叫做 multitail 可以在同一个终端同时查看多个日志文件。
\n首先安装 multitail:
\n# wget ftp://ftp.is.co.za/mirror/ftp.rpmforge.net/redhat/el6/en/x86_64/dag/RPMS/multitail-5.2.9-1.el6.rf.x86_64.rpm\n# yum -y localinstall multitail-5.2.9-1.el6.rf.x86_64.rpm\n
\nmultitail 工具支持文本的高亮显示,内容过滤以及更多你可能需要的功能。
\n如下就来一个有用的例子:
\n此时我们既想查看secure的日志指定过滤关键字输出,又想查看实时的网络ping情况:
\n命令如下:
\n# multitail -e "Accepted" /var/log/secure -l "ping baidu.com"\n
\n
\n是不是很方便?如果平时我们想查看两个日志之间的关联性,可以观察日志输出是否有触发等。如果分开两个终端可能来回进行切换有点浪费时间,这个multitail工具查看未尝不是一个好方法。
\n持续 ping 并将结果记录到日志 \n这个时候你去ping几个包把结果丢出来,人家会反驳你,刚刚那段时间有问题而已,现在业务都恢复正常了,网络肯定正常啊,这个时候估计你要气死。
\n你要是再拿出zabbix等网络监控的数据,这个时候就不太妥当了,zabbix的采集数据间隔你不可能设置成1秒钟1次吧?小编就遇到过这样的问题,结果我通过以下的命令进行了ping监控采集。然后再有人让我背锅的时候,我把出问题时间段的ping数据库截取出来,大家公开谈,结果那次被我叼杠回去了,以后他们都不敢轻易甩锅了,这个感觉好啊。
\n命令:
\nping api.jpush.cn | awk '{ print $0"\\t" strftime("%Y-%m-%d %H:%M:%S",systime()) } ' >> /tmp/jiguang.log &`\n
\n输出的结果会记录到/tmp/jiguang.log 中,每秒钟新增一条ping记录,如下:
\n
\nssh实现端口转发 \n可能很多的朋友都听说过ssh是linux下的远程登录安全协议,就是通俗的远程登录管理服务器。但是应该很少朋友会听说过ssh还可以做端口转发。其实ssh用来做端口转发的功能还是很强大的,下面就来做示范。
\n实例背景:我们公司是有堡垒机的,任何操作均需要在堡垒机上进行,有写开发人员需要访问ELasticSearch的head面板查看集群状态,但是我们并不想将ElasticSearch的9200端口映射出去,依然想通过堡垒机进行访问。
\n所以才会将通往堡垒机(192.168.1.15)的请求转发到服务器ElasticSearch(192.168.1.19)的9200上。
\n例子:\n将发往本机(192.168.1.15)的9200端口访问转发到192.168.1.19的9200端口
\nssh -p 22 -C -f -N -g -L 9200:192.168.1.19:9200 ihavecar@192.168.1.19`\n
\n记住:前提是先进行秘钥传输。
\n命令执行完后,访问192.168.1.15:9200端口则真实是访问192.168.1.19:9200端口。
\ngrep命令进阶 \ngrep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来
\ngrep搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。
\n\n^ #锚定行的开始 如:'^grep'匹配所有以grep开头的行。 \n$ \n. #匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。\n* #匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。 \n.* #一起用代表任意字符。 \n[] #匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。 \n[^] #匹配一个不在指定范围内的字符\n\\(..\\) #标记匹配字符,如'\\(love\\)',love被标记为1。 \n\\< #锚定单词的开始,如:'\\<grep'匹配包含以grep开头的单词的行。 \n\\> #锚定单词的结束,如'grep\\>'匹配包含以grep结尾的单词的行。 \nx\\{m\\} #重复字符x,m次,如:'0\\{5\\}'匹配包含5个o的行。 \nx\\{m,\\} #重复字符x,至少m次,如:'o\\{5,\\}'匹配至少有5个o的行。 \nx\\{m,n\\}#重复字符x,至少m次,不多于n次,如:'o\\{5,10\\}'匹配5--10个o的行。 \n\\w #匹配文字和数字字符,也就是[A-Za-z0-9],\n\\W #\\w的反置形式,匹配一个或多个非单词字符,如点号句号等。 \n\\b #单词锁定符,如: '\\bgrep\\b'只匹配grep。 \n
\n\n-n 打印行号\n grep -n \".*\" h.txt 所有打印行号\n grep -n \"root\" h.txt 匹配的内容显示行号\n-v 不包括\n-E 表示过滤 多个参数\n grep -Ev \"sshd|network|crond|sysstat|\"\n-o:仅打印你需要的东西,默认打印正行\n grep -o \"hello\" h.txt\n-i:忽略大小写\n grep -i \"hello\" h.txt\n-c: 用于统计文中出现的次数\n--color=auto 过滤字段添加颜色\n 利用正则打印特定字符\n\\b:作为边界符,边界只包含特定字符的行\n grep \"\\boldboy\\b\" /etc/passwd -->只过滤包含oldboy的行\n
\n\negrep: == grep -E 用于显示文件中符合条件的字符\n env|egrep \"USER|MAIL|PWD|LOGNAME\"\n 用的表达式不一样 ,egerp更加规范\negrep -o \"oldboy|hello\" h.txt -->仅仅输出 oldboy 和 hello\n
\n\n# 查找指定关键字个数 \ngrep '\\bboot\\b' logs_bak.txt 【\\b单词锁定符,只匹配boot】\n# 输出logs_bak.txt 文件中含有从logs.txt文件中读取出的关键词的内容行 \ncat logs_bak.txt \n cat logs.txt \n cat logs.txt | grep -nf logs_bak.txt\n# 从多个文件中查找关键词 \ngrep \"omc\" /etc/passwd /etc/shadow 【多文件查询时,会用冒号前添加文件名】\n# 打印IP信息 \nifconfig eth0|grep -E \"([0-9]{1,3}\\.){3}\" 【-E 表达式匹配,用小括号括起来表示一个整体】\n# 同时过滤多个关键字 \ncat /etc/passwd|grep -E \"boy|omc\"\n ==> cat /etc/passwd|egrep \"omc|boy\" 【用 | 划分多个关键字】\n# 显示当前目录下面以.txt 结尾的文件中的所有包含每个字符串至少有7个连续小写字符的字符串的行 \ngrep '\\w\\{7\\}' *.txt\n ==> grep '[a-z]\\{7\\}' *.txt 【注意特殊字符的转义】 \n
\n\n# A 查询匹配内容的一行之外,后n行的显示 \n # B 查询匹配内容的一行之外,前n行的显示\n # C 查询匹配内容的一行之外,显示上下n行\ngrep -n 'yum' -A 3 logs_bak.txt\n
\n",
"link": "\\zh-cn\\blog\\linux\\ope.html",
"meta": {}
}
\ No newline at end of file
diff --git a/zh-cn/blog/net/c_sqlserver_nginx.html b/zh-cn/blog/net/c_sqlserver_nginx.html
new file mode 100644
index 0000000..756c27d
--- /dev/null
+++ b/zh-cn/blog/net/c_sqlserver_nginx.html
@@ -0,0 +1,337 @@
+
+
+
+
+
+
+
+
+
+ c_sqlserver_nginx
+
+
+
+
+ ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + sqlserver + Nginx
+一、前言
+在之前的文章(ASP.NET Core 实战:Linux 小白的 .NET Core 部署之路)中,我介绍了如何在 Linux 环境中安装 .NET Core SDK / .NET Core Runtime、Nginx、sqlserver,以及如何将我们的 ASP.NET Core MVC 程序部署到 Linux 上,同时,使用 supervisor 守护程序守护我们的 .NET Core 程序。如果,你有看过那篇文章,并且和我一样是个 Linux 小白用户的话,可能第一感觉就是,把 .NET Core 项目部署在 IIS 上也挺好。
+将 .NET Core 项目部署到 Linux 上如此复杂,就没有简单的部署方式吗?
+你好,有的,Docker 了解一下~~~
+PS:这里的示例代码还是采用之前的毕业设计项目,在这篇文章发布的时候,我已经在程序的仓库中添加了对于 Docker 的支持,你可以下载下来,自己尝试一下,毕竟,实践出真知。
+代码仓储:https://github.com/burningmyself/micro
+二、Step by Step
+1、安装 Docker & Docker Compose
+在代码交付的过程中,偶尔会遇到这样的问题,在本地测试是好的,但是部署到测试环境、生产环境时就出这样那样的问题,同时,因为本地与测试环境、生产环境之间存在差异,我们可能无法在本地复现这些问题,那么,有没有一种工具可以很好的解决这一问题呢?随着历史的车轮不断前行,容器技术诞生了。
+Docker,作为最近几年兴起的一种虚拟化容器技术,他可以将我们的运行程序与操作系统做一个隔离,例如这里我们需要运行 .NET Core 程序,我们不再需要关心底层的操作系统是什么,不需要在每台需要需要运行程序的机器上安装程序运行的各种依赖,我们可以通过程序打包成镜像的方式,将应用程序和该程序的依赖全部置于一个镜像文件中,这时,只要别的机器上有安装 Docker,就可以通过我们打包的这个镜像来运行这个程序。
+1.1、卸载 Docker
+在安装 Docker 之前,我们应该确定当前的机器上是否已经安装好了 Docker,为了防止与现在安装的 Docker CE 发生冲突,这里我们先卸载掉以前版本的 Docker,如果你确定你的机器上并没有安装 Docker 的话此步可以跳过。
+在 Linux 中可以使用 \ 加 Enter 在输入很长很长的语句时进行换行,这里和后面的命令都是采用这样的方式。
+sudo yum remove docker
+docker-client
+docker-client-latest
+docker-common
+docker-latest
+docker-latest-logrotate
+docker-logrotate
+docker-engine
+1.2、添加 yum 源
+在安装 Docker CE 的方式上,我是采用将 Docker CE 的源添加到 yum 源中,之后我们就可以直接使用 yum install 安装 Docker CE,整个的安装过程如下。
+安装工具包从而可以让我们在 yum 中添加别的仓储源
+sudo yum install -y yum-utils \
+ device-mapper-persistent-data \
+ lvm2
+
+设置 docker ce 的稳定库地址
+sudo yum-config-manager \
+ --add-repo \
+ https://download.docker.com/linux/centos/docker-ce.repo
+
+安装 docker ce
+sudo yum install docker-ce docker-ce-cli containerd.io
+
+当我们安装好 Docker 之后,我们就可以使用 docker 命令验证我们是否在机器上成功安装了 Docker,同时,也可以使用 docker --version 命令查看我们安装的 Docker CE 版本。
+
+1.3、设置开机自启
+当 Docker 已经在我们的机器上安装完成后,我们就可以将 Docker 设置成机器的自启服务,这样,如果出现服务器重启的情况下,我们的 Docker 也可以随服务器的重启自动启动 Docker 服务。
+启动 Docker 服务并允许开机自启
+sudo systemctl start docker
+
+查看当前 dokcer 的运行情况
+sudo systemctl status docker
+
+1.4、Hello World
+就像我们在学习一门新的语言时,运行的第一句代码,几乎都是打印出 Hello World,而在 Docker Hub 中,也有这么一个镜像,在无数的 Docker 教程中,安装完 Docker 后,第一件事就是拉取这个镜像文件,“告诉” Docker,我来了。
+Docker Hub 是存放镜像的仓库,里面包含了许多的镜像文件,因为服务器在国外的原因,下载的速度可能不理想,像国内的阿里云、腾讯云也有提供对于 Docker 镜像的加速器服务,你可以按需使用,当然,你也可以创建属于你的私有镜像仓库。
+docker run 命令,它会在我们的本地镜像库中先寻找这个镜像,然后运行。如果在本地没有找到的话,则会自动使用 docker pull 从 Docker Hub 中寻找,能找到的话,则会自动下载到本地,然后运行,找不到的话,这条命令也就运行失败了。
+
+1.5、安装 Docker Compose
+在实际的项目开发中,我们可能会有多个应用镜像,例如在本篇文章的示例中,为了在 Docker 中运行我们的程序,我们需要三个镜像:应用程序自身镜像、sqlserver Server 镜像、以及 Nginx 镜像,为了将我们的程序启动起来,我们需要手敲各个容器的启动参数,环境变量,容器命名,指定不同容器的链接参数等等一系列的操作,又多又烦,可能某一步操作失败后程序就无法正常运行。而当我们使用了 Docker Compose 之后,我们就可以把这些命令一次性写在 docker-compose.yml 配置文件中,以后每次启动我们的应用程序时,只需要通过 docker compose 命令就可以自动帮我们完成这些操作。
+从 github 下载 docker compose 二进制文件
+sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
+
+对下载的二进制文件应用可执行权限
+sudo chmod +x /usr/local/bin/docker-compose
+
+查看 docker compose 版本
+docker-compose --version
+
+
+2、构建程序镜像
+当我们在服务器上安装好 docker 和 docker compose 之后,就可以开始构建我们的程序镜像了。首先我们需要对我们的运行程序添加对于 Docker 的支持。你可以自己手动在 MVC 项目中添加 Dockerfile 文件,或是通过右键添加 Docker 支持。
+
+Dockerfile 就像一个执行的清单,它告诉 Docker,我们这个镜像在构建和运行时需要按照什么样的命令运行。打开 VS 为我们自动创建的 Dockerfile,可以看到清晰的分成了四块的内容。
+
+我们知道,.NET Core 程序的运行需要依赖于 .NET Core Runtime(CoreCLR),因此,为了使我们的程序可以运行起来,我们需要从 hub 中拉取 runtime ,并在 此基础上构建我们的应用镜像。同时,为了避免因为基础的环境的不同造成对程序的影响,这里的 Runtime 需要同程序开发时的 .NET Core SDK 版本保持一致,所以这里我使用的是 .NET Core 3.0 Runtime。
+一个镜像中包含了应用程序及其所有的依赖,与虚拟机不同的是,容器中的每个镜像最终是共享了宿主机的操作系统资源,容器作为用户空间中的独立进程运行在主机操作系统上。
+
+PS:图片版权归属于微软的技术文档,如有侵权,请联系我删除,源文件地址:什么是 Docker?
+镜像可以看成一个个小型的“虚拟主机”,这里我们在镜像中创建了一个 /app 路径作为我们程序在镜像中的工作目录,同时,将 80,443 端口暴露给 Docker,从而可以使我们在镜像外面通过端口访问到当前镜像中的运行的程序。
+FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+因为我们的应用是一个微服架构的应用,最终的项目依赖于解决方案中的各个类库以及我们从 Nuget 中下载的各种第三方组件,在部署时,需要将这些组件打包成 dll 引用。所以,这里我们需要使用 .NET Core SDK 中包含的 .NET Core CLI 进行还原和构建。
+就像在下面的代码中,我们在镜像的内部创建了一个 /src 的路径,将当前解决方案下的类库都复制到这个目录下,之后通过 dotnet restore 命令还原我们的主程序所依赖的各个组件。当我们还原好依赖的组件后,就可以使用 dotnet build 命令生成 Release版本的 dll 文件,同时输出到之前创建的 /app 路径下。
+FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
+WORKDIR /src
+COPY . .
+WORKDIR /src/templates/service/host/Base.IdentityServer
+RUN dotnet restore -nowarn:msb3202,nu1503
+RUN dotnet build --no-restore -c Release -o /app
+
+
+上面一步可以看成我们在使用 VS 生成 Release 版本的解决方案,当生成没有出错之后,我们就可以进行程序的发布。
+FROM build AS publish
+RUN dotnet publish --no-restore -c Release -o /app
+
+当已经生成发布文件之后,按照我们平时部署在 Windows 上的过程,这时就可以通过 IIS 部署运行了,因此,构建我们应用镜像的最后一步就是通过 dotnet 命令执行我们的程序。
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app .
+ENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]
+
+似乎到这一步构建程序镜像就结束了,按照这样流程做的话,就需要我们将整个的解决方案上传到服务器上了,可是,很多时候,我们仅仅是把我们在本地发布好的项目上传到服务器上,这与我们现在的构建流程具有很大的不同,所以这里我们来修改 Dockerfile 文件,从而符合我们的发布流程。
+从上面分析 Dockerfile 的过程中不难看出,在服务器上构建镜像的第二步、第三步就是我们现在在开发环境中手动完成的部分,所以这里,我们只需要对这部分进行删除即可,修改后的 Dockerfile 如下。
+FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster
+WORKDIR /app
+COPY . /app
+EXPOSE 80
+EXPOSE 443
+ENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]
+
+在修改后的 Dockerfile 中,可以看到,我们删去了 build 和 release 的过程,选择直接将我们 Dockerfile 路径下的文件拷贝到镜像中的 /app 路径下,然后直接执行 dotnet 命令,运行我们的程序。
+为了确保 Dockerfile 与发布后的文件处于同一路径下,这里我们需要使用 VS 修改 Dockerfile 的属性值,确保会复制到输出的目录下,这里选择如果较新则复制即可。
+
+3、编写 docker-compose.yml
+当我们构建好应用的镜像,对于 Nginx 和 sqlserver 我们完全可以从 hub 中拉取下来,再执行一些配置即可。所以,我们现在就可以编写 docker compose 文件,来定义我们的应用镜像运行时需要包含的依赖以及每个镜像的启动顺序。
+右键选中 MVC 项目,添加一个 docker-compose.yml 文件,同样的,需要修改该文件的属性,以便于该文件可以复制到输出目录下。注意,这里的文件名和上文的 Dockerfile 都是特定的,你不能做任何的修改。如果你的电脑上已经安装了 Docker for Windows,你也可以使用 VS,右键添加,选中容器业务流程协调程序支持自动对 docker compose 进行配置。
+
+在 yml 文件中,我定义了三个镜像:AdminApiGateway.Host、Base.IdentityServer、Base.HttpApi.Host。三个镜像的定义中有许多相同的地方,都设置了自动重启(restart),以及都处于同一个桥接网络下(psu-net)从而达到镜像间的通信。
+sqlserver 是 SqlServer 的镜像,我们通过环境变量 SA_PASSWORD 设置了 SqlServer 的数据库连接密码,并通过挂载卷的方式将镜像中的数据库文件持久化到我们的服务器本地路径中。同时,将镜像的 1433 端口映射到服务器的 1433 端口上。
+AdminApiGateway.Host 则是我们的程序后台网关镜像,采用位于 /data/dotnet/AdminApiGateway/ 路径下的 Dockerfile 文件进行构建的,因为主程序的运行需要依赖于数据库,所以这里采用 depends_on 属性,使我们的应用镜像依赖于 sqlserver 镜像,即,在 sqlserver 启动后才会启动应用镜像。
+nginx 则是我们的 nginx 镜像,这里将镜像中的 80 端口和 443 端口都映射到服务器 IP 上,因为我们需要配置 Nginx 从而监听我们的程序,所以通过挂载卷的方式,将本地的 nginx.conf 配置文件用配置映射到镜像中。同时,因为我们在构建应用镜像的 Dockerfile 文件时,对外暴露了 80,443 端口,所以这里就可以通过 links 属性进行监听(如果构建时未暴露端口,你可以在 docker compose 文件中通过 Expose 属性暴露镜像中的端口)。
+Nginx 的配置文件如下,这里特别需要注意文件的格式,缩进,一点小错误都可能导致镜像无法正常运行。如果你和我一样将 nginx.conf 放到程序运行路径下的,别忘了修改文件的属性。
+user nginx;
+worker_processes 1;
+
+error_log /var/log/nginx/error_log.log warn;
+pid /var/run/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ gzip on; #开启gzip
+ gzip_disable "msie6"; #IE6不使用gzip
+ gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"
+ gzip_proxied any; #代理结果数据的压缩
+ gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值
+ gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果
+ gzip_http_version 1.1; #识别http协议的版本
+ gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩
+ gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩
+
+ include /etc/nginx/conf.d/*.conf;
+
+ server {
+ #nginx同时开启http和https
+ listen 80 default backlog=2048;
+ listen 443 ssl;
+ server_name ysf.djtlpay.com;
+
+ ssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;
+ ssl_certificate_key /ssl/2_ysf.djtlpay.com.key;
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Host $http_host;
+ proxy_cache_bypass $http_upgrade;
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+ }
+}
+
+一个完整的 docker compose 文件如下,包含了三个镜像以及一个桥接网络。
+version: '3.0'
+
+services:
+ # 指定服务名称
+ sqlserver:
+ # 指定服务使用的镜像
+ image: mcr.microsoft.com/mssql/server
+ # 指定容器名称
+ container_name: sqlserver
+ # 指定服务运行的端口
+ ports:
+ - "1433"
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /data/sqlserver:/var/opt/mssql
+ # 挂断自动重新启动
+ restart: always
+ environment:
+ - TZ=Asia/Shanghai
+ - SA_PASSWORD=mssql-MSSQL
+ - ACCEPT_EULA=Y
+ # 指定容器运行的用户为root
+ user:
+ root
+ # 指定服务名称
+ redis:
+ # 指定服务使用的镜像
+ image: redis
+ # 指定容器名称
+ container_name: redis
+ # 指定服务运行的端口
+ ports:
+ - 6379:6379
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/redis:/data
+ - /data/redis/redis.conf:/etc/redis.conf
+ # 挂断自动重新启动
+ restart: always
+ # 指定容器执行命令
+ command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes
+ # 指定容器的环境变量
+ environment:
+ - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致
+ # 指定服务名称
+ mongo:
+ # 指定服务使用的镜像
+ image: mongo
+ # 指定容器名称
+ container_name: mongo
+ # 指定服务运行的端口
+ ports:
+ - 27017:27017
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /etc/localtime:/etc/localtime
+ - /data/mongodb/db:/data/db
+ - /data/mongodb/configdb:/data/configdb
+ - /data/mongodb/initdb:/docker-entrypoint-initdb.d
+ # 挂断自动重新启动
+ restart: always
+ # 指定容器的环境变量
+ environment:
+ - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致
+ - AUTH=yes
+ - MONGO_INITDB_ROOT_USERNAME=admin
+ - MONGO_INITDB_ROOT_PASSWORD=admin
+ nginx:
+ # 指定服务使用的镜像
+ image: nginx
+ # 指定容器名称
+ container_name: nginx
+ # 指定服务运行的端口
+ ports:
+ - 80:80
+ - 443:443
+ # 指定容器中需要挂载的文件
+ volumes:
+ - /etc/localtime:/etc/localtime
+ # 挂断自动重新启动
+ restart: always
+
+ AdminApiGateway.Host:
+ image: 'volosoft/microservice-demo-public-website-gateway:${TAG:-latest}'
+ build:
+ context: ../
+ dockerfile: micro/gateways/AdminApiGateway.Host/Dockerfile
+ depends_on:
+ - sqlserver
+ - redis
+ - mongo
+
+ Base.IdentityServer:
+ image: 'Base.IdentityServer:${TAG:-latest}'
+ build:
+ context: ../
+ dockerfile: micro/modules/base/host/Base.IdentityServer/Dockerfile
+ depends_on:
+ - sqlserver
+ - redis
+ - mongo
+ - AdminApiGateway.Host
+
+ Base.HttpApi.Host:
+ image: 'Base.HttpApi.Host:${TAG:-latest}'
+ build:
+ context: ../
+ dockerfile: micro/modules/base/host/Base.HttpApi.Host/Dockerfile
+ depends_on:
+ - sqlserver
+ - redis
+ - mongo
+ - AdminApiGateway.Host
+ - Base.IdentityServer
+
+这里需要注意,所有有用到镜像间的通信的地方,我们都需要使用镜像名进行指代,例如上面的 nginx 的配置文件中,我们需要将监听的地址改为镜像名称,以及,我们需要修改程序的数据库访问字符串的服务器地址
+4、发布部署程序
+当我们构建好 docker compose 文件后就可以把整个文件上传到服务器上进行构建 docker 镜像了。这里我将所有的部署文件放在服务器的 /data/wwwroot/micro/ 路径下,这时我们就可以通过 docker compose 命令进行镜像构建。
+定位到部署文件在的位置,我们可以直接使用下面的命令进行镜像的(重新)构建,启动,并链接一个服务相关的容器,整个过程都会在后台运行,如果你希望看到整个过程的话,你可以去掉 -d 参数。
+执行镜像构建,启动
+docker-compose up -d
+
+当 up 命令执行完成后,我们就可以通过 ps 命令查看正在运行的容器,若有的容器并没有运行起来,则可以使用 logs 查看容器的运行日志从而进行排错。
+查看所有正在运行的容器
+docker-compose ps
+
+显示容器运行日志
+docker-compose logs
+
+三、总结
+本章主要是介绍了如何通过 docker 容器,完整的部署一个可实际使用的 .NET Core 的单体应用,相比于之前通过 Linux 部署 .NET Core 应用,可以看到整个步骤少了很多,也简单很多。文中涉及到了一些 docker 的命令,如果你之前并没有接触过 docker 的话,可能需要你进一步的了解。当我们将程序打包成一个镜像之后,你完全可以将镜像上传到私有镜像仓库中,或是直接打包成镜像的压缩文件,这样,当需要切换部署环境时,只需要获取到这个镜像之后即可快速完成部署,相比之前,极大的方便了我们的工作。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/net/c_sqlserver_nginx.json b/zh-cn/blog/net/c_sqlserver_nginx.json
new file mode 100644
index 0000000..5b395ff
--- /dev/null
+++ b/zh-cn/blog/net/c_sqlserver_nginx.json
@@ -0,0 +1,6 @@
+{
+ "filename": "c_sqlserver_nginx.md",
+ "__html": "ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + sqlserver + Nginx \n一、前言 \n在之前的文章(ASP.NET Core 实战:Linux 小白的 .NET Core 部署之路)中,我介绍了如何在 Linux 环境中安装 .NET Core SDK / .NET Core Runtime、Nginx、sqlserver,以及如何将我们的 ASP.NET Core MVC 程序部署到 Linux 上,同时,使用 supervisor 守护程序守护我们的 .NET Core 程序。如果,你有看过那篇文章,并且和我一样是个 Linux 小白用户的话,可能第一感觉就是,把 .NET Core 项目部署在 IIS 上也挺好。
\n将 .NET Core 项目部署到 Linux 上如此复杂,就没有简单的部署方式吗?
\n你好,有的,Docker 了解一下~~~
\nPS:这里的示例代码还是采用之前的毕业设计项目,在这篇文章发布的时候,我已经在程序的仓库中添加了对于 Docker 的支持,你可以下载下来,自己尝试一下,毕竟,实践出真知。
\n代码仓储:https://github.com/burningmyself/micro
\n二、Step by Step \n1、安装 Docker & Docker Compose \n在代码交付的过程中,偶尔会遇到这样的问题,在本地测试是好的,但是部署到测试环境、生产环境时就出这样那样的问题,同时,因为本地与测试环境、生产环境之间存在差异,我们可能无法在本地复现这些问题,那么,有没有一种工具可以很好的解决这一问题呢?随着历史的车轮不断前行,容器技术诞生了。
\nDocker,作为最近几年兴起的一种虚拟化容器技术,他可以将我们的运行程序与操作系统做一个隔离,例如这里我们需要运行 .NET Core 程序,我们不再需要关心底层的操作系统是什么,不需要在每台需要需要运行程序的机器上安装程序运行的各种依赖,我们可以通过程序打包成镜像的方式,将应用程序和该程序的依赖全部置于一个镜像文件中,这时,只要别的机器上有安装 Docker,就可以通过我们打包的这个镜像来运行这个程序。
\n1.1、卸载 Docker \n在安装 Docker 之前,我们应该确定当前的机器上是否已经安装好了 Docker,为了防止与现在安装的 Docker CE 发生冲突,这里我们先卸载掉以前版本的 Docker,如果你确定你的机器上并没有安装 Docker 的话此步可以跳过。
\n在 Linux 中可以使用 \\ 加 Enter 在输入很长很长的语句时进行换行,这里和后面的命令都是采用这样的方式。
\nsudo yum remove docker \ndocker-client \ndocker-client-latest \ndocker-common \ndocker-latest \ndocker-latest-logrotate \ndocker-logrotate \ndocker-engine
\n1.2、添加 yum 源 \n在安装 Docker CE 的方式上,我是采用将 Docker CE 的源添加到 yum 源中,之后我们就可以直接使用 yum install 安装 Docker CE,整个的安装过程如下。
\n安装工具包从而可以让我们在 yum 中添加别的仓储源
\nsudo yum install -y yum-utils \\\n device-mapper-persistent-data \\\n lvm2\n
\n设置 docker ce 的稳定库地址
\nsudo yum-config-manager \\\n --add-repo \\\n https://download.docker.com/linux/centos/docker-ce.repo\n
\n安装 docker ce
\nsudo yum install docker-ce docker-ce-cli containerd.io\n
\n当我们安装好 Docker 之后,我们就可以使用 docker 命令验证我们是否在机器上成功安装了 Docker,同时,也可以使用 docker --version 命令查看我们安装的 Docker CE 版本。
\n
\n1.3、设置开机自启 \n当 Docker 已经在我们的机器上安装完成后,我们就可以将 Docker 设置成机器的自启服务,这样,如果出现服务器重启的情况下,我们的 Docker 也可以随服务器的重启自动启动 Docker 服务。
\n启动 Docker 服务并允许开机自启
\nsudo systemctl start docker\n
\n查看当前 dokcer 的运行情况
\nsudo systemctl status docker\n
\n1.4、Hello World \n就像我们在学习一门新的语言时,运行的第一句代码,几乎都是打印出 Hello World,而在 Docker Hub 中,也有这么一个镜像,在无数的 Docker 教程中,安装完 Docker 后,第一件事就是拉取这个镜像文件,“告诉” Docker,我来了。
\nDocker Hub 是存放镜像的仓库,里面包含了许多的镜像文件,因为服务器在国外的原因,下载的速度可能不理想,像国内的阿里云、腾讯云也有提供对于 Docker 镜像的加速器服务,你可以按需使用,当然,你也可以创建属于你的私有镜像仓库。
\ndocker run 命令,它会在我们的本地镜像库中先寻找这个镜像,然后运行。如果在本地没有找到的话,则会自动使用 docker pull 从 Docker Hub 中寻找,能找到的话,则会自动下载到本地,然后运行,找不到的话,这条命令也就运行失败了。
\n
\n1.5、安装 Docker Compose \n在实际的项目开发中,我们可能会有多个应用镜像,例如在本篇文章的示例中,为了在 Docker 中运行我们的程序,我们需要三个镜像:应用程序自身镜像、sqlserver Server 镜像、以及 Nginx 镜像,为了将我们的程序启动起来,我们需要手敲各个容器的启动参数,环境变量,容器命名,指定不同容器的链接参数等等一系列的操作,又多又烦,可能某一步操作失败后程序就无法正常运行。而当我们使用了 Docker Compose 之后,我们就可以把这些命令一次性写在 docker-compose.yml 配置文件中,以后每次启动我们的应用程序时,只需要通过 docker compose 命令就可以自动帮我们完成这些操作。
\n从 github 下载 docker compose 二进制文件
\nsudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose\n
\n对下载的二进制文件应用可执行权限
\nsudo chmod +x /usr/local/bin/docker-compose\n
\n查看 docker compose 版本
\ndocker-compose --version\n
\n
\n2、构建程序镜像 \n当我们在服务器上安装好 docker 和 docker compose 之后,就可以开始构建我们的程序镜像了。首先我们需要对我们的运行程序添加对于 Docker 的支持。你可以自己手动在 MVC 项目中添加 Dockerfile 文件,或是通过右键添加 Docker 支持。
\n
\nDockerfile 就像一个执行的清单,它告诉 Docker,我们这个镜像在构建和运行时需要按照什么样的命令运行。打开 VS 为我们自动创建的 Dockerfile,可以看到清晰的分成了四块的内容。
\n
\n我们知道,.NET Core 程序的运行需要依赖于 .NET Core Runtime(CoreCLR),因此,为了使我们的程序可以运行起来,我们需要从 hub 中拉取 runtime ,并在 此基础上构建我们的应用镜像。同时,为了避免因为基础的环境的不同造成对程序的影响,这里的 Runtime 需要同程序开发时的 .NET Core SDK 版本保持一致,所以这里我使用的是 .NET Core 3.0 Runtime。
\n一个镜像中包含了应用程序及其所有的依赖,与虚拟机不同的是,容器中的每个镜像最终是共享了宿主机的操作系统资源,容器作为用户空间中的独立进程运行在主机操作系统上。\n
\nPS:图片版权归属于微软的技术文档,如有侵权,请联系我删除,源文件地址:什么是 Docker?
\n镜像可以看成一个个小型的“虚拟主机”,这里我们在镜像中创建了一个 /app 路径作为我们程序在镜像中的工作目录,同时,将 80,443 端口暴露给 Docker,从而可以使我们在镜像外面通过端口访问到当前镜像中的运行的程序。
\nFROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\n
\n因为我们的应用是一个微服架构的应用,最终的项目依赖于解决方案中的各个类库以及我们从 Nuget 中下载的各种第三方组件,在部署时,需要将这些组件打包成 dll 引用。所以,这里我们需要使用 .NET Core SDK 中包含的 .NET Core CLI 进行还原和构建。
\n就像在下面的代码中,我们在镜像的内部创建了一个 /src 的路径,将当前解决方案下的类库都复制到这个目录下,之后通过 dotnet restore 命令还原我们的主程序所依赖的各个组件。当我们还原好依赖的组件后,就可以使用 dotnet build 命令生成 Release版本的 dll 文件,同时输出到之前创建的 /app 路径下。
\nFROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build\nWORKDIR /src\nCOPY . .\nWORKDIR /src/templates/service/host/Base.IdentityServer\nRUN dotnet restore -nowarn:msb3202,nu1503\nRUN dotnet build --no-restore -c Release -o /app\n\n
\n上面一步可以看成我们在使用 VS 生成 Release 版本的解决方案,当生成没有出错之后,我们就可以进行程序的发布。
\nFROM build AS publish\nRUN dotnet publish --no-restore -c Release -o /app\n
\n当已经生成发布文件之后,按照我们平时部署在 Windows 上的过程,这时就可以通过 IIS 部署运行了,因此,构建我们应用镜像的最后一步就是通过 dotnet 命令执行我们的程序。
\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app .\nENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]\n
\n似乎到这一步构建程序镜像就结束了,按照这样流程做的话,就需要我们将整个的解决方案上传到服务器上了,可是,很多时候,我们仅仅是把我们在本地发布好的项目上传到服务器上,这与我们现在的构建流程具有很大的不同,所以这里我们来修改 Dockerfile 文件,从而符合我们的发布流程。
\n从上面分析 Dockerfile 的过程中不难看出,在服务器上构建镜像的第二步、第三步就是我们现在在开发环境中手动完成的部分,所以这里,我们只需要对这部分进行删除即可,修改后的 Dockerfile 如下。
\nFROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster\nWORKDIR /app\nCOPY . /app \nEXPOSE 80\nEXPOSE 443\nENTRYPOINT ["dotnet", "Base.IdentityServer.dll"]\n
\n在修改后的 Dockerfile 中,可以看到,我们删去了 build 和 release 的过程,选择直接将我们 Dockerfile 路径下的文件拷贝到镜像中的 /app 路径下,然后直接执行 dotnet 命令,运行我们的程序。
\n为了确保 Dockerfile 与发布后的文件处于同一路径下,这里我们需要使用 VS 修改 Dockerfile 的属性值,确保会复制到输出的目录下,这里选择如果较新则复制即可。
\n
\n3、编写 docker-compose.yml \n当我们构建好应用的镜像,对于 Nginx 和 sqlserver 我们完全可以从 hub 中拉取下来,再执行一些配置即可。所以,我们现在就可以编写 docker compose 文件,来定义我们的应用镜像运行时需要包含的依赖以及每个镜像的启动顺序。
\n右键选中 MVC 项目,添加一个 docker-compose.yml 文件,同样的,需要修改该文件的属性,以便于该文件可以复制到输出目录下。注意,这里的文件名和上文的 Dockerfile 都是特定的,你不能做任何的修改。如果你的电脑上已经安装了 Docker for Windows,你也可以使用 VS,右键添加,选中容器业务流程协调程序支持自动对 docker compose 进行配置。
\n
\n在 yml 文件中,我定义了三个镜像:AdminApiGateway.Host、Base.IdentityServer、Base.HttpApi.Host。三个镜像的定义中有许多相同的地方,都设置了自动重启(restart),以及都处于同一个桥接网络下(psu-net)从而达到镜像间的通信。
\nsqlserver 是 SqlServer 的镜像,我们通过环境变量 SA_PASSWORD 设置了 SqlServer 的数据库连接密码,并通过挂载卷的方式将镜像中的数据库文件持久化到我们的服务器本地路径中。同时,将镜像的 1433 端口映射到服务器的 1433 端口上。
\nAdminApiGateway.Host 则是我们的程序后台网关镜像,采用位于 /data/dotnet/AdminApiGateway/ 路径下的 Dockerfile 文件进行构建的,因为主程序的运行需要依赖于数据库,所以这里采用 depends_on 属性,使我们的应用镜像依赖于 sqlserver 镜像,即,在 sqlserver 启动后才会启动应用镜像。
\nnginx 则是我们的 nginx 镜像,这里将镜像中的 80 端口和 443 端口都映射到服务器 IP 上,因为我们需要配置 Nginx 从而监听我们的程序,所以通过挂载卷的方式,将本地的 nginx.conf 配置文件用配置映射到镜像中。同时,因为我们在构建应用镜像的 Dockerfile 文件时,对外暴露了 80,443 端口,所以这里就可以通过 links 属性进行监听(如果构建时未暴露端口,你可以在 docker compose 文件中通过 Expose 属性暴露镜像中的端口)。
\nNginx 的配置文件如下,这里特别需要注意文件的格式,缩进,一点小错误都可能导致镜像无法正常运行。如果你和我一样将 nginx.conf 放到程序运行路径下的,别忘了修改文件的属性。
\nuser nginx;\nworker_processes 1;\n\nerror_log /var/log/nginx/error_log.log warn;\npid /var/run/nginx.pid;\n\n\nevents {\n worker_connections 1024;\n}\n\n\nhttp {\n include /etc/nginx/mime.types;\n default_type application/octet-stream;\n\n log_format main '$remote_addr - $remote_user [$time_local] "$request" '\n '$status $body_bytes_sent "$http_referer" '\n '"$http_user_agent" "$http_x_forwarded_for"';\n\n access_log /var/log/nginx/access.log main;\n\n sendfile on;\n #tcp_nopush on;\n\n keepalive_timeout 65;\n\n #gzip on;\n\n gzip on; #开启gzip\n gzip_disable "msie6"; #IE6不使用gzip\n gzip_vary on; #设置为on会在Header里增加 "Vary: Accept-Encoding"\n gzip_proxied any; #代理结果数据的压缩\n gzip_comp_level 6; #gzip压缩比(1~9),越小压缩效果越差,但是越大处理越慢,所以一般取中间值\n gzip_buffers 16 8k; #获取多少内存用于缓存压缩结果\n gzip_http_version 1.1; #识别http协议的版本\n gzip_min_length 1k; #设置允许压缩的页面最小字节数,超过1k的文件会被压缩\n gzip_types application/javascript text/css; #对特定的MIME类型生效,js和css文件会被压缩\n\n include /etc/nginx/conf.d/*.conf;\n\n server {\n #nginx同时开启http和https\n listen 80 default backlog=2048;\n listen 443 ssl;\n server_name ysf.djtlpay.com;\n\n ssl_certificate /ssl/1_ysf.djtlpay.com_bundle.crt;\n ssl_certificate_key /ssl/2_ysf.djtlpay.com.key;\n\n location / {\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Host $http_host;\n proxy_cache_bypass $http_upgrade;\n root /usr/share/nginx/html;\n index index.html index.htm;\n }\n } \n}\n
\n一个完整的 docker compose 文件如下,包含了三个镜像以及一个桥接网络。
\nversion: '3.0'\n\nservices:\n # 指定服务名称\n sqlserver:\n # 指定服务使用的镜像\n image: mcr.microsoft.com/mssql/server\n # 指定容器名称\n container_name: sqlserver\n # 指定服务运行的端口\n ports:\n - "1433"\n # 指定容器中需要挂载的文件 \n volumes:\n - /data/sqlserver:/var/opt/mssql\n # 挂断自动重新启动 \n restart: always\n environment:\n - TZ=Asia/Shanghai\n - SA_PASSWORD=mssql-MSSQL\n - ACCEPT_EULA=Y\n # 指定容器运行的用户为root\n user:\n root\n # 指定服务名称\n redis:\n # 指定服务使用的镜像\n image: redis\n # 指定容器名称\n container_name: redis\n # 指定服务运行的端口\n ports:\n - 6379:6379\n # 指定容器中需要挂载的文件\n volumes:\n - /etc/localtime:/etc/localtime\n - /data/redis:/data\n - /data/redis/redis.conf:/etc/redis.conf\n # 挂断自动重新启动\n restart: always\n # 指定容器执行命令\n command: redis-server /etc/redis.conf --requirepass xiujingredis. --appendonly yes\n # 指定容器的环境变量\n environment:\n - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致\n # 指定服务名称\n mongo:\n # 指定服务使用的镜像\n image: mongo\n # 指定容器名称\n container_name: mongo\n # 指定服务运行的端口\n ports:\n - 27017:27017\n # 指定容器中需要挂载的文件\n volumes:\n - /etc/localtime:/etc/localtime\n - /data/mongodb/db:/data/db\n - /data/mongodb/configdb:/data/configdb\n - /data/mongodb/initdb:/docker-entrypoint-initdb.d \n # 挂断自动重新启动\n restart: always\n # 指定容器的环境变量\n environment:\n - TZ=Asia/Shanghai # 设置容器时区与宿主机保持一致\n - AUTH=yes\n - MONGO_INITDB_ROOT_USERNAME=admin\n - MONGO_INITDB_ROOT_PASSWORD=admin\n nginx:\n # 指定服务使用的镜像\n image: nginx\n # 指定容器名称\n container_name: nginx\n # 指定服务运行的端口\n ports:\n - 80:80\n - 443:443\n # 指定容器中需要挂载的文件\n volumes:\n - /etc/localtime:/etc/localtime\n # 挂断自动重新启动\n restart: always\n\n AdminApiGateway.Host:\n image: 'volosoft/microservice-demo-public-website-gateway:${TAG:-latest}'\n build:\n context: ../\n dockerfile: micro/gateways/AdminApiGateway.Host/Dockerfile\n depends_on:\n - sqlserver\n - redis\n - mongo \n\n Base.IdentityServer:\n image: 'Base.IdentityServer:${TAG:-latest}'\n build:\n context: ../\n dockerfile: micro/modules/base/host/Base.IdentityServer/Dockerfile\n depends_on:\n - sqlserver\n - redis\n - mongo\n - AdminApiGateway.Host\n\n Base.HttpApi.Host:\n image: 'Base.HttpApi.Host:${TAG:-latest}'\n build:\n context: ../\n dockerfile: micro/modules/base/host/Base.HttpApi.Host/Dockerfile\n depends_on:\n - sqlserver\n - redis\n - mongo\n - AdminApiGateway.Host\n - Base.IdentityServer \n
\n这里需要注意,所有有用到镜像间的通信的地方,我们都需要使用镜像名进行指代,例如上面的 nginx 的配置文件中,我们需要将监听的地址改为镜像名称,以及,我们需要修改程序的数据库访问字符串的服务器地址
\n4、发布部署程序 \n当我们构建好 docker compose 文件后就可以把整个文件上传到服务器上进行构建 docker 镜像了。这里我将所有的部署文件放在服务器的 /data/wwwroot/micro/ 路径下,这时我们就可以通过 docker compose 命令进行镜像构建。
\n定位到部署文件在的位置,我们可以直接使用下面的命令进行镜像的(重新)构建,启动,并链接一个服务相关的容器,整个过程都会在后台运行,如果你希望看到整个过程的话,你可以去掉 -d 参数。
\n执行镜像构建,启动
\ndocker-compose up -d\n
\n当 up 命令执行完成后,我们就可以通过 ps 命令查看正在运行的容器,若有的容器并没有运行起来,则可以使用 logs 查看容器的运行日志从而进行排错。
\n查看所有正在运行的容器
\ndocker-compose ps\n
\n显示容器运行日志
\ndocker-compose logs\n
\n三、总结 \n本章主要是介绍了如何通过 docker 容器,完整的部署一个可实际使用的 .NET Core 的单体应用,相比于之前通过 Linux 部署 .NET Core 应用,可以看到整个步骤少了很多,也简单很多。文中涉及到了一些 docker 的命令,如果你之前并没有接触过 docker 的话,可能需要你进一步的了解。当我们将程序打包成一个镜像之后,你完全可以将镜像上传到私有镜像仓库中,或是直接打包成镜像的压缩文件,这样,当需要切换部署环境时,只需要获取到这个镜像之后即可快速完成部署,相比之前,极大的方便了我们的工作。
\n",
+ "link": "\\zh-cn\\blog\\net\\c_sqlserver_nginx.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/sql/mybatis.html b/zh-cn/blog/sql/mybatis.html
new file mode 100644
index 0000000..2ab0b4f
--- /dev/null
+++ b/zh-cn/blog/sql/mybatis.html
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+ mybatis
+
+
+
+
+ Mybatis使用心德
+什么是Mybatis?
+
+
+Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
+
+
+MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
+
+
+通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
+
+
+Mybaits的优点:
+
+
+基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
+
+
+与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
+
+
+很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
+
+
+能够与Spring很好的集成;
+
+
+提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
+
+
+MyBatis框架的缺点:
+
+
+SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
+
+
+SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
+
+
+MyBatis框架适用场合:
+
+
+MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
+
+
+对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
+
+
+MyBatis与Hibernate有哪些不同?
+
+
+Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
+
+
+Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
+
+
+Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
+
+
+#{}和${}的区别是什么?
+
+
+#{}是预编译处理,${}是字符串替换。
+
+
+Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
+
+
+Mybatis在处理{}时,就是把{}替换成变量的值。
+
+
+使用#{}可以有效的防止SQL注入,提高系统安全性。
+
+
+当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
+第1种:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
+<select id = ”selectorder” parametertype = ”int” resultetype = ”me.gacl.domain.order” >
+ select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
+</select >
+
+第2种:通过 来映射字段名和实体类属性名的一一对应的关系。
+<select id ="getOrder" parameterType ="int" resultMap ="orderresultmap" >
+select * from orders where order_id=#{id}
+</select >
+<resultMap type =”me.gacl.domain.order” id =”orderresultmap” >
+ <!–用id属性来映射主键字段– >
+ <id property =”id” column =”order_id” >
+ <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性– >
+ <result property = “orderno” column =”order_no”/ >
+ <result property =”price” column =”order_price” />
+</reslutMap >
+
+模糊查询like语句该怎么写?
+第1种:在Java代码中添加sql通配符。
+string wildcardname = “%smi%”;
+list<name > names = mapper.selectlike(wildcardname);
+<select id =”selectlike” >
+select * from foo where bar like #{value}
+</select >
+
+第2种:在sql语句中拼接通配符,会引起sql注入
+string wildcardname = “smi”;
+list<name > names = mapper.selectlike(wildcardname);
+<select id =”selectlike” >
+select * from foo where bar like "%"#{value}"%"
+</select >
+
+通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
+Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
+Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个 <select>、<insert>、<update>、<delete>
标签,都会被解析为一个MapperStatement对象。
+举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为 com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。
+Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
+Mybatis是如何进行分页的?分页插件的原理是什么?
+Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
+分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
+Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
+第一种是使用 标签,逐一定义数据库列名和对象属性名之间的映射关系。
+第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
+有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
+如何执行批量插入?
+首先,创建一个简单的insert语句:
+<insert id =”insertname” >
+insert into names (name) values (#{value})
+</insert >
+
+然后在java代码中像下面这样执行批处理插入:
+list < string > names = new arraylist();
+names.add(“fred”);
+names.add(“barney”);
+names.add(“betty”);
+names.add(“wilma”);
+
+sqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);
+try {
+ namemapper mapper = sqlsession.getmapper(namemapper.class);
+ for (string name: names) {
+ mapper.insertname(name);
+ }
+ sqlsession.commit();
+} catch (Exception e) {
+ e.printStackTrace();
+ sqlSession.rollback();
+ throw e;
+}
+finally {
+ sqlsession.close();
+}
+
+如何获取自动生成的(主)键值?
+insert 方法总是返回一个int值 ,这个值代表的是插入的行数。
+如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
+示例:
+<insert id=”insertname” usegeneratedkeys=”true ” keyproperty=”id”>
+ insert into names (name) values (#{name})
+</insert>
+name name = new name();
+name.setname(“fred”);
+int rows = mapper.insertname(name);
+
+system.out.println(“rows inserted = ” + rows);
+system.out.println(“generated key value = ” + name.getid());
+
+在mapper中如何传递多个参数?
+
+第一种:
+DAO层的函数
+
+
+<select id ="selectUser" resultMap ="BaseResultMap" >
+ select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}
+</select >
+
+
+第二种:使用 @param 注解:
+
+
+public interface usermapper {
+ user selectuser (@param(“username”) string username,@param (“hashedpassword”) string hashedpassword) ;
+}
+
+然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
+<select id =”selectuser” resulttype =”user” >
+ select id, username, hashedpassword
+ from some_table
+ where username = #{username}
+ and hashedpassword = #{hashedpassword}
+</select >
+
+
+第三种:多个参数封装成map
+
+try {
+
+
+ Map < String, Object > map = new HashMap();
+ map.put("start" , start);
+ map.put("end" , end);
+ return sqlSession.selectList("StudentID.pagination" , map);
+} catch (Exception e) {
+ e.printStackTrace();
+ sqlSession.rollback();
+ throw e;
+} finally {
+ MybatisUtil.closeSqlSession();
+
+Mybatis动态sql有什么用?执行原理?有哪些动态sql?
+Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
+Mybatis提供了9种动态sql标签:
+trim|where|set|foreach|if|choose|when|otherwise|bind。
+Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
+<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>
,加上动态sql的9个标签,其中 <sql>
为sql片段标签,通过 <include>
标签引入sql片段, <selectKey>
为不支持自增的主键生成策略标签。
+Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
+不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
+原因就是namespace+id是作为Map <String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
+为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
+Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
+一对一、一对多的关联查询 ?
+<mapper namespace ="com.lcb.mapping.userMapper" >
+
+ <select id ="getClass" parameterType ="int" resultMap ="ClassesResultMap" >
+ select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
+ </select >
+ <resultMap type ="com.lcb.user.Classes" id ="ClassesResultMap" >
+
+ <id property ="id" column ="c_id" />
+ <result property ="name" column ="c_name" />
+ <association property ="teacher" javaType ="com.lcb.user.Teacher" >
+ <id property ="id" column ="t_id" />
+ <result property ="name" column ="t_name" />
+ </association >
+ </resultMap >
+
+ <select id ="getClass2" parameterType ="int" resultMap ="ClassesResultMap2" >
+ select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}
+ </select >
+ <resultMap type ="com.lcb.user.Classes" id ="ClassesResultMap2" >
+ <id property ="id" column ="c_id" />
+ <result property ="name" column ="c_name" />
+ <association property ="teacher" javaType ="com.lcb.user.Teacher" >
+ <id property ="id" column ="t_id" />
+ <result property ="name" column ="t_name" />
+ </association >
+
+
+ <collection property ="student" ofType ="com.lcb.user.Student" >
+ <id property ="id" column ="s_id" />
+ <result property ="name" column ="s_name" />
+ </collection >
+ </resultMap >
+</mapper >
+
+MyBatis实现一对一有几种方式?具体怎么操作的?
+有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
+嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
+MyBatis实现一对多有几种方式,怎么操作的?
+有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
+Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
+Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
+它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
+当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
+Mybatis的一级、二级缓存:
+1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
+2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
+3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
+什么是MyBatis的接口绑定?有哪些实现方式?
+接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
+接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
+使用MyBatis的mapper接口调用时有哪些要求?
+1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
+2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
+3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
+4、Mapper.xml文件中的namespace即是mapper接口的类路径。
+Mapper编写有哪几种方式?
+接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。
+1、在sqlMapConfig.xml中配置mapper.xml的位置
+<mappers >
+ <mapper resource ="mapper.xml文件的地址" />
+ <mapper resource ="mapper.xml文件的地址" />
+</mappers >
+
+2、定义mapper接口
+3、实现类集成SqlSessionDaoSupportmapper方法中可以this.getSqlSession()进行数据增删改查。
+4、spring 配置
+<bean id =" " class ="mapper接口的实现" >
+ <property name ="sqlSessionFactory" ref ="sqlSessionFactory" > </property >
+</bean >
+
+第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:
+1、在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
+<mappers >
+ <mapper resource ="mapper.xml文件的地址" />
+ <mapper resource ="mapper.xml文件的地址" />
+</mappers >
+
+2、定义mapper接口:
+mapper.xml中的namespace为mapper接口的地址
+mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致
+Spring中定义
+<bean id ="" class ="org.mybatis.spring.mapper.MapperFactoryBean" >
+ <property name ="mapperInterface" value ="mapper接口地址" />
+ <property name ="sqlSessionFactory" ref ="sqlSessionFactory" />
+</bean >
+
+第三种:使用mapper扫描器:
+1、mapper.xml文件编写:
+mapper.xml中的namespace为mapper接口的地址;mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。
+2、定义mapper接口:
+注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录
+3、配置mapper扫描器:
+<bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" >
+ <property name ="basePackage" value ="mapper接口包地址" > </property >
+ <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" />
+</bean >
+
+4、使用扫描器后从spring容器中获取mapper的实现对象。
+简述Mybatis的插件运行原理,以及如何编写一个插件。
+Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
+编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/sql/mybatis.json b/zh-cn/blog/sql/mybatis.json
new file mode 100644
index 0000000..fdf193b
--- /dev/null
+++ b/zh-cn/blog/sql/mybatis.json
@@ -0,0 +1,6 @@
+{
+ "filename": "mybatis.md",
+ "__html": "Mybatis使用心德 \n什么是Mybatis? \n\n\nMybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
\n \n\nMyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
\n \n\n通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
\n \n \nMybaits的优点: \n\n\n基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
\n \n\n与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
\n \n\n很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
\n \n\n能够与Spring很好的集成;
\n \n\n提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
\n \n \nMyBatis框架的缺点: \n\n\nSQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
\n \n\nSQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
\n \n \nMyBatis框架适用场合: \n\n\nMyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
\n \n\n对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
\n \n \nMyBatis与Hibernate有哪些不同? \n\n\nMybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
\n \n\nMybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
\n \n\nHibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
\n \n \n#{}和${}的区别是什么? \n\n\n#{}是预编译处理,${}是字符串替换。
\n \n\nMybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
\n \n\nMybatis在处理{}时,就是把{}替换成变量的值。
\n \n\n使用#{}可以有效的防止SQL注入,提高系统安全性。
\n \n \n当实体类中的属性名和表中的字段名不一样 ,怎么办 ? \n第1种:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
\n<select id = ”selectorder” parametertype = ”int” resultetype = ”me.gacl.domain.order” > \n select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};\n</select > \n
\n第2种:通过 来映射字段名和实体类属性名的一一对应的关系。
\n<select id =\"getOrder\" parameterType =\"int\" resultMap =\"orderresultmap\" > \nselect * from orders where order_id=#{id}\n</select > \n<resultMap type =”me.gacl.domain.order” id =”orderresultmap” > \n <!–用id属性来映射主键字段– > \n <id property =”id” column =”order_id” > \n <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性– > \n <result property = “orderno” column =”order_no”/ > \n <result property =”price” column =”order_price” /> \n</reslutMap > \n
\n模糊查询like语句该怎么写? \n第1种:在Java代码中添加sql通配符。
\nstring wildcardname = “%smi%”;\nlist<name > names = mapper.selectlike(wildcardname);\n<select id =”selectlike” > \nselect * from foo where bar like #{value}\n</select > \n
\n第2种:在sql语句中拼接通配符,会引起sql注入
\nstring wildcardname = “smi”;\nlist<name > names = mapper.selectlike(wildcardname);\n<select id =”selectlike” > \nselect * from foo where bar like \"%\"#{value}\"%\"\n</select > \n
\n通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗? \nDao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
\nMapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个 <select>、<insert>、<update>、<delete>
标签,都会被解析为一个MapperStatement对象。
\n举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为 com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。
\nMapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
\nMybatis是如何进行分页的?分页插件的原理是什么? \nMybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
\n分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
\nMybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? \n第一种是使用 标签,逐一定义数据库列名和对象属性名之间的映射关系。
\n第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
\n有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
\n如何执行批量插入? \n首先,创建一个简单的insert语句:
\n<insert id =”insertname” > \ninsert into names (name) values (#{value})\n</insert > \n
\n然后在java代码中像下面这样执行批处理插入:
\nlist < string > names = new arraylist();\nnames.add(“fred”);\nnames.add(“barney”);\nnames.add(“betty”);\nnames.add(“wilma”);\n\nsqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);\ntry {\n namemapper mapper = sqlsession.getmapper(namemapper.class);\n for (string name: names) {\n mapper.insertname(name);\n }\n sqlsession.commit();\n} catch (Exception e) {\n e.printStackTrace();\n sqlSession.rollback();\n throw e;\n}\nfinally {\n sqlsession.close();\n}\n
\n如何获取自动生成的(主)键值? \ninsert 方法总是返回一个int值 ,这个值代表的是插入的行数。
\n如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
\n示例:
\n<insert id=”insertname” usegeneratedkeys=”true ” keyproperty=”id”>\n insert into names (name) values (#{name}) \n</insert>\nname name = new name();\nname.setname(“fred”);\nint rows = mapper.insertname(name);\n\nsystem.out.println(“rows inserted = ” + rows);\nsystem.out.println(“generated key value = ” + name.getid());\n
\n在mapper中如何传递多个参数? \n\n第一种:\nDAO层的函数 \n \n\n<select id =\"selectUser\" resultMap =\"BaseResultMap\" > \n select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}\n</select > \n
\n\n第二种:使用 @param 注解: \n \n\npublic interface usermapper {\n user selectuser (@param(“username”) string username,@param (“hashedpassword”) string hashedpassword) ;\n}\n
\n然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
\n<select id =”selectuser” resulttype =”user” > \n select id, username, hashedpassword\n from some_table\n where username = #{username}\n and hashedpassword = #{hashedpassword}\n</select > \n
\n\n第三种:多个参数封装成map \n \ntry {\n \n \n Map < String, Object > map = new HashMap();\n map.put(\"start\" , start);\n map.put(\"end\" , end);\n return sqlSession.selectList(\"StudentID.pagination\" , map);\n} catch (Exception e) {\n e.printStackTrace();\n sqlSession.rollback();\n throw e;\n} finally {\n MybatisUtil.closeSqlSession();\n
\nMybatis动态sql有什么用?执行原理?有哪些动态sql? \nMybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
\nMybatis提供了9种动态sql标签:
\ntrim|where|set|foreach|if|choose|when|otherwise|bind。
\nXml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签? \n<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>
,加上动态sql的9个标签,其中 <sql>
为sql片段标签,通过 <include>
标签引入sql片段, <selectKey>
为不支持自增的主键生成策略标签。
\nMybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复? \n不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
\n原因就是namespace+id是作为Map <String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
\n为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? \nHibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
\n一对一、一对多的关联查询 ? \n<mapper namespace =\"com.lcb.mapping.userMapper\" > \n \n <select id =\"getClass\" parameterType =\"int\" resultMap =\"ClassesResultMap\" > \n select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}\n </select > \n <resultMap type =\"com.lcb.user.Classes\" id =\"ClassesResultMap\" > \n \n <id property =\"id\" column =\"c_id\" /> \n <result property =\"name\" column =\"c_name\" /> \n <association property =\"teacher\" javaType =\"com.lcb.user.Teacher\" > \n <id property =\"id\" column =\"t_id\" /> \n <result property =\"name\" column =\"t_name\" /> \n </association > \n </resultMap > \n \n <select id =\"getClass2\" parameterType =\"int\" resultMap =\"ClassesResultMap2\" > \n select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}\n </select > \n <resultMap type =\"com.lcb.user.Classes\" id =\"ClassesResultMap2\" > \n <id property =\"id\" column =\"c_id\" /> \n <result property =\"name\" column =\"c_name\" /> \n <association property =\"teacher\" javaType =\"com.lcb.user.Teacher\" > \n <id property =\"id\" column =\"t_id\" /> \n <result property =\"name\" column =\"t_name\" /> \n </association > \n\n\n <collection property =\"student\" ofType =\"com.lcb.user.Student\" > \n <id property =\"id\" column =\"s_id\" /> \n <result property =\"name\" column =\"s_name\" /> \n </collection > \n </resultMap > \n</mapper > \n
\nMyBatis实现一对一有几种方式?具体怎么操作的? \n有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;
\n嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
\nMyBatis实现一对多有几种方式,怎么操作的? \n有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
\nMybatis是否支持延迟加载?如果支持,它的实现原理是什么? \nMybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
\n它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
\n当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
\nMybatis的一级、二级缓存: \n1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
\n2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
\n3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
\n什么是MyBatis的接口绑定?有哪些实现方式? \n接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
\n接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
\n使用MyBatis的mapper接口调用时有哪些要求? \n1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同;\n2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;\n3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;\n4、Mapper.xml文件中的namespace即是mapper接口的类路径。
\nMapper编写有哪几种方式? \n接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。
\n1、在sqlMapConfig.xml中配置mapper.xml的位置
\n<mappers > \n <mapper resource =\"mapper.xml文件的地址\" /> \n <mapper resource =\"mapper.xml文件的地址\" /> \n</mappers > \n
\n2、定义mapper接口
\n3、实现类集成SqlSessionDaoSupportmapper方法中可以this.getSqlSession()进行数据增删改查。
\n4、spring 配置
\n<bean id =\" \" class =\"mapper接口的实现\" > \n <property name =\"sqlSessionFactory\" ref =\"sqlSessionFactory\" > </property > \n</bean > \n
\n第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:\n1、在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
\n<mappers > \n <mapper resource =\"mapper.xml文件的地址\" /> \n <mapper resource =\"mapper.xml文件的地址\" /> \n</mappers > \n
\n2、定义mapper接口:
\nmapper.xml中的namespace为mapper接口的地址\nmapper接口中的方法名和mapper.xml中的定义的statement的id保持一致\nSpring中定义
\n<bean id =\"\" class =\"org.mybatis.spring.mapper.MapperFactoryBean\" > \n <property name =\"mapperInterface\" value =\"mapper接口地址\" /> \n <property name =\"sqlSessionFactory\" ref =\"sqlSessionFactory\" /> \n</bean > \n
\n第三种:使用mapper扫描器:
\n1、mapper.xml文件编写:
\nmapper.xml中的namespace为mapper接口的地址;mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。
\n2、定义mapper接口:
\n注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录
\n3、配置mapper扫描器:
\n<bean class =\"org.mybatis.spring.mapper.MapperScannerConfigurer\" > \n <property name =\"basePackage\" value =\"mapper接口包地址\" > </property > \n <property name =\"sqlSessionFactoryBeanName\" value =\"sqlSessionFactory\" /> \n</bean > \n
\n4、使用扫描器后从spring容器中获取mapper的实现对象。
\n简述Mybatis的插件运行原理,以及如何编写一个插件。 \nMybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
\n编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
\n",
+ "link": "\\zh-cn\\blog\\sql\\mybatis.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/sql/mysql_backups.html b/zh-cn/blog/sql/mysql_backups.html
new file mode 100644
index 0000000..b8045ad
--- /dev/null
+++ b/zh-cn/blog/sql/mysql_backups.html
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
+
+
+ mysql_backups
+
+
+
+
+ 数据备份与恢复
+
+一、备份简介
+ 2.1 备份分类
+ 2.2 备份工具
+二、mysqldump
+ 2.1 常用参数
+ 2.2 全量备份
+ 2.3 增量备份
+三、mysqlpump
+ 3.1 功能优势
+ 3.2 常用参数
+四、Xtrabackup
+ 4.1 在线安装
+ 4.2 全量备份
+ 4.3 增量备份
+五、二进制日志的备份
+
+一、备份简介
+2.1 备份分类
+按照不同的思考维度,通常将数据库的备份分为以下几类:
+物理备份 与 逻辑备份
+
+物理备份:备份的是完整的数据库目录和数据文件。采用该模式会进行大量的 IO 操作,但不含任何逻辑转换,因此备份和恢复速度通常都比较快。
+逻辑备份:通过数据库结构和内容信息来进行备份。因为要执行逻辑转换,因此其速度较慢,并且在以文本格式保存时,其输出文件的大小大于物理备份。逻辑备份的还原的粒度可以从服务器级别(所有数据库)精确到具体表,但备份不会包括日志文件、配置文件等与数据库无关的内容。
+
+全量备份 与 增量备份
+
+全量备份:备份服务器在给定时间点上的所有数据。
+增量备份:备份在给定时间跨度内(从一个时间点到另一个时间点)对数据所做的更改。
+
+在线备份 与 离线备份
+
+在线备份:数据库服务在运行状态下进行备份。此时其他客户端依旧可以连接到数据库,但为了保证数据的一致性,在备份期间可能会对数据进行加锁,此时客户端的访问依然会受限。
+离线备份:在数据库服务停机状态下进行备份。此备份过程简单,但由于无法提供对外服务,通常会对业务造成比较大的影响。
+
+2.2 备份工具
+MySQL 支持的备份工具有很多种,这里列出常用的三种:
+
+mysqldump :这是 MySQL 自带的备份工具,其采用的备份方式是逻辑备份,支持全库备份、单库备份、单表备份。由于其采用的是逻辑备份,所以生成的备份文件比物理备份的大,且所需恢复时间也比较长。
+mysqlpump :这是 MySQL 5.7 之后新增的备份工具,在 mysqldump 的基础上进行了功能的扩展,支持多线程备份,支持对备份文件进行压缩,能够提高备份的速度和降低备份文件所需的储存空间。
+Xtrabackup :这是 Percona 公司开发的实时热备工具,能够在不停机的情况下进行快速可靠的热备份,并且备份期间不会间断数据库事务的处理。它支持数据的全备和增备,并且由于其采用的是物理备份的方式,所以恢复速度比较快。
+
+二、mysqldump
+2.1 常用参数
+mysqldump 的基本语法如下:
+# 备份数据库或数据库中的指定表
+mysqldump [options] db_name [tbl_name ...]
+# 备份多个指定的数据库
+mysqldump [options] --databases db_name ...
+# 备份当前数据库实例中的所有表
+mysqldump [options] --all-databases
+
+options 代表可选操作,常用的可选参数如下:
+
+
+--host=host_name, -h host_name
+指定服务器地址。
+
+
+--user=user_name, -u user_name
+指定用户名。
+
+
+--password[=password], -p[password]
+指定密码。通常无需在命令行中明文指定,按照提示输入即可。
+
+
+--default-character-set=charset_name
+导出文本使用的字符集,默认为 utf8。
+
+
+--events, -E
+备份包含数据库中的事件。
+
+
+--ignore-table=db_name.tbl_name
+不需要进行备份的表,必须使用数据库和表名来共同指定。也可以作用于视图。
+
+
+--routines, -R
+备份包含数据库中的存储过程和自定义函数。
+
+
+--triggers
+备份包含数据库中的触发器。
+
+
+--where='where_condition', -w 'where_condition'
+在对单表进行导出时候,可以指定过滤条件,例如指定用户名 --where="user='jimf'"
或用户范围 -w"userid>1"
。
+
+
+--lock-all-tables, -x
+锁定所有数据库中的所有表,从而保证备份数据的一致性。此选项自动关闭 --single-transaction
和 --lock-tables
。
+
+
+--lock-tables, -l
+锁定当前数据库中所有表,能够保证当前数据库中表的一致性,但不能保证全局的一致性。
+
+
+--single-transaction
+此选项会将事务隔离模式设置为 REPEATABLE READ 并开启一个事务,从而保证备份数据的一致性。主要用于事务表,如 InnoDB 表。 但是此时仍然不能在备份表上执行 ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE, TRUNCATE TABLE 等操作,因为 REPEATABLE READ 并不能隔离这些操作。
+另外需要注意的是 --single-transaction
选项与 --lock-tables
选项是互斥的,因为 LOCK TABLES 会导致任何正在挂起的事务被隐式提交。转储大表时,可以将 --single-transaction
选项与 --quick
选项组合使用 。
+
+
+--quick, -q
+主要用于备份大表。它强制 mysqldump 一次只从服务器检索一行数据,避免一次检索所有行而导致缓存溢出。
+
+
+--flush-logs, -F
+在开始备份前刷新 MySQL 的日志文件。此选项需要 RELOAD 权限。如果此选项与 --all-databases
配合使用,则会在每个数据库开始备份前都刷新一次日志。如果配合 --lock-all-tables
,--master-data
或 --single-transaction
使用,则只会在锁定所有表或者开启事务时刷新一次。
+
+
+--master-data[=value ]
+可以通过配置此参数来控制生成的备份文件是否包含 CHANGE MASTER 语句,该语句中包含了当前时间点二进制日志的信息。该选项有两个可选值:1 和 2 ,设置为 1 时 CHANGE MASTER 语句正常生成,设置为 2 时以注释的方式生成。--master-data
选项还会自动关闭 --lock-tables
选项,而且如果你没有指定 --single-transaction
选项,那么它还会启用 --lock-all-tables
选项,在这种情况下,会在备份开始时短暂内获取全局读锁。
+
+
+2.2 全量备份
+mysqldump 的全量备份与恢复的操作比较简单,示例如下:
+# 备份雇员库
+mysqldump -uroot -p --databases employees > employees_bak.sql
+
+# 恢复雇员库
+mysql -uroot -p < employees_bak.sql
+
+单表备份:
+# 备份雇员库中的职位表
+mysqldump -uroot -p --single-transaction employees titles > titles_bak.sql
+
+# 恢复雇员库中的职位表
+mysql> use employees;
+mysql> source /root/mysqldata/titles_bak.sql;
+
+2.3 增量备份
+mysqldump 本身并不能直接进行增量备份,需要通过分析二进制日志的方式来完成。具体示例如下:
+1. 基础全备
+1.先执行一次全备作为基础,这里以单表备份为例,需要用到上文提到的 --master-data
参数,语句如下:
+mysqldump -uroot -p --master-data=2 --flush-logs employees titles > titles_bak.sql
+
+使用 more 命令查看备份文件,此时可以在文件开头看到 CHANGE MASTER 语句,语句中包含了二进制日志的名称和偏移量信息,具体如下:
+
+
+2. 增量恢复
+对表内容进行任意修改,然后通过分析二进制日志文件来生成增量备份的脚本文件,示例如下:
+mysqlbinlog --start-position=155 \
+--database=employees ${MYSQL_HOME}/data/mysql-bin.000004 > titles_inr_bak_01.sql
+
+需要注意的是,在实际生产环境中,可能在全量备份后与增量备份前的时间间隔里生成了多份二进制文件,此时需要对每一个二进制文件都执行相同的命令:
+mysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000005 > titles_inr_bak_02.sql
+mysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000006 > titles_inr_bak_03.sql
+.....
+
+之后将全备脚本 ( titles_bak.sql ),以及所有的增备脚本 ( inr_01.sql,inr_02.sql .... ) 通过 source 命令导入即可,这样就完成了全量 + 增量的恢复。
+三、mysqlpump
+3.1 功能优势
+mysqlpump 在 mysqldump 的基础上进行了扩展增强,其主要的优点如下:
+
+
+能够并行处理数据库及其中的对象,从而可以加快备份进程;
+
+
+能够更好地控制数据库及数据库对象(表,存储过程,用户帐户等);
+
+
+能够直接对备份文件进行压缩;
+
+
+备份时能够显示进度指标(估计值);
+
+
+备份用户时生成的是 CREATE USER 与 GRANT 语句,而不是像 mysqldump 一样备份成数据,可以方便用户按需恢复。
+
+
+3.2 常用参数
+mysqlpump 的使用和 mysqldump 基本一致,这里不再进行赘述。以下主要介绍部分新增的可选项,具体如下:
+
+
+--default-parallelism=N
+每个并行处理队列的默认线程数。默认值为 2。
+
+
+--parallel-schemas=[N:]db_list
+用于并行备份多个数据库:db_list 是一个或多个以逗号分隔的数据库名称列表;N 为使用的线程数,如果没有设置,则使用 --default-parallelism
参数的值。
+
+
+--users
+将用户信息备份为 CREATE USER 语句和 GRANT 语句 。如果想要只备份用户信息,则可以使用下面的命令:
+mysqlpump --exclude-databases=% --users
+
+
+
+--compress-output=algorithm
+默认情况下,mysqlpump 不对备份文件进行压缩。可以使用该选项指定压缩格式,当前支持 LZ4 和 ZLIB 两种格式。需要注意的是压缩后的文件可以占用更少的存储空间,但是却不能直接用于备份恢复,需要先进行解压,具体如下:
+# 采用lz4算法进行压缩
+mysqlpump --compress-output=LZ4 > dump.lz4
+# 恢复前需要先进行解压
+lz4_decompress input_file output_file
+
+# 采用ZLIB算法进行压缩
+mysqlpump --compress-output=ZLIB > dump.zlib
+zlib_decompress input_file output_file
+
+MySQL 发行版自带了上面两个压缩工具,不需要进行额外安装。以上就是 mysqlpump 新增的部分常用参数,完整参数可以参考官方文档:mysqlpump — A Database Backup Program
+
+
+四、Xtrabackup
+4.1 在线安装
+Xtrabackup 可以直接使用 yum 命令进行安装,这里我的 MySQL 为 8.0 ,对应安装的 Xtrabackup 也为 8.0,命令如下:
+# 安装Percona yum 源
+yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
+
+# 安装
+yum install percona-xtrabackup-80
+
+4.2 全量备份
+全量备份的具体步骤如下:
+1. 创建备份
+Xtrabackup 全量备份的基本语句如下,可以使用 target-dir 指明备份文件的存储位置,parallel 则是指明操作的并行度:
+xtrabackup --backup --user=root --password --parallel=3 --target-dir=/data/backups/
+
+以上进行的是整个数据库实例的备份,如果需要备份指定数据库,则可以使用 --databases 进行指定。
+另外一个容易出现的异常是:Xtrabackup 在进行备份时,默认会去 /var/lib/mysql/mysql.sock
文件里获取数据库的 socket 信息,如果你修改了数据库的 socket 配置,则需要使用 --socket 参数进行重新指定,否则会抛出找不到连接的异常。备份完整后需要立即执行的另外一个操作是 prepare (准备备份)。
+2. 准备备份
+由于备份是将所有物理库表等文件复制到备份目录,而整个过程需要持续一段时间,此时备份的数据中就可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务,最终导致备份结果处于不一致状态。此时需要进行 prepare 操作来回滚未提交的事务及同步已经提交的事务至数据文件,从而使得整体达到一致性状态。命令如下:
+xtrabackup --prepare --target-dir=/data/backups/
+
+需要特别注意的在该阶段不要随意中断 xtrabackup 进程,因为这可能会导致数据文件损坏,备份将无法使用。
+3. 恢复备份
+由于 xtrabackup 执行的是物理备份,所以想要进行恢复,必须先要停止 MySQL 服务。同时这里我们可以删除 MySQL 的数据目录来模拟数据丢失的情况,之后使用以下命令将备份文件拷贝到 MySQL 的数据目录下:
+# 模拟数据异常丢失
+rm -rf /usr/app/mysql-8.0.17/data/*
+
+# 将备份文件拷贝到 data 目录下
+xtrabackup --copy-back --target-dir=/data/backups/
+
+copy-back 命令只需要指定备份文件的位置,不需要指定 MySQL 数据目录的位置,因为 Xtrabackup 会自动从 /etc/my.cnf
上获取 MySQL 的相关信息,包括数据目录的位置。如果不需要保留备份文件,可以直接使用 --move-back
命令,代表直接将备份文件移动到数据目录下。此时数据目录的所有者通常为执行命令的用户,需要更改为 mysql 用户,命令如下:
+chown -R mysql:mysql /usr/app/mysql-8.0.18/data
+
+再次启动即可完成备份恢复。
+4.3 增量备份
+使用 Xtrabackup 进行增量备份时,每一次增量备份都需要以上一次的备份为基础,之后再将增量备份运用到第一次全备之上,从而完成备份。具体操作如下:
+1. 创建备份
+这里首先创建一个全备作为基础:
+xtrabackup --backup --user=root --password=xiujingmysql. --host=172.17.0.4 --port=13306 --datadir=/data/mysql/data --parallel-3 --target-dir=/data/backups
+
+之后修改库中任意数据,然后进行第一次增量备份,此时需要使用 incremental-basedir
指定基础目录为全备目录:
+xtrabackup --user=root --password --backup --target-dir=/data/backups/inc1 \
+--incremental-basedir=/data/backups/base
+
+再修改库中任意数据,然后进行第二次增量备份,此时需要使用 incremental-basedir
指定基础目录为上一次增备目录:
+xtrabackup --user=root --password --backup --target-dir=/data/backups/inc2 \
+--incremental-basedir=/data/backups/inc1
+
+2. 准备备份
+准备基础备份:
+xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base
+
+将第一次备份作用于全备数据:
+xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base \
+--incremental-dir=/data/backups/inc1
+
+将第二次备份作用于全备数据:
+xtrabackup --prepare --target-dir=/data/backups/base \
+--incremental-dir=/data/backups/inc2
+
+在准备备份时候,除了最后一次增备外,其余的准备命令都需要加上 --apply-log-only
选项来阻止事务的回滚,因为备份时未提交的事务可能正在进行,并可能在下一次增量备份中提交,如果不进行阻止,那么增量备份将没有任何意义。
+3. 恢复备份
+恢复备份和全量备份时相同,只需要最终准备好的全备数据复制到 MySQL 的数据目录下即可:
+xtrabackup --copy-back --target-dir=/data/backups/base
+# 必须修改文件权限,否则无法启动
+chown -R mysql:mysql /usr/app/mysql-8.0.17/data
+
+此时增量备份就已经完成。需要说明的是:按照上面的情况,如果第二次备份之后发生了宕机,那么第二次备份后到宕机前的数据依然没法通过 Xtrabackup 进行恢复,此时就只能采用上面介绍的分析二进制日志的恢复方法。由此可以看出,无论是采用何种备份方式,二进制日志都是非常重要的,因此最好对其进行实时备份。
+五、二进制日志的备份
+想要备份二进制日志文件,可以通过定时执行 cp 或 scp 等命令来实现,也可以通过 mysqlbinlog 自带的功能来实现远程备份,将远程服务器上的二进制日志文件复制到本机,命令如下:
+mysqlbinlog --read-from-remote-server --raw --stop-never \
+--host=主机名 --port=3306 \
+--user=用户名 --password=密码 初始复制时的日志文件名
+
+需要注意的是这里的用户必须具有 replication slave 权限,因为上述命令本质上是模拟主从复制架构下,从节点通过 IO 线程不断去获取主节点的二进制日志,从而达到备份的目的。
+参考资料
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/sql/mysql_backups.json b/zh-cn/blog/sql/mysql_backups.json
new file mode 100644
index 0000000..50e87db
--- /dev/null
+++ b/zh-cn/blog/sql/mysql_backups.json
@@ -0,0 +1,6 @@
+{
+ "filename": "mysql_backups.md",
+ "__html": "数据备份与恢复 \n\n一、备份简介 \n 2.1 备份分类 \n 2.2 备份工具 \n二、mysqldump \n 2.1 常用参数 \n 2.2 全量备份 \n 2.3 增量备份 \n三、mysqlpump \n 3.1 功能优势 \n 3.2 常用参数 \n四、Xtrabackup \n 4.1 在线安装 \n 4.2 全量备份 \n 4.3 增量备份 \n五、二进制日志的备份 \n \n一、备份简介 \n2.1 备份分类 \n按照不同的思考维度,通常将数据库的备份分为以下几类:
\n物理备份 与 逻辑备份
\n\n物理备份:备份的是完整的数据库目录和数据文件。采用该模式会进行大量的 IO 操作,但不含任何逻辑转换,因此备份和恢复速度通常都比较快。 \n逻辑备份:通过数据库结构和内容信息来进行备份。因为要执行逻辑转换,因此其速度较慢,并且在以文本格式保存时,其输出文件的大小大于物理备份。逻辑备份的还原的粒度可以从服务器级别(所有数据库)精确到具体表,但备份不会包括日志文件、配置文件等与数据库无关的内容。 \n \n全量备份 与 增量备份
\n\n全量备份:备份服务器在给定时间点上的所有数据。 \n增量备份:备份在给定时间跨度内(从一个时间点到另一个时间点)对数据所做的更改。 \n \n在线备份 与 离线备份
\n\n在线备份:数据库服务在运行状态下进行备份。此时其他客户端依旧可以连接到数据库,但为了保证数据的一致性,在备份期间可能会对数据进行加锁,此时客户端的访问依然会受限。 \n离线备份:在数据库服务停机状态下进行备份。此备份过程简单,但由于无法提供对外服务,通常会对业务造成比较大的影响。 \n \n2.2 备份工具 \nMySQL 支持的备份工具有很多种,这里列出常用的三种:
\n\nmysqldump :这是 MySQL 自带的备份工具,其采用的备份方式是逻辑备份,支持全库备份、单库备份、单表备份。由于其采用的是逻辑备份,所以生成的备份文件比物理备份的大,且所需恢复时间也比较长。 \nmysqlpump :这是 MySQL 5.7 之后新增的备份工具,在 mysqldump 的基础上进行了功能的扩展,支持多线程备份,支持对备份文件进行压缩,能够提高备份的速度和降低备份文件所需的储存空间。 \nXtrabackup :这是 Percona 公司开发的实时热备工具,能够在不停机的情况下进行快速可靠的热备份,并且备份期间不会间断数据库事务的处理。它支持数据的全备和增备,并且由于其采用的是物理备份的方式,所以恢复速度比较快。 \n \n二、mysqldump \n2.1 常用参数 \nmysqldump 的基本语法如下:
\n# 备份数据库或数据库中的指定表 \nmysqldump [options] db_name [tbl_name ...]\n# 备份多个指定的数据库 \nmysqldump [options] --databases db_name ...\n# 备份当前数据库实例中的所有表 \nmysqldump [options] --all-databases\n
\noptions 代表可选操作,常用的可选参数如下:
\n\n\n--host=host_name, -h host_name
\n指定服务器地址。
\n \n\n--user=user_name, -u user_name
\n指定用户名。
\n \n\n--password[=password], -p[password]
\n指定密码。通常无需在命令行中明文指定,按照提示输入即可。
\n \n\n--default-character-set=charset_name
\n导出文本使用的字符集,默认为 utf8。
\n \n\n--events, -E
\n备份包含数据库中的事件。
\n \n\n--ignore-table=db_name.tbl_name
\n不需要进行备份的表,必须使用数据库和表名来共同指定。也可以作用于视图。
\n \n\n--routines, -R
\n备份包含数据库中的存储过程和自定义函数。
\n \n\n--triggers
\n备份包含数据库中的触发器。
\n \n\n--where='where_condition', -w 'where_condition'
\n在对单表进行导出时候,可以指定过滤条件,例如指定用户名 --where="user='jimf'"
或用户范围 -w"userid>1"
。
\n \n\n--lock-all-tables, -x
\n锁定所有数据库中的所有表,从而保证备份数据的一致性。此选项自动关闭 --single-transaction
和 --lock-tables
。
\n \n\n--lock-tables, -l
\n锁定当前数据库中所有表,能够保证当前数据库中表的一致性,但不能保证全局的一致性。
\n \n\n--single-transaction
\n此选项会将事务隔离模式设置为 REPEATABLE READ 并开启一个事务,从而保证备份数据的一致性。主要用于事务表,如 InnoDB 表。 但是此时仍然不能在备份表上执行 ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE, TRUNCATE TABLE 等操作,因为 REPEATABLE READ 并不能隔离这些操作。
\n另外需要注意的是 --single-transaction
选项与 --lock-tables
选项是互斥的,因为 LOCK TABLES 会导致任何正在挂起的事务被隐式提交。转储大表时,可以将 --single-transaction
选项与 --quick
选项组合使用 。
\n \n\n--quick, -q
\n主要用于备份大表。它强制 mysqldump 一次只从服务器检索一行数据,避免一次检索所有行而导致缓存溢出。
\n \n\n--flush-logs, -F
\n在开始备份前刷新 MySQL 的日志文件。此选项需要 RELOAD 权限。如果此选项与 --all-databases
配合使用,则会在每个数据库开始备份前都刷新一次日志。如果配合 --lock-all-tables
,--master-data
或 --single-transaction
使用,则只会在锁定所有表或者开启事务时刷新一次。
\n \n\n--master-data[=value ]
\n可以通过配置此参数来控制生成的备份文件是否包含 CHANGE MASTER 语句,该语句中包含了当前时间点二进制日志的信息。该选项有两个可选值:1 和 2 ,设置为 1 时 CHANGE MASTER 语句正常生成,设置为 2 时以注释的方式生成。--master-data
选项还会自动关闭 --lock-tables
选项,而且如果你没有指定 --single-transaction
选项,那么它还会启用 --lock-all-tables
选项,在这种情况下,会在备份开始时短暂内获取全局读锁。
\n \n \n2.2 全量备份 \nmysqldump 的全量备份与恢复的操作比较简单,示例如下:
\n# 备份雇员库 \nmysqldump -uroot -p --databases employees > employees_bak.sql\n\n# 恢复雇员库 \nmysql -uroot -p < employees_bak.sql\n
\n单表备份:
\n# 备份雇员库中的职位表 \nmysqldump -uroot -p --single-transaction employees titles > titles_bak.sql\n\n# 恢复雇员库中的职位表 \nmysql> use employees; \nmysql> source /root/mysqldata/titles_bak.sql; \n
\n2.3 增量备份 \nmysqldump 本身并不能直接进行增量备份,需要通过分析二进制日志的方式来完成。具体示例如下:
\n1. 基础全备 \n1.先执行一次全备作为基础,这里以单表备份为例,需要用到上文提到的 --master-data
参数,语句如下:
\nmysqldump -uroot -p --master-data=2 --flush-logs employees titles > titles_bak.sql\n
\n使用 more 命令查看备份文件,此时可以在文件开头看到 CHANGE MASTER 语句,语句中包含了二进制日志的名称和偏移量信息,具体如下:
\n\n
\n2. 增量恢复 \n对表内容进行任意修改,然后通过分析二进制日志文件来生成增量备份的脚本文件,示例如下:
\nmysqlbinlog --start-position=155 \\\n--database=employees ${MYSQL_HOME}/data/mysql-bin.000004 > titles_inr_bak_01.sql\n
\n需要注意的是,在实际生产环境中,可能在全量备份后与增量备份前的时间间隔里生成了多份二进制文件,此时需要对每一个二进制文件都执行相同的命令:
\nmysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000005 > titles_inr_bak_02.sql\nmysqlbinlog --database=employees ${MYSQL_HOME}/data/mysql-bin.000006 > titles_inr_bak_03.sql\n.....\n
\n之后将全备脚本 ( titles_bak.sql ),以及所有的增备脚本 ( inr_01.sql,inr_02.sql .... ) 通过 source 命令导入即可,这样就完成了全量 + 增量的恢复。
\n三、mysqlpump \n3.1 功能优势 \nmysqlpump 在 mysqldump 的基础上进行了扩展增强,其主要的优点如下:
\n\n\n能够并行处理数据库及其中的对象,从而可以加快备份进程;
\n \n\n能够更好地控制数据库及数据库对象(表,存储过程,用户帐户等);
\n \n\n能够直接对备份文件进行压缩;
\n \n\n备份时能够显示进度指标(估计值);
\n \n\n备份用户时生成的是 CREATE USER 与 GRANT 语句,而不是像 mysqldump 一样备份成数据,可以方便用户按需恢复。
\n \n \n3.2 常用参数 \nmysqlpump 的使用和 mysqldump 基本一致,这里不再进行赘述。以下主要介绍部分新增的可选项,具体如下:
\n\n\n--default-parallelism=N
\n每个并行处理队列的默认线程数。默认值为 2。
\n \n\n--parallel-schemas=[N:]db_list
\n用于并行备份多个数据库:db_list 是一个或多个以逗号分隔的数据库名称列表;N 为使用的线程数,如果没有设置,则使用 --default-parallelism
参数的值。
\n \n\n--users
\n将用户信息备份为 CREATE USER 语句和 GRANT 语句 。如果想要只备份用户信息,则可以使用下面的命令:
\nmysqlpump --exclude-databases=% --users\n
\n \n\n--compress-output=algorithm
\n默认情况下,mysqlpump 不对备份文件进行压缩。可以使用该选项指定压缩格式,当前支持 LZ4 和 ZLIB 两种格式。需要注意的是压缩后的文件可以占用更少的存储空间,但是却不能直接用于备份恢复,需要先进行解压,具体如下:
\n# 采用lz4算法进行压缩 \nmysqlpump --compress-output=LZ4 > dump.lz4\n# 恢复前需要先进行解压 \nlz4_decompress input_file output_file\n\n# 采用ZLIB算法进行压缩 \nmysqlpump --compress-output=ZLIB > dump.zlib\nzlib_decompress input_file output_file\n
\nMySQL 发行版自带了上面两个压缩工具,不需要进行额外安装。以上就是 mysqlpump 新增的部分常用参数,完整参数可以参考官方文档:mysqlpump — A Database Backup Program
\n \n \n四、Xtrabackup \n4.1 在线安装 \nXtrabackup 可以直接使用 yum 命令进行安装,这里我的 MySQL 为 8.0 ,对应安装的 Xtrabackup 也为 8.0,命令如下:
\n# 安装Percona yum 源 \nyum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm\n\n# 安装 \nyum install percona-xtrabackup-80\n
\n4.2 全量备份 \n全量备份的具体步骤如下:
\n1. 创建备份 \nXtrabackup 全量备份的基本语句如下,可以使用 target-dir 指明备份文件的存储位置,parallel 则是指明操作的并行度:
\nxtrabackup --backup --user=root --password --parallel=3 --target-dir=/data/backups/\n
\n以上进行的是整个数据库实例的备份,如果需要备份指定数据库,则可以使用 --databases 进行指定。
\n另外一个容易出现的异常是:Xtrabackup 在进行备份时,默认会去 /var/lib/mysql/mysql.sock
文件里获取数据库的 socket 信息,如果你修改了数据库的 socket 配置,则需要使用 --socket 参数进行重新指定,否则会抛出找不到连接的异常。备份完整后需要立即执行的另外一个操作是 prepare (准备备份)。
\n2. 准备备份 \n由于备份是将所有物理库表等文件复制到备份目录,而整个过程需要持续一段时间,此时备份的数据中就可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务,最终导致备份结果处于不一致状态。此时需要进行 prepare 操作来回滚未提交的事务及同步已经提交的事务至数据文件,从而使得整体达到一致性状态。命令如下:
\nxtrabackup --prepare --target-dir=/data/backups/\n
\n需要特别注意的在该阶段不要随意中断 xtrabackup 进程,因为这可能会导致数据文件损坏,备份将无法使用。
\n3. 恢复备份 \n由于 xtrabackup 执行的是物理备份,所以想要进行恢复,必须先要停止 MySQL 服务。同时这里我们可以删除 MySQL 的数据目录来模拟数据丢失的情况,之后使用以下命令将备份文件拷贝到 MySQL 的数据目录下:
\n# 模拟数据异常丢失 \nrm -rf /usr/app/mysql-8.0.17/data/*\n\n# 将备份文件拷贝到 data 目录下 \nxtrabackup --copy-back --target-dir=/data/backups/\n
\ncopy-back 命令只需要指定备份文件的位置,不需要指定 MySQL 数据目录的位置,因为 Xtrabackup 会自动从 /etc/my.cnf
上获取 MySQL 的相关信息,包括数据目录的位置。如果不需要保留备份文件,可以直接使用 --move-back
命令,代表直接将备份文件移动到数据目录下。此时数据目录的所有者通常为执行命令的用户,需要更改为 mysql 用户,命令如下:
\nchown -R mysql:mysql /usr/app/mysql-8.0.18/data\n
\n再次启动即可完成备份恢复。
\n4.3 增量备份 \n使用 Xtrabackup 进行增量备份时,每一次增量备份都需要以上一次的备份为基础,之后再将增量备份运用到第一次全备之上,从而完成备份。具体操作如下:
\n1. 创建备份 \n这里首先创建一个全备作为基础:
\nxtrabackup --backup --user=root --password=xiujingmysql. --host=172.17.0.4 --port=13306 --datadir=/data/mysql/data --parallel-3 --target-dir=/data/backups\n
\n之后修改库中任意数据,然后进行第一次增量备份,此时需要使用 incremental-basedir
指定基础目录为全备目录:
\nxtrabackup --user=root --password --backup --target-dir=/data/backups/inc1 \\\n--incremental-basedir=/data/backups/base\n
\n再修改库中任意数据,然后进行第二次增量备份,此时需要使用 incremental-basedir
指定基础目录为上一次增备目录:
\nxtrabackup --user=root --password --backup --target-dir=/data/backups/inc2 \\\n--incremental-basedir=/data/backups/inc1\n
\n2. 准备备份 \n准备基础备份:
\nxtrabackup --prepare --apply-log-only --target-dir=/data/backups/base\n
\n将第一次备份作用于全备数据:
\nxtrabackup --prepare --apply-log-only --target-dir=/data/backups/base \\\n--incremental-dir=/data/backups/inc1\n
\n将第二次备份作用于全备数据:
\nxtrabackup --prepare --target-dir=/data/backups/base \\\n--incremental-dir=/data/backups/inc2\n
\n在准备备份时候,除了最后一次增备外,其余的准备命令都需要加上 --apply-log-only
选项来阻止事务的回滚,因为备份时未提交的事务可能正在进行,并可能在下一次增量备份中提交,如果不进行阻止,那么增量备份将没有任何意义。
\n3. 恢复备份 \n恢复备份和全量备份时相同,只需要最终准备好的全备数据复制到 MySQL 的数据目录下即可:
\nxtrabackup --copy-back --target-dir=/data/backups/base\n# 必须修改文件权限,否则无法启动 \nchown -R mysql:mysql /usr/app/mysql-8.0.17/data\n
\n此时增量备份就已经完成。需要说明的是:按照上面的情况,如果第二次备份之后发生了宕机,那么第二次备份后到宕机前的数据依然没法通过 Xtrabackup 进行恢复,此时就只能采用上面介绍的分析二进制日志的恢复方法。由此可以看出,无论是采用何种备份方式,二进制日志都是非常重要的,因此最好对其进行实时备份。
\n五、二进制日志的备份 \n想要备份二进制日志文件,可以通过定时执行 cp 或 scp 等命令来实现,也可以通过 mysqlbinlog 自带的功能来实现远程备份,将远程服务器上的二进制日志文件复制到本机,命令如下:
\nmysqlbinlog --read-from-remote-server --raw --stop-never \\\n--host=主机名 --port=3306 \\\n--user=用户名 --password=密码 初始复制时的日志文件名\n
\n需要注意的是这里的用户必须具有 replication slave 权限,因为上述命令本质上是模拟主从复制架构下,从节点通过 IO 线程不断去获取主节点的二进制日志,从而达到备份的目的。
\n参考资料 \n\n",
+ "link": "\\zh-cn\\blog\\sql\\mysql_backups.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/tool/git.html b/zh-cn/blog/tool/git.html
index e0cb1b6..97a419a 100644
--- a/zh-cn/blog/tool/git.html
+++ b/zh-cn/blog/tool/git.html
@@ -197,6 +197,157 @@ 程序员的那些迷之缩写
PRD : Product Requirement Document. 产品需求文档
+git常用命令归纳
+
+
+
+分支命令
+说明
+
+
+
+
+git branch
+列出所有本地分支机构。
+
+
+git branch -a
+列出远程和本地分支。
+
+
+git checkout -b branch_name
+创建一个本地分支并切换到该分支。
+
+
+git checkout branch_name
+切换到现有分支。
+
+
+git push origin branch_name
+将分支推送到远程。
+
+
+git branch -m new_name
+重命名当前分支。
+
+
+git branch -d branch_name
+删除本地分支。
+
+
+git push origin :branch_name
+删除远程分支。
+
+
+
+
+
+
+日志命令
+说明
+
+
+
+
+git log --oneline
+单行显示提交历史记录。
+
+
+git log -2
+显示最近N次提交的提交历史记录。
+
+
+git log -p -2
+用diff显示最近N次提交的提交历史记录。
+
+
+git diff
+在工作树中显示所有本地文件更改。
+
+
+git diff myfile
+显示对文件所做的更改。
+
+
+git blame myfile
+显示谁更改了文件的内容和时间。
+
+
+git remote show origin
+显示远程分支及其到本地的映射。
+
+
+
+
+
+
+清理命令
+说明
+
+
+
+
+git clean -f
+删除所有未跟踪的文件。
+
+
+git clean -df
+删除所有未跟踪的文件和目录。
+
+
+git checkout -- .
+撤消对所有文件的本地修改。
+
+
+git reset HEAD myfile
+取消暂存文件。
+
+
+
+
+
+
+标签命令
+说明
+
+
+
+
+git tag
+列出所有标签。
+
+
+git tag -a tag_name -m "tag message"
+创建一个新标签。
+
+
+git push --tags
+将所有标签推送到远程仓库。
+
+
+
+
+
+
+存放命令
+说明
+
+
+
+
+git stash save "stash name" && git stash
+将更改保存到存储中。
+
+
+git stash list
+列出所有藏匿处。
+
+
+git stash pop
+应用藏匿处。
+
+
+
diff --git a/zh-cn/blog/tool/git.json b/zh-cn/blog/tool/git.json
index 00e181d..e66e979 100644
--- a/zh-cn/blog/tool/git.json
+++ b/zh-cn/blog/tool/git.json
@@ -1,6 +1,6 @@
{
"filename": "git.md",
- "__html": "Git 常用命令速查手册 \n
\n初始化仓库 \ngit init\n
\n设置远程仓库地址后再做push \n''' s\ngit remote add origin https://gitee.com/useraddress/HelloGitee.git \n'''
\n将文件添加到仓库 \ngit add 文件名 # 将工作区的某个文件添加到暂存区\ngit add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件\ngit add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件\ngit add . # 将当前工作区的所有文件都加入暂存区\ngit add -i # 进入交互界面模式,按需添加文件到缓存区\n
\n将暂存区文件提交到本地仓库 \ngit commit -m "提交说明" # 将暂存区内容提交到本地仓库\ngit commit -a -m "提交说明" # 跳过缓存区操作,直接把工作区内容提交到本地仓库\n
\n查看仓库当前状态 \ngit status\n
\n比较文件异同 \ngit diff # 工作区与暂存区的差异\ngit diff 分支名 #工作区与某分支的差异,远程分支这样写:remotes/origin/分支名\ngit diff HEAD # 工作区与HEAD指针指向的内容差异\ngit diff 提交id 文件路径 # 工作区某文件当前版本与历史版本的差异\ngit diff --stage # 工作区文件与上次提交的差异(1.6 版本前用 --cached)\ngit diff 版本TAG # 查看从某个版本后都改动内容\ngit diff 分支A 分支B # 比较从分支A和分支B的差异(也支持比较两个TAG)\ngit diff 分支A...分支B # 比较两分支在分开后各自的改动\n\n# 另外:如果只想统计哪些文件被改动,多少行被改动,可以添加 --stat 参数\n
\n查看历史记录 \ngit log # 查看所有commit记录(SHA-A校验和,作者名称,邮箱,提交时间,提交说明)\ngit log -p -次数 # 查看最近多少次的提交记录\ngit log --stat # 简略显示每次提交的内容更改\ngit log --name-only # 仅显示已修改的文件清单\ngit log --name-status # 显示新增,修改,删除的文件清单\ngit log --oneline # 让提交记录以精简的一行输出\ngit log –graph –all --online # 图形展示分支的合并历史\ngit log --author=作者 # 查询作者的提交记录(和grep同时使用要加一个--all--match参数)\ngit log --grep=过滤信息 # 列出提交信息中包含过滤信息的提交记录\ngit log -S查询内容 # 和--grep类似,S和查询内容间没有空格\ngit log fileName # 查看某文件的修改记录,找背锅专用\n
\n代码回滚 \ngit reset HEAD^ # 恢复成上次提交的版本\ngit reset HEAD^^ # 恢复成上上次提交的版本,就是多个^,以此类推或用~次数\n\ngit reflog\n\ngit reset --hard 版本号\n\n--soft:只是改变HEAD指针指向,缓存区和工作区不变;\n--mixed:修改HEAD指针指向,暂存区内容丢失,工作区不变;\n--hard:修改HEAD指针指向,暂存区内容丢失,工作区恢复以前状态;\n
\n同步远程仓库 \ngit push -u origin master\n
\n删除版本库文件 \ngit rm 文件名\n
\n版本库里的版本替换工作区的版本 \ngit checkout -- test.txt\n
\n本地仓库内容推送到远程仓库 \ngit remote add origin git@github.com:帐号名/仓库名.git\n
\n将本地仓库内容推送到远程仓库 \n''' s\ngit add . #将当前目录所有文件添加到git暂存区\ngit commit -m "my first commit" #提交并备注提交信息\ngit push origin master #将本地提交推送到远程仓库\n'''
\n从远程仓库克隆项目到本地 \ngit clone git@github.com:git帐号名/仓库名.git\n
\n创建分支 \ngit checkout -b dev\n-b表示创建并切换分支\n上面一条命令相当于一面的二条:\ngit branch dev //创建分支\ngit checkout dev //切换分支\n
\n查看分支 \ngit branch\n
\n合并分支 \ngit merge dev\n//用于合并指定分支到当前分支\n\ngit merge --no-ff -m "merge with no-ff" dev\n//加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并\n
\n删除分支 \ngit branch -d dev\n
\n查看分支合并图 \ngit log --graph --pretty=oneline --abbrev-commit\n
\n查看远程库信息 \ngit remote\n// -v 显示更详细的信息\n
\ngit相关配置 \n# 安装完Git后第一件要做的事,设置用户信息(global可换成local在单独项目生效):\ngit config --global user.name "用户名" # 设置用户名\ngit config --global user.email "用户邮箱" #设置邮箱\ngit config --global user.name # 查看用户名是否配置成功\ngit config --global user.email # 查看邮箱是否配置\n\n# 其他查看配置相关\ngit config --global --list # 查看全局设置相关参数列表\ngit config --local --list # 查看本地设置相关参数列表\ngit config --system --list # 查看系统配置参数列表\ngit config --list # 查看所有Git的配置(全局+本地+系统)\ngit config --global color.ui true //显示git相关颜色\n
\n撤消某次提交 \ngit revert HEAD # 撤销最近的一个提交\ngit revert 版本号 # 撤销某次commit\n
\n拉取远程分支到本地仓库 \ngit checkout -b 本地分支 远程分支 # 会在本地新建分支,并自动切换到该分支\ngit fetch origin 远程分支:本地分支 # 会在本地新建分支,但不会自动切换,还需checkout\ngit branch --set-upstream 本地分支 远程分支 # 建立本地分支与远程分支的链接\n
\n标签命令 \ngit tag 标签 //打标签命令,默认为HEAD\ngit tag //显示所有标签\ngit tag 标签 �版本号 //给某个commit版本添加标签\ngit show 标签 //显示某个标签的详细信息\n
\n同步远程仓库更新 \ngit fetch origin master\n //从远程获取最新的到本地,首先从远程的origin的master主分支下载最新的版本到origin/master分支上,然后比较本地的master分支和origin/master分支的差别,最后进行合并。\n\ngit fetch比git pull更加安全\ngit pull origin master\n
\n推送时选择强制推送 \n强制推送需要执行下面的命令(默认不推荐该行为):
\ngit push origin master -f\n
\ngit 提示授权失败解决 \ngit config --system --unset credential.helper # 管理员权限执行命令\n
\ngit 授权永久有效 \ngit config --global credential.helper 'store'\n
\n程序员的那些迷之缩写 \n就像你可能不知道 现充 其实是 现实生活很充实的人生赢家 的缩写一样,我们经常看到 Github 上的码农们在 code review 时,把乱七八糟的缩写写得到处都是——娴熟的司机们都会使用缩写来达到提高逼格的效果——我们第一次看到时还是会出现一脸懵逼的状况,这里整理一下这些缩写都是什么含义,以后我们也可以欢快地装逼了。
\n\n\nPR: Pull Request. 拉取请求,给其他项目提交代码
\n \n\nLGTM: Looks Good To Me. 朕知道了 代码已经过 review,可以合并
\n \n\nSGTM: Sounds Good To Me. 和上面那句意思差不多,也是已经通过了 review 的意思
\n \n\nWIP: Work In Progress. 传说中提 PR 的最佳实践是,如果你有个改动很大的 PR,可以在写了一部分的情况下先提交,但是在标题里写上 WIP,以告诉项目维护者这个功能还未完成,方便维护者提前 review 部分提交的代码。
\n \n\nPTAL: Please Take A Look. 你来瞅瞅?用来提示别人来看一下
\n \n\nTBR: To Be Reviewed. 提示维护者进行 review
\n \n\nTL , DR: Too Long; Didn't Read. 太长懒得看。也有很多文档在做简略描述之前会写这么一句
\n \n\nTBD: To Be Done(or Defined/Discussed/Decided/Determined). 根据语境不同意义有所区别,但一般都是还没搞定的意思
\n \n\nPRD : Product Requirement Document. 产品需求文档
\n \n \n",
+ "__html": "Git 常用命令速查手册 \n
\n初始化仓库 \ngit init\n
\n设置远程仓库地址后再做push \n''' s\ngit remote add origin https://gitee.com/useraddress/HelloGitee.git \n'''
\n将文件添加到仓库 \ngit add 文件名 # 将工作区的某个文件添加到暂存区\ngit add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件\ngit add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件\ngit add . # 将当前工作区的所有文件都加入暂存区\ngit add -i # 进入交互界面模式,按需添加文件到缓存区\n
\n将暂存区文件提交到本地仓库 \ngit commit -m "提交说明" # 将暂存区内容提交到本地仓库\ngit commit -a -m "提交说明" # 跳过缓存区操作,直接把工作区内容提交到本地仓库\n
\n查看仓库当前状态 \ngit status\n
\n比较文件异同 \ngit diff # 工作区与暂存区的差异\ngit diff 分支名 #工作区与某分支的差异,远程分支这样写:remotes/origin/分支名\ngit diff HEAD # 工作区与HEAD指针指向的内容差异\ngit diff 提交id 文件路径 # 工作区某文件当前版本与历史版本的差异\ngit diff --stage # 工作区文件与上次提交的差异(1.6 版本前用 --cached)\ngit diff 版本TAG # 查看从某个版本后都改动内容\ngit diff 分支A 分支B # 比较从分支A和分支B的差异(也支持比较两个TAG)\ngit diff 分支A...分支B # 比较两分支在分开后各自的改动\n\n# 另外:如果只想统计哪些文件被改动,多少行被改动,可以添加 --stat 参数\n
\n查看历史记录 \ngit log # 查看所有commit记录(SHA-A校验和,作者名称,邮箱,提交时间,提交说明)\ngit log -p -次数 # 查看最近多少次的提交记录\ngit log --stat # 简略显示每次提交的内容更改\ngit log --name-only # 仅显示已修改的文件清单\ngit log --name-status # 显示新增,修改,删除的文件清单\ngit log --oneline # 让提交记录以精简的一行输出\ngit log –graph –all --online # 图形展示分支的合并历史\ngit log --author=作者 # 查询作者的提交记录(和grep同时使用要加一个--all--match参数)\ngit log --grep=过滤信息 # 列出提交信息中包含过滤信息的提交记录\ngit log -S查询内容 # 和--grep类似,S和查询内容间没有空格\ngit log fileName # 查看某文件的修改记录,找背锅专用\n
\n代码回滚 \ngit reset HEAD^ # 恢复成上次提交的版本\ngit reset HEAD^^ # 恢复成上上次提交的版本,就是多个^,以此类推或用~次数\n\ngit reflog\n\ngit reset --hard 版本号\n\n--soft:只是改变HEAD指针指向,缓存区和工作区不变;\n--mixed:修改HEAD指针指向,暂存区内容丢失,工作区不变;\n--hard:修改HEAD指针指向,暂存区内容丢失,工作区恢复以前状态;\n
\n同步远程仓库 \ngit push -u origin master\n
\n删除版本库文件 \ngit rm 文件名\n
\n版本库里的版本替换工作区的版本 \ngit checkout -- test.txt\n
\n本地仓库内容推送到远程仓库 \ngit remote add origin git@github.com:帐号名/仓库名.git\n
\n将本地仓库内容推送到远程仓库 \n''' s\ngit add . #将当前目录所有文件添加到git暂存区\ngit commit -m "my first commit" #提交并备注提交信息\ngit push origin master #将本地提交推送到远程仓库\n'''
\n从远程仓库克隆项目到本地 \ngit clone git@github.com:git帐号名/仓库名.git\n
\n创建分支 \ngit checkout -b dev\n-b表示创建并切换分支\n上面一条命令相当于一面的二条:\ngit branch dev //创建分支\ngit checkout dev //切换分支\n
\n查看分支 \ngit branch\n
\n合并分支 \ngit merge dev\n//用于合并指定分支到当前分支\n\ngit merge --no-ff -m "merge with no-ff" dev\n//加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并\n
\n删除分支 \ngit branch -d dev\n
\n查看分支合并图 \ngit log --graph --pretty=oneline --abbrev-commit\n
\n查看远程库信息 \ngit remote\n// -v 显示更详细的信息\n
\ngit相关配置 \n# 安装完Git后第一件要做的事,设置用户信息(global可换成local在单独项目生效):\ngit config --global user.name "用户名" # 设置用户名\ngit config --global user.email "用户邮箱" #设置邮箱\ngit config --global user.name # 查看用户名是否配置成功\ngit config --global user.email # 查看邮箱是否配置\n\n# 其他查看配置相关\ngit config --global --list # 查看全局设置相关参数列表\ngit config --local --list # 查看本地设置相关参数列表\ngit config --system --list # 查看系统配置参数列表\ngit config --list # 查看所有Git的配置(全局+本地+系统)\ngit config --global color.ui true //显示git相关颜色\n
\n撤消某次提交 \ngit revert HEAD # 撤销最近的一个提交\ngit revert 版本号 # 撤销某次commit\n
\n拉取远程分支到本地仓库 \ngit checkout -b 本地分支 远程分支 # 会在本地新建分支,并自动切换到该分支\ngit fetch origin 远程分支:本地分支 # 会在本地新建分支,但不会自动切换,还需checkout\ngit branch --set-upstream 本地分支 远程分支 # 建立本地分支与远程分支的链接\n
\n标签命令 \ngit tag 标签 //打标签命令,默认为HEAD\ngit tag //显示所有标签\ngit tag 标签 �版本号 //给某个commit版本添加标签\ngit show 标签 //显示某个标签的详细信息\n
\n同步远程仓库更新 \ngit fetch origin master\n //从远程获取最新的到本地,首先从远程的origin的master主分支下载最新的版本到origin/master分支上,然后比较本地的master分支和origin/master分支的差别,最后进行合并。\n\ngit fetch比git pull更加安全\ngit pull origin master\n
\n推送时选择强制推送 \n强制推送需要执行下面的命令(默认不推荐该行为):
\ngit push origin master -f\n
\ngit 提示授权失败解决 \ngit config --system --unset credential.helper # 管理员权限执行命令\n
\ngit 授权永久有效 \ngit config --global credential.helper 'store'\n
\n程序员的那些迷之缩写 \n就像你可能不知道 现充 其实是 现实生活很充实的人生赢家 的缩写一样,我们经常看到 Github 上的码农们在 code review 时,把乱七八糟的缩写写得到处都是——娴熟的司机们都会使用缩写来达到提高逼格的效果——我们第一次看到时还是会出现一脸懵逼的状况,这里整理一下这些缩写都是什么含义,以后我们也可以欢快地装逼了。
\n\n\nPR: Pull Request. 拉取请求,给其他项目提交代码
\n \n\nLGTM: Looks Good To Me. 朕知道了 代码已经过 review,可以合并
\n \n\nSGTM: Sounds Good To Me. 和上面那句意思差不多,也是已经通过了 review 的意思
\n \n\nWIP: Work In Progress. 传说中提 PR 的最佳实践是,如果你有个改动很大的 PR,可以在写了一部分的情况下先提交,但是在标题里写上 WIP,以告诉项目维护者这个功能还未完成,方便维护者提前 review 部分提交的代码。
\n \n\nPTAL: Please Take A Look. 你来瞅瞅?用来提示别人来看一下
\n \n\nTBR: To Be Reviewed. 提示维护者进行 review
\n \n\nTL , DR: Too Long; Didn't Read. 太长懒得看。也有很多文档在做简略描述之前会写这么一句
\n \n\nTBD: To Be Done(or Defined/Discussed/Decided/Determined). 根据语境不同意义有所区别,但一般都是还没搞定的意思
\n \n\nPRD : Product Requirement Document. 产品需求文档
\n \n \ngit常用命令归纳 \n\n\n\n分支命令 \n说明 \n \n \n\n\ngit branch \n列出所有本地分支机构。 \n \n\ngit branch -a \n列出远程和本地分支。 \n \n\ngit checkout -b branch_name \n创建一个本地分支并切换到该分支。 \n \n\ngit checkout branch_name \n切换到现有分支。 \n \n\ngit push origin branch_name \n将分支推送到远程。 \n \n\ngit branch -m new_name \n重命名当前分支。 \n \n\ngit branch -d branch_name \n删除本地分支。 \n \n\ngit push origin :branch_name \n删除远程分支。 \n \n \n
\n\n\n\n日志命令 \n说明 \n \n \n\n\ngit log --oneline \n单行显示提交历史记录。 \n \n\ngit log -2 \n显示最近N次提交的提交历史记录。 \n \n\ngit log -p -2 \n用diff显示最近N次提交的提交历史记录。 \n \n\ngit diff \n在工作树中显示所有本地文件更改。 \n \n\ngit diff myfile \n显示对文件所做的更改。 \n \n\ngit blame myfile \n显示谁更改了文件的内容和时间。 \n \n\ngit remote show origin \n显示远程分支及其到本地的映射。 \n \n \n
\n\n\n\n清理命令 \n说明 \n \n \n\n\ngit clean -f \n删除所有未跟踪的文件。 \n \n\ngit clean -df \n删除所有未跟踪的文件和目录。 \n \n\ngit checkout -- . \n撤消对所有文件的本地修改。 \n \n\ngit reset HEAD myfile \n取消暂存文件。 \n \n \n
\n\n\n\n标签命令 \n说明 \n \n \n\n\ngit tag \n列出所有标签。 \n \n\ngit tag -a tag_name -m "tag message" \n创建一个新标签。 \n \n\ngit push --tags \n将所有标签推送到远程仓库。 \n \n \n
\n\n\n\n存放命令 \n说明 \n \n \n\n\ngit stash save "stash name" && git stash \n将更改保存到存储中。 \n \n\ngit stash list \n列出所有藏匿处。 \n \n\ngit stash pop \n应用藏匿处。 \n \n \n
\n",
"link": "\\zh-cn\\blog\\tool\\git.html",
"meta": {}
}
\ No newline at end of file
diff --git a/zh-cn/blog/tool/minio.html b/zh-cn/blog/tool/minio.html
new file mode 100644
index 0000000..f222c0b
--- /dev/null
+++ b/zh-cn/blog/tool/minio.html
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+ minio
+
+
+
+
+ MinIO 搭建使用
+MinIO简介
+MinIO 是一款基于Go语言的高性能对象存储服务,在Github上已有19K+Star。它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。 本文将使用 MinIO 来自建一个对象存储服务用于存储图片。
+安装及部署
+
+MinIO的安装方式有很多,这里我们使用它在Docker环境下的安装方式。
+
+
+docker pull minio/minio
+
+
+在Docker容器中运行MinIO,这里我们将MiniIO的数据和配置文件夹挂在到宿主机上:
+
+docker run -p 9000:9000 --name minio \
+ --restart=always \
+ -v /etc/localtime:/etc/localtime \
+ -v /data/minio/data:/data \
+ -v /data/minio/config:/root/.minio \
+ -d minio/minio server /data
+
+
+
+上传文件及使用
+
+通过使用MinIO的网页端即可完成文件的上传下载功能,下面我们以图片上传下载为例来演示下该功能。
+
+
+
+
+存储桶创建完成后,通过上传按钮可以上传文件,这里我们上传一张图片:
+
+
+
+图片上传完成后,我们可以通过拷贝链接按钮来获取图片访问路径,但是这只是个临时的访问路径:
+
+
+
+要想获取一个永久的访问路径,需要修改存储桶的访问策略,我们可以点击存储桶右上角的编辑策略按钮来修改访问策略;
+
+
+
+这里有三种访问策略可以选择,一种只读、一种只写、一种可读可写,这里我们选择只读即可,但是需要注意的是,访问前缀需要设置为*.*,否则会无法访问;
+
+
+
+设置完成后,我们只需要通过拷贝链接中的前一串路径即可永久访问该文件;
+
+MinIO客户端的使用
+
+虽然MinIO的网页端管理已经很方便了,但是官网还是给我们提供了基于命令行的客户端MinIO Client(简称mc),下面我们来讲讲它的使用方法。
+
+常用命令
+
+下面我们先来熟悉下mc的命令,这些命令和Linux中的命令有很多相似之处。
+
+
+
+
+命令
+作用
+
+
+
+
+ls
+列出文件和文件夹
+
+
+mb
+创建一个存储桶或一个文件夹
+
+
+cat
+显示文件和对象内容
+
+
+pipe
+将一个STDIN重定向到一个对象或者文件或者STDOUT
+
+
+share
+生成用于共享的URL
+
+
+cp
+拷贝文件和对象
+
+
+mirror
+给存储桶和文件夹做镜像
+
+
+find
+基于参数查找文件
+
+
+diff
+对两个文件夹或者存储桶比较差异
+
+
+rm
+删除文件和对象
+
+
+events
+管理对象通知
+
+
+watch
+监听文件和对象的事件
+
+
+policy
+管理访问策略
+
+
+session
+为cp命令管理保存的会话
+
+
+config
+管理mc配置文件
+
+
+update
+检查软件更新
+
+
+version
+输出版本信息
+
+
+
+安装及配置
+
+由于MinIO服务端中并没有自带客户端,所以我们需要安装配置完客户端后才能使用,这里以Docker环境下的安装为例。
+
+
+下载MinIO Client 的Docker镜像:
+
+docker pull minio/mc
+
+
+docker run -it --entrypoint=/bin/sh minio/mc
+
+
+运行完成后我们需要进行配置,将我们自己的MinIO服务配置到客户端上去,配置的格式如下:
+
+mc config host add <ALIAS> <YOUR-S3-ENDPOINT> <YOUR-ACCESS-KEY> <YOUR-SECRET-KEY> <API-SIGNATURE>
+
+
+mc config host add minio http://localhost:9000 minioadmin minioadmin S3v4
+
+常用操作
+
+
+mc ls minio
+
+mc ls minio/blog
+
+
+
+mc mb minio/test
+
+
+mc share download minio/blog/avatar.png
+
+
+mc find minio/blog --name "*.png"
+
+
+
+mc policy set download minio/test /
+
+mc policy list minio/test /
+
+参考资料
+详细了解MinIO可以参考官方文档:https://docs.min.io/cn/minio-quickstart-guide.html
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/tool/minio.json b/zh-cn/blog/tool/minio.json
new file mode 100644
index 0000000..634dc0b
--- /dev/null
+++ b/zh-cn/blog/tool/minio.json
@@ -0,0 +1,6 @@
+{
+ "filename": "minio.md",
+ "__html": "MinIO 搭建使用 \nMinIO简介 \nMinIO 是一款基于Go语言的高性能对象存储服务,在Github上已有19K+Star。它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。 本文将使用 MinIO 来自建一个对象存储服务用于存储图片。
\n安装及部署 \n\nMinIO的安装方式有很多,这里我们使用它在Docker环境下的安装方式。
\n \n\ndocker pull minio/minio\n
\n\n在Docker容器中运行MinIO,这里我们将MiniIO的数据和配置文件夹挂在到宿主机上: \n \ndocker run -p 9000:9000 --name minio \\\n --restart=always \\\n -v /etc/localtime:/etc/localtime \\\n -v /data/minio/data:/data \\\n -v /data/minio/config:/root/.minio \\\n -d minio/minio server /data\n
\n\n
\n上传文件及使用 \n\n通过使用MinIO的网页端即可完成文件的上传下载功能,下面我们以图片上传下载为例来演示下该功能。
\n \n\n
\n\n存储桶创建完成后,通过上传按钮可以上传文件,这里我们上传一张图片: \n \n
\n\n图片上传完成后,我们可以通过拷贝链接按钮来获取图片访问路径,但是这只是个临时的访问路径: \n \n
\n\n要想获取一个永久的访问路径,需要修改存储桶的访问策略,我们可以点击存储桶右上角的编辑策略按钮来修改访问策略; \n \n
\n\n这里有三种访问策略可以选择,一种只读、一种只写、一种可读可写,这里我们选择只读即可,但是需要注意的是,访问前缀需要设置为*.*,否则会无法访问; \n \n
\n\n设置完成后,我们只需要通过拷贝链接中的前一串路径即可永久访问该文件; \n \nMinIO客户端的使用 \n\n虽然MinIO的网页端管理已经很方便了,但是官网还是给我们提供了基于命令行的客户端MinIO Client(简称mc),下面我们来讲讲它的使用方法。
\n \n常用命令 \n\n下面我们先来熟悉下mc的命令,这些命令和Linux中的命令有很多相似之处。
\n \n\n\n\n命令 \n作用 \n \n \n\n\nls \n列出文件和文件夹 \n \n\nmb \n创建一个存储桶或一个文件夹 \n \n\ncat \n显示文件和对象内容 \n \n\npipe \n将一个STDIN重定向到一个对象或者文件或者STDOUT \n \n\nshare \n生成用于共享的URL \n \n\ncp \n拷贝文件和对象 \n \n\nmirror \n给存储桶和文件夹做镜像 \n \n\nfind \n基于参数查找文件 \n \n\ndiff \n对两个文件夹或者存储桶比较差异 \n \n\nrm \n删除文件和对象 \n \n\nevents \n管理对象通知 \n \n\nwatch \n监听文件和对象的事件 \n \n\npolicy \n管理访问策略 \n \n\nsession \n为cp命令管理保存的会话 \n \n\nconfig \n管理mc配置文件 \n \n\nupdate \n检查软件更新 \n \n\nversion \n输出版本信息 \n \n \n
\n安装及配置 \n\n由于MinIO服务端中并没有自带客户端,所以我们需要安装配置完客户端后才能使用,这里以Docker环境下的安装为例。
\n \n\n下载MinIO Client 的Docker镜像: \n \ndocker pull minio/mc\n
\n\ndocker run -it --entrypoint=/bin/sh minio/mc\n
\n\n运行完成后我们需要进行配置,将我们自己的MinIO服务配置到客户端上去,配置的格式如下: \n \nmc config host add <ALIAS> <YOUR-S3-ENDPOINT> <YOUR-ACCESS-KEY> <YOUR-SECRET-KEY> <API-SIGNATURE>\n
\n\nmc config host add minio http://localhost:9000 minioadmin minioadmin S3v4\n
\n常用操作 \n\n\nmc ls minio\n\nmc ls minio/blog\n
\n
\n\nmc mb minio/test \n
\n\nmc share download minio/blog/avatar.png\n
\n\nmc find minio/blog --name \"*.png\" \n
\n\n\nmc policy set download minio/test /\n\nmc policy list minio/test /\n
\n参考资料 \n详细了解MinIO可以参考官方文档:https://docs.min.io/cn/minio-quickstart-guide.html
\n",
+ "link": "\\zh-cn\\blog\\tool\\minio.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/web/es6.html b/zh-cn/blog/web/es6.html
index 7926bad..4e4fbf2 100644
--- a/zh-cn/blog/web/es6.html
+++ b/zh-cn/blog/web/es6.html
@@ -381,7 +381,8 @@ Generators
总结
以上就是 ES6 最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%
-更多ES6语法点击这里
+更多ES6语法点击这里
+《JavaScript 语言入门教程》
diff --git a/zh-cn/blog/web/es6.json b/zh-cn/blog/web/es6.json
index c61f001..2b9b5d5 100644
--- a/zh-cn/blog/web/es6.json
+++ b/zh-cn/blog/web/es6.json
@@ -1,6 +1,6 @@
{
"filename": "es6.md",
- "__html": "JavaScript ES6 规范 \nES6 简介 \nECMAScript 6 简称 ES6,是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
\nECMAScript 和 JavaScript 的关系:前者是后者的语法规格,后者是前者的一种实现
\nBabel :将ES6代码转为ES5代码
\n新特性 \nlet、const \nlet 定义的变量不会被变量提升,const 定义的常量不能被修改,let 和 const 都是块级作用域
\nES6前,js 是没有块级作用域 {} 的概念的。(有函数作用域、全局作用域、eval作用域)
\nES6后,let 和 const 的出现,js 也有了块级作用域的概念,前端的知识是日新月异的~
\n变量提升:在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部;不在函数内即在全局作用域的最顶部。这样就会引起一些误解。例如:
\nconsole.log(a); // undefined\nvar a = 'hello';\n \n# 上面的代码相当于\nvar a;\nconsole.log(a);\na = 'hello';\n \n# 而 let 就不会被变量提升\nconsole.log(a); // a is not defined\nlet a = 'hello';\n
\nconst 定义的常量不能被修改
\nvar name = \"bai\" ;\nname = \"ming\" ;\nconsole .log(name); \nconst name = \"bai\" ;\nname = \"ming\" ; \nconsole .log(name);\n
\nimport、export \nimport导入模块、export导出模块
\n\nimport people from './example' \n \n\nimport * as example from \"./example.js\" \nconsole .log(example.name)\nconsole .log(example.getName())\n \n\nimport {name, age} from './example' \n \n \n\nexport default App\n \n\nexport class App extend Component {};\n
\nclass、extends、super \nES5中最令人头疼的的几个部分:原型、构造函数,继承,有了ES6我们不再烦恼!
\nES6引入了Class(类)这个概念。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nconsole .log(this .type + ' says ' + say);\n}\n}\n \nlet animal = new Animal();\nanimal.says('hello' ); \n \nclass Cat extends Animal {\nconstructor () {\nsuper ();\nthis .type = 'cat' ;\n}\n}\n \nlet cat = new Cat();\ncat.says('hello' ); \n
\n上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实力对象可以共享的。
\nClass之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
\nsuper关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
\nES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
\n\nvar Shape = function (id, x, y ) {\nthis .id = id,\nthis .move(x, y);\n};\nShape.prototype.move = function (x, y ) {\nthis .x = x;\nthis .y = y;\n};\n \nvar Rectangle = function id (ix, x, y, width, height ) {\nShape.call(this , id, x, y);\nthis .width = width;\nthis .height = height;\n};\nRectangle.prototype = Object .create(Shape.prototype);\nRectangle.prototype.constructor = Rectangle;\n \nvar Circle = function (id, x, y, radius ) {\nShape.call(this , id, x, y);\nthis .radius = radius;\n};\nCircle.prototype = Object .create(Shape.prototype);\nCircle.prototype.constructor = Circle;\n \n\nclass Shape {\nconstructor (id, x, y) {\nthis .id = id this .move(x, y);\n}\nmove(x, y) {\nthis .x = x this .y = y;\n}\n}\n \nclass Rectangle extends Shape {\nconstructor (id, x, y, width, height) {\nsuper (id, x, y) this .width = width this .height = height;\n}\n}\n \nclass Circle extends Shape {\nconstructor (id, x, y, radius) {\nsuper (id, x, y) this .radius = radius;\n}\n}\n
\narrow functions (箭头函数) \n函数的快捷写法。不需要 function 关键字来创建函数,省略 return 关键字,继承当前上下文的 this 关键字
\n\nvar arr1 = [1 , 2 , 3 ];\nvar newArr1 = arr1.map(function (x ) {\nreturn x + 1 ;\n});\n \n\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map((x ) => {\nx + 1 \n});\n
\n箭头函数小细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的;当你函数中有且仅有一个表达式的时候可以省略{}
\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map(x => x + 1 );\n
\nJavaScript语言的this对象一直是一个令人头痛的问题,运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n}\nvar animal = new Animal();\nanimal.says('hi' ); \n
\n解决办法:
\n\nsays(say) {\nvar self = this ;\nsetTimeout(function ( ) {\nconsole .log(self.type + ' says ' + say);\n}, 1000 );\n}\n \n\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}.bind(this ), 1000 );\n}\n \n\n\nsays(say) {\nsetTimeout(() => {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n
\ntemplate string (模板字符串) \n解决了 ES5 在字符串功能上的痛点。
\n第一个用途:字符串拼接。将表达式嵌入字符串中进行拼接,用 和${}
来界定。
\n\nvar name1 = \"bai\" ;\nconsole .log('hello' + name1);\n \n\nconst name2 = \"ming\" ;\nconsole .log(`hello${name2} ` );\n
\n第二个用途:在ES5时我们通过反斜杠来做多行字符串拼接。ES6反引号 `` 直接搞定。
\n\nvar msg = \"Hi \\\nman!\" ;\n \n\nconst template = `<div>\n<span>hello world</span>\n</div>` ;\n
\n另外:includes repeat
\n\nlet str = 'hahah' ;\nconsole .log(str.includes('y' )); \n \n\nlet s = 'he' ;\nconsole .log(s.repeat(3 )); \n
\ndestructuring (解构) \n简化数组和对象中信息的提取。
\nES6前,我们一个一个获取对象信息;
\nES6后,解构能让我们从对象或者数组里取出数据存为变量
\n\nvar people1 = {\nname : 'bai' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n};\n \nvar myName = people1.name;\nvar myAge = people1.age;\nvar myColor = people1.color[0 ];\nconsole .log(myName + '----' + myAge + '----' + myColor);\n \n\nlet people2 = {\nname : 'ming' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n}\n \nlet { name, age } = people2;\nlet [first, second] = people2.color;\nconsole .log(`${name} ----${age} ----${first} ` );\n
\ndefault 函数默认参数 \n\nfunction foo (num ) {\nnum = num || 200 ;\nreturn num;\n}\n \n\nfunction foo (num = 200 ) {\nreturn num;\n}\n
\nrest arguments (rest参数) \n解决了 es5 复杂的 arguments 问题
\nfunction foo (x, y, ...rest ) {\nreturn ((x + y) * rest.length);\n}\nfoo(1 , 2 , 'hello' , true , 7 ); \n
\nSpread Operator (展开运算符) \n第一个用途:组装数组
\nlet color = ['red' , 'yellow' ];\nlet colorful = [...color, 'green' , 'blue' ];\nconsole .log(colorful); \n
\n第二个用途:获取数组除了某几项的其他项
\nlet num = [1 , 3 , 5 , 7 , 9 ];\nlet [first, second, ...rest] = num;\nconsole .log(rest); \n
\n对象 \n对象初始化简写
\n\nfunction people (name, age ) {\nreturn {\nname : name,\nage : age\n};\n}\n \n\nfunction people (name, age ) {\nreturn {\nname,\nage\n};\n}\n
\n对象字面量简写(省略冒号与 function 关键字)
\n\nvar people1 = {\nname : 'bai' ,\ngetName : function ( ) {\nconsole .log(this .name);\n}\n};\n \n\nlet people2 = {\nname : 'bai' ,\ngetName () {\nconsole .log(this .name);\n}\n};\n
\n另外:Object.assign()
\nES6 对象提供了Object.assign()这个方法来实现浅复制。Object.assign()可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}
\nconst obj = Object .assign({}, objA, objB)\n \n\nthis .seller = Object .assign({}, this .seller, response.data)\n
\nPromise \n用同步的方式去写异步代码
\n\nfetch('/api/todos' )\n.then(res => res.json())\n.then(data => ({\ndata\n}))\n.catch(err => ({\nerr\n}));\n
\nGenerators \n生成器( generator)是能返回一个迭代器的函数。
\n生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
\n这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
\n迭代器:当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
\n\nfunction *createIterator ( ) {\nyield 1 ;\nyield 2 ;\nyield 3 ;\n}\n \n\nlet iterator = createIterator();\n \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \n
\n迭代器对异步编程作用很大,异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
\n生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
\n那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的:
\nfunction run (taskDef ) {\n\n\nlet task = taskDef();\n \n\nlet result = task.next();\n \n\nfunction step ( ) {\n\nif (!result.done) {\nresult = task.next();\nstep();\n}\n}\n \n\nstep();\n}\n
\n总结 \n以上就是 ES6 最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%
\n更多ES6语法点击这里
\n",
+ "__html": "JavaScript ES6 规范 \nES6 简介 \nECMAScript 6 简称 ES6,是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
\nECMAScript 和 JavaScript 的关系:前者是后者的语法规格,后者是前者的一种实现
\nBabel :将ES6代码转为ES5代码
\n新特性 \nlet、const \nlet 定义的变量不会被变量提升,const 定义的常量不能被修改,let 和 const 都是块级作用域
\nES6前,js 是没有块级作用域 {} 的概念的。(有函数作用域、全局作用域、eval作用域)
\nES6后,let 和 const 的出现,js 也有了块级作用域的概念,前端的知识是日新月异的~
\n变量提升:在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部;不在函数内即在全局作用域的最顶部。这样就会引起一些误解。例如:
\nconsole.log(a); // undefined\nvar a = 'hello';\n \n# 上面的代码相当于\nvar a;\nconsole.log(a);\na = 'hello';\n \n# 而 let 就不会被变量提升\nconsole.log(a); // a is not defined\nlet a = 'hello';\n
\nconst 定义的常量不能被修改
\nvar name = \"bai\" ;\nname = \"ming\" ;\nconsole .log(name); \nconst name = \"bai\" ;\nname = \"ming\" ; \nconsole .log(name);\n
\nimport、export \nimport导入模块、export导出模块
\n\nimport people from './example' \n \n\nimport * as example from \"./example.js\" \nconsole .log(example.name)\nconsole .log(example.getName())\n \n\nimport {name, age} from './example' \n \n \n\nexport default App\n \n\nexport class App extend Component {};\n
\nclass、extends、super \nES5中最令人头疼的的几个部分:原型、构造函数,继承,有了ES6我们不再烦恼!
\nES6引入了Class(类)这个概念。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nconsole .log(this .type + ' says ' + say);\n}\n}\n \nlet animal = new Animal();\nanimal.says('hello' ); \n \nclass Cat extends Animal {\nconstructor () {\nsuper ();\nthis .type = 'cat' ;\n}\n}\n \nlet cat = new Cat();\ncat.says('hello' ); \n
\n上面代码首先用class定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。简单地说,constructor内定义的方法和属性是实例对象自己的,而constructor外定义的方法和属性则是所有实力对象可以共享的。
\nClass之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。上面定义了一个Cat类,该类通过extends关键字,继承了Animal类的所有属性和方法。
\nsuper关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
\nES6的继承机制,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
\n\nvar Shape = function (id, x, y ) {\nthis .id = id,\nthis .move(x, y);\n};\nShape.prototype.move = function (x, y ) {\nthis .x = x;\nthis .y = y;\n};\n \nvar Rectangle = function id (ix, x, y, width, height ) {\nShape.call(this , id, x, y);\nthis .width = width;\nthis .height = height;\n};\nRectangle.prototype = Object .create(Shape.prototype);\nRectangle.prototype.constructor = Rectangle;\n \nvar Circle = function (id, x, y, radius ) {\nShape.call(this , id, x, y);\nthis .radius = radius;\n};\nCircle.prototype = Object .create(Shape.prototype);\nCircle.prototype.constructor = Circle;\n \n\nclass Shape {\nconstructor (id, x, y) {\nthis .id = id this .move(x, y);\n}\nmove(x, y) {\nthis .x = x this .y = y;\n}\n}\n \nclass Rectangle extends Shape {\nconstructor (id, x, y, width, height) {\nsuper (id, x, y) this .width = width this .height = height;\n}\n}\n \nclass Circle extends Shape {\nconstructor (id, x, y, radius) {\nsuper (id, x, y) this .radius = radius;\n}\n}\n
\narrow functions (箭头函数) \n函数的快捷写法。不需要 function 关键字来创建函数,省略 return 关键字,继承当前上下文的 this 关键字
\n\nvar arr1 = [1 , 2 , 3 ];\nvar newArr1 = arr1.map(function (x ) {\nreturn x + 1 ;\n});\n \n\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map((x ) => {\nx + 1 \n});\n
\n箭头函数小细节:当你的函数有且仅有一个参数的时候,是可以省略掉括号的;当你函数中有且仅有一个表达式的时候可以省略{}
\nlet arr2 = [1 , 2 , 3 ];\nlet newArr2 = arr2.map(x => x + 1 );\n
\nJavaScript语言的this对象一直是一个令人头痛的问题,运行上面的代码会报错,这是因为setTimeout中的this指向的是全局对象。
\nclass Animal {\nconstructor () {\nthis .type = 'animal' ;\n}\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n}\nvar animal = new Animal();\nanimal.says('hi' ); \n
\n解决办法:
\n\nsays(say) {\nvar self = this ;\nsetTimeout(function ( ) {\nconsole .log(self.type + ' says ' + say);\n}, 1000 );\n}\n \n\nsays(say) {\nsetTimeout(function ( ) {\nconsole .log(this .type + ' says ' + say);\n}.bind(this ), 1000 );\n}\n \n\n\nsays(say) {\nsetTimeout(() => {\nconsole .log(this .type + ' says ' + say);\n}, 1000 );\n}\n
\ntemplate string (模板字符串) \n解决了 ES5 在字符串功能上的痛点。
\n第一个用途:字符串拼接。将表达式嵌入字符串中进行拼接,用 和${}
来界定。
\n\nvar name1 = \"bai\" ;\nconsole .log('hello' + name1);\n \n\nconst name2 = \"ming\" ;\nconsole .log(`hello${name2} ` );\n
\n第二个用途:在ES5时我们通过反斜杠来做多行字符串拼接。ES6反引号 `` 直接搞定。
\n\nvar msg = \"Hi \\\nman!\" ;\n \n\nconst template = `<div>\n<span>hello world</span>\n</div>` ;\n
\n另外:includes repeat
\n\nlet str = 'hahah' ;\nconsole .log(str.includes('y' )); \n \n\nlet s = 'he' ;\nconsole .log(s.repeat(3 )); \n
\ndestructuring (解构) \n简化数组和对象中信息的提取。
\nES6前,我们一个一个获取对象信息;
\nES6后,解构能让我们从对象或者数组里取出数据存为变量
\n\nvar people1 = {\nname : 'bai' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n};\n \nvar myName = people1.name;\nvar myAge = people1.age;\nvar myColor = people1.color[0 ];\nconsole .log(myName + '----' + myAge + '----' + myColor);\n \n\nlet people2 = {\nname : 'ming' ,\nage : 20 ,\ncolor : ['red' , 'blue' ]\n}\n \nlet { name, age } = people2;\nlet [first, second] = people2.color;\nconsole .log(`${name} ----${age} ----${first} ` );\n
\ndefault 函数默认参数 \n\nfunction foo (num ) {\nnum = num || 200 ;\nreturn num;\n}\n \n\nfunction foo (num = 200 ) {\nreturn num;\n}\n
\nrest arguments (rest参数) \n解决了 es5 复杂的 arguments 问题
\nfunction foo (x, y, ...rest ) {\nreturn ((x + y) * rest.length);\n}\nfoo(1 , 2 , 'hello' , true , 7 ); \n
\nSpread Operator (展开运算符) \n第一个用途:组装数组
\nlet color = ['red' , 'yellow' ];\nlet colorful = [...color, 'green' , 'blue' ];\nconsole .log(colorful); \n
\n第二个用途:获取数组除了某几项的其他项
\nlet num = [1 , 3 , 5 , 7 , 9 ];\nlet [first, second, ...rest] = num;\nconsole .log(rest); \n
\n对象 \n对象初始化简写
\n\nfunction people (name, age ) {\nreturn {\nname : name,\nage : age\n};\n}\n \n\nfunction people (name, age ) {\nreturn {\nname,\nage\n};\n}\n
\n对象字面量简写(省略冒号与 function 关键字)
\n\nvar people1 = {\nname : 'bai' ,\ngetName : function ( ) {\nconsole .log(this .name);\n}\n};\n \n\nlet people2 = {\nname : 'bai' ,\ngetName () {\nconsole .log(this .name);\n}\n};\n
\n另外:Object.assign()
\nES6 对象提供了Object.assign()这个方法来实现浅复制。Object.assign()可以把任意多个源对象自身可枚举的属性拷贝给目标对象,然后返回目标对象。第一参数即为目标对象。在实际项目中,我们为了不改变源对象。一般会把目标对象传为{}
\nconst obj = Object .assign({}, objA, objB)\n \n\nthis .seller = Object .assign({}, this .seller, response.data)\n
\nPromise \n用同步的方式去写异步代码
\n\nfetch('/api/todos' )\n.then(res => res.json())\n.then(data => ({\ndata\n}))\n.catch(err => ({\nerr\n}));\n
\nGenerators \n生成器( generator)是能返回一个迭代器的函数。
\n生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,有意思的是函数会在每个yield后暂停。
\n这里生活中有一个比较形象的例子。咱们到银行办理业务时候都得向大厅的机器取一张排队号。你拿到你的排队号,机器并不会自动为你再出下一张票。也就是说取票机“暂停”住了,直到下一个人再次唤起才会继续吐票。
\n迭代器:当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,它返回的对象具有两个属性:done和value。value是你获得的值,done用来表明你的generator是否已经停止提供值。继续用刚刚取票的例子,每张排队号就是这里的value,打印票的纸是否用完就这是这里的done。
\n\nfunction *createIterator ( ) {\nyield 1 ;\nyield 2 ;\nyield 3 ;\n}\n \n\nlet iterator = createIterator();\n \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \nconsole .log(iterator.next().value); \n
\n迭代器对异步编程作用很大,异步调用对于我们来说是很困难的事,我们的函数并不会等待异步调用完再执行,你可能会想到用回调函数,(当然还有其他方案比如Promise比如Async/await)。
\n生成器可以让我们的代码进行等待。就不用嵌套的回调函数。使用generator可以确保当异步调用在我们的generator函数运行一下行代码之前完成时暂停函数的执行。
\n那么问题来了,咱们也不能手动一直调用next()方法,你需要一个能够调用生成器并启动迭代器的方法。就像这样子的:
\nfunction run (taskDef ) {\n\n\nlet task = taskDef();\n \n\nlet result = task.next();\n \n\nfunction step ( ) {\n\nif (!result.done) {\nresult = task.next();\nstep();\n}\n}\n \n\nstep();\n}\n
\n总结 \n以上就是 ES6 最常用的一些语法,可以说这20%的语法,在ES6的日常使用中占了80%
\n更多ES6语法点击这里 \n《JavaScript 语言入门教程》
\n",
"link": "\\zh-cn\\blog\\web\\es6.html",
"meta": {}
}
\ No newline at end of file
diff --git a/zh-cn/blog/web/javascript.html b/zh-cn/blog/web/javascript.html
new file mode 100644
index 0000000..03f029a
--- /dev/null
+++ b/zh-cn/blog/web/javascript.html
@@ -0,0 +1,685 @@
+
+
+
+
+
+
+
+
+
+ javascript
+
+
+
+
+ JavaScript 基础
+
+一、概念简介
+二、基本类型
+ 2.1 数值类型
+ 2.2 字符类型
+ 2.3 基本类型检测
+三、引用类型
+ 3.1 Object 类型
+ 3.2 Array 类型
+ 3.3 Date 类型
+ 3.4 Funcation 类型
+ 3.5 引用类型检测
+四、内置对象
+ 4.1 Global 对象
+ 4.2 window 对象
+五、作用域与闭包
+ 5.1 作用域
+ 5.2 作用域链
+ 5.3 闭包
+六、对象设计
+ 6.1 数据属性
+ 6.2 访问器属性
+ 6.3 读取属性
+ 6.4 创建对象
+
+一、概念简介
+JavaScript 是一种专为与网页交互而设计的脚本语言,由以下三个部分组成:
+
+ECMAScript :由 ECMA-262 定义,提供核心语言功能;
+文档对象模型 (DOM) :提供访问和操作网页内容的方法和接口;
+浏览器对象模型 (BOM) :提供与浏览器交互的方法和接口。
+
+ECMAScript 提供了语言的核心功能,它定义了以下七种数据类型:
+
+六种基本数据类型 :Undefined
,Null
,Boolean
,Number
,String
,Symbol
( ES 6新增 );
+一种引用数据类型 :统称为 Object 类型;具体又细分为 Object
,Array
,Date
,RegExp
,Function
等类型。另外和 Java 语言类似,对于布尔,数值,字符串等基本类型,分别存在其对应的包装类型 Boolean,Number,String,但通常我们并不会使用到这些包装类型,只需要使用其基本类型即可。
+
+二、基本类型
+2.1 数值类型
+1. 进制数值
+ECMAScript 中的 Number 支持以下三种常用进制:
+
+十进制 :正常数值就是十进制;
+八进制 :八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7);
+十六进制 :十六进制字面值的前两位必须是 0x,后跟任意的十六进制数字(0~9 及 A~F)。
+
+console .log(56 );
+console .log(070 );
+console .log(0x38 );
+
+2. 浮点数值
+ECMAScript 的数值类型同样支持浮点数,但是由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会尽量将浮点数值转换为整数值存储:
+var a = 10.0 ;
+console .log(a);
+
+和其他语言类似,浮点数中的数值也是不精准的,示例如下:
+var a = 0.1 ; var b = 0.2 ;
+
+a + b ;
+a+b === 0.3 ;
+
+如果想要对浮点数进行精确计算,可以使用 decimal.js , math.js 等第三方库。
+3. 科学计数法
+ECMAScript 支持使用科学计数法来表达数值:
+8e-2
+8e2
+
+4. parseInt() \ parseFloat()
+parseInt 可以用于解析字符串并返回整数,parseFloat 用于解析字符串并返回浮点数:
+parseInt ("56" );
+parseInt ("0x38" , 16 );
+parseInt ("56.6" );
+
+parseFloat ("12.2" );
+
+parseInt ("blue" );
+
+5. toFixed()
+toFixed 用于保留指定位数的小数,但需要注意的是其四舍五入的行为是不确定的:
+1.35 .toFixed(1 )
+1.335 .toFixed(2 )
+1.3335 .toFixed(3 )
+1.33335 .toFixed(4 )
+1.333335 .toFixed(5 )
+1.3333335 .toFixed(6 )
+
+想要解决这个问题,需要重写 toFixed 方法并通过判断最后一位是否大于或等于5来决定是否需要进位,具体代码如下:
+
+Number .prototype.toFixed = function (len ) {
+ if (len>20 || len<0 ){
+ throw new RangeError ('toFixed() digits argument must be between 0 and 20' );
+ }
+
+ var number = Number (this );
+ if (isNaN (number) || number >= Math .pow(10 , 21 )) {
+ return number.toString();
+ }
+ if (typeof (len) == 'undefined' || len == 0 ) {
+ return (Math .round(number)).toString();
+ }
+ var result = number.toString(),
+ numberArr = result.split('.' );
+
+ if (numberArr.length<2 ){
+
+ return padNum(result);
+ }
+ var intNum = numberArr[0 ],
+ deciNum = numberArr[1 ],
+ lastNum = deciNum.substr(len, 1 );
+
+ if (deciNum.length == len){
+
+ return result;
+ }
+ if (deciNum.length < len){
+
+ return padNum(result)
+ }
+
+ result = intNum + '.' + deciNum.substr(0 , len);
+ if (parseInt (lastNum, 10 )>=5 ){
+
+ var times = Math .pow(10 , len);
+ var changedInt = Number (result.replace('.' ,'' ));
+ changedInt++;
+ changedInt /= times;
+ result = padNum(changedInt+'' );
+ }
+ return result;
+
+ function padNum (num ) {
+ var dotPos = num.indexOf('.' );
+ if (dotPos === -1 ){
+
+ num += '.' ;
+ for (var i = 0 ;i<len;i++){
+ num += '0' ;
+ }
+ return num;
+ } else {
+
+ var need = len - (num.length - dotPos - 1 );
+ for (var j = 0 ;j<need;j++){
+ num += '0' ;
+ }
+ return num;
+ }
+ }
+}
+
+
+参考自:js中小数四舍五入和浮点数的研究
+
+2.2 字符类型
+1. 字符串表示
+ECMAScript 支持使用双引号 "
或单引号 '
来表示字符串,并且 ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,示例如下:
+var lang = "Java" ;
+
+lang = lang + "Script" ;
+
+2. 转换为字符串
+要把一个值转换为一个字符串有两种方式:
+
+使用对象方法 toString() :大多数对象都具有这个方法,但需要注意的是 null 和 undefined 没有;
+使用转型函数 String() :使用该转型函数时,如果传入的值有 toString() 方法,则调用该方法并返回相应的结果;如果传入的值是 null,则返回 "null" ;如果传入值是 undefined,则返回 "undefined" 。 示例如下:
+
+var a = null ;
+a.toString()
+String (a)
+
+3. 常用的字符串操作
+
+concat() :用于拼接一个或多个字符串;
+slice() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置;
+substring() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置;
+substr() :用于截取字符串,接收两个参数,分别代表截取的开始位置和截取的长度;
+indexOf() \ lastIndexOf() :均接收两个参数,分别代表待查找的字符串和查找的开始位置;
+trim() :用于去除字符串前后的空格。
+
+slice,substring,substr 等方法在传入正数参数时,其行为比较好预期,但传递参数是负数时,则具体的行为表现如下:
+
+slice() :会将传入的负值与字符串的长度相加;
+substring() :方法会把所有负值参数都转换为 0 ;
+substr() :会将第一个负值参数加上字符串的长度,如果传递了第二个参数且为负数时候,会将其转换为 0 。
+
+var stringValue = "hello world" ;
+
+
+alert(stringValue.slice(3 ));
+alert(stringValue.substring(3 ));
+alert(stringValue.substr(3 ));
+
+
+alert(stringValue.slice(3 , 7 ));
+alert(stringValue.substring(3 ,7 ));
+alert(stringValue.substr(3 , 7 ));
+
+
+alert(stringValue.slice(-3 ));
+alert(stringValue.substring(-3 ));
+alert(stringValue.substr(-3 ));
+
+
+alert(stringValue.slice(3 , -4 ));
+alert(stringValue.substring(3 , -4 ));
+alert(stringValue.substr(3 , -4 ));
+
+2.3 基本类型检测
+JavaScript 是一种弱类型的语言,在声明变量时候可以不必指明其具体类型,而是由程序进行推断。如果想要知道变量具体属于哪一个基础类型,可以使用 typeof 关键字,它的返回情况如下:
+
+undefined :如果对应的值未定义;
+boolean :如果对应的值是布尔值;
+string :如果对应的值是字符串;
+number :如果对应的值是数值;
+object :如果对应的值是对象或 null;
+function :如果对应的值是函数则返回 function。 函数在本质上也是对象,但是由于其一等公民的特殊地位,所以将其和其他普通对象进行区分是很有必要的,因此 typeof 对其检测时会返回 function ,而不是 object 。
+
+三、引用类型
+3.1 Object 类型
+创建 Object 实例有以下两种方式:
+
+使用 new 操作符后跟着 Object 构造函数;
+使用对象字面量的方式。
+
+
+var user = new Object ();
+user.name = "heibaiying" ;
+user.age = 30 ;
+
+
+var user = {
+ name : "heibaiying" ,
+ age : 30
+};
+
+3.2 Array 类型
+创建数组也有两种方式,基于构造函数的方式和基于对象字面量的方式:
+
+var colors = new Array ();
+var colors = new Array (20 );
+var colors = new Array ("red" , "blue" , "green" );
+
+
+var names = [];
+var colors = ["red" , "blue" , "green" ];
+
+数组的长度保存在其 length 属性中,和其他语言中的 length 属性不同,这个值是不是只读的,可以用其进行数组的截断操作或添加新的数据项,示例如下:
+var colors = ["red" , "blue" , "green" ];
+
+colors.length = 2 ;
+colors[colors.length] = "green" ;
+colors[10 ] = "black" ;
+
+数组的其他常用方法如下:
+1. 检测数组
+colors instanceof Array
+Array .isArray(colors)
+
+2. 转换方法
+var colors = ["red" , "blue" , "green" ];
+
+colors.valueOf();
+colors;
+colors.toString();
+colors.join("|" );
+
+3. 栈方法
+ECMAScript 的数组提供了类似栈的特性,能够实现后进先出:
+var colors = ["red" , "blue" , "green" ];
+
+colors.push("black" );
+colors.pop()
+colors
+
+4. 队列方法
+ECMAScript 的数组提供了类似栈的特性,能够实现先进先出:
+colors.push("black" ,"yellow" );
+colors.shift()
+colors
+
+5. 重排序方法
+var values = [1 , 2 , 3 , 4 , 5 ];
+values.reverse();
+values
+
+
+function compare (value1, value2 ) {
+ if (value1 < value2) {
+ return -1 ;
+ } else if (value1 > value2) {
+ return 1 ;
+ } else {
+ return 0 ;
+ }
+}
+values.sort(compare)
+values
+
+6. 操作方法
+concat() 用于拼接并返回新的数组:
+var colors = ["red" , "green" , "blue" ];
+var colors2 = colors.concat("yellow" , ["black" , "brown" ]);
+
+colors
+colors2
+
+slice() 用于截取数组并返回新的数组,它接收两个参数,分别代表截取的开始位置和结束位置,它是一个前开后闭的区间:
+var colors = ["red" , "green" , "blue" , "yellow" , "purple" ];
+
+var colors2 = colors.slice(1 );
+var colors3 = colors.slice(0 ,2 );
+
+splice() 用于删除并在删除位置新增数据项,它接收任意个参数,其中第一个参数为删除的开始位置,第二个参数为删除多少个数据项,之后可以接任意个参数,用于表示待插入的数据项:
+var colors = ["red" , "green" , "blue" , "yellow" ];
+
+colors.splice(1 ,2 )
+colors
+
+colors.splice(1 ,0 ,"black" ,"green" )
+colors
+
+7. 位置方法
+indexOf() 和 lastIndexOf() 用于查找指定元素的 Index ,它们都接收两个参数:待查找项和查找的起点位置:
+var colors = ["red", "green", "blue", "yellow", "green", "blue"];
+
+colors.indexOf("green"); // 1
+colors.indexOf("green", 3); // 4
+colors.lastIndexOf("green"); // 4
+colors.lastIndexOf("green", 3); // 1
+
+8. 迭代方法
+ECMAScript 5 提供了五个迭代方法:
+
+every() :判断数组中的每个元素是否满足指定条件,如果全部满足则返回 true,否则返回 flase;
+some() :判断数组中的每个元素是否满足指定条件,只要有一个满足则返回 true,否则返回 flase;
+filter() :过滤并返回符合条件的元素组成的数组。
+forEach() :对数组中的每一项运行给定函数。
+map() :对数组中的每一项运行给定函数,并返回每次函数调用结果所组成的数组。
+
+var numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ];
+
+numbers.every(function (value, index, array ) {
+ return value > 3 ;
+});
+
+
+numbers.some(function (value, index, array ) {
+ return value > 3 ;
+});
+
+
+numbers.filter(function (value, index, array ) {
+ return value > 3 ;
+});
+
+
+numbers.forEach(function (value, index, array ) {
+ console .log(value);
+});
+
+numbers.map(function (value, index, array ) {
+ return value * 10 ;
+});
+
+
+9. 归并方法
+ECMAScript 5 提供了两个归并数组的方法: reduce() 和 reduceRight() 。 它们都接收四个参数:前一个值、当前值、当前项的索引 和 数组本身,使用示例如下:
+var values = [1 , 2 , 3 , 4 , 5 ];
+var sum01 = values.reduce(function (prev, cur, index, array ) {
+ return prev + cur;
+});
+
+var sum02 = values.reduceRight(function (prev, cur, index, array ) {
+ return prev + cur;
+});
+
+3.3 Date 类型
+创建一个日期对象,可以使用 new 操作符和 Date 构造函数:
+var now = new Date();
+now.toLocaleString()
+
+var date = new Date(2018 , 7 , 8 , 8 , 30 , 20 );
+date.toLocaleString();
+
+如果你只想知道当前时间的毫秒数,可以直接使用 Date 对象的静态方法:
+Date .now()
+1568426130593
+
+1. 格式转换
+
+toLocaleString() :按照浏览器所在时区返回相应的日期格式;
+toString() :返回日期时间数据和的时区数据;
+valueOf() :返回日期的时间戳格式。
+
+var date = new Date (2018 , 7 , 8 , 8 , 30 , 20 );
+
+console .log(date.toLocaleString());
+console .log(date.toString());
+console .log(date.valueOf());
+
+由于 valueOf() 返回的是日期的时间戳格式,所以对于 date 对象,可以直接使用比较运算符来比较其大小:
+var date01 = new Date (2018 , 7 , 8 , 8 , 30 , 20 );
+var date02 = new Date (2016 , 7 , 8 , 8 , 30 , 20 );
+
+console .log(date01 > date02);
+console .log(date01 < date02);
+
+2. 常用方法
+
+getTime() \ setTime(毫秒) :返回和设置整个日期的所代表的毫秒数;与 valueOf() 方法返回的值相同;
+getFullYear() \ setFullYear(年) :返回和设置4位数的年份;
+getMonth() \ setMonth(月) :返回和设置月份,其中 0 表示一月, 11 表示十二月;
+getDate() \ setDate(日) :返回和设置月份中的天数(1到31);
+getDay() :返回和设置星期几 ( 其中0表示星期日, 6表示星期六);
+getHours() \ setHours(时) :返回和设置小时数(0到23);
+getMinutes() \ setMinutes(分) :返回和设置日期中的分钟数(0到59);
+getSeconds() \ setSeconds(秒) :返回和设置日期中的秒数(0到59);
+getMilliseconds() \ setMilliseconds(毫秒) :返回和设置日期中的毫秒数。
+
+3.4 Funcation 类型
+1. 函数参数
+ECMAScript 使用 function 关键字来声明函数,但和其他语言不同的是,ECMAScript 中函数对于参数的限制是非常宽松的,例如你在定义函数时定义了两个参数,但在调用时可以只传递一个参数、也可以传三个参数,甚至不传递,示例如下:
+function test (first, second) {
+ console.log("first:" + first + ",second:" + second);
+}
+test(1 )
+test(1 ,2 )
+test(1 ,2 ,3 )
+
+之所以能实现这样的效果,是因为 ECMAScript 在函数内部使用了一个数组 arguments 来维护所有参数,函数接收到的始终都是这个数组,而在实际使用时指向的也是这个数组中的具体元素,所以以上的函数等价于下面的函数:
+function test (first, second ) {
+ console .log("first:" + arguments [0 ] + ",second:" + arguments [1 ]);
+}
+
+2. 改变函数作用域
+在 ECMAScript 5 中,每个函数都包含两个非继承而来的方法:apply() 和 call() ,它们都用于在特定的作用域中调用函数。简单来说,可以用这两个方法来改变函数的实际调用对象,从而改变 this 的值,因为 this 总是指向当前函数的实际调用对象:
+window .color = "red" ;
+var o = { color : "blue" };
+function sayColor ( ) {
+ console .log(this .color);
+}
+
+sayColor();
+sayColor.call(this );
+sayColor.call(window );
+sayColor.call(o);
+
+apply() 和 call() 的第一个参数都是指代函数的调用对象,它们的区别主要在于第二个参数:apply() 支持使用数组或 arguments 给调用函数传值,而 call() 给调用函数传值时,必须逐个列举:
+function sum (num1, num2 ) {
+ return num1 + num2;
+}
+
+function callSum1 (num1, num2 ) {
+ return sum.apply(this , arguments );
+}
+
+function callSum2 (num1, num2 ) {
+ return sum.apply(this , [num1, num2]);
+}
+
+function callSum3 (num1, num2 ) {
+ return sum.call(this , num1, num2);
+}
+
+callSum1(10 , 10 );
+callSum2(10 , 10 );
+callSum3(10 , 10 );
+
+3. 绑定函数作用域
+如果想要将函数绑定在某个特定的作用域上,可以使用 bind() 函数:
+window .color = "red" ;
+var o = { color : "blue" };
+function sayColor ( ) {
+ console .log(this .color);
+}
+
+var objectSayColor = sayColor.bind(o);
+objectSayColor();
+
+3.5 引用类型检测
+想要检测某个对象是否属于某个引用类型,可以使用 instanceof 关键字:
+var date = new Date();
+date instanceof Date // true
+
+四、内置对象
+4.1 Global 对象
+ECMAScript 中内置了一个全局对象 Global ,任何不属于任何其他对象的属性和方法,最终都是它的属性和方法。 ES 通过该内置对象,提供了一些可以直接调用的全局方法,常用的如下:
+
+isNaN() :用于确定一个值是否为 NaN;
+isFinite() :用于判断被传入的参数值是否为一个有限数值;
+parseInt() \ parseFloat() :解析并返回一个整数 \ 浮点数;
+encodeURI() :对 URI 进行编码,但不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井号;
+encodeURIComponent() :对 URI 进行编码,会对任何非标准字符进行编码。
+
+4.2 window 对象
+ECMAScript 并没有提供任何直接访问 Global 对象的方法,但是浏览器却基于 Global 扩展实现了 window 对象,可以直接在浏览器环境下使用:
+window .isFinite(12 )
+a = 12 ;
+window .a
+
+五、作用域与闭包
+5.1 作用域
+在 ECMAScript 6 之前,只存在两种作用域,即:全局作用域 和 函数作用域,不存在块级作用域。这意味着在除了函数外的任何代码块中使用 var 关键字声明的变量都会被提升为全局变量,示例如下:
+function test ( ) {
+ var age =12 ;
+}
+age
+
+if (true ) {
+ var name = "heibaiying" ;
+}
+name
+
+这种情况同样适用与 for 循环代码块:
+for (var i = 0 ; i < 10 ; i++) {}
+console .log(i);
+
+5.2 作用域链
+由于函数作用域的存在,函数内的变量不能被外部访问,但是函数内的变量可以被其内部的函数访问,并且函数也可以访问其父级作用域上的变量,从而形成一条从其自身作用域到全局作用域的链条,示例如下:
+var global = "global" ;
+var outer = "outer global" ;
+
+(function outer ( ) {
+ var outer = "outer" ;
+
+ function inner ( ) {
+ console .log(global, outer);
+ }
+
+ inner()
+})();
+
+
+
+5.3 闭包
+由于函数作用域的存在,函数内的变量不能被外部访问,这可以保证变量的私有性。但如果你想允许外部对内部变量进行特定操作,可以通过闭包来实现。闭包是指有权访问另一个函数作用域中的变量的函数。示例如下:
+var contain = function () {
+
+ var arr = [];
+
+ return {
+ push: function () {
+ arr.push(...arguments);
+ },
+
+ get: function () {
+ return arr;
+ }
+ }
+};
+
+var ctn = contain();
+ctn.push(1 , 2 , 3 , 4 );
+ctn.get();
+
+六、对象设计
+ECMAScript 中的对象都有两种基本属性:数据属性和访问器属性。
+6.1 数据属性
+数据属性有以下 4 个描述其行为的特性:
+
+Enumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。
+Writable :表示能否修改属性的值;对于直接在对象上定义的属性, 该值默认为 true。
+Value :对应属性的数据值。默认值为 undefined。
+Configurable :表示能否对属性进行删除,修改等配置操作,对于直接在对象上定义的属性, 该值默认为 true。需要注意的是一旦将该属性的值设置为 false,就不能再将其设置为 true 。即一旦设置为不可配置,就不能再修改为可配置。因为你已经修改为不可配置,此时任何配置操作都无效了,自然修改 Configurable 属性的操作也无效。
+
+var person = {age : 12 };
+Object .defineProperty(person, "name" , {
+ Enumerable : false ,
+ writable : false ,
+ value : "heibai"
+});
+
+console .log(person.name);
+person.name = "ying" ;
+console .log(person.name);
+
+for (var key in person) {
+ console .log("key:" + key + ",value:" + person[key])
+}
+
+6.2 访问器属性
+访问器属性也有以下 4 个描述其行为的特性:
+
+Configurable :表示能否对属性进行删除,修改等配置操作;对于直接在对象上定义的属性, 该值默认为 true。
+Enumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。
+Get :在读取属性时调用的函数。默认值为 undefined。
+Set :在写入属性时调用的函数。默认值为 undefined。
+
+var student = {
+ _age : null ,
+ birthday : new Date (2012 ,7 ,2 )
+};
+Object .defineProperty(student, "age" , {
+
+ get : function ( ) {
+ if (this ._age == null ) {
+
+ return new Date ().getFullYear() - this .birthday.getFullYear()
+ }else {
+ return this ._age;
+ }
+ },
+ set : function (newValue ) {
+ if (newValue < 0 ) {
+ console .log("年龄不能设置为负数" );
+ } else {
+ this ._age = newValue;
+ }
+ }
+});
+
+student.age = -1 ;
+console .log(student.age);
+student.age = 12 ;
+console .log(student.age);
+
+6.3 读取属性
+想要获取一个对象的数据属性和访问器属性,可以使用 Object.getOwnPropertyDescriptor() 方法,类似于其他语言中的反射机制。这个方法接收两个参数:属性所在的对象和要读取属性名称。沿用上面的例子,示例如下:
+var descriptor = Object .getOwnPropertyDescriptor(student, "age" );
+console .log(descriptor.get);
+console .log(descriptor.enumerable);
+
+6.4 创建对象
+在 ECMAScript 中,对象就是一种特殊的函数,想要声明一个对象,可以结合使用构造器模式和原型模式:基本属性可以通过构造器传入;但方法声明需要定义在原型属性上,如果直接定义在构造器上,每个对象实例都会创建该方法函数,即每个对象实例调用的都是自己重复声明的方法函数,示例如下:
+function Person (name, age, job ) {
+ this .name = name;
+ this .age = age;
+ this .job = job;
+ this .friends = ["hei" , "bai" ];
+
+ this .sayAge = function ( ) {
+ console .log(this .age)
+ }
+}
+
+Person.prototype = {
+ constructor : Person,
+
+ sayName: function ( ) {
+ alert(this .name);
+ }
+}
+
+
+var person1 = new Person("user01" , 29 , "Software Engineer" );
+var person2 = new Person("user02" , 27 , "Doctor" );
+
+person1.friends.push("ying" );
+console .log(person1.friends);
+console .log(person2.friends);
+console .log(person1 instanceof Person);
+console .log(person1.constructor === Person);
+console .log(person1.sayName === person2.sayName);
+console .log(person1.sayAge===person2.sayAge);
+
+参考资料
+
+尼古拉斯·泽卡斯 . JavaScript高级程序设计(第3版). 人民邮电出版社 . 2012-3-29
+JS中小数四舍五入和浮点数的研究
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/web/javascript.json b/zh-cn/blog/web/javascript.json
new file mode 100644
index 0000000..6a55cab
--- /dev/null
+++ b/zh-cn/blog/web/javascript.json
@@ -0,0 +1,6 @@
+{
+ "filename": "javascript.md",
+ "__html": "JavaScript 基础 \n\n一、概念简介 \n二、基本类型 \n 2.1 数值类型 \n 2.2 字符类型 \n 2.3 基本类型检测 \n三、引用类型 \n 3.1 Object 类型 \n 3.2 Array 类型 \n 3.3 Date 类型 \n 3.4 Funcation 类型 \n 3.5 引用类型检测 \n四、内置对象 \n 4.1 Global 对象 \n 4.2 window 对象 \n五、作用域与闭包 \n 5.1 作用域 \n 5.2 作用域链 \n 5.3 闭包 \n六、对象设计 \n 6.1 数据属性 \n 6.2 访问器属性 \n 6.3 读取属性 \n 6.4 创建对象 \n \n一、概念简介 \nJavaScript 是一种专为与网页交互而设计的脚本语言,由以下三个部分组成:
\n\nECMAScript :由 ECMA-262 定义,提供核心语言功能; \n文档对象模型 (DOM) :提供访问和操作网页内容的方法和接口; \n浏览器对象模型 (BOM) :提供与浏览器交互的方法和接口。 \n \nECMAScript 提供了语言的核心功能,它定义了以下七种数据类型:
\n\n六种基本数据类型 :Undefined
,Null
,Boolean
,Number
,String
,Symbol
( ES 6新增 ); \n一种引用数据类型 :统称为 Object 类型;具体又细分为 Object
,Array
,Date
,RegExp
,Function
等类型。另外和 Java 语言类似,对于布尔,数值,字符串等基本类型,分别存在其对应的包装类型 Boolean,Number,String,但通常我们并不会使用到这些包装类型,只需要使用其基本类型即可。 \n \n二、基本类型 \n2.1 数值类型 \n1. 进制数值
\nECMAScript 中的 Number 支持以下三种常用进制:
\n\n十进制 :正常数值就是十进制; \n八进制 :八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7); \n十六进制 :十六进制字面值的前两位必须是 0x,后跟任意的十六进制数字(0~9 及 A~F)。 \n \nconsole .log(56 ); \nconsole .log(070 ); \nconsole .log(0x38 ); \n
\n2. 浮点数值
\nECMAScript 的数值类型同样支持浮点数,但是由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会尽量将浮点数值转换为整数值存储:
\nvar a = 10.0 ;\nconsole .log(a); \n
\n和其他语言类似,浮点数中的数值也是不精准的,示例如下:
\nvar a = 0.1 ; var b = 0.2 ;\n\na + b ; \na+b === 0.3 ; \n
\n如果想要对浮点数进行精确计算,可以使用 decimal.js , math.js 等第三方库。
\n3. 科学计数法
\nECMAScript 支持使用科学计数法来表达数值:
\n8e-2 \n8e2 \n
\n4. parseInt() \\ parseFloat()
\nparseInt 可以用于解析字符串并返回整数,parseFloat 用于解析字符串并返回浮点数:
\nparseInt (\"56\" ); \nparseInt (\"0x38\" , 16 ); \nparseInt (\"56.6\" ); \n\nparseFloat (\"12.2\" ); \n\nparseInt (\"blue\" ); \n
\n5. toFixed()
\ntoFixed 用于保留指定位数的小数,但需要注意的是其四舍五入的行为是不确定的:
\n1.35 .toFixed(1 ) \n1.335 .toFixed(2 ) \n1.3335 .toFixed(3 ) \n1.33335 .toFixed(4 ) \n1.333335 .toFixed(5 ) \n1.3333335 .toFixed(6 ) \n
\n想要解决这个问题,需要重写 toFixed 方法并通过判断最后一位是否大于或等于5来决定是否需要进位,具体代码如下:
\n\nNumber .prototype.toFixed = function (len ) {\n if (len>20 || len<0 ){\n throw new RangeError ('toFixed() digits argument must be between 0 and 20' );\n }\n \n var number = Number (this );\n if (isNaN (number) || number >= Math .pow(10 , 21 )) {\n return number.toString();\n }\n if (typeof (len) == 'undefined' || len == 0 ) {\n return (Math .round(number)).toString();\n }\n var result = number.toString(),\n numberArr = result.split('.' );\n\n if (numberArr.length<2 ){\n \n return padNum(result);\n }\n var intNum = numberArr[0 ], \n deciNum = numberArr[1 ],\n lastNum = deciNum.substr(len, 1 );\n \n if (deciNum.length == len){\n \n return result;\n }\n if (deciNum.length < len){\n \n return padNum(result)\n }\n \n result = intNum + '.' + deciNum.substr(0 , len);\n if (parseInt (lastNum, 10 )>=5 ){\n \n var times = Math .pow(10 , len); \n var changedInt = Number (result.replace('.' ,'' ));\n changedInt++;\n changedInt /= times;\n result = padNum(changedInt+'' );\n }\n return result;\n \n function padNum (num ) {\n var dotPos = num.indexOf('.' );\n if (dotPos === -1 ){\n \n num += '.' ;\n for (var i = 0 ;i<len;i++){\n num += '0' ;\n }\n return num;\n } else {\n \n var need = len - (num.length - dotPos - 1 );\n for (var j = 0 ;j<need;j++){\n num += '0' ;\n }\n return num;\n }\n }\n}\n
\n\n参考自:js中小数四舍五入和浮点数的研究
\n \n2.2 字符类型 \n1. 字符串表示
\nECMAScript 支持使用双引号 "
或单引号 '
来表示字符串,并且 ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量,示例如下:
\nvar lang = \"Java\" ;\n\nlang = lang + \"Script\" ;\n
\n2. 转换为字符串
\n要把一个值转换为一个字符串有两种方式:
\n\n使用对象方法 toString() :大多数对象都具有这个方法,但需要注意的是 null 和 undefined 没有; \n使用转型函数 String() :使用该转型函数时,如果传入的值有 toString() 方法,则调用该方法并返回相应的结果;如果传入的值是 null,则返回 "null" ;如果传入值是 undefined,则返回 "undefined" 。 示例如下: \n \nvar a = null ;\na.toString() \nString (a) \n
\n3. 常用的字符串操作
\n\nconcat() :用于拼接一个或多个字符串; \nslice() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置; \nsubstring() :用于截取字符串,接收两个参数,分别代表截取的开始位置和结束位置; \nsubstr() :用于截取字符串,接收两个参数,分别代表截取的开始位置和截取的长度; \nindexOf() \\ lastIndexOf() :均接收两个参数,分别代表待查找的字符串和查找的开始位置; \ntrim() :用于去除字符串前后的空格。 \n \nslice,substring,substr 等方法在传入正数参数时,其行为比较好预期,但传递参数是负数时,则具体的行为表现如下:
\n\nslice() :会将传入的负值与字符串的长度相加; \nsubstring() :方法会把所有负值参数都转换为 0 ; \nsubstr() :会将第一个负值参数加上字符串的长度,如果传递了第二个参数且为负数时候,会将其转换为 0 。 \n \nvar stringValue = \"hello world\" ;\n\n\nalert(stringValue.slice(3 )); \nalert(stringValue.substring(3 )); \nalert(stringValue.substr(3 )); \n\n\nalert(stringValue.slice(3 , 7 )); \nalert(stringValue.substring(3 ,7 )); \nalert(stringValue.substr(3 , 7 )); \n\n\nalert(stringValue.slice(-3 )); \nalert(stringValue.substring(-3 )); \nalert(stringValue.substr(-3 )); \n\n\nalert(stringValue.slice(3 , -4 )); \nalert(stringValue.substring(3 , -4 )); \nalert(stringValue.substr(3 , -4 )); \n
\n2.3 基本类型检测 \nJavaScript 是一种弱类型的语言,在声明变量时候可以不必指明其具体类型,而是由程序进行推断。如果想要知道变量具体属于哪一个基础类型,可以使用 typeof 关键字,它的返回情况如下:
\n\nundefined :如果对应的值未定义; \nboolean :如果对应的值是布尔值; \nstring :如果对应的值是字符串; \nnumber :如果对应的值是数值; \nobject :如果对应的值是对象或 null; \nfunction :如果对应的值是函数则返回 function。 函数在本质上也是对象,但是由于其一等公民的特殊地位,所以将其和其他普通对象进行区分是很有必要的,因此 typeof 对其检测时会返回 function ,而不是 object 。 \n \n三、引用类型 \n3.1 Object 类型 \n创建 Object 实例有以下两种方式:
\n\n使用 new 操作符后跟着 Object 构造函数; \n使用对象字面量的方式。 \n \n\nvar user = new Object ();\nuser.name = \"heibaiying\" ;\nuser.age = 30 ;\n\n\nvar user = {\n name : \"heibaiying\" ,\n age : 30 \n};\n
\n3.2 Array 类型 \n创建数组也有两种方式,基于构造函数的方式和基于对象字面量的方式:
\n\nvar colors = new Array ();\nvar colors = new Array (20 );\nvar colors = new Array (\"red\" , \"blue\" , \"green\" );\n\n\nvar names = [];\nvar colors = [\"red\" , \"blue\" , \"green\" ];\n
\n数组的长度保存在其 length 属性中,和其他语言中的 length 属性不同,这个值是不是只读的,可以用其进行数组的截断操作或添加新的数据项,示例如下:
\nvar colors = [\"red\" , \"blue\" , \"green\" ]; \n\ncolors.length = 2 ; \ncolors[colors.length] = \"green\" ; \ncolors[10 ] = \"black\" ; \n
\n数组的其他常用方法如下:
\n1. 检测数组
\ncolors instanceof Array \nArray .isArray(colors)\n
\n2. 转换方法
\nvar colors = [\"red\" , \"blue\" , \"green\" ];\n\ncolors.valueOf(); \ncolors; \ncolors.toString(); \ncolors.join(\"|\" ); \n
\n3. 栈方法
\nECMAScript 的数组提供了类似栈的特性,能够实现后进先出:
\nvar colors = [\"red\" , \"blue\" , \"green\" ];\n\ncolors.push(\"black\" ); \ncolors.pop() \ncolors \n
\n4. 队列方法
\nECMAScript 的数组提供了类似栈的特性,能够实现先进先出:
\ncolors.push(\"black\" ,\"yellow\" ); \ncolors.shift() \ncolors \n
\n5. 重排序方法
\nvar values = [1 , 2 , 3 , 4 , 5 ];\nvalues.reverse();\nvalues \n\n\nfunction compare (value1, value2 ) {\n if (value1 < value2) {\n return -1 ;\n } else if (value1 > value2) {\n return 1 ;\n } else {\n return 0 ;\n }\n}\nvalues.sort(compare)\nvalues \n
\n6. 操作方法
\nconcat() 用于拼接并返回新的数组:
\nvar colors = [\"red\" , \"green\" , \"blue\" ];\nvar colors2 = colors.concat(\"yellow\" , [\"black\" , \"brown\" ]);\n\ncolors \ncolors2 \n
\nslice() 用于截取数组并返回新的数组,它接收两个参数,分别代表截取的开始位置和结束位置,它是一个前开后闭的区间:
\nvar colors = [\"red\" , \"green\" , \"blue\" , \"yellow\" , \"purple\" ];\n\nvar colors2 = colors.slice(1 ); \nvar colors3 = colors.slice(0 ,2 ); \n
\nsplice() 用于删除并在删除位置新增数据项,它接收任意个参数,其中第一个参数为删除的开始位置,第二个参数为删除多少个数据项,之后可以接任意个参数,用于表示待插入的数据项:
\nvar colors = [\"red\" , \"green\" , \"blue\" , \"yellow\" ];\n\ncolors.splice(1 ,2 ) \ncolors \n\ncolors.splice(1 ,0 ,\"black\" ,\"green\" ) \ncolors \n
\n7. 位置方法
\nindexOf() 和 lastIndexOf() 用于查找指定元素的 Index ,它们都接收两个参数:待查找项和查找的起点位置:
\nvar colors = [\"red\", \"green\", \"blue\", \"yellow\", \"green\", \"blue\"];\n\ncolors.indexOf(\"green\"); // 1\ncolors.indexOf(\"green\", 3); // 4\ncolors.lastIndexOf(\"green\"); // 4\ncolors.lastIndexOf(\"green\", 3); // 1\n
\n8. 迭代方法
\nECMAScript 5 提供了五个迭代方法:
\n\nevery() :判断数组中的每个元素是否满足指定条件,如果全部满足则返回 true,否则返回 flase; \nsome() :判断数组中的每个元素是否满足指定条件,只要有一个满足则返回 true,否则返回 flase; \nfilter() :过滤并返回符合条件的元素组成的数组。 \nforEach() :对数组中的每一项运行给定函数。 \nmap() :对数组中的每一项运行给定函数,并返回每次函数调用结果所组成的数组。 \n \nvar numbers = [1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 ];\n\nnumbers.every(function (value, index, array ) {\n return value > 3 ;\n}); \n\n\nnumbers.some(function (value, index, array ) {\n return value > 3 ;\n}); \n\n\nnumbers.filter(function (value, index, array ) {\n return value > 3 ;\n}); \n\n\nnumbers.forEach(function (value, index, array ) {\n console .log(value);\n});\n\nnumbers.map(function (value, index, array ) {\n return value * 10 ;\n}); \n\n
\n9. 归并方法
\nECMAScript 5 提供了两个归并数组的方法: reduce() 和 reduceRight() 。 它们都接收四个参数:前一个值、当前值、当前项的索引 和 数组本身,使用示例如下:
\nvar values = [1 , 2 , 3 , 4 , 5 ];\nvar sum01 = values.reduce(function (prev, cur, index, array ) {\n return prev + cur;\n}); \n\nvar sum02 = values.reduceRight(function (prev, cur, index, array ) {\n return prev + cur;\n}); \n
\n3.3 Date 类型 \n创建一个日期对象,可以使用 new 操作符和 Date 构造函数:
\nvar now = new Date();\nnow.toLocaleString() \n \nvar date = new Date(2018 , 7 , 8 , 8 , 30 , 20 );\ndate.toLocaleString(); \n
\n如果你只想知道当前时间的毫秒数,可以直接使用 Date 对象的静态方法:
\nDate .now()\n1568426130593 \n
\n1. 格式转换
\n\ntoLocaleString() :按照浏览器所在时区返回相应的日期格式; \ntoString() :返回日期时间数据和的时区数据; \nvalueOf() :返回日期的时间戳格式。 \n \nvar date = new Date (2018 , 7 , 8 , 8 , 30 , 20 );\n\nconsole .log(date.toLocaleString()); \nconsole .log(date.toString()); \nconsole .log(date.valueOf()); \n
\n由于 valueOf() 返回的是日期的时间戳格式,所以对于 date 对象,可以直接使用比较运算符来比较其大小:
\nvar date01 = new Date (2018 , 7 , 8 , 8 , 30 , 20 );\nvar date02 = new Date (2016 , 7 , 8 , 8 , 30 , 20 );\n\nconsole .log(date01 > date02); \nconsole .log(date01 < date02); \n
\n2. 常用方法
\n\ngetTime() \\ setTime(毫秒) :返回和设置整个日期的所代表的毫秒数;与 valueOf() 方法返回的值相同; \ngetFullYear() \\ setFullYear(年) :返回和设置4位数的年份; \ngetMonth() \\ setMonth(月) :返回和设置月份,其中 0 表示一月, 11 表示十二月; \ngetDate() \\ setDate(日) :返回和设置月份中的天数(1到31); \ngetDay() :返回和设置星期几 ( 其中0表示星期日, 6表示星期六); \ngetHours() \\ setHours(时) :返回和设置小时数(0到23); \ngetMinutes() \\ setMinutes(分) :返回和设置日期中的分钟数(0到59); \ngetSeconds() \\ setSeconds(秒) :返回和设置日期中的秒数(0到59); \ngetMilliseconds() \\ setMilliseconds(毫秒) :返回和设置日期中的毫秒数。 \n \n3.4 Funcation 类型 \n1. 函数参数
\nECMAScript 使用 function 关键字来声明函数,但和其他语言不同的是,ECMAScript 中函数对于参数的限制是非常宽松的,例如你在定义函数时定义了两个参数,但在调用时可以只传递一个参数、也可以传三个参数,甚至不传递,示例如下:
\nfunction test (first, second) {\n console.log(\"first:\" + first + \",second:\" + second);\n}\ntest(1 ) \ntest(1 ,2 ) \ntest(1 ,2 ,3 ) \n
\n之所以能实现这样的效果,是因为 ECMAScript 在函数内部使用了一个数组 arguments 来维护所有参数,函数接收到的始终都是这个数组,而在实际使用时指向的也是这个数组中的具体元素,所以以上的函数等价于下面的函数:
\nfunction test (first, second ) {\n console .log(\"first:\" + arguments [0 ] + \",second:\" + arguments [1 ]);\n}\n
\n2. 改变函数作用域
\n在 ECMAScript 5 中,每个函数都包含两个非继承而来的方法:apply() 和 call() ,它们都用于在特定的作用域中调用函数。简单来说,可以用这两个方法来改变函数的实际调用对象,从而改变 this 的值,因为 this 总是指向当前函数的实际调用对象:
\nwindow .color = \"red\" ;\nvar o = { color : \"blue\" };\nfunction sayColor ( ) {\n console .log(this .color);\n}\n\nsayColor(); \nsayColor.call(this ); \nsayColor.call(window ); \nsayColor.call(o); \n
\napply() 和 call() 的第一个参数都是指代函数的调用对象,它们的区别主要在于第二个参数:apply() 支持使用数组或 arguments 给调用函数传值,而 call() 给调用函数传值时,必须逐个列举:
\nfunction sum (num1, num2 ) {\n return num1 + num2;\n}\n\nfunction callSum1 (num1, num2 ) {\n return sum.apply(this , arguments );\n}\n\nfunction callSum2 (num1, num2 ) {\n return sum.apply(this , [num1, num2]);\n}\n\nfunction callSum3 (num1, num2 ) {\n return sum.call(this , num1, num2);\n}\n\ncallSum1(10 , 10 );\ncallSum2(10 , 10 );\ncallSum3(10 , 10 );\n
\n3. 绑定函数作用域
\n如果想要将函数绑定在某个特定的作用域上,可以使用 bind() 函数:
\nwindow .color = \"red\" ;\nvar o = { color : \"blue\" };\nfunction sayColor ( ) {\n console .log(this .color);\n}\n\nvar objectSayColor = sayColor.bind(o);\nobjectSayColor(); \n
\n3.5 引用类型检测 \n想要检测某个对象是否属于某个引用类型,可以使用 instanceof 关键字:
\nvar date = new Date();\ndate instanceof Date // true\n
\n四、内置对象 \n4.1 Global 对象 \nECMAScript 中内置了一个全局对象 Global ,任何不属于任何其他对象的属性和方法,最终都是它的属性和方法。 ES 通过该内置对象,提供了一些可以直接调用的全局方法,常用的如下:
\n\nisNaN() :用于确定一个值是否为 NaN; \nisFinite() :用于判断被传入的参数值是否为一个有限数值; \nparseInt() \\ parseFloat() :解析并返回一个整数 \\ 浮点数; \nencodeURI() :对 URI 进行编码,但不会对本身属于 URI 的特殊字符进行编码,例如冒号、正斜杠、问号和井号; \nencodeURIComponent() :对 URI 进行编码,会对任何非标准字符进行编码。 \n \n4.2 window 对象 \nECMAScript 并没有提供任何直接访问 Global 对象的方法,但是浏览器却基于 Global 扩展实现了 window 对象,可以直接在浏览器环境下使用:
\nwindow .isFinite(12 ) \na = 12 ;\nwindow .a \n
\n五、作用域与闭包 \n5.1 作用域 \n在 ECMAScript 6 之前,只存在两种作用域,即:全局作用域 和 函数作用域,不存在块级作用域。这意味着在除了函数外的任何代码块中使用 var 关键字声明的变量都会被提升为全局变量,示例如下:
\nfunction test ( ) {\n var age =12 ;\n}\nage \n\nif (true ) {\n var name = \"heibaiying\" ;\n}\nname \n
\n这种情况同样适用与 for 循环代码块:
\nfor (var i = 0 ; i < 10 ; i++) {}\nconsole .log(i); \n
\n5.2 作用域链 \n由于函数作用域的存在,函数内的变量不能被外部访问,但是函数内的变量可以被其内部的函数访问,并且函数也可以访问其父级作用域上的变量,从而形成一条从其自身作用域到全局作用域的链条,示例如下:
\nvar global = \"global\" ;\nvar outer = \"outer global\" ;\n\n(function outer ( ) {\n var outer = \"outer\" ;\n\n function inner ( ) {\n console .log(global, outer);\n }\n\n inner()\n})();\n\n\n
\n5.3 闭包 \n由于函数作用域的存在,函数内的变量不能被外部访问,这可以保证变量的私有性。但如果你想允许外部对内部变量进行特定操作,可以通过闭包来实现。闭包是指有权访问另一个函数作用域中的变量的函数。示例如下:
\nvar contain = function () {\n \n var arr = [];\n\n return {\n push: function () {\n arr.push(...arguments);\n },\n \n get: function () {\n return arr;\n }\n }\n};\n\nvar ctn = contain();\nctn.push(1 , 2 , 3 , 4 );\nctn.get(); \n
\n六、对象设计 \nECMAScript 中的对象都有两种基本属性:数据属性和访问器属性。
\n6.1 数据属性 \n数据属性有以下 4 个描述其行为的特性:
\n\nEnumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。 \nWritable :表示能否修改属性的值;对于直接在对象上定义的属性, 该值默认为 true。 \nValue :对应属性的数据值。默认值为 undefined。 \nConfigurable :表示能否对属性进行删除,修改等配置操作,对于直接在对象上定义的属性, 该值默认为 true。需要注意的是一旦将该属性的值设置为 false,就不能再将其设置为 true 。即一旦设置为不可配置,就不能再修改为可配置。因为你已经修改为不可配置,此时任何配置操作都无效了,自然修改 Configurable 属性的操作也无效。 \n \nvar person = {age : 12 };\nObject .defineProperty(person, \"name\" , {\n Enumerable : false ,\n writable : false ,\n value : \"heibai\" \n});\n\nconsole .log(person.name); \nperson.name = \"ying\" ;\nconsole .log(person.name); \n\nfor (var key in person) {\n console .log(\"key:\" + key + \",value:\" + person[key]) \n}\n
\n6.2 访问器属性 \n访问器属性也有以下 4 个描述其行为的特性:
\n\nConfigurable :表示能否对属性进行删除,修改等配置操作;对于直接在对象上定义的属性, 该值默认为 true。 \nEnumerable :表示该属性能否通过 for-in 循环返回;对于直接在对象上定义的属性, 该值默认为 true。 \nGet :在读取属性时调用的函数。默认值为 undefined。 \nSet :在写入属性时调用的函数。默认值为 undefined。 \n \nvar student = {\n _age : null ,\n birthday : new Date (2012 ,7 ,2 )\n};\nObject .defineProperty(student, \"age\" , {\n \n get : function ( ) {\n if (this ._age == null ) {\n \n return new Date ().getFullYear() - this .birthday.getFullYear()\n }else {\n return this ._age;\n }\n },\n set : function (newValue ) {\n if (newValue < 0 ) {\n console .log(\"年龄不能设置为负数\" );\n } else {\n this ._age = newValue;\n }\n }\n});\n\nstudent.age = -1 ; \nconsole .log(student.age); \nstudent.age = 12 ;\nconsole .log(student.age); \n
\n6.3 读取属性 \n想要获取一个对象的数据属性和访问器属性,可以使用 Object.getOwnPropertyDescriptor() 方法,类似于其他语言中的反射机制。这个方法接收两个参数:属性所在的对象和要读取属性名称。沿用上面的例子,示例如下:
\nvar descriptor = Object .getOwnPropertyDescriptor(student, \"age\" );\nconsole .log(descriptor.get); \nconsole .log(descriptor.enumerable); \n
\n6.4 创建对象 \n在 ECMAScript 中,对象就是一种特殊的函数,想要声明一个对象,可以结合使用构造器模式和原型模式:基本属性可以通过构造器传入;但方法声明需要定义在原型属性上,如果直接定义在构造器上,每个对象实例都会创建该方法函数,即每个对象实例调用的都是自己重复声明的方法函数,示例如下:
\nfunction Person (name, age, job ) {\n this .name = name;\n this .age = age;\n this .job = job;\n this .friends = [\"hei\" , \"bai\" ];\n \n this .sayAge = function ( ) {\n console .log(this .age)\n }\n}\n\nPerson.prototype = {\n constructor : Person,\n \n sayName: function ( ) {\n alert(this .name);\n }\n}\n\n\nvar person1 = new Person(\"user01\" , 29 , \"Software Engineer\" );\nvar person2 = new Person(\"user02\" , 27 , \"Doctor\" );\n\nperson1.friends.push(\"ying\" );\nconsole .log(person1.friends); \nconsole .log(person2.friends); \nconsole .log(person1 instanceof Person); \nconsole .log(person1.constructor === Person); \nconsole .log(person1.sayName === person2.sayName); \nconsole .log(person1.sayAge===person2.sayAge); \n
\n参考资料 \n\n尼古拉斯·泽卡斯 . JavaScript高级程序设计(第3版). 人民邮电出版社 . 2012-3-29 \nJS中小数四舍五入和浮点数的研究 \n \n",
+ "link": "\\zh-cn\\blog\\web\\javascript.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/web/js_tool_method.html b/zh-cn/blog/web/js_tool_method.html
new file mode 100644
index 0000000..8cdc394
--- /dev/null
+++ b/zh-cn/blog/web/js_tool_method.html
@@ -0,0 +1,978 @@
+
+
+
+
+
+
+
+
+
+ js_tool_method
+
+
+
+
+ JavaScript 工具函数大全
+数组
+
+all:布尔全等判断
+
+const all = (arr, fn = Boolean ) => arr.every(fn);
+
+all([4 , 2 , 3 ], x => x > 1 );
+all([1 , 2 , 3 ]);
+
+
+
+allEqual:检查数组各项相等
+
+const allEqual = arr => arr.every(val => val === arr[0 ]);
+
+allEqual([1 , 2 , 3 , 4 , 5 , 6 ]);
+allEqual([1 , 1 , 1 , 1 ]);
+
+
+
+approximatelyEqual:约等于
+
+const approximatelyEqual = (v1, v2, epsilon = 0.001 ) => Math .abs(v1 - v2) < epsilon;
+
+approximatelyEqual(Math .PI / 2.0 , 1.5708 );
+
+
+
+arrayToCSV:数组转CSV格式(带空格的字符串)
+
+
+const arrayToCSV = (arr, delimiter = ',' ) =>
+ arr.map(v => v.map(x => `"${x} "` ).join(delimiter)).join('\n' );
+
+arrayToCSV([['a' , 'b' ], ['c' , 'd' ]]);
+arrayToCSV([['a' , 'b' ], ['c' , 'd' ]], ';' );
+
+
+
+arrayToHtmlList:数组转li列表
+
+const arrayToHtmlList = (arr, listID ) =>
+ (el => (
+ (el = document .querySelector('#' + listID)),
+ (el.innerHTML += arr.map(item => `<li>${item} </li>` ).join('' ))
+ ))();
+
+arrayToHtmlList(['item 1' , 'item 2' ], 'myListID' );
+
+
+
+average:平均数
+
+const average = (...nums ) => nums.reduce((acc, val ) => acc + val, 0 ) / nums.length;
+average(...[1 , 2 , 3 ]);
+average(1 , 2 , 3 );
+
+
+
+averageBy:数组对象属性平均数
+
+此代码段将获取数组对象属性的平均值
+const averageBy = (arr, fn ) =>
+ arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val ) => acc + val, 0 ) /
+ arr.length;
+
+averageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], o => o.n);
+averageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], 'n' );
+
+
+
+bifurcate:拆分断言后的数组
+
+可以根据每个元素返回的值,使用reduce()和push() 将元素添加到第二次参数fn中 。
+const bifurcate = (arr, filter ) =>
+ arr.reduce((acc, val, i ) => (acc[filter[i] ? 0 : 1 ].push(val), acc), [[], []]);
+bifurcate(['beep' , 'boop' , 'foo' , 'bar' ], [true , true , false , true ]);
+
+
+
+
+castArray:其它类型转数组
+
+const castArray = val => (Array .isArray(val) ? val : [val]);
+
+castArray('foo' );
+castArray([1 ]);
+castArray(1 );
+
+
+
+compact:去除数组中的无效/无用值
+
+const compact = arr => arr.filter(Boolean );
+
+compact([0 , 1 , false , 2 , '' , 3 , 'a' , 'e' * 23 , NaN , 's' , 34 ]);
+
+
+
+
+countOccurrences:检测数值出现次数
+
+const countOccurrences = (arr, val ) => arr.reduce((a, v ) => (v === val ? a + 1 : a), 0 );
+countOccurrences([1 , 1 , 2 , 1 , 2 , 3 ], 1 );
+
+
+
+deepFlatten:递归扁平化数组
+
+const deepFlatten = arr => [].concat(...arr.map(v => (Array .isArray(v) ? deepFlatten(v) : v)));
+
+deepFlatten([1 , [2 ], [[3 ], 4 ], 5 ]);
+
+
+
+difference:寻找差异(并返回第一个数组独有的)
+
+此代码段查找两个数组之间的差异,并返回第一个数组独有的。
+
+const difference = (a, b ) => {
+ const s = new Set (b);
+ return a.filter(x => !s.has(x));
+};
+
+difference([1 , 2 , 3 ], [1 , 2 , 4 ]);
+
+
+
+differenceBy:先执行再寻找差异
+
+在将给定函数应用于两个列表的每个元素之后,此方法返回两个数组之间的差异。
+const differenceBy = (a, b, fn ) => {
+ const s = new Set (b.map(fn));
+ return a.filter(x => !s.has(fn(x)));
+};
+
+differenceBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor);
+differenceBy([{ x : 2 }, { x : 1 }], [{ x : 1 }], v => v.x);
+
+
+
+dropWhile:删除不符合条件的值
+
+此代码段从数组顶部开始删除元素,直到传递的函数返回为true。
+const dropWhile = (arr, func ) => {
+ while (arr.length > 0 && !func(arr[0 ])) arr = arr.slice(1 );
+ return arr;
+};
+
+dropWhile([1 , 2 , 3 , 4 ], n => n >= 3 );
+
+
+
+flatten:指定深度扁平化数组
+
+此代码段第二参数可指定深度。
+const flatten = (arr, depth = 1 ) =>
+ arr.reduce((a, v ) => a.concat(depth > 1 && Array .isArray(v) ? flatten(v, depth - 1 ) : v), []);
+
+flatten([1 , [2 ], 3 , 4 ]);
+flatten([1 , [2 , [3 , [4 , 5 ], 6 ], 7 ], 8 ], 2 );
+
+
+
+indexOfAll:返回数组中某值的所有索引
+
+此代码段可用于获取数组中某个值的所有索引,如果此值中未包含该值,则返回一个空数组。
+const indexOfAll = (arr, val ) => arr.reduce((acc, el, i ) => (el === val ? [...acc, i] : acc), []);
+
+indexOfAll([1 , 2 , 3 , 1 , 2 , 3 ], 1 );
+indexOfAll([1 , 2 , 3 ], 4 );
+
+
+
+intersection:两数组的交集
+
+
+const intersection = (a, b ) => {
+ const s = new Set (b);
+ return a.filter(x => s.has(x));
+};
+
+intersection([1 , 2 , 3 ], [4 , 3 , 2 ]);
+
+
+
+intersectionWith:两数组都符合条件的交集
+
+此片段可用于在对两个数组的每个元素执行了函数之后,返回两个数组中存在的元素列表。
+
+const intersectionBy = (a, b, fn ) => {
+ const s = new Set (b.map(fn));
+ return a.filter(x => s.has(fn(x)));
+};
+
+intersectionBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor);
+
+
+
+intersectionWith:先比较后返回交集
+
+const intersectionWith = (a, b, comp ) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1 );
+
+intersectionWith([1 , 1.2 , 1.5 , 3 , 0 ], [1.9 , 3 , 0 , 3.9 ], (a, b) => Math .round(a) === Math .round(b));
+
+
+
+minN:返回指定长度的升序数组
+
+const minN = (arr, n = 1 ) => [...arr].sort((a, b ) => a - b).slice(0 , n);
+
+minN([1 , 2 , 3 ]);
+minN([1 , 2 , 3 ], 2 );
+
+
+
+negate:根据条件反向筛选
+
+
+const negate = func => (...args ) => !func(...args);
+
+[1 , 2 , 3 , 4 , 5 , 6 ].filter(negate(n => n % 2 === 0 ));
+
+
+
+randomIntArrayInRange:生成两数之间指定长度的随机数组
+
+const randomIntArrayInRange = (min, max, n = 1 ) =>
+ Array .from({ length : n }, () => Math .floor(Math .random() * (max - min + 1 )) + min);
+
+randomIntArrayInRange(12 , 35 , 10 );
+
+
+
+sample:在指定数组中获取随机数
+
+const sample = arr => arr[Math .floor(Math .random() * arr.length)];
+
+sample([3 , 7 , 9 , 11 ]);
+
+
+
+sampleSize:在指定数组中获取指定长度的随机数
+
+此代码段可用于从数组中获取指定长度的随机数,直至穷尽数组。 使用Fisher-Yates算法对数组中的元素进行随机选择。
+const sampleSize = ([...arr], n = 1 ) => {
+ let m = arr.length;
+ while (m) {
+ const i = Math .floor(Math .random() * m--);
+ [arr[m], arr[i]] = [arr[i], arr[m]];
+ }
+ return arr.slice(0 , n);
+};
+
+sampleSize([1 , 2 , 3 ], 2 );
+sampleSize([1 , 2 , 3 ], 4 );
+
+
+
+shuffle:“洗牌” 数组
+
+此代码段使用Fisher-Yates算法随机排序数组的元素。
+
+const shuffle = ([...arr] ) => {
+ let m = arr.length;
+ while (m) {
+ const i = Math .floor(Math .random() * m--);
+ [arr[m], arr[i]] = [arr[i], arr[m]];
+ }
+ return arr;
+};
+
+const foo = [1 , 2 , 3 ];
+shuffle(foo);
+
+
+
+nest:根据parent_id生成树结构(阿里一面真题)
+
+根据每项的parent_id,生成具体树形结构的对象。
+const nest = (items, id = null , link = 'parent_id' ) =>
+ items
+ .filter(item => item[link] === id)
+ .map(item => ({ ...item, children : nest(items, item.id) }));
+
+
+用法:
+ const comments = [
+ { id : 1 , parent_id : null },
+ { id : 2 , parent_id : 1 },
+ { id : 3 , parent_id : 1 },
+ { id : 4 , parent_id : 2 },
+ { id : 5 , parent_id : 4 }
+];
+const nestedComments = nest(comments);
+
+
+函数
+
+attempt:捕获函数运行异常
+
+该代码段执行一个函数,返回结果或捕获的错误对象。
+onst attempt = (fn, ...args ) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return e instanceof Error ? e : new Error (e);
+ }
+};
+var elements = attempt(function (selector ) {
+ return document .querySelectorAll(selector);
+}, '>_>' );
+if (elements instanceof Error ) elements = [];
+
+
+
+defer:推迟执行
+
+const defer = (fn, ...args ) => setTimeout(fn, 1 , ...args);
+
+defer(console .log, 'a' ), console .log('b' );
+
+
+
+runPromisesInSeries:运行多个Promises
+
+const runPromisesInSeries = ps => ps.reduce((p, next ) => p.then(next), Promise .resolve());
+const delay = d => new Promise (r => setTimeout(r, d));
+
+runPromisesInSeries([() => delay(1000 ), () => delay(2000 )]);
+
+
+
+
+timeTaken:计算函数执行时间
+
+
+const timeTaken = callback => {
+ console .time('timeTaken' );
+ const r = callback();
+ console .timeEnd('timeTaken' );
+ return r;
+};
+
+timeTaken(() => Math .pow(2 , 10 ));
+
+
+
+createEventHub:简单的发布/订阅模式
+
+创建一个发布/订阅(发布-订阅)事件集线,有emit,on和off方法。
+
+使用Object.create(null)创建一个空的hub对象。
+emit,根据event参数解析处理程序数组,然后.forEach()通过传入数据作为参数来运行每个处理程序。
+on,为事件创建一个数组(若不存在则为空数组),然后.push()将处理程序添加到该数组。
+off,用.findIndex()在事件数组中查找处理程序的索引,并使用.splice()删除。
+
+const createEventHub = () => ({
+ hub : Object .create(null ),
+ emit(event, data) {
+ (this .hub[event] || []).forEach(handler => handler(data));
+ },
+ on(event, handler) {
+ if (!this .hub[event]) this .hub[event] = [];
+ this .hub[event].push(handler);
+ },
+ off(event, handler) {
+ const i = (this .hub[event] || []).findIndex(h => h === handler);
+ if (i > -1 ) this .hub[event].splice(i, 1 );
+ if (this .hub[event].length === 0 ) delete this .hub[event];
+ }
+});
+
+
+用法:
+const handler = data => console .log(data);
+const hub = createEventHub();
+let increment = 0 ;
+
+
+hub.on('message' , handler);
+hub.on('message' , () => console .log('Message event fired' ));
+hub.on('increment' , () => increment++);
+
+
+hub.emit('message' , 'hello world' );
+hub.emit('message' , { hello : 'world' });
+hub.emit('increment' );
+
+
+hub.off('message' , handler);
+
+
+
+memoize:缓存函数
+
+通过实例化一个Map对象来创建一个空的缓存。
+通过检查输入值的函数输出是否已缓存,返回存储一个参数的函数,该参数将被提供给已记忆的函数;如果没有,则存储并返回它。
+const memoize = fn => {
+ const cache = new Map ();
+ const cached = function (val ) {
+ return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this , val)) && cache.get(val);
+ };
+ cached.cache = cache;
+ return cached;
+};
+
+
+
+Ps: 这个版本可能不是很清晰,还有Vue源码版的:
+
+export function cached <F : Function > (fn: F ): F {
+ const cache = Object .create(null )
+ return (function cachedFn (str: string ) {
+ const hit = cache[str]
+ return hit || (cache[str] = fn(str))
+ }: any)
+}
+
+
+
+once:只调用一次的函数
+
+const once = fn => {
+ let called = false
+ return function ( ) {
+ if (!called) {
+ called = true
+ fn.apply(this , arguments )
+ }
+ }
+};
+
+
+
+flattenObject:以键的路径扁平化对象
+
+使用递归。
+
+利用Object.keys(obj)联合Array.prototype.reduce(),以每片叶子节点转换为扁平的路径节点。
+如果键的值是一个对象,则函数使用调用适当的自身prefix以创建路径Object.assign()。
+否则,它将适当的前缀键值对添加到累加器对象。
+prefix除非您希望每个键都有一个前缀,否则应始终省略第二个参数。
+
+const flattenObject = (obj, prefix = '' ) =>
+ Object .keys(obj).reduce((acc, k ) => {
+ const pre = prefix.length ? prefix + '.' : '' ;
+ if (typeof obj[k] === 'object' ) Object .assign(acc, flattenObject(obj[k], pre + k));
+ else acc[pre + k] = obj[k];
+ return acc;
+ }, {});
+
+flattenObject({ a : { b : { c : 1 } }, d : 1 });
+
+
+
+unflattenObject:以键的路径展开对象
+
+与上面的相反,展开对象。
+const unflattenObject = obj =>
+ Object .keys(obj).reduce((acc, k ) => {
+ if (k.indexOf('.' ) !== -1 ) {
+ const keys = k.split('.' );
+ Object .assign(
+ acc,
+ JSON .parse(
+ '{' +
+ keys.map((v, i ) => (i !== keys.length - 1 ? `"${v} ":{` : `"${v} ":` )).join('' ) +
+ obj[k] +
+ '}' .repeat(keys.length)
+ )
+ );
+ } else acc[k] = obj[k];
+ return acc;
+ }, {});
+
+unflattenObject({ 'a.b.c' : 1 , d : 1 });
+
+
+这个的用途,在做Tree组件或复杂表单时取值非常舒服。
+字符串
+
+byteSize:返回字符串的字节长度
+
+const byteSize = str => new Blob([str]).size;
+
+byteSize('😀' );
+byteSize('Hello World' );
+
+
+
+capitalize:首字母大写
+
+const capitalize = ([first, ...rest] ) =>
+ first.toUpperCase() + rest.join('' );
+
+capitalize('fooBar' );
+capitalize('fooBar' , true );
+
+
+
+capitalizeEveryWord:每个单词首字母大写
+
+const capitalizeEveryWord = str => str.replace(/\b[a-z]/g , char => char.toUpperCase());
+
+capitalizeEveryWord('hello world!' );
+
+
+
+decapitalize:首字母小写
+
+const decapitalize = ([first, ...rest] ) =>
+ first.toLowerCase() + rest.join('' )
+
+decapitalize('FooBar' );
+decapitalize('FooBar' );
+
+
+
+luhnCheck:银行卡号码校验(luhn算法)
+
+Luhn算法的实现,用于验证各种标识号,例如信用卡号,IMEI号,国家提供商标识号等。
+与String.prototype.split('')结合使用,以获取数字数组。获得最后一个数字。实施luhn算法。如果被整除,则返回,否则返回。
+const luhnCheck = num => {
+ let arr = (num + '' )
+ .split('' )
+ .reverse()
+ .map(x => parseInt (x));
+ let lastDigit = arr.splice(0 , 1 )[0 ];
+ let sum = arr.reduce((acc, val, i ) => (i % 2 !== 0 ? acc + val : acc + ((val * 2 ) % 9 ) || 9 ), 0 );
+ sum += lastDigit;
+ return sum % 10 === 0 ;
+};
+
+
+用例:
+luhnCheck('4485275742308327' );
+luhnCheck(6011329933655299 );
+luhnCheck(123456789 );
+
+补充:银行卡号码的校验规则:
+银行卡号码的校验采用Luhn算法,校验过程大致如下:
+
+从右到左给卡号字符串编号,最右边第一位是1,最右边第二位是2,最右边第三位是3….
+从右向左遍历,对每一位字符t执行第三个步骤,并将每一位的计算结果相加得到一个数s。
+对每一位的计算规则:如果这一位是奇数位,则返回t本身,如果是偶数位,则先将t乘以2得到一个数n,如果n是一位数(小于10),直接返回n,否则将n的个位数和十位数相加返回。
+如果s能够整除10,则此号码有效,否则号码无效。
+
+因为最终的结果会对10取余来判断是否能够整除10,所以又叫做模10算法。
+当然,还是库比较香: bankcardinfo
+
+splitLines:将多行字符串拆分为行数组。
+
+使用String.prototype.split()和正则表达式匹配换行符并创建一个数组。
+const splitLines = str => str.split(/\r?\n/ );
+
+splitLines('This\nis a\nmultiline\nstring.\n' );
+
+
+
+stripHTMLTags:删除字符串中的HTMl标签
+
+从字符串中删除HTML / XML标签。
+使用正则表达式从字符串中删除HTML / XML 标记。
+const stripHTMLTags = str => str.replace(/<[^>]*>/g , '' );
+
+stripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>' );
+
+
+对象
+
+dayOfYear:当前日期天数
+
+const dayOfYear = date =>
+ Math .floor((date - new Date (date.getFullYear(), 0 , 0 )) / 1000 / 60 / 60 / 24 );
+
+dayOfYear(new Date ());
+
+
+
+forOwn:迭代属性并执行回调
+
+const forOwn = (obj, fn ) => Object .keys(obj).forEach(key => fn(obj[key], key, obj));
+forOwn({ foo : 'bar' , a : 1 }, v => console .log(v));
+
+
+
+Get Time From Date:返回当前24小时制时间的字符串
+
+const getColonTimeFromDate = date => date.toTimeString().slice(0 , 8 );
+
+getColonTimeFromDate(new Date ());
+
+
+
+Get Days Between Dates:返回日期间的天数
+
+const getDaysDiffBetweenDates = (dateInitial, dateFinal ) =>
+ (dateFinal - dateInitial) / (1000 * 3600 * 24 );
+
+getDaysDiffBetweenDates(new Date ('2019-01-01' ), new Date ('2019-10-14' ));
+
+
+
+is:检查值是否为特定类型。
+
+const is = (type, val ) => ![, null ].includes(val) && val.constructor === type;
+
+is(Array , [1 ]);
+is(ArrayBuffer , new ArrayBuffer ());
+is(Map , new Map ());
+is(RegExp , /./g);
+is(Set , new Set ());
+is(WeakMap , new WeakMap ());
+is(WeakSet , new WeakSet ());
+is(String , '' );
+is(String , new String ('' ));
+is(Number , 1 );
+is(Number , new Number (1 ));
+is(Boolean , true );
+is(Boolean , new Boolean (true ));
+
+
+
+isAfterDate:检查是否在某日期后
+
+const isAfterDate = (dateA, dateB ) => dateA > dateB;
+
+isAfterDate(new Date (2010 , 10 , 21 ), new Date (2010 , 10 , 20 ));
+
+
+
+isBeforeDate:检查是否在某日期前
+
+const isBeforeDate = (dateA, dateB ) => dateA < dateB;
+
+isBeforeDate(new Date (2010 , 10 , 20 ), new Date (2010 , 10 , 21 ));
+
+
+
+tomorrow:获取明天的字符串格式时间
+
+
+const tomorrow = () => {
+ let t = new Date ();
+ t.setDate(t.getDate() + 1 );
+ return t.toISOString().split('T' )[0 ];
+};
+
+tomorrow();
+
+
+
+equals:全等判断
+
+在两个变量之间进行深度比较以确定它们是否全等。
+此代码段精简的核心在于Array.prototype.every()的使用。
+const equals = (a, b ) => {
+ if (a === b) return true ;
+ if (a instanceof Date && b instanceof Date ) return a.getTime() === b.getTime();
+ if (!a || !b || (typeof a !== 'object' && typeof b !== 'object' )) return a === b;
+ if (a.prototype !== b.prototype) return false ;
+ let keys = Object .keys(a);
+ if (keys.length !== Object .keys(b).length) return false ;
+ return keys.every(k => equals(a[k], b[k]));
+};
+
+
+用法:
+equals({ a : [2 , { e : 3 }], b : [4 ], c : 'foo' }, { a : [2 , { e : 3 }], b : [4 ], c : 'foo' });
+
+
+数字
+
+randomIntegerInRange:生成指定范围的随机整数
+
+const randomIntegerInRange = (min, max ) => Math .floor(Math .random() * (max - min + 1 )) + min;
+
+randomIntegerInRange(0 , 5 );
+
+
+
+randomNumberInRange:生成指定范围的随机小数
+
+const randomNumberInRange = (min, max ) => Math .random() * (max - min) + min;
+
+randomNumberInRange(2 , 10 );
+
+
+
+round:四舍五入到指定位数
+
+const round = (n, decimals = 0 ) => Number (`${Math .round(`${n} e${decimals} ` )} e-${decimals} ` );
+
+round(1.005 , 2 );
+
+
+
+sum:计算数组或多个数字的总和
+
+
+const sum = (...arr ) => [...arr].reduce((acc, val ) => acc + val, 0 );
+
+sum(1 , 2 , 3 , 4 );
+sum(...[1 , 2 , 3 , 4 ]);
+
+
+
+toCurrency:简单的货币单位转换
+
+const toCurrency = (n, curr, LanguageFormat = undefined ) =>
+ Intl .NumberFormat(LanguageFormat, { style : 'currency' , currency : curr }).format(n);
+
+toCurrency(123456.789 , 'EUR' );
+toCurrency(123456.789 , 'USD' , 'en-us' );
+toCurrency(123456.789 , 'USD' , 'fa' );
+toCurrency(322342436423.2435 , 'JPY' );
+
+
+浏览器操作及其它
+
+bottomVisible:检查页面底部是否可见
+
+const bottomVisible = () =>
+ document .documentElement.clientHeight + window .scrollY >=
+ (document .documentElement.scrollHeight || document .documentElement.clientHeight);
+
+bottomVisible();
+
+
+
+Create Directory:检查创建目录
+
+此代码段调用fs模块的existsSync()检查目录是否存在,如果不存在,则mkdirSync()创建该目录。
+const fs = require ('fs' );
+const createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined );
+createDirIfNotExists('test' );
+
+
+
+currentURL:返回当前链接url
+
+const currentURL = () => window .location.href;
+
+currentURL();
+
+
+
+distance:返回两点间的距离
+
+该代码段通过计算欧几里得距离来返回两点之间的距离。
+const distance = (x0, y0, x1, y1 ) => Math .hypot(x1 - x0, y1 - y0);
+
+distance(1 , 1 , 2 , 3 );
+
+
+
+elementContains:检查是否包含子元素
+此代码段检查父元素是否包含子元素。
+
+const elementContains = (parent, child ) => parent !== child && parent.contains(child);
+
+elementContains(document .querySelector('head' ), document .querySelector('title' ));
+elementContains(document .querySelector('body' ), document .querySelector('body' ));
+
+
+
+getStyle:返回指定元素的生效样式
+
+const getStyle = (el, ruleName ) => getComputedStyle(el)[ruleName];
+
+getStyle(document .querySelector('p' ), 'font-size' );
+
+
+
+getType:返回值或变量的类型名
+
+const getType = v =>
+ v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
+
+getType(new Set ([1 , 2 , 3 ]));
+getType([1 , 2 , 3 ]);
+
+
+
+hasClass:校验指定元素的类名
+
+const hasClass = (el, className ) => el.classList.contains(className);
+hasClass(document .querySelector('p.special' ), 'special' );
+
+
+
+hide:隐藏所有的指定标签
+
+const hide = (...el ) => [...el].forEach(e => (e.style.display = 'none' ));
+
+hide(document .querySelectorAll('img' ));
+
+
+
+httpsRedirect:HTTP 跳转 HTTPS
+
+const httpsRedirect = () => {
+ if (location.protocol !== 'https:' ) location.replace('https://' + location.href.split('//' )[1 ]);
+};
+
+httpsRedirect();
+
+
+
+insertAfter:在指定元素之后插入新元素
+
+const insertAfter = (el, htmlString ) => el.insertAdjacentHTML('afterend' , htmlString);
+
+
+insertAfter(document .getElementById('myId' ), '<p>after</p>' );
+
+
+
+insertBefore:在指定元素之前插入新元素
+
+const insertBefore = (el, htmlString ) => el.insertAdjacentHTML('beforebegin' , htmlString);
+
+insertBefore(document .getElementById('myId' ), '<p>before</p>' );
+
+
+
+isBrowser:检查是否为浏览器环境
+
+此代码段可用于确定当前运行时环境是否为浏览器。这有助于避免在服务器(节点)上运行前端模块时出错。
+const isBrowser = () => ![typeof window , typeof document ].includes('undefined' );
+
+isBrowser();
+isBrowser();
+
+
+
+isBrowserTab:检查当前标签页是否活动
+
+const isBrowserTabFocused = () => !document .hidden;
+
+isBrowserTabFocused();
+
+
+
+nodeListToArray:转换nodeList为数组
+
+const nodeListToArray = nodeList => [...nodeList];
+
+nodeListToArray(document .childNodes);
+
+
+
+Random Hexadecimal Color Code:随机十六进制颜色
+
+
+const randomHexColorCode = () => {
+ let n = (Math .random() * 0xfffff * 1000000 ).toString(16 );
+ return '#' + n.slice(0 , 6 );
+};
+
+randomHexColorCode();
+
+
+
+scrollToTop:平滑滚动至顶部
+
+const scrollToTop = () => {
+ const c = document .documentElement.scrollTop || document .body.scrollTop;
+ if (c > 0 ) {
+ window .requestAnimationFrame(scrollToTop);
+ window .scrollTo(0 , c - c / 8 );
+ }
+};
+
+scrollToTop();
+
+
+
+smoothScroll:滚动到指定元素区域
+
+该代码段可将指定元素平滑滚动到浏览器窗口的可见区域。
+const smoothScroll = element =>
+ document .querySelector(element).scrollIntoView({
+ behavior : 'smooth'
+ });
+
+smoothScroll('#fooBar' );
+smoothScroll('.fooBar' );
+
+
+
+detectDeviceType:检测移动/PC设备
+
+const detectDeviceType = () =>
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i .test(navigator.userAgent)
+ ? 'Mobile'
+ : 'Desktop' ;
+
+
+
+getScrollPosition:返回当前的滚动位置
+
+默认参数为window ,pageXOffset(pageYOffset)为第一选择,没有则用scrollLeft(scrollTop)
+const getScrollPosition = (el = window ) => ({
+ x : el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
+ y : el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
+});
+
+getScrollPosition();
+
+
+
+size:获取不同类型变量的字节长度
+
+这个的实现非常巧妙,利用Blob类文件对象的特性,获取对象的长度。
+另外,多重三元运算符,是真香。
+const size = val =>
+ Array .isArray(val)
+ ? val.length
+ : val && typeof val === 'object'
+ ? val.size || val.length || Object .keys(val).length
+ : typeof val === 'string'
+ ? new Blob([val]).size
+ : 0 ;
+
+size([1 , 2 , 3 , 4 , 5 ]);
+size('size' );
+size({ one : 1 , two : 2 , three : 3 });
+
+
+
+
+escapeHTML:转义HTML
+
+当然是用来防XSS攻击啦。
+const escapeHTML = str =>
+ str.replace(
+ /[&<>'"]/g ,
+ tag =>
+ ({
+ '&' : '&' ,
+ '<' : '<' ,
+ '>' : '>' ,
+ "'" : ''' ,
+ '"' : '"'
+ }[tag] || tag)
+ );
+
+escapeHTML('<a href="#">Me & you</a>' );
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/web/js_tool_method.json b/zh-cn/blog/web/js_tool_method.json
new file mode 100644
index 0000000..fba6961
--- /dev/null
+++ b/zh-cn/blog/web/js_tool_method.json
@@ -0,0 +1,6 @@
+{
+ "filename": "js_tool_method.md",
+ "__html": "JavaScript 工具函数大全 \n数组 \n\nall:布尔全等判断 \n \nconst all = (arr, fn = Boolean ) => arr.every(fn);\n\nall([4 , 2 , 3 ], x => x > 1 ); \nall([1 , 2 , 3 ]); \n\n
\n\nallEqual:检查数组各项相等 \n \nconst allEqual = arr => arr.every(val => val === arr[0 ]);\n\nallEqual([1 , 2 , 3 , 4 , 5 , 6 ]); \nallEqual([1 , 1 , 1 , 1 ]); \n\n
\n\napproximatelyEqual:约等于 \n \nconst approximatelyEqual = (v1, v2, epsilon = 0.001 ) => Math .abs(v1 - v2) < epsilon;\n\napproximatelyEqual(Math .PI / 2.0 , 1.5708 ); \n\n
\n\narrayToCSV:数组转CSV格式(带空格的字符串) \n \n\nconst arrayToCSV = (arr, delimiter = ',' ) => \n arr.map(v => v.map(x => `\"${x} \"` ).join(delimiter)).join('\\n' );\n \narrayToCSV([['a' , 'b' ], ['c' , 'd' ]]); \narrayToCSV([['a' , 'b' ], ['c' , 'd' ]], ';' ); \n\n
\n\narrayToHtmlList:数组转li列表 \n \nconst arrayToHtmlList = (arr, listID ) => \n (el => (\n (el = document .querySelector('#' + listID)),\n (el.innerHTML += arr.map(item => `<li>${item} </li>` ).join('' ))\n ))();\n \narrayToHtmlList(['item 1' , 'item 2' ], 'myListID' );\n\n
\n\naverage:平均数 \n \nconst average = (...nums ) => nums.reduce((acc, val ) => acc + val, 0 ) / nums.length;\naverage(...[1 , 2 , 3 ]); \naverage(1 , 2 , 3 ); \n\n
\n\naverageBy:数组对象属性平均数 \n \n此代码段将获取数组对象属性的平均值
\nconst averageBy = (arr, fn ) => \n arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val ) => acc + val, 0 ) /\n arr.length;\n \naverageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], o => o.n); \naverageBy([{ n : 4 }, { n : 2 }, { n : 8 }, { n : 6 }], 'n' ); \n\n
\n\nbifurcate:拆分断言后的数组 \n \n可以根据每个元素返回的值,使用reduce()和push() 将元素添加到第二次参数fn中 。
\nconst bifurcate = (arr, filter ) => \n arr.reduce((acc, val, i ) => (acc[filter[i] ? 0 : 1 ].push(val), acc), [[], []]);\nbifurcate(['beep' , 'boop' , 'foo' , 'bar' ], [true , true , false , true ]); \n\n\n
\n\ncastArray:其它类型转数组 \n \nconst castArray = val => (Array .isArray(val) ? val : [val]);\n\ncastArray('foo' ); \ncastArray([1 ]); \ncastArray(1 ); \n\n
\n\ncompact:去除数组中的无效/无用值 \n \nconst compact = arr => arr.filter(Boolean );\n\ncompact([0 , 1 , false , 2 , '' , 3 , 'a' , 'e' * 23 , NaN , 's' , 34 ]); \n\n\n
\n\ncountOccurrences:检测数值出现次数 \n \nconst countOccurrences = (arr, val ) => arr.reduce((a, v ) => (v === val ? a + 1 : a), 0 );\ncountOccurrences([1 , 1 , 2 , 1 , 2 , 3 ], 1 ); \n\n
\n\ndeepFlatten:递归扁平化数组 \n \nconst deepFlatten = arr => [].concat(...arr.map(v => (Array .isArray(v) ? deepFlatten(v) : v)));\n\ndeepFlatten([1 , [2 ], [[3 ], 4 ], 5 ]); \n\n
\n\ndifference:寻找差异(并返回第一个数组独有的) \n \n此代码段查找两个数组之间的差异,并返回第一个数组独有的。
\n\nconst difference = (a, b ) => {\n const s = new Set (b);\n return a.filter(x => !s.has(x));\n};\n\ndifference([1 , 2 , 3 ], [1 , 2 , 4 ]); \n\n
\n\ndifferenceBy:先执行再寻找差异 \n \n在将给定函数应用于两个列表的每个元素之后,此方法返回两个数组之间的差异。
\nconst differenceBy = (a, b, fn ) => {\n const s = new Set (b.map(fn));\n return a.filter(x => !s.has(fn(x)));\n};\n\ndifferenceBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor); \ndifferenceBy([{ x : 2 }, { x : 1 }], [{ x : 1 }], v => v.x); \n\n
\n\ndropWhile:删除不符合条件的值 \n \n此代码段从数组顶部开始删除元素,直到传递的函数返回为true。
\nconst dropWhile = (arr, func ) => {\n while (arr.length > 0 && !func(arr[0 ])) arr = arr.slice(1 );\n return arr;\n};\n\ndropWhile([1 , 2 , 3 , 4 ], n => n >= 3 ); \n\n
\n\nflatten:指定深度扁平化数组 \n \n此代码段第二参数可指定深度。
\nconst flatten = (arr, depth = 1 ) => \n arr.reduce((a, v ) => a.concat(depth > 1 && Array .isArray(v) ? flatten(v, depth - 1 ) : v), []);\n\nflatten([1 , [2 ], 3 , 4 ]); \nflatten([1 , [2 , [3 , [4 , 5 ], 6 ], 7 ], 8 ], 2 ); \n\n
\n\nindexOfAll:返回数组中某值的所有索引 \n \n此代码段可用于获取数组中某个值的所有索引,如果此值中未包含该值,则返回一个空数组。
\nconst indexOfAll = (arr, val ) => arr.reduce((acc, el, i ) => (el === val ? [...acc, i] : acc), []);\n\nindexOfAll([1 , 2 , 3 , 1 , 2 , 3 ], 1 ); \nindexOfAll([1 , 2 , 3 ], 4 ); \n\n
\n\nintersection:两数组的交集 \n \n\nconst intersection = (a, b ) => {\n const s = new Set (b);\n return a.filter(x => s.has(x));\n};\n\nintersection([1 , 2 , 3 ], [4 , 3 , 2 ]); \n\n
\n\nintersectionWith:两数组都符合条件的交集 \n \n此片段可用于在对两个数组的每个元素执行了函数之后,返回两个数组中存在的元素列表。
\n\nconst intersectionBy = (a, b, fn ) => {\n const s = new Set (b.map(fn));\n return a.filter(x => s.has(fn(x)));\n};\n\nintersectionBy([2.1 , 1.2 ], [2.3 , 3.4 ], Math .floor); \n\n
\n\nintersectionWith:先比较后返回交集 \n \nconst intersectionWith = (a, b, comp ) => a.filter(x => b.findIndex(y => comp(x, y)) !== -1 );\n\nintersectionWith([1 , 1.2 , 1.5 , 3 , 0 ], [1.9 , 3 , 0 , 3.9 ], (a, b) => Math .round(a) === Math .round(b)); \n\n
\n\nminN:返回指定长度的升序数组 \n \nconst minN = (arr, n = 1 ) => [...arr].sort((a, b ) => a - b).slice(0 , n);\n\nminN([1 , 2 , 3 ]); \nminN([1 , 2 , 3 ], 2 ); \n\n
\n\nnegate:根据条件反向筛选 \n \n\nconst negate = func => (...args ) => !func(...args);\n\n[1 , 2 , 3 , 4 , 5 , 6 ].filter(negate(n => n % 2 === 0 )); \n\n
\n\nrandomIntArrayInRange:生成两数之间指定长度的随机数组 \n \nconst randomIntArrayInRange = (min, max, n = 1 ) => \n Array .from({ length : n }, () => Math .floor(Math .random() * (max - min + 1 )) + min);\n \nrandomIntArrayInRange(12 , 35 , 10 ); \n\n
\n\nsample:在指定数组中获取随机数 \n \nconst sample = arr => arr[Math .floor(Math .random() * arr.length)];\n\nsample([3 , 7 , 9 , 11 ]); \n\n
\n\nsampleSize:在指定数组中获取指定长度的随机数 \n \n此代码段可用于从数组中获取指定长度的随机数,直至穷尽数组。 使用Fisher-Yates算法对数组中的元素进行随机选择。
\nconst sampleSize = ([...arr], n = 1 ) => {\n let m = arr.length;\n while (m) {\n const i = Math .floor(Math .random() * m--);\n [arr[m], arr[i]] = [arr[i], arr[m]];\n }\n return arr.slice(0 , n);\n};\n\nsampleSize([1 , 2 , 3 ], 2 ); \nsampleSize([1 , 2 , 3 ], 4 ); \n\n
\n\nshuffle:“洗牌” 数组 \n \n此代码段使用Fisher-Yates算法随机排序数组的元素。
\n\nconst shuffle = ([...arr] ) => {\n let m = arr.length;\n while (m) {\n const i = Math .floor(Math .random() * m--);\n [arr[m], arr[i]] = [arr[i], arr[m]];\n }\n return arr;\n};\n\nconst foo = [1 , 2 , 3 ];\nshuffle(foo); \n\n
\n\nnest:根据parent_id生成树结构(阿里一面真题) \n \n根据每项的parent_id,生成具体树形结构的对象。
\nconst nest = (items, id = null , link = 'parent_id' ) => \n items\n .filter(item => item[link] === id)\n .map(item => ({ ...item, children : nest(items, item.id) }));\n\n
\n用法:
\n const comments = [\n { id : 1 , parent_id : null },\n { id : 2 , parent_id : 1 },\n { id : 3 , parent_id : 1 },\n { id : 4 , parent_id : 2 },\n { id : 5 , parent_id : 4 }\n];\nconst nestedComments = nest(comments); \n\n
\n函数 \n\nattempt:捕获函数运行异常 \n \n该代码段执行一个函数,返回结果或捕获的错误对象。
\nonst attempt = (fn, ...args ) => {\n try {\n return fn(...args);\n } catch (e) {\n return e instanceof Error ? e : new Error (e);\n }\n};\nvar elements = attempt(function (selector ) {\n return document .querySelectorAll(selector);\n}, '>_>' );\nif (elements instanceof Error ) elements = []; \n\n
\n\ndefer:推迟执行 \n \nconst defer = (fn, ...args ) => setTimeout(fn, 1 , ...args);\n\ndefer(console .log, 'a' ), console .log('b' ); \n\n
\n\nrunPromisesInSeries:运行多个Promises \n \nconst runPromisesInSeries = ps => ps.reduce((p, next ) => p.then(next), Promise .resolve());\nconst delay = d => new Promise (r => setTimeout(r, d));\n\nrunPromisesInSeries([() => delay(1000 ), () => delay(2000 )]);\n\n\n
\n\ntimeTaken:计算函数执行时间 \n \n\nconst timeTaken = callback => {\n console .time('timeTaken' );\n const r = callback();\n console .timeEnd('timeTaken' );\n return r;\n};\n\ntimeTaken(() => Math .pow(2 , 10 )); \n\n
\n\ncreateEventHub:简单的发布/订阅模式 \n \n创建一个发布/订阅(发布-订阅)事件集线,有emit,on和off方法。
\n\n使用Object.create(null)创建一个空的hub对象。 \nemit,根据event参数解析处理程序数组,然后.forEach()通过传入数据作为参数来运行每个处理程序。 \non,为事件创建一个数组(若不存在则为空数组),然后.push()将处理程序添加到该数组。 \noff,用.findIndex()在事件数组中查找处理程序的索引,并使用.splice()删除。 \n \nconst createEventHub = () => ({\n hub : Object .create(null ),\n emit(event, data) {\n (this .hub[event] || []).forEach(handler => handler(data));\n },\n on(event, handler) {\n if (!this .hub[event]) this .hub[event] = [];\n this .hub[event].push(handler);\n },\n off(event, handler) {\n const i = (this .hub[event] || []).findIndex(h => h === handler);\n if (i > -1 ) this .hub[event].splice(i, 1 );\n if (this .hub[event].length === 0 ) delete this .hub[event];\n }\n});\n\n
\n用法:
\nconst handler = data => console .log(data);\nconst hub = createEventHub();\nlet increment = 0 ;\n\n\nhub.on('message' , handler);\nhub.on('message' , () => console .log('Message event fired' ));\nhub.on('increment' , () => increment++);\n\n\nhub.emit('message' , 'hello world' ); \nhub.emit('message' , { hello : 'world' }); \nhub.emit('increment' ); \n\n\nhub.off('message' , handler);\n\n
\n\nmemoize:缓存函数 \n \n通过实例化一个Map对象来创建一个空的缓存。
\n通过检查输入值的函数输出是否已缓存,返回存储一个参数的函数,该参数将被提供给已记忆的函数;如果没有,则存储并返回它。
\nconst memoize = fn => {\n const cache = new Map ();\n const cached = function (val ) {\n return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this , val)) && cache.get(val);\n };\n cached.cache = cache;\n return cached;\n};\n\n\n
\nPs: 这个版本可能不是很清晰,还有Vue源码版的:
\n\nexport function cached <F : Function > (fn: F ): F {\n const cache = Object .create(null )\n return (function cachedFn (str: string ) {\n const hit = cache[str]\n return hit || (cache[str] = fn(str))\n }: any)\n}\n\n
\n\nonce:只调用一次的函数 \n \nconst once = fn => {\n let called = false \n return function ( ) {\n if (!called) {\n called = true \n fn.apply(this , arguments )\n }\n }\n};\n\n
\n\nflattenObject:以键的路径扁平化对象 \n \n使用递归。
\n\n利用Object.keys(obj)联合Array.prototype.reduce(),以每片叶子节点转换为扁平的路径节点。 \n如果键的值是一个对象,则函数使用调用适当的自身prefix以创建路径Object.assign()。 \n否则,它将适当的前缀键值对添加到累加器对象。 \nprefix除非您希望每个键都有一个前缀,否则应始终省略第二个参数。 \n \nconst flattenObject = (obj, prefix = '' ) => \n Object .keys(obj).reduce((acc, k ) => {\n const pre = prefix.length ? prefix + '.' : '' ;\n if (typeof obj[k] === 'object' ) Object .assign(acc, flattenObject(obj[k], pre + k));\n else acc[pre + k] = obj[k];\n return acc;\n }, {});\n \nflattenObject({ a : { b : { c : 1 } }, d : 1 }); \n\n
\n\nunflattenObject:以键的路径展开对象 \n \n与上面的相反,展开对象。
\nconst unflattenObject = obj => \n Object .keys(obj).reduce((acc, k ) => {\n if (k.indexOf('.' ) !== -1 ) {\n const keys = k.split('.' );\n Object .assign(\n acc,\n JSON .parse(\n '{' +\n keys.map((v, i ) => (i !== keys.length - 1 ? `\"${v} \":{` : `\"${v} \":` )).join('' ) +\n obj[k] +\n '}' .repeat(keys.length)\n )\n );\n } else acc[k] = obj[k];\n return acc;\n }, {});\n \nunflattenObject({ 'a.b.c' : 1 , d : 1 }); \n\n
\n这个的用途,在做Tree组件或复杂表单时取值非常舒服。
\n字符串 \n\nbyteSize:返回字符串的字节长度 \n \nconst byteSize = str => new Blob([str]).size;\n\nbyteSize('😀' ); \nbyteSize('Hello World' ); \n\n
\n\ncapitalize:首字母大写 \n \nconst capitalize = ([first, ...rest] ) => \n first.toUpperCase() + rest.join('' );\n \ncapitalize('fooBar' ); \ncapitalize('fooBar' , true ); \n\n
\n\ncapitalizeEveryWord:每个单词首字母大写 \n \nconst capitalizeEveryWord = str => str.replace(/\\b[a-z]/g , char => char.toUpperCase());\n\ncapitalizeEveryWord('hello world!' ); \n\n
\n\ndecapitalize:首字母小写 \n \nconst decapitalize = ([first, ...rest] ) => \n first.toLowerCase() + rest.join('' )\n\ndecapitalize('FooBar' ); \ndecapitalize('FooBar' ); \n\n
\n\nluhnCheck:银行卡号码校验(luhn算法) \n \nLuhn算法的实现,用于验证各种标识号,例如信用卡号,IMEI号,国家提供商标识号等。
\n与String.prototype.split('')结合使用,以获取数字数组。获得最后一个数字。实施luhn算法。如果被整除,则返回,否则返回。
\nconst luhnCheck = num => {\n let arr = (num + '' )\n .split('' )\n .reverse()\n .map(x => parseInt (x));\n let lastDigit = arr.splice(0 , 1 )[0 ];\n let sum = arr.reduce((acc, val, i ) => (i % 2 !== 0 ? acc + val : acc + ((val * 2 ) % 9 ) || 9 ), 0 );\n sum += lastDigit;\n return sum % 10 === 0 ;\n};\n\n
\n用例:
\nluhnCheck('4485275742308327' ); \nluhnCheck(6011329933655299 ); \nluhnCheck(123456789 ); \n
\n补充:银行卡号码的校验规则:
\n银行卡号码的校验采用Luhn算法,校验过程大致如下:
\n\n从右到左给卡号字符串编号,最右边第一位是1,最右边第二位是2,最右边第三位是3…. \n从右向左遍历,对每一位字符t执行第三个步骤,并将每一位的计算结果相加得到一个数s。 \n对每一位的计算规则:如果这一位是奇数位,则返回t本身,如果是偶数位,则先将t乘以2得到一个数n,如果n是一位数(小于10),直接返回n,否则将n的个位数和十位数相加返回。 \n如果s能够整除10,则此号码有效,否则号码无效。 \n \n因为最终的结果会对10取余来判断是否能够整除10,所以又叫做模10算法。\n当然,还是库比较香: bankcardinfo
\n\nsplitLines:将多行字符串拆分为行数组。 \n \n使用String.prototype.split()和正则表达式匹配换行符并创建一个数组。
\nconst splitLines = str => str.split(/\\r?\\n/ );\n\nsplitLines('This\\nis a\\nmultiline\\nstring.\\n' ); \n\n
\n\nstripHTMLTags:删除字符串中的HTMl标签 \n \n从字符串中删除HTML / XML标签。
\n使用正则表达式从字符串中删除HTML / XML 标记。
\nconst stripHTMLTags = str => str.replace(/<[^>]*>/g , '' );\n\nstripHTMLTags('<p><em>lorem</em> <strong>ipsum</strong></p>' ); \n\n
\n对象 \n\ndayOfYear:当前日期天数 \n \nconst dayOfYear = date => \n Math .floor((date - new Date (date.getFullYear(), 0 , 0 )) / 1000 / 60 / 60 / 24 );\n\ndayOfYear(new Date ()); \n\n
\n\nforOwn:迭代属性并执行回调 \n \nconst forOwn = (obj, fn ) => Object .keys(obj).forEach(key => fn(obj[key], key, obj));\nforOwn({ foo : 'bar' , a : 1 }, v => console .log(v)); \n\n
\n\nGet Time From Date:返回当前24小时制时间的字符串 \n \nconst getColonTimeFromDate = date => date.toTimeString().slice(0 , 8 );\n\ngetColonTimeFromDate(new Date ()); \n\n
\n\nGet Days Between Dates:返回日期间的天数 \n \nconst getDaysDiffBetweenDates = (dateInitial, dateFinal ) => \n (dateFinal - dateInitial) / (1000 * 3600 * 24 );\n \ngetDaysDiffBetweenDates(new Date ('2019-01-01' ), new Date ('2019-10-14' )); \n\n
\n\nis:检查值是否为特定类型。 \n \nconst is = (type, val ) => ![, null ].includes(val) && val.constructor === type;\n\nis(Array , [1 ]); \nis(ArrayBuffer , new ArrayBuffer ()); \nis(Map , new Map ()); \nis(RegExp , /./g); \nis(Set , new Set ()); \nis(WeakMap , new WeakMap ()); \nis(WeakSet , new WeakSet ()); \nis(String , '' ); \nis(String , new String ('' )); \nis(Number , 1 ); \nis(Number , new Number (1 )); \nis(Boolean , true ); \nis(Boolean , new Boolean (true )); \n\n
\n\nisAfterDate:检查是否在某日期后 \n \nconst isAfterDate = (dateA, dateB ) => dateA > dateB;\n\nisAfterDate(new Date (2010 , 10 , 21 ), new Date (2010 , 10 , 20 )); \n\n
\n\nisBeforeDate:检查是否在某日期前 \n \nconst isBeforeDate = (dateA, dateB ) => dateA < dateB;\n\nisBeforeDate(new Date (2010 , 10 , 20 ), new Date (2010 , 10 , 21 )); \n\n
\n\ntomorrow:获取明天的字符串格式时间 \n \n\nconst tomorrow = () => {\n let t = new Date ();\n t.setDate(t.getDate() + 1 );\n return t.toISOString().split('T' )[0 ];\n};\n\ntomorrow(); \n\n
\n\nequals:全等判断 \n \n在两个变量之间进行深度比较以确定它们是否全等。
\n此代码段精简的核心在于Array.prototype.every()的使用。
\nconst equals = (a, b ) => {\n if (a === b) return true ;\n if (a instanceof Date && b instanceof Date ) return a.getTime() === b.getTime();\n if (!a || !b || (typeof a !== 'object' && typeof b !== 'object' )) return a === b;\n if (a.prototype !== b.prototype) return false ;\n let keys = Object .keys(a);\n if (keys.length !== Object .keys(b).length) return false ;\n return keys.every(k => equals(a[k], b[k]));\n};\n\n
\n用法:
\nequals({ a : [2 , { e : 3 }], b : [4 ], c : 'foo' }, { a : [2 , { e : 3 }], b : [4 ], c : 'foo' }); \n\n
\n数字 \n\nrandomIntegerInRange:生成指定范围的随机整数 \n \nconst randomIntegerInRange = (min, max ) => Math .floor(Math .random() * (max - min + 1 )) + min;\n\nrandomIntegerInRange(0 , 5 ); \n\n
\n\nrandomNumberInRange:生成指定范围的随机小数 \n \nconst randomNumberInRange = (min, max ) => Math .random() * (max - min) + min;\n\nrandomNumberInRange(2 , 10 ); \n\n
\n\nround:四舍五入到指定位数 \n \nconst round = (n, decimals = 0 ) => Number (`${Math .round(`${n} e${decimals} ` )} e-${decimals} ` );\n\nround(1.005 , 2 ); \n\n
\n\nsum:计算数组或多个数字的总和 \n \n\nconst sum = (...arr ) => [...arr].reduce((acc, val ) => acc + val, 0 );\n\nsum(1 , 2 , 3 , 4 ); \nsum(...[1 , 2 , 3 , 4 ]); \n\n
\n\ntoCurrency:简单的货币单位转换 \n \nconst toCurrency = (n, curr, LanguageFormat = undefined ) => \n Intl .NumberFormat(LanguageFormat, { style : 'currency' , currency : curr }).format(n);\n \ntoCurrency(123456.789 , 'EUR' ); \ntoCurrency(123456.789 , 'USD' , 'en-us' ); \ntoCurrency(123456.789 , 'USD' , 'fa' ); \ntoCurrency(322342436423.2435 , 'JPY' ); \n\n
\n浏览器操作及其它 \n\nbottomVisible:检查页面底部是否可见 \n \nconst bottomVisible = () => \n document .documentElement.clientHeight + window .scrollY >=\n (document .documentElement.scrollHeight || document .documentElement.clientHeight);\n\nbottomVisible(); \n\n
\n\nCreate Directory:检查创建目录 \n \n此代码段调用fs模块的existsSync()检查目录是否存在,如果不存在,则mkdirSync()创建该目录。
\nconst fs = require ('fs' );\nconst createDirIfNotExists = dir => (!fs.existsSync(dir) ? fs.mkdirSync(dir) : undefined );\ncreateDirIfNotExists('test' ); \n\n
\n\ncurrentURL:返回当前链接url \n \nconst currentURL = () => window .location.href;\n\ncurrentURL(); \n\n
\n\ndistance:返回两点间的距离 \n \n该代码段通过计算欧几里得距离来返回两点之间的距离。
\nconst distance = (x0, y0, x1, y1 ) => Math .hypot(x1 - x0, y1 - y0);\n\ndistance(1 , 1 , 2 , 3 ); \n\n
\n\nelementContains:检查是否包含子元素\n此代码段检查父元素是否包含子元素。 \n \nconst elementContains = (parent, child ) => parent !== child && parent.contains(child);\n\nelementContains(document .querySelector('head' ), document .querySelector('title' )); \nelementContains(document .querySelector('body' ), document .querySelector('body' )); \n\n
\n\ngetStyle:返回指定元素的生效样式 \n \nconst getStyle = (el, ruleName ) => getComputedStyle(el)[ruleName];\n\ngetStyle(document .querySelector('p' ), 'font-size' ); \n\n
\n\ngetType:返回值或变量的类型名 \n \nconst getType = v => \n v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();\n \ngetType(new Set ([1 , 2 , 3 ])); \ngetType([1 , 2 , 3 ]); \n\n
\n\nhasClass:校验指定元素的类名 \n \nconst hasClass = (el, className ) => el.classList.contains(className);\nhasClass(document .querySelector('p.special' ), 'special' ); \n\n
\n\nhide:隐藏所有的指定标签 \n \nconst hide = (...el ) => [...el].forEach(e => (e.style.display = 'none' ));\n\nhide(document .querySelectorAll('img' )); \n\n
\n\nhttpsRedirect:HTTP 跳转 HTTPS \n \nconst httpsRedirect = () => {\n if (location.protocol !== 'https:' ) location.replace('https://' + location.href.split('//' )[1 ]);\n};\n\nhttpsRedirect(); \n\n
\n\ninsertAfter:在指定元素之后插入新元素 \n \nconst insertAfter = (el, htmlString ) => el.insertAdjacentHTML('afterend' , htmlString);\n\n\ninsertAfter(document .getElementById('myId' ), '<p>after</p>' ); \n\n
\n\ninsertBefore:在指定元素之前插入新元素 \n \nconst insertBefore = (el, htmlString ) => el.insertAdjacentHTML('beforebegin' , htmlString);\n\ninsertBefore(document .getElementById('myId' ), '<p>before</p>' ); \n\n
\n\nisBrowser:检查是否为浏览器环境 \n \n此代码段可用于确定当前运行时环境是否为浏览器。这有助于避免在服务器(节点)上运行前端模块时出错。
\nconst isBrowser = () => ![typeof window , typeof document ].includes('undefined' );\n\nisBrowser(); \nisBrowser(); \n\n
\n\nisBrowserTab:检查当前标签页是否活动 \n \nconst isBrowserTabFocused = () => !document .hidden;\n\nisBrowserTabFocused(); \n\n
\n\nnodeListToArray:转换nodeList为数组 \n \nconst nodeListToArray = nodeList => [...nodeList];\n\nnodeListToArray(document .childNodes); \n\n
\n\nRandom Hexadecimal Color Code:随机十六进制颜色 \n \n\nconst randomHexColorCode = () => {\n let n = (Math .random() * 0xfffff * 1000000 ).toString(16 );\n return '#' + n.slice(0 , 6 );\n};\n\nrandomHexColorCode(); \n\n
\n\nscrollToTop:平滑滚动至顶部 \n \nconst scrollToTop = () => {\n const c = document .documentElement.scrollTop || document .body.scrollTop;\n if (c > 0 ) {\n window .requestAnimationFrame(scrollToTop);\n window .scrollTo(0 , c - c / 8 );\n }\n};\n\nscrollToTop();\n\n
\n\nsmoothScroll:滚动到指定元素区域 \n \n该代码段可将指定元素平滑滚动到浏览器窗口的可见区域。
\nconst smoothScroll = element => \n document .querySelector(element).scrollIntoView({\n behavior : 'smooth' \n });\n \nsmoothScroll('#fooBar' ); \nsmoothScroll('.fooBar' ); \n\n
\n\ndetectDeviceType:检测移动/PC设备 \n \nconst detectDeviceType = () => \n /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i .test(navigator.userAgent)\n ? 'Mobile' \n : 'Desktop' ;\n\n
\n\ngetScrollPosition:返回当前的滚动位置 \n \n默认参数为window ,pageXOffset(pageYOffset)为第一选择,没有则用scrollLeft(scrollTop)
\nconst getScrollPosition = (el = window ) => ({\n x : el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,\n y : el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop\n});\n\ngetScrollPosition(); \n\n
\n\nsize:获取不同类型变量的字节长度 \n \n这个的实现非常巧妙,利用Blob类文件对象的特性,获取对象的长度。
\n另外,多重三元运算符,是真香。
\nconst size = val => \n Array .isArray(val)\n ? val.length\n : val && typeof val === 'object' \n ? val.size || val.length || Object .keys(val).length\n : typeof val === 'string' \n ? new Blob([val]).size\n : 0 ;\n\nsize([1 , 2 , 3 , 4 , 5 ]); \nsize('size' ); \nsize({ one : 1 , two : 2 , three : 3 }); \n\n\n
\n\nescapeHTML:转义HTML \n \n当然是用来防XSS攻击啦。
\nconst escapeHTML = str => \n str.replace(\n /[&<>'\"]/g ,\n tag =>\n ({\n '&' : '&' ,\n '<' : '<' ,\n '>' : '>' ,\n \"'\" : ''' ,\n '\"' : '"' \n }[tag] || tag)\n );\n\nescapeHTML('<a href=\"#\">Me & you</a>' ); \n\n
\n",
+ "link": "\\zh-cn\\blog\\web\\js_tool_method.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/web/react_interview.html b/zh-cn/blog/web/react_interview.html
new file mode 100644
index 0000000..24b34e6
--- /dev/null
+++ b/zh-cn/blog/web/react_interview.html
@@ -0,0 +1,845 @@
+
+
+
+
+
+
+
+
+
+ react_interview
+
+
+
+
+ React 面试问题
+
+如果你是一位有理想的前端开发人员,并且正在准备面试,那么这篇文章就是为你准备的。本文收集了 React 面试中最常见的 50 大问题,这是一份理想的指南,让你为 React 相关的面试做好充分的准备工作。首先我们快速了解一下 React 在市场上的需求和现状,然后再开始讨论 React 面试问题。
+
+JavaScript 工具的市场地位正在缓慢而稳定地上升当中,而对 React 认证的需求正在飞速增长。选择正确的技术来开发应用程序或网站变得愈加艰难。React 被认为是 Javascript 语言中增长最快的框架。
+虚拟 DOM 和可复用部件等独特特性吸引了前端开发人员的注意。尽管成熟的框架(如 Angular、Meteor 和 Vue 等)在 MVC(模型 - 视图 - 控制器)中只是一个“视图”库,但它们都有很强的竞争力。下图显示了常见 JS 框架的趋势:
+
+以下是面试官最有可能提出的 50 个面试问题和答案。
+React 面试问题——常规知识
+
+
+真实 DOM 和虚拟 DOM 的区别
+
+
+
+真实 DOM
+虚拟 DOM
+
+
+
+
+1.更新较慢
+1.更新较快
+
+
+2.可以直接更新 HTML
+2.不能直接更新 HTML
+
+
+3.元素更新时创建一个新 DOM
+3.元素更新时更新 JSX
+
+
+4.DOM 操作开销较大
+4.DOM 操作非常容易
+
+
+5.内存浪费严重
+5.没有内存浪费
+
+
+
+
+
+什么是 React?
+
+React 是 2011 年由 Facebook 开发的前端 JavaScript 库。
+它遵循基于组件的方法,这种方法可以用来构建可复用的 UI 组件。
+它用于复杂的交互式 Web 端和移动端用户界面开发。
+尽管它在 2015 年才开源,但得到了一家巨头的支持。
+
+
+
+React 的特点是什么?
+
+轻量级 DOM,以获得更好的性能。
+在 React 中,一切都被视为组件。
+React 使用 JSX(JavaScript eXtension),使我们可以编写类似于 HTML 的 JavaScript。
+React 不是直接运行在浏览器的文档对象模型(DOM)上,而是运行在虚拟 DOM 上。
+ReactJS 遵循单向数据流或单向数据绑定。
+
+
+
+列出 React 的一些主要优势。
+
+可以提高应用程序的性能。
+可以方便地用在客户端和服务端。
+由于有了 JSX,代码的可读性提高了。
+使用 React 后,编写 UI 测试用例变得非常容易。
+
+
+
+React 有哪些局限?
+
+React 只是一个库,而不是一个成熟的框架。
+它的库很大,需要花费一些时间来理解。
+新手程序员可能很难入门。
+由于它使用了内联模板和 JSX,编码也比较复杂。
+
+
+
+什么是 JSX?
+
+
+JSX 是 JavaScript XML 的简写。这是 React 使用的一种文件类型,具备 JavaScript 的表现力,并使用 HTML 作为模板语法。这样一来 HTML 文件理解起来就非常简单。这种文件可以创造稳健的应用程序并提高其效率。下面是一个 JSX 实例:
+render(){
+ return (
+ <div >
+ <h1 > Hello World from Codersera!!</h1 >
+ </div >
+ );
+ }
+
+
+你对虚拟 DOM 有什么了解?解释其工作机制。
+
+虚拟 DOM 是轻量级的 JavaScript 对象,一开始只是真实 DOM 的一个副本。它是一个节点树,将组件列为对象及其属性和内容的列表。React 的渲染功能从 React 的各个部分生成一个节点树。然后,它会根据由不同用户或系统行为引起的信息模型突变来更新此树。
+虚拟 DOM 的工作机制只有简单的三步组成。
+1. 每当任何基础信息更改时,整个 UI 就会以虚拟 DOM 的表示形式重新渲染。
+
+
+2. 然后计算先前的 DOM 表示和新的 DOM 表示之间的区别。
+
+
+3. 计算完成后,只有实际更改的内容才会更新到真实 DOM。
+
+
+
+为什么浏览器无法读取 JSX?
+
+React 使用 JSX(JavaScript eXtension),我们可以用它编写类似于 HTML 的 JavaScript。但由于 JSX 不是合法的 JavaScript,因此浏览器无法直接读取它。如果 JavaScript 文件包含 JSX,则必须将其转换。你需要一个转换器将 JSX 转换为浏览器可以理解的常规 Javascript。目前最常用的转换器是 Babel。
+
+与 ES5 相比,React 的 ES6 语法有何不同?
+
+ES5 和 ES6 的语法区别如下:
+
+var React = require ('react' );
+
+import React from 'react' ;
+
+******** export vs exports *********
+
+
+module .exports = Component;
+
+export default Component;
+
+****** function *****
+// ES5
+
+var MyComponent = React .createClass ({
+ render: function( ) {
+ return <h3 > Hello CoderSera! </h3 >
+ },
+});
+
+
+class MyComponent extends React .Component {
+ render() {
+ return <h3 > Hello CoderSera! </h3 >
+ }
+}
+
+ ******* props ******
+
+
+var App = React.createClass({
+ propTypes : { name : React.PropTypes.string },
+ render : function ( ) {
+ return <h3 > Hello, { this.props.name }! < /h3 >
+ },
+});
+
+
+class App extends React .Component {
+ render() {
+ return <h3 > Hello, { this.props.name }! </h3 >
+ }
+}
+
+ ****** state *****
+
+
+var App = React.createClass({
+ getInitialState : function ( ) {
+ return { name : 'world' };
+ }
+
+ render: function ( ) {
+ return <h3 > Hello, { this.state.name }! < /h3 > ;
+ },
+});
+
+
+class App extends React .Component {
+ constructor () {
+ super ();
+ this .state = { name : 'world' };
+ }
+
+ render() {
+ return <h3 > Hello, { this.state.name }! < /h3 >
+ }
+ render() {
+ return ;
+ <h3 > Hello, { this.state.name }! < /h3 >
+ }
+
+
+
+React 和 Angular 有何不同?
+
+
+
+
+React 对比 Angular
+React
+Angular
+
+
+
+
+架构
+使用虚拟 DOM
+使用真实 DOM
+
+
+渲染
+服务端渲染
+客户端渲染
+
+
+DOM
+使用虚拟 DOM
+使用真实 DOM
+
+
+数据绑定
+单向数据绑定
+双向数据绑定
+
+
+调试
+编译时调试
+运行时调试
+
+
+开发者
+Facebook
+谷歌
+
+
+
+
+如何理解“在 React 中,一切都是组件”。
+
+React 应用程序的 UI 构建块都是组件。这些部分将整个 UI 划分为许多可自治和可复用的微小部分。然后独立的某个部分发生变化就不会影响 UI 的其余部分。
+
+解释 React 中 render() 的目的。
+
+它被视为普通函数,但 render() 函数必须返回某些值,无论值是否为空。调用组件文件时默认会调用 render() 方法,因为组件需要显示 HTML 标记,或者我们可以说 JSX 语法。每个 React 组件必须有一个 render() 函数,它返回单个 React 元素,该元素代表原生 DOM 组件。如果需要渲染多个 HTML 元素,则必须将它们分组在一个封闭的标签内,如form、group和div等。此函数必须保持纯净,就是说它在每次调用时必须返回相同的结果。
+import React, { Component } from 'react' ;
+
+class App extends Component {
+ render() {
+ return (<div > <h1 className ='App-title' > hello CoderSera </h1 > </div > )
+ }
+}
+
+export default App;
+
+
+
+什么是 Hooks?
+
+Hooks 是一项新功能,使你无需编写类即可使用状态等 React 功能。来看一个 useState hook 示例。
+<em>import </em>{useState} <em>from </ em>'react' ;<br><br><em>function </em>Example() {<br> <em>// Declare a new
+
+
+什么是 props?
+
+
+
+React 中的状态是什么,如何使用?
+
+组件可以通过状态来跟踪其执行的任何渲染之间的信息。
+状态用于可变数据或将要更改的数据。这对于用户输入尤其方便。以搜索栏为例,用户输入数据时他们看到的内容也会更新。
+
+状态和 props 的区别。
+
+
+
+
+条件
+状态
+Props
+
+
+
+
+从父组件接收初始值
+是
+是
+
+
+父组件可以更改值
+否
+是
+
+
+在组件内设置默认值
+是
+是
+
+
+在组件内更改
+是
+否
+
+
+为子组件设置初始值
+是
+是
+
+
+在子组件内更改
+否
+是
+
+
+
+
+如何更新组件的状态?
+
+可以使用 this.setState() 更新组件的状态。
+class MyComponent extends React .Component {
+ constructor () {
+ super ();
+ this .state = {
+ name : 'Maxx' ,
+ id : '101'
+ }
+ }
+ render()
+ {
+ setTimeout(() => {this .setState({name :'Jaeha' , id :'222' })},2000 )
+ return (
+ <div >
+ <h1 > Hello {this.state.name}</h1 >
+ <h2 > Your Id is {this.state.id}</h2 >
+ </div >
+ );
+ }
+ }
+ ReactDOM.render(
+ <MyComponent /> , document .getElementById('content' )
+);
+
+
+18.React 中的箭头函数是什么?如何使用?
+粗箭头 => 用于定义匿名函数,这通常是将参数传递给回调函数的最简单方法。但是你需要在使用它时优化性能。注意:每次渲染组件时,在 render 方法中使用箭头函数都会创建一个新函数,这可能会影响性能。
+
+render() {
+ return (
+ <MyInput onChange={this.handleChange.bind(this) } />
+ );
+}
+//With Arrow Function
+render() {
+ return(
+ <MyInput onChange={ (e) => this.handleOnChange(e) } />
+ );
+}
+
+
+
+有状态和无状态组件的区别。
+
+
+
+
+有状态组件
+无状态组件
+
+
+
+
+在内存中存储组件状态更改的信息
+计算组件的内部状态
+
+
+有权更改状态
+无权更改状态
+
+
+包含过去、现在和可能的未来状态更改的信息
+没有包含关于状态更改的信息
+
+
+无状态组件通知它们关于状态更改的需求,然后它们将 props 传递给前者
+它们从有状态组件接收 props,将其视为回调函数
+
+
+
+
+React 组件的生命周期有哪些阶段?
+
+React 组件的生命周期分为三个不同阶段:
+
+
+详细解释 React 组件的生命周期技术。
+
+一些最重要的生命周期方法包括:
+
+
+componentWillMount()——在初始渲染发生之前,即在 React 将组件插入 DOM 之前立即调用一次。请务必注意,在此方法中调用 this.setState() 不会触发重新渲染。
+
+
+componentDidMount()——在渲染函数之后触发此方法。现在可以访问更新的 DOM,这意味着该方法是初始化其他需要访问 DOM 的 Javascript 库以及数据提取操作的最佳选择。
+
+
+componentWillReceiveProps()——componentWillReceiveProps() 在组件接收新 props 时调用。在调用 render() 方法之前,我们可以用这个方法对 prop 过渡做出反应。在此函数中调用 this.setState() 不会触发额外的重新渲染,我们可以通过 this.props 访问旧的 props。
+
+
+shouldComponentUpdate()——我们可以用它来决定下一个组件的状态是否应触发重新渲染。此方法返回一个布尔值,默认为 true。但是我们可以返回 false,并且不会调用以下方法:
+
+
+componentWillUpdate()——当接收到新的 props 或状态时,在渲染(更新)之前立即调用此方法。我们可以用它在更新之前做准备,但是不允许使用 this.setState()。
+
+
+componentDidUpdate()——React 更新 DOM 后立即调用此方法。我们可以使用此方法与更新后的 DOM 交互,或执行任何渲染后操作。
+
+
+componentWillUnmount()——从 DOM 卸载组件之前立即调用此方法。我们可以用它执行可能需要的任何清理工作。
+
+
+
+React 中的事件是什么?
+
+在 React 中,事件是对特定动作(如鼠标悬停、鼠标单击和按键等)触发的反应。处理这些事件类似于处理 DOM 元素上的事件。但是在语法上存在一些差异,例如:
+
+
+事件命名使用驼峰式大小写,而不是仅使用小写字母。
+
+
+事件作为函数而不是字符串传递。
+
+
+事件参数包含一组特定于事件的属性。每个事件类型都包含它自己的属性和行为,这些属性和行为只能通过它的事件处理程序访问。
+
+如何在 React 中创建事件?
+
+class Display extends React .Component ( {
+ show(evt) {
+
+ },
+ render() {
+
+ return (
+ <div onClick ={this.show} > Click Me!</div >
+ );
+ }
+});
+
+
+
+
+什么是 React 中的合成事件?
+
+合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同的浏览器行为合并为一个 API。这样做是为了确保在各个浏览器的事件中显示一致的特征。
+
+如何理解 React 中的引用?
+
+Ref 是 React 引用的简写。它是一个属性,帮助存储对特定元素或组件的引用,由组件渲染的配置函数返回。它用于返回对渲染返回的特定元素或组件的引用。当我们需要 DOM 测量或向组件添加方法时,它们会派上用场。
+class ReferenceDemo extends React .Component {
+ display() {
+ const name = this .inputDemo.value;
+ document .getElementById('disp' ).innerHTML = name;
+ }
+ render() {
+ return (
+ <div >
+ Name: <input type ="text" ref ={input => this.inputDemo = input} />
+ <button name ="Click" onClick ={this.display} > Click</button >
+
+ <h2 > Hello <span id ="disp" > </span > !!!</h2 >
+ </div >
+ );
+ }
+ }
+
+
+
+列出一些应该使用引用的情况?
+
+以下是应使用 ref 的情况:
+
+
+当你需要管理焦点、选择文本或媒体播放时。
+
+
+触发命令式动画。
+
+
+与第三方 DOM 库集成。
+
+
+
+如何模块化 React 代码?
+
+可以使用 export 和 import 属性来模块化软件。它们有助于在不同的文档中单独编写组件。
+
+export default class ChildComponent extends React .Component {
+ render() {
+ return ( <div >
+ <h1 > This is a child component</h1 >
+ </div > )
+ }
+}
+
+
+import ChildComponent from './childcomponent.js' ;
+class ParentComponent extends React .Component {
+ render() {
+ return (
+ <div >
+ <App />
+ </div >
+ );
+ }
+}
+
+
+
+在 React 中如何创建表单?
+
+React 提供了一种有状态的,响应式的方法来构建表单。与其他 DOM 元素不同,HTML 表单元素在 React 中的工作机制有所不同。例如,表单数据通常由组件而不是 DOM 处理,并且通常使用受控组件来实现。
+区别在于可以使用回调函数来处理表单事件,然后使用容器的状态存储表单数据。这使你的组件可以更好地控制表单控制元素和表单数据。
+回调函数是在发生事件(包括更改表单控制值或表单提交)时触发的。
+handleSubmit(event) {
+ alert('A name was submitted: ' + this .state.value);
+ event.preventDefault();
+}
+
+render() {
+ return (
+
+
+
+<form onSubmit={this.handleSubmit}>
+ <label>
+ Name:
+ <input type="text" value={this.state.value} onChange={this.handleSubmit} />
+ </label>
+ <input type="submit" value="Submit" />
+ </form>
+
+
+
+ );
+}
+
+
+
+如何理解受控和非受控组件?
+
+
+
+
+受控组件
+非受控组件
+
+
+
+
+它们不维护自己的状态
+它们维护自己的状态
+
+
+数据由父组件控制
+数据由 DOM 控制
+
+
+它们通过 props 获得当前值,然后通过回调通知更改
+使用引用来获得它们的当前值
+
+
+
+
+高阶组件(HOC)是什么意思?
+
+React 中的高阶组件是一种在组件之间共享通用功能而无需重复代码的模式。
+高阶组件实际上不是组件,它是一个接受组件并返回新组件的函数。它将一个组件转换为另一个组件,并添加其他数据或功能
+
+HOC 可以做什么?
+
+HOC 可用于许多任务,例如:
+
+
+代码复用,逻辑和引导抽象。
+
+
+渲染劫持。
+
+
+状态抽象和控制。
+
+
+Props 操控。
+
+
+
+什么是纯组件?
+
+React 并没有在我们的组件中编写 shouldComponent 方法,而是引入了一个带有内置 shouldComponentUpdate 实现的新组件,它是 React.PureComponent 组件。
+React.PureComponent 通过浅层 prop 和状态比较来实现它。在某些情况下,你可以使用 React.PureComponent 来提高性能。
+
+React 中键有什么用途?
+
+键可帮助 React 识别哪些项目已更改、添加或删除。应该为数组内的元素提供键,以赋予元素稳定的身份。键必须是唯一的。
+当使用动态创建的组件或用户更改列表时,React 键非常有用。设置键值后,更改后的组件就能保持唯一标识。
+
+MVC 框架的主要问题有哪些?
+
+以下是 MVC 框架的一些主要问题:
+
+MVC 不能解决代码复杂性问题。它也不能解决代码复用或灵活性问题。
+它不保证解耦代码。
+
+
+你对 Flux 有什么了解?
+
+Flux 是 Facebook 内部与 React 搭配使用的架构。它不是框架或库。只是一种新型的体系结构,是对 React 和单向数据流概念的补充:
+Flux 的各个组成部分如下:
+
+动作(Actions)——帮助数据传递到调度器的辅助方法。
+调度器(Dispatcher)——接收动作并将负载广播到已注册的回调。
+存储(Stores)——具有已注册到调度器的回调的应用程序状态和逻辑的容器。
+控制器视图(Controller Views)——从组件中获取状态并通过 props 传递给子组件的 React 组件。
+
+
+
+什么是 Redux?
+
+Redux 是一种状态管理工具。尽管它主要与 React 搭配使用,但也可以与其他任何 JavaScript 框架或库搭配。
+Redux 允许你在一个称为存储(Store)的对象中管理整个应用程序状态。
+对存储的更新将触发与存储的已更新部分连接的组件的重新渲染。当我们想要更新某些东西时,我们称之为动作(Action)。我们还创建函数来处理这些动作并返回更新的存储。这些函数称为 Reducer。
+
+Redux 遵循的三大原则是什么?
+
+
+
+单一可信来源:整个应用程序的状态存储在单个存储区中的对象 / 状态树中。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
+
+
+状态是只读的:更改状态的唯一方法是触发动作。动作是描述更改的普通 JS 对象。就像状态是数据的最小表示一样,动作是数据更改的最小表示。
+
+
+使用纯函数更改:为了确认动作是如何转换状态树的,你需要纯函数。纯函数是返回值仅取决于其参数值的函数。
+
+
+
+如何理解“单一可信源”?
+
+单一可信源(SSOT)是构造信息模型和相关数据模式的实践,其中每个数据元素都只能在一个地方掌握(或编辑)
+Redux 使用“存储”将应用程序的整个状态存储在一个位置。因此,组件的所有状态都存储在存储中,并且存储本身会接收更新。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
+
+列出 Redux 的组件。
+
+Redux 由以下组件组成:
+
+动作——这是一个描述发生了什么的对象。
+Reducer——确定状态如何变化的地方。
+存储——整个应用程序的状态 / 对象树保存在存储中。
+视图——仅显示存储提供的数据。
+
+
+数据在 Redux 中是如何流动的?
+
+
+
+在 Redux 中如何定义动作?
+
+React 中的动作必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,你也可以为其添加更多属性。在 Redux 中使用称为“动作创建者”的函数来创建动作。以下是动作和动作创建者的示例:
+function addTodo (text ) {
+ return {
+ type : ADD_TODO,
+ text
+ }
+}
+
+
+
+说明 Reducer 的作用。
+
+Reducer 是用于指示 ACTION 反应中应用程序状态变化的简单功能。它接收先前的状态和动作,然后返回新的状态。它根据动作类型确定需要哪种更新,然后返回新值。如果没有要完成的工作,它将按原样返回先前状态。
+
+在 Redux 中存储的用途是什么?
+
+存储是一个 JavaScript 对象,可以保存应用程序的状态,并提供一些辅助方法来访问状态、调度动作并记录侦听器。应用程序的整个状态 / 对象树存储在单个存储中。因此 Redux 非常容易理解且可预测。我们可以将中间件转移到存储,以管理数据处理任务,并维护更改存储状态的各种活动的日志。通过 Reducer,所有活动都返回新的状态。
+
+Redux 与 Flux 有何不同?
+
+
+
+
+Flux
+Redux
+
+
+
+
+存储包括状态和更改逻辑
+存储和更改逻辑是分离的
+
+
+有多个存储
+只有一个存储
+
+
+所有存储不互通,是平行的
+带有分层 Reducer 的单个存储
+
+
+有单个调度器
+没有调度器的概念
+
+
+React 组件订阅到存储
+容器组件是有联系的
+
+
+状态是可变的
+状态是不可变的
+
+
+
+
+Redux 有哪些优势?
+
+Redux 的优点如下:
+
+结果的可预测性——由于总是有单一可信源,比如存储,因此当前状态与动作及应用程序的其他部分同步时不会出现混乱。
+可维护性——代码易于维护,具有可预测的结果和严格的结构。
+服务端渲染——你只需将在服务器上创建的存储传递给客户端即可。这对于初始渲染非常有用,并优化了应用程序性能,提供了更好的用户体验。
+开发人员工具——从动作到状态更改,开发人员可以利用这些工具实时跟踪应用程序中发生的所有事情。
+社区和生态系统——Redux 背后拥有巨大的社区,用起来更加便利。大批优秀的开发者为库的发展做出了贡献,并开发了很多应用程序。
+易于测试——Redux 的代码主要是较小的、纯净的和孤立的函数。这使代码可测试且独立。
+组织——Redux 精确地规定了代码的组织方式,这使得团队合作时代码更加一致,更容易理解。
+
+
+什么是 React Router?
+
+React Router 是建立在 React 之上的功能强大的路由库。它使 URL 与网页上显示的数据保持同步。它保持标准化的结构和行为,可用于开发单页 Web 应用程序。React Router 有一个简单的 API。React Router 提供了一种方法,只会显示你的应用中路由匹配你的定义的那些组件 。
+
+为什么在 React Router v4 中使用 switch 关键字?
+
+在 Switch 组件内,Route 和Redirect 组件嵌套在内部。从 Switch 顶部的 Route/Redirect 组件开始到底部的 Route/Redirect,根据浏览器中当前的 URL 是否与 Route/Redirect 组件的 prop/ 路径匹配,将每个组件评估为 true 或 false。
+Switch 只会渲染第一个匹配的子级。当我们嵌套了下面这样的路由时真的很方便:
+<Switch>
+ <Route path ="/accounts/new" component ={AddForm} />
+ <Route path ={ `/accounts /:accountId `} component ={Profile} />
+</Switch >
+
+
+
+为什么我们在 React 中需要一个路由器?
+
+路由器用于定义多个路由,并且当用户键入特定的 URL 时,如果该 URL 与路由器内部定义的任何“路由”的路径匹配,则该用户将被重定向到该路由。因此我们需要在应用程序中添加一个路由器库,以允许创建多个路由,每个路由都为我们指向一个独特的视图。
+从 React Router 包导入的组件有两个属性,一个是将用户引导到指定路径的 path,另一个是用于定义所述路径中内容的 component。
+<switch >
+ <route exact path=’/’ component={Home}/>
+ <route path=’/posts/:id’ component={Newpost}/>
+ <route path=’/posts’ component={Post}/>
+</switch>
+
+
+
+列出 React Router 的优点。
+
+几个优点是:
+
+
+就像 React 基于组件的理念一样,在 React Router v4 中 API 是“完全组件化的”。路由器可以可视化为单个根组件(),其中包含特定的子路由()。
+
+
+无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在组件中。
+
+
+包是拆分的:三个包分别用于 Web、Native 和 Core。这使我们的应用更加紧凑。它们的编码样式类似,所以很容易来回切换。
+
+
+React Router 与传统路由有何不同?
+
+
+
+
+
+~
+传统路由
+React 路由
+
+
+
+
+参与的页面
+每个视图对应一个新页面
+只涉及单个 HTML 页面
+
+
+URL 更改
+向服务器发送一个 HTTP 请求并接收对应的 HTML 页面
+只有历史属性被更改
+
+
+体验
+用户其实是在每个视图的不同页面间切换
+用户以为自己正在不同的页面间切换
+
+
+
+原文链接: https://codersera.com/blog/top-50-react-questions-you-need-to-prepare-for-the-interview-in-2019/
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/web/react_interview.json b/zh-cn/blog/web/react_interview.json
new file mode 100644
index 0000000..c20c004
--- /dev/null
+++ b/zh-cn/blog/web/react_interview.json
@@ -0,0 +1,6 @@
+{
+ "filename": "react_interview.md",
+ "__html": "React 面试问题 \n\n如果你是一位有理想的前端开发人员,并且正在准备面试,那么这篇文章就是为你准备的。本文收集了 React 面试中最常见的 50 大问题,这是一份理想的指南,让你为 React 相关的面试做好充分的准备工作。首先我们快速了解一下 React 在市场上的需求和现状,然后再开始讨论 React 面试问题。
\n \nJavaScript 工具的市场地位正在缓慢而稳定地上升当中,而对 React 认证的需求正在飞速增长。选择正确的技术来开发应用程序或网站变得愈加艰难。React 被认为是 Javascript 语言中增长最快的框架。
\n虚拟 DOM 和可复用部件等独特特性吸引了前端开发人员的注意。尽管成熟的框架(如 Angular、Meteor 和 Vue 等)在 MVC(模型 - 视图 - 控制器)中只是一个“视图”库,但它们都有很强的竞争力。下图显示了常见 JS 框架的趋势:
\n
\n以下是面试官最有可能提出的 50 个面试问题和答案。
\nReact 面试问题——常规知识 \n\n\n真实 DOM 和虚拟 DOM 的区别
\n\n\n\n真实 DOM \n虚拟 DOM \n \n \n\n\n1.更新较慢 \n1.更新较快 \n \n\n2.可以直接更新 HTML \n2.不能直接更新 HTML \n \n\n3.元素更新时创建一个新 DOM \n3.元素更新时更新 JSX \n \n\n4.DOM 操作开销较大 \n4.DOM 操作非常容易 \n \n\n5.内存浪费严重 \n5.没有内存浪费 \n \n \n
\n \n\n什么是 React?
\n\nReact 是 2011 年由 Facebook 开发的前端 JavaScript 库。 \n它遵循基于组件的方法,这种方法可以用来构建可复用的 UI 组件。 \n它用于复杂的交互式 Web 端和移动端用户界面开发。 \n尽管它在 2015 年才开源,但得到了一家巨头的支持。 \n \n \n\nReact 的特点是什么?
\n\n轻量级 DOM,以获得更好的性能。 \n在 React 中,一切都被视为组件。 \nReact 使用 JSX(JavaScript eXtension),使我们可以编写类似于 HTML 的 JavaScript。 \nReact 不是直接运行在浏览器的文档对象模型(DOM)上,而是运行在虚拟 DOM 上。 \nReactJS 遵循单向数据流或单向数据绑定。 \n \n \n\n列出 React 的一些主要优势。
\n\n可以提高应用程序的性能。 \n可以方便地用在客户端和服务端。 \n由于有了 JSX,代码的可读性提高了。 \n使用 React 后,编写 UI 测试用例变得非常容易。 \n \n \n\nReact 有哪些局限?
\n\nReact 只是一个库,而不是一个成熟的框架。 \n它的库很大,需要花费一些时间来理解。 \n新手程序员可能很难入门。 \n由于它使用了内联模板和 JSX,编码也比较复杂。 \n \n \n\n什么是 JSX?
\n \n \nJSX 是 JavaScript XML 的简写。这是 React 使用的一种文件类型,具备 JavaScript 的表现力,并使用 HTML 作为模板语法。这样一来 HTML 文件理解起来就非常简单。这种文件可以创造稳健的应用程序并提高其效率。下面是一个 JSX 实例:
\nrender(){\n return ( \n <div > \n <h1 > Hello World from Codersera!!</h1 > \n </div > \n );\n }\n
\n\n你对虚拟 DOM 有什么了解?解释其工作机制。 \n \n虚拟 DOM 是轻量级的 JavaScript 对象,一开始只是真实 DOM 的一个副本。它是一个节点树,将组件列为对象及其属性和内容的列表。React 的渲染功能从 React 的各个部分生成一个节点树。然后,它会根据由不同用户或系统行为引起的信息模型突变来更新此树。\n虚拟 DOM 的工作机制只有简单的三步组成。
\n1. 每当任何基础信息更改时,整个 UI 就会以虚拟 DOM 的表示形式重新渲染。\n
\n
\n2. 然后计算先前的 DOM 表示和新的 DOM 表示之间的区别。\n
\n
\n3. 计算完成后,只有实际更改的内容才会更新到真实 DOM。\n
\n
\n\n为什么浏览器无法读取 JSX? \n \nReact 使用 JSX(JavaScript eXtension),我们可以用它编写类似于 HTML 的 JavaScript。但由于 JSX 不是合法的 JavaScript,因此浏览器无法直接读取它。如果 JavaScript 文件包含 JSX,则必须将其转换。你需要一个转换器将 JSX 转换为浏览器可以理解的常规 Javascript。目前最常用的转换器是 Babel。
\n\n与 ES5 相比,React 的 ES6 语法有何不同? \n \nES5 和 ES6 的语法区别如下:
\n\nvar React = require ('react' );\n\nimport React from 'react' ;\n \n******** export vs exports *********\n \n\nmodule .exports = Component;\n\nexport default Component;\n \n****** function *****\n// ES5 \n \nvar MyComponent = React .createClass ({\n render: function( ) {\n return <h3 > Hello CoderSera! </h3 > \n },\n});\n \n\nclass MyComponent extends React .Component {\n render() {\n return <h3 > Hello CoderSera! </h3 > \n }\n}\n \n ******* props ******\n \n\nvar App = React.createClass({\n propTypes : { name : React.PropTypes.string },\n render : function ( ) {\n return <h3 > Hello, { this.props.name }! < /h3 > \n },\n});\n \n\nclass App extends React .Component {\n render() {\n return <h3 > Hello, { this.props.name }! </h3 > \n }\n}\n \n ****** state *****\n \n\nvar App = React.createClass({\n getInitialState : function ( ) {\n return { name : 'world' };\n } \n \n render: function ( ) {\n return <h3 > Hello, { this.state.name }! < /h3 > ;\n },\n});\n \n\nclass App extends React .Component {\n constructor () {\n super ();\n this .state = { name : 'world' };\n }\n \n render() {\n return <h3 > Hello, { this.state.name }! < /h3 > \n }\n render() {\n return ;\n <h3 > Hello, { this.state.name }! < /h3 > \n }\n\n
\n\nReact 和 Angular 有何不同? \n \n\n\n\nReact 对比 Angular \nReact \nAngular \n \n \n\n\n架构 \n使用虚拟 DOM \n使用真实 DOM \n \n\n渲染 \n服务端渲染 \n客户端渲染 \n \n\nDOM \n使用虚拟 DOM \n使用真实 DOM \n \n\n数据绑定 \n单向数据绑定 \n双向数据绑定 \n \n\n调试 \n编译时调试 \n运行时调试 \n \n\n开发者 \nFacebook \n谷歌 \n \n \n
\n\n如何理解“在 React 中,一切都是组件”。 \n \nReact 应用程序的 UI 构建块都是组件。这些部分将整个 UI 划分为许多可自治和可复用的微小部分。然后独立的某个部分发生变化就不会影响 UI 的其余部分。
\n\n解释 React 中 render() 的目的。 \n \n它被视为普通函数,但 render() 函数必须返回某些值,无论值是否为空。调用组件文件时默认会调用 render() 方法,因为组件需要显示 HTML 标记,或者我们可以说 JSX 语法。每个 React 组件必须有一个 render() 函数,它返回单个 React 元素,该元素代表原生 DOM 组件。如果需要渲染多个 HTML 元素,则必须将它们分组在一个封闭的标签内,如form、group和div等。此函数必须保持纯净,就是说它在每次调用时必须返回相同的结果。
\nimport React, { Component } from 'react' ;\n \nclass App extends Component {\n render() {\n return (<div > <h1 className ='App-title' > hello CoderSera </h1 > </div > )\n }\n}\n \nexport default App;\n\n
\n\n什么是 Hooks? \n \nHooks 是一项新功能,使你无需编写类即可使用状态等 React 功能。来看一个 useState hook 示例。
\n<em>import </em>{useState} <em>from </ em>'react' ;<br><br><em>function </em>Example() {<br> <em>// Declare a new\n
\n\n什么是 props? \n \n\n\nReact 中的状态是什么,如何使用? \n \n组件可以通过状态来跟踪其执行的任何渲染之间的信息。
\n状态用于可变数据或将要更改的数据。这对于用户输入尤其方便。以搜索栏为例,用户输入数据时他们看到的内容也会更新。
\n\n状态和 props 的区别。 \n \n\n\n\n条件 \n状态 \nProps \n \n \n\n\n从父组件接收初始值 \n是 \n是 \n \n\n父组件可以更改值 \n否 \n是 \n \n\n在组件内设置默认值 \n是 \n是 \n \n\n在组件内更改 \n是 \n否 \n \n\n为子组件设置初始值 \n是 \n是 \n \n\n在子组件内更改 \n否 \n是 \n \n \n
\n\n如何更新组件的状态? \n \n可以使用 this.setState() 更新组件的状态。
\nclass MyComponent extends React .Component {\n constructor () {\n super ();\n this .state = {\n name : 'Maxx' ,\n id : '101' \n }\n }\n render()\n {\n setTimeout(() => {this .setState({name :'Jaeha' , id :'222' })},2000 )\n return ( \n <div > \n <h1 > Hello {this.state.name}</h1 > \n <h2 > Your Id is {this.state.id}</h2 > \n </div > \n );\n }\n }\n ReactDOM.render(\n <MyComponent /> , document .getElementById('content' )\n);\n\n
\n18.React 中的箭头函数是什么?如何使用?
\n粗箭头 => 用于定义匿名函数,这通常是将参数传递给回调函数的最简单方法。但是你需要在使用它时优化性能。注意:每次渲染组件时,在 render 方法中使用箭头函数都会创建一个新函数,这可能会影响性能。
\n\nrender() { \n return (\n <MyInput onChange={this.handleChange.bind(this) } />\n );\n}\n//With Arrow Function\nrender() { \n return(\n <MyInput onChange={ (e) => this.handleOnChange(e) } />\n );\n}\n\n
\n\n有状态和无状态组件的区别。 \n \n\n\n\n有状态组件 \n无状态组件 \n \n \n\n\n在内存中存储组件状态更改的信息 \n计算组件的内部状态 \n \n\n有权更改状态 \n无权更改状态 \n \n\n包含过去、现在和可能的未来状态更改的信息 \n没有包含关于状态更改的信息 \n \n\n无状态组件通知它们关于状态更改的需求,然后它们将 props 传递给前者 \n它们从有状态组件接收 props,将其视为回调函数 \n \n \n
\n\nReact 组件的生命周期有哪些阶段? \n \nReact 组件的生命周期分为三个不同阶段:
\n\n\n详细解释 React 组件的生命周期技术。 \n \n一些最重要的生命周期方法包括:
\n\n\ncomponentWillMount()——在初始渲染发生之前,即在 React 将组件插入 DOM 之前立即调用一次。请务必注意,在此方法中调用 this.setState() 不会触发重新渲染。
\n \n\ncomponentDidMount()——在渲染函数之后触发此方法。现在可以访问更新的 DOM,这意味着该方法是初始化其他需要访问 DOM 的 Javascript 库以及数据提取操作的最佳选择。
\n \n\ncomponentWillReceiveProps()——componentWillReceiveProps() 在组件接收新 props 时调用。在调用 render() 方法之前,我们可以用这个方法对 prop 过渡做出反应。在此函数中调用 this.setState() 不会触发额外的重新渲染,我们可以通过 this.props 访问旧的 props。
\n \n\nshouldComponentUpdate()——我们可以用它来决定下一个组件的状态是否应触发重新渲染。此方法返回一个布尔值,默认为 true。但是我们可以返回 false,并且不会调用以下方法:
\n \n\ncomponentWillUpdate()——当接收到新的 props 或状态时,在渲染(更新)之前立即调用此方法。我们可以用它在更新之前做准备,但是不允许使用 this.setState()。
\n \n\ncomponentDidUpdate()——React 更新 DOM 后立即调用此方法。我们可以使用此方法与更新后的 DOM 交互,或执行任何渲染后操作。
\n \n\ncomponentWillUnmount()——从 DOM 卸载组件之前立即调用此方法。我们可以用它执行可能需要的任何清理工作。
\n \n \n\nReact 中的事件是什么? \n \n在 React 中,事件是对特定动作(如鼠标悬停、鼠标单击和按键等)触发的反应。处理这些事件类似于处理 DOM 元素上的事件。但是在语法上存在一些差异,例如:
\n\n\n事件命名使用驼峰式大小写,而不是仅使用小写字母。
\n \n\n事件作为函数而不是字符串传递。
\n \n \n事件参数包含一组特定于事件的属性。每个事件类型都包含它自己的属性和行为,这些属性和行为只能通过它的事件处理程序访问。
\n\n如何在 React 中创建事件? \n \nclass Display extends React .Component ( { \n show(evt) {\n \n }, \n render() { \n \n return ( \n <div onClick ={this.show} > Click Me!</div > \n ); \n }\n});\n\n\n
\n\n什么是 React 中的合成事件? \n \n合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同的浏览器行为合并为一个 API。这样做是为了确保在各个浏览器的事件中显示一致的特征。
\n\n如何理解 React 中的引用? \n \nRef 是 React 引用的简写。它是一个属性,帮助存储对特定元素或组件的引用,由组件渲染的配置函数返回。它用于返回对渲染返回的特定元素或组件的引用。当我们需要 DOM 测量或向组件添加方法时,它们会派上用场。
\nclass ReferenceDemo extends React .Component {\n display() {\n const name = this .inputDemo.value;\n document .getElementById('disp' ).innerHTML = name;\n }\n render() {\n return ( \n <div > \n Name: <input type =\"text\" ref ={input => this.inputDemo = input} />\n <button name =\"Click\" onClick ={this.display} > Click</button > \n \n <h2 > Hello <span id =\"disp\" > </span > !!!</h2 > \n </div > \n );\n }\n }\n\n
\n\n列出一些应该使用引用的情况? \n \n以下是应使用 ref 的情况:
\n\n\n当你需要管理焦点、选择文本或媒体播放时。
\n \n\n触发命令式动画。
\n \n\n与第三方 DOM 库集成。
\n \n \n\n如何模块化 React 代码? \n \n可以使用 export 和 import 属性来模块化软件。它们有助于在不同的文档中单独编写组件。
\n\nexport default class ChildComponent extends React .Component {\n render() {\n return ( <div > \n <h1 > This is a child component</h1 > \n </div > )\n }\n}\n \n\nimport ChildComponent from './childcomponent.js' ;\nclass ParentComponent extends React .Component { \n render() { \n return ( \n <div > \n <App /> \n </div > \n ); \n }\n}\n\n
\n\n在 React 中如何创建表单? \n \nReact 提供了一种有状态的,响应式的方法来构建表单。与其他 DOM 元素不同,HTML 表单元素在 React 中的工作机制有所不同。例如,表单数据通常由组件而不是 DOM 处理,并且通常使用受控组件来实现。\n区别在于可以使用回调函数来处理表单事件,然后使用容器的状态存储表单数据。这使你的组件可以更好地控制表单控制元素和表单数据。\n回调函数是在发生事件(包括更改表单控制值或表单提交)时触发的。
\nhandleSubmit(event) {\n alert('A name was submitted: ' + this .state.value);\n event.preventDefault();\n}\n \nrender() {\n return ( \n \n \n \n<form onSubmit={this.handleSubmit}>\n <label>\n Name:\n <input type=\"text\" value={this.state.value} onChange={this.handleSubmit} />\n </label>\n <input type=\"submit\" value=\"Submit\" />\n </form>\n \n \n \n );\n}\n\n
\n\n如何理解受控和非受控组件? \n \n\n\n\n受控组件 \n非受控组件 \n \n \n\n\n它们不维护自己的状态 \n它们维护自己的状态 \n \n\n数据由父组件控制 \n数据由 DOM 控制 \n \n\n它们通过 props 获得当前值,然后通过回调通知更改 \n使用引用来获得它们的当前值 \n \n \n
\n\n高阶组件(HOC)是什么意思? \n \nReact 中的高阶组件是一种在组件之间共享通用功能而无需重复代码的模式。\n高阶组件实际上不是组件,它是一个接受组件并返回新组件的函数。它将一个组件转换为另一个组件,并添加其他数据或功能
\n\nHOC 可以做什么? \n \nHOC 可用于许多任务,例如:
\n\n\n代码复用,逻辑和引导抽象。
\n \n\n渲染劫持。
\n \n\n状态抽象和控制。
\n \n\nProps 操控。
\n \n \n\n什么是纯组件? \n \nReact 并没有在我们的组件中编写 shouldComponent 方法,而是引入了一个带有内置 shouldComponentUpdate 实现的新组件,它是 React.PureComponent 组件。\nReact.PureComponent 通过浅层 prop 和状态比较来实现它。在某些情况下,你可以使用 React.PureComponent 来提高性能。
\n\nReact 中键有什么用途? \n \n键可帮助 React 识别哪些项目已更改、添加或删除。应该为数组内的元素提供键,以赋予元素稳定的身份。键必须是唯一的。\n当使用动态创建的组件或用户更改列表时,React 键非常有用。设置键值后,更改后的组件就能保持唯一标识。
\n\nMVC 框架的主要问题有哪些? \n \n以下是 MVC 框架的一些主要问题:
\n\nMVC 不能解决代码复杂性问题。它也不能解决代码复用或灵活性问题。 \n它不保证解耦代码。 \n \n\n你对 Flux 有什么了解? \n \nFlux 是 Facebook 内部与 React 搭配使用的架构。它不是框架或库。只是一种新型的体系结构,是对 React 和单向数据流概念的补充:
\nFlux 的各个组成部分如下:
\n\n动作(Actions)——帮助数据传递到调度器的辅助方法。 \n调度器(Dispatcher)——接收动作并将负载广播到已注册的回调。 \n存储(Stores)——具有已注册到调度器的回调的应用程序状态和逻辑的容器。 \n控制器视图(Controller Views)——从组件中获取状态并通过 props 传递给子组件的 React 组件。 \n \n
\n\n什么是 Redux? \n \nRedux 是一种状态管理工具。尽管它主要与 React 搭配使用,但也可以与其他任何 JavaScript 框架或库搭配。
\nRedux 允许你在一个称为存储(Store)的对象中管理整个应用程序状态。
\n对存储的更新将触发与存储的已更新部分连接的组件的重新渲染。当我们想要更新某些东西时,我们称之为动作(Action)。我们还创建函数来处理这些动作并返回更新的存储。这些函数称为 Reducer。
\n\nRedux 遵循的三大原则是什么? \n \n\n\n单一可信来源:整个应用程序的状态存储在单个存储区中的对象 / 状态树中。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
\n \n\n状态是只读的:更改状态的唯一方法是触发动作。动作是描述更改的普通 JS 对象。就像状态是数据的最小表示一样,动作是数据更改的最小表示。
\n \n\n使用纯函数更改:为了确认动作是如何转换状态树的,你需要纯函数。纯函数是返回值仅取决于其参数值的函数。
\n \n \n\n如何理解“单一可信源”? \n \n单一可信源(SSOT)是构造信息模型和相关数据模式的实践,其中每个数据元素都只能在一个地方掌握(或编辑)\nRedux 使用“存储”将应用程序的整个状态存储在一个位置。因此,组件的所有状态都存储在存储中,并且存储本身会接收更新。单一状态树使我们能更容易地跟踪历史更改,更方便地调试或检查应用程序。
\n\n列出 Redux 的组件。 \n \nRedux 由以下组件组成:
\n\n动作——这是一个描述发生了什么的对象。 \nReducer——确定状态如何变化的地方。 \n存储——整个应用程序的状态 / 对象树保存在存储中。 \n视图——仅显示存储提供的数据。 \n \n\n数据在 Redux 中是如何流动的? \n \n
\n\n在 Redux 中如何定义动作? \n \nReact 中的动作必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,你也可以为其添加更多属性。在 Redux 中使用称为“动作创建者”的函数来创建动作。以下是动作和动作创建者的示例:
\nfunction addTodo (text ) {\n return {\n type : ADD_TODO, \n text \n }\n}\n\n
\n\n说明 Reducer 的作用。 \n \nReducer 是用于指示 ACTION 反应中应用程序状态变化的简单功能。它接收先前的状态和动作,然后返回新的状态。它根据动作类型确定需要哪种更新,然后返回新值。如果没有要完成的工作,它将按原样返回先前状态。
\n\n在 Redux 中存储的用途是什么? \n \n存储是一个 JavaScript 对象,可以保存应用程序的状态,并提供一些辅助方法来访问状态、调度动作并记录侦听器。应用程序的整个状态 / 对象树存储在单个存储中。因此 Redux 非常容易理解且可预测。我们可以将中间件转移到存储,以管理数据处理任务,并维护更改存储状态的各种活动的日志。通过 Reducer,所有活动都返回新的状态。
\n\nRedux 与 Flux 有何不同? \n \n\n\n\nFlux \nRedux \n \n \n\n\n存储包括状态和更改逻辑 \n存储和更改逻辑是分离的 \n \n\n有多个存储 \n只有一个存储 \n \n\n所有存储不互通,是平行的 \n带有分层 Reducer 的单个存储 \n \n\n有单个调度器 \n没有调度器的概念 \n \n\nReact 组件订阅到存储 \n容器组件是有联系的 \n \n\n状态是可变的 \n状态是不可变的 \n \n \n
\n\nRedux 有哪些优势? \n \nRedux 的优点如下:
\n\n结果的可预测性——由于总是有单一可信源,比如存储,因此当前状态与动作及应用程序的其他部分同步时不会出现混乱。 \n可维护性——代码易于维护,具有可预测的结果和严格的结构。 \n服务端渲染——你只需将在服务器上创建的存储传递给客户端即可。这对于初始渲染非常有用,并优化了应用程序性能,提供了更好的用户体验。 \n开发人员工具——从动作到状态更改,开发人员可以利用这些工具实时跟踪应用程序中发生的所有事情。 \n社区和生态系统——Redux 背后拥有巨大的社区,用起来更加便利。大批优秀的开发者为库的发展做出了贡献,并开发了很多应用程序。 \n易于测试——Redux 的代码主要是较小的、纯净的和孤立的函数。这使代码可测试且独立。 \n组织——Redux 精确地规定了代码的组织方式,这使得团队合作时代码更加一致,更容易理解。 \n \n\n什么是 React Router? \n \nReact Router 是建立在 React 之上的功能强大的路由库。它使 URL 与网页上显示的数据保持同步。它保持标准化的结构和行为,可用于开发单页 Web 应用程序。React Router 有一个简单的 API。React Router 提供了一种方法,只会显示你的应用中路由匹配你的定义的那些组件 。
\n\n为什么在 React Router v4 中使用 switch 关键字? \n \n在 Switch 组件内,Route 和Redirect 组件嵌套在内部。从 Switch 顶部的 Route/Redirect 组件开始到底部的 Route/Redirect,根据浏览器中当前的 URL 是否与 Route/Redirect 组件的 prop/ 路径匹配,将每个组件评估为 true 或 false。\nSwitch 只会渲染第一个匹配的子级。当我们嵌套了下面这样的路由时真的很方便:
\n<Switch>\n <Route path =\"/accounts/new\" component ={AddForm} /> \n <Route path ={ `/accounts /:accountId `} component ={Profile} /> \n</Switch > \n\n
\n\n为什么我们在 React 中需要一个路由器? \n \n路由器用于定义多个路由,并且当用户键入特定的 URL 时,如果该 URL 与路由器内部定义的任何“路由”的路径匹配,则该用户将被重定向到该路由。因此我们需要在应用程序中添加一个路由器库,以允许创建多个路由,每个路由都为我们指向一个独特的视图。
\n从 React Router 包导入的组件有两个属性,一个是将用户引导到指定路径的 path,另一个是用于定义所述路径中内容的 component。
\n<switch >\n <route exact path=’/’ component={Home}/>\n <route path=’/posts/:id’ component={Newpost}/>\n <route path=’/posts’ component={Post}/>\n</switch>\n\n
\n\n列出 React Router 的优点。 \n \n几个优点是:
\n\n\n就像 React 基于组件的理念一样,在 React Router v4 中 API 是“完全组件化的”。路由器可以可视化为单个根组件(),其中包含特定的子路由()。
\n \n\n无需手动设置历史值:在 React Router v4 中,我们要做的就是将路由包装在组件中。
\n \n\n包是拆分的:三个包分别用于 Web、Native 和 Core。这使我们的应用更加紧凑。它们的编码样式类似,所以很容易来回切换。
\n \n\nReact Router 与传统路由有何不同?
\n \n \n\n\n\n~ \n传统路由 \nReact 路由 \n \n \n\n\n参与的页面 \n每个视图对应一个新页面 \n只涉及单个 HTML 页面 \n \n\nURL 更改 \n向服务器发送一个 HTTP 请求并接收对应的 HTML 页面 \n只有历史属性被更改 \n \n\n体验 \n用户其实是在每个视图的不同页面间切换 \n用户以为自己正在不同的页面间切换 \n \n \n
\n原文链接: https://codersera.com/blog/top-50-react-questions-you-need-to-prepare-for-the-interview-in-2019/
\n",
+ "link": "\\zh-cn\\blog\\web\\react_interview.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/web/vue_cp_react.html b/zh-cn/blog/web/vue_cp_react.html
new file mode 100644
index 0000000..a0b95b4
--- /dev/null
+++ b/zh-cn/blog/web/vue_cp_react.html
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+ vue_cp_react
+
+
+
+
+ 前端框架用vue还是react?清晰对比两者差异
+前言
+近两年前端技术层出不穷,目前市面上已经有了很多供前端人员使用的开发框架,转眼19年已过大半,前端框架领域日趋成熟,实现了三足鼎立的局面,截止到10月22日,Angular,react和vue数据统计如下图所示:
+
+最近在学习使用框架的时候,分别使用vue和react开发了两个移动端产品,对这两个框架的学习曲线有了一些感悟,这两个都是现在比较热门的js框架,它俩在使用方式上和学习复杂度上还是有很大区别的,这里简单总结下两者的差异。
+主要从以下几个方面入手方面展开:
+
+框架的诞生
+设计思想
+编写语法
+脚手架构建工具
+数据绑定
+虚拟DOM
+指令
+性能优化
+原生渲染native
+ssr服务端渲染
+生命周期函数
+销毁组件
+状态集管理工具
+
+诞生
+vue
+vue由尤雨溪开发,由独立团队维护,现在大部分的子项目都交给团队成员打理,Vue核心库依然主要由尤雨溪亲自维护。vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它有一个特性是虚拟DOM,从性能上碾轧angularJS,这个时候,vue1.0悄悄的问世了,它的优雅,轻便也吸引了一部分用户,开始受到关注,16年中旬,VUE2.0问世,不管从性能上,还是从成本上都隐隐超过了react,火的一塌糊涂,这个时候,angular开发团队也开发了angular2.0版本,并且更名为angular,吸收了react、vue的优点,加上angular本身的特点,也吸引到很多用户,目前已经迭代到8.0了。友情提示注意下vue的诞生时间,如果正好有小伙伴在面试,被问到你是从什么时候开始接触并且使用vue的,你要是回答用了5、6年了那场面就十分尴尬了。
+react
+起初facebook在建设instagram(图片分享)的时候,因为牵扯到一个东西叫数据流,那为了处理数据流并且还要考虑好性能方面的问题,Facebook开始对市场上的各种前端MVC框架去进行一个研究,然而并没有看上眼的,于是Facebook觉得,还是自己开发一个才是最棒的,那么他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,他们就自己开发了一套,果然大牛创造力还是很强大的。
+React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
+设计思想
+vue
+vue的官网中说它是一款渐进式框架,采用自底向上增量开发的设计。这里我们需要明确一个概念,什么是渐进式框架。在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统(components)、客户端路由(vue-router)、大规模状态管理(vuex)来构建一个完整的框架。Vue从设计角度来讲,虽然能够涵盖所有这些内容,但是你并不需要一上手就把所有东西全用上,因为没有必要。无论从学习角度,还是实际情况,这都是可选的。声明式渲染和组建系统是Vue的核心库所包含内容,而客户端路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。
+react
+react主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以手动实现,比如借助 onChange 和 setState 来实现一个双向的数据流。而vue是基于可变数据的,支持双向绑定,它提供了v-model这样的指令来实现文本框的数据流双向绑定。
+编写语法
+vue
+vue推荐的做法是webpack+vue-loader的单文件组件格式,vue保留了html、css、js分离的写法,使得现有的前端开发者在开发的时候能保持原有的习惯,更接近常用的web开发方式,模板就是普通的html,数据绑定使用mustache风格,样式直接使用css。其中style 标签还提供了一个可选的scoped属性,它会为组件内 CSS 指定作用域,用它来控制仅对当前组件有效还是全局生效。
+模板和JSX是各有利弊的东西。模板更贴近我们的HTML,可以让我们更直观地思考语义结构,更好地结合CSS的书写。
+同时vue也支持JSX语法,因为是真正的JavaScript,拥有这个语言本身的所有的能力,可以进行复杂的逻辑判断,进行选择性的返回最终要返回的DOM结构,能够实现一些在模板的语法限制下,很难做到的一些事情。
+react
+用过react的开发者可能知道,react是没有模板的,直接就是一个渲染函数,它中间返回的就是一个虚拟DOM树,React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'。JSX实际就是一套使用XML语法,用于让我们更简单地去描述树状结构的语法糖。在react中,所有的组件的渲染功能都依靠JSX。你可以在render()中编写类似XML的语法,它最终会被编译成原生JavaScript。不仅仅是 HTML 可以用 JSX 来表达,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理。JSX是基于 JS 之上的一套额外语法,学习使用起来有一定的成本。
+构建工具
+vue
+vue提供了CLI 脚手架,可以帮助你非常容易地构建项目。全局安装之后,我们就可以用 vue create命令创建一个新的项目,vue 的 CLI 跟其他 CLI不同之处在于,有多个可选模板,有简单的也有复杂的,可以让用户自定义选择需要安装的模块,还可以将你的选择保存成模板,便于后续使用。
+极简的配置,更快的安装,可以更快的上手。它也有一个更完整的模板,包括单元测试在内的各种内容都涵盖,但是,它的复杂度也更高,这又涉及到根据用例来选择恰当复杂度的问题。
+react
+React 在这方面也提供了 create-react-app,但是现在还存在一些局限性:
+
+它不允许在项目生成时进行任何配置,而 Vue CLI 运行于可升级的运行时依赖之上,该运行时可以通过插件进行扩展。
+它只提供一个构建单页面应用的默认选项,而 Vue 提供了各种用途的模板。
+它不能用用户自建的预设配置构建项目,这对企业环境下预先建立约定是特别有用的。
+
+而要注意的是这些限制是故意设计的,这有它的优势。例如,如果你的项目需求非常简单,你就不需要自定义生成过程。你能把它作为一个依赖来更新。
+数据绑定
+vue
+vue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。
+Vue采用数据劫持&发布-订阅模式的方式,vue在创建vm的时候,会将数据配置在实例当中,然后通过Object.defineProperty对数据进行操作,为数据动态添加了getter与setter方法,当获取数据的时候会触发对应的getter方法,当设置数据的时候会触发对应的setter方法,从而进一步触发vm的watcher方法,然后数据更改,vm则会进一步触发视图更新操作。
+react
+react是单向数据流,react中属性是不允许更改的,状态是允许更改的。react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。在setState中,传入一个对象,就会将组件的状态中键值对的部分更改,还可以传入一个函数,这个回调函数必须向上面方式一样的一个对象函数可以接受prevState和props。通过调用this.setState去更新this.state,不能直接操作this.state,请把它当成不可变的。
+调用setState更新this.state,它不是马上就会生效的,它是异步的。所以不要认为调用完setState后可以立马获取到最新的值。多个顺序执行的setState不是同步的一个接着一个的执行,会加入一个异步队列,然后最后一起执行,即批处理。
+setState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。
+diff算法
+vue
+vue中diff算法实现流程
+在内存中构建虚拟dom树
+将内存中虚拟dom树渲染成真实dom结构
+数据改变的时候,将之前的虚拟dom树结合新的数据生成新的虚拟dom树
+将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对),来更新只需要被替换的DOM,而不是全部重绘。在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。
+会将对比出来的差异进行重新渲染
+react
+react中diff算法实现流程
+DOM结构发生改变-----直接卸载并重新create
+DOM结构一样-----不会卸载,但是会update变化的内容
+所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点
+(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)
+React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点。
+vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。
+指令
+指令 (Directives) 是带有
+v- 前缀的特殊特性,指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
+vue
+vue中提供很多内部指令供我们使用,它可以让我们进行一些模板的操作,例如有时候,我们的data中的存放的数据不是个简单的数字或者字符串,而是数组Array类型,这个时候,我们要把数组的元素展示在视图上,就需要用到vue提供的 v-for 指令,来实现列表的渲染。
+react
+因为react中没有v-for指令,所以循环渲染的时候需要用到map()方法来渲染视图,并且将符合条件的元素放入一个新数组返回。
+性能优化
+vue
+vue中的每个组件内部自动实现了
+shouldComponentUpdate的优化,在vue里面由于依赖追踪系统的存在,当任意数据变动的时,Vue的每一个组件都精确地知道自己是否需要重绘,所以并不需要手动优化。用vue渲染这些组件的时候,数据变了,对应的组件基本上去除了手动优化的必要性。而在react中我们需要手动去优化其性能,但是当数据特别多的时候vue中的watcher也会特别多,从而造成页面卡顿,所以一般数据比较多的大型项目会倾向于使用react。在react官网中,官方也建议我们使用React来构建快速响应的大型 Web 应用程序。
+react
+当props或state发生改变的时候会触发
+shouldComponentUpdate生命周期函数,它是用来控制组件是否被重新渲染的,如果它返回true,则执行render函数,更新组件;如果它返回false,则不会触发重新渲染的过程。
+有的时候我们希望它在更新之前,和之前的状态进行一个对比,这个时候我们就需要重写
+shouldComponentUpdate来避免不必要的dom操作,对比当前的props或state和更新之后的nextProps或nextState,返回true时 ,组件更新;返回false,则不会更新,节省性能。
+shouldComponentUpdate(nextProps, nextState) {
+ if (this .props.a !== nextProps.a) {
+ return true ;
+ }
+ if (this .state.b !== nextState.b) {
+ return true ;
+ }
+ return false ;
+}
+
+我们也可以创建一个继承React.PureComponent的React组件,它自带
+shouldComponentUpdate,可以对props进行浅比较,发现更新之后的props与当前的props一样,就不会进行render了。
+classTestextendsReact.PureComponent{constructor(props){super(props);}render(){return
hello...{this.props.a}
}}
+由于React.PureComponent进行的是浅比较,也就是说它只会对比原对象的值是否相同,当我们的props或state为数组或者对象这种引用类型的时候,我们修改它的数值,由于数据引用指针没有发生改变,所以组件也是不会重新渲染的。这个时候我们就需要进行深拷贝,创建一个新的对象或数组,将原对象的各项属性的"值"(数组的所有元素)拷贝过来,是"值"而不仅仅是"引用地址"。我们可以使用slice()方法:
+ew_state.todos = new_state.todos.slice();
+
+或者引入immutable库来实现数据不可变。
+原生渲染native
+native指的是使用原生API来开发App,比如ios使用OC语言,android使用java。
+vue
+vue和Weex进行官方合作,weex是阿里巴巴发起的跨平台用户界面开发框架,它的思想是多个平台,只写一套代码,weex允许你使用 vue 语法开发不仅仅可以运行在浏览器端,还能被用于开发 iOS 和 Android 上的原生应用的组件。即只需要编写一份代码,即可运行在Web、iOS、Android上。
+weex相对来说上手比较简单,安装vue-cli之后就可以使用,学习门槛低,但是它的社区目前还处于成长期,react native的社区非常成熟活跃,有非常丰富的组件可供扩展。
+react
+react native是Facebook在2015年3月在F8开发者大会上开源的跨平台UI框架,需针对iOS、Android不同编写2份代码,使用react native需要按照文档安装配置很多依赖的工具,相对比较麻烦。weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架。
+weex的目标在于抹平各个平台的差异性,从而简化应用开发。而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题。
+ssr服务端渲染
+服务端渲染核心在于方便seo优化,后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完整的html页面,再直接返回给浏览器,以便用户浏览。
+vue
+2016 年 10 月 25 日,zeit.co背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js。
+服务端渲染支持流式渲染,因为HTTP请求也是流式,Vue 的服务端渲染结果可以直接 pipe 到返回的请求里面。这样一来,就可以更早地在浏览器中呈现给用户内容,通过合理的缓存策略,可以有效地提升服务端渲染的性能。
+
+基于 Vue.js
+自动代码分层
+服务端渲染
+强大的路由功能,支持异步数据
+静态文件服务
+ES2015+ 语法支持
+打包和压缩 JS 和 CSS
+HTML 头部标签管理
+本地开发支持热加载
+集成 ESLint
+支持各种样式预处理器: SASS、LESS、 Stylus 等等
+支持 HTTP/2 推送
+
+react
+Next是一个React框架,允许使用React构建SSR和静态web应用
+
+服务器渲染,获取数据非常简单
+无需学习新框架,支持静态导出。
+支持CSS-in-JS库
+自动代码拆分,加快页面加载速度,不加载不必要的代码
+基于Webpack的开发环境,支持模块热更新(HMR)
+支持Babel和Webpack自定义配置服务器、路由和next插件。
+能够部署在任何能运行node的平台
+内置页面搜索引擎优化(SEO)处理
+在生产环境下,打包文件体积更小,运行速度更快
+
+生命周期
+vue
+【初始化阶段(4个)】
+(1)beforeCreate
+此钩子函数不能获取到数据,dom元素也没有渲染出来,此钩子函数不会用来做什么事情。
+(2)created
+此钩子函数,数据已经挂载了,但是dom节点还是没有渲染出来,在这个钩子函数里面,如果同步更改数据的话,不会影响运行中钩子函数的执行。可以用来发送ajax请求,也可以做一些初始化事件的相关操作。
+(3)beforeMount
+代表dom节点马上要被渲染出来了,但是还没有真正的渲染出来,此钩子函数跟created钩子函数基本一样,也可以做一些初始化数据的配置。
+(4)mounted
+是生命周期初始化阶段的最后一个钩子函数,数据已经挂载完毕了,真实dom也可以获取到了。
+【运行中阶段(2个)】
+(5)beforeUpdate
+运行中钩子函数beforeUpdate默认是不会执行的,当数据更改的时候,才会执行。数据更新的时候,先调用beforeUpdate,然后数据更新引发视图渲染完成之后,再会执行updated。运行时beforeUpdate这个钩子函数获取的数据还是更新之前的数据(获取的是更新前的dom内容),在这个钩子函数里面,千万不能对数据进行更改,会造成死循环。
+(6)updated
+这个钩子函数获取的数据是更新后的数据,生成新的虚拟dom,跟上一次的虚拟dom结构进行比较,比较出来差异(diff算法)后再渲染真实dom,当数据引发dom重新渲染的时候,在updated钩子函数里面就可以获取最新的真实dom了。
+【销毁阶段(2个)】
+(7)beforeDestroy
+切换路由的时候,组件就会被销毁了,销毁之前执行beforeDestroy。在这个钩子函数里面,我们可以做一些善后的操作,例如可以清空一下全局的定时器(created钩子函数绑定的初始化阶段的事件)、清除事件绑定。
+(8)destoryed
+组件销毁后执行destroyed,销毁后组件的双向数据绑定、事件监听watcher相关的都被移除掉了,但是组件的真实dom结构还是存在在页面中的。
+添加keep-alive标签后会增加active和deactive这两个生命周期函数,初始化操作放在actived里面,一旦切换组件,因为组件没有被销毁,所以它不会执行销毁阶段的钩子函数,所以移除操作需要放在deactived里面,在里面进行一些善后操作,这个时候created钩子函数只会执行一次,销毁的钩子函数一直没有执行。
+
+react
+【初始化阶段(5个)】:
+(1)getDefaultProps:实例化组件之后,组件的getDefaultProps钩子函数会执行
+这个钩子函数的目的是为组件的实例挂载默认的属性
+这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,创建出所有实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性
+理论上来说,写成函数返回对象的方式,是为了防止实例共享,但是react专门为了让实例共享,只能让这个函数只执行一次
+组件间共享默认属性会减少内存空间的浪费,而且也不需要担心某一个实例更改属性后其他的实例也会更改的问题,因为组件不能自己更改属性,而且默认属性的优先级低。
+(2)getInitialState:为实例挂载初始状态,且每次实例化都会执行,也就是说,每一个组件实例都拥有自己独立的状态。
+(3)componentWillMount:执行componentWillMount,相当于Vue里的created+beforeMount,这里是在渲染之前最后一次更改数据的机会,在这里更改的话是不会触发render的重新执行。
+(4)render:渲染dom
+render()方法必须是一个纯函数,他不应该改变
+state,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果
+shouldComponentUpdate()返回
+false,
+render()不会被调用。
+(5)componentDidMount:相当于Vue里的mounted,多用于操作真实dom
+【运行中阶段(5个)】
+当组件mount到页面中之后,就进入了运行中阶段,在这里有5个钩子函数,但是这5个函数只有在数据(属性、状态)发送改变的时候才会执行
+(1)componentWillReceiveProps(nextProps,nextState)
+当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行。初始化props时候不会主动执行
+当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数还没有同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态。
+(2)接下来就会执行shouldComponentUpdate(nextProps,nextState),这个函数的作用:当属性或状态发生改变后控制组件是否要更新,提高性能,返回true就更新,否则不更新,默认返回true。
+接收nextProp、nextState,根据根据新属性状态和原属性状态作出对比、判断后控制是否更新
+如果
+shouldComponentUpdate()返回
+false,
+componentWillUpdate,
+render和
+componentDidUpdate不会被调用。
+(3)componentWillUpdate,在这里,组件马上就要重新render了,多做一些准备工作,千万千万,不要在这里修改状态,否则会死循环 相当于Vue中的beforeUpdate
+(4)render,重新渲染dom
+(5)componentDidUpdate,在这里,新的dom结构已经诞生了,相当于Vue里的updated
+【销毁阶段】
+当组件被销毁之前的一刹那,会触发componentWillUnmount,临死前的挣扎
+相当于Vue里的beforeDestroy,所以说一般会做一些善后的事情,例如使定时器无效,取消网络请求或清理在
+componentDidMount中创建的任何监听。
+
+销毁组件
+vue
+vue在调用$destroy方法的时候就会执行beforeDestroy生命周期函数,然后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说如果想要对残留的dom结构进行处理必须在destroyed生命周期函数中处理。
+react
+react执行完componentWillUnmount之后把事件、数据、dom都全部处理掉了,也就是说当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件相当于被销毁,所以根本不需要其他的钩子函数了。react销毁组件的时候,会将组件的dom结构也移除,vue则不然,在调用destory方法销毁组件的时候,组件的dom结构还是存在于页面中的,this.$destory组件结构还是存在的,只是移除了事件监听,所以这就是为什么vue中有destroyed,而react却没有componentDidUnmount。
+状态集管理工具
+vue
+vuex是一个专门为vue构建的状态集管理工具,vue和react都是基于组件化开发的,项目中包含很多的组件,组件都会有组件嵌套,想让组件中的数据被其他组件也可以访问到就需要使用到Vuex。
+vuex的流程
+将需要共享的状态挂载到state上:this.$store.state来调用
+创建store,将状态挂载到state上,在根实例里面配置store,之后我们在组件中就可以通过this.$store.state来使用state中管理的数据,但是这样使用时,当state的数据更改的时候,vue组件并不会重新渲染,所以我们要通过计算属性computed来使用,但是当我们使用多个数据的时候这种写法比较麻烦,vuex提供了mapState辅助函数,帮助我们在组件中获取并使用vuex的store中保存的状态。
+我们通过getters来创建状态:通过this.$store.getters来调用
+可以根据某一个状态派生出一个新状态,vuex也提供了mapGetters辅助函数来帮助我们在组件中使用getters里的状态。
+使用mutations来更改state:通过this.$store.commit来调用
+我们不能直接在组件中更改state,而是需要使用mutations来更改,mutations也是一个纯对象,里面包含很多更改state的方法,这些方法的形参接收到state,在函数体里更改,这时,组件用到的数据也会更改,实现响应式。vuex提供了mapMutations方法来帮助我们在组件中调用mutations 的方法。
+使用actions来处理异步操作:this.$store.dispatch来调用
+Actions类似于mutations,不同在于:Actions提交的是mutations,而不是直接变更状态。Actions可以包含任意异步操作。也就是说,如果有这样的需求:在一个异步操作处理之后,更改状态,我们在组件中应该先调用actions,来进行异步动作,然后由actions调用mutations来更改数据。在组件中通过this.$store.dispatch方法调用actions的方法,当然也可以使用mapMutations来辅助使用。
+react
+2015年Redux出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。它的出现主要是为解决react中组件之间的通信问题。建议把数据放入到redux中管理,目的就是方便数据统一,好管理。项目一旦出现问题,可以直接定位问题点。组件扩展的时候,后续涉及到传递的问题。本来的话,组件使用自己的数据,但是后来公用组件,还需要考虑如何值传递,在redux中可以存储至少5G以上的数据。
+redux的流程
+
+
+创建store: 从redux工具中取出createStore去生成一个store。
+创建一个reducer,然后将其传入到createStore中辅助store的创建。 reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就3是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态。 想要给store创建默认状态其实就是给reducer一个参数创建默认值。
+组件通过调用store.getState方法来使用store中的state,挂载在了自己的状态上。
+组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer
+reducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变, reducer返回什么状态,store.getState就可以获取什么状态。
+我们可以在组件中,利用store.subscribe方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态。
+
+小结
+vue和react的核心都是专注于轻量级的视图层,虽然只是解决一个很小的问题,但是它们庞大的生态圈提供了丰富的配套工具,一开始它并不会给你提供全套的配置方案,将所有的功能都一次性给你打包好,它只会给你提供一些简单的核心功能,当你需要做一个更复杂的应用时,再增添相应的工具。例如做一个单页应用的时候才需要用路由;做一个相当庞大的应用,涉及到多组件状态共享以及多个开发者共同协作时,才可能需要大规模状态管理方案。
+框架的存在就是为了帮助我们应对不同的项目复杂度,当我们面对一个大型、复杂的开发项目时,使用太简陋的工具会极大的降低开发人员的生产力,影响工作效率,框架的诞生就是在这些工程中提取一些重复的并且已经受过验证的模式,抽象到一个已经帮你设计好的API封装当中,帮助我们去应对不同复杂度的问题。所以在开发的过程中,选择一个合适的框架就会事半功倍。但是,框架本身也有复杂度,有些框架会让人一时不知如何上手。当你接到一个并不复杂的需求,却使用了很复杂的框架,那么就相当于杀鸡用牛刀,会遇到工具复杂度所带来的副作用,不仅会失去工具本身所带来优势,还会增加各种问题,例如学习成本、上手成本,以及实际开发效率等。
+所以并不是说做得少的框架就不如做的做的框架,每个框架都有各自的优势和劣势,并不能找到完全符合需求的框架,最重要的适合当前项目,目前两大框架的生态圈一片繁荣,react社区是当前最活跃的,最快的时候三天更新一个版本,一个问题可能存在几十种不同的解决方案,这就需要我们前端人员去在不同的功能之间做取舍,以后前端框架的发展方向应该是小而精、灵活以及开放的,核心功能+生态附加库可以帮我们更加灵活的构建项目,为了跟上前进的脚步,就需要不停的吸收最新的内容,这也是从事前端开发领域的一大乐趣,希望大家都能在学习中获得长足的进步。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zh-cn/blog/web/vue_cp_react.json b/zh-cn/blog/web/vue_cp_react.json
new file mode 100644
index 0000000..a92408f
--- /dev/null
+++ b/zh-cn/blog/web/vue_cp_react.json
@@ -0,0 +1,6 @@
+{
+ "filename": "vue_cp_react.md",
+ "__html": "前端框架用vue还是react?清晰对比两者差异 \n前言 \n近两年前端技术层出不穷,目前市面上已经有了很多供前端人员使用的开发框架,转眼19年已过大半,前端框架领域日趋成熟,实现了三足鼎立的局面,截止到10月22日,Angular,react和vue数据统计如下图所示:
\n
\n最近在学习使用框架的时候,分别使用vue和react开发了两个移动端产品,对这两个框架的学习曲线有了一些感悟,这两个都是现在比较热门的js框架,它俩在使用方式上和学习复杂度上还是有很大区别的,这里简单总结下两者的差异。\n主要从以下几个方面入手方面展开:
\n\n框架的诞生 \n设计思想 \n编写语法 \n脚手架构建工具 \n数据绑定 \n虚拟DOM \n指令 \n性能优化 \n原生渲染native \nssr服务端渲染 \n生命周期函数 \n销毁组件 \n状态集管理工具 \n \n诞生 \nvue \nvue由尤雨溪开发,由独立团队维护,现在大部分的子项目都交给团队成员打理,Vue核心库依然主要由尤雨溪亲自维护。vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它有一个特性是虚拟DOM,从性能上碾轧angularJS,这个时候,vue1.0悄悄的问世了,它的优雅,轻便也吸引了一部分用户,开始受到关注,16年中旬,VUE2.0问世,不管从性能上,还是从成本上都隐隐超过了react,火的一塌糊涂,这个时候,angular开发团队也开发了angular2.0版本,并且更名为angular,吸收了react、vue的优点,加上angular本身的特点,也吸引到很多用户,目前已经迭代到8.0了。友情提示注意下vue的诞生时间,如果正好有小伙伴在面试,被问到你是从什么时候开始接触并且使用vue的,你要是回答用了5、6年了那场面就十分尴尬了。
\nreact \n起初facebook在建设instagram(图片分享)的时候,因为牵扯到一个东西叫数据流,那为了处理数据流并且还要考虑好性能方面的问题,Facebook开始对市场上的各种前端MVC框架去进行一个研究,然而并没有看上眼的,于是Facebook觉得,还是自己开发一个才是最棒的,那么他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,他们就自己开发了一套,果然大牛创造力还是很强大的。\nReact 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
\n设计思想 \nvue \nvue的官网中说它是一款渐进式框架,采用自底向上增量开发的设计。这里我们需要明确一个概念,什么是渐进式框架。在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统(components)、客户端路由(vue-router)、大规模状态管理(vuex)来构建一个完整的框架。Vue从设计角度来讲,虽然能够涵盖所有这些内容,但是你并不需要一上手就把所有东西全用上,因为没有必要。无论从学习角度,还是实际情况,这都是可选的。声明式渲染和组建系统是Vue的核心库所包含内容,而客户端路由、状态管理、构建工具都有专门解决方案。这些解决方案相互独立,你可以在核心的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。
\nreact \nreact主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以手动实现,比如借助 onChange 和 setState 来实现一个双向的数据流。而vue是基于可变数据的,支持双向绑定,它提供了v-model这样的指令来实现文本框的数据流双向绑定。
\n编写语法 \nvue \nvue推荐的做法是webpack+vue-loader的单文件组件格式,vue保留了html、css、js分离的写法,使得现有的前端开发者在开发的时候能保持原有的习惯,更接近常用的web开发方式,模板就是普通的html,数据绑定使用mustache风格,样式直接使用css。其中style 标签还提供了一个可选的scoped属性,它会为组件内 CSS 指定作用域,用它来控制仅对当前组件有效还是全局生效。\n模板和JSX是各有利弊的东西。模板更贴近我们的HTML,可以让我们更直观地思考语义结构,更好地结合CSS的书写。\n同时vue也支持JSX语法,因为是真正的JavaScript,拥有这个语言本身的所有的能力,可以进行复杂的逻辑判断,进行选择性的返回最终要返回的DOM结构,能够实现一些在模板的语法限制下,很难做到的一些事情。
\nreact \n用过react的开发者可能知道,react是没有模板的,直接就是一个渲染函数,它中间返回的就是一个虚拟DOM树,React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'。JSX实际就是一套使用XML语法,用于让我们更简单地去描述树状结构的语法糖。在react中,所有的组件的渲染功能都依靠JSX。你可以在render()中编写类似XML的语法,它最终会被编译成原生JavaScript。不仅仅是 HTML 可以用 JSX 来表达,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理。JSX是基于 JS 之上的一套额外语法,学习使用起来有一定的成本。
\n构建工具 \nvue \nvue提供了CLI 脚手架,可以帮助你非常容易地构建项目。全局安装之后,我们就可以用 vue create命令创建一个新的项目,vue 的 CLI 跟其他 CLI不同之处在于,有多个可选模板,有简单的也有复杂的,可以让用户自定义选择需要安装的模块,还可以将你的选择保存成模板,便于后续使用。\n极简的配置,更快的安装,可以更快的上手。它也有一个更完整的模板,包括单元测试在内的各种内容都涵盖,但是,它的复杂度也更高,这又涉及到根据用例来选择恰当复杂度的问题。
\nreact \nReact 在这方面也提供了 create-react-app,但是现在还存在一些局限性:
\n\n它不允许在项目生成时进行任何配置,而 Vue CLI 运行于可升级的运行时依赖之上,该运行时可以通过插件进行扩展。 \n它只提供一个构建单页面应用的默认选项,而 Vue 提供了各种用途的模板。 \n它不能用用户自建的预设配置构建项目,这对企业环境下预先建立约定是特别有用的。 \n \n而要注意的是这些限制是故意设计的,这有它的优势。例如,如果你的项目需求非常简单,你就不需要自定义生成过程。你能把它作为一个依赖来更新。
\n数据绑定 \nvue \nvue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。\nVue采用数据劫持&发布-订阅模式的方式,vue在创建vm的时候,会将数据配置在实例当中,然后通过Object.defineProperty对数据进行操作,为数据动态添加了getter与setter方法,当获取数据的时候会触发对应的getter方法,当设置数据的时候会触发对应的setter方法,从而进一步触发vm的watcher方法,然后数据更改,vm则会进一步触发视图更新操作。
\nreact \nreact是单向数据流,react中属性是不允许更改的,状态是允许更改的。react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。在setState中,传入一个对象,就会将组件的状态中键值对的部分更改,还可以传入一个函数,这个回调函数必须向上面方式一样的一个对象函数可以接受prevState和props。通过调用this.setState去更新this.state,不能直接操作this.state,请把它当成不可变的。\n调用setState更新this.state,它不是马上就会生效的,它是异步的。所以不要认为调用完setState后可以立马获取到最新的值。多个顺序执行的setState不是同步的一个接着一个的执行,会加入一个异步队列,然后最后一起执行,即批处理。\nsetState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。
\ndiff算法 \nvue \nvue中diff算法实现流程
\n在内存中构建虚拟dom树\n将内存中虚拟dom树渲染成真实dom结构\n数据改变的时候,将之前的虚拟dom树结合新的数据生成新的虚拟dom树\n将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对),来更新只需要被替换的DOM,而不是全部重绘。在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。\n会将对比出来的差异进行重新渲染
\nreact \nreact中diff算法实现流程
\nDOM结构发生改变-----直接卸载并重新create\nDOM结构一样-----不会卸载,但是会update变化的内容\n所有同一层级的子节点.他们都可以通过key来区分-----同时遵循1.2两点\n(其实这个key的存在与否只会影响diff算法的复杂度,换言之,你不加key的情况下,diff算法就会以暴力的方式去根据一二的策略更新,但是你加了key,diff算法会引入一些另外的操作)
\nReact会逐个对节点进行更新,转换到目标节点。而最后插入新的节点,涉及到的DOM操作非常多。diff总共就是移动、删除、增加三个操作,而如果给每个节点唯一的标识(key),那么React优先采用移动的方式,能够找到正确的位置去插入新的节点。\nvue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。
\n指令 \n指令 (Directives) 是带有\nv- 前缀的特殊特性,指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
\nvue \nvue中提供很多内部指令供我们使用,它可以让我们进行一些模板的操作,例如有时候,我们的data中的存放的数据不是个简单的数字或者字符串,而是数组Array类型,这个时候,我们要把数组的元素展示在视图上,就需要用到vue提供的 v-for 指令,来实现列表的渲染。
\nreact \n因为react中没有v-for指令,所以循环渲染的时候需要用到map()方法来渲染视图,并且将符合条件的元素放入一个新数组返回。
\n性能优化 \nvue \nvue中的每个组件内部自动实现了\nshouldComponentUpdate的优化,在vue里面由于依赖追踪系统的存在,当任意数据变动的时,Vue的每一个组件都精确地知道自己是否需要重绘,所以并不需要手动优化。用vue渲染这些组件的时候,数据变了,对应的组件基本上去除了手动优化的必要性。而在react中我们需要手动去优化其性能,但是当数据特别多的时候vue中的watcher也会特别多,从而造成页面卡顿,所以一般数据比较多的大型项目会倾向于使用react。在react官网中,官方也建议我们使用React来构建快速响应的大型 Web 应用程序。
\nreact \n当props或state发生改变的时候会触发\nshouldComponentUpdate生命周期函数,它是用来控制组件是否被重新渲染的,如果它返回true,则执行render函数,更新组件;如果它返回false,则不会触发重新渲染的过程。\n有的时候我们希望它在更新之前,和之前的状态进行一个对比,这个时候我们就需要重写\nshouldComponentUpdate来避免不必要的dom操作,对比当前的props或state和更新之后的nextProps或nextState,返回true时 ,组件更新;返回false,则不会更新,节省性能。
\nshouldComponentUpdate(nextProps, nextState) {\n if (this .props.a !== nextProps.a) {\n return true ;\n }\n if (this .state.b !== nextState.b) {\n return true ;\n }\n return false ;\n}\n
\n我们也可以创建一个继承React.PureComponent的React组件,它自带\nshouldComponentUpdate,可以对props进行浅比较,发现更新之后的props与当前的props一样,就不会进行render了。\nclassTestextendsReact.PureComponent{constructor(props){super(props);}render(){return
hello...{this.props.a}
}}\n由于React.PureComponent进行的是浅比较,也就是说它只会对比原对象的值是否相同,当我们的props或state为数组或者对象这种引用类型的时候,我们修改它的数值,由于数据引用指针没有发生改变,所以组件也是不会重新渲染的。这个时候我们就需要进行深拷贝,创建一个新的对象或数组,将原对象的各项属性的"值"(数组的所有元素)拷贝过来,是"值"而不仅仅是"引用地址"。我们可以使用slice()方法:\new_state.todos = new_state.todos.slice();\n
\n或者引入immutable库来实现数据不可变。
\n原生渲染native \nnative指的是使用原生API来开发App,比如ios使用OC语言,android使用java。
\nvue \nvue和Weex进行官方合作,weex是阿里巴巴发起的跨平台用户界面开发框架,它的思想是多个平台,只写一套代码,weex允许你使用 vue 语法开发不仅仅可以运行在浏览器端,还能被用于开发 iOS 和 Android 上的原生应用的组件。即只需要编写一份代码,即可运行在Web、iOS、Android上。\nweex相对来说上手比较简单,安装vue-cli之后就可以使用,学习门槛低,但是它的社区目前还处于成长期,react native的社区非常成熟活跃,有非常丰富的组件可供扩展。
\nreact \nreact native是Facebook在2015年3月在F8开发者大会上开源的跨平台UI框架,需针对iOS、Android不同编写2份代码,使用react native需要按照文档安装配置很多依赖的工具,相对比较麻烦。weex的思想是多个平台,只写一套代码,而react-native的思想是多个平台可以写多套代码,但其使用的是同一套语言框架。\nweex的目标在于抹平各个平台的差异性,从而简化应用开发。而react-native承认了各个平台之间的差异,退而求其次,在语言和框架层面对平台进行抽象,从方法论的角度去解决多平台开发的问题。
\nssr服务端渲染 \n服务端渲染核心在于方便seo优化,后端先调用数据库,获得数据之后,将数据和页面元素进行拼装,组合成完整的html页面,再直接返回给浏览器,以便用户浏览。
\nvue \n2016 年 10 月 25 日,zeit.co背后的团队对外发布了 Next.js,一个 React 的服务端渲染应用框架。几小时后,与 Next.js 异曲同工,一个基于 Vue.js 的服务端渲染应用框架应运而生,我们称之为:Nuxt.js。\n服务端渲染支持流式渲染,因为HTTP请求也是流式,Vue 的服务端渲染结果可以直接 pipe 到返回的请求里面。这样一来,就可以更早地在浏览器中呈现给用户内容,通过合理的缓存策略,可以有效地提升服务端渲染的性能。
\n\n基于 Vue.js \n自动代码分层 \n服务端渲染 \n强大的路由功能,支持异步数据 \n静态文件服务 \nES2015+ 语法支持 \n打包和压缩 JS 和 CSS \nHTML 头部标签管理 \n本地开发支持热加载 \n集成 ESLint \n支持各种样式预处理器: SASS、LESS、 Stylus 等等 \n支持 HTTP/2 推送 \n \nreact \nNext是一个React框架,允许使用React构建SSR和静态web应用
\n\n服务器渲染,获取数据非常简单 \n无需学习新框架,支持静态导出。 \n支持CSS-in-JS库 \n自动代码拆分,加快页面加载速度,不加载不必要的代码 \n基于Webpack的开发环境,支持模块热更新(HMR) \n支持Babel和Webpack自定义配置服务器、路由和next插件。 \n能够部署在任何能运行node的平台 \n内置页面搜索引擎优化(SEO)处理 \n在生产环境下,打包文件体积更小,运行速度更快 \n \n生命周期 \nvue \n【初始化阶段(4个)】\n(1)beforeCreate\n此钩子函数不能获取到数据,dom元素也没有渲染出来,此钩子函数不会用来做什么事情。\n(2)created\n此钩子函数,数据已经挂载了,但是dom节点还是没有渲染出来,在这个钩子函数里面,如果同步更改数据的话,不会影响运行中钩子函数的执行。可以用来发送ajax请求,也可以做一些初始化事件的相关操作。\n(3)beforeMount\n代表dom节点马上要被渲染出来了,但是还没有真正的渲染出来,此钩子函数跟created钩子函数基本一样,也可以做一些初始化数据的配置。\n(4)mounted\n是生命周期初始化阶段的最后一个钩子函数,数据已经挂载完毕了,真实dom也可以获取到了。\n【运行中阶段(2个)】\n(5)beforeUpdate\n运行中钩子函数beforeUpdate默认是不会执行的,当数据更改的时候,才会执行。数据更新的时候,先调用beforeUpdate,然后数据更新引发视图渲染完成之后,再会执行updated。运行时beforeUpdate这个钩子函数获取的数据还是更新之前的数据(获取的是更新前的dom内容),在这个钩子函数里面,千万不能对数据进行更改,会造成死循环。\n(6)updated\n这个钩子函数获取的数据是更新后的数据,生成新的虚拟dom,跟上一次的虚拟dom结构进行比较,比较出来差异(diff算法)后再渲染真实dom,当数据引发dom重新渲染的时候,在updated钩子函数里面就可以获取最新的真实dom了。\n【销毁阶段(2个)】\n(7)beforeDestroy\n切换路由的时候,组件就会被销毁了,销毁之前执行beforeDestroy。在这个钩子函数里面,我们可以做一些善后的操作,例如可以清空一下全局的定时器(created钩子函数绑定的初始化阶段的事件)、清除事件绑定。\n(8)destoryed\n组件销毁后执行destroyed,销毁后组件的双向数据绑定、事件监听watcher相关的都被移除掉了,但是组件的真实dom结构还是存在在页面中的。\n添加keep-alive标签后会增加active和deactive这两个生命周期函数,初始化操作放在actived里面,一旦切换组件,因为组件没有被销毁,所以它不会执行销毁阶段的钩子函数,所以移除操作需要放在deactived里面,在里面进行一些善后操作,这个时候created钩子函数只会执行一次,销毁的钩子函数一直没有执行。
\n
\nreact \n【初始化阶段(5个)】:\n(1)getDefaultProps:实例化组件之后,组件的getDefaultProps钩子函数会执行\n这个钩子函数的目的是为组件的实例挂载默认的属性\n这个钩子函数只会执行一次,也就是说,只在第一次实例化的时候执行,创建出所有实例共享的默认属性,后面再实例化的时候,不会执行getDefaultProps,直接使用已有的共享的默认属性\n理论上来说,写成函数返回对象的方式,是为了防止实例共享,但是react专门为了让实例共享,只能让这个函数只执行一次\n组件间共享默认属性会减少内存空间的浪费,而且也不需要担心某一个实例更改属性后其他的实例也会更改的问题,因为组件不能自己更改属性,而且默认属性的优先级低。\n(2)getInitialState:为实例挂载初始状态,且每次实例化都会执行,也就是说,每一个组件实例都拥有自己独立的状态。\n(3)componentWillMount:执行componentWillMount,相当于Vue里的created+beforeMount,这里是在渲染之前最后一次更改数据的机会,在这里更改的话是不会触发render的重新执行。\n(4)render:渲染dom\nrender()方法必须是一个纯函数,他不应该改变\nstate,也不能直接和浏览器进行交互,应该将事件放在其他生命周期函数中。 如果\nshouldComponentUpdate()返回\nfalse,\nrender()不会被调用。\n(5)componentDidMount:相当于Vue里的mounted,多用于操作真实dom\n【运行中阶段(5个)】\n当组件mount到页面中之后,就进入了运行中阶段,在这里有5个钩子函数,但是这5个函数只有在数据(属性、状态)发送改变的时候才会执行\n(1)componentWillReceiveProps(nextProps,nextState)\n当父组件给子组件传入的属性改变的时候,子组件的这个函数才会执行。初始化props时候不会主动执行\n当执行的时候,函数接收的参数是子组件接收到的新参数,这个时候,新参数还没有同步到this.props上,多用于判断新属性和原有属性的变化后更改组件的状态。\n(2)接下来就会执行shouldComponentUpdate(nextProps,nextState),这个函数的作用:当属性或状态发生改变后控制组件是否要更新,提高性能,返回true就更新,否则不更新,默认返回true。\n接收nextProp、nextState,根据根据新属性状态和原属性状态作出对比、判断后控制是否更新\n如果\nshouldComponentUpdate()返回\nfalse,\ncomponentWillUpdate,\nrender和\ncomponentDidUpdate不会被调用。\n(3)componentWillUpdate,在这里,组件马上就要重新render了,多做一些准备工作,千万千万,不要在这里修改状态,否则会死循环 相当于Vue中的beforeUpdate\n(4)render,重新渲染dom\n(5)componentDidUpdate,在这里,新的dom结构已经诞生了,相当于Vue里的updated\n【销毁阶段】\n当组件被销毁之前的一刹那,会触发componentWillUnmount,临死前的挣扎\n相当于Vue里的beforeDestroy,所以说一般会做一些善后的事情,例如使定时器无效,取消网络请求或清理在\ncomponentDidMount中创建的任何监听。
\n
\n销毁组件 \nvue \nvue在调用$destroy方法的时候就会执行beforeDestroy生命周期函数,然后组件被销毁,这个时候组件的dom结构还存在于页面结构中,也就说如果想要对残留的dom结构进行处理必须在destroyed生命周期函数中处理。
\nreact \nreact执行完componentWillUnmount之后把事件、数据、dom都全部处理掉了,也就是说当父组件从渲染这个子组件变成不渲染这个子组件的时候,子组件相当于被销毁,所以根本不需要其他的钩子函数了。react销毁组件的时候,会将组件的dom结构也移除,vue则不然,在调用destory方法销毁组件的时候,组件的dom结构还是存在于页面中的,this.$destory组件结构还是存在的,只是移除了事件监听,所以这就是为什么vue中有destroyed,而react却没有componentDidUnmount。
\n状态集管理工具 \nvue \nvuex是一个专门为vue构建的状态集管理工具,vue和react都是基于组件化开发的,项目中包含很多的组件,组件都会有组件嵌套,想让组件中的数据被其他组件也可以访问到就需要使用到Vuex。\nvuex的流程
\n将需要共享的状态挂载到state上:this.$store.state来调用
\n创建store,将状态挂载到state上,在根实例里面配置store,之后我们在组件中就可以通过this.$store.state来使用state中管理的数据,但是这样使用时,当state的数据更改的时候,vue组件并不会重新渲染,所以我们要通过计算属性computed来使用,但是当我们使用多个数据的时候这种写法比较麻烦,vuex提供了mapState辅助函数,帮助我们在组件中获取并使用vuex的store中保存的状态。
\n我们通过getters来创建状态:通过this.$store.getters来调用
\n可以根据某一个状态派生出一个新状态,vuex也提供了mapGetters辅助函数来帮助我们在组件中使用getters里的状态。
\n使用mutations来更改state:通过this.$store.commit来调用
\n我们不能直接在组件中更改state,而是需要使用mutations来更改,mutations也是一个纯对象,里面包含很多更改state的方法,这些方法的形参接收到state,在函数体里更改,这时,组件用到的数据也会更改,实现响应式。vuex提供了mapMutations方法来帮助我们在组件中调用mutations 的方法。
\n使用actions来处理异步操作:this.$store.dispatch来调用
\nActions类似于mutations,不同在于:Actions提交的是mutations,而不是直接变更状态。Actions可以包含任意异步操作。也就是说,如果有这样的需求:在一个异步操作处理之后,更改状态,我们在组件中应该先调用actions,来进行异步动作,然后由actions调用mutations来更改数据。在组件中通过this.$store.dispatch方法调用actions的方法,当然也可以使用mapMutations来辅助使用。
\nreact \n2015年Redux出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。它的出现主要是为解决react中组件之间的通信问题。建议把数据放入到redux中管理,目的就是方便数据统一,好管理。项目一旦出现问题,可以直接定位问题点。组件扩展的时候,后续涉及到传递的问题。本来的话,组件使用自己的数据,但是后来公用组件,还需要考虑如何值传递,在redux中可以存储至少5G以上的数据。\nredux的流程
\n
\n\n创建store: 从redux工具中取出createStore去生成一个store。 \n创建一个reducer,然后将其传入到createStore中辅助store的创建。 reducer是一个纯函数,接收当前状态和action,返回一个状态,返回什么,store的状态就3是什么,需要注意的是,不能直接操作当前状态,而是需要返回一个新的状态。 想要给store创建默认状态其实就是给reducer一个参数创建默认值。 \n组件通过调用store.getState方法来使用store中的state,挂载在了自己的状态上。 \n组件产生用户操作,调用actionCreator的方法创建一个action,利用store.dispatch方法传递给reducer \nreducer对action上的标示性信息做出判断后对新状态进行处理,然后返回新状态,这个时候store的数据就会发生改变, reducer返回什么状态,store.getState就可以获取什么状态。 \n我们可以在组件中,利用store.subscribe方法去订阅数据的变化,也就是可以传入一个函数,当数据变化的时候,传入的函数会执行,在这个函数中让组件去获取最新的状态。 \n \n小结 \nvue和react的核心都是专注于轻量级的视图层,虽然只是解决一个很小的问题,但是它们庞大的生态圈提供了丰富的配套工具,一开始它并不会给你提供全套的配置方案,将所有的功能都一次性给你打包好,它只会给你提供一些简单的核心功能,当你需要做一个更复杂的应用时,再增添相应的工具。例如做一个单页应用的时候才需要用路由;做一个相当庞大的应用,涉及到多组件状态共享以及多个开发者共同协作时,才可能需要大规模状态管理方案。\n框架的存在就是为了帮助我们应对不同的项目复杂度,当我们面对一个大型、复杂的开发项目时,使用太简陋的工具会极大的降低开发人员的生产力,影响工作效率,框架的诞生就是在这些工程中提取一些重复的并且已经受过验证的模式,抽象到一个已经帮你设计好的API封装当中,帮助我们去应对不同复杂度的问题。所以在开发的过程中,选择一个合适的框架就会事半功倍。但是,框架本身也有复杂度,有些框架会让人一时不知如何上手。当你接到一个并不复杂的需求,却使用了很复杂的框架,那么就相当于杀鸡用牛刀,会遇到工具复杂度所带来的副作用,不仅会失去工具本身所带来优势,还会增加各种问题,例如学习成本、上手成本,以及实际开发效率等。\n所以并不是说做得少的框架就不如做的做的框架,每个框架都有各自的优势和劣势,并不能找到完全符合需求的框架,最重要的适合当前项目,目前两大框架的生态圈一片繁荣,react社区是当前最活跃的,最快的时候三天更新一个版本,一个问题可能存在几十种不同的解决方案,这就需要我们前端人员去在不同的功能之间做取舍,以后前端框架的发展方向应该是小而精、灵活以及开放的,核心功能+生态附加库可以帮我们更加灵活的构建项目,为了跟上前进的脚步,就需要不停的吸收最新的内容,这也是从事前端开发领域的一大乐趣,希望大家都能在学习中获得长足的进步。
\n",
+ "link": "\\zh-cn\\blog\\web\\vue_cp_react.html",
+ "meta": {}
+}
\ No newline at end of file