commit 6f0c1026267231f40acb6724c8dad34aba677fc9 Author: lemonchann Date: Sun Nov 17 01:12:14 2019 +0800 创建博客 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..849da1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +_site +.sass-cache +.idea +.local +.local/* +.jekyll-cache \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..e570d2a --- /dev/null +++ b/404.html @@ -0,0 +1,3 @@ + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eb2398e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 bit-ranger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4df94a --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +it is a blog Repositories +--- +##[点我查看中文说明/Click here for Chinese instructions](https://github.com/bit-ranger/blog/blob/gh-pages/README_zh_CN.md) + +# Blog Address + + + + +# Must Modify + +## 1.swiftype + +This service provides the on-site search function. + +Service address: . + +Documentation: + +After the setup is complete, you need to modify the `swiftype.searchId` in `_config.yml`. + +In your swiftype engine, go to `Install Search`, you will find the `swiftype.searchId`. + +```html + +``` + +## 2.gitment + +This service provides the comment function. + +Service address: . + +After the setup is complete, you need to modify the `gitment` in `_config.yml`. diff --git a/README_zh_CN.md b/README_zh_CN.md new file mode 100644 index 0000000..d06519a --- /dev/null +++ b/README_zh_CN.md @@ -0,0 +1,33 @@ +# 博客地址 + + + +# 必改内容 + +## 1.swiftype + +此服务提供站内搜索功能 + +服务地址: + +文档: + +设置完毕后,您需要修改 `_config.yml` 中 `swiftype.searchId`。 + +在自己的引擎中,进入 `Install Search`, 你将找到 `swiftype.searchId`。 + +```html + +``` + +## 2.gitment + +此服务提供评论功能 + +服务地址: + +设置完毕后, 需要修改 `_config.yml` 中的 `gitment`。 diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..66e8cd8 --- /dev/null +++ b/_config.yml @@ -0,0 +1,48 @@ +plugins: [jekyll-paginate] + +permalink: /:categories/:title/ + +highlighter: rouge +markdown: kramdown +kramdown: + input: GFM + syntax_highlighter: rouge + + +paginate: 10 +paginate_path: "page/:num" +baseurl: "" + + + +defaults: + - + scope: + path: "" + type: posts + values: + layout: post + styles : [highlight.css,gitment.css,post.css] + scripts : [gitment.js, post.js] + + + +title: "lemonchann" +email: lemonchann@foxmail.com +description: "" +github: + username: lemonchann +#swiftype: + #searchId: iXZyPokVfxFaBTvi64Y6 +#gitment: + #repo: blog + #client_id: a6fb73b3e790e234bab8 + #client_secret: cc10aaff53a03d05ab2ee002dbf401dd7627c7a3 + +url: "https://lemonchann.github.io/blog" +imgrepo: "https://lemonchann.github.io/blog/static/img" + + +#url: "http://127.0.0.1:4000" +#imgrepo: "http://127.0.0.1:4000/static/img" + diff --git a/_includes/footer.html b/_includes/footer.html new file mode 100644 index 0000000..6981d01 --- /dev/null +++ b/_includes/footer.html @@ -0,0 +1,15 @@ + + + + + {% for script in page.scripts %} + {%endfor%} diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000..fd1095e --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,19 @@ + + {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} + + + + + + + + + + + + + + {% for style in page.styles %} + {%endfor%} + + diff --git a/_includes/header.html b/_includes/header.html new file mode 100644 index 0000000..17125ce --- /dev/null +++ b/_includes/header.html @@ -0,0 +1,34 @@ +
+ +
+ diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..5a41654 --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,25 @@ + + + +{% include head.html %} + + + +{% include header.html %} + +
+
+ {{ content }} +
+ +
+ +{% include footer.html %} + + + diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..871a261 --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,16 @@ +--- +layout: default +--- +
+
+
+
+

{{ page.title }}

+
+
+
+ {{ content }} +
+
+
+
\ No newline at end of file diff --git a/_layouts/post.html b/_layouts/post.html new file mode 100644 index 0000000..27f72ca --- /dev/null +++ b/_layouts/post.html @@ -0,0 +1,103 @@ +--- +layout: default +--- +
+
+
+
+

{{ page.title }}

+ + +
+
+
+ {{content}} +
+
+
+ + + + + + +
+
+
+ +
+
+
+
+
+  Toc +
+
+
+
+
+  Tags +
+
+
    + {% for tag in page.tags %} +
  • +  {{ tag }} +
  • + {% endfor %} +
+
+
+ +
+
+
\ No newline at end of file diff --git a/_posts/2013-11-17-complement.md b/_posts/2013-11-17-complement.md new file mode 100644 index 0000000..8643b0a --- /dev/null +++ b/_posts/2013-11-17-complement.md @@ -0,0 +1,43 @@ +--- +layout: post +title: 为什么要用补码表示负数 +tags: 补码 computer +categories: common +--- + +抛开二进制不谈,我们先来看看10进制 + +假设世界上没有负号且数字最大只有3位,我们要把 `0~999` 分成两部分,一部分表示负数,一部分表示正数,而且不影响他们的运算规律,应当如何去做? + +首先,最大的负数加上一等于零,那么用999表示最大的负数再合适不过,现在需要正负数各一半,那么正数部分应当为 `0 ~ 499`,负数部分应当为 `500~999`,我们暂时把这些表示负数的数字成为`对照数字`。 + +验证一下,`18 - 5 = 18 + 995 = 1013 = 13` + +有没有一种计算方法可以方便地找出`-5`与`995`的关系呢? + +有,那就是`999 - 5 + 1` + +--- + +将这个规律推及到二进制中 + +假设二进制数只有8位,那么8个1代表最大的负数,将这些数分为正负各一半,那么正数部分应该为 `0 ~ 01111111`,负数部分应当为 `10000000 ~ 11111111`。 + +验证一下,`00010010 - 00000101 = 00010010 + 11111011 = 00001101` + +与十进制同理,`-00000101`的对照数字可以用相同方法计算出来,`11111111 - 00000101 + 1` + +等等,我们发现了一个特性,这真是一个神奇的特性! + +`11111111 - 00000101` 等于`00000101` 按位取反! + +现在,我们已经总结出了一点经验,在二进制中,负数的对照数字等于它的数值位按位取反再加一。 + +对于一个二进制负数来说,它是有符号的,因此他的第一位为符号位,剩余的为数值位,那么完整的表述就是负数的对照数字为`符号位不变,数值位按位取反再加一`,这个对照数字就是`补码`。 + +--- + +最后回答一下标题中的问题,显而易见,使用补码的好处就是,可以用加法来计算减法,从而简化电路设计。 + + + diff --git a/_posts/2013-12-07-java-io.md b/_posts/2013-12-07-java-io.md new file mode 100644 index 0000000..7e0376b --- /dev/null +++ b/_posts/2013-12-07-java-io.md @@ -0,0 +1,135 @@ +--- +layout: post +title: Java IO +tags: Java IO +categories: Java +--- + + +* TOC +{:toc} + +![io][io] + +# IO流的分类 + +根据处理数据类型的不同分为:字符流和字节流 + +根据数据流向不同分为:输入流和输出流 + +## 字符流和字节流 + +字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别: + +* 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。 + +* 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。 + +结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。 + + +## 输入流和输出流 + +对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。 + +# 字节流 + +## 输入 + + +* `InputStream` 是所有的输入字节流的父类,它是一个抽象类。 + +* `ByteArrayInputStream`、
`StringBufferInputStream`、
`FileInputStream` 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。`PipedInputStream` 是从与其它线程共用的管道中读取数据 + + +* `ObjectInputStream` 和所有`FilterInputStream` 的子类都是装饰流(装饰器模式的主角) + +## 输出 + + +* `OutputStream` 是所有的输出字节流的父类,它是一个抽象类。 + +* `ByteArrayOutputStream`、
`FileOutputStream` 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。`PipedOutputStream` 是向与其它线程共用的管道中写入数据, + +* `ObjectOutputStream` 和所有`FilterOutputStream` 的子类都是装饰流。 + +## 配对 + +![io-match][io-match] + +图中蓝色的为主要的对应部分,红色的部分就是不对应部分。紫色的虚线部分代表这些流一般要搭配使用。 + + +* `LineNumberInputStream` 主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。 + +* `PushbackInputStream` 的功能是查看最后一个字节,不满意就放回流中。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream 几乎实现相近的功能。(预读,可以将读取内容的一部分放回流的开头,下回重读) + +* `StringBufferInputStream` 已经被Deprecated,本身就不应该出现在InputStream 部分,主要因为String 应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。 + +* `SequenceInputStream` 可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO 包中去除,还完全不影响IO 包的结构,却让其更“纯洁”――纯洁的Decorator 模式。 + +* `PrintStream` 也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO 包!System.out 和System.out 就是PrintStream 的实例! + +# 字符流 + +## 输入 + + +* `Reader` 是所有的输入字符流的父类,它是一个抽象类。 + +* `CharReader`、`StringReader` 是两种基本的介质流,它们分别将Char 数组、String中读取数据。`PipedReader`是从与其它线程共用的管道中读取数据。 + +* `BufferedReader` 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。 + +* `FilterReader` 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。 + +* `InputStreamReader` 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流(`转换流`)。`FileReader` 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream`转变为Reader 的方法。 + +## 输出 + + +* `Writer` 是所有的输出字符流的父类,它是一个抽象类。 + +* `CharArrayWriter`、`StringWriter` 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据, + +* `BufferedWriter` 是一个装饰器为Writer 提供缓冲功能。 + +* `PrintWriter` 和`PrintStream` 极其类似,功能和使用也非常相似。 + +* `OutputStreamWriter` 是OutputStream 到Writer 转换的桥梁(`转换流`),它的子类`FileWriter` 其实就是一个实现此功能的具体类。 + +## 配对 + +![io-match-2][io-match-2] + + +# RandomAccessFile + +该类并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点: + + +* 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。 + +* 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw) + +**注意**:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。 + + +# 管道 + +Java的I/O库提供了一个称做链接(Chaining)的机制,可以将一个流处理器跟另一个流处理器首尾相接,以其中之一的输出为输入,形成一个 流管道的链接。 + +例如,`DataInputStream`流处理器可以把`FileInputStream`流对象的输出当作输入,将Byte类型的数据转换成Java的原始类型和String类型的数据。 + +# 设计模式 + +Java I/O库的两个设计模式: + + +* `装饰者模式`:也叫**包装器模式**,在由InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器可以 对另一些流处理器起到装饰作用,形成新的,具有改善了的功能的流处理器。装饰者模式是Java I/O库的整体设计模式。 + +* `适配器模式`:在由InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器是对其它类型的流源的适配。 + +[io]: {{"/i0.jpg" | prepend: site.imgrepo }} +[io-match]: {{"/io-match.jpg" | prepend: site.imgrepo }} +[io-match-2]: {{"/io-match-2.jpg" | prepend: site.imgrepo }} \ No newline at end of file diff --git a/_posts/2013-12-19-java-nio.md b/_posts/2013-12-19-java-nio.md new file mode 100644 index 0000000..8f3753a --- /dev/null +++ b/_posts/2013-12-19-java-nio.md @@ -0,0 +1,1015 @@ +--- +layout: post +title: Java NIO +tags: Java NIO +categories: Java +--- + + +* TOC +{:toc} + +# 核心 + +* Channels +* Buffers +* Selectors + +# 概述 + +## Channel 和 Buffer + +基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示: + +![](http://ifeve.com/wp-content/uploads/2013/06/overview-channels-buffers1.png) + +**Channel**和**Buffer**有好几种类型。下面是JAVA NIO中的一些主要Channel的实现: + +* FileChannel +* DatagramChannel +* SocketChannel +* ServerSocketChannel + +这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。 + +以下是Java NIO里关键的Buffer实现: + +* ByteBuffer +* CharBuffer +* DoubleBuffer +* FloatBuffer +* IntBuffer +* LongBuffer +* ShortBuffer + +这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。 + +Java NIO 还有个 `MappedByteBuffer`,用于表示内存映射文件 + +## Selector + +**Selector**允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。 + +这是在一个单线程中使用一个Selector处理3个Channel的图示: + +![](http://ifeve.com/wp-content/uploads/2013/06/overview-selectors.png) + +要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。 + + +# Channel + +Java NIO的通道类似流,但又有些不同: + + +* 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。 +* 通道可以异步地读写。 +* 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。 + +正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。如下图所示: + +![](http://ifeve.com/wp-content/uploads/2013/06/overview-channels-buffers.png) + +* FileChannel 从文件中读写数据。 +* DatagramChannel 能通过UDP读写网络中的数据。 +* SocketChannel 能通过TCP读写网络中的数据。 +* ServerSocketChannel 可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。 + +```java +RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); +FileChannel inChannel = aFile.getChannel(); + +ByteBuffer buf = ByteBuffer.allocate(48); + +int bytesRead = inChannel.read(buf); +while (bytesRead != -1) { + +System.out.println("Read " + bytesRead); +buf.flip(); + +while(buf.hasRemaining()){ +System.out.print((char) buf.get()); +} + +buf.clear(); +bytesRead = inChannel.read(buf); +} +aFile.close(); +``` + +注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。 + + +# Buffer + +Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。 + +缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。 + + +## Buffer的基本用法 + +使用Buffer读写数据一般遵循以下四个步骤: + +1. 写入数据到Buffer + +2. 调用flip()方法 + +3. 从Buffer中读取数据 + +4. 调用clear()方法或者compact()方法 + +当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。 + +一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。 + +clear()方法会清空整个缓冲区。 + +compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。 + +```java +RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); +FileChannel inChannel = aFile.getChannel(); + +//create buffer with capacity of 48 bytes +ByteBuffer buf = ByteBuffer.allocate(48); + +int bytesRead = inChannel.read(buf); //read into buffer. +while (bytesRead != -1) { + + buf.flip(); //make buffer ready for read + + while(buf.hasRemaining()){ + System.out.print((char) buf.get()); // read 1 byte at a time + } + + buf.clear(); //make buffer ready for writing + bytesRead = inChannel.read(buf); +} +aFile.close(); +``` + +## Buffer的capacity,position和limit + +缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。 + +为了理解Buffer的工作原理,需要熟悉它的三个属性: + +* capacity +* position +* limit + +position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。 + +这里有一个关于capacity,position和limit在读写模式中的说明,详细的解释在插图后面。 + +![](http://ifeve.com/wp-content/uploads/2013/06/buffers-modes.png) + +**capacity** + +作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”.你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。 + +**position** + +当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1. + +当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。 + +**limit** + +在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。 + +当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position) + +## Buffer的类型 + +Java NIO 有以下Buffer类型 + +* ByteBuffer +* MappedByteBuffer +* CharBuffer +* DoubleBuffer +* FloatBuffer +* IntBuffer +* LongBuffer +* ShortBuffer + +如你所见,这些Buffer类型代表了不同的数据类型。换句话说,就是可以通过char,short,int,long,float 或 double类型来操作缓冲区中的字节。 + +MappedByteBuffer 有些特别 + +## Buffer的分配 + +要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。 + +下面是一个分配48字节capacity的ByteBuffer的例子。 + +```java +ByteBuffer buf = ByteBuffer.allocate(48); +``` + +## 向Buffer中写数据 + +写数据到Buffer有两种方式: + +从Channel写到Buffer。 + +通过Buffer的put()方法写到Buffer里。 + +从Channel写到Buffer的例子 + +```java +int bytesRead = inChannel.read(buf); //read into buffer. +``` + +通过put方法写Buffer的例子: + +```java +buf.put(127); +``` + +put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。 更多Buffer实现的细节参考JavaDoc。 + +## flip()方法 + +flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。 + +换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。 + +## 从Buffer中读取数据 + +从Buffer中读取数据有两种方式: + +从Buffer读取数据到Channel。 + +使用get()方法从Buffer中读取数据。 + +从Buffer读取数据到Channel的例子: + +```java +//read from buffer into channel. +int bytesWritten = inChannel.write(buf); +``` + +使用get()方法从Buffer中读取数据的例子 + +```java +byte aByte = buf.get(); +``` + +get方法有很多版本,允许你以不同的方式从Buffer中读取数据。例如,从指定position读取,或者从Buffer中读取数据到字节数组。更多Buffer实现的细节参考JavaDoc。 + +## rewind()方法 + +Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。 + +## clear()与compact()方法 + +一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。 + +如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。 + +如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。 + +如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。 + +compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。 + +## mark()与reset()方法 + +通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如: + +```java +buffer.mark(); +//call buffer.get() a couple of times, e.g. during parsing. +buffer.reset(); //set position back to mark. +``` + +## equals()与compareTo()方法 + +可以使用equals()和compareTo()方法比较两个Buffer。 + +equals() + +当满足下列条件时,表示两个Buffer相等: + +1. 有相同的类型(byte、char、int等)。 + +2. Buffer中剩余的byte、char等的个数相等。 + +3. Buffer中所有剩余的byte、char等都相同。 + +如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。 + +compareTo()方法 + +compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer: + +1. 第一个不相等的元素小于另一个Buffer中对应的元素 。 + +2. 所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。 + +(注:剩余元素是从 position到limit之间的元素) + + +# Scatter/Gather + +Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel中读取或者写入到Channel的操作。 + +分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。 + +聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。 + +scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。 + +## Scattering Reads + +Scattering Reads是指数据从一个channel读取到多个buffer中。如下图描述: + +![](http://ifeve.com/wp-content/uploads/2013/06/scatter.png) + +```java +ByteBuffer header = ByteBuffer.allocate(128); +ByteBuffer body = ByteBuffer.allocate(1024); + +ByteBuffer[] bufferArray = { header, body }; + +channel.read(bufferArray); +``` + +注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。 + +Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。 + +## Gathering Writes + +Gathering Writes是指数据从多个buffer写入到同一个channel。如下图描述: +![](http://ifeve.com/wp-content/uploads/2013/06/gather.png) + +```java +ByteBuffer header = ByteBuffer.allocate(128); +ByteBuffer body = ByteBuffer.allocate(1024); + +//write data into buffers + +ByteBuffer[] bufferArray = { header, body }; + +channel.write(bufferArray); +``` + +buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。 + + +# 通道之间的数据传输 + +在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel传输到另外一个channel。 + +## transferFrom() + +FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个简单的例子: + +```java +RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); +FileChannel fromChannel = fromFile.getChannel(); + +RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); +FileChannel toChannel = toFile.getChannel(); + +long position = 0; +long count = fromChannel.size(); + +toChannel.transferFrom(position, count, fromChannel); +``` + +方法的输入参数position表示从position处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。 +此外要注意,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。 + + +## transferTo() + +transferTo()方法将数据从FileChannel传输到其他的channel中。下面是一个简单的例子: + +```java +RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); +FileChannel fromChannel = fromFile.getChannel(); + +RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); +FileChannel toChannel = toFile.getChannel(); + +long position = 0; +long count = fromChannel.size(); + +fromChannel.transferTo(position, count, toChannel); +``` + +是不是发现这个例子和前面那个例子特别相似?除了调用方法的FileChannel对象不一样外,其他的都一样。 +上面所说的关于SocketChannel的问题在transferTo()方法中同样存在。SocketChannel会一直传输数据直到目标buffer被填满。 + + +# Selector + +Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。 + +## 为什么使用Selector + +仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。 + +但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。不管怎么说,关于那种设计的讨论应该放在另一篇不同的文章中。在这里,只要知道使用Selector能够处理多个通道就足够了。 + +## Selector的创建 + +```java +Selector selector = Selector.open(); +``` + +## 向Selector注册通道 + +为了将Channel和Selector配合使用,必须将channel注册到selector上。 + +```java +channel.configureBlocking(false); +SelectionKey key = channel.register(selector, Selectionkey.OP_READ); +``` + +与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。 + +注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件: +* Connect +* Accept +* Read +* Write + +通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。 + +这四种事件用SelectionKey的四个常量来表示: +* `SelectionKey.OP_CONNECT` +* `SelectionKey.OP_ACCEPT` +* `SelectionKey.OP_READ` +* `SelectionKey.OP_WRITE` + +如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下: + +```java +int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; +``` + +## SelectionKey + +当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些属性: +* interest集合 +* ready集合 +* Channel +*Selector +* 附加的对象(可选) + +### interest集合 + +```java +int interestSet = selectionKey.interestOps(); + +boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; +boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; +boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; +boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; +``` + +可以看到,用“位与”操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。 + +### ready集合 + +ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。 + +```java +int readySet = selectionKey.readyOps(); +``` + +可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型: + +```java +selectionKey.isAcceptable(); +selectionKey.isConnectable(); +selectionKey.isReadable(); +selectionKey.isWritable(); +``` + +### Channel + Selector + +```java +Channel channel = selectionKey.channel(); +Selector selector = selectionKey.selector(); +``` + +### 附加的对象 + +可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下: + +```java +selectionKey.attach(theObject); +Object attachedObj = selectionKey.attachment(); +``` + +还可以在用register()方法向Selector注册Channel的时候附加对象。如: + +```java +SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject); +``` + +## 通过Selector选择通道 + +一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。 + +下面是select()方法: + +* int select() +* int select(long timeout) +* int selectNow() + +select()阻塞到至少有一个通道在你注册的事件上就绪了。 + +select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。 + +selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。 + +select()方法返回的int值表示自上次调用select()方法后有多少通道已经就绪(不累加)如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。 + +### selectedKeys() + +一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示: + +```java +Set selectedKeys = selector.selectedKeys(); +``` + +当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。 + +可以遍历这个已选择的键集合来访问就绪的通道。如下: + +```java +Set selectedKeys = selector.selectedKeys(); +Iterator keyIterator = selectedKeys.iterator(); +while(keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + if(key.isAcceptable()) { + // a connection was accepted by a ServerSocketChannel. + } else if (key.isConnectable()) { + // a connection was established with a remote server. + } else if (key.isReadable()) { + // a channel is ready for reading + } else if (key.isWritable()) { + // a channel is ready for writing + } + keyIterator.remove(); +} +``` + +这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。 + +注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。 + +SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。 + +## wakeUp() + +某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。 + +如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来(wake up)”。 + +## close() + +用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。 + +## 完整的示例 + +这里有一个完整的示例,打开一个Selector,注册一个通道注册到这个Selector上(通道的初始化过程略去),然后持续监控这个Selector的四种事件(接受,连接,读,写)是否就绪。 + +```java +Selector selector = Selector.open(); +channel.configureBlocking(false); +SelectionKey key = channel.register(selector, SelectionKey.OP_READ); +while(true) { + int readyChannels = selector.select(); + if(readyChannels == 0) continue; + Set selectedKeys = selector.selectedKeys(); + Iterator keyIterator = selectedKeys.iterator(); + while(keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + if(key.isAcceptable()) { + // a connection was accepted by a ServerSocketChannel. + } else if (key.isConnectable()) { + // a connection was established with a remote server. + } else if (key.isReadable()) { + // a channel is ready for reading + } else if (key.isWritable()) { + // a channel is ready for writing + } + keyIterator.remove(); + } +} +``` + +# FileChannel + +Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。 + +FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。 + +## 打开FileChannel + +在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。下面是通过RandomAccessFile打开FileChannel的示例: + +```java +RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); +FileChannel inChannel = aFile.getChannel(); +``` + +## 从FileChannel读取数据 + +调用多个read()方法之一从FileChannel中读取数据。如: + +```java +ByteBuffer buf = ByteBuffer.allocate(48); +int bytesRead = inChannel.read(buf); +``` + +首先,分配一个Buffer。从FileChannel中读取的数据将被读到Buffer中。 + +然后,调用FileChannel.read()方法。该方法将数据从FileChannel读取到Buffer中。read()方法返回的int值表示了有多少字节被读到了Buffer中。如果返回-1,表示到了文件末尾。 + +## 向FileChannel写数据 + +```java +String newData = "New String to write to file..." + System.currentTimeMillis(); + +ByteBuffer buf = ByteBuffer.allocate(48); +buf.clear(); +buf.put(newData.getBytes()); + +buf.flip(); + +while(buf.hasRemaining()) { + channel.write(buf); +} +``` + +## 关闭FileChannel + +```java +channel.close(); +``` + +## FileChannel的position方法 + +有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。 + +也可以通过调用position(long pos)方法设置FileChannel的当前位置。 + +```java +long pos = channel.position(); +channel.position(pos +123); +``` + +如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。 + +如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。 + +## FileChannel的size方法 + +FileChannel实例的size()方法将返回该实例所关联文件的大小。如: + +```java +long fileSize = channel.size(); +``` + +## FileChannel的truncate方法 + +可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如: + +```java +channel.truncate(1024); +``` + +这个例子截取文件的前1024个字节。 + +## FileChannel的force方法 + +FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。 + +force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。 + +下面的例子同时将文件数据和元数据强制写到磁盘上: + +```java +channel.force(true); +``` + +# SocketChannel + +Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel: +1. 打开一个SocketChannel并连接到互联网上的某台服务器。 +2. 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。 + +## 打开 SocketChannel + +```java +SocketChannel socketChannel = SocketChannel.open(); +socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); +``` + +## 关闭 SocketChannel + +```java +socketChannel.close(); +``` + +## 从 SocketChannel 读取数据 + +```java +ByteBuffer buf = ByteBuffer.allocate(48); +int bytesRead = socketChannel.read(buf); +``` + +## 写入 SocketChannel + +```java +String newData = "New String to write to file..." + System.currentTimeMillis(); + +ByteBuffer buf = ByteBuffer.allocate(48); +buf.clear(); +buf.put(newData.getBytes()); + +buf.flip(); + +while(buf.hasRemaining()) { + channel.write(buf); +} +``` + +注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。 + +## 非阻塞模式 + +可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。 + +### connect() + +如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。像这样: + +```java +socketChannel.configureBlocking(false); +socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); + +while(! socketChannel.finishConnect() ){ + //wait, or do something else... +} +``` + +### write() + +非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。 + +### read() + +非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。 + +### 非阻塞模式与选择器 + +非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。Selector与SocketChannel的搭配使用会在后面详讲。 + +# ServerSocket
Channel + +Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。 + +```java +ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); + +serverSocketChannel.socket().bind(new InetSocketAddress(9999)); + +while(true){ + SocketChannel socketChannel = + serverSocketChannel.accept(); + + //do something with socketChannel... +} + +serverSocketChannel.close(); +``` + +通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。 + +通常不会仅仅只监听一个连接,在while循环中调用 accept()方法. + +ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null.如: + +```java +ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); + +serverSocketChannel.socket().bind(new InetSocketAddress(9999)); +serverSocketChannel.configureBlocking(false); + +while(true){ + SocketChannel socketChannel = + serverSocketChannel.accept(); + + if(socketChannel != null){ + //do something with socketChannel... + } +} +``` + +# DatagramChannel + +Java NIO中的DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。 + +## 打开 DatagramChannel + +```java +DatagramChannel channel = DatagramChannel.open(); +channel.socket().bind(new InetSocketAddress(9999)); +``` + +## 接收数据 + +通过receive()方法从DatagramChannel接收数据,如: + +```java +ByteBuffer buf = ByteBuffer.allocate(48); +buf.clear(); +channel.receive(buf); +``` + +receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。 + +## 发送数据 + +```java +String newData = "New String to write to file..." + System.currentTimeMillis(); + +ByteBuffer buf = ByteBuffer.allocate(48); +buf.clear(); +buf.put(newData.getBytes()); +buf.flip(); + +int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80)); +``` + +这个例子发送一串字符到”jenkov.com”服务器的UDP端口80。 因为服务端并没有监控这个端口,所以什么也不会发生。也不会通知你发出的数据包是否已收到,因为UDP在数据传送方面没有任何保证。 + +## 连接到特定的地址 + +可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。 + +```java +channel.connect(new InetSocketAddress("jenkov.com", 80)); +``` + +当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。 + +```java +int bytesRead = channel.read(buf); +int bytesWritten = channel.write(but); +``` + +# pipe + +Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。 +这里是Pipe原理的图示: +![](http://ifeve.com/wp-content/uploads/2013/06/pipe.bmp) + +## 创建管道 + +```java +Pipe pipe = Pipe.open(); +``` + +## 向管道写数据 + +要向管道写数据,需要访问sink通道。 + +```java +Pipe.SinkChannel sinkChannel = pipe.sink(); +``` + +通过调用SinkChannel的write()方法,将数据写入SinkChannel + +```java +String newData = "New String to write to file..." + System.currentTimeMillis(); +ByteBuffer buf = ByteBuffer.allocate(48); +buf.clear(); +buf.put(newData.getBytes()); + +buf.flip(); + +while(buf.hasRemaining()) { + sinkChannel.write(buf); +} +``` + +## 从管道读取数据 + +从读取管道的数据,需要访问source通道,像这样: + +```java +Pipe.SourceChannel sourceChannel = pipe.source(); +``` + +调用source通道的read()方法来读取数据,像这样: + +```java +ByteBuffer buf = ByteBuffer.allocate(48); +int bytesRead = sourceChannel.read(buf); +``` + +read()方法返回的int值会告诉我们多少字节被读进了缓冲区。 + +# Java NIO与IO + +## Java NIO和IO的主要区别 + + | | | | +---|---|---|---|--- +IO | 面向流 | 阻塞IO | / +NIO | 面向缓冲 | 非阻塞IO | 选择器 + +## 面向流与面向缓冲 + +Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。 + +## 阻塞与非阻塞IO + +Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。 + +## 选择器(Selectors) + +Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。 + +## NIO和IO如何影响应用程序的设计 + +无论您选择IO或NIO工具箱,可能会影响您应用程序设计的以下几个方面: +1. 对NIO或IO类的API调用。 +2. 数据处理。 +3. 用来处理数据的线程数。 + +### API调用 + +当然,使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外,因为并不是仅从一个InputStream逐字节读取,而是数据必须先读入缓冲区再处理。 + +### 数据处理 + +使用纯粹的NIO设计相较IO设计,数据处理也受到影响。 + +在IO设计中,我们从InputStream或 Reader逐字节读取数据。假设你正在处理一基于行的文本数据流,例如: + + Name: Anna + Age: 25 + Email: anna@mailserver.com + Phone: 1234567890 + +该文本行的流可以这样处理: + +```java +InputStream input = … ; // get the InputStream from the client socket +BufferedReader reader = new BufferedReader(new InputStreamReader(input)); +String nameLine = reader.readLine(); +String ageLine = reader.readLine(); +String emailLine = reader.readLine(); +String phoneLine = reader.readLine(); +``` + +请注意处理状态由程序执行多久决定。换句话说,一旦reader.readLine()方法返回,你就知道肯定文本行就已读完, readline()阻塞直到整行读完,这就是原因。你也知道此行包含名称;同样,第二个readline()调用返回的时候,你知道这行包含年龄等。 正如你可以看到,该处理程序仅在有新数据读入时运行,并知道每步的数据是什么。一旦正在运行的线程已处理过读入的某些数据,该线程不会再回退数据(大多如此) + + + +而一个NIO的实现会有所不同,下面是一个简单的例子: + +```java +ByteBuffer buffer = ByteBuffer.allocate(48); +int bytesRead = inChannel.read(buffer); +``` + +注意第二行,从通道读取字节到ByteBuffer。当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内。你所知道的是,该缓冲区包含一些字节,这使得处理有点困难。 +假设第一次 read(buffer)调用后,读入缓冲区的数据只有半行,例如,“Name:An”,你能处理数据吗?显然不能,需要等待,直到整行数据读入缓存,在此之前,对数据的任何处理毫无意义。 + +所以,你怎么知道是否该缓冲区包含足够的数据可以处理呢?好了,你不知道。发现的方法只能查看缓冲区中的数据。其结果是,在你知道所有数据都在缓冲区里之前,你必须检查几次缓冲区的数据。这不仅效率低下,而且可以使程序设计方案杂乱不堪。例如: + +```java +ByteBuffer buffer = ByteBuffer.allocate(48); +int bytesRead = inChannel.read(buffer); +while(! bufferFull(bytesRead) ) { + bytesRead = inChannel.read(buffer); +} +``` + +bufferFull()方法必须跟踪有多少数据读入缓冲区,并返回真或假,这取决于缓冲区是否已满。换句话说,如果缓冲区准备好被处理,那么表示缓冲区满了。 + +bufferFull()方法扫描缓冲区,但必须保持在bufferFull()方法被调用之前状态相同。如果没有,下一个读入缓冲区的数据可能无法读到正确的位置。这不是不可能的,但却是需要注意的又一问题。 + +如果缓冲区已满,它可以被处理。如果它不满,并且在你的实际案例中有意义,你或许能处理其中的部分数据。但是许多情况下并非如此。 + +### 用来处理数据的线程数 + +NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 + +如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。 + +如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。 + diff --git a/_posts/2014-01-02-linux-print.md b/_posts/2014-01-02-linux-print.md new file mode 100644 index 0000000..0888058 --- /dev/null +++ b/_posts/2014-01-02-linux-print.md @@ -0,0 +1,138 @@ +--- +layout: post +title: Linux打印 +tags: Linux 打印 +categories: Linux +--- + + +* TOC +{:toc} + +# echo + +echo是Shell的一个内部指令,用于在屏幕上打印出指定的字符串。 + +## 参数 + +`-n` 不要在最后自动换行 + +`-e` 若字符串中出现以下字符,则特别加以处理,而不会将它当成一般 + +文字输出: +>` \a` 发出警告声; +> +>` \b` 删除前一个字符; +> +>` \c` 最后不加上换行符号; +> +>` \f` 换行但光标仍旧停留在原来的位置; +> +>` \n` 换行且光标移至行首; +> +>` \r` 光标移至行首,但不换行; +> +>` \t` 插入tab; +> +>` \v` 与\f相同; +> +>` \\` 插入\字符; +> +>` \nnn` 插入nnn(八进制)所代表的ASCII字符; + +`–help` 显示帮助 + +`–version` 显示版本信息 + +## 原样输出字符串 + +若需要原样输出字符串(不进行转义),请使用单引号 + + echo '$name\"' + +## 显示命令执行结果 + + echo `date` + +# printf + +printf 命令用于格式化输出, 是echo命令的增强版。它是C语言printf()库函数的一个有限的变形,并且在语法上有些不同。 + +注意:printf 由 POSIX 标准所定义,移植性要比 echo 好。 + +printf 不像 echo 那样会自动换行,必须显式添加换行符(\n)。 + + $printf "Hello, Shell\n" + Hello, Shell + $ + +printf 命令的语法 + +`printf format-string [arguments...]` + +format-string 为格式控制字符串,arguments 为参数列表。 + +这里仅说明与C语言printf()函数的不同: + +* printf 命令不用加括号 + +* format-string 可以没有引号,但最好加上,单引号双引号均可。 + +* 参数多于格式控制符(%)时,format-string 可以重用,可以将所有参数都转换。 + +* arguments 使用空格分隔,不用逗号。 + +注意,根据POSIX标准,浮点格式%e、%E、%f、%g与%G是“不需要被支持”。这是因为awk支持浮点预算,且有它自己的printf语句。这样Shell程序中需要将浮点数值进行格式化的打印时,可使用小型的awk程序实现。然而,内建于bash、ksh93和zsh中的printf命令都支持浮点格式。 + +## 参数 + +`--help`:在线帮助; + +`--version`:显示版本信息。 + +## 格式替代符 + +`%b` 相对应的参数被视为含有要被处理的转义序列之字符串。 + +`%c` ASCII字符。显示相对应参数的第一个字符 + +`%d`, `%i` 十进制整数 + +`%e`, `%E`, `%f` 浮点格式 + +`%g` `%e`或`%f`转换,看哪一个较短,则删除结尾的零 + +`%G` `%E`或`%f`转换,看哪一个较短,则删除结尾的零 + +`%o` 不带正负号的八进制值 + +`%s` 字符串 + +`%u` 不带正负号的十进制值 + +`%x` 不带正负号的十六进制值,使用a至f表示10至15 %X 不带正负号的十六进制值,使用A至F表示10至15 + +`%%` 字面意义的% + +## 转义 + +`\a` 警告字符,通常为ASCII的BEL字符 + +`\b` 后退 + + +`\c` 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略 + +`\f` 换页(formfeed) + +`\n` 换行 + +`\r` 回车(Carriage return) + +`\t` 水平制表符 + +`\v` 垂直制表符 + +`\\` 一个字面上的反斜杠字符 + +`\ddd` 表示1到3位数八进制值的字符,仅在格式字符串中有效 \0ddd 表示1到3位的八进制值字符 \ No newline at end of file diff --git a/_posts/2014-01-05-vim.md b/_posts/2014-01-05-vim.md new file mode 100644 index 0000000..1263674 --- /dev/null +++ b/_posts/2014-01-05-vim.md @@ -0,0 +1,267 @@ +--- +layout: post +title: VIM +tags: VIM Linux +categories: special +--- + + +* TOC +{:toc} + +## 1.切换模式 + +`i` → Insert 模式,在光标前插入 + +`ESC` → 回到 Normal 模式,Normal 模式下,所有键都是功能键 + +`:help ` → 显示相关命令的帮助。你也可以就输入 :help 而不跟命令。(退出帮助需要输入:q) + +## 2.存盘 & 退出 + +`:e file` → 打开一个文件 + +`:w` → 存盘 + +`:saveas file` → 另存为 + +`:x`, `ZZ` 或 `:wq` → 保存并退出 (:x 表示仅在需要时保存,ZZ不需要输入冒号并回车) + +`:q!` → 退出不保存 + +`:qa!` 强行退出所有的正在编辑的文件,就算别的文件有更改。 + +`:bn` 和 `:bp` → 你可以同时打开很多文件,使用这两个命令来切换下一个或上一个文件。(`:n`到下一个文件) + +## 3.插入 + +`a` → 在光标后插入 + +`o` → 在当前行后插入一个新行 + +`O` → 在当前行前插入一个新行 + +`cw` → 替换从光标所在位置后到一个单词结尾的字符 + +## 4.删除 + +`x` → 删当前光标所在的一个字符。 + +`dd` → 删除当前行,并把删除的行存到剪贴板里 + +`d` → 删除,常用于组合 + +## 5.拷贝 & 粘贴 + +`yy` → 拷贝当前行当行于 ddP + +`p` → 粘贴(小写后) + +`P` → 粘贴(大写前) + +`y` → 拷贝,常用于组合 + +>vim有12个粘贴板,分别是`0 1 2 ... 9 a " +`用`:reg`命令可以查看各个粘贴板里的内容,在vim中简单用`y`只是复制到`"`粘贴板里,同样用`p`粘贴的也是这个粘贴板里的内容。 +> +>* `"Ny` 指定`N`粘贴板复制(主意引号) +> +>* `+` 粘贴板是系统粘贴板,`"+y`复制,`"+p`粘贴 + + +## 6.定位 + +`hjkl` (←↓↑→) + +### 6.1 行内定位 + +`0` → 数字零,到行头 + +`^` → 到本行第一个不是blank字符的位置(所谓blank字符就是空格,tab,换行,回车等) + +`$` → 到本行行尾 + +`g_` → 到本行最后一个不是blank字符的位置。 + +`fa` → 到本行下一个为`a`的字符处 + +`Fa` → 到本行上一个为`a`的字符处 + +`ta` → 到`a`前的第一个字符 + +`Ta` → 到`a`后的第一个字符 + +### 6.2 行间定位 + +`NG` → 到第 `N` 行 (命令中的G是大写的) + +`:N` → 到第 `N` 行 + +`gg` → 到第一行。(相当于1G,或 :1) + +`G` → 到最后一行。 + +### 6.3 全文定位 + +`w` → 到下一个单词的开头。(默认单词形式) + +`e` → 到当前单词的结尾。(默认单词形式) + +`W` → 到下一个单词的开头。(包含空格`?`) + +`E` → 到当前单词的结尾。(包含空格`?`) + +`%` → 到匹配的括号处,包括 `( { [` (需要把光标先移到括号上) + +`*` → 到`下一个`匹配单词 + +`# ` → 到`上一个`匹配单词 + +`/pattern` → 搜索 `pattern` 的字符串(需要回车,如果搜索出多个匹配,可按`n`键到下一个) + +## 7.Undo & Redo + +`u` → undo + +``→ redo + +## 8.重复 + +`.` → (小数点) 可以重复`上一次`的命令 + +`N ` → 重复某个命令`N次`,command可以为`.` + +>使用`.`时,若上一次的命令为`N `,则原样执行`N ` +> +>使用`N `时,若`command`为`.`,则`N`会覆盖`.`自带的次数 + +## 9.组合 + +* `` + +>例如 +> +>`0y$` → 从行头拷贝到行尾 +> +>`ye` → 从当前位置拷贝到本单词的最后一个字符 +> +>`y2/foo` → 拷贝2个foo之间的内容 + +## 10.区域选择 + +`Na` 包括object + +`Ni` 不包括object + +* `action` 可以是任何的命令,如 d (删除), y (拷贝), v (可以视模式选择)。 + +* `object` 可能是: `w` 一个单词, `W` 一个以空格为分隔的单词, `s` 一个句字, `p` 一个段落;也可以是成对出现的字符:`" ' ) } ]` + +* `N` 表示选取第N层,不写默认为1 + + +## 11.自动提示 + +`` 或 `` + +## 12.宏录制 + +`qa` → 把操作记录在寄存器 `a` + +`q` → 停止录制 + +`@a` → replay`a`寄存器中的宏。 + +`@@` → replay最新录制的宏。 + +>示例: +> +>在一个只有一行且这一行只有“1”的文本中,键入如下命令: +> +>`qaYpq` +> +>`qa` 开始录制 +> +>`Yp` 复制行. +> +>`` 增加1. +> +>`q` 停止录制. + +这个宏的作用是:复制上一行并增加1 + +测试: + +@a → 在1下面写下 2 + +@@ → 在2 正面写下3 + +现在做 100@@ 会创建新的100行,并把数据增加到 103. + +## 13.可视化选择 + +`v` 可视 + +`V` 可视行 + +`` 可视块 + +选择了可视化范围后,可做如下操作: + +>* `J` → 把所有的行连接起来(变成一行) +> +>* `<` → 左缩进 +> +>* `>` → 右缩进 +> +>* `=` → 自动缩进 + +在所有被选择的行后加上点东西: + +`` + +选中行 + +`$` 到行最后(不加将在每行行首编辑) + +`A` 块操作中进入插入模式 + +输入 + +`ESC` + +## 14.分屏 + +`split` → 创建分屏 + +`vsplit` → 创建垂直分屏 + +`方向` → 方向可以是 hjkl 或 ←↓↑→,用来切换分屏。 + +`_ ` → 最大化尺寸 + +`|` → 垂直分屏最大化尺寸 + +`+` → 增加尺寸 + +`-` → 减小尺寸 + + +--- + +## 其他 + +帮助文档 → `:help usr_02.txt` + +gvim 启动设置(显示行号,配色,代码高亮): + +>编辑安装目录下`_vimrc`文件 +> +>添加如下代码 +> +>set nu! +> +>colorscheme desert +> +>syntax enable +> +>syntax on diff --git a/_posts/2014-01-25-linux-filecmd.md b/_posts/2014-01-25-linux-filecmd.md new file mode 100644 index 0000000..e2171b3 --- /dev/null +++ b/_posts/2014-01-25-linux-filecmd.md @@ -0,0 +1,405 @@ +--- +layout: post +title: Linux目录与文件 +tags: Linux 命令 +categories: Linux +--- + + +* TOC +{:toc} + +>`.` +> +>代表此层目录 +> +>`..` +> +>代表上一层目录 +> +>`-` +> +>代表前一个工作目录 +> +>`~` +> +>代表『目前使用者身份』所在的家目录 +> +>`~account` +> +>代表 account 这个使用者的家目录(account是个帐号名称) + +--- + +## `cd ` + +**变换目录** + +--- + +## `pwd` + +**显示目前所在的目录** + +[root@www ~]# `pwd [-Pl]` + +选项与参数: + +`-P`:显示出真实的路径,而非使用连结 (link) 路径。 + +`-l ` : 显示逻辑路径(默认) + +--- + +## `mkdir` + +**创建新目录** + +[root@www ~]# `mkdir [-mp]` 目录名称 + +选项与参数: + +`-m` :配置文件的权限喔!直接配置,不需要看默认权限 (umask) 的脸色~ + +`-p` :帮助你直接将所需要的目录(包含上一级目录)递归创建起来! + +--- + +## `rmdir` + +**删除『空』的目录** + +[root@www ~]# `rmdir [-p]` 目录名称 + +选项与参数: + +`-p` :连同上一级『空的』目录也一起删除 + +--- + +## `$PATH` + +**环境变量** + +不同身份使用者默认的PATH不同,默认能够随意运行的命令也不同(如root与vbird); + +PATH是可以修改的,所以一般使用者还是可以透过修改PATH来运行某些位於/sbin或/usr/sbin下的命令来查询; + +使用绝对路径或相对路径直接指定某个命令的档名来运行,会比搜寻PATH来的正确; + +命令应该要放置到正确的目录下,运行才会比较方便;本目录(.)最好不要放到PATH当中。 + +--- + +## `ls` + +**查看文件与目录** + +[root@linux ~]# `ls [-aAdfFhilRS]` 目录名称 + +[root@linux ~]# `ls [--color={none,auto,always}]` 目录名称 + +[root@linux ~]# `ls [--full-time]` 目录名称 + +选项与参数: + +`-a` :全部的档案,连同隐藏档( 开头为 . 的档案) 一起列出来~ + +`-A` :全部的档案,连同隐藏档,但不包括 . 与 .. 这两个目录,一起列出来~ + +`-d` :仅列出目录本身,而不是列出目录内的档案数据 + +`-f` :直接列出结果,而不进行排序 (ls 预设会以档名排序!) + +`-F` :根据档案、目录等信息,给予附加数据结构 + +>例如: `*`:代表可执行档; `/`:代表目录; `=`:代表 socket 档案; `|`:代表 FIFO 档案 + +`-h` :将档案容量以人类较易读的方式(例如 GB, KB 等等)列出来 + +`-i` :列出 inode 位置,而非列出档案属性 + +`-l` :长数据串行出,包含档案的属性等等数据; + +`-n` :列出 UID 与 GID 而非使用者与群组的名称 + +`-r` :将排序结果反向输出 + +`-R` :连同子目录内容一起列出来; + +`-S` :以档案容量大小排序! + +`-t` :依时间排序 + +`--color=never` :不要依据档案特性给予颜色显示; + +`--color=always` :显示颜色 + +`--color=auto` :让系统自行依据设定来判断是否给予颜色 + +`--full-time` :以完整时间模式 (包含年、月、日、时、分) 输出 + +`--time={atime,ctime}` :输出 access 时间或 改变权限属性时间 (ctime) 而非内容变更时间 (modification time) + +--- + +## `cp` + +**复制文件或目录** + +[root@www ~]# `cp [-adfilprsu]` 来源档(source) 目标档(destination) + +[root@www ~]# `cp [options]` source1 source2 source3 .... directory + +选项与参数: + +`-a ` :相当於 -pdr 的意思,至於 pdr 请参考下列说明;(常用) + +`-d` :若来源档为连结档的属性(link file),则复制连结档属性而非文件本身; + +`-f` :为强制(force)的意思,若目标文件已经存在且无法开启,则移除后再尝试一次; + +`-i` :若目标档(destination)已经存在时,在覆盖时会先询问动作的进行(常用) + +`-l` :进行硬式连结(hard link)的连结档创建,而非复制文件本身; + +`-p` :连同文件的属性一起复制过去,而非使用默认属性(备份常用); + +`-r` :递回持续复制,用於目录的复制行为;(常用) + +`-s` :复制成为符号连结档 (symbolic link),亦即『捷径』文件; + +`-u` :若 destination 比 source 旧才升级 destination ! + +最后需要注意的,如果来源档有两个以上,则最后一个目的档一定要是『目录』才行! + +--- + +## `rm` + +**移除文件或目录** + +[root@www ~]# `rm [-fir]` 文件或目录 + +选项与参数: + +`-f` :就是 force 的意思,忽略不存在的文件,不会出现警告信息; + +`-i` :互动模式,在删除前会询问使用者是否动作 + +`-r` :递回删除啊!最常用在目录的删除了!这是非常危险的选项!!! + +--- + +## `mv` + +**移动文件与目录,或更名** + +[root@www ~]# `mv [-fiu]` source destination + +[root@www ~]# `mv [options]` source1 source2 source3 .... directory + +选项与参数: + +`-f` :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖; + +`-i` :若目标文件 (destination) 已经存在时,就会询问是否覆盖! + +`-u` :若目标文件已经存在,且 source 比较新,才会升级 (update) + +--- + +## `basename`,`dirname` + +**取得路径的文件名称与目录名称** + +[root@www ~]# `basename` /etc/sysconfig/network + +network <== 很简单!就取得最后的档名~ + +[root@www ~]# `dirname` /etc/sysconfig/network + +/etc/sysconfig <== 取得的变成目录名了! + +--- + +## `cat` + +**查看文件** + +[root@www ~]# `cat [-AbEnTv]` + +选项与参数: + +`-A` :相当於 -vET 的整合选项,可列出一些特殊字符而不是空白而已; + +`-b` :列出行号,仅针对非空白行做行号显示,空白行不标行号! + +`-E` :将结尾的断行字节 $ 显示出来; + +`-n` :列印出行号,连同空白行也会有行号,与 -b 的选项不同; + +`-T` :将 [tab] 按键以 ^I 显示出来; + +`-v` :列出一些看不出来的特殊字符 + +## `tac` + +**反向显示** + +--- + +## `nl` + +**添加行号显示** + +[root@www ~]# `nl [-bnw]` 文件 + +选项与参数: + +`-b` :指定行号指定的方式,主要有两种: + +> `-b a` :表示不论是否为空行,也同样列出行号(类似 cat -n); +> +> `-b t` :如果有空行,空的那一行不要列出行号(默认值); + + + +`-n` :列出行号表示的方法,主要有三种: + +> `-n ln` :行号在萤幕的最左方显示; +> +> `-n rn` :行号在自己栏位的最右方显示,且不加 0 ; +> +> `-n rz` :行号在自己栏位的最右方显示,且加 0 ; + + + +`-w` :行号栏位的占用的位数。 + +--- + +## `more` + +**一页一页翻动** + +[root@www ~]# `more` /etc/man.config + +在more查看文件时可在最底行输入: + +空白键 (`space`):代表向下翻一页; + +`Enter` :代表向下翻『一行』; + +`/`字串 :代表在这个显示的内容当中,向下搜寻『字串』这个关键字; + +`:f` :立刻显示出档名以及目前显示的行数; + +`q` :代表立刻离开 more ,不再显示该文件内容。 + +`b` 或 `[ctrl]-b` :代表往回翻页,不过这动作只对文件有用,对管线无用。 + +--- + +## `less` + +**一页一页翻动** + +[root@www ~]# `less` /etc/man.config + +在more查看文件时可在最底行输入: + +空白键(`space`) :向下翻动一页; + +[`pagedown`]:向下翻动一页; + +[`pageup`] :向上翻动一页; + +`/`字串 :向下搜寻『字串』的功能; + +`?`字串 :向上搜寻『字串』的功能; + +`n` :重复前一个搜寻 (与 / 或 ? 有关!) + +`N` :反向的重复前一个搜寻 (与 / 或 ? 有关!) + +`q` :离开 less 这个程序; + +--- + +## `head` + +**取出前面几行** + +[root@www ~]# `head [-n number]` 文件 + +选项与参数: + +`-n` :后面接数字,代表显示几行的意思 + +--- + +## `tail` + +**取出后面几行** + +[root@www ~]# `tail [-n number]` 文件 + +选项与参数: + +`-n` :后面接数字,代表显示几行的意思 + +`-f` :表示持续侦测后面所接的档名,要等到按下[ctrl]-c才会结束tail的侦测 + +--- + +## `od` + +**非纯文字档** + +[root@www ~]# `od [-t TYPE]` 文件 + +选项或参数: + +`-t` :后面可以接各种『类型 (TYPE)』的输出,例如: + +>`a` :利用默认的字节来输出; +> +>`c` :使用 ASCII 字节来输出 +> +>`d`[size] :利用十进位(decimal)来输出数据,每个整数占用 size bytes +> +>`f`[size] :利用浮点数值(floating)来输出数据,每个数占用 size bytes +> +>`o`[size] :利用八进位(octal)来输出数据,每个整数占用 size bytes +> +>`x`[size] :利用十六进位(hexadecimal)来输出数据,每个整数占用 size bytes + +--- + +## `touch` + +**修改文件时间或建置新档** + +[root@www ~]# `touch [-acdmt]` 文件 + +选项与参数: + +`-a` :仅修订 access time; + +`-c` :仅修改文件的时间,若该文件不存在则不创建新文件; + +`-d` :后面可以接欲修订的日期而不用目前的日期,也可以使用 --date="日期或时间" + +`-m` :仅修改 mtime ; + +`-t` :后面可以接欲修订的时间而不用目前的时间,格式为[YYMMDDhhmm] + +--- + +## `file` + +**查看文件类型** + +[root@www ~]# `file` 文件 + diff --git a/_posts/2014-02-08-linux-power.md b/_posts/2014-02-08-linux-power.md new file mode 100644 index 0000000..26f1e46 --- /dev/null +++ b/_posts/2014-02-08-linux-power.md @@ -0,0 +1,185 @@ +--- +layout: post +title: Linux权限管理 +tags: Linux 命令 +categories: Linux +--- + +* TOC +{:toc} + +## `chmod` + +**改变权限** + +*数字法* + +root@www ~]# `chmod [-R] xyz` 文件或目录 + +选项与参数: + +`xyz` : 就是刚刚提到的数字类型的权限属性,为 rwx 属性数值的相加。 + +`-R` : 进行递归(recursive)的持续变更,亦即连同次目录下的所有文件都会变更 + +*符号法* + +chmod + +ugoa + ++(加入) -(除去) =(设定) + +rwx + +文件或目录 + +>例: +> +>[root@www ~]# chmod u=rwx,go=rx .bashrc +> +>\# 注意!那个 u=rwx,go=rx 是连在一起的,中间并没有任何空格! + +--- + +## `chgrp ` + + +**改变所属群组** + +chgrp [root@www ~]# `chgrp [-R] filename ...` + +选项与参数: + +`-R` : 进行递归(recursive)的持续变更,亦即连同次目录下的所有文件、目录 都更新成为这个群组之意。常常用在变更某一目录内所有的文件之情况。 + +--- + +## `chown` + +**改变文件拥有者** + +[root@www ~]# `chown [-R]` 账号名称 文件或目录 + +[root@www ~]# `chown [-R]` 账号名称:组名 文件或目录 + +选项与参数: + +`-R` : 进行递归(recursive)的持续变更,亦即连同次目录下的所有文件都变更 + +--- + +## `chattr` + +**配置文件隐藏属性** + +[root@www ~]# `chattr [+-=][ASacdistu]` 文件或目录名称 + +选项与参数: + +`+` :添加某一个特殊参数,其他原本存在参数则不动。 + +`-` :移除某一个特殊参数,其他原本存在参数则不动。 + +`=` :配置一定,且仅有后面接的参数 + +>`A` :当配置了 A 这个属性时,若你有存取此文件(或目录)时,他的存取时间 atime将不会被修改,可避免I/O较慢的机器过度的存取磁碟。这对速度较慢的计算机有帮助。 + +`S` :一般文件是非同步写入磁碟的(原理请参考第五章sync的说明),如果加上 S 这个属性时,当你进行任何文件的修改,该更动会『同步』写入磁碟中。 + +`a` :当配置 a 之后,这个文件将只能添加数据,而不能删除也不能修改数据,只有root才能配置这个属性。 + +`c` :这个属性配置之后,将会自动的将此文件『压缩』,在读取的时候将会自动解压缩,但是在储存的时候,将会先进行压缩后再储存(看来对於大文件似乎蛮有用的!) + +`d` :当 dump 程序被运行的时候,配置 d 属性将可使该文件(或目录)不会被 dump 备份 + +`i` :这个 i 可就很厉害了!他可以让一个文件『不能被删除、改名、配置连结也无法写入或新增数据!』对於系统安全性有相当大的助益!只有 root 能配置此属性 + +`s` :当文件配置了 s 属性时,如果这个文件被删除,他将会被完全的移除出这个硬盘空间,所以如果误删了,完全无法救回来了喔! + +`u` :与 s 相反的,当使用 u 来配置文件时,如果该文件被删除了,则数据内容其实还存在磁碟中,可以使用来救援该文件喔! + +注意:属性配置常见的是 a 与 i 的配置值,而且很多配置值必须要身为 root 才能配置 + +--- + +## `lsattr` + +**显示文件隐藏属性** + +[root@www ~]# `lsattr [-adR]` 文件或目录 + +选项与参数: + +`-a` :将隐藏档的属性也秀出来; + +`-d` :如果接的是目录,仅列出目录本身的属性而非目录内的档名; + +`-R` :连同子目录的数据也一并列出来! + +--- + +## `umask` + +**被排除的权限默认值** + +*目前使用者在创建文件或目录时候的被排除的权限默认值* + +[root@www ~]# `umask` 显示`数字`型态的权限配置分数 + +0022 <==与一般权限有关的是后面三个数字,表示在777中被排除的权限,即该目录中新增文件或目录的默认权限为755,又因为新增文件默认不具有x权限,所以该目录中新增目录的默认权限为755,文件的默认权限为644 + +[root@www ~]# `umask -S` 以`符号`类型的方式来显示出权限 + +u=rwx,g=rx,o=rx + +[root@www ~]# `umask 002` `指定`权限配置分数 + +`!` 异或运算可排除权限 + +--- + +## 文件特殊权限 + +**`SUID`** + +>`1` SUID 权限仅对二进位程序(binary program)有效;(对目录无效) + +>`2` 运行者对於该程序需要具有 x 权限; + +>`3` 运行者将具有该程序拥有者 (owner) 的权限,该权限仅在运行该程序的过程中有效 (run-time) + +>例如:/etc/shadow只有root能够访问,但是普通用户对于passwd有s权限,passwd的owner是root,所以普通用户可以在运行passwd时暂时获得root权限对/etc/shadow进行操作。 + +`!` SUID权限指的是文件权限中owner部分x权限的特殊版 + +`! chmod 4---` 可以赋予SUID权限,所属用户的x位将变成s, 若不具备x权限,s将变成S + +**`SGID`** + +*当为文件时* + +>`1` SGID 仅对二进位程序有用; +> +>`2` 运行者对於该程序需具有x 权限; +> +>`3` 运行者在运行的过程中将会获得该程序群组(group)的权限,该权限仅在运行该程序的过程中有效 (run-time) --- 基本同SUID + +*当为目录时* + +>`1` 使用者若对於此目录具有 r 与 x 的权限时,该使用者能够进入此目录; +> +>`2` 使用者在此目录下的有效群组(effective group)将会变成该目录所属的群组;用途:若使用者在此目录下具有 w 的权限(可以新建文件),则使用者所创建的新文件的群组与此目录所属的群组相同。 + +`!` SGID权限指的是文件或目录权限中group部分x权限的特殊版 +`! chmod 2---` 可以赋予SGID权限,所属组的x位将变成s, 若不具备x权限,s将变成S + +**`SBIT`** + +·SBIT权限仅对目录有效;(对文件无效) + +·使用者对於此目录需要具有 w, x 权限; + +·当使用者在该目录下创建文件或目录时,仅有自己与 root 才有权力删除该文件 + +`! chmod 1---` 可以赋予SBIT权限,其他的x位将变成t diff --git a/_posts/2014-02-13-linux-ps.md b/_posts/2014-02-13-linux-ps.md new file mode 100644 index 0000000..b84fd32 --- /dev/null +++ b/_posts/2014-02-13-linux-ps.md @@ -0,0 +1,545 @@ +--- +layout: post +title: Linux进程管理 +tags: Linux 进程管理 +categories: Linux +--- + + +* TOC +{:toc} + +## `&` + + +**将命令放在后台运行** + +command & + +此时将会产生1个任务编号与一个PID,命令执行完成后将会在前台出现提示 + +>后台执行的任务如果存在信息输出,最好将其写入到文件,否则将会在前台显示,影响操作 + +--- + +## `[ctrl]-z` + +**将命令放在后台暂停** + +此时将会产生1个任务编号及其命令 + +--- + +## `jobs` + +**查看后台任务状态** + +[root@www ~]# `jobs [-lrs]` + +选项与参数: + +`-l` :除了列出 job number 与命令串之外,同时列出 PID 的号码; + +`-r` :仅列出正在背景 run 的工作; + +`-s` :仅列出正在背景当中暂停 (stop) 的工作。 + +>`+` 代表最近被放到背景的工作号码, `-` 代表最近最后第二个被放置到背景中的工作号码,其余没有符号 + +--- + +## `fg` + +**将后台任务放到前台** + +[root@www ~]# `fg %jobnumber` + +选项与参数: + +`%jobnumber` :jobnumber 为任务号码(数字),那个 % 是可有可无的! + +--- + +## `bg` + +**将后台暂停的任务变为运行中** + +[root@www ~]# `bg %jobnumber` + +--- + +## `kill` + +**移除任务** + +[root@www ~]# `kill -signal %jobnumber` + +[root@www ~]# `kill -l` + +选项与参数: + +`-l` :这个是 L 的小写,列出目前 kill 能够使用的讯号 (signal) 有哪些 + +`signal` :代表给予后面接的那个工作什么样的指示罗!用 man 7 signal 可知: + +> `-1` :重新读取一次参数的配置档 (类似 reload); +> +> `-2` :代表与由键盘输入 [ctrl]-c 同样的动作; +> +> `-9` :立刻强制删除一个工作; +> +> `-15`:以正常的程序方式终止一项工作。与 -9 是不一样的。 + +--- + +## `nohup` + +**离线任务** + +>`!特别说明` 前面的几个命令中所谓的"背景”,都是指终端机背景(不会被ctrl+c中断),并非系统背景,一旦退出终端,任务立即终止。`nohup`可以在退出终端后继续执行任务。 + +[root@www ~]# `nohup` [命令与参数] <==在终端机前台中工作 + +[root@www ~]# `nohup` [命令与参数] & <==在终端机后台中工作 + +--- + +## `pstree` + +**显示进程树** + +[root@www ~]# `pstree [-A|U] [-up]` + +选项与参数: + +`-A` :各程序树之间的连接以 ASCII 字节来连接; + +`-U` :各程序树之间的连接以万国码的字节来连接。在某些终端介面下可能会有错误; + +`-p` :并同时列出每个 process 的 PID; + +`-u` :并同时列出每个 process 的所属帐号名称。 + +--- + +## `ps` + +**将某个时间点的程序运行情况撷取下来** + +[root@www ~]# `ps aux` <==观察系统所有的程序数据 + +[root@www ~]# `ps -lA` <==也是能够观察所有系统的数据 + +[root@www ~]# `ps axjf` <==连同部分程序树状态 + +选项与参数: + +`-A` :所有的 process 均显示出来,与 -e 具有同样的效用; + +`-a` :不与 terminal 有关的所有 process ; + +`-u` :有效使用者 (effective user) 相关的 process ; + +`x` :通常与 a 这个参数一起使用,可列出较完整资讯。 + +输出格式规划: + +`l` :较长、较详细的将该 PID 的的资讯列出; + +`j` :工作的格式 (jobs format) + +`-f` :做一个更为完整的输出。 + +--- + +### `ps -l` + +**仅观察自己的 bash 相关程序** + +`F`:代表这个程序旗标 (process flags),说明这个程序的总结权限,常见号码有: + +>若为 `4` 表示此程序的权限为 root ; +> +>若为 `1` 则表示此子程序仅进行复制(fork)而没有实际运行(exec)。 + +`S`:代表这个程序的状态 (STAT),主要的状态有: + +>`R` (Running):该程序正在运行中; +> +>`S` (Sleep):该程序目前正在睡眠状态(idle),但可以被唤醒(signal)。 +> +>`D` :不可被唤醒的睡眠状态,通常这支程序可能在等待 I/O 的情况(ex>列印) +> +>`T` :停止状态(stop),可能是在工作控制(背景暂停)或除错 (traced) 状态; +> +>`Z` (Zombie):僵尸状态,程序已经终止但却无法被移除至内存外。 + +`UID/PID/PPID`:代表此程序被该 UID 所拥有/程序的 PID 号码/此程序的父程序 PID 号码 + +`C`:代表 CPU 使用率,单位为百分比 + +`PRI/NI`:Priority/Nice 的缩写,代表此程序被 CPU 所运行的优先顺序,数值越小代表该程序越快被 CPU 运行 + +`ADDR/SZ/WCHAN`:都与内存有关,ADDR 是 kernel function,指出该程序在内存的哪个部分,如果是个 running 的程序,一般就会显示『 - 』 / SZ 代表此程序用掉多少内存 / WCHAN 表示目前程序是否运行中,同样的, 若为 - 表示正在运行中。 + +`TTY`:登陆者的终端机位置,若为远程登陆则使用动态终端介面 (pts/n); + +`TIME`:使用掉的 CPU 时间,注意,是此程序实际花费 CPU 运行的时间,而不是系统时间; + +`CMD`:就是 command 的缩写,造成此程序的触发程序之命令为何 + +--- + +### `ps aux` + +**观察系统所有程序** + +`USER`:该 process 属於那个使用者帐号的? + +`PID` :该 process 的程序识别码。 + +`%CPU`:该 process 使用掉的 CPU 资源百分比; + +`%MEM`:该 process 所占用的实体内存百分比; + +`VSZ` :该 process 使用掉的虚拟内存量 (Kbytes) + +`RSS` :该 process 占用的固定的内存量 (Kbytes) + +`TTY` :该 process 是在那个终端机上面运行,若与终端机无关则显示 ?,另外, tty1-tty6 是本机上面的登陆者程序,若为 pts/0 等等的,则表示为由网络连接进主机的程序。 + +`STAT`:该程序目前的状态,状态显示与 ps -l 的 S 旗标相同 (R/S/T/Z) + +`START`:该 process 被触发启动的时间; + +`TIME` :该 process 实际使用 CPU 运行的时间。 + +`COMMAND`:该程序的实际命令为何? + +>ps结果后接 `` 表示为僵尸程序 + +--- + +## `top` + +**动态观察程序的变化** + +[root@www ~]# `top [-d 数字] | top [-bnp]` + +选项与参数: + +`-d` :后面可以接秒数,就是整个程序画面升级的秒数。默认是 5 秒; + +`-b` :以批量的方式运行 top ,还有更多的参数可以使用,通常会搭配数据流重导向来将批量的结果输出成为文件。 + +`-n` :与 -b 搭配,意义是,需要进行几次 top 的输出结果。 + +`-p` :指定某些个 PID 来进行观察监测。 + +在 top 运行过程当中可以使用的按键命令: + +`?` :显示在 top 当中可以输入的按键命令; + +`P`:以 CPU 的使用资源排序显示; + +`M` :以 Memory 的使用资源排序显示; + +`N` :以 PID 来排序喔! + +`T` :由该 Process 使用的 CPU 时间累积 (TIME+) 排序。 + +`k` :给予某个 PID 一个讯号 (signal) + +`r` :给予某个 PID 重新制订一个 nice 值。 + +`q` :离开 top 软件的按键。 + +--- + +**`echo $$`** ( 查看bash的pid) + +--- + +## `signal` + +| 代号 | 名称 | 内容 | +|:----:|:----:|:----| +| 1 | SIGHUP | 启动被终止的程序,可让该 PID 重新读取配置,类似重新启动 | +| 2 | SIGINT | 相当於用键盘输入 [ctrl]-c 来中断一个程序 | +| 9 | SIGKILL | 强制中断一个程序,尚未完成的部分可能会有遗留物| +| 15 | SIGTERM | 正常结束程序,如果该程序已经发生问题,signal将失效 | +| 17 | SIGSTOP | 相当於用键盘输入 [ctrl]-z 来暂停一个程序 | + +--- + +## `kill` + +**杀死进程** + +[root@www ~]# `kill PID` + +`!特别说明`:kill后面直接加数字表示杀死进程,这与上面的移除任务用法是不同的 + +--- + +## `killall` + +**按程序启动命令杀死进程** + +[root@www ~]# ` killall [-iIe] [command name]` + +选项与参数: + +`-i` :interactive 互动的意思,删除时,会出现提示 + +`-e` :exact 准确的意思,后面接的 command name要与运行中程序的启动命令一致,但整个完整的命令不能超过 15 个字节(若程序启动时使用了参数,则程序名与后面接的参数作为整体) + +`-I` :命令名称(可能含参数)忽略大小写。 + + +--- + +## `nice` + +**指定nice值启动新程序** + +[root@www ~]# `nice [-n 数字] command` + +选项与参数: + +`-n` :后面接一个数值,数值的范围 -20 ~ 19。 + +--- + +## `renice` + +**重设指定程序的nice值** + +[root@www ~]# `renice [number] PID` + +选项与参数: + +`PID` :某个程序的 ID + +--- + +## `free` + +**观察内存使用情况** + +[root@www ~]# `free [-b|-k|-m|-g] [-t]` + +选项与参数: + +默认单位为 Kbytes,可以使用 `-b`(bytes),`-m`(Mbytes),`-k`(Kbytes),`-g`(Gbytes) 来显示单位 + +`-t` :显示实体内存与 swap 的总量。 + +--- + +## `uname` + +**查看系统核心信息** + +[root@www ~]# `uname [-asrmpi]` + +选项与参数: + +`-a` :所有系统相关的资讯,包括底下的数据都会被列出来; + +`-s` :系统核心名称 + +`-r` :核心的版本 + +`-m` :本系统的硬件名称,例如 i686 或 x86_64 等; + +`-p` :CPU 的类型,与 -m 类似,只是显示的是 CPU 的类型! + +`-i` :硬件的平台 (ix86) + +--- + +## `uptime` + +**观察系统启动时间与工作负载** + + +--- + +## `netstat` + +**追踪网络与Socket** + +[root@www ~]# `netstat -[atunlp]` + +选项与参数: + +`-a` :将目前系统上所有的连线、监听、Socket 数据都列出来 + +`-t` :列出 tcp 网络封包的数据 + +`-u` :列出 udp 网络封包的数据 + +`-n` :不以程序的服务名称,以端口号 (port number) 来显示; + +`-l` :列出目前正在网络监听 (listen) 的服务; + +`-p` :列出该网络服务的程序 PID + + +网络参数 + +`Proto` :网络的封包协议,主要分为 TCP 与 UDP 封包,相关数据请参考服务器篇; + +`Recv-Q`:非由使用者程序连结到此 socket 的复制的总 bytes 数; + +`Send-Q`:非由远程主机传送过来的 acknowledged 总 bytes 数; + +`Local Address` :本地端的 IP:port 情况 + +`Foreign Address`:远程主机的 IP:port 情况 + +`State` :连线状态,主要有创建(ESTABLISED)及监听(LISTEN); + +本机程序 + +`Proto` :一般就是 unix 啦; + +`RefCnt`:连接到此 socket 的程序数量; + +`Flags` :连线的旗标; + +`Type` :socket 存取的类型。主要有确认连线的 STREAM 与不需确认的 DGRAM 两种; + +`State` :若为 CONNECTED 表示多个程序之间已经连线创建。 + +`Path` :连接到此 socket 的相关程序的路径!或者是相关数据输出的路径。 + +--- + +## `dmesg` + +**查看核心产生的信息** + +--- + +## `vmstat` + +**侦测系统资源变化** + +[root@www ~]# `vmstat [-a] [间隔时间] [总次数]]` <==CPU/内存等资讯 + +[root@www ~]# ` vmstat [-fs]` <==内存相关 + +[root@www ~]# `vmstat [-S 单位]` <==配置显示数据的单位 + +[root@www ~]# `vmstat [-d]` <==与磁碟有关 + +[root@www ~]# `vmstat [-p 分割槽]` <==与磁碟有关 + +选项与参数: + +`-a` :使用 inactive/active(活跃与否) 取代 buffer/cache 的内存输出资讯; + +`-f` :启动到目前为止,系统复制 (fork) 的程序数; + +`-s` :将一些事件 (启动至目前为止) 导致的内存变化情况列表说明; + +`-S` :后面可以接单位,让显示的数据有单位。例如 K/M 取代 bytes 的容量; + +`-d` :列出磁碟的读写总量统计表 + +`-p` :后面列出分割槽,可显示该分割槽的读写总量统计表 + +>内存栏位 (`procs`) 的项目分别为: + +>>`r` :等待运行中的程序数量;`b`:不可被唤醒的程序数量。这两个项目越多,代表系统越忙碌 (因为系统太忙,所以很多程序就无法被运行或一直在等待而无法被唤醒之故)。 + +>内存栏位 (`memory`) 项目分别为: + +>>`swpd`:虚拟内存被使用的容量; `free`:未被使用的内存容量; `buff`:用於缓冲内存; `cache`:用於高速缓存。 这部份则与 `free` 是相同的。 + +>内存置换空间 (`swap`) 的项目分别为: + +>>`si`:由磁碟中将程序取出的量; `so`:由於内存不足而将没用到的程序写入到磁碟的 `swap` 的容量。 如果 si/so 的数值太大,表示内存内的数据常常得在磁碟与主内存之间传来传去,系统效能会很差! + +>磁碟读写 (`io`) 的项目分别为: + +>>`bi`:由磁碟写入的区块数量; `bo`:写入到磁碟去的区块数量。如果这部份的值越高,代表系统的 I/O 非常忙碌! + +>系统 (`system`) 的项目分别为: + +>>`in`:每秒被中断的程序次数; `cs`:每秒钟进行的事件切换次数;这两个数值越大,代表系统与周边设备的沟通非常频繁! 这些周边设备当然包括磁碟、网络卡、时间钟等。 + +>`CPU` 的项目分别为: + +>>`us`:非核心层的 CPU 使用状态; `sy`:核心层所使用的 CPU 状态; `id`:闲置的状态; `wa`:等待 I/O 所耗费的 CPU 状态; `st`:被虚拟机器 (virtual machine) 所盗用的 CPU 使用状态 (2.6.11 以后才支持)。 + +--- + +## `fuser` + +**查看使用指定的文件或文件系统的进程** + +[root@www ~]# `fuser [-umv] [-k [i] [-signal]]` file/dir + +选项与参数: + +`-u` :除了程序的 PID 之外,同时列出该程序的拥有者; + +`-m` :后面接的那个档名会主动的上提到该文件系统的最顶层,对 umount 不成功很有效! + +`-v` :可以列出每个文件与程序还有命令的完整相关性! + +`-k` :找出使用该文件/目录的 PID ,并试图以 SIGKILL 这个讯号给予该 PID; + +`-i` :必须与 -k 配合,在删除 PID 之前会先询问使用者意愿! + +`-signal`:例如 -1 -15 等等,若不加的话,默认是 SIGKILL (-9) 罗! + +>权限: + +`c` :此程序在当前的目录下(非次目录); + +`e` :可被触发为运行状态; + +`f` :是一个被开启的文件; + +`r` :代表顶层目录 (root directory); + +`F` :该文件被开启了,不过在等待回应中; + +`m` :可能为分享的动态函式库; + +--- + +## `lsof` + +**列出由程序开启的文件** + +[root@www ~]# `lsof [-aUu] [+d]` + +选项与参数: + +`-a` :多项数据需要『同时成立』才显示出结果时!(内连接`?`) + +`-U` :仅列出 Unix like 系统的 socket 文件类型; + +`-u` :后面接 username,列出该使用者相关程序所开启的文件; + +`+d` :后面接目录,即找出某个目录底下已经被开启的文件! + +--- + +## `pidof` + +**找出某个正在的运行程序的pid** + +[root@www ~]# `pidof [-sx] program_name...` + +选项与参数: + +`-s` :仅列出一个 PID 而不列出所有的 PID + +`-x` :同时列出该 program name 可能的 PPID 那个程序的 PID + diff --git a/_posts/2014-02-20-linux-boot.md b/_posts/2014-02-20-linux-boot.md new file mode 100644 index 0000000..5c7df3d --- /dev/null +++ b/_posts/2014-02-20-linux-boot.md @@ -0,0 +1,147 @@ +--- +layout: post +title: Linux启动流程 +tags: Linux boot +categories: Linux +--- + + +* TOC +{:toc} + +![](http://image.beekka.com/blog/201308/bg2013081708.png) + +# 1.加载内核 + +操作系统接管硬件以后,首先读入`/boot `目录下的内核文件。 + +# 2.启动初始化进程 + +内核文件加载以后,就开始运行第一个程序`/sbin/init`,它的作用是初始化系统环境。 + +# 3.确定运行级别 + +许多程序需要开机启动。它们在Windows叫做"服务"(service),在Linux就叫做"守护进程"(daemon)。 + +init进程的一大任务,就是去运行这些开机启动的程序。但是,不同的场合需要启动不同的程序,比如用作服务器时,需要启动Apache,用作桌面就不需要。Linux允许为不同的场合,分配不同的开机启动程序,这就叫做"`运行级别`"(runlevel)。也就是说,启动时根据"运行级别",确定要运行哪些程序。 + +Linux预置七种运行级别(0-6)。 + +* 0是关机, + +* 1是单用户模式(也就是维护模式), + +* 6是重启。 + +* 运行级别2-5,各个发行版不太一样,对于Debian来说,都是同样的多用户模式(也就是正常模式)。 + +init进程首先读取文件 /etc/inittab,它是运行级别的设置文件。第一行内容: + + id:2:initdefault: + +initdefault的值是2,表明系统启动时的运行级别为2。 + +每个运行级别在/etc目录下面,都有一个对应的子目录,指定要加载的程序。 + + /etc/rc0.d + /etc/rc1.d + /etc/rc2.d + /etc/rc3.d + /etc/rc4.d + /etc/rc5.d + /etc/rc6.d +    +"rc"表示run command(运行程序),"d"表示directory(目录) 。 + +/etc/rc2.d中指定的程序: + + $ ls /etc/rc2.d + README + S01motd + S13rpcbind + S14nfs-common + S16binfmt-support + S16rsyslog + S16sudo + S17apache2 + S18acpid + ...  + +除了第一个文件README以外,其他文件名都是"字母S+两位数字+程序名"的形式。 + +* 字母`S`表示Start,也就是启动的意思(启动脚本的运行参数为start),如果这个位置是字母`K`,就代表Kill(关闭),即如果从其他运行级别切换过来,需要关闭的程序(启动脚本的运行参数为stop)。 + +* 后面的两位数字表示处理顺序,数字越小越早处理,所以第一个启动的程序是motd,然后是rpcbing、nfs......数字相同时,则按照程序名的字母顺序启动,所以rsyslog会先于sudo启动。 + +# 4.加载开机启动顺序 + +七种预设的"运行级别"各自有一个目录,存放需要开机启动的程序。如果多个"运行级别"需要启动同一个程序,那么这个程序的启动脚本,就会在每一个目录里都有一个拷贝。这样会造成管理上的困扰:如果要修改启动脚本,岂不是每个目录都要改一遍? + +Linux的解决办法,就是七个 /etc/rcN.d 目录里列出的程序,都设为`链接文件`,指向另外一个目录 /etc/init.d ,真正的启动脚本都统一放在这个目录中。init进程逐一加载开机启动程序,其实就是运行这个目录里的启动脚本。 + +这样做的另一个好处,就是如果你要手动关闭或重启某个进程,直接到目录 /etc/init.d 中寻找启动脚本即可。 + +比如,我要重启Apache服务器,就运行下面的命令: + + $ sudo /etc/init.d/apache2 restart + +/etc/init.d 这个目录名最后一个字母d,是directory的意思,表示这是一个目录,用来与程序 /etc/init 区分。 + +# 5.用户登陆 + +用户的登录方式有三种: + +* 命令行登录 + +init进程调用getty程序(意为get teletype),让用户输入用户名和密码。输入完成后,再调用login程序,核对密码(Debian还会再多运行一个身份核对程序/etc/pam.d/login)。如果密码正确,就从文件 /etc/passwd 读取该用户指定的shell,然后启动这个shell。 + +* ssh登录 + +系统调用sshd程序(Debian还会再运行/etc/pam.d/ssh ),取代getty和login,然后启动shell。 + +* 图形界面登录 + +init进程调用显示管理器,Gnome图形界面对应的显示管理器为gdm(GNOME Display Manager),然后用户输入用户名和密码。如果密码正确,就读取/etc/gdm3/Xsession,启动用户的会话。 + +# 6.进入 login shell + +Debian默认的shell是Bash,它会读入一系列的配置文件,不同的登陆方式加载不同的配置文件 + +* 命令行登录:首先读入 /etc/profile,这是对所有用户都有效的配置;然后`依次`寻找下面三个文件,这是针对当前用户的配置。 + + ~/.bash_profile + ~/.bash_login + ~/.profile   + +`*`需要注意的是,这三个文件只要有一个存在,就不再读入后面的文件了。 + +* ssh登录:与命令行登录完全相同。 + +* 图形界面登录:只加载 /etc/profile 和 ~/.profile。~/.bash_profile 不管有没有,都不会运行。 + +# 7.打开 non-login shell + +进入 login shell完成后,Linux的启动过程就算结束了。 + +用户进入操作系统以后,常常会再手动开启一个shell。这个shell就叫做 non-login shell,意思是它不同于登录时出现的那个shell,不读取 /etc/profile 和 ~/.profile 等配置文件。 + +non-login shell 是用户最常接触的shell,它会读入用户自己的bash配置文件 ~/.bashrc。大多数时候,我们对于bash的定制,都是写在这个文件里面的。 + +`*` 如果不启动 non-login shell , ~/.bashrc 照样会运行,文件 ~/.profile 中存在下面的代码 + +~~~ +  if [ -n "$BASH_VERSION" ]; then +    if [ -f "$HOME/.bashrc" ]; then +      . "$HOME/.bashrc" +    fi +  fi +~~~ + +因此,只要运行~/.profile文件,~/.bashrc文件就会连带运行,但是如果存在~/.bash_profile文件,那么有可能不会运行~/.profile文件。解决办法是把下面代码写入 ~/.bash_profile,让 ~/.profile 始终能够运行。 + + +~~~ +  if [ -f ~/.profile ]; then +    . ~/.profile +  fi +~~~ diff --git a/_posts/2014-02-23-ip.md b/_posts/2014-02-23-ip.md new file mode 100644 index 0000000..94ac9cf --- /dev/null +++ b/_posts/2014-02-23-ip.md @@ -0,0 +1,62 @@ +--- +layout: post +title: IP 类型 +tags: IP computer +categories: network +published: true +--- + + +# 分类 + +![ip][ip] + +IP通过地址开头判断类型,将以 `0`,`10`,`110` 开头的地址分为ABC三类 + +将ip转换成十进制后 + +A类地址第一个字节在 `0 - 127` 之间, + +B类地址第一个字节在 `128 - 191` 之间 + +C类地址第一个字节在 `192 - 223` 之间 + +然后剩下的位被分为网络号与主机号,主机有两个特殊的值: + +主机号全部为`0`的ip代表一个网段 + +主机号全部为`1`的ip代表广播地址,应用程序可以通过这个ip将信息发送到该网段下的所有主机 + + + +# 子网掩码 + +IP寻址时还需用到子网掩码,子网掩码与IP等长,由连续的1组成 + +ip中被子网掩码掩去(对ip进行与运算)的部分将被视为网络号,剩余部分将被视为主机号 + +如需要划分5个子网,其二进制为`101`,这将在ip中占去`3`位,3位可以划分出6(`2 ^ 3 - 2`)个子网满足5个子网的要求 + +一个byte中占去前3位后为`11100000`,该子网掩码十进制为`224` + + + +# 私有地址 + +tcp/ip协议中,专门保留了三个IP地址区域作为私有地址,其地址范围如下: + +10.0.0.0/8:10.0.0.0~10.255.255.255 + +172.16.0.0/12:172.16.0.0~172.31.255.255 + +192.168.0.0/16:192.168.0.0~192.168.255.255 + +ip后面的斜线和数字,表示ip的中网络号所占的位数 + +例如172.16.0.0/12表示将前12位全部作为网络号,效果等同于子网掩码 255.240.0.0 + + + + + +[ip]: {{"/ip.jpg" | prepend: site.imgrepo }} diff --git a/_posts/2014-03-13-resubmit.md b/_posts/2014-03-13-resubmit.md new file mode 100644 index 0000000..fae144d --- /dev/null +++ b/_posts/2014-03-13-resubmit.md @@ -0,0 +1,24 @@ +--- +layout: post +title: 防止重复提交的几种办法 +tags: resubmit +categories: web +published: true +--- + +## 重定向 +提交操作完成后,将请求重定向到一个只读操作,此后无论客户端如何刷新页面,发送的都是只读操作的请求。 + +## 副作用 +页面初始化时生成随机码,提交操作后删除随机码,然后在服务端进行判断,如果请求中随机码为空,则认为重复提交。 + +## 令牌 +生成form页面之前在session中保存一个随机生成的token,并写入到form中的隐藏域。 + +然后在提交时进行判断:如果token与session中的相同,则为首次提交,此时删除session中的token,否则一律为非正常提交。 + +## UI +提交后禁用提交按钮 + +## 数据库 +数据库添加约束,禁止重复数据 diff --git a/_posts/2014-03-16-tomcat-https.md b/_posts/2014-03-16-tomcat-https.md new file mode 100644 index 0000000..4766472 --- /dev/null +++ b/_posts/2014-03-16-tomcat-https.md @@ -0,0 +1,92 @@ +--- +layout: post +title: Tomcat Https 配置 +tags: tomcat Http Https ssl Java +categories: Java +published: false +--- + +#### `1`.生成秘钥库 + +~~~bash +keytool + –genkey + –keyalg 算法(通常用RSA) + –dname "cn=服务器名, + ou=组织单位名, + o=组织名, + l=城市名, + st=省/市/自治区, + c=国家双字母代码" + -alias 别名(非必选项) + -keypass 密码 + -keystore 秘钥库文件名 + -storepass 密码 + -validity 有效天数 +~~~ + +#### `2`.生成浏览器证书文件 + +~~~bash +keytool + -export + -keystore 秘钥库文件名 + -alias 秘钥库别名(非必选项) + -storepass 秘钥库密码 + -file 证书文件名 +~~~ + +#### `3`.生成私钥文件 + +~~~bash +keytool + -importkeystore + -srckeystore 秘钥库文件名 + -deststoretype PKCS12 + -destkeystore p12文件名 + +openssl + pkcs12 + -in p12文件名 + -out pem文件名 + -nodes +~~~ + +#### `4`.Tomcat配置 + +打开 %CATALINA_HOME%/conf/server.xml + +解开注释或添加代码(443为https默认端口,不会在URL中显示) + +~~~bash + +~~~ + +#### `5`.强制https访问 + +打开 %CATALINA_HOME%/conf/web.xml + +添加代码 + +~~~xml + + + + CLIENT-CERT + Client Cert Users-only Area + + + + + SSL + /* + + + CONFIDENTIAL + + +~~~ diff --git a/_posts/2014-03-22-reentrantlock.md b/_posts/2014-03-22-reentrantlock.md new file mode 100644 index 0000000..c1d6057 --- /dev/null +++ b/_posts/2014-03-22-reentrantlock.md @@ -0,0 +1,29 @@ +--- +layout: post +title: 可重入锁 +tags: Java Concurrent Lock +categories: Java +--- + +Java 中的锁是可重入的,当线程试图获得它自己占有的锁时,请求会成功。 + +这样使用`synchronized`并不会造成死锁 + +~~~java +public synchronized void test(){ + //do something + synchronized(this) { + //do something + synchronized (this){ + //do something + synchronized (this){ + //do something + } + } + } +} +~~~ + +这是因为java线程是基于 “每线程(per-thread)”,而不是基于“每调用的(per-invocation)” 的,也就是说java为每个线程分配一个锁,而不是为每次调用分配一个锁。 + +重入的实现是通过为每个锁关联一个请求计数和一个占有它的线程。当请求计数器为0时,这个锁可以被认为是未占用的,当一个线程请求一个未占用的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线程再次请求这个锁时,请求计数器就会增加,当该线程退出`syncronized`块时,计数器减1,当计数器为0时,锁被释放。 \ No newline at end of file diff --git a/_posts/2014-04-09-algorithm-gist.md b/_posts/2014-04-09-algorithm-gist.md new file mode 100644 index 0000000..4a72e16 --- /dev/null +++ b/_posts/2014-04-09-algorithm-gist.md @@ -0,0 +1,322 @@ +--- +layout: post +title: 算法小汇 +tags: 三色旗 汉诺塔 斐波那契数列 骑士周游 算法 algorithm +categories: algorithm +--- + +* TOC +{:toc} + +# 三色旗 + +问题描述:一条绳子上悬挂了一组旗帜,旗帜分为三种颜色,现在需要把旗帜按顺序将相同的颜色的放在一起,没有旗帜的临时存放点,只能在绳子上操作,每次只能交换两个旗帜 + +例如:原本旗帜的顺序为`rwbrbwwrbwbrbwrbrw`需要变成`bbbbbbwwwwwwrrrrrr` + +解决思路:遍历元素,如果元素该放到左边就与左边交换,该放到右边就与右边交换,左右边界动态调整,剩余的正好属于中间 + +~~~java +public class Tricolour { + + private final static String leftColor = "b"; + private final static String rightColor = "r"; + + public void tricolour(Object[] src){ + //中间区域的左端点 + int mli = 0; + //中间区域的右端点 + int mri = src.length-1; + //遍历元素,从mli开始到mri结束,mli与mri会动态调整,但是i比mli变化快 + for (int i = mli; i <= mri; i++) { + //如果当前元素属于左边 + if(isPartOfLeft(src[i])){ + //将当前元素换与中间区域左端点元素互换 + swap(src,mli,i); + //mli位的元素是经过处理的,一定不是红色,所以不需要再分析i位新元素 + //左端点右移 + mli++; + } + //如果当前元素属于右边 + else if(isPartOfRight(src[i])){ + //从中间区域的右端点开始向左扫描元素 + while (mri > i) { + //如果扫描到的元素属于右边,右端点左移,继续向左扫描,否则停止扫描 + if(isPartOfRight(src[mri])){ + mri--; + } else { + break; + } + } + //将当前元素交换到中间区域右端点 + swap(src,mri,i); + //右端点左移 + mri--; + //mri位的元素可能是蓝色的,需要再分析i位新元素 + i--; + } + } + } + + private boolean isPartOfLeft(Object item){ + return leftColor.equals(item); + } + + private boolean isPartOfRight(Object item){ + return rightColor.equals(item); + } + + private void swap(Object[] src, int fst, int snd){ + Object tmp = src[fst]; + src[fst] = src[snd]; + src[snd] = tmp; + } + + public static void main(String[] args) { + String[] flags = + new String[]{"r","b","w","w","b","r","r","w","b","b","r","w","b"}; + new Tricolour().tricolour(flags); + for (String flag : flags) { + System.out.printf("%s,",flag); + } + } +} +~~~ + +# 汉诺塔 + +~~~java +public class Hanoi { + + private void move(int n, char a, char b, char c){ + if(n == 1){ + System.out.printf("盘%d由%s移动到%s\n",n,a,c); + } else{ + move(n-1,a,c,b); + System.out.printf("盘%d由%s移动到%s\n",n,a,c); + move(n-1,b,a,c); + } + } + + public static void main(String[] args) { + new Hanoi().move(5, 'A','B','C'); + } +} +~~~ + +# 斐波那契数列 + +~~~java +public class Fibonacci { + + private int[] src ; + + public int fibonacci(int n){ + if(src == null){ + src = new int[n+1]; + } + if(n == 0 || n==1){ + src[n] = n; + return n; + } + src[n] = fibonacci(n-1) + fibonacci(n-2); + return src[n]; + } + + public int fibonacci2(int n){ + if(src == null){ + src = new int[n+1]; + } + src[0] = 0; + src[1] = 1; + for (int i = 2; i < src.length; i++) { + src[i] = src[i-1] + src[i-2]; + } + return src[n]; + } + + public static void main(String[] args) { + Fibonacci fib = new Fibonacci(); + int n = fib.fibonacci(20); + System.out.println(n); + for (int i : fib.src) { + System.out.println(i); + } + } +} +~~~ + +# 骑士周游 + +骑士只能按照如图所示的方法前进,且每个格子只能路过一次,现在指定一个起点,判断骑士能否走完整个棋盘. + +思路:对任意一个骑士所在的位置,找出其所有可用的出口,若无可用出口则周游失败,再对每个出口找出其可用的子出口,然后骑士移动至子出口最少的出口处,重复以上过程. + +![](http://img.blog.csdn.net/20130710174118812?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamlhamlheW91YmE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) + +~~~java +import java.awt.Point; +import static java.lang.System.out; + +public class KnightTour { + + // 对应骑士可走的八個方向 + private final static Point[] relatives = new Point[]{ + new Point(-2,1), + new Point(-1,2), + new Point(1,2), + new Point(2,1), + new Point(2,-1), + new Point(1,-2), + new Point(-1,-2), + new Point(-2,-1) + }; + + private final static int direct = 8; + + public KnightTour(int sideLen){ + this.sideLen = sideLen; + board = new int[sideLen][sideLen]; + } + + private int sideLen; + + private int[][] board; + + public boolean travel(int startX, int startY) { + + // 当前出口的位置集 + Point[] nexts; + + // 记录每个出口的可用出口个数 + int[] exits; + + //当前位置 + Point current = new Point(startX, startY); + + board[current.x][current.y] = 1; + + //当前步的编号 + for(int m = 2; m <= Math.pow(board.length, 2); m++) { + + //清空每个出口的可用出口数 + nexts = new Point[direct]; + exits = new int[direct]; + + int count = 0; + // 试探八个方向 + for(int k = 0; k < direct; k++) { + int tmpX = current.x + relatives[k].x; + int tmpY = current.y + relatives[k].y; + + // 如果这个方向可走,记录下来 + if(accessable(tmpX, tmpY)) { + nexts[count] = new Point(tmpX,tmpY); + // 可走的方向加一個 + count++; + } + } + + //可用出口数最少的出口在exits中的索引 + int minI = 0; + if(count == 0) { + return false; + } else { + // 记录每个出口的可用出口数 + for(int l = 0; l < count; l++) { + for(int k = 0; k < direct; k++) { + int tmpX = nexts[l].x + relatives[k].x; + int tmpY = nexts[l].y + relatives[k].y; + if(accessable(tmpX, tmpY)){ + exits[l]++; + } + } + } + + // 从可走的方向中寻找最少出路的方向 + int tmp = exits[0]; + for(int l = 1; l < count; l++) { + if(exits[l] < tmp) { + tmp = exits[l]; + minI = l; + } + } + } + + // 走最少出路的方向 + current = new Point(nexts[minI]); + board[current.x][current.y] = m; + } + + return true; + } + + private boolean accessable(int x, int y){ + return x >= 0 && y >= 0 && x < sideLen && y < sideLen && board[x][y] == 0; + } + + public static void main(String[] args) { + int sideLen = 9; + KnightTour knight = new KnightTour(sideLen); + + if(knight.travel(4,2)) { + out.println("游历完成!"); + } else { + out.println("游历失败!"); + } + + for(int y = 0; y < sideLen; y++) { + for(int x = 0; x < sideLen; x++) { + out.printf("%3d ", knight.board[x][y]); + } + out.println(); + } + } +} +~~~ + +# 帕斯卡三角 + +~~~java +public class Pascal extends JFrame{ + + private final int maxRow; + + Pascal(int maxRow){ + setBackground(Color.white); + setTitle("帕斯卡三角"); + setSize(520, 350); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setVisible(true); + this.maxRow = maxRow; + } + + /** + * 获取值 + * @param row 行号 从0开始 + * @param col 列号 从0开始 + * @return + */ + private int value(int row,int col){ + int v = 1; + for (int i = 1; i < col; i++) { + v = v * (row - i + 1) / i; + } + return v; + } + + @Override + public void paint(Graphics g) { + int r, c; + for(r = 0; r <= maxRow; r++) { + for(c = 0; c <= r; c++){ + g.drawString(String.valueOf(value(r, c)), (maxRow - r) * 20 + c * 40, r * 20 + 50); + } + } + } + + public static void main(String[] args) { + new Pascal(12); + } +} +~~~ diff --git a/_posts/2014-04-15-fileupload.md b/_posts/2014-04-15-fileupload.md new file mode 100644 index 0000000..5588a9a --- /dev/null +++ b/_posts/2014-04-15-fileupload.md @@ -0,0 +1,246 @@ +--- +layout: post +title: Custom Fileupload +tags: fileupload Java Http +categories: web +published: true +--- + +* TOC +{:toc} + +本文的目的是简要说明如何编写一个文件上传组件,使他的功能类似 [commons-fileupload][commons-fileupload], 并在结尾处提供了完整代码的获取方式。 + +# HTTP +本文讨论的是基于 HTTP 协议的文件上传,下面先来看看 HTTP 请求的真面目。 + +首先,用 JavaSe 类库中的 Socket 搭建一个超简单的服务器,这个服务器只有一个功能,就是完整地打印整个 HTTP 请求体。 + +~~~java +public class Server { + + private ServerSocket serverSocket; + + public Server() throws IOException{ + serverSocket = new ServerSocket(8080); + } + + public void show() throws IOException{ + while(true){ + Socket socket = serverSocket.accept(); + byte[] buf = new byte[1024]; + InputStream is = socket.getInputStream(); + OutputStream os = new ByteArrayOutputStream(); + int n = 0; + while ((n = is.read(buf)) > -1){ + os.write(buf,0,n); + } + os.close(); + is.close(); + socket.close(); + + System.out.println(os); + } + } + + public static void main(String[] args) throws IOException { + new Server().show(); + } + +} +~~~ + +将服务器运行起来之后,在浏览器中输入地址:`http://localhost:8080` + +在我的机器上,显示如下内容,可以看到,这个一个get请求 + +![http-get][http-get] + +下面利用一个 html 的 form表单提交 post 请求 + +~~~html +
+ + + +
+~~~ + +在我的机器上,显示如下内容 + +![http-post][http-post] + +注意图中被红色框起来的部分,第一个红框指示了本次请求中,用来分隔不同元素的分隔线。 + +每个元素将以此分隔线作为第一行,后面紧跟对元素的描述,描述与内容用空行分隔。 + +分隔线的后面加两个小短横代表整个请求体结束,即**EOF**。 + +我们需要做的工作,就是利用分隔线,从请求体中分离出每个元素,分析HTTP请求头的工作可以交给Servlet。 + +# 分析 + +那么,如何分离呢? + +java中的 `InputStream` 只能读取一次,所以我们想要方便地分析一个流,最直接的办法就是将其缓存下来。 + +RandomAccessFile 或许能够满足需求,RandomAccessFile 可以提供一个指针用于在文件中的随意移动,然而需要读写本地文件的方案不会是最优方案。 + +先将整个流读一遍将内容缓存到内存中? 这种方案在多个客户端同时提交大文件时一定是不可靠的。 + +最理想的方案可能是,我只需要读一遍 `InputStream` , 读完后将得到一个有序列表,列表中存放每个元素对象。 + +很明显,JavaSe的流没有提供这个功能 + +我们知道从 `InputStreeam` 中获取内容需要使用 `read` 方法,返回 `-1` 表示读到了流的末尾,如果我们增强一下`read`的功能,让其在读到每个元素末尾的时候返回 `-1`,这样不就可以分离出每个元素了吗,至于判断是否到了整个流的末尾,自有办法。 + + +# 设计 + +如何增强`read`方法呢? + +`read`方法要在读到元素末尾时返回`-1` , 一定需要先对已读取的内容进行分析,判断是否元素末尾。 + +我的做法是,内部维护一个buffer,`read`方法在读取时先将字节写入到这个buffer中,然后分析其中是否存在分隔线,然后将buffer中可用的元素复制到客户端提供的buffer。 + +这个内部维护的buffer并不总是满的,其中的字节来自read方法的原始功能,所以我们需要一个变量来记录buffer中有效字节的末尾位置 `tail`。 + +我们还需要一个变量 `pos` 来标记buffer中是否存在分隔线,pos的值即为分隔线的开头在buffer中的位置,如果buffer中不存在分隔线pos的值将为-1。 + +但是问题没这个简单,分隔线在buffer中存在状态有两种情况: + +情况A,分隔线完好地存在于buffer中,图中的bundary即为分隔线 + +![boundary-A][boundary-A] + +情况B,分隔线的一部分存在于buffer中 + +![boundary-B][boundary-B] + +在B情况下,`boundary`有多少字节存在于buffer中是不确定的,而且依靠这些不完整的字节根本无法判断他是否属于boundary开头。 + +例如,buffer中没有发现boundary,但是buffer末尾的3个字节与boundary开头相同,这种情况可能只是巧合,boundary并没有被截断。 + +对于这个问题,有一个解决办法,我们不必检查到buffer末尾,而是在buffer末尾留一个关健区`pad`。 + +这个关健区中很有可能存在被截断boundary,每次检查到`pad`开头时立即收手,此位置之前的数据可以确保没有boundary,在下次填充buffer时,将这个关健区中的数据复制到buffer开头再处理。很显然,关健区`pad`长度应该等于boundary,如图: + +![pad][pad] + +# 关键代码 + +**在buffer中检查boundary** + +~~~java +private int findSeparator() { + int first; + int match = 0; + //若buffer中head至tail之间的字节数小于boundaryLength,那么maxpos将小于head,循环将不会运行,返回值为-1 + int maxpos = tail - boundaryLength; + for (first = head; first <= maxpos && match != boundaryLength; first++) { + first = findByte(boundary[0], first); + if (first == -1 || first > maxpos) { + return -1; + } + for (match = 1; match < boundaryLength; match++) { + if (buffer[first + match] != boundary[match]) { + break; + } + } + } + if (match == boundaryLength) { + return first - 1; + } + return -1; +} +~~~ + + +**填充buffer** + +~~~java +private int makeAvailable() throws IOException { + //该方法在available返回0时才会被调用,若pos!=-1那pos==head,表示boundary处于head位,可用字节数为0 + if (pos != -1) { + return 0; + } + + // 将pad位之后的数据移动到buffer开头 + total += tail - head - pad; + System.arraycopy(buffer, tail - pad, buffer, 0, pad); + + // 将buffer填满 + head = 0; + tail = pad; + //循环读取数据,直至将buffer填满,在此过程中,每次读取都将检索buffer中是否存在boundary,无论存在与否,都将即时返回可用数据量 + for (;;) { + int bytesRead = input.read(buffer, tail, bufSize - tail); + if (bytesRead == -1) { + //理论上因为会对buffer不断进行检索,读到boundary时就会return 0,read方法将返回 -1, + //所以不会读到input末尾,如果运行到了这里,表示发生了错误. + final String msg = "Stream ended unexpectedly"; + throw new RuntimeException(msg); + } + if (notifier != null) { + notifier.noteBytesRead(bytesRead); + } + tail += bytesRead; + findSeparator(); + //若buffer中的数据量小于keepRegion(boundaryLength),av将必定等于0,循环将继续,直至数据量大于或等于keepRegion(boundaryLength). + //此时将检索buffer中是否包含boundary,若包含,将返回boundary所在位置pos之前的数据量,若不包含,将返回pad位之前的数据量 + int av = available(); + + if (av > 0 || pos != -1) { + return av; + } + } +} +~~~ + +**强化后的read方法** + +~~~java +@Override +public int read(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new RuntimeException("the stream is closed"); + } + if (len == 0) { + return 0; + } + int res = available(); + if (res == 0) { + res = makeAvailable(); + if (res == 0) { + return -1; + } + } + res = Math.min(res, len); + System.arraycopy(buffer, head, b, off, res); + head += res; + total += res; + return res; +} +~~~ + +# 源码获取 + +我已经按照这套想法完整地实现了文件上传组件 + +有兴趣的朋友可以从我的Gighub获取源码 [点我获取][github] + +使用方法:[点我查看][use] + + + + + + +[http-get]: {{"/http-get.png" | prepend: site.imgrepo }} +[http-post]: {{"/http-post.png" | prepend: site.imgrepo }} +[boundary-A]: {{"/boundary-A.png" | prepend: site.imgrepo }} +[boundary-B]: {{"/boundary-B.png" | prepend: site.imgrepo }} +[pad]: {{"/boundary-C.png" | prepend: site.imgrepo }} +[github]: https://github.com/dubuyuye/fileupload/releases +[use]: https://github.com/dubuyuye/fileupload +[commons-fileupload]: https://github.com/apache/commons-fileupload \ No newline at end of file diff --git a/_posts/2014-04-20-flex.md b/_posts/2014-04-20-flex.md new file mode 100644 index 0000000..a55d87b --- /dev/null +++ b/_posts/2014-04-20-flex.md @@ -0,0 +1,687 @@ +--- +layout: post +title: Flex +tags: Flex ActionScript script +categories: front-end +--- + +* TOC +{:toc} + + +# 产品 + +1. Adobe® Flex™ SDK + +2. Adobe® Flex™ Builder™ + +3. Adobe® Flex™ Data Services + +4. Adobe® Flex™ Charting + + +# Flex基础 + +## 应用程序模型 + +用容器(如Box)控件(如Button)来描述用户的操作界面 + +## MVC模型 + +1. 模型/Model 组件封装了数据和与数据相关的行为。 + +2. 视图/View 组件定义了应用程序的用户界面。 + +3. 控制器/Controller 组件则负责处理程序中的数据连接。 + +## Flex 编程模型 + +Flex 包含了**Flex 类库**、**MXML** 和**ActionScript** 编程语言,以及**Flex 编译器**和**调试器**。 + +>MXML 和 ActionScript 编程语言都提供了访问Flex 类库的能力。 + +通常的做法是:使用MXML 去定义用户界面的元素,使用ActionScript 去定义客户端的逻辑并进行控制。 + +Flex 类库包括了**Flex 组件**、**管理器**和**行为**。 + + +## Flex 元素 + +* Flex framework + +* MXML + +* ActionScript 3.0 + +* CSS + +* 图形资源 + +* 数据 + + +~~~xml + + + + + + + +~~~ + +## 发布方式 + +1. 客户端模式,即应用程序只运行在客户端上而不需要服务器资源。 + +2. 使用简单的RPC 访问服务器数据,即使用HTTPService(HTTP GET 或POST 请求)和WebService(通过使用SOAP)。 + +3. Flex Data Services 模式,可以提供更为高级的特性,如数据同步、安全增强等等。 + + + +# 界面布局 + +样式 + +~~~xml + + { + fontSize: 36px; + fontWeight: bold; + } + + +~~~ +外部样式 + +~~~xml + + +~~~ + +## 控制应用程序的外观 + +1. 大小/Sizes,即组件或应用程序的高度和宽度。 + +2. 样式/Styles,即一组特性,如字体、排列方式、颜 +色等。它们都是通过层叠样式( CSS)来进行设置的。 + +3. 皮肤/Skins,即可以进行改变的组件视觉元素。 + +4. 行为/Behaviors,即Flex 组件在视觉或听觉效果方面的变化。 + +5. 视图状态/View state 可以让你通过修改它的基础内容,来改变组件或程序的内容和外观。 + +6. 变换/Transitions 可以让你定义屏幕上发生改变的视图状态。 + + +## 受约束的布局 + +受约束的布局可以确保用户界面中的组件在程序窗 + +口大小发生变化时,也能自动地作出调节。备注:可以通过使用嵌套的布局容器/nested layout container 来实现相同的目的 + +创建受约束的布局,你必须将容器的布局属性设置为绝对方式( `layout="absolute"`)备注:帆布容器/canvas container 并不需要进行layout=”absolute”的属性设置,因为它默认是绝对布局方式。 + +在Flex 中,所有的约束都是被设置为与**容器**的相对距离,它们不可能被设置为相对于其它控件 + +**控件** + +ComboBox,List,HorizontalList。 + + + +# 事件与行为 + +外部ActionScript + +~~~xml + + +~~~ + +被事件触发的行为 + +mxml中的组件被赋予id后,在as中将可以直接作为对象进就行操作,如例子中的`vBox` + +~~~xml +var locales:ArrayCollection = new ArrayCollection([{label:"en_us", data:"en_us"},{label:"zh_cn", data:"zh_cn"}]); + +var child:ComboBox = new ComboBox(); +child.dataProvider=locales; +vBox.addChild(child); + +~~~ + +也可以在as中为事件绑定行为 + +~~~xml +//change事件绑定 +localeComboBox.addEventListener(ListEvent.CHANGE,localeComboxChangeHandler); + +~~~ + +## 多态页面 + +用ViewStack 组件、创建单独的MXML 文件、或者使用视图状态 + +## 创建行为 + +~~~xml + + + +~~~ + + + + +# 连接数据 + +数据源,url获取xml类型数据 + +flex 不直接连接数据库,而是用数据服务组件获取xml数据 + +~~~xml + + +~~~ + +数据绑定,并非简单的引用,dataProvider与simpleData.lastResult.products.items是同步变化的 + +~~~xml + + + + + + +~~~ + +事件绑定 + +~~~xml + + +~~~ + +## 连接数据服务器 + +1. WebService 提供对使用SOAP 的web 服务器的访问。 + +2. HTTPService 提供对返回数据的HTTP URLs 的访问。 + +3. RemoteObject 通过使用AML 协议提供对Java 对象(Java Beans、EJBs、POJOs)的访问。该选项目前仅适用于Flex Data Services 或Macromedia ColdFusion MX 7.0.2. + + +## 安全性 + +1. swf与数据源必须同域 + +2. 可以使用代理访问远程数据源,但是swf必须与代理服务器同域 + +3. 安装`crossdomain.xml`(跨域策略/cross-domain policy)文件在数据源的宿主Web 服务器上。 +crossdomain.xml 文件允许位于其它域中的SWF 文件对数据源的访问。 + + + + +# 图表组件 + +* 区域形图表/Area charts + +* 气泡形图表/Bubble charts + +* 烛形图表/Candlestick charts + +* 柱形图表/Column charts + +* 高低开合形图表/HighLowOpenClose charts + +* 线形图表/Line charts + +* 饼形图表/Pie charts + +* 标绘形图表/Plot charts + +* 扩展CartesianChart 控件来创建定制的图表 + +>使用`dataProvider` 属性定义图表的数据 + +# MXML + +MXM 相关的技术标准有 + +1. XML 标准。XML 文档使用标签去决定结构化信息的内容,以及它们之间的关系。 + +2. 事件模型标准。Flex 事件模型是文档对象模型/Document Object Model (DOM)第三级事件的一个子集,该模型是由World Wide Web Consortium(W3C)起草制定。 + +3. Web 服务标准Flex 提供与服务器交互的MXML 标签,遵循了Web 服务描述语言/Web Services +Description Language(WSDL)的规则。具体包括了简单对象访问协议/Simple Object Access Pr +otocol(SOAP)和超文本传送协议/Hypertext Transfer Protocol(HTTP)。 + +4. Java 标准 + +Flex 提供了与服务器端Java 对象交互的MXML 标签,包括plain old Java objects(POJOs),Java + +Beans 和企业级/Enterprise JavaBeans(EJBs)。 + +5. HTTP 标准 + +Flex 提供了相应的MXML 标签去支持标准的HTTP GET 和POST 请求,以及对HTTP 返回数据的处理。 + +6. 图形标准 + +Flex 还提供了相应的MXML 标签去使用JPEG,GIF 和PNG 图象。Flex 还能够将SWF 文件和Scala +ble Vector Graphics(SVG)文件导入到应用程序中。 + +7. 层叠样式表标准MXML 样式的定义和使用遵循了W3C Cascading Style Sheets (CSS)标准。 + + + +# ActionScript + +ActionScript 3.0 的特性完全实现了ECMAScript for XML (E4X)。 +在flex中使用ActionScript + +1. 在``标签中插入ActionScript 代码块。 + +2. 调用存储在**system_classes** 目录结构中的全局ActionScript 功能函数。 + +3. 引用**user_classes **中的外部类和包来处理更为复杂的任务。 + +4. 使用标准的Flex 组件。 + +5. 使用ActionScript 类扩展已有的组件。 + +6. 使用ActionScript 创建新的组件。 + +7. 在Flash 创建环境中创建新的组件( SWC 文件)。 + + +# XML + +## 创建xml对象 + +* 字面量方式,AS可以自动识别,如果加上引号就不是XML类型而是String类型了 + +~~~xml +var textXmlObj:XML = text; + +~~~ + +* XML字面量中可以加入动态内容 + +~~~xml +var text_node:String = "text"; +var textXmlObj:XML = {text_node}; + +~~~ + +* 通过XML类创建实例 + +~~~xml +var myText:String = "text"; +var str:String = ""+ myText + ""; +var textXmlObj:XML = new XML(str); + +~~~ + +* 从外部加载XML对象 + +~~~xml + + +~~~ + + +## 处理XML对象 + +~~~xml + + + Mobile Phone + $199 + + + Car Charger + $34 + + + +~~~ + +使用**`E4X`**简化对XML的操作 + +~~~xml + + +~~~ + +### 查询 + +使用`.`操作符查询节点 + +~~~xml +simpleData.lastResult.item + +~~~ +使用`[]`访问指定索引的节点,**将无法检测到数据变化**,根节点被忽略 + +~~~xml +simpleData.lastResult.item[0] + +~~~ +使用`..`访问所有指定名称的节点,忽略上下级关系 + +~~~xml +simpleData.lastResult..name + +~~~ +使用`@`访问节点属性 + +~~~xml +simpleData.lastResult.item[0].@index + +~~~ +过滤节点(`<`与`>`无法在mxml中使用,需要转义) + +~~~xml +simpleData.lastResult.item.(price==34) + +~~~ +过滤属性 + +~~~xml +simpleData.lastResult.item.(@index==0) + +~~~ +修改获取的数据(不改变xml源对象) + +~~~xml +simpleData.lastResult.item.(price=998)} + +~~~ +HttpService对象在加载外部XML后会自动把它转换成ArrayCollection对象 + +如果需要属性结构如(Tree组件),需要在HTTPService对象中加上resultFormat="e4x"以XML的格式读取进来而不要转换为ArrayCollection + +### 修改 + +>如果绑定的xml数据发生的变化,如何将变化传递到引用?目前发现似乎是自动的,有待研究。 + + +~~~xml +var myxml:XML = + + 30 + adobe + + + +~~~ +添加节点,insert指定子节点位置插入,append添加到子节点末尾,prepend插入到子节点开头 + +~~~xml +myxml.insertChildAfter(myxml.book[0],); +myxml.insertChildBefore(myxml.book[0],); +myxml.appendChild(); +myxml.prependChild(); + +~~~ +添加属性 + +~~~xml +myxml.book[0].@date="2008"; + +~~~ +修改xml对象,此处增加``标签content为奥多比 + +~~~xml +myxml.book[0].author="奥多比"; + +~~~ +删除节点,属性,content + +~~~xml +delete myxml.book[0].@name; +delete myxml.book[0].author; +delete myxml.book[0].price.text()[0]; + +~~~ + + + +# 备忘 + +IDEA 编译出错 **java.net.SocketTimeoutException: Accept timed out** +>[Stack Overflow](http://stackoverflow.com/questions/11016571/intellij-idea-11-flex-compilation-issue) +> +>1. Go to **Settings** > **Compiler** > **Flex Compiler** +> +>2. Choose **Mxmlc/compx** instead of the default **Built-in compiler shell** +> +>3. Compile your application + +# 整合SpringMVC + +Flex要想与Spring进行配合使用,需要一个附加的组件BlazeDS。BlazeDS可以将Flex前端的通信内容(amf 格式 Action Message format,一种flex定义的通信规则)转换为java的相关类和对象。 + +请求处理的简单流程是,flex客户端向服务器发送Request请求,服务器根据Web.xml中的映射,找到Spring配置文档中与flex相关的配置,这个配置就是`MessageBroker`,MessageBroker是Spring容器根据flex的`services-config.xml`文件生成的,在services-config.xml中指定了消息通道,Spring将请求交给这个通道(一般是默认通道),通道根据`remoting-config.xml`中配置的remoting-service和Adapter将flex的amf转换为java的实例信息。如果需要安全或者消息订阅(jms)还需要提供`messaging-config.xml`和`proxy-config.xml`配置文件。 + +结合使用Spring和BlazeDS必须对MessageBroker进行配置。从flex客户端发来的Http 消息(messages)通过Spring的DispatcherServlet(在web.xml中配置)传递给Spring管理的MessageBroker(在Spring的配置文档中指明)。如果使用Spring管理的MessageBroker就不必配置BlazeDS的`MessageBrokerServlet`(在web.xml文档中指明)了。 + +## 依赖 + +~~~xml + + 3.0.1.RELEASE + 4.6 + + + + + org.springframework + spring-webmvc + ${spring.version} + + + org.springframework.flex + org.springframework.flex + 1.0.3.RELEASE + + + + + + com.adobe.flex + messaging-common + ${flex.version} + + + com.adobe.flex + messaging-core + ${flex.version} + + + com.adobe.flex + messaging-remoting + ${flex.version} + + + backport-util-concurrent + backport-util-concurrent + 3.1 + + + cglib + cglib + 2.2.2 + + + Spring MVC Dispatcher Servlet + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + + classpath:applicationContext.xml + + + 1 + + + + + Spring MVC Dispatcher Servlet + /messagebroker/* + + +~~~ + +Flex通过`AMF`通道的形式进行通信的,所谓的AMF通道就是指一种编码形式。MessageBroker可以将Flex格式的请求转换成具体的java的调用,并将反馈回的内容(response)编码成Flex能够识别的对象(amf)。 + +## Srping容器 + +使用了默认位置的配置文件 + +~~~xml + + + + + + + + +~~~ + + +## 通道 + +在BlazeDS的配置文件services-config.xml中定义的通道必须与相应的映射相对应。 + +~~~xml + + + + + + + +~~~ + +## 配置远程服务 + +这里有列举了两种发布方式,任选一种即可。 + +①创建使用注解发布的服务 + +~~~java +package top.rainynight.flex2spring; + +import org.springframework.flex.remoting.RemotingDestination; +import org.springframework.flex.remoting.RemotingInclude; +import org.springframework.stereotype.Component; + +@Component +@RemotingDestination +public class AnnotationRPC { + + public String read(String id) { + return "annotation works"; + } + +} + +~~~ +②创建使用xml发布的服务 + +~~~java +package top.rainynight.flex2spring; + +/** + * Created by sllx on 2015-03-16. + */ +public class XmlConfRPC { + public String read(String id) { + return "xml works"; + } +} + +~~~ +发布配置发布方式任选一种即可 + +~~~xml + + + + + + +~~~ + + +## flex使用默认channel +flex配置编译参数 + +~~~ +-services "pathToWebappRoot/WEB-INF/flex/services-config.xml" -locale en_US + +~~~ + +## 测试 + +~~~xml + + + + + + + + + + + +~~~ \ No newline at end of file diff --git a/_posts/2014-05-07-linux-job.md b/_posts/2014-05-07-linux-job.md new file mode 100644 index 0000000..3e2ecbf --- /dev/null +++ b/_posts/2014-05-07-linux-job.md @@ -0,0 +1,166 @@ +--- +layout: post +title: Linux计划任务 +tags: 计划任务 Linux +categories: Linux +--- + +* TOC +{:toc} + + +## `at` + +at 是个可以处理**仅运行一次**就结束计划任务的命令,不过要运行 at 时, 必须要有 atd 这个服务的支持才行。 + +[root@www ~]# `/etc/init.d/atd restart` + +使用 at 这个命令来产生所要运行的任务,并将任务以文件的方式写入 `/var/spool/at` 目录内,该任务便能被 atd 服务运行 + +>对at进行权限控制 +> +>* 先找寻 `/etc/at.allow` 这个文件,写在这个文件中的使用者才**能**使用 at ,没有在这个文件中的使用者则不能使用 at (即使没有写在 at.deny 当中); +> +>* 如果 /etc/at.allow 不存在,就寻找 `/etc/at.deny` 这个文件,写在这个 at.deny 的使用者**不能**使用 at ,而没有在这个 at.deny 文件中的使用者,就可以使用 at 咯; +> +>* 如果两个文件都不存在,那么只有 root 可以使用 at 命令。 + +[root@www ~]# `at [-mldv] TIME` + +[root@www ~]# `at -c 任务号码` + +选项与参数: + +`-m` :当 at 的任务完成后,即使没有输出信息,亦以 email 通知使用者该任务已完成。 + +`-l` :at -l 相当於 `atq`,列出目前系统上面的所有该使用者的 at 任务; + +`-d` :at -d 相当於 `atrm` ,可以取消一个在 at 排程中的任务; + +`-v` :可以使用较明显的时间格式列出 at 排程中的任务列表; + +`-c` :可以列出后面接的该项工作的实际命令内容。 + +>TIME:时间格式,这里可以定义出『什么时候要进行 at 这项工作』的时间,格式有: +> +>* `HH:MM` +> +> ex> 04:00 +> +> 在今日的 HH:MM 时刻进行,若该时刻已超过,则明天的 HH:MM 进行此工作。 +> +>* `HH:MM YYYY-MM-DD` +> +> ex> 04:00 2009-03-17 +> +> 强制在某年某月某日的某时刻进行! +> +>* `HH:MM[am|pm] [Month] [Date]` +> +> ex> 04pm March 17 +> +> 强制在某年某月某日的某时刻进行! +> +>* `HH:MM[am|pm] + number [minutes|hours|days|weeks]` +> +> ex> now + 5 minutes ex> 04pm + 3 days +> +> 在某个时间点『再加几个时间后』才进行。 + +`!`at命令会进入atshell环境,使用绝对路径输入指令不易出现问题 + +`!`at 的运行与终端机环境无关,想要查看io结果,需要指定终端`echo "Hello" > /dev/tty1` + +`!`at 的运行独立于bash,所以at任务可以离线运行 + +**`batch`**:系统有空(负载低于0.8 )时才进行背景任务,用法与at完全相同,本质上是是at命令加一些控制参数 + + + +## `crontab` + +crontab 这个命令所配置的工作将会循环的**一直进行**下去, 可编辑 /etc/crontab 来支持,让 crontab 生效的服务是 `crond` + +[root@www ~]# ·`/etc/init.d/crond restart` + +当使用者使用 crontab 这个命令来创建工作排程之后,该项工作就会被纪录到 `/var/spool/cron/`目录内 + +cron 运行的每一项工作都会被纪录到 `/var/log/cron` 内 + +>权限控制(与at相同,默认保留/etc/cron.deny ) +> +>* `/etc/cron.allow`:将可以使用 crontab 的帐号写入其中,若不在这个文件内的使用者则不可使用 crontab; +> +>* `/etc/cron.deny`:将不可以使用 crontab 的帐号写入其中,若未记录到这个文件当中的使用者,就可以使用 crontab + +[root@www ~]# `crontab [-u username] [-l|-e|-r]` + +选项与参数: + +`-u` :只有 root 才能进行这个任务,亦即帮其他使用者创建/移除 crontab 工作排程; + +`-e` :编辑 crontab 的工作内容 + +`-l` :查阅 crontab 的工作内容 + +`-r` :移除所有的 crontab 的工作内容,若仅要移除一项,请用 -e 去编辑。 + +[dmtsai@www ~]$ `crontab -e` + +>`0 12 * * * ` +> +>`分 时 日 月 周 <命令串>` + +特殊字符 + +`*`代表任何时刻都接受的意思 + +`,`代表分隔时段的意思 + +`-`代表一段时间范围内 + +`/n`代表每间隔n单位 + +系统计划任务(不受crontab -e命令管理) +编辑`/etc/crontab`文件,该文件每1分钟被检查一次 + + +## `anacron` + +侦测系统未进行的 crontab 任务(例如 crontab 设置在周末运行,但是周末关机了) + +[root@www ~]# `anacron [-sfn] [job]..` + +[root@www ~]# `anacron -u [job]..` + +选项与参数: + +`-s` :开始一连续的运行各项工作 (job),会依据时间记录档的数据判断是否进行; + +`-f` :强制进行,而不去判断时间记录档的时间戳记; + +`-n` :立刻进行未进行的任务,而不延迟 (delay) 等待时间; + +`-u` :仅升级时间记录档的时间戳记,不进行任何工作。 + +`job` :由 **/etc/anacrontab** 定义的各项工作名称。 + +配置文件位于`/etc/anacrontab` + +>[root@www ~]# cat /etc/anacrontab +> +>SHELL=/bin/sh +> +>PATH=/sbin:/bin:/usr/sbin:/usr/bin +> +>MAILTO=root + +>1 65 cron.daily run-parts /etc/cron.daily +> +>7 70 cron.weekly run-parts /etc/cron.weekly +> +>30 75 cron.monthly run-parts /etc/cron.monthly + +天数 延迟时间(分) 工作名称定义 实际要进行的命令串 + +任务文件位于`/var/spool/anacron/` \ No newline at end of file diff --git a/_posts/2014-05-12-acturl-generic.md b/_posts/2014-05-12-acturl-generic.md new file mode 100644 index 0000000..c0a6984 --- /dev/null +++ b/_posts/2014-05-12-acturl-generic.md @@ -0,0 +1,82 @@ +--- +layout: post +title: 超类中的泛型 +tags: generic type Java +categories: java +published: false +--- + +为了减少工作量,开发者往往喜欢将相同的特性放入超类,通过继承实现代码共享 + +例如web项目中常见的`BaseDao`,并使用泛型对参数与返回值类型进行约束 + +某些情况下,我们可能需要获得真实的类型参数,也就是`T`的真实类型 + +首先,沿着继承链向上追溯,找到传入类型参数的位置,并获取存储类型参数的对象 + +~~~java + private static ParameterizedType getParameterizedType(Class clazz){ + Type st = clazz.getGenericSuperclass(); + if(st == null){ + return null; + } + if(st instanceof ParameterizedType){ + ParameterizedType pt = (ParameterizedType)st; + return pt; + } else{ + return getParameterizedType(clazz.getSuperclass()); + } + } +~~~ + +类的参数数量是不定的,如 `Map` 定义了两个,`List` 只定义了一个,所以类型参数是个列表,获取真实类型时需要指定要取哪一个 + +~~~java + private static Class getClass(ParameterizedType pt, int index){ + Type param = pt.getActualTypeArguments()[index]; + if(param instanceof Class){ + return (Class)param; + } else { + return null; + } + } +~~~ + +通常情况下,到这里已经可以取到真实类型了,但是上面的代码忽略了一些不正常情况 + +**情况一:** + +超类定义了多个参数,而子类中是分批传参的,此时从继承链上最近的位置获得的参数列表是不全的 + +~~~java +class Generic{} +class SubGeneric extends Generic{} +class Acturl extends SubGeneric{} +~~~ + +**情况二:** + +在继承链中的某个位置传入了类型参数,然后又定义了一个新的参数,此时从继承链上最近的位置获取的参数列表和顶层类的参数已经没有关系了 + +~~~ java +class Generic{} +class SubGeneric extends Generic{} +class Acturl extends SubGeneric{} +~~~ + +**情况三:** + +这是情况一与情况二的混合版,而且参数`V`的位置都变了 + +~~~ java +class Generic{} +class SubGeneric extends Generic{} +class Acturl extends SubGeneric{} +~~~ + + +这些问题有空再研究 + +~~~java +//TODO +~~~ \ No newline at end of file diff --git a/_posts/2014-06-10-ssh.md b/_posts/2014-06-10-ssh.md new file mode 100644 index 0000000..9079d69 --- /dev/null +++ b/_posts/2014-06-10-ssh.md @@ -0,0 +1,126 @@ +--- +layout: post +title: SSH +tags: Linux 命令 SSH +categories: Linux +--- + +* TOC +{:toc} + + +## 登陆 + +**`-p`指定主机的端口** + +`$ ssh -p port user@host` + +**默认端口为22** + +`$ ssh user@host` + +**默认使用本机用户名** + +`$ ssh host` + +## 过程 + +1. 远程主机接收到用户的登陆请求,把自己的公钥发给用户 + +2. 用户使用这个公钥,将密码加密后发回来 + +3. 远程主机用自己的私钥,解密登陆密码,如果密码正确,就同意用户登陆 + +>此处如果有攻击者截取了用户请求,再将自己的公钥发送给用户,然后就可以用自己的私钥解密出用户的私密信息,这就是`中间人攻击` +> +>应对的方法:用户首次连接远程主机时,远程主机将发送一段128位长的公钥指纹,用户需要自行与远程主机网站发布的公钥指纹进行对比以判断真伪,当用户信任了此公钥后,它将被保存在`$HOME/.ssh/known_hosts`中,下次连接时无需再次确认。 + +
+ +## 公钥登陆 + +原理:用户将自己的`公钥`保存在远程主机上,登录时,远程主机向用户发送一段随机码,用户用自己的`私钥`签名后,在发回远程主机,远程主机用实现保存的`公钥`进行验证,如果成功,表示用户的身份正确,无需输入密码 + +1. 用户生成一对自己的密钥 + +2. 将用户的`公钥`内容添加到 `$HOME/.ssh/authorized_keys`中(可以用`ssh-copy-id user@host`进行该操作) + +3. 重启ssh服务 `/etc/init.d/ssh restart` + +>公钥登陆相关配置 /etc/ssh/sshd_config +> +>RSAAuthentication yes +> +>PubkeyAuthentication yes +> +>AuthorizedKeysFile .ssh/authorized_keys + + +## 远程操作 + +1. 直接操作 `$ ssh user@host command` + +2. 用户和远程主机之间,建立命令和数据的传输通道 + +>将当前目录下的src文件,复制到远程主机的$HOME目录 +> +>`$ tar czv src | ssh user@host "tar xz"` +> +>将远程主机$HOME目录下面的src文件,复制到用户的当前目录 +> +>`$ ssh user@host "tar cz src" | tar xzv` + + +## 绑定本地端口 + +`ssh -D port user@host` + +`-D` 指定与远程host建立隧道的本地端口 + + +## 本地端口转发 + +假定`localhost`是本地主机,`remotehost`是远程主机,这两台主机之间无法连通。但是,另外还有一台`boardhost`,可以同时与前面两台主机互连。 + +在本机键入如下命令 + +`$ ssh -L localPort:remotehost:remotePort boardhost` + +`L`参数一共接受三个值,分别是"`本地端口:目标主机:目标主机端口`" + +该命令的意思是指定SSH绑定本地端口`localPort`,然后指定`boardhost`将所有的数据,转发到目标主机`remotehost`的`remotePort`端口 + +>remotehost是boardhost 的相对地址(或绝对地址),因为数据其实是由boardhost 传输到remotehost中的,与localhost无关 + +这样一来`localhost`与`remotehost`之间将形成私密隧道,访问`localPort`就等于访问`remotePort` + + +## 远程端口转发 + +假定`hostA`是本地主机,`hostB`是远程主机,这两台主机之间无法连通,而且,`boardhost`是一台内网主机,即`boardhost`可以访问`hostA`,但是`hostA`无法访问`boardhost` + +在`boardhost`键入如下命令 + +`$ ssh -R portA:hostB:portB hostA` + +`R`参数也是接受三个值,分别是"`远程主机端口:目标主机:目标主机端口`"。这条命令的意思,就是让`hostA`监听它自己的`portA`端口,然后将所有数据经由`boardhost`,转发到`hostB`的`portB`端口。 + +对`boardhost`来说`hostA`是远程机器,在`boardhost`机器上指定`hostA`监听某个端口,称为远程端口转发 + +>远程端口转发的前提条件是,`hostA`和`boardhost`两台主机都有sshd和ssh客户端,其原理就是:一开始由`hostA`充当`Server`,`boardhost`充当`Client`,`boardhost`发起请求建立一个连接;连接建立完成后,`hostA`就可以使用这个连接将充当`Clinet`,将数据转发至`boardhost`充当的`Server`;`boardhost`接收到数据后又需要充当`Client`将数据转发到`hostB` + +## 其他参数 + +`N`参数,表示只连接远程主机,不打开远程shell; + +`T`参数,表示不为这个连接分配TTY。 + +这个两个参数可以放在一起用,代表这个SSH连接只用来传数据,不执行远程操作。 + +`$ ssh -NT -D port user@host` + +`f`参数,表示SSH连接成功后,转入后台运行。这样一来,就可以在不中断SSH连接的情况下,在本地shell中执行其他操作。 + +`$ ssh -f -D port user@host` + +要关闭这个后台连接,就只有用kill命令去杀掉进程。 \ No newline at end of file diff --git a/_posts/2014-06-26-effective.md b/_posts/2014-06-26-effective.md new file mode 100644 index 0000000..bef531b --- /dev/null +++ b/_posts/2014-06-26-effective.md @@ -0,0 +1,1080 @@ +--- +layout: post +title: Effective Java +tags: Effective Java +categories: Java +--- + +* TOC +{:toc} + + +# `一` 创建和销毁对象 + +## `1`.考虑用静态工厂方法代替构造器 + +静态工厂方法的优势: + +`1`.有名称,可以见名知义了解获取对象的特点 + +`2`.不必每次调用时都创建一个对象 + +`3`.可以返回原类型的任何子类型对象 + +`4`.创建参数化类型实例时,可以使代码更简洁(右边无需再写一遍) + +`5`.不可变对象可以进行缓存,以提升性能 + +## `2`.遇到多个构造器参数时要考虑用构建器 + +构建器优势: + +`1`.重叠构造器代码冗余 + +`2`.`JavaBean`模式阻止了将类做成不可变的可能 + +`3`.`builder`可以进行参数检查 + +`4`.无论创建多少个对象,builder只需要一个,且可以在创建不同对象时进行调整,或者自动填充某些域 。 + +不足:创建对象前需要先创建builder + +## `3`.用私有构造器或者枚举类型强化Singleton属性 + +不使用枚举时: + +`1`.构造器私有 + +`2`.构造器抛出异常 `if(INSTANCE!=null) throw` + +`3`.序列化时必须声明所有实例域都是瞬时(`transient`)的,并提供一个`readResolve`方法返回现有的单例 + +## `4`.通过私有构造器强化不可实例化的能力 + +并在构造器中抛出异常 + +## `5`.避免创建不必要的对象 + +`1`.如果对象是不可变的,将始终可以重用 + +`2`.对于给定对象的适配器,不需要创建多个实例,例如:对于同一Map对象的keySet方法,每次返回的都是同一个对象 + +`3`.优先使用基本类型,而不是装箱类型 + +`4`.通过维护对象池来避免创建对象并不是好主意,除非对象非常昂贵(数据库连接池). + +## `6`.消除过期的对象引用 + +内存泄露常见来源: + +`1`.无意识的对象保持(只要是类自己管理内存,程序员就应该警惕内存泄露问题) + +`2`.被遗忘在缓存中的对象引用(考虑用`WeakHashMap`实现缓存) + +`3`.监听器和其他回调没有显式地取消注册 + +4种引用: + +`1`.**强引用** `Object obj = new Object();`: 只要强引用还存在,GC永远不会回收 + +`2`.**软引用** `SoftReference`:用来描述一些有用但不必需的对象,在系统将要发生内存溢出之前,GC会对软引用对象进行回收,若回收后仍没有足够内存,才会抛出异常。 + +`3`.**弱引用** `WeakReference`:也是用来描述非必需对象的,但是强度比软引用更弱,弱引用对象只能生存到下一次GC发生之前,当GC工作时,无论内存是否足够,都会回收弱引用对象 + +`4`.**虚引用** `PhantomReference`:也称为幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个实例。 为对象设置一个虚引用的唯一目的就是能在这个对象被GC回收时收到一个系统通知。 + +## `7`.避免使用终结方法 + +`1`.终结方法不能保证会被及时执行,也不能保证会被执行,所以不应该依赖终结方法来更新重要的持久状态 + +`2`.终结方法中发生的异常不会使线程终止,也不会打印栈轨迹,对象将处于破坏状态 + +`3`.终结方法有非常严重的性能损失 + +`4`.如果需要终止资源,应该显式地提供终止方法(io中的close方法),通常与try-finally结合使用,以确保及时终止 + +注意: + +`1`.除非作为安全网或者终止非关键资源,否则不要使用终结方法,最好就当没这个方法 + +`2`.如果使用了终结方法,要记住`super.finalize` + +`3`.如果终结方法作为安全网,要记录终结方法的非法用法 + +`4`.如果终结方法所属的类不是final的,请考虑使用`终结方法守卫者` + +--- + +# `二` 通用方法 + +## `8`.覆盖equals时请遵守通用约定 + +必须遵守`自反性`、`对称性`、`传递性`、`一致性`、`非空性` + +可以用`&&`连接所有对field的比较结果使代码简洁 + +诀窍: + +`1`.先使用`==` 操作符比较以优化性能 + +`2`.使用`instanceof`检查类型是否正确可以同时搞定null判断 + +`3`.只比较关键域,可以通过关键域计算出来的冗余域不需要比较 + +`4`.优先比较最可能不同、开销最低的域 + +`5`.对`float`,`double`用`compare`特殊处理,对`数组`用`Arrays.equals`处理 + +`6`.覆盖equals时总要覆盖hashCode + +`7`.不要企图让equals太智能,不需要考虑参数可能存在的等价关系 + +`8`.不要修改equals方法的参数类型(修改了就不是覆盖了),@Override可以避免这一点 + +`9`.**里氏替换原则** + +`10`.优先用**组合+公有视图**的方式扩展值组件,而不是用继承 + +## `9`.覆盖equals时总要覆盖hashCode + +`1`.相同的对象必须具有相同的hashCode(在hash表中,hashCode用来定位hash bucket,如果相等的对象有不同的hashCode,那么hash表将从错误的hash bucket中寻找对象,最后会判定为不存在) + +`2`.用`result = 31 * result + c`的方式获取hashCode,result初值设为17,c为每个域的散列值 + +`3`.能够通过关键域计算出来的**冗余域**可以排除 + +`4`.如果计算hashCode开销很大,可以对hashCode进行缓存,并在hashCode方法第一次被访问时才进行计算 + +`5`.不要试图排除关键域(equals用到的所有域)来提高性能,这样虽然会提高hashCode计算速度,但是会提高hashCode重复率,反而降低散列性能。 + +## `10`.始终要覆盖toString + +`1`.优点:易于阅读,易于调试,易与其他应用交换数据 + +`2`.提供一个静态工厂用于将toString返回值转换为本身的类型 + +`3`.在文档中明确表示是否规定了toString的格式,如果规定了格式就要严格遵守 + +`4`.对toString返回值中包含的关键内容提供访问方法 + +## `11`.谨慎地覆盖clone + +`1`.实现Cloneable接口(空接口)需要提供一个`public clone`(Object中声明为`proteced`)方法 + +`2`.从jdk1.5开始,引入了`协变返回类型`,clone方法被覆盖后可以返回Object的子类型(通则:永远不要让客户去做任何类库能完成的事情) + + `3`.clone方法就是另一个构造器;必须确保它不会伤害到原始对象,并确保正确地创建被克隆对象中的约束条件。 + + `4`.clone架构与引用可变对象的`final`域的正常用法是不相兼容的,为了使类可clone,有时需要去掉一些final + + `5`.使用`深拷贝`,递归地调用每个域中包含的域,一直深入到不可变类为止 + + `6`.拷贝"长"对象(数组,链表等)时,如果使用**递归**会创建很多指针消耗栈空间,可以考虑换成**迭代** + + `7`.另一种方法:先调用super.clone(),将所有域设为0值,然后为其赋值新对象。这种方法简单合理,但是没有”直接操作对象及其域中对象的clone方法“快。 + + `8`.clone过程中不应该调用任何非final方法(如果是private方法则不需要final),因为子类有机会改变该方法的行为。 + + `9`.重写的`public clone`方法应该忽略`CloneNotSupportedException`,但如果 + +该类是用来被继承的,则应该模拟Object.clone:声明为protected,抛出异常,不实现Cloneable。 + +`10`.对于不可变类,提供clone没有太大意义 + +`11`.用`拷贝构造器`(即参数类型为类本身的构造器)或者`拷贝工厂`代替clone,可以增加一个参数实现克隆对象的转型(例如接受一个Collection参数与一个Class参数) + +## `12`.考虑实现Comparable接口 + +`1`.可以方便地使用各种泛型算法与Comparable的集合实现 + +`2`.强烈建议`(x.compareTo(y) == 0) == (x.equals(y))`,但非必须,若违反了这个条件,应该予以说明。 + +`3`.不满足上一条约定的对象放入`有序集合`(使用compareTo,如TreeSet)中后,将无法遵守集合接口通用(使用equals)约定(如BigDecimal,浮点数) + +`4`.优先用`组合+公有视图`的方式扩展值组件,而不是用继承(同equals) + +`5`.Comparable只规定了符号,没有规定数值大小,可以直接返回两个数值相减的结果,但是要小心溢出 + +--- + +# `三` 类和接口 + +## `13`.使类和成员的可访问性最小化 + +`1`.实例域决不能是公有的 + +`2`.静态公共域只能为final修饰的基本类型或者final修饰的不可变类 + +`3`.用`Collections.unmodifiableList(arr)` + +将数组变成不可变列表后再提供访问;或者在访问方法中返回数组的clone() + +## `14`.在公有类中使用访问方法而非公有域 + +暴露域将失去内部表示法的灵活性,也将失去对数据访问进行限制的能力 + +## `15`.使可变性最小化 + +`1`.创建不可变类的5条规则 + +>`(1)`.不要提供任何会修改对象状态的方法 +> +>`(2)`.保证类不会被扩展(final或者构造器私有) +> +>`(3)`.使所有域都是final的 +> +>`(4)`.使所有域成为私有的 +> +>`(5)`.确保对于任何可变组件的互斥访问 + + +`2`.用公有的静态域或静态工厂为不可变类提供缓存 + +`3`不可变对象本质上是线程安全的,它们不要求同步,可以被自由地共享,永远不需要保护性拷贝,不需要拷贝构造器 + +`4`.不可变类可以共享内部信息 + +`5`.不可变对象为其他对象提供了大量的构件 + +`6`.不可变类的缺点:每一个不同的值都需要一个单独的对象,存在性能问题,可以提供1个配套类来进行过渡操作(如StringBuilder) + +`7`.因历史问题没有设置成final的不可变类(如`BigInteger`),在使用时需要检查对象是否被扩展了,如果被扩展了就可能需要保护性拷贝 + +`8`.如果实现`Serializable`,必须显式地提供`readObject`和`readResolve`方法或者使用`ObjectOutputStream.writeUnshared`和`ObjectInputStream.readUnshared`方法 + +`9`.除非有必须的理由让类可变,否则类应该是不可变的;如果类无法做成不可变的,也应该尽力限制其可变性。因此域也应该是final的。 + +## `16`.复合优先于继承 + +`1`.继承破坏了封装性,子类依赖于父类的实现细节,若父类内部发生变动,将可能损坏子类。 + +`2`.假设子类覆盖父类所有的方法,增加了对参数的类型检查;后来父类增加了新方法,而子类没有同步更新,非法的参数将有可能通过类型检查,这将会产生安全漏洞 + +`3`.假设子类增加了新方法,后来父类也新增了1个签名相同但返回类型不同的方法,编译将不通过;如果方法签名与返回类型都相同,将形成覆盖,又回到了上一种情况 + +`4`.`复合/转发`的类称为组件,并实现相同接口的类为`包装类`,每个接口只需要1个转发类 + +`5`.包装类不适用于回调框架,回调函数传递是1个指向自身的引用,避开了外面的包装类 + +`6`.继承会将api中所有缺陷传播到子类中去,而复合却可以设计新的api隐藏缺陷 + +`7`.只有真正存在从属关系时,才应该使用继承 + +## `17`.要么为继承而设计,并提供文档说明,要么就禁止继承 + +`1`.**决不能在构造器中调用可以被覆盖的方法**,否则这样的方法将会在子类构造器之前运行,若是该方法中存在对域的访问,将会造成空指针异常 + +`2`.类必须提供适当的hook,以便能够进入子类的内部工作流程 + +`3`.如果实现了Cloneable或Serializable接口,那么`clone`与`readObject`都**不**能调用可被覆盖的方法 + +`4`.如果实现了Serializable接口,必须使readObject与writeReplace成为**受保护**的方法,否则它们将会被子类忽略掉 + +`5`.如果必须让类可被继承,**务必保证这个类永远不会调用可覆盖方法**,可以机械地消除可覆盖方法的`自用性`:将每个可覆盖方法的方法体移到一个私有的“`辅助方法`中”,然后让可覆盖方法调用辅助方法实现功能,这样可以确保可覆盖方法永远不会干涉到父类的运行。 + +## `18`.接口优于抽象类 + +`1`.现有的类易被更新,以实现新的接口 + +`2`.接口是定义mixin(混合类型)理想选择 + +`3`.接口允许我们构造非层次接口的类型框架 + +`4`.在包装类模式下,接口使得安全地增强类的功能成为可能 + +`5`.通过对导出的每个接口都提供一个`骨架实现`(skeletal implementation)类,把接口和抽象类的优点结合起来 + +`6`.实现了接口的类可以把对于接口方法的调用转发到一个内部私有类的实例上,这个内部私有类扩展了骨架实现类,这种方法被称为“`多重模拟继承`”。 + +`7`.抽象类比接口更易于扩展,抽象类可以直接增加方法,接口却不可以。 + +`8`.接口一旦被公开发行,并且已被广泛实现,再想改变接口是不可能的。 + +`9`.接口通常是定义允许多个实现的类型的最佳途径,但是当**演变**的容易性比**灵活性**和**功能性**更重要时例外。 + +## `19`.接口只用于定义类型 + +`1`.常量接口模式是对接口的不良使用 + +`2`.如果这些常量最好被看作枚举类型的成员,就应该是`枚举类型`,否则应该使用不可实例化的工具类来导出这些常量。 + +`3`.如果大量利用工具类来导出常量,可以通过利用`静态导入`机制,避免用类名来修饰常量名。(1.5版本后) + +## `20`.类层次优于标签类 + +`1`.标签类过于冗长,容易出错,并且效率低下 + +`2`.编译器确保每个类的构造器都初始化它的数据域,对于根类中声明的每个抽象方法,都确保有一个实现。 + +`3`.类层次的另一种好处在于,它们可以用来反映类型之间本质上的层次关系,有助于增强灵活性,并进行更好的编译时类型检查。 + +## `21`.用函数对象表示策略 + +`1`.如果一个类仅仅导出一个方法,它的实例实际上就等同于一个指向该方法的指针。 + +`2`.设计具体的策略类时,还需要定义一个策略接口 + +`3`.如果它被重复执行,考虑将函数对象存储到一个私有的静态final域里,并重用它。 + +`4`.宿主类可以导出公有的静态域(或静态工厂方法),其类型为策略接口,具体的策略类可以是宿主类的私有嵌套类。 + +## `22`.优先考虑静态成员类 + +`1`.嵌套类存在的目的应该只是为它的外围类提供服务。 + +`2`.嵌套类有四种:`静态成员类`,`非静态成员类`,`匿名类`,`局部类` + +`3`.非静态成员类每个实例都隐含着与外围类的一个实例相关联。 + +`4`.如果嵌套类的实例可以在它的外围类的实例之外独立存在,这个嵌套类就必须是静态成员类;在没有外围实例的情况下,想创建非静态成员类的实例是不可能的。 + +`5`.非静态成员类的一种常见用法是定义一个`Adapter`,它允许外部类的实例被看作是另一个不相关的类的实例。(如集合视图 keySet iterator) + +`6`.如果声明成员类不要求访问外围实例,那成员类就应该是静态的。非静态成员类的实例必须要有一个外围的实例 + +`7`.私有静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。 + +`8`.匿名类的常见用法是①动态地创建函数对象(第21条),②创建对象过程(Runabel,Thead,TimerTask),③在静态工厂方法的内部 + +`9`.在任何可以声明局部变量的地方,都可以声明局部类,并且局部类也遵守同样的作用域规则。 + +--- + +# `四` 泛型 + +## `23`.不要在新代码中使用原生态类型 + +`1`.错误应该尽早发现,最好在编译期就发现。 + +`2`.如果使用原生类型,就失去了泛型在安全性和表述性方面的所有优势 + +`3`.在不在乎集合中的元素类型时,使用**无限制的通配符**类型(``) + +`4`.在类文字中必须使用原生态类型;因为泛型会被擦除,所以`instanceof`操作符使用原生类型就可以了。 + +## `24`.消除非受检警告 + +`1`.泛型相关的编译警告: + +>* 非受检强制转化警告(unchecked cast warnnings) +> +>* 非受检方法调用警告 +> +>* 非受检普通数组创建警告 +> +>* 非受检转换警告(unchecked conversion warnings) + +`2`.要尽可能地消除每一个非受检警告 + +`3`.如果无法消除警告,可以同时证明引起警告的代码是类型安全的,只有在这种情况下才可以用一个`@SuppressWarnings("unchecked")`注解来禁止这条警告。 + +`4`.SuppressWarnings注解可以用在任何粒度的级别中,应该始终在尽可能小的范围中使用它,永远不要在整个类中使用它 + +`5`.如果在长度不止一行(即一行代码中存在多个动作)的代码中使用了SuppressWarnings注解,可以将它移到一个局部变量声明中,以减小SuppressWarnings注解的影响范围。 + +`6`.每当使用`@SuppressWarnings("unchecked")`时,都要添加一条注释,说明为什么这么做是安全的。 + +## `25`.列表优先于数组 + +`1`.数组与泛型的区别 + +>* 数组是`协变`的,如果`sub`为`super`的子类型,那么`sub[]`也是`super[]`的子类型;相反,泛型则是`不可变`的,对于两个类型`Type1`与`Type2`,`List`与`List`没有任何关系。 +> +>* 数组是`具体化`的,因此数组在运行时才知道并检查它的元素类型约束;而泛型则是通过擦除(erasure)来实现的,泛型只在编译时强化类型信息,并在运行时擦除元素类型信息,擦除可以使泛型与早期无泛型的代码兼容。 + +`2`.泛型与数组不能混合使用,因为泛型数组不是类型安全的。(因为数组是协变的,所以数组可以自动向上转型,我们可以往向上转型后的数组中插入与转型前不同的类型的数据,而借转型前数组名义取出的对象将会产生CalssCastException) + +~~~ +List[] stringLists = new List[]; +List intList = Arrays.asList(42); +Object[] objects = stringLists; +objects[0] = intList; +String s = stringLists[0].get(0); +~~~ + +`3`.每当调用可变参数方法时,就会创建一个数组来存放参数,如果这个数组的元素类型不是可具体化的,就会得到一条警告,除了禁止这个警告,别无他法。 + +`4`.数组提供了运行时的类型安全,但是没有提供编译时的类型安全;泛型提供了编译时的类型安全,但是没有提供运行时的类型安全。 + +`5`.不可具体化的类型数组转换只能在特殊情况下使用。 + +## `26`.优先考虑泛型 + +`1`.禁止数组类型的未受检转换比禁止标量类型的更加危险 + +`2`.使用泛型比使用需要在客户端代码中进行转换的类型更加安全,也更加容易,在设计新类型的时候,要确保不需要这种转换就能使用。 + + +## `27`.优先考虑泛型方法 + +`1`.如果泛型方法返回的对象是无状态的,可以考虑将该对象做成`泛型单例` + +`2`.调用泛型方法时,编译器会对返回值进行类型推到,以适应接收者的类型 + +## `28`.利用有限通配符来提升API的灵活性 + +`1`.泛型中使用`extends`时,每个类型都是自身的子类型;使用`super`时,每个类型都是自身的超类型。 + +`2`.为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。**PECS**(producer-extends;consumer-super) + +`3`.不要用通配符类型作为返回类型 + +`4`.如果编译器不能推断你希望它拥有的类型,可以通过显式的类型参数来指定。 + +`5`.如果类型参数在方法中声明中只出现一次,可以用通配符取代它。 + +`6`.不能把null之外的任何值放进List中 + +## `29`.优先考虑类型安全的异构容器 + +`1`.`cast`方法是java的cast操作符的动态模拟,它只检验它的参数是否为Class对象所表示的类型的实例,如果是,就返回参数,否则抛出ClassCastException + +`2`.类型安全的异构容器的缺陷 + +>* 恶意的客户端可以很轻松的破坏类型安全,只要它以原生态类型使用Class对象,在容器的添加方法第一行加入参数的类型检查(`type.cast(obj)`)可以避免这种问题。 +> +>* 不可存放不可具体化的类型数据(如`List`,因为`List`的类型为List.class,而`List.class`是语法错误),只能保存String或String[]等可具体化的对象。 + +`3`.利用有限制类型参数或者有限制通配符来限制可以表示的类型 + +`4`.`asSubclass(List.class)`方法将Class类的类型令牌安全且动态地转换成参数所表示的类(List)的一个子类的通配符表示法(? extends List),参数必须为调用者的父类型,如果成功,返回它的参数,否则抛出ClassCastExcpetion + +--- + +# `五` 枚举和注解 + +## `30`.用enum代替int常量 + +`1`.因为没有可以访问的构造器,枚举类型是真正的`final`。 + +`2`.枚举类型允许添加任意的方法和域,并实现任意的接口。枚举提供了所有Object方法的高级实现,实现了Comparable和Serializable接口,并针对枚举类型的可任意改变性设计了序列化方式。 + +`3`.枚举有一个静态的`values`方法,按照声明顺序返回它的值数组,`toString`方法返回每个枚举值的声明名称。 + +`4`.在同一个枚举类型中,将特定的行为与每个具体的枚举常量关联起来:在枚举类型中声明一个抽象的方法,并**在特定于常量的类主体中,覆盖该方法**。 + +`5`.每一个枚举常量可以关联特定的数据(在构造器中绑定),因此特定于枚举常量的方法可以与特定于枚举常量的数据结合起来。 + +`6`.枚举类型有一个自动产生的valueOf(string)方法,它将常量的名字转变成常量本身。如果枚举类型中覆盖toString,要考虑编写一个`fromString`方法,将定制的字符串表示法变回相应的枚举。 + +`7`.如果多个枚举常量同时共享多个行为,要考虑`策略枚举`。 + +## `31`.用实例域代替序数 + +`1`.所有枚举都有一个`ordinal`方法,它返回每个枚举常量在类型中的数字位置 + +`2`.永远不要根据枚举的序数导出与他关联的值,而是要将值保存在一个实例域中。 + +## `32`.用EnumSet代替位域 + +`1`.`or`位运算将几个常量合并到一个集合中,称为`位域`。(形如111→rwx) + +`2`.EnumSet可以表示从单个枚举类型中提取的多个值的集合,`EnumSet.of` + +`3`.截止jdk1.6,无法创建不可变的EnumSet + +## `33`.用EnumMap代替序数索引 + +`1`.`EnumMap`构造器采用键类型的Class对象:这是一个有限制的类型令牌,它提供了运行时的泛型信息。实例化需要指定Class类型,可以保存同一个类型的对象与值的映射关系 + +`2`.最好不要用序数来索引数组,而要用EnumMap,如果需要表示的关系是多维的,就用嵌套的EnumMap + +## `34`.用接口模拟可伸缩的枚举 + +`1`.用接口模拟可伸缩的枚举有个小小的不足,即无法实现从一个枚举类型继承到另一个枚举类型。 + +`2`.虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,模拟可伸缩的枚举。` & interfaceType>` + +## `35`.注解优先于命名模式 + +`1`.注解的声明类型就是自身通过`Retention`和`Target` 注解进行注解,`@Retention(RetentionPolicy.RUNTIME)`元注解表明,应该在运行时保留;`@Target(ElementType.METHOD)`元注解表明,只有在方法声明中才是合法的。 + +`2`.`InvocationTargetException` 封装了目标方法在运行时产生的异常,出现该异常表明目标方法确实被运行了,使用`getCause()`可以获得方法运行时产生的具体异常。 + +`3`.用花括号包裹用逗号分隔的参数将自动变成参数数组 + +`4`.如果编写的工具需要在源文件上添加信息,就要定义一组适当的注解类型。 + +## `36`.坚持使用Override注解 + +`1`.`Override`会对标记的方法进行代码检查,确保该方法成功地被重写了。 + +`2`.现代IDE在检测到Override注解时会提醒用户小心无意识的覆盖。 + +`3`.在抽象类或接口中使用Override可以确保不会添加新的方法。 + +## `37`.用标记接口定义类型 + +`1`.标记接口是没有包含方法声明的接口,只是指明了实现类应该具有的属性。 + +`2`.标记接口有两点胜过注解 + +>* 标记接口可以在编译时进行类型检查,保证参数的有效性 +> +>* 标记接口类型更容易指定实现类的约束条件,如Set就是一个标记接口,他限制了实现类必须具有Collection特性。 + +`3`.注解有两点胜过标记接口 + +>* 注解可以在内部丰富信息而不影响客户端使用 +> +>* 标记注解在支持注解的框架中具有一致性 + +`4`.如果标记需要应用到任何程序元素而不仅仅是类或接口上,就应该使用注解;否则应该使用标记接口;如果该标记只用于特定的接口,最好将标记定义成该接口的子接口(Set与Collection) + +`5`.如果一个标记永远不会扩展,应该定义成标记接口;如果未来可能需要在标记上添加更多信息,则应该定义成标记注解。 + +--- + +# `六` 方法 + +## `38`.检查参数的有效性 + +`1`.应该在发生错误后,能够尽快检测出错误,不要让错误在过深的地方爆发出来。 + +`2`.对于公有的方法,要用`@throws`标签在文档中说明违反参数值限制时会抛出的异常。 + +`3`.非公有的方法通常应该使用断言来检查参数, `-ea`启用 + +`4`.如果有效性检查非常昂贵,或者已经隐含在计算中完成则无需进行检查 + +`5`.使用`异常转译`技术,将计算过程中抛出的异常转换为正确的异常。 + +## `39`.必要时进行保护性拷贝 + +`1`.对构造器的每个可变参数进行保护性拷贝 + +`2`.保护性拷贝是在检查参数的有效性之`前`进行的,并且有效性检查是针对拷贝之`后`的对象,而不是针对原始对象,这样可以避免在拷贝阶段从另一个线程改变类的参数。 + +`3`.对于参数类型可以被不信任方子类化的参数,不要使用`clone`。 + +`4`.如果需要访问类的内部域,返回内部域的保护性拷贝。 + +`5`.如果不能容忍客户端传入的对象可变,使用保护性拷贝 + +`6`.长度非0的数组总是可变的,使用保护性拷贝或者返回数组的不可变视图 + +`7`.通常使用Date.getTime()返回的long类型作为内部的时间表示法。 + +`8`.包装类一般客户端自己使用,无需使用保护性拷贝。 + +## `40`.谨慎设计方法签名 + +`1`.谨慎地选择方法的名称 + +`2`.不要过于追求提供便利的方法,只用某项操作被经常用到时,才提供快捷方式。 + +`3`.避免过长的参数列表,相同类型的长参数序列格外有害。 + +缩短参数列表的三种方法 + +>* 把方法分解成多个方法,但是会导致方法过多,但是提升方法的正交性,可以减少方法数目。 +> +>* 创建辅助类,用来保存参数的分组。 +> +>* 从对象构建到方法调用都采用`Builder`模式 + +`4`.对于参数类型,优先使用接口而不是类。 + +`5`.对于boolean参数,要优先使用连个元素的枚举类型。 + +## `41`.慎用重载 + +`1`.要调用哪个重载方法是在`编译`时做出决定的。 + +`2`.对于重载方法的选择是`静态`的,而对于被覆盖的方法的选择是`动态`的。 + +`3`.如果普通用户根本不知道"对于一组给定的参数,其中哪个重载方法将会调用",那么这样的API很可能导致错误。 + +`4`.安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法;如果方法使用可变参数,保守的策略是根本不重载它。 + +`5`.一个类的多个构造器总是重载的,很多情况下可以用静态工厂代替构造器。 + +`6`.对于一对重载方法,如果至少有一个对应的参数在两个方法中具有"**根本不同**(无法强转)"的类型,那就不会让用户感到混淆。 + +`7`.将`list.remove()`参数转换成`Integer`,迫使选择正确的重载方法。**java语言添加了自动装箱后,破坏了List接口**。 + +`8`.当传递的参数没有本质的不同时,必须确保所有的重载方法行为一致,做法是让更具体化的重载方法把调用转发给更一般化的重载方法。 + +## `42`.慎用可变参数 + +`1`.参数数量检测无法在编译期完成 + +`2`.可变参数方法会将多个参数收集成**数组**,但若参数本身就是数组,那么收集后的数组将无法得到预期的结果(`Arrays.asList`) + +`3`.不必改造具有数组参数的每个方法;只当确实是在数量不定的值上执行调用时才使用可变参数 + +`4`.重视性能时要小心,可变参数方法每次调用都会进行数组分配和初始化 + +`5`.提供大多数使用情况下的不可变参数方法,再额外附加可变参数方法(`EnumSet.of`) + +## `43`.返回零长度的数组或者集合,而不是null + +`1`.如果返回null,每次调用该方法时都会需要进行空值判断 + +`2`.长度为0的数组是不可变的,因此返回的空数组可以自由共享 + +(`Collections.emptySet`; + +`Collections.emptyList`; + +`Collections.emptyMap`) + +## `44`.为所有导出的API元素编写文档注释 + +`1`.文档注释应该简洁地描述出它和客户端之间的约定 + +`2`.应该列举出这个方法的所有前提条件,后置条件和副作用 + +`3`.`{@code}`会造成包裹的内容以代码片段形式呈现(即生成code标签) + +`4`.`{@literal}`会自动转义包裹的内容 + +`5`.同一个类或者接口中的两个成员或者构造器,不应该具有相同的概要描述 + +`6`.不要忽视类是否是**线程安全**的,以及类是否是**可序列化**的。 + +`7`.`{@inheritDoc}`继承注释 + +--- + +# `七` 通用程序设计 + +## `45`.将局部变量的作用域最小化 + +`1`.最有力的方法就是在第一次使用它的地方声明 + +`2`.几乎每个局部变量都应该包含一个初始化表达式,如果没有足够的信息来进行有意义的初始化,就推迟这个声明,try-catch除外。 + +`3`.循环中提供了特殊的机会来将变量的作用域最小化,如果在循环终止后不再需要循环变量的内容,`for`循环就优先于`while`循环。 + +## `46`.for-each 循环优先于传统for循环 + +`1`.嵌套的for-each循环,外层循环的对象可以在内层对象中保持,不会随内层循环移动指针。 + +`2`.有3种情况无法使用for-each + +>* 需要遍历并删除元素时 +> +>* 需要遍历元素并取代某些值时 +> +>* 需要并行迭代多个集合时 + +## `47`.了解和使用类库 + +`java.lang`; + +`java.util`; + +`java.io`; + +`java.util.concurrent`; + +## `48`.如果需要精确的答案,请避免使用float和double + +使用`BigDecimal`,`int` 或者 `long` 进行货币计算 + +## `49`.基本类型优先于装箱基本类型 + +`1`.基本类型与装箱基本类型的区别 + +>* 基本类型只有值,而装箱基本类型的**同一性**不仅取决于值 +> +>* 装箱基本类型除了值还多了一个null +> +>* 基本类型更节省空间 + +`2`.对装箱基本类型使用==操作符几乎总是错误的。 + +`3`.当一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型会**自动拆箱**。若被拆的对象为null,将抛出异常。 + +`4`.在集合中只能使用装箱基本类型 + +## `50`.如果其他类型更适合,则尽量避免使用字符串 + +`1`.字符串不适合代替其他的值类型 + +`2`.字符串不适合代替枚举类型 + +`3`.字符串不适合代替聚集类型(即将多个部分拼接成字符串用关键字分隔),最好用一个简单的类来代替。 + +`4`.字符串不可替代控制共享资源访问权限的键 + +## `51`.当心字符串连接的性能 + +`1`.为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间 + +`2`.为了性能,用`StringBuilder`替代String + +## `52`.通过接口引用对象 + +`1`.如果有合适的接口类型存在,那么对于参数,返回值,变量和域来说,就都应该使用接口类型进行声明 + +`2`.如果对象属于基于类的框架,就应该使用基类引用这个对象,尽量用抽象类型引用对象 + +`3`.如果程序依赖于类中额外的方法,那么这种类应该只被用来引用实例,而不应该作为参数类型。 + +## `53`.接口优先于反射机制 + +反射功能应该只有在设计时被用到,或者只是用来创建对象,程序在运行时不应该以反射方式访问对象 + +## `54`.谨慎地使用本地方法 + +`1`.本地方法3个用途 + +>* 提供访问特定于平台的机制的能力,如注册表和文件锁 +> +>* 提供了访问遗留代码库的能力 +> +>* 通过本地语言,编写程序中注重性能的部分 + +`2`.使用本地方法**不值得提倡**,`java.util.prefs`提供了访问注册表的能力,`java.awt.SystemTray`提供了访问桌面系统托盘的能力 + +`3`.本地语言是`不安全`的,无法免受内存毁坏错误的影响。 + +## `55`.谨慎地进行优化 + +`1`.好的程序体现了信息隐藏的原则,只要有可能,就要把设计决策(暂时理解为业务逻辑)集中在单个模块中,因此,可以改变单个决策,而不会影响到系统的其他部分。 + +`2`.努力避免那些限制性能的设计决策,系统中最难更改的是指定模块**交互关系**的组件,**API**,**线路层协议**和**永久数据格式** + +`3`.要考虑API设计设计决策的性能后果 + +>* 如果将公类类型成为可变的,就可能导致大量不必要的保护性拷贝 +> +>* 在适合使用复合模式的公有类中使用继承,会把这个类与它的超类永远束缚在一起 +> +>* 在API中使用实现类而不是接口,会将客户端束缚在具体的实现类上,如果将来出现更快的实现也无法使用。 + +## `56`.遵守普遍接受的命名习惯 + +`1`.《`The Java Language Specification`》 + +`2`.对于缩写单词,也应该遵守首字母大写原则 + +`3`.常量域是唯一推荐使用下划线的情形 + +`4`.类型参数,`T`任意类型,`E`集合元素类型,`KV`键值映射类型,`X`异常,任何类型的序列可以是T,U,V或者T1,T2,t3 + +`5`.转换对象类型,返回不同类型的独立对象的方法 `toType`; + +返回视图`asType`; + +返回一个与被调用对象相同值的基本类型的方法`typeValue`; + +静态工厂`valueOf`,`of`,`getInstance`, + +`newInstance`,`getType`,`NewType` + +--- + +# `八` 异常 + +## `57`.只针对不正常情况使用异常 + +`1`.异常机制的设计初衷是为了不正常的情形,很少有JVM实现会对它进行优化;把代码放在try-catch中反而阻止了本来可以进行的优化;对数组进行遍历的标准模式并会不导致冗余的检查,有些现代的JVM实现会将它们优化掉。 + +`2`.异常只应用于不正常情况,不应该用于正常的控制流。 + +`3`.如果类具有状态,有两种方法可以避免异常,第一种是`状态测试法`,如Iteable接口的hasNext。 + +`4`.另一种方法是`可识别返回值法`,并发环境下或状态检查影响性能时更适用,如果所有情况等同,优先使用状态检测法。 + +## `58`.对可恢复的情况使用受检异常,对编程错误使用运行时异常 + +`1`.三种可抛出结构:`受检异常`,`运行时异常`,`错误` + +`2`.如果期望调用者能够适当地恢复,使用受检的异常 + +`3`.用运行时异常表明编程错误,大多数运行时异常表示提前违例,即API用户没有遵守约定 + +`4`.错误通常被JVM保留,用于表示资源不足,约束失败等,最好不要再实现任何新的Error子类 + +`5`.如果不清楚是否有可能恢复,最好使用**未受检**的异常 + +`6`.受检的异常往往需要表明可回复条件,提供一些便于查询信息的方法尤其重要 + +## `59`.避免不必要地使用受检的异常 + +`1`.如果正确使用API不能阻止异常的产生,而一旦产生异常API使用者能够立即采取有效的动作,就值得使用受检的异常,除非两个条件都成立,否则更适合使用**未受检**异常。 + +`2`.把受检的异常变成未受检异常的方法是把抛出异常的方法分成两个方法,其中一个方法返回boolean表示是否应该抛出异常(类似状态检测法)。这种方法在并发环境下或者异常检测很耗性能时则不适用。 + +## `60`.优先使用标准的异常 + +`ConcurrentModificationException`; + +`UnsupportedOperationException` + +## `61`.抛出与抽象相对应的异常 + +`1`.更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常,这种做法称为`异常转译` + +`2`.一种特殊的异常转译形式称为`异常链`,如果低层异常对于调试高层异常有帮助,就适合使用异常链,高层异常提供访问方法获得低层异常(`Throwable.getCause`) + +`3`.大多数标准异常都有异常链构造器,没有异常链构造器的,可以使用Throwable的`initCause`方法设置原因。 + +`4`.异常转译不应滥用,更好的做法是**及时进行参数检查**,尽早中断异常链。 + +## `62`.每个方法抛出的异常都要有文档 + +`1`.始终要单独声明受检的异常,而不是抛出多个异常的共同的超类,并且利用`@throws`标记,准确记录下抛出每个异常的条件 + +`2`.不要在方法声明中抛出任何`RuntimException` + +`3`.如果一个类中的多个方法出于同样的原因抛出同样的异常,可以将异常的信息写入到类的文档注释中。 + +## `63`.在细节消息中包含能捕捉失败的信息 + +`1`.如果捕获失败,异常的细节信息应该包含所有对异常有贡献的参数和域的值 + +`2`.在异常的**构造器**中引入细节信息 + +## `64`.努力使失败保持原子性 + +`1`.失败的方法调用应该使对象保持在被调用之前的状态 + +`2`.保持失败原子性的方法: + +>* 设计不可变类 +> +>* 检查参数有效性,提前失败,将所有可能产生失败的运算放到改变状态的操作之前 +> +>* 编写恢复代码 +> +>* 在对象的一份临时拷贝上操作,然后用拷贝中的结果代替对象的内容(如`Collections.sort`) + +`3`.在缺乏同步机制的情况下并发访问对象,难以保持失败原子性。 + +## `65`.不要忽略异常 + +有一种情形可以忽略异常,即关闭FileInputStream时,因为文件状态没有改变,不必终止当前操作,即使这种情况下,也应该将异常信息记录下来,以便排查。 + +--- + +## `九` 并发 + +## `66`.同步访问共享的可变数据 + +`1`.同步可以保证每个线程看到的对象处于一致 的状态中;保证每个线程都看到锁对象被修改的结果。 + +`2`.java语言规范保证,读或者写一个变量是原子的,除非这个变量的类型为`long`或`double` + +`3`.读写原子数据的时候也应该使用同步,不仅为了`互斥访问`,也为了在线程间进行可靠的`通信`。详见**java内存模型**。 + +`4`.不要用`Thead.sotp`。要中断一个线程,建议做法是轮询boolean域,通过另一个线程修改这个boolean域;具体方法是添加对这个boolean域的读写方法,并对读写方法添加`同步`。 + +`5`.`volatile`修饰符虽然**不**执行互斥访问,但是可以保证每个线程在读取该域的时候都能看到**最近**刚被写入的值。 + +`6`.使用`AtomicLong`进行原子化long操作 + +`7`.最佳做法是要么不共享可变数据,要么就共享不可变数据,就是将可变数据限制在单个线程内部。 + +`8`.活性失败:线程永远无法读取到变量的变化 + +`9`.安全性失败:一个线程修改非原子变量时,另一个线程过来读取,此时只能读取到**过渡**状态的数据。 + +## `67`.避免过度同步 + +`1`.为了避免活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制。尽量不要在同步代码块中调用可被覆盖的方法或者客户端提供的方法(**原则上永远不要信任客户端**) + +`2`.当客户端造成服务端变化后能够预定通知,即观察者(`Observer`)模式 + +`3`.线程B需要获得锁才能继续执行,但是线程B执行完当前的代码片段后,线程A才能让出锁,此时将会造成`死锁`。死锁的根本原因是,多个线程同时需求同步锁。 + +`4`.java中的锁是`可重入`的,这样的锁简化了多线程面向对象的构造 + +`5`.不要把锁对象传递给客户端,而是传递锁对象的快照,这样可以避免死锁 + +`6`.java类库提供了并发集合`CopyOnWriteArrayList` + +`7`.同步区域内的工作越少越好 + +`8`.如果一个可变的类需要并发访问,应该使这个类变成线程安全的,通过内部同步可以实现;如果不确定是否需要同步,那就不要同步,并建立文档,注明它不是线程安全的 + +`9`.如果在内部同步了类,就可以进行`分拆锁`,`分离锁`和`非阻塞`并发控制 + +`10`.如果方法修改了静态域,那么就必须同步对这个域的访问,即使它往往只作用于单个线程,这是为了可靠的线程通信 + +## `68`.executor和task优先于线程 + +1.`Executor Framework` + +~~~ +ExecutorService executor = Executors.newSingleThreadExecutor(); +executor.execute(runnable); +executor.shutdown(); +invokeAny +invokeAll +awaitTermination +ExecutorCompletionService +~~~ + +`2`.如果编写的是小程序,或者轻载服务器,使用`Executors.newCachedThreadPool` + +`3`.在大负载的产品服务器中,使用`Executors.newFixedThreadPool` + +`4`.尽量不要编写自己的工作队列,尽量不直接使用线程。现在的关键抽象不再是Thread了,而是task。task有两种:Runnable与Callable,执行任务的关键机制是ExecutorService。 + +`5`.ScheduledThreadPool代替Timer + +`6`.《`Java Concurrency in Practice`》 + +## `69`.并发工具优先于wait和notify + +`1`.concurrent包中三类工具: + +>* Executor Framework +> +>* 并发集合 +> +>* 同步器 + +`2`.并发集合中不可能排除并发活动,将他锁定没什么作用,只会使程序速度变慢。 + +`3`.应该优先使用`ConcurrentHashMap`,而不是`Collections.synchronizedMap`或者`Hashtable` + +`4`.阻塞队列用于工作队列或**生产者-消费者**队列,任意个生产者线程向工作队列中添加项目,并且当工作项目可用时,任意个消费者线程从工作队列中取出项目 + +`5`.同步器是一些使线程能够等待另一个线程的对象(互斥),最常用的同步器是`CountDownLatch`和`Semaphore`,较不常用的是CyclicBarrier和Exchanger + +`6`.倒计数锁存器,当countDown数量足够时,将自动从`await`状态唤醒,否则将继续等待。 + +`7`.线程饥饿死锁 + +`8`.对于间歇式的定时,始终应该优先使用`System.nanoTime`,而不是System.currentTimeMills + +`9`.`a.wait()`,使所有访问a对象的线程等待,该方法必须在同步区域内被调用;始终应该使用`wait循环模式`调用wati方法,永远不要在循环之外调用wait方法。 + +`10`.优先使用`notifyAll`,因为被意外唤醒的线程会在循环中检查条件,如果条件不满足,会继续等待。 + +## `70`.线程安全性的文档化 + +`1`.在一个方法声明中出现synchronized修饰符,这个是实现细节,不是导出API的一部分 + +`2`.一个类为了可以被多个线程安全地使用,必须在文档中清楚说明它所支持的线程安全级别: + +>* **不可变的** +> +>* **无条件的线程安全**:可变类,但是有足够的内部同步,可以被并发使用 +> +>* **有条件的线程安全**:某些方法需要外部同步 +> +>* **非线程安全**:必须外部同步 +> +>* **线程对立**:即使所有调用都有外部同步,依旧不能安全地并发使用。通常因为没有同步地修改静态数据 + +`3`.必须指明哪个调用序列需要外部同步,还要指明为了调用这个序列,**必须获得哪一把锁**。如果一个对象代表了另一个对象的一个视图,客户端通常就必须在后台对象上同步,以防止其他线程直接修改后台对象。 + +`4`.除非从返回类型来看已非常明显,否则静态工厂必须在文档中说明被返回对象的线程安全性 + +`5`.私有锁对象必须声明为final的,只能用在无条件线程安全类上,有条件线程安全类不能使用,因为外部调用时无法获得锁。 + +## `71`.慎用延迟初始化 + +`1`.对静态域使用延迟初始化,用 `lazy initialization holder class` 模式(静态私有内部类) + +`2`.对实例域使用延迟初始化,用**双重检查模式**,第二次检查在同步中进行 + +`3`.如果允许重复初始化,用**单重检查模式** + +## `72`.不要依赖于线程调度器 + +`1`.任何依赖于线程调度器来达到正确性或者性能要求的程序,很可能不可移植 + +`2`.最好的办法是确保可运行的线程平均数量**不**明显多于处理器数量 + +`3`.如果线程没有在做有意义的工作,就不应该运行 + +`4`.不要企图通过`Thread.yield`来修正程序,它唯一的作用是在测试期间人为增加程序的并发性 + +`5`.线程优先级是Java平台上最**不可移植**的特征 + +## `73`.避免使用线程组 + +--- + +# `十` 序列化 + +## `74`.谨慎地实现Serializable接口 + +`1`.实现Serializable接口的代价是, + +* 一旦一个类被发布,就大大降低了改变这个类实现的可能性 + +* 增加了出现BUG和安全漏洞的可能性,反序列化是语言之外的对象创建机制 + +* 随着类发行新的版本,相关的测试负担也增加了 + +`2`.如果没有声明`serialVersionUID`,系统将在运行时通过类名,接口,成员等自动产生 + +`3`.为了继承而设计的类,应该**尽少**实现Serializable接口,用户接口也应该尽少继承Serializable接口 + +`4`.如果类有一些约束条件,当类的实例域被初始化为默认值,就会违背这些约束条件,此时需要添加`readObjectNoData`方法,并在其中抛出`InvalidObjectException` + +`5`.对于为继承而设计的可序列化的类,应该提供一个**无参构造器**,以及一个**初始化方法**,readObject与有参构造器共用这个初始化方法 + +`6`.`AtomicReference`操作原子引用,这是一个很好的线程安全状态机 + +`7`.内部类不应该实现Serializable,内部类的默认序列化形式是定义不清楚的,然而静态成员类却可以实现Serializable + +## `75`.考虑使用自定义的序列化形式 + +`1`.如果没有先认真考虑默认的序列化形式是否合适,则不要贸然接受;理想的序列化形式应该只包含该对象所表示的**逻辑数据**,而逻辑数据与物理表示法应该是各自独立的 + +`2`.如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式 + +`3`.即使确认了默认的序列化形式是合适的,通常还必须提供一个`readObject`方法以保证约束关系和安全性 + +`4`.当一个对象的物理表示法与逻辑内容有实质性的区别时,使用默认的序列化形式会有以下4个缺点 + +* 这个类的导出API永远束缚在该类的内部表示法上 + +* 消耗过多的空间,因为会反序列化所有的域 + +* 消耗过多的时间,因为会遍历对象图的拓扑关系 + +* 引起栈溢出,因为会递归遍历对象图 + +`5`.`defaultWriteObject`方法被调用的时,每个**未**被标记为`transient`的域都会被序列化;`defaultReadObject`将会反序列化这些域,在决定将一个域做成非transient之前,一定要确信它的值将是逻辑状态的一部人 + +`6`.如果所有的域都是瞬时的,不调用defaultWriteObject和defaultReadObject也是允许的,但是不推荐这么做,可能会引起`StreamCorruptedException` + +`7`.如果在读取整个对象状态的任何其他方法上强制任何**同步**,则也必须在对象序列化上强制这种同步 + +`8`.不管选择哪种序列化形式,都要为自己编写的每个可序列化的类声明一个显式的序列版本`UID`,这样可以避免因为自动生成UID造成不兼容 + +`9`.如果想为一个类生成一个新的版本,使这个类与现有的类不兼容,那么只需修改UID即可,反序列化时将会发生`InvalidClassException` + +## `76`.保护性地编写readObject方法 + +`1`.《`Java Object Serialization Specification`》 + +`2`.当一个对象被反序列化的时候,如果哪个域将被客户端所引用,就必须要做**保护性拷贝** + +`3`.不要使用`writeUnshared`和`readUnshared`方法 + +writeUnshared:将“未共享”对象写入 ObjectOutputStream。此方法等同于 writeObject,不同点在于它总是将给定对象作为流中惟一的新对象进行写入 + +readUnshared:从流中读取一个不共享的对象,同readObject,但不会让后续的readObject和readUnshared调用引用这次调用反序列化得到的对象 + +`4`.readObject不可以调用可被覆盖的方法,一旦调用,这个方法将在子类的域被反序列化之前运行,如果这个方法依赖子类的域,将导致程序失败 + +`5`.对于任何约束条件,如果检查失败,则抛出`InvalidObjectException`,这写检查应该在所有的保护性拷贝之后 + +`6`.如果整个对象图在反序列化之后必须进行验证,则使用`ObjectInputValidation`接口 + +## `77`.对于实例的控制,枚举类型优先于readResolve + +`1`.在反序列化之后,新建对象上的readResolve方法将被调用,然后该方法返回的对象将取代新建的对象 + +`2`.如果依赖readObject进行实例控制,带有对象类型的所有实例域必须声明为transient的 + +`3`.用readResolve进行实例控制并不过时,如果必须编写可序列化的实例受控的类,它的实例在编译时还不知道,你就无法将类表示成枚举 + +`4`.如果把readResolve放在一个**final**类上,它就应该是**私有**的;如果readResolve方法不是私有的,并且子类没有覆盖它,对序列化过的子类进行反序列化,将会获得一个**超类**的实例,可能出现ClassCastException + +## `78`.考虑用序列化代理代替序列化实例 + +`1`.实际在流之间传输的是代理对象,反序列化之后,代理对象通过`readResolve`返回对象,此时通过构造器创建外围对象,构造器中自带参数检查;序列化时,外围类通过`writeReplace`返回序列化代理实例。 + +`2`.writeReplace方法将在对象被写入流之前执行,然后用方法返回的实例替换自身被写入流中,与readResolve相对 \ No newline at end of file diff --git a/_posts/2014-08-20-jvm.md b/_posts/2014-08-20-jvm.md new file mode 100644 index 0000000..1a9c189 --- /dev/null +++ b/_posts/2014-08-20-jvm.md @@ -0,0 +1,541 @@ +--- +layout: post +title: Java虚拟机 +tags: JVM Java +categories: Java +--- + +* TOC +{:toc} + +# 一、走近JAVA + +## 第一章、走近JAVA + +java技术体系 + +![java][java] + +### 1.1 jdk1.7的主要特性 + +G1收集器 + +JSR-292对非JAVA语言的调用支持 + +ARM指令集`?` + +Sparc指令集`?` + +**新语法**:原生二进制(0b开头),switch支持字符串,"<>"操作符,异常处理改进,简化变长参数方法调用,面向资源的try-catch-finally + +**多核并行**:java.util.concurrent.forkjoin + +openJdk子项目Sumatra:为java提供使用GPU、APU运算能力的工具 + +java并行框架:hadoop MapReduce + +Scala,Erlang,Clojure天生具备并行计算能力 + +### 1.2 虚拟机历史 + +classic/exact vm → hotspot vm(sun jdk 与 open jdk 共同的虚拟机) → oracle 收购 bea与sun 拥有JRockit VM 与 Hotspot VM + +jit编译器 → OSR 栈上替换(on-stack replace) + +jsr : Java Specification Requests java规范请求 + +### 1.3 Open Service Gateway Initiative(OSGI) + +一种java模块化标准《深入理解OSGI:Equinox原理,应用与最佳实践》 + +混合语言:运行于java平台上的其他语言:Clojure,JRuby/Rails,Groovy + + +# 二、自动内存管理机制 + +## 第二章、java内存区域与内存溢出异常 + +### 2.1 运行时数据区 + +![runtime][runtime] + +#### 2.1.1 程序计数器 + +程序计数器是一块比较小的内存空间,可以看成是当前线程所执行的字节码的行号指示器,字节码解释器通过改变计数器的值来选取指令。 + +java多线程通过轮流切换实现,每个线程都需要有一个独立的计数器,这类内存称为"线程私有"的内存。这是java虚拟机唯一没有规定任何OutOfMemoryError情况的区域。`线程私有`。 + +#### 2.1.2 java虚拟机栈 + +每个方法对应一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等,每个方法的执行对应着一个栈帧在虚拟机中的入栈与出栈。`线程私有`。 + +局部变量表存放了编译期可知的各种基本数据类型、对象引用(reference类型,不等同于对象本身)、returnAddress类型 + +long,double占两个局部变量空间,其余占一个,局部变量表所需空间在编译时确定,运行期不会改变。 + +#### 2.1.3本地方法栈 + +与java虚拟机栈作用类似,区别是虚拟机栈执行java方法,本地方法栈执行native方法。`线程私有`。 + +>**以上三个线程私有的内存区域,随线程而生,随线程而灭,无需考虑垃圾回收** + +#### 2.1.4 java堆 + +最大的空间,`所有线程共享`,用于存放对象实例(数组也是对象),GC管理的主要区域,GC基本采用分代收集算法(新生代,老年代,永久代(方法区))。 + +java堆只需要逻辑连续,不要求物理连续 + +#### 2.1.5 方法区 + +`所有线程共享`,存储类信息、常量、静态变量、字段描述,方法描述,即时编译器编译后的代码等。Hotspot中称为永久代。 + +#### 2.1.6 运行时常量池 + +是`方法区`的一部分,class文件中包含类的版本,字段,方法,接口,常量池等。运行时常量池具备动态性,class常量池反之。 + +#### 2.1.7 直接内存 + +使用nativie函数分配堆外内存,然后在堆中通过DirectoryByteBuffer对象作为这块内存的引用。 + +不会受到java堆大小的限制,会受到本机总内存以及处理器寻址空间的限制。 + +### 2.2 HotSpot虚拟机对象探秘 + +#### 2.2.1 对象的创建 + +(1)检查能否在常量池定位到一个类的符号引用, + +(2)检查这个类是否已被加载、解析和初始化过,如果没有,执行相应加载过程 + +(3)从java堆中分配确定大小的内存,有两种分配方式:指针碰撞与空闲列表,取决于垃圾收集器是否带有压缩整理功能 + +(4)分配空间的线程安全:同步或者本地线程分配缓冲,是否使用TLAB:-XX:+/-UseTLAB + +(5)分配完成后,空间初始化为零值,设置对象头信息 + +(6)执行(构造方法),字段赋值 + +#### 2.2.2 对象的内存布局:对象头,实例数据,对齐填充 + + (1)**对象头**包括两部分信息: + + 第一部分:存储对象自身的运行时数据,如 hashcode,GC分代年龄,锁状态标识,线程持有的锁,偏向线程ID,偏向时间戳等 + + 第二部分:类型指针,即对象指向它的类元数据的指针,虚拟机以此确定对象是哪个类的实例,数组长度(if) + + (2)**实例数据**:类中字段的内容,默认分配策略总是将相同宽度的字段分配到一起,所以子类较窄的变量可能插入父类变量的空隙中 + + (3)**对齐填充**:不是必然存在的,仅仅起到占位符的作用,因为对象大小以及对象头的大小必须是8字节的整数倍 + +#### 2.2.3 对象的访问定位 + +**句柄** (reference指向句柄池,句柄池指向实例数据和类型数据),稳定 + +![handle][handle] + +**直接指针** (reference指向实例数据,实例数据中存放类型数据指针),速度快,少一次指针定位 + +![point][point] + +HotSpot使用直接指针 + +确式内存管理:虚拟机可以知道内存中某个位置的具体数据是什么类型。这样做可以摒弃句柄池而使用直接指针。HotSpot使用OopMap数据结构实现。 + +### 2.3 OutOfMemoryError异常 + +程序计数器不会发生,其他内存区域都有可能发生 + +>-Xms10M -Xmx10M 设置java堆的大小 +> +>-Xmn2g 设置新生代大小,java堆总大小=新生代+老年代 +> +>-Xss 设置栈容量 +> +>-verbose:gc 显示gc信息 +> +>-XX:+HeapDumpOnOutOfMemoryError 出现内存溢出异常时Dump出当前的内存堆转储快照 +> +>-XX:PermSize -XX:MaxPermSize 限制方法区大小 +> +>-XX:MaxDirectMemorySize 指定直接内存容量,默认为java堆最大值 + +#### 2.3.1 java堆溢出 + +java.lang.OutOfMemoryError: Java heap space 堆过小,或大量应该被清理的对象依旧被保持(内存泄露) + +#### 2.3.2 虚拟机栈和本地方法栈溢出 + +如果栈深度大于允许最大深度—StackOverFlowError 定义大量局部变量(如递归),栈最少104k(jdk1.7.0_51) + +如果扩展栈时无法申请到足够的空间—OutOfMemoryError:unable to create new native thread + +因为每个线程都有独立的栈,所以在多线程环境下,每个线程的栈越大越容易发生内存溢出。 + +若不允许更换64位虚拟机(32位Win每个进程最多2GB内存),或者减少线程数量,那么减小最大堆或者减小最大栈反而能换取更多线程。 + +#### 2.3.3 方法区和运行时常量池溢出 + +java.lang.OutOfMemoryError: PermGen space 方法区过小、过多的动态代理或使用大量第三方jar文件,造成class文件过多,方法过多。 + +String.intern()是一个native方法,如果字符串常量池中已经包含一个equal此String对象的字符串,则返回常量池中的这个对象;否则,将此String对象包含的字符串添加到常量池中,并返回此常量池中对象的引用。(jdk1.6中,intern会将首次出现的String复制一个新的实例至常量池;而jdk1.7中会在常量池中记录首次出现的实例引用,不会复制) + +groovy等jvm平台的动态语言会持续创建类来实现语言的动态性,极易发生该类型的溢出;包含大量jsp的应用、基于OSGI的应用也容易发生该溢出。 + +#### 2.3.4 本机直接内存溢出 + +HeapDump中看不到明显的异常,NIO可能引发这种异常 + +## 第三章、垃圾收集器与内存分配策略 + +### 3.1 对象已死吗? + +#### 3.1.1 引用计数算法 + +给对象添加一个引用计数器,每产生一个对该对象的引用,计数器+1;引用失效时,计数器-1,计数器为0时,表示对象不可用. + +但是主流的java虚拟机没有使用引用计数算法,主要因为它很难解决对象之间相互循环引用的问题。 + +#### 3.1.2 可达性分析算法 + +通过一系列“GC Roots”作为起始点向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,证明此对象不可用. + +java,C# ,List都是通过可达性分析来判断对象是否存活的. + +在java语言中,可作为GC Roots的对象包括以下几种: + +虚拟机栈(栈帧中的本地变量表)中引用的对象, + +方法区中类静态属性引用的对象, + +方法区中常量引用的对象, + +本地方法栈中JNI(即native方法)引用的对象 + +#### 3.1.3 引用概念扩充,强度依次减弱: + +* **强引用**(Strong Refrence) + +Object obj = new Object(); 只要强引用还存在,GC永远不会回收. + +* **软引用**(Soft Reference) + +用来描述一些有用但不必需的对象,在系统将要发生内存溢出之前,GC会对软引用对象进行回收,若回收后仍没有足够内存,才会抛出异常。 + +JDK1.2之后提供了SoftReference 类来实现软引用 + +* **弱引用**(Weak Reference) + +也是用来描述非必需对象的,但是强度比软引用更弱,弱引用对象只能生存到下一次GC发生之前,当GC工作时,无论内存是否足够,都会回收弱引用对象 + +JDK1.2之后提供了WeakReference 类来实现弱引用 + +* **虚引用**(Phantom Reference) + +也称为幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个实例。 + +为对象设置一个虚引用的唯一目的就是能在这个对象被GC回收时收到一个系统通知。 + +JDK1.2之后提供了PhantomReference 类来实现虚引用 + +#### 3.1.4 生存还是死亡 + +可达性分析算法中不可达的对象,并非是“非死不可”的,对象死亡需要一个过程: + +(1)若不可达,筛选对象,若对象没有覆盖finalize()方法,或者finalize()已经执行过,将判定为没有必要执行(将跳过第2步);若判定为有必要执行,这个对象将放之在F-Queue队列中,并在稍后由一个虚拟机自动建立、低优先级的Finalizeer线程去执行。 + +(2)执行finalize()方法,只是触发,不会等待方法执行结束,目的是防止finalize执行时间过长或者发生死循环引起F-Queue中对象永久等待。 + +finalize方法是对象逃脱死亡的最后一次机会,若在finalize()方法中该对象被重新引用,该对象将被移出“即将回收集合”。 + +任何一个对象的finalize()方法只会被系统自动调用一次。该方法不确定性太高,建议不使用。 + +(3)回收对象 + +#### 3.1.5 回收方法区 + +(1)废弃常量和无用的类可以被回收 + +**废弃常量**: + +系统中不存在对这个常量的引用 + +**无用的类**: + +①该类的所有实例都被回收,也就是java堆中不存在该类的任何实例 + +②加载该类的ClassLoader已经被回收 + +③该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 + +(2)回收设置 + +-Xnoclassgc 不对类进行回收 + +-verbase:class -XX:+TraceClassLoading -XX:+TraceClassUnLoading 查看类的加载和卸载信息 + +其中-verbase:class -XX:+TraceClassLoading可以再Product版虚拟机使用,-XX:+TraceClassUnLoading需要FastDebug版支持 + +频繁自定义ClassLoader的场景都需要虚拟机具备类卸载功能,以保证永久代不会溢出。 + +### 3.2 垃圾收集算法 + +#### 3.2.1 标记-清除算法 + +先标记处所有需要回收的对象,标记完成后统一回收,这是最基础的收集算法,后续的收集算法都是基于这个思路改进的。 + +不足:①标记和清除两个过程效率都不高 ②会产生大量不连续的内存碎片,可能导致在创建大对象时,因没有足够的连续内存而触发另一次垃圾收集动作。 + +#### 3.2.2 复制算法 + +将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块survivor;当回收时,将Eden和survivor中还存活的对象一次性复制到另一块survivor中,最后清理掉Eden和刚才用过的survivor空间。 + +HotSpot默认Eden和survivor的大小比例为8:1(-XX:ServivorRatio=8设置比例),也就是每次新生代中可用内存为整个新生代容量的90%,只有10%会被浪费。 + +当survivor不够用时,需要依赖其他内存(老年代)进行分配担保。当survivor不够用时,这些对象将直接通过分配担保机制进入老年代。 + +>为什么需要两块Survivor? + 为了确保每次复制后,都必定有一块Survivor是空闲的。如果只有1个Survivor,将Eden复制到Survivor中然后清理Eden之后,还要将Survivor复制回Eden。 + +#### 3.2.3 标记-整理算法 + +标记过程与标记-清除算法相同,然后让所有对象向一端移动,最后清理掉端边界以外的内存。老年代适合这种算法。 + +#### 3.2.4 分代收集算法 + +根据对象存活周期不同,将内存分为几块,一般是把java堆分为新生代和老年代,这样可以根据年代特点选用合适的收集算法。 + +**新生代**:对象存活率低,选用复制算法 + +**老年代**:对象存活率高,没有额外空间进行分配担保,选用标记-清除或者标记-整理算法 + +### 3.3 HotSpot算法实现 + +#### 3.3.1 枚举根节点: + +GC Roots 主要在全局性引用(常量或类静态属性)与执行上下文(栈帧中的本地变量表)中,若GC Roots过多(例如方法区数据过多),将消耗大量时间。 + +可达性分析时,整个系统的引用关系必须是不变的(一致性快照),因此必须停顿所有JAVA执行线程。 + +因为使用准确式GC,并不需要检查所有执行上下文和全局引用,虚拟机有办法知道哪些地方存放着对象引用。 + +缺陷--对执行时间敏感 + +#### 3.3.2 安全点(Safe Point): + +只在安全点生成OopMap并"stop the world",因为要节省空间。 + +只在能让程序长时间执行的指令流(如方法调用、循环跳转、异常跳转)中选定安全点,因为安全点过少则GC等待时间过长,安全点过多则会增大运行负荷。 + +**抢先式中断**:gc发生时,先中断全部线程,若有线程不在安全点上,则恢复线程让其跑到安全点上。(几乎没有虚拟机使用) + +**主动式中断**:gc发生时,不直接操作线程,在安全点和创建对象分配内存的位置设立一个标志,各个线程轮询这个标志,当中断标志位真时,自动中断挂起。 + +缺陷--在gc之前就已中断的线程无法进入安全点挂起,需通过安全区域来解决。 + +>线程中断有先后顺序,如何保证在错开的时间内引用关系不会发生改变? +> +>JAVA多线程是通过轮流切换实现的,一个线程执行时,其他线程都是阻塞的,不存在错开运行的情况;而且安全点设在长指令流中,不存在引用关系变化。 + +#### 3.3.3 安全区域(Safe Region): + +在一段代码片段中,引用关系不会发生变化,在这个区域中的任意地方开始GC都是安全的,这个区域称为安全区域,可以看做扩展了的安全点。 + +当线程执行到 Safe Region 时,首先标记自己进入SafeRegion,当这段时间内要发起GC时可以不用关心SafeRegion状态的线程。 + +当线程要离开 Safe Region 时,它要检查系统是否完成了根节点枚举(或者是整个GC过程),如果完成了则线程继续运行,否则必须等待。 + +### 3.4 垃圾收集器 + +![gc][gc] + +有连线表示可以搭配使用 + +#### 3.4.1 Serial收集器: + +复制算法 + +最基本、最古老、单线程,工作时会停顿其他所有工作线程,因为简单且高效,所以作为Client模式下的默认新生代收集器。 + +#### 3.4.2 ParNew收集器: + +复制算法 + +本质上是Serial收集器的并行(多个垃圾处理线程并行工作,但是用户线程仍然等待)多线程版本,指定CMS后的默认新生代收集器。 + +#### 3.4.3 Parallel Scavenge收集器: + +复制算法 + +并行多线程 + +关注于使CUP达到可控的吞吐量(运行用户代码的时间与JVM总运行时间的比值),高效利用CUP,适合于在后台运算不需要交互的任务。俗称“吞吐量优先收集器” + +`-XX:MaxGCPauseMillis` 控制最大垃圾收集停顿时间,大于0的毫秒数,这个值越小,新生代越小,吞吐量越小; + +`-XX:GCTimeRatio` 设置吞吐量大小,大于0小于100的整数n,最大允许垃圾收集时间比例为 1/(1+n),n默认为99,即最大立即收集时间为1% + +`-XX:UseAdaptiveSizePolicy` 开关参数,使用GC自适应调节策略 + +#### 3.4.4 Serial Old收集器: + +标记-整理算法 + +Serial收集器老年代版本,单线程。 + +#### 3.4.5 Parallel Old收集器: + +标记-整理算法 + +Parallel Scavenge收集器的老年代版本,多线程, + +在出现该收集器之前Parallel Scavenge只能与Serial Old搭配使用,但是Serial Old会拖累Parallel Scavenge,导致这种组合没有CMS+ParNew给力 + +在注重**吞吐量**以及**CPU资源**的场合,应该优先考虑Parallel Scavenge + Parallel Old组合。 + +#### 3.4.6 CMS收集器: + +hotspot中的第一款并发收集器,可以与用户线程同时工作 + +标记-清除算法,分4个步骤 + +①初始标记 标记GC能关联的对象 + +②并发标记 + +③重新标记 修正并发标记期间因程序继续运作而导致标记变动的一部分对象的标记记录 + +④并发清除 + +①③仍需停顿,但耗时短②④耗时长,但可以与用户线程一起工作 + +以最短回收停顿时间为目标的收集器。也称“并发低停顿收集器” + +>3个缺点: +> +>* 对CPU资源敏感,面向并发的程序都对CPU资源敏感,在并发阶段(②④)会占用一部分线程(默认(cpu数量+3)/4)导致应用程序变慢,吞吐量变小。 +> +>* 无法处理浮动垃圾,在④阶段,用户线程还活着,那么在标记之后可能产生新的垃圾,这部分垃圾只能留给下一次GC回收,称为“浮动垃圾”。 + +因为CMS收集同时用户线程也需要空间来运行,所以要预留足够空间给用户线程使用,所以老年代使用68%(1.6中为92%)空间后就会激活CMS。 + +-XX:CMSInitiatingOccupancyFraction 设置激活CMS的内存百分比, + +设高了可以减少GC次数提高性能,但是更容易出现因预留空间无法满足用户程序的情况而临时启用Serial Old反而降低性能的情况。 + +>* 基于标记-清除算法,会产生大量空间碎片。 +> +-XX:UseCMSCompactAtFullCollection 当CMS触发Full GC时对内存碎片进行合并整理,无法并发,停顿会变长。 +> +>-XX:CMSFullGCsBeforeCompaction 设置执行多少次不整理的Full GC后执行一次整理的Full GC,默认值为0,表示每次Full GC都会整理。 + +#### 3.4.7 G1收集器 + +标记-整理算法 + +面向服务端应用,特点: + +(1)并行与并发,利用多个CPU减少停顿时间,总体上可以与用户线程并发执行。 + +(2)分代收集,不需要与其他收集器配合,可以采用不同的方式处理垃圾。 + +(3)空间整合,虽然整体上采用标记-整理算法,但是局部(两个Region之间)上采用复制算法,不会产生内存空间碎片。 + +(4)可预测的停顿,能够让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集的上的时间不得超过N毫秒。 + +使用G1时,java堆被分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离,而是一部分Region的集合。 + +能够预测停顿时间的原因:有计划地避免在整个java堆中进行全区域的垃圾收集,G1跟踪各个Region里垃圾的价值大小,在后台维护一个优先列表, + +每次根据允许的收集时间优先收集价值最大的Region。 + +每个Region对应一个Rememberd Set,当程序对Reference类型进行写操作时,将会检测Reference引用的对象是否处于不同的Region中, + +如果是,就会将相关引用信息写入对象所属Region的Remeberd Set中,当内存回收时,GC根节点的枚举范围加入Remeberd Set就不必对全堆扫描。 + +G1运作步骤:初始标记, 并发标记, 最终标记(修正变动,需停顿,可并行), 筛选回收。 + +#### 3.4.8 GC日志 + +`-XX:+PrintGCDetails` 开启垃圾回收日志 + +>33.125:[GC[DefNew:3324K->152K(3712K),0.0025925 secs]3324K-152K(11904K),0.0031680 secs] + +`33.125`:虚拟机启动以来经过的秒数 + +"`[GC`"或者"`[Full GC`":停顿类型后者表示发生了Stop The World,如果是调用System.gc()方法触发的收集,将会显示为:"[Full GC (System)" + +`DefNew`:GC发生的区域,该名称与垃圾收集器相关,DefNew为Serial收集器新生代区域 + +`3324K->152K(3712K)`:GC前该**区域**已使用容量->GC后该区域已使用容量(该区域总容量) + +`0.0025925 secs`:该区域GC占用的时间 + +`3324K->152K(11904K)`:GC前java**堆**已使用的容量->GC后java堆已使用的容量(java堆总容量) + +#### 3.4.9 垃圾收集器参数 + +![arg0][arg0] + +![arg1][arg1] + +### 3.5 内存分配与回收策略 + +#### 3.5.1 对象优先在Eden分配 + +Minor GC:新生代GC,对象大多朝生夕灭,GC非常频繁,回收速度较快。 + +Full GC/Major GC:老年代GC,经常会伴随至少一次Minor GC,速度一般比Minor GC慢10倍以上。 + +#### 3.5.2 大对象直接进入老年代 + +典型:很长的字符串以及数组 + +`-XX:PretenureSizeThreshold=3145728`(不能写成3MB,只对Serial和ParNew有效),令大于这个设置值的对象直接在老年代分配,目的是避免在Eden以及两个Servivor区发生大量的内存复制。 + +创建大对象时容易触发GC,即时此时还有大量的空间, 应尽量避免创建短命的大对象。 + +#### 3.5.3 长期存活的对象进入老年代 + +jvm给每个对象定义了一个年龄计数器。 + +如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor中,并将年龄设置为1。 + +对象在Survivor中每"熬过"一次Minor GC,年龄就+1,当年龄增加到15(默认)时,将在下一次GC被晋升到老年代中。 + +`-XX:MaxTenuringThreshold=15` 设置对象晋升老年代的阈值。 + +`-XX:+PrintTenuringDistribution` 显示更详细的GC信息 + +#### 3.5.4 动态对象年龄判定 + +如果在Survivor中相同年龄对象大小的总和大于Survivor大小的一半,年龄大于或等于该大小的对象就可以直接进入老年代,无需增长MaxTenuringThreshold。 + +#### 3.5.5 空间担保分配 + +发生Minor GC前,JVM会检查老年代最大可用连续内存是否大于新生代所有对象总大小, + +如果这个条件成立,Minor GC可以确保是安全的。 + +如果不成立,虚拟机会查看`HandlePromotionFailure`设置是否允许担保失败。 + +如果不允许,将进行一次Full GC。 + +如果允许,那么会继续检查老年代最大可以连续空间是否大于历次晋升到老年代对象的平均大小. + +如果小于,将进行一次Full GC。 + +如果大于,将尝试进行一次Minor GC,尽管这次GC是有风险的。 + +如果尝试失败,将进行一次Full GC。 + +所谓风险:Minor GC时,Survivor中无法容纳的对象将担保分配至老年代,如果本次GC时新生代对象超过平均大小,因为具体超过多少是未知的,老年代将可能无法容纳这些对象。 + + + +[java]: {{"/java-system.png" | prepend: site.imgrepo }} +[runtime]: {{"/jvm-runtime.png" | prepend: site.imgrepo }} +[handle]: {{"/jvm-handle.png" | prepend: site.imgrepo }} +[point]: {{"/jvm-point.png" | prepend: site.imgrepo }} +[gc]: {{"/jvm-gc.png" | prepend: site.imgrepo }} +[arg0]: {{"/jvm-arg0.png" | prepend: site.imgrepo }} +[arg1]: {{"/jvm-arg1.png" | prepend: site.imgrepo }} \ No newline at end of file diff --git a/_posts/2014-09-19-sort.md b/_posts/2014-09-19-sort.md new file mode 100644 index 0000000..91a9693 --- /dev/null +++ b/_posts/2014-09-19-sort.md @@ -0,0 +1,433 @@ +--- +layout: post +title: 八种排序 +tags: 算法 algorithm 排序 +categories: algorithm +--- + + +* TOC +{:toc} + +![八大排序][八大排序] + +
+ +## 1.直接插入排序 + +基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。 + +![插入排序][插入排序] + +~~~java +/** + * 形象描述:踢馆 + * 直接插入排序(从小到大) + * @param src 待排序的数组 + */ +public void straight_insertion_sort(int[] src){ + int tmp; + for (int i = 0; i < src.length; i++) { + tmp = src[i]; + int j = i - 1; + //向前匹配,凡是比自己大的都往后移一位,碰到不比自己大的直接跳出 + for (; j >= 0 && tmp + +## 2.希尔排序(缩小增量排序) + +基本思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中相同位置的元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,再对每组中相同位置的元素进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。 + +![希尔排序][希尔排序] + +~~~java +/** + * 希尔排序(缩小增量排序)(从小到大) + * @param src + */ +public void shell_sort(int[] src){ + int inc = src.length; + int tmp; + while(true){ + inc = (int)Math.ceil(inc/2);//增量,亦即每组长度 + //遍历每组进行排序的元素索引,具体看内部注释 + for (int x = 0; x < inc; x++) { + //以下内容即插入排序,但是搜索头步进inc,回顾时步退inc + //这是一个模拟,可以想象为:将src分组,每组长度为inc,然后对每组的第0,1,2,3...个元素分别进行插入排序 + //即:第二组的第一个元素与第一组的第一个元素进行插入排序,第三组的第一个元素与第一二组的第一个元素进行插入排序... + // 第二组的第二个元素与第一组的第二个元素进行插入排序,第三组的第二个元素与第一二组的第二个元素进行插入排序... + // ... + for (int i = x + inc; i < src.length; i += inc) { + tmp = src[i]; + int j = i - inc; + for (; j >= 0 && tmp < src[j]; j-=inc) { + src[j + inc] = src[j]; + } + src[j + inc] = tmp; + } + } + //不必担心增量为1时会完全等同于普通的直接插入排序,进而造成性能问题 + //原因:在排序过程中匹配时遵循"小者胜,大者让"的原则,因此在搜索头进行回顾匹配时,会很快(1~2个元素左右)碰上不久前刚刚打败过自己的对手. + if(inc == 1){ + break; + } + } +} +~~~ + +
+ +## 3.简单选择排序 + +基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。 + +![简单选择排序][简单选择排序] + +~~~java +/** + * 简单选择排序(从小到大) + * @param src + */ +public void select_sort(int[] src){ + int pos,tmp,j; //定义最小值索引,最小值缓存,最小值检索起点索引 + //在每个i位置放入合适的值:i==0时放最小值;i==1时放第二小的值... + for (int i = 0; i < src.length; i++) { + //假定i位置的值就是合适的值 + tmp = src[i]; + pos = i; + //检索i之后的所有元素(前面的元素都比i位置的小),找到一个最小的,与i位置的元素换位 + for (j = i+1; j < src.length; j++) { + if(src[j] < tmp){ + tmp = src[j]; + pos = j; + } + } + src[pos] = src[i]; + src[i] = tmp; + } +} +~~~ + +
+ +## 4.堆排序 + +基本思想:构建一个大顶堆完全二叉树,此时整个二叉树是无序的,二叉树中的无序部分称为堆,堆顶为最大值。将堆中的末尾节点与堆顶节点互换,将刚换到末尾的节点踢出堆(刚被换到末尾的节点与之前被换到末尾的节点之间将是有序的),调整剩余节点,使其满足大顶堆的要求。以此类堆,最后堆将消失,得到一组正序排列的节点。 + +建堆: + +![堆排序1][堆排序1] + +交换,从堆中踢出最大数 + +![堆排序2][堆排序2] + +剩余结点再建堆,再交换踢出最大数 + +![堆排序3][堆排序3] + +~~~java +/** + * 调整堆 + * @param src 存放节点的数组,节点号从1开始 + * @param i 调整起点节点号 + * @param size 有效节点长度 + */ +private void adjHeap(int[] src,int i,int size){ + int lc = 2*i;//左子节点号 + int rc = 2*i + 1;//右子节点号 + int mx = i;//最大值节点号 + //非叶节点最大序号为size/2 + if(i<=size/2){ + //如果左子节点存在,且大于最大节点 + if(lc<=size && src[lc-1]>src[mx-1]){ + mx = lc; + } + //如果右子节点存在,且大于最大节点 + if(rc<=size && src[rc-1]>src[mx-1]){ + mx = rc; + } + //表示节点关系需要变化 + if(mx!=i){ + swap(src,mx,i); + adjHeap(src,mx,size); + } + } +} + +/** + * 交换节点 + * @param src 存放节点的数组,节点号从1开始 + * @param p 前一个节点号 + * @param n 后一个节点号 + */ +private void swap(int[] src,int p, int n){ + int tmp = src[p-1]; + src[p-1] = src[n-1]; + src[n-1] = tmp; +} + +/** + * 创建初始堆 + * @param src 存放节点的数组,节点号从1开始 + * @param size 有效节点长度 + */ +private void buildHeap(int[] src, int size){ + for(int i=size/2; i>=1; i--){ + adjHeap(src,i,size); + } +} + +/** + * 最大堆排序(从小到大) + * @param src + */ +public void heap_sort(int[] src){ + int size = src.length; + buildHeap(src,size); + for(int i=size; i>=1; i--){ + swap(src,1,i); + adjHeap(src,1,i-1); + } +} +~~~ + +
+ +## 5.冒泡排序 + +基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,由始至终对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。 + +![冒泡排序][冒泡排序] + +~~~java +/** + * 冒泡排序(从小到大) + * @param src + */ +public void bubble_sort(int[] src){ + int tmp; + //移动测头 + for (int i = 0; i < src.length; i++) { + //从测头向前推进,若相邻元素前者大于后者,则两者换位 + for (int j = i; j > 0; j--) { + if(src[j] < src[j-1]){ + tmp = src[j]; + src[j] = src[j-1]; + src[j-1] = tmp; + } + } + } +} +~~~ + +
+ +## 6.快速排序 + +基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。 + +![快速排序][快速排序] + +~~~java +/** + * 快速排序(从小到大) + * @param src + */ +public void quick_sort(int[] src){ + doSort(src,0,src.length-1); +} + +/** + * @param src 数组对象 + * @param bi 有效位开头 + * @param ei 有效位结尾 + * @return + */ +private int getMiddle(int[] src, int bi, int ei){ + //缓存开头的元素作为中轴 + int tmp = src[bi]; + while(bi < ei){ + //从右向左开始对比,不比中轴小的元素跳过,并继续向左推进 + while(bi < ei && src[ei] >= tmp){ + ei--; + } + //能够执行到这里,表示src[ei]= ei) return; + int mid = getMiddle(src,bi,ei); + doSort(src,bi,mid-1); + doSort(src,mid+1,ei); +} +~~~ + +
+ +## 7.归并排序 + +基本思想:归并排序法是将两个以上有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列 + +![归并排序][归并排序] + +~~~java +/** + * @param src 数组对象 + * @param bi 有效位开头 + * @param ei 有效位结尾 + */ +private void doSort(int[] src, int bi, int ei){ + if(bi >= ei) return; + int mid = (bi + ei)/2; + doSort(src, bi, mid); + doSort(src, mid + 1, ei); + merge(src, bi, mid, ei); +} + +/** + * @param src 数组对象 + * @param bi 有效位开头 + * @param mid 有效位中点 + * @param ei 有效位结尾 + */ +private void merge(int[] src, int bi, int mid, int ei){ + int[] tmp = new int[ei-bi+1]; + int ti = 0; //tmp数组的索引 + int fbi = bi; //前半段的起点 + int bbi = mid + 1;//后半段的起点 + while (fbi<=mid&&bbi<=ei) { + if(src[fbi] <= src[bbi]){ + //更小的元素将先被放入tmp,然后再对比下一个 + //因为是递归方法,所以前半段是有序的,后半段也是有序的, + //所以前半段的第一个元素比后半段的第一个元素小,那么前半段的第一个元素一定是最小的 + //比较大小往tmp中插入的过程其实类似选择排序+希尔排序 + tmp[ti++] = src[fbi++]; + }else{ + tmp[ti++] = src[bbi++]; + } + } + //有序元素放入tmp后,前半段的游标可能没有到头(因为后半段的可能先到头了) + while (fbi<=mid){ + tmp[ti++] = src[fbi++]; + } + //有序元素放入tmp后,后半段的游标可能没有到头(因为前半段的可能先到头了) + while (bbi<=ei){ + tmp[ti++] = src[bbi++]; + } + //用tmp中排好序的元素替换掉src中指定范围的元素 + for (ti = 0; ti < tmp.length; ti++) { + src[bi+ti] = tmp[ti]; + } +} + +public void merge_sort(int[] src){ + doSort(src,0,src.length-1); +} +~~~ + +
+ +## 8.基数排序 + +基本思想:创建0-9十个容器,对数组中每个数取其相同位的数字,将数组元素放入到对应的容器中,然后按容器顺序从容器中取出放回数组,以此类推当位数递增到最大数的位宽时,排序完成 + +![基数排序][基数排序] + +~~~java +public void radix_sort(int[] src){ + //取最大值 + int max = src[0]; + for (int i = 0; i < src.length; i++) { + max = src[i] > max ? src[i] : max; + } + //取最大值的位数 + int w = 0; + while (max>0){ + max/=10; + w++; + } + //创建十个队列,用二维数组模拟 + int[][] bkt = new int[10][src.length]; + + //记录每个bkt队列中有几个src元素 + int[] cm = new int[10]; + + for (int i = 0; i < w; i++) { + //遍历元素,按元素的第i位数值分类排放 + for (int j = 0; j < src.length; j++) { + int item = src[j]; + //求src中第j(从0开始)个元素的第i(从0开始)位数字 + //例如987的第1位数字是(987%100)/10 + int l = (item % (int)Math.pow(10,i+1))/(int)Math.pow(10,i); + //bkt第l个队列可能有多个数据,用现有的数量表示位置 + bkt[l][cm[l]] = item; + //位置后移1位 + cm[l] += 1; + } + //重新取出放回src + //记录已经取出的个数 + int count = 0; + for (int j = 0; j < 10; j++) { + //第i位为j的元素的个数 + int cml = cm[j]; + while (cm[j] > 0){ + //j从小到大递增,这样取数据将使src具有递增趋势 + //将这样的元素存入bkt,将使bkt中每个队列都具有递增趋势 + //为了不破换这样的趋势,将需要从每个bkt队列的开头开始取数据 + src[count] = bkt[j][cml-cm[j]]; + //减去一个元素 + cm[j] -= 1; + count++; + } + } + } +} +~~~ + +[八大排序]:http://my.csdn.net/uploads/201205/12/1336792573_1570.png + +[插入排序]:http://my.csdn.net/uploads/201205/12/1336792682_4218.png + +[希尔排序]:http://my.csdn.net/uploads/201205/12/1336792712_3691.png + +[简单选择排序]:http://my.csdn.net/uploads/201205/12/1336793705_2932.png + +[堆排序1]:http://my.csdn.net/uploads/201205/12/1336793950_6973.png + +[堆排序2]:http://my.csdn.net/uploads/201205/12/1336793977_2095.png + +[堆排序3]:http://my.csdn.net/uploads/201205/12/1336793996_7183.png + +[冒泡排序]:http://my.csdn.net/uploads/201205/12/1336807392_9769.png + +[快速排序]:http://my.csdn.net/uploads/201205/12/1336807447_1275.png + +[归并排序]:http://my.csdn.net/uploads/201205/12/1336807959_9188.png + +[基数排序]:http://my.csdn.net/uploads/201205/12/1336807988_2428.png diff --git a/_posts/2014-10-09-bitmap.md b/_posts/2014-10-09-bitmap.md new file mode 100644 index 0000000..33662c5 --- /dev/null +++ b/_posts/2014-10-09-bitmap.md @@ -0,0 +1,64 @@ +--- +layout: post +title: Bitmap +tags: 算法 algorithm Bitmap +categories: algorithm +--- + +# bitmap + +所谓bitmap就是用一个bit位来标记某个元素对应的value,而key即是这个元素。由于采用bit为单位来存储数据,因此在可以大大的节省存储空间 + +## 算法思想 + +32位机器上,一个整形,比如 `int a;` 在内存中占32bit,可以用对应的32个bit位来表示十进制的0-31个数,bitmap算法利用这种思想处理大量数据的排序与查询 + +优点: + +* 效率高,不许进行比较和移位 + +* 占用内存少,比如有N=10000000个正整数,用bit存储占用内存为N/8 = 1250000Bytes = 1.2M,如果采用int数组存储,则需要38M以上 + +缺点: + +无法对存在重复的数据进行排序和查找,因为key唯一,value只有0/1 + +示例: + +申请一个int型的内存空间,则有4Byte,32bit。输入 4, 2, 1, 3时: + +![bitmap][bitmap] + +思想比较简单,关键是十进制和二进制bit位需要一个map映射表,把10进制映射到bit位上 + +## map映射表 + +假设需要排序或者查找的总数N=10000000,那么我们需要申请的内存空间为 int a[N/32 + 1]。其中a[0]在内存中占32位,依此类推: + +bitmap表为: + +a[0] ------> 0 - 31 + +a[1] ------> 32 - 63 + +a[2] ------> 64 - 95 + +a[3] ------> 96 - 127 + +...... + +## 位移转换 + +(1) 求十进制数0-N对应的在数组a中的下标 + +`index_loc = N / 32`即可,index_loc即为n对应的数组下标。例如n = 76, 则loc = 76 / 32 = 2,因此76在a[2]中。 + +(2)求十进制数0-N对应的bit位 + +`bit_loc = N % 32`即可,例如 n = 76, bit_loc = 76 % 32 = 12 + +(3)利用移位0-31使得对应的32bit位为1 + +`int[index_loc] << bit_loc` + +[bitmap]: {{"/bitmap.jpg" | prepend: site.imgrepo }} \ No newline at end of file diff --git a/_posts/2014-10-17-shell-trash.md b/_posts/2014-10-17-shell-trash.md new file mode 100644 index 0000000..f807cf8 --- /dev/null +++ b/_posts/2014-10-17-shell-trash.md @@ -0,0 +1,42 @@ +--- +layout: post +title: 告别rm,自制回收站 +tags: trash shell Linux script +categories: linux +--- + +常在河边走,哪有不湿鞋?命令敲多了,总会手欠的,笔者刚在不久前尝到了`rm`命令带来的苦果,于是一怒之下决定自制一个回收脚本,从此告别rm命令。 + +因为rm是落子无悔的,所以网上有一种做法是自制一个shell脚本,封装一些mv操作,并用这个新脚本替换原来的rm命令。本人不太赞成这种做法,一是因为rm是系统默认的命令,已经是一种事实上的标准,若胡乱改动其功能,难免会对依赖这个命令的程序造成影响;二是因为,改变了rm的删除行为后,系统就失去了彻底删除一个文件的能力,为了弥补又必须考虑从回收站中彻底删除文件的情形,反而将简单问题复杂化了。 + +我的做法非常简单,回收脚本直接作为一个新命令,使用的时候想删除就删除,想回收就回收,连从回收站恢复的功能都不需要,想恢复文件,自己用mv从回收站移出来就行了。 + +~~~ +# !/bin/bash + +readonly trash_home=/tmp/trash + +for target in "$@" +do + dest=${trash_home}`realpath $target | xargs dirname` + + if [ ! -e ${dest} ]; then + mkdir -p ${dest}; + fi + + if [ -d $target ]; then + mv -i $target ${dest}; + elif [ -f $target ]; then + mv -i $target ${dest}; + fi +done +~~~ + +在`/etc/profile.d`目录新建文件`trash`,将以上代码粘贴进去,然后赋予其可执行权限。 + +命令运行效果应该如此: + +![trash][trash] + +[trash]: {{"/shell-trash.png" | prepend: site.imgrepo }} + diff --git a/_posts/2014-11-18-java-concurrent.md b/_posts/2014-11-18-java-concurrent.md new file mode 100644 index 0000000..4f8a90f --- /dev/null +++ b/_posts/2014-11-18-java-concurrent.md @@ -0,0 +1,536 @@ +--- +layout: post +title: Java Concurrent +tags: Java Concurrent +categories: Java +--- + +* TOC +{:toc} + + +线程拥有通过程序运行的独立的并发路径,并且每个线程都有自己的程序计数器,称为堆栈和本地变量。线程存在于进程中,它们与同一进程内的其他线程共享内存、文件句柄以及进程状态。 + +--- + +JDK 5.0 中的并发改进可以分为三组: + +* JVM 级别更改。大多数现代处理器对并发对某一硬件级别提供支持,通常以 **compare-and-swap** (CAS)指令形式。CAS 是一种低级别的、细粒度的技术,它允许多个线程更新一个内存位置,同时能够检测其他线程的冲突并进行恢复。它是许多高性能并发算法的基础。在 JDK 5.0 之前,Java 语言中用于协调线程之间的访问的惟一原语是同步,同步是更重量级和粗粒度的。公开 CAS 可以开发高度可伸缩的并发 Java 类。这些更改主要由 JDK 库类使用,而不是由开发人员使用。 + +* 低级实用程序类 -- `锁定`和`原子类`。使用 CAS 作为并发原语,**ReentrantLock** 类提供与 synchronized 原语相同的锁定和内存语义,然而这样可以更好地控制锁定(如计时的锁定等待、锁定轮询和可中断的锁定等待)和提供更好的可伸缩性(竞争时的高性能)。大多数开发人员将不再直接使用 ReentrantLock 类,而是使用在 ReentrantLock 类上构建的高级类。 + +* 高级实用程序类。这些类实现并发构建块,每个计算机科学文本中都会讲述这些类 -- `信号`、`互斥`、`闩锁`、`屏障`、`交换程序`、`线程池`和`线程安全集合类`等。大部分开发人员都可以在应用程序中用这些类来替换许多同步、wait() 和 notify() 的使用,从而提高性能、可读性和正确性。 + +--- + +# 线程安全的类 + +* 首先它必须在单线程环境中正确运行。如果正确实现了类,那么说明它符合规范,对该类的对象的任何顺序的操作(公共字段的读写、公共方法的调用)都不应该: + + * 使对象处于无效状态; + + * 观察处于无效状态的对象; + + * 违反类的任何变量、前置条件或后置条件。 + +* 而且,要成为线程安全的类,在从多个线程访问时,它必须继续正确运行,而不管运行时环境执行那些线程的调度和交叉,且无需对部分调用代码执行任何其他同步。 + +* 如果没有线程之间的某种明确协调,比如锁定,运行时可以随意在需要时在多线程中交叉操作执行。 + +* 在并发编程中,一种被普遍认可的原则就是:尽可能的使用**不可变对象**来创建简单、可靠的代码。 + +# Thread + +* 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。 + +* 无论是执行对象的`wait`、`notify`还是`notifyAll`方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。 + +* 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。 + +* JVM基于多线程,默认情况下不能保证运行时线程的时序性。 + + +## interrupt + +* 当调用`th.interrput()`的时候,线程th的中断状态(interrupted status) 会被置位。我们可以通过Thread.currentThread().isInterrupted() 来检查这个布尔型的中断状态。 + +* **没有任何语言方面的需求要求一个被中断的程序应该终止**。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。这说明interrupt中断的是线程的某一部分业务逻辑,前提是线程需要检查自己的中断状态(isInterrupted())。 + +* 当th被阻塞的时候,比如被`Object.wait`, `Thread.join`和`Thread.sleep`三种方法之一阻塞时, 调用它的interrput()方法,可想而知,没有占用CPU运行的线程是不可能给自己的中断状态置位的, 这就会产生一个InterruptedException异常。 + +## join + +* join方法可以让一个线程**等待**另一个线程执行完成。 + +* 若t是一个正在执行的Thread对象,t.join() 将会使当前线程暂停执行并等待t执行完成。重载的join()方法可以让开发者自定义等待周期。然而,和sleep()方法一样join()方法依赖于操作系统的时间处理机制,你不能假定join()方法将会精确的等待你所定义的时长。 + +* 如同sleep()方法,join()方法响应中断并在中断时抛出InterruptedException。 + +## 同步 + +* 同步的构造方法没有意义,因为当这个对象被创建的时候,只有创建对象的线程能访问它。 + +* 静态方法是和类(而不是对象)相关的,所以线程会请求类对象(Class Object)的内部锁。因此用来控制类的**静态域**访问的锁不同于控制**对象**访问的锁。 + +* 如果类中的两个域需要同步访问,但是两个域没有什么关联,那么可以为两个域个创建一个私有的锁对象,使两个域能分别同步。 + +## 锁 + +* 死锁描述了这样一种情景,两个或多个线程永久阻塞,互相等待对方释放资源。 + +* 饥饿是指当一个线程不能正常的访问共享资源并且不能正常执行的情况。这通常在共享资源被其他“贪心”的线程长期占用时发生 + +* 一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有可能发生活锁。同死锁一样,发生活锁的线程无法继续执行, 然而线程并没有阻塞, 他们在忙于响应对方无法恢复工作。 + +* wait方法,释放锁并挂起 + +* 如果在一个执行序列中,需要确定对象的状态,那么某个线程在执行这个序列时,需要获得所有这些对象的锁(如一来一回的相互鞠躬,必须保证不会同时鞠躬也不会同时静止) + + +# 线程安全集合 + +* java.util.concurrent 包添加了多个新的线程安全集合类 `ConcurrentHashMap`, `CopyOnWriteArrayList`, `CopyOnWriteArraySet` + +* JDK 5.0 还提供了两个新集合接口 -- `Queue` 和 `BlockingQueue`。Queue 接口与 List 类似,但它只允许从后面插入,从前面删除。BlockingQueue定义了一个先进先出的数据结构,当你尝试往满队列中添加元素,或者从空队列中获取元素时,将会阻塞或者超时。 + +* `ConcurrentMap`是java.util.Map的子接口,定义了一些有用的原子操作。移除或者替换键值对的操作只有当key存在时才能进行,而新增操作只有当key不存在时才能进行, 使这些操作原子化,可以避免同步。ConcurrentMap的标准实现是`ConcurrentHashMap`,它是`HashMap`的并发模式。 + +* `ConcurrentNavigableMap`是ConcurrentMap的子接口,支持近似匹配。ConcurrentNavigableMap的标准实现是`ConcurrentSkipListMap`,它是`TreeMap`的并发模式。 + +* 所有这些集合,通过 在集合里新增对象和访问或移除对象的操作之间,定义一个`happens-before`的关系,来帮助程序员避免内存一致性错误。 + +## CopyOnWrite + +* Vector 的常见应用是存储通过组件注册的监听器的列表。当发生适合的事件时,该组件将在监听器的列表中迭代,调用每个监听器。为了防止ConcurrentModificationException,迭代线程必须复制列表或锁定列表,以便进行整体迭代,而这两种情况都需要大量的性能成本。 + +* CopyOnWriteArrayList及CopyOnWriteArraySet 类通过每次添加或删除元素时创建数组的新副本,避免了这个问题,但是进行中的迭代保持对创建迭代器时的副本进行操作。虽然复制也会有一些成本,但是在许多情况下,迭代要比修改多得多,在这些情况下,写入时复制要比其他备用方法具有更好的性能和并发性。 + +## ConcurrentHashMap + +* `Hashtable` 和 `synchronizedMap` 所采取的获得同步的简单方法(同步 Hashtable 或者同步 Map 封装器对象中的每个方法)有两个主要的不足: + + * 第一,这种方法对于可伸缩性是一种障碍,因为一次只能有一个线程可以访问 hash 表; + + * 第二,这样仍不足以提供真正的线程安全性,许多公用的混合操作仍然需要额外的同步。虽然诸如 get() 和 put() 之类的简单操作可以在不需要额外同步的情况下安全地完成,但还是有一些公用的操作序列,例如迭代或者 put-if-absent(空则放入),需要外部的同步,以避免数据争用。 + +* 在大多数情况下,`ConcurrentHashMap` 是 Hashtable或 Collections.synchronizedMap(new HashMap()) 的简单替换。然而,其中有一个显著不同,即 ConcurrentHashMap 实例中的同步**不锁定**Map进行独占使用, 实际上,没有办法锁定 ConcurrentHashMap 进行独占使用,它被设计用于进行并发访问。为了使集合不被锁定进行独占使用,还提供了公用的混合操作的其他(原子)方法,如 **put-if-absent**。 + +* ConcurrentHashMap 返回的迭代器是弱一致的,意味着它们将不抛出ConcurrentModificationException ,将进行 "合理操作" 来反映迭代过程中其他线程对 Map 的修改。 + +## 队列 + +* `Queue` 接口比 List 简单得多,仅包含 put() 和 take() 方法,并允许比 LinkedList 更有效的实现。 + +* Queue 接口还允许实现来确定存储元素的顺序。`ConcurrentLinkedQueue` 类实现先进先出(first-in-first-out,FIFO)队列。`PriorityQueue` 类实现优先级队列(也称为堆),它对于构建调度器非常有用,调度器必须按优先级或预期的执行时间执行任务。 + +* 实现 Queue 的类是: + + * `LinkedList` 已经进行了改进来实现 Queue。 + + * `PriorityQueue` 非线程安全的优先级队列(堆)实现,根据自然顺序或比较器返回元素。 + + * `ConcurrentLinkedQueue` 快速、线程安全的、无阻塞 FIFO 队列。 + +## 弱一致的迭代器 + +* java.util 包中的集合类都返回 `fail-fast` 迭代器,这意味着它们假设线程在集合内容中进行迭代时,集合不会更改它的内容。如果 fail-fast 迭代器检测到在迭代过程中进行了更改操作,那么它会抛出 **ConcurrentModificationException**,这是不可控异常。 + +* java.util.concurrent 集合返回的迭代器称为弱一致的(`weakly consistent`)迭代器。对于这些类,如果元素自从迭代开始已经删除,且尚未由 next() 方法返回,那么它将不返回到调用者。如果元素自迭代开始已经添加,那么它可能返回调用者,也可能不返回。在一次迭代中,无论如何更改底层集合,元素不会被返回两次。 + + + +# 线程池 + +* 管理一大组小任务的标准机制是**组合工作队列**和**线程池**。工作队列就是要处理的任务的队列,前面描述的 Queue 类完全适合。线程池是线程的集合,每个线程都提取公用工作队列。当一个工作线程完成任务处理后,它会返回队列,查看是否有其他任务需要处理。如果有,它会转移到下一个任务,并开始处理。 + +* 线程池为线程生命周期间接成本问题和资源崩溃问题提供了解决方案。 + * 通过对多个任务重新使用线程,创建线程的间接成本将分布到多个任务中。 + * 作为一种额外好处,因为请求到达时,线程已经存在,从而可以消除由创建线程引起的延迟, 因此,可以立即处理请求,使应用程序更易响应。 + * 而且,通过正确调整线程池中的线程数,可以强制超出特定限制的任何请求等待,直到有线程可以处理它,它们等待时所消耗的资源要少于使用额外线程所消耗的资源,这样可以防止资源崩溃。 + +# Executor 框架 + +* `Executor` 接口关注任务提交,确定执行策略。这使在部署时调整执行策略(队列限制、池大小、优先级排列等等)更加容易,更改的代码最少。 + +* 大多数 Executor 实现类还实现 `ExecutorService`接口,ExecutorService是Executor的子接口,它还管理执行服务的生命周期。这使它们更易于管理,并向生命可能比单独 Executor 的生命更长的应用程序提供服务。 + +* **执行策略**定义何时在哪个线程中运行任务,执行任务可能消耗的资源级别(线程、内存等等),以及如果执行程序超载该怎么办。 + +## ExecutorService + +* `ExecutorService`接口在提供了execute方法的同时,新加了更加通用的`submit`方法。 + +* 通过submit方法返回的`Future`对象可以读取`Callable`任务的执行结果,或管理Callable任务和Runnable任务的状态。 + +* ExecutorService也提供了批量运行Callable任务的方法,ExecutorService还提供了一些关闭执行器的方法。 + +## ScheduledExecutorService + +* `ScheduledExecutorService`扩展ExecutorService接口并添加了`schedule`方法。调用schedule方法可以在指定的延时后执行一个Runnable或者Callable任务。 + +* ScheduledExecutorService接口还定义了按照指定时间间隔定期执行任务的`scheduleAtFixedRate`方法和`scheduleWithFixedDelay`方法。 + +## Executors + +`Executors类`包含用于构造许多不同类型的 Executor 实现的静态工厂方法: + +* `newCachedThreadPool()` 创建不限制大小的线程池,但是当以前创建的线程可以使用时将重新使用那些线程。如果没有现有线程可用,将创建新的线程并将其添加到池中。已有 60 秒钟未被使用的线程将其终止并从缓存中删除。 + +* `newFixedThreadPool(int n)` 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务。 + +* `newSingleThreadExecutor()` 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务, 此线程池保证所有任务的执行顺序按照任务的提交顺序执行。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。 + +* `newScheduledThreadPool(int n)` 创建一个大小无限的线程池, 此线程池支持定时以及周期性执行任务的需求。 + +* 如果上面的方法都不满足需要,可以尝试`ThreadPoolExecutor`或者`ScheduledThreadPoolExecutor`。 + +## 定制 ThreadPoolExecutor + +通过使用包含 `ThreadFactory` 变量的工厂方法或构造函数的版本,可以定义线程的创建。ThreadFactory 是工厂对象,其构造执行程序要使用的新线程。 + +```java +public class DaemonThreadFactory implements ThreadFactory { + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + } +} +``` + +有时,Executor 不能执行任务,因为它已经关闭或者因为 Executor 使用受限制队列存储等待任务,而该队列已满。在这种情况下,需要咨询执行程序的`RejectedExecutionHandler` 来确定如何处理任务: + +* 抛出异常(默认情况), + +* 放弃任务, + +* 在调用者的线程中执行任务, + +* 或放弃队列中最早的任务以为新任务腾出空间。 + +* `setRejectedExecutionHandler`可以设置拒绝的执行处理程序。 + + +## 需要特别考虑的问题 + +如果应用程序对特定执行程序进行了假设,那么应该在 Executor 定义和初始化的附近对这些进行说明,从而使善意的更改不会破坏应用程序的正确功能。 + +* 一些任务可能同时等待其他任务完成。在这种情况下,当线程池没有足够的线程时,如果所有当前执行的任务都在等待另一项任务,而该任务因为线程池已满不能执行,那么线程池可能会死锁。 + +* 一组线程必须作为共同操作组一起工作。在这种情况下,需要确保线程池能够容纳所有线程。 + +## 调整线程池 + +* 如果线程池太小,资源可能不能被充分利用,在一些任务还在工作队列中等待执行时,可能会有处理器处于闲置状态。 + +* 另一方面,如果线程池太大,则将有许多有效线程,因为大量有效线程使用内存,或者因为每项任务要比使用少量线程有更多上下文切换,性能可能会受损。 + +* Amdahl 法则提供很好的近似公式来确定线程池的大小: + +>用 `WT` 表示每项任务的平均等待时间,`ST` 表示每项任务的平均服务时间(计算时间)。则 `WT/ST` 是每项任务等待所用时间的百分比。对于 N 处理器系统,池中可以近似有 `N*(1+WT/ST)` 个线程。 + +## Future 接口 + +* `Future` 接口允许表示已经完成的任务、正在执行过程中的任务或者尚未开始执行的任务。通过 Future 接口,可以尝试取消尚未完成的任务,查询任务已经完成还是取消了,以及提取(或等待)任务的结果值。 + +* `FutureTask` 类实现了 Future,并包含一些构造函数,允许将 Runnable 或 Callable和 Future 接口封装。因为 FutureTask 也实现 Runnable,所以可以只将 FutureTask 提供给 Executor。一些提交方法(如 `ExecutorService.submit()`)除了提交任务之外,还将返回 Future 接口。 + +* `Future.get()` 方法检索任务计算的结果, 如果任务尚未完成,那么 Future.get() 将被阻塞直到任务完成;如果任务已经完成,那么它将立即返回结果; 如果任务完成,但有异常,则抛出 ExecutionException。 + +## 使用 Future 构建缓存 + +该示例利用 ConcurrentHashMap 中的原子 `putIfAbsent()` 方法,确保仅有一个线程试图计算给定关键字的值。如果其他线程随后请求同一关键字的值,它仅能等待(通过 Future.get() 的帮助)第一个线程完成。因此两个线程不会计算相同的值。 + +```java +public class Cache { + private ConcurrentMap> map = new ConcurrentHashMap<>(); + private Executor executor = Executors.newFixedThreadPool(8); + + static class Task implements Callable{ + @Override + public V call() throws Exception { + return null; + } + } + + public V get(final K key) throws ExecutionException, InterruptedException { + FutureTask ft = map.get(key); + if (ft == null) { + ft = new FutureTask(new Task()); + FutureTask old = map.putIfAbsent(key, ft); + if (old == null){ + executor.execute(ft); + }else{ + ft = old; + } + } + return ft.get(); + } +} +``` + +## CompletionService + +* `CompletionService` 将执行服务与类似 Queue 的接口组合,从任务执行中删除任务结果的处理。CompletionService 接口包含用来提交将要执行的任务的 submit() 方法和用来询问下一完成任务的 `take()`/`poll()` 方法。 + +* CompletionService 允许应用程序结构化,使用 `Producer`/`Consumer` 模式,其中生产者创建任务并提交,消费者请求完成任务的结果并处理这些结果。CompletionService 接口由 `ExecutorCompletionService` 类实现,该类使用 Executor 处理任务并从 CompletionService 导出 `submit`/`poll`/`take` 方法。 + +* 下列代码使用 Executor 和 CompletionService 来启动许多任务,并使用第一个生成的非空结果,然后取消其余任务: + +```java + V solve(Executor e, Collection> tasks) throws InterruptedException, ExecutionException { + CompletionService ecs = new ExecutorCompletionService<>(e); + List> futures = new ArrayList<>(); + V result = null; + + try { + tasks.forEach(p -> futures.add(ecs.submit(p))); + for (int i = 0; i < tasks.size(); ++i) { + V r = ecs.take().get(); + if (r != null) { + result = r; + break; + } + } + } finally { + for (Future f : futures){ + f.cancel(true); + } + } + + return result; +} +``` + +## Fork/Join + +* `fork/join`框架是ExecutorService接口的一种具体实现,目的是为了更好地利用多处理器带来的好处。它是为那些能够被递归地拆解成子任务的工作类型量身设计的。其目的在于能够使用所有可用的运算能力来提升你的应用的性能。 + +* 类似于ExecutorService接口的其他实现,fork/join框架会将任务分发给线程池中的工作线程。fork/join框架的独特之处在与它使用工作窃取(`work-stealing`)算法。完成自己的工作而处于空闲的工作线程能够从其他仍然处于忙碌(busy)状态的工作线程处窃取等待执行的任务。 + +* fork/join框架的核心是`ForkJoinPool`类,它是对`AbstractExecutorService`类的扩展。ForkJoinPool实现了工作偷取算法,并可以执行ForkJoinTask任务。 + +* 在Java SE 8中,java.util.`Arrays`类的一系列`parallelSort()`方法就使用了fork/join来实现。这些方法与sort()系列方法很类似,但是通过使用fork/join框架,借助了并发来完成相关工作, 在多处理器系统中,对大数组的并行排序会比串行排序更快。 + +* 其他采用了fork/join框架的方法还有java.util.`streams`包中的一些方法,此包是Java SE 8发行版中Project Lambda的一部分。 + +# 同步工具 + +## Semaphore + +* `Semaphore` 类实现标准 Dijkstra 计数信号。计数信号可以认为具有一定数量的许可权,该许可权可以获得或释放。如果有剩余的许可权,`acquire()` 方法将成功,否则该方法将被阻塞,直到其他线程释放`release()`许可权, 线程一次可以获得多个许可权。 + +* 计数信号可以用于限制有权对资源进行并发访问的线程数。该方法对于实现资源池或限制 Web 爬虫(Web crawler)中的输出 socket 连接非常有用。 + +* 注意信号不跟踪哪个线程拥有多少许可权, 这由应用程序来决定,以确保何时线程释放许可权,该信号表示其他线程拥有许可权或者正在释放许可权,以及其他线程知道它的许可权已释放。 + +## 互斥 + +* 计数信号的一种特殊情况是**互斥**,或者互斥信号。互斥就是具有单一许可权的计数信号,意味着在给定时间仅一个线程可以具有许可权, 互斥可以用于管理对共享资源的独占访问。 + +* 虽然互斥许多地方与锁定一样,但互斥还有一个锁定通常没有的功能,就是互斥可以由不具有许可权的其他线程来释放, 这在死锁恢复时会非常有用。 + +## CyclicBarrier + +* `CyclicBarrier` 类可以帮助同步,它允许一组线程等待整个线程组到达公共屏障点。CyclicBarrier 是使用整型变量构造的,其确定组中的线程数。当一个线程到达屏障时(通过调用 CyclicBarrier.`await()`),它会被阻塞,直到所有线程都到达屏障,然后在该点允许所有线程继续执行。 + +* CyclicBarrier可以重新使用, 一旦所有线程都已经在屏障处集合并释放,则可以将该屏障重新初始化到它的初始状态。 还可以指定在屏障处等待时的超时;如果在该时间内其余线程还没有到达屏障,则认为屏障被打破,所有正在等待的线程会收到 `BrokenBarrierException`。 + +* 下列代码将创建 CyclicBarrier 并启动一组线程,每个线程在到达屏障点前会打印出自己的名字,等待其他线程到齐后,将执行CyclicBarrier绑定的Runnable,该Runnable在每个屏障点只执行一次。 + +```java + public static void main(String[] args){ + Runnable ready = new Runnable() { + @Override + public void run() { + System.out.println("ready"); + } + }; + + CyclicBarrier cyclicBarrier = new CyclicBarrier(5, ready); + + ExecutorService executor = Executors.newCachedThreadPool(); + for (int i = 0; i < 5; i++) { + Runnable task = new Runnable() { + @Override + public void run() { + try { + System.out.println(Thread.currentThread().getName()); + cyclicBarrier.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (BrokenBarrierException e) { + e.printStackTrace(); + } + } + }; + executor.submit(task); + } + executor.shutdown(); + } +``` + +## CountdownLatch + +* `CountdownLatch` 类与 CyclicBarrier 相似,因为它的角色是对已经在它们中间分摊了问题的一组线程进行协调。它也是使用整型变量构造的,指明计数的初始值,但是与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。 + +* 其中,CyclicBarrier 是到达屏障的所有线程的大门,只有当所有线程都已经到达屏障或屏障被打破时,才允许这些线程通过。CountdownLatch 将到达和等待功能分离,任何线程都可以通过调用 `countDown()` 减少当前计数,这种方式不会阻塞线程,而只是减少计数。`await()` 方法的行为与 CyclicBarrier.await() 稍微有所不同,调用 await() 任何线程都会被阻塞,直到闩锁计数减少为零,在该点等待的所有线程才被释放,对 await() 的后续调用将立即返回。 + +* 当问题已经分解为许多部分,每个线程都被分配一部分计算时,CountdownLatch 非常有用。在工作线程结束时,它们将减少计数,协调线程可以在闩锁处等待当前这一批计算结束,然后继续移至下一批计算。 + +```java +public static void main(String[] args) throws InterruptedException { + int concurrency = 5; + ExecutorService executor = Executors.newCachedThreadPool(); + + final CountDownLatch ready = new CountDownLatch(concurrency); + final CountDownLatch start = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(concurrency); + for(int i=0; i exchanger = new Exchanger<>(); + ExecutorService executor = Executors.newCachedThreadPool(); + + Runnable producer = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 5; i++) { + try { + String data = "produce"; + System.out.println(Thread.currentThread().getName() + "-offer----" + data); + exchanger.exchange(data); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }; + + Runnable consumer = new Runnable() { + @Override + public void run() { + for (int i = 0; i < 5; i++) { + try { + String data = exchanger.exchange(null); + System.out.println(Thread.currentThread().getName() + "-get----" + data); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + } + }; + + executor.execute(producer); + executor.execute(consumer); + executor.shutdown(); +} +``` + +# Lock工具 + +* `Lock` 接口将内置监视器锁定的行为普遍化,允许多个锁定实现,同时提供一些内置锁定缺少的功能,如**计时**的等待、**可中断**的等待、锁定**轮询**、每个锁定有多个**条件**等待集合以及**无阻塞**结构的锁定。 + +* java.util.concurrent.`locks`包提供了复杂的锁, 锁对象作用非常类似同步代码使用的隐式锁, 每次只有一个线程可以获得锁对象。通过关联`Condition`对象,锁对象也支持`wait/notify`机制。 + +* 锁对象之于隐式锁最大的优势在于,它们**有能力收回获取锁的尝试**(如果获取锁失败,将不会继续请求,以免发生死锁)。如果当前锁对象不可用,或者锁请求超时(如果超时时间已指定),`tryLock`方法会收回获取锁的请求。如果在锁获取前,另一个线程发送了一个中断,`lockInterruptibly`方法也会收回获取锁的请求。 + + +## ReentrantLock + +* `ReentrantLock` 是具有与隐式监视器锁定(使用 synchronized 方法和语句访问)相同的基本行为和语义的 Lock 的实现,但它具有扩展的能力。 + +* 在竞争条件下,ReentrantLock 的实现要比现在的 synchronized 实现更具有可伸缩性。这意味着当许多线程都竞争相同锁定时,使用 ReentrantLock 的吞吐量通常要比 synchronized 好。 + +* 虽然 ReentrantLock 类有许多优点,但是与同步相比,它有一个主要**缺点** -- 它可能忘记释放锁定。因为锁定失误(忘记释放锁定)的风险,所以对于基本锁定,强烈建议继续使用 synchronized,除非真的需要 ReentrantLock 额外的灵活性和可伸缩性。 + +* 建议当获得和释放 ReentrantLock 时使用下列结构: + +```java +Lock lock = new ReentrantLock(); +... +lock.lock(); +try { + // perform operations protected by lock +} +catch(Exception ex) { + // restore invariants +} +finally { + lock.unlock(); +} +``` + + + +## Condition + +* 就像 Lock 接口是同步的具体化,`Condition` 接口是 Object 中 wait() 和 notify() 方法的具体化。Lock 中的一个方法是 `newCondition()`,它要求该锁定返回新的 Condition 对象限制。 + +* `await()`、`signal()` 和 `signalAll()` 方法类似于 wait()、notify() 和 notifyAll(),但增加了灵活性,每个 Lock 都可以创建多个条件变量。这简化了一些并发算法的实现。 + +## ReadWriteLock + +* ReentrantLock 实现的锁定规则非常简单 -- 每当一个线程具有锁定时,其他线程必须等待,直到该锁定可用。 + +* 有时,当对数据结构的读取通常多于修改时,可以使用更复杂的称为读写锁定的锁定结构,它允许有多个并发读者,同时还允许一个写入者独占锁定。该方法在一般情况下(只读)提供了更大的并发性,同时在必要时仍提供独占访问的安全性。 + +* `ReadWriteLock` 接口和 `ReentrantReadWriteLock` 类提供这种功能 -- **多读者**、**单写入**者锁定规则,可以用这种功能来保护共享的易变资源。 + +# 原子变量 + +* java.util.concurrent.`atomic`包定义了对单一变量进行原子操作的类。所有的类都提供了`get`和`set`方法,可以使用它们像读写`volatile`变量一样读写原子类。就是说,同一变量上的一个set操作对于任意后续的get操作存在happens-before关系。原子的`compareAndSet`方法也有内存一致性特点,就像应用到整型原子变量中的简单原子算法。 + +* 即使大多数用户将很少直接使用它们,原子变量类(`AtomicInteger`、`AtomicLong`、`AtomicReference` 等等)也有充分理由是最显著的新并发类。这些类公开对 JVM 的低级别改进,允许进行具有高度可伸缩性的**原子读-修改-写操作**。大多数现代 CPU 都有原子读-修改-写的原语,比如比较并交换(CAS)或加载链接/条件存储(LL/SC)。原子变量类使用硬件提供的最快的并发结构来实现。 + +* 几乎 java.util.concurrent 中的所有类都是在 `ReentrantLock` 之上构建的,ReentrantLock 则是在**原子变量类**的基础上构建的。所以,虽然仅少数并发专家使用原子变量类,但 java.util.concurrent 类的很多可伸缩性改进都是由它们提供的。 + +* 原子变量主要用于为原子地更新 "热" 字段提供有效的、细粒度的方式, "热" 字段是指由多个线程频繁访问和更新的字段。另外,原子变量还是计数器或生成序号的自然机制。 + +# 并发随机数 + +* 在JDK7中,java.util.concurrent包含了一个相当便利的类,`ThreadLocalRandom`,当应用程序期望在多个线程或ForkJoinTasks中使用随机数时。 + +* 对于并发访问,使用TheadLocalRandom代替Math.random()可以减少竞争,从而获得更好的性能。 + +* 只需调用ThreadLocalRandom.`current()`, 然后调用它的其中一个方法去获取一个随机数即可。 + +# 性能与可伸缩性 + +* **性能**是 "可以快速执行此任务的程度" 的评测。**可伸缩性**描述应用程序的**吞吐量如何表现为它的工作量和可用计算资源增加**。 + +* 可伸缩的程序可以按比例使用更多的处理器、内存或 I/O 带宽来处理更多个工作量。当我们在并发环境中谈论可伸缩性时,我们是在问当许多线程同时访问给定类时,这个类的执行情况。 + +* java.util.concurrent 中的低级别类 ReentrantLock 和原子变量类的可伸缩性要比内置监视器(同步)锁定高得多。因此,使用 ReentrantLock 或原子变量类来协调共享访问的类也可能更具有可伸缩性。 + diff --git a/_posts/2014-12-13-java-newfeature.md b/_posts/2014-12-13-java-newfeature.md new file mode 100644 index 0000000..ef0d1a4 --- /dev/null +++ b/_posts/2014-12-13-java-newfeature.md @@ -0,0 +1,561 @@ +--- +layout: post +title: Java8 新特性 +tags: Java 新特性 +categories: Java +--- + +* TOC +{:toc} + + +# `1` 语言新特性 + +## `1.1` Lambda + +自动推测形参类型`e` + +~~~ java +Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) ); +~~~ +指定形参类型`e` + +~~~ java +Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) ); +~~~ +方法体可以用`{}`包裹 + +~~~ java +Arrays.asList( "a", "b", "d" ).forEach( e -> { + System.out.print( e ); + System.out.print( e ); +} ); +~~~ + +**effectively final**,lambda引用的对象会自动转为final + +~~~java +String separator = ","; +Arrays.asList( "a", "b", "d" ).forEach( + ( String e ) -> System.out.print( e + separator ) ); +~~~ + +## `1.2` FunctionalInterface + +函数接口就是只具有一个方法的普通接口,这样的接口,可以被隐式转换为lambda表达式, + +然而,一旦在此接口中增加了方法,它将不再是函数接口,使用lambda时也将编译失败。 + +`@FunctionalInterface`注解可以约束接口的行为,**默认方法**与**静态方法**不会影响函数接口 + +~~~java +@FunctionalInterface +public interface Functional { + void method(); +} +~~~ + +## `1.3` 接口默认方法 + +~~~java +public interface Defaulable { + // Interfaces now allow default methods, the implementer may or + // may not implement (override) them. + default String notRequired() { + return "Default implementation"; + } +} + +public class DefaultableImpl implements Defaulable { +} + +public class OverridableImpl implements Defaulable { + @Override + public String notRequired() { + return "Overridden implementation"; + } +} +~~~ + +## `1.4` 接口静态方法 + +~~~java +private interface DefaulableFactory { + // Interfaces now allow static methods + static Defaulable create( Supplier< Defaulable > supplier ) { + return supplier.get(); + } +} +~~~ + +## `1.5` 方法引用 + +可以将类中既有 方法引用为lambda + +~~~java +public static class Car { + + public static Car create( final Supplier< Car > supplier ) { + return supplier.get(); + } + + public static void collide( final Car car ) { + System.out.println( "Collided " + car.toString() ); + } + + public void repair() { + System.out.println( "Repaired " + this.toString() ); + } + + public void follow( final Car another ) { + System.out.println( "Following the " + another.toString() ); + } + +} +~~~ + +①构造器引用,语法为 `Class::new`,效果如同 `() -> new Class()` + +~~~java +Car car = Car.create( Car::new ); +final List< Car > cars = Arrays.asList( car ); +~~~ + +②静态方法引用,语法为 `Class::static_method`,效果如同 `p -> Class.static_method(p)` + +~~~java +cars.forEach( Car::collide ); +~~~ + +③实例**无参**方法引用,语法为 `Class::method`,效果如同 `p -> p.method()`,该方法没有参数 + +~~~java +cars.forEach( Car::repair ); +~~~ + +④实例**有参**方法引用,语法为 `instance::method` ,效果如同 `p -> instance.method(p)` + +~~~java +final Car police = Car.create( Car::new ); +cars.forEach( police::follow ); +~~~ + +⑤其他 + +~~~java + super::methName //引用某个对象的父类方法 + TypeName[]::new //引用一个数组的构造器 +~~~ + +## `1.6` 重复注解 + +相同的注解可以在同一地方声明多次,由 `@Repeatable` 提供此特性 + +~~~java +/** + * @Repeatable( Filters.class ) + * 表示该注解可重复使用,注解内容存放于Filters中 + */ +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) +@Repeatable( Filters.class ) +public @interface Filter { + String value(); +}; + +/** + * 存放@Filter注解的数组 + * 该注解必须可以被访问 + */ +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface Filters { + Filter[] value(); +} + +/** + * 测试 + */ +@Filter( "filter1" ) +@Filter( "filter2" ) +public interface Filterable { +} + +public static void main(String[] args) { + //java8 提供的新方法,用于获取重复的注解 + for(Filter filter : Filterable.class.getAnnotationsByType(Filter.class ) ) { + System.out.println( filter.value() ); + } +} +~~~ + +## `1.7` 类型推测 + +~~~java +public class TypeInfer { + public static void main(String[] args) { + + final Value< String > value = new Value<>(); + + //value.setV(Value.defV()); //以前的写法 + + //Value.defV()返回类型被推测为String + value.setV(Value.defV()); + System.out.println(value.getV()); + } +} + +class Value< T > { + + private T o; + + public static T defV() { + //若T不为Object,将会出现类型转换异常 + return (T)new Object(); + } + + public void setV(T o){ + this.o = o; + } + + public T getV(){ + return o; + } +} +~~~ + +## `1.8` 扩展注解 + +java8几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现以及方法异常 + +`ElementType.TYPE_USE`和`ElementType.TYPE_PARAMETER` 用于描述注解上下文 + +~~~java +public class AnnotationEX { + + @Retention( RetentionPolicy.RUNTIME ) + @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } ) + public @interface Anno { + } + + public static class TestAEX< @Anno T > extends @Anno Object { + public void method() throws @Anno Exception { + } + + } + + public static void main(String[] args) { + final TestAEX< String > holder = new @Anno TestAEX<>(); + @Anno Collection< @Anno String > strings = new ArrayList<>(); + } +} +~~~ + +# `2` 类库新特性 + +## `2.1` Optional + +~~~java +//将String对象装入Optional容器 +Optional< String > stringOptional = Optional.ofNullable( null ); + +//判断容器中的String对象是否不为空 +System.out.println(stringOptional.isPresent() ); + +//orElseGet通过lambda产生一个默认值 +System.out.println(stringOptional.orElseGet( () -> "[default string]" ) ); + +//map对String对象进行转化,然后返回一个新的Optional实例, +// 此处s为null,所以未转换直接返回了1个新Optional实例, +// orElse直接产生一个默认值 +System.out.println(stringOptional.map( p -> "[" + p + "]" ).orElse( "Hello Optional" ) ); +~~~ + +## `2.2` Stream + +stream操作被分成了**中间操作**与**最终操作**两种 + +**中间操作**返回一个新的stream对象。中间操作总是采用惰性求值方式,运行一个像filter这样的中间操作实际上没有进行任何过滤,相反它在遍历元素时会产生了一个新的stream对象,这个新的stream对象包含原始stream中符合给定谓词的所有元素。 + +**最终操作**可能直接遍历stream,产生一个结果或副作用,如forEach、sum等。当最终操作执行结束之后,stream管道被认为已经被消耗了,没有可能再被使用了。在大多数情况下,最终操作都是采用及早求值方式,及早完成底层数据源的遍历。 + +~~~java +public enum Status { + OPEN, CLOSED +}; + +public class Task { + private final Status status; + private final Integer points; + + Task( final Status status, final Integer points ) { + this.status = status; + this.points = points; + } + + public Integer getPoints() { + return points; + } + + public Status getStatus() { + return status; + } + + @Override + public String toString() { + return String.format( "[%s, %d]", status, points ); + } +} + +~~~ + +~~~java +public static void main(String[] args){ + Collection< Task > tasks = Arrays.asList( + new Task( Status.OPEN, 5 ), + new Task( Status.OPEN, 13 ), + new Task( Status.CLOSED, 8 ) + ); + + long totalPointsOfOpenTasks = tasks + .stream() + .filter( task -> task.getStatus() == Status.OPEN ) + .mapToInt( Task::getPoints ) + .sum(); + + System.out.println( "Total points: " + totalPointsOfOpenTasks ); +} +~~~ + +原生并行处理 + +~~~java +double totalPoints = tasks + .stream() + .parallel() + .map( task -> task.getPoints() ) // or map( Task::getPoints ) + .reduce( 0, Integer::sum ); + +System.out.println( "Total points (all tasks): " + totalPoints ); +~~~ + +分组 + +~~~java +final Map< Status, List< Task >> map = tasks + .stream() + .collect( Collectors.groupingBy(Task::getStatus) ); + +System.out.println( map ); +~~~ + +权重 + +~~~java +//计算权重 +final Collection< String > result = tasks + .stream() // Stream< String > + .mapToInt( Task::getPoints ) // IntStream + .asLongStream() // LongStream + .mapToDouble( points -> points / totalPoints ) // DoubleStream + .boxed() // Stream< Double > + .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream + .mapToObj( percentage -> percentage + "%" ) // Stream< String> + .collect( Collectors.toList() ); // List< String > + +System.out.println( result ); +~~~ + +## `2.3` Date/Time API + +时间格式均为 **ISO-8601** + +`Clock` + +~~~java +//日期时间都有,有时区 +Clock clock = Clock.systemUTC(); +System.out.println(clock.instant()); +System.out.println(clock.millis()); +~~~ + +`LocalDate` + +~~~java +//只有日期没有时间 +LocalDate localDate = LocalDate.now(); +LocalDate localDateFromClock = LocalDate.now(clock); +System.out.println(localDate); +System.out.println(localDateFromClock); +~~~ + +`LocalTime` + +~~~java +//只有时间没有日期 +LocalTime localTime = LocalTime.now(); +LocalTime localTimeFromClock = LocalTime.now(clock); +System.out.println(localTime); +System.out.println(localTimeFromClock); +~~~ + +`LocalDateTime ` + +~~~java +//日期时间都有,没有时区 +LocalDateTime localDateTime = LocalDateTime.now(); +LocalDateTime localDateTimeFromClock = LocalDateTime.now(clock); +System.out.println(localDateTime); +System.out.println(localDateTimeFromClock); +~~~ + +`ZonedDateTime` + +~~~java +//指定时区,只有ZonedDateTime,没有ZoneDate与ZoneTime +ZonedDateTime zonedDateTime = ZonedDateTime.now(); +ZonedDateTime zonedDateTimeFromClock = ZonedDateTime.now(); +ZonedDateTime zonedDateTimeFromZone = ZonedDateTime.now(ZoneId.of( "America/Los_Angeles" )); +System.out.println(zonedDateTime); +System.out.println(zonedDateTimeFromClock); +System.out.println(zonedDateTimeFromZone); +~~~ + +`Duration` + +~~~java +//计算时间差 +LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 ); +LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 ); +Duration duration = Duration.between( from, to ); +System.out.println( "Duration in days: " + duration.toDays() ); +System.out.println( "Duration in hours: " + duration.toHours() ); +~~~ + +## `2.4` Nashorn + +`javax.script.ScriptEngine`的另一种实现,允许js与java相互调用 + +~~~java +ScriptEngineManager manager = new ScriptEngineManager(); +ScriptEngine engine = manager.getEngineByName( "JavaScript" ); + +System.out.println( engine.getClass().getName() ); +System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) ); +~~~ + +## `2.5` Base64 + +Base64编码已经成为Java8类库的标准 + +~~~java +final String text = "Base64 finally in Java 8!"; + +final String encoded = Base64 + .getEncoder() + .encodeToString( text.getBytes(StandardCharsets.UTF_8 ) ); +System.out.println( encoded ); + +final String decoded = new String( + Base64.getDecoder().decode( encoded ), + StandardCharsets.UTF_8 ); +System.out.println( decoded ); +~~~ + +>其他编码器与解码器 +> +>`Base64.getUrlEncoder() Base64.getUrlDecoder()` +> +>`Base64.getMimeEncoder() Base64.getMimeDecoder()` + +## `2.6` 并行(parallel )数组 + +**并行数组**操作可以在多核机器上极大提高性能 + +~~~java +long[] arrayOfLong = new long [ 20000 ]; + +//对arrayLong所有元素随机赋值 +Arrays.parallelSetAll(arrayOfLong, + index -> ThreadLocalRandom.current().nextInt(1000000)); + +//打印前10个元素 +Arrays.stream( arrayOfLong ).limit( 10 ).forEach( + i -> System.out.print( i + " " ) ); +System.out.println(); + +//对arrayLong数组排序 +Arrays.parallelSort( arrayOfLong ); + +//打印前10个元素 +Arrays.stream( arrayOfLong ).limit( 10 ).forEach( + i -> System.out.print( i + " " ) ); +System.out.println(); +~~~ + +## `2.7` 并发(concurrency) + +* java.util.concurrent.`ConcurrentHashMap`类中加入了一些新方法来支持聚集操作 + +* java.util.concurrent.`ForkJoinPool`类中加入了一些新方法来支持共有资源池(common pool) + +* java.util.concurrent.locks.`StampedLock`类提供基于容量的锁,这种锁有三个模型来控制读写操作 + +* java.util.concurrent.atomic包中增加新类:
+`DoubleAccumulator`,
+`DoubleAdder`,
+`LongAccumulator`,
+`LongAdder` + + +# `3` 编译器新特性 + +## `3.1` 参数名 + +方法参数的名字保留在Java字节码中,并且能够在运行时获取它们 + +编译时需要加上 `–parameters` ,**maven-compiler-plugin** 可进行配置 + +~~~java +public class ParameterNames { + public static void main(String[] args) throws Exception { + Method method = ParameterNames.class.getMethod( "main", String[].class ); + for( final Parameter parameter: method.getParameters() ) { + System.out.println( "Parameter: " + parameter.getName() ); + } + } +} +~~~ + +# `4` 新的Java工具 + +## `4.1` Nashorn引擎 `jjs` + +它接受一些JavaScript源代码为参数,并且执行这些源代码 + +创建 **func.js** 文件 + +~~~javascript +function f() { + return 1; +}; + +print( f() + 1 ); +~~~ +~~~bash +jjs func.js +~~~ + +## `4.2` 类依赖分析器 `jdeps` + +它可以显示Java类的包级别或类级别的依赖,它接受一个.class文件,一个目录,或者一个jar文件作为输入。jdeps默认把结果输出到系统输出(控制台)上。 + +~~~bash +jdeps org.springframework.core-3.0.5.RELEASE.jar +~~~ + +如果依赖不在classpath中,就会显示 **not found** + +# `5` JVM新特性 + +**PermGen**空间被移除了,取而代之的是**Metaspace**(JEP 122)。JVM选项**-XX:PermSize**与**-XX:MaxPermSize**分别被**-XX:MetaSpaceSize**与**-XX:MaxMetaspaceSize**所代替 \ No newline at end of file diff --git a/_posts/2014-12-21-webservice.md b/_posts/2014-12-21-webservice.md new file mode 100644 index 0000000..e61fc1a --- /dev/null +++ b/_posts/2014-12-21-webservice.md @@ -0,0 +1,754 @@ +--- +layout: post +title: WebService +tags: WebService Java +categories: web +--- + +* TOC +{:toc} + + +**WSDL** + +`definitions` 为根节点,属性为 + +>`name`:WS 名称,默认为“实现类 + Service” +> +>`targetNamespace`:WS 目标命名空间,默认为“WS 实现类对应包名倒排后构成的地址” + +`definitions`的5个子节点 + +>`types`:描述了 WS 中所涉及的数据类型 +> +>`portType`:定义了 WS 接口名称`portType.name`(`endpointInterface`默认为“WS 实现类所实现的接口”),及其操作名称,以及每个操作的输入与输出消息 +> +>`message`:对相关消息进行了定义(供 types 与 portType 使用) +> +>`binding`:提供了对 WS 的数据绑定方式 +> +>`service`:WS 名称及其端口名称`service_port.name`(`portName`默认为“WS 实现类 + Port”),以及对应的 WSDL 地址 + +**SOAP** + +`header` + +`body` + +--- + +# `一`、产品 + +**soap**风格 (JAX-WS规范JSR-224) + +JAX-WS RI:https://jax-ws.java.net/ Oracle 官方提供的实现 + +Axis:http://axis.apache.org/ + +CXF:http://cxf.apache.org/ + +**rest**风格 (JAX-RS规范JSR-339) + +Jersey:https://jersey.java.net/ Oracle 官方提供的实现 + +Restlet:http://restlet.com/ + +RESTEasy:http://resteasy.jboss.org/ + +CXF:http://cxf.apache.org/ + +# `二`、JDK发布与调用 + +## `1`.发布 + +### 定义接口 + +~~~java +//此注解必须 +@WebService +public interface HelloService { + String say(); +} +~~~ + +### 实现接口 + +~~~java +@WebService(serviceName = "HelloService", + portName = "HelloServicePort", + endpointInterface = "org.sllx.practice.jdkws.HelloService") +public class HelloServiceImpl implements HelloService { + @Override + public String say() { + return "helloWorld"; + } +} +~~~ + +### 发布服务 + +~~~java +public class Server { + public static void main(String[] args){ + String address = "http://localhost:8081/ws/soap/hello"; + HelloService helloService = new HelloServiceImpl(); + Endpoint.publish(address, helloService); + System.out.println("ws is published [" + address + "?wsdl]"); + } +} +~~~ + +访问`http://localhost:8081/ws/soap/hello?wsdl`即可查看详情 + +## `2`.调用 + +### 静态客户端 + + `wsimport http://localhost:8080/ws/soap/hello?wsdl`//通过 WSDL 地址生成 class 文件 + + `jar -cf client.jar .` //通过 jar 命令将若干 class 文件压缩为一个 jar 包 + +`rmdir /s/q demo `//删除生成的 class 文件(删除根目录即可) + +~~~java +public static void main(String[] args){ + HelloService_Service hss = new HelloService_Service(); + HelloService hs = hss.getHelloServicePort(); + System.out.println(hs.say()); +} +~~~ + +### 动态代理客户端 + +只需提供`HelloService`接口,无需jar + +~~~java +public static void main(String[] args){ + try { + URL wsdl = new URL("http://localhost:8081/ws/soap/hello?wsdl"); + QName serviceName = new QName("http://jdkws.practice.sllx.org/", "HelloService"); + QName portName = new QName("http://jdkws.practice.sllx.org/", "HelloServicePort"); + Service service = Service.create(wsdl, serviceName); + HelloService helloService = service.getPort(portName, HelloService.class); + String result = helloService.say(); + System.out.println(result); + } catch (Exception e) { + e.printStackTrace(); + } +} +~~~ + +# `三`、CXF + +User为自定义的POJO类 + +web发布示例集成spring + +## `1.`SOAP风格 + +### 定义接口 + +~~~java +package top.rainynight.sitews.user.ws; + +import top.rainynight.sitews.user.entity.User; +import javax.jws.WebService; + +@WebService +public interface UserWS { + + User lookOver(int id); +} +~~~ + +### 实现接口 + +~~~java +package top.rainynight.sitews.user.ws; + +import top.rainynight.sitews.user.entity.User; +import org.springframework.stereotype.Component; + +@Component("userWS") +public class UserWSImpl implements UserWS { + + @Override + public User lookOver(int id) { + return new User(); + } +} +~~~ + + +### 发布服务 + +#### ①Jetty发布 + +配置依赖 + +~~~xml + + UTF-8 + 3.0.0 + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + ${cxf.version} + + + org.apache.cxf + cxf-rt-transports-http-jetty + ${cxf.version} + + +~~~ + +发布 + +~~~java +public class JaxWsServer { + public static void main(String[] args) { + JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean(); + factory.setAddress("http://localhost:8080/ws/user"); + factory.setServiceClass(UserWS.class); + factory.setServiceBean(new UserWSImpl()); + factory.create(); + System.out.println("soap ws is published"); + } +} +~~~ + +访问`http://localhost:8080/ws/user?wsdl` + +#### ②Web发布 + +配置依赖 + +~~~xml + + UTF-8 + 4.0.5.RELEASE + 3.0.0 + + + + + org.springframework + spring-web + ${spring.version} + + + org.apache.cxf + cxf-rt-frontend-jaxws + ${cxf.version} + + + org.apache.cxf + cxf-rt-transports-http + ${cxf.version} + + +~~~ + +配置web.xml + +~~~xml + + org.springframework.web.context.ContextLoaderListener + + + contextConfigLocation + + classpath:spring.xml + + + + + cxf + org.apache.cxf.transport.servlet.CXFServlet + + + cxf + /* + +~~~ + +发布 + +~~~xml + + + + + + + + +~~~ + +访问`http://localhost:8080/ws/user?wsdl` + +### 调用服务 + +#### ①静态客户端 + +~~~java +public class JaxWsClient { + + public static void main(String[] args) { + JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); + factory.setAddress("http://localhost:8080/ws/user"); + factory.setServiceClass(UserWS .class); + + UserWS userWS = factory.create(UserWS .class); + User result = userWS .lookOver(1); + System.out.println(result); + } +} +~~~ + +#### ②动态代理客户端 + +~~~java +public class JaxWsDynamicClient { + + public static void main(String[] args) { + JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance(); + Client client = factory.createClient("http://localhost:8080/ws/user?wsdl"); + + try { + Object[] results = client.invoke("lookOver", 1); + System.out.println(results[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +~~~ + +#### ③通用动态代理客户端 + +既可调用 JAX-WS 服务,也可调用 Simple 服务 + +~~~java +public class DynamicClient { + + public static void main(String[] args) { + DynamicClientFactory factory = DynamicClientFactory.newInstance(); + Client client = factory.createClient("http://localhost:8080/ws/user?wsdl"); + + try { + Object[] results = client.invoke("lookOver", 1); + System.out.println(results[0]); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +~~~ + +#### ④Spring客户端 + +~~~xml + + + + + +~~~ + +~~~java +public class UserWSClient{ + + public static void main(String[] args) { + ApplicationContext context = new ClassPathXmlApplicationContext("spring-client.xml"); + + UserWS userWS= context.getBean("userWSClient", UserWS.class); + User result = userWS.lookOver(1); + System.out.println(result); + } +} +~~~ + +## `2`.REST风格 + +### 定义接口 + +`!`REST 规范允许资源类没有接口 + +>请求方式注解,包括:`@GET、@POST、@PUT、@DELETE` +> +>请求路径注解,包括:`@Path` ,其中包括一个路径参数 +> +>数据格式注解,包括:`@Consumes`(输入)、`@Produces`(输出),可使用 `MediaType` 常量 +> +>相关参数注解,包括:`@PathParam`(路径参数)、`@FormParam`(表单参数),此外还有 `@QueryParam`(请求参数) + +~~~java +package top.rainynight.sitews.user.ws; + +import top.rainynight.sitews.user.entity.User; +import javax.jws.WebService; + +@WebService +@Path("/") +public interface UserWS { + + @GET + @Path("{id:[1-9]{1,9}}") + @Produces(MediaType.APPLICATION_JSON) + User lookOver(@PathParam("id") int id); +} +~~~ + +### 实现接口 + +~~~java +package top.rainynight.sitews.user.ws; + +import top.rainynight.sitews.user.entity.User; +import org.springframework.stereotype.Component; + +@Component("userWS") +public class UserWSImpl implements UserWS { + + @Override + public User lookOver(int id) { + return new User(); + } +} +~~~ + +### 发布服务 + +#### ①jetty发布 + +配置依赖 + +~~~xml + + UTF-8 + 3.0.0 + 2.4.1 + + + + + org.apache.cxf + cxf-rt-frontend-jaxrs + ${cxf.version} + + + org.apache.cxf + cxf-rt-transports-http-jetty + ${cxf.version} + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson.version} + + +~~~ + +发布 + +~~~java +public class Server { + + public static void main(String[] args) { + // 添加 ResourceClass + List> resourceClassList = new ArrayList>(); + resourceClassList.add(UserWSImpl .class); + + // 添加 ResourceProvider + List resourceProviderList = new ArrayList(); + resourceProviderList.add(new SingletonResourceProvider(new ProductServiceImpl())); + + // 添加 Provider + List providerList = new ArrayList(); + providerList.add(new JacksonJsonProvider()); + + // 发布 REST 服务 + JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean(); + factory.setAddress("http://localhost:8080/rs/user"); + factory.setResourceClasses(resourceClassList); + factory.setResourceProviders(resourceProviderList); + factory.setProviders(providerList); + factory.create(); + System.out.println("rest ws is published"); + } +} +~~~ + +访问`http://localhost:8080/rs/user?_wadl` + +#### ②web发布 + +配置依赖 + +~~~xml + + UTF-8 + 4.0.6.RELEASE + 3.0.0 + 2.4.1 + + + + + org.springframework + spring-web + ${spring.version} + + + org.apache.cxf + cxf-rt-frontend-jaxrs + ${cxf.version} + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson.version} + + +~~~ + +配置web.xml + +~~~xml + + org.springframework.web.context.ContextLoaderListener + + + contextConfigLocation + + classpath:spring.xml + + + + + cxf + org.apache.cxf.transport.servlet.CXFServlet + + + cxf + /* + +~~~ + +发布 + +~~~xml + + + + + + + + + + + + + + + +~~~ + +访问`http://localhost:8080/rs/user?_wadl` + +### 调用服务 + +#### ①JAX-RS 1.0 客户端 + +~~~java +public class JAXRSClient { + + public static void main(String[] args) { + String baseAddress = "http://localhost:8080/rs/user"; + + List providerList = new ArrayList(); + providerList.add(new JacksonJsonProvider()); + + UserWS userWS = JAXRSClientFactory.create(baseAddress, UserWS.class, providerList); + + } +} +~~~ + +#### ②JAX-RS 2.0 客户端 + +~~~java +public class JAXRS20Client { + + public static void main(String[] args) { + String baseAddress = "http://localhost:8080/rs/user"; + + JacksonJsonProvider jsonProvider = new JacksonJsonProvider(); + + List userList = ClientBuilder.newClient() + .register(jsonProvider) + .target(baseAddress) + .path("/1") + .request(MediaType.APPLICATION_JSON) + .get(new GenericType>() {}); + } +} +~~~ + +#### ③WebClient 客户端 + +~~~java +public class CXFWebClient { + + public static void main(String[] args) { + String baseAddress = "http://localhost:8080/rs/user"; + + List providerList = new ArrayList(); + providerList.add(new JacksonJsonProvider()); + + List userList = WebClient.create(baseAddress, providerList) + .path("/1") + .accept(MediaType.APPLICATION_JSON) + .get(new GenericType>() {}); + + } +} +~~~ + +#### ④AJAX客户端 + +~~~javascript +$.ajax({ + type: 'get', + url: 'http://localhost:8080/rs/user/1', + dataType: 'json', + success: function(data) { +//... + } +}); +~~~ + +跨域方案1:jsonp + +~~~xml + + org.apache.cxf + cxf-rt-rs-extension-providers + ${cxf.version} + +~~~ + +~~~xml + + + + + + + + + + + + + + + +~~~ + +~~~javascript +$.ajax({ + type: 'get', + url: 'http://localhost:8080/rs/user/1', + dataType: 'jsonp', + jsonp: '_jsonp', + jsonpCallback: 'callback', + success: function(data) { + //... + } +}); +~~~ + +跨域方案2:cors + +~~~xml + + org.apache.cxf + cxf-rt-rs-security-cors + ${cxf.version} + +~~~ + +~~~xml + + + + + + + + + + + +~~~ + +allowOrigins 设置客户端域名 + +IE8 中使用 jQuery 发送 AJAX 请求时,需要配置 `$.support.cors = true` + + + + + + + + + + + + + + + + +## `~~`警告`~~` +`!warning` 极重要,cxf在wsdl中发布的`targetNamespace`是实现类路径,而被调用时却只接受接口路径。所以,请将接口与实现类放在同一路径下,或者在实现类中指定`targetNamespace`为接口的路径;否则客户端将抛出 ..common.i18n.UncheckedException: No operation was found with the name ... 异常 \ No newline at end of file diff --git a/_posts/2015-01-11-linux-make.md b/_posts/2015-01-11-linux-make.md new file mode 100644 index 0000000..66c71fc --- /dev/null +++ b/_posts/2015-01-11-linux-make.md @@ -0,0 +1,494 @@ +--- +layout: post +title: make +tags: make Linux script +categories: Linux +published: false +--- + + +* TOC +{:toc} + +代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build) + + +指定规则进行编译,默认为当前目录下的Makefile文件 + +~~~ +$ make -f rules.txt +或者 +make --file=rules.txt + +~~~ + +# Makefile 文件结构 + +Makefile文件由一系列规则(rules)构成。每条规则的形式如下。 + +~~~ + : +[tab] + +~~~ + +上面第一行冒号前面的部分,叫做"目标"(target),冒号后面的部分叫做"前置条件"(prerequisites);第二行必须由一个tab键起首,后面跟着"命令"(commands)。 + +"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。 + +## 目标(target) + +一个目标(target)就构成一条规则。目标通常是文件名,指明Make命令所要构建的对象 。目标可以是一个文件名,也可以是多个文件名,之间用空格分隔。 + +除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)。 + +~~~ +clean: + rm *.o + +~~~ +上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于"伪目标 ",作用是删除对象文件。 + +~~~ +$ make clean + +~~~ + +但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。 + +为了避免这种情况,可以明确声明clean是"伪目标",写法如下。 + +~~~ +.PHONY: clean +clean: + rm *.o temp + +~~~ + +声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。像.PHONY这样的内置目标名还有不少,可以查看手册。 + +如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。 + +~~~ +$ make + +~~~ + +## 前置条件(prerequisites) + +前置条件通常是一组文件名,之间用空格分隔。它指定了"目标"是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的last-modification时间戳比目标的时间戳新),"目标"就需要重新构建。 + +~~~ +result.txt: source.txt + cp source.txt result.txt + +~~~ + +如果当前目录中,source.txt 已经**存在**,那么make result.txt可以正常运行,否则必须再写一条规则,来生成 source.txt 。 + +~~~ +source.txt: + echo "this is the source" > source.txt + +~~~ + +上面代码中,source.txt后面没有前置条件,就意味着它跟其他文件都无关,只要这个文件还**不存在**,每次调用make source.txt,它都会生成。 + +~~~ +$ make result.txt +$ make result.txt + +~~~ + +上面命令连续执行两次make result.txt。第一次执行会先新建 source.txt,然后再新建 result.txt。第二次执行,Make发现 source.txt **没有变动**(时间戳晚于 result.txt),就不会执行任何操作,result.txt 也不会重新生成。 + +如果需要生成多个文件,往往采用下面的写法。 + +~~~ +source: file1 file2 file3 + +~~~ + +上面代码中,source 是一个伪目标,只有三个前置文件,没有任何对应的命令。 + +~~~ +$ make source + +~~~ + +执行make source命令后,就会一次性生成 file1,file2,file3 三个文件 + +## 命令(commands) + +命令(commands)表示如何更新目标文件,由一行或多行的Shell命令组成。它是构建"目标"的具体指令,它的运行结果通常就是生成目标文件。 + +每行命令之前必须有一个tab键。如果想用其他键,可以用内置变量.RECIPEPREFIX声明。 + +~~~ +.RECIPEPREFIX = > +all: +> echo Hello, world + +~~~ + +上面代码用.RECIPEPREFIX指定,大于号(>)替代tab键。所以,每一行命令的起首变成了大于号,而不是tab键。 + +需要注意的是,每行命令在一个单独的shell中执行。这些Shell之间没有继承关系。 + +~~~ +var-lost: + export foo=bar + echo "foo=[$$foo]" + +~~~ +上面代码执行后(make var-lost),取不到foo的值。因为两行命令在两个不同的进程执行。一个解决办法是将两行命令写在一行,中间用分号分隔。 + +~~~ +var-kept: + export foo=bar; echo "foo=[$$foo]" + +~~~ + +另一个解决办法是在换行符前加反斜杠转义。 + +~~~ +var-kept: + export foo=bar; \ + echo "foo=[$$foo]" + +~~~ + +最后一个方法是加上.ONESHELL:命令。 + +~~~ +.ONESHELL: +var-kept: + export foo=bar; + echo "foo=[$$foo]" + +~~~ + + +# Makefile 文件语法 + +**`# `号注释** + +## 通配符 + +通配符(wildcard)用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 [...] 。比如, *.o 表示所有后缀名为o的文件。 + +## 回声(echoing) + +正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)。 + +在命令的前面加上@,就可以关闭回声。 + +## 模式匹配 + +Make命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是%。比如,假定当前目录下有 f1.c 和 f2.c 两个源码文件,需要将它们编译为对应的对象文件。 + +~~~ +%.o: %.c + +~~~ + +等同于下面的写法。 + +~~~ +f1.o: f1.c +f2.o: f2.c + +~~~ + +使用匹配符%,可以将大量同类型的文件,只用一条规则就完成构建。 + +>待验证 +> +>关于模式匹配有个误解区,%.o:%.c并不是直接去匹配文件目录里的文件,而是匹配上下文的东西: +> +>`%.o:%.c` +> +>`gcc main.c -o main.o` +> +>`main.o:main.c` +> +>当我们make的时候 会执行 gcc main.c -o main.o + + +## 变量和赋值符 + +Makefile 允许使用等号自定义变量。 + +调用时,变量需要放在 $( ) 之中。 + +~~~ +txt = Hello World +test: + @echo $(txt) + +~~~ + +调用Shell变量,需要在美元符号前,再加一个美元符号,这是因为Make命令会对美元符号转义。 + +~~~ +test: + @echo $$HOME + +~~~ + +有时,变量的值可能指向另一个变量。 + +~~~ +v1 = $(v2) + +~~~ + +上面代码中,变量 v1 的值是另一个变量 v2。这时会产生一个问题,v1 的值到底在定义时扩展(**静态扩展**),还是在运行时扩展(**动态扩展**)?如果 v2 的值是动态的,这两种扩展方式的结果可能会差异很大。 + +为了解决类似问题,Makefile一共提供了四个赋值运算符 (=、:=、?=、+=),它们的区别请看[StackOverflow](http://stackoverflow.com/questions/448910/makefile-variable-assignment)。 + +~~~ +VARIABLE = value +# 在执行时扩展,允许递归扩展。 + +VARIABLE := value +# 在定义时扩展。 + +VARIABLE ?= value +# 只有在该变量为空时才设置值。 + +VARIABLE += value +# 将值追加到变量的尾端。 + +~~~ + +## 内置变量(Implicit Variables) + +Make命令提供一系列内置变量,比如,$(CC) 指向当前使用的编译器,$(MAKE) 指向当前使用的Make工具。这主要是为了跨平台的兼容性,详细的内置变量清单见[手册](https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html)。 + +~~~ +output: + $(CC) -o output input.c + +~~~ + +## 自动变量(Automatic Variables) + +(1)`$@` + +`$@`指代当前目标,就是Make命令当前构建的那个目标。比如,make foo的 `$@` 就指代foo。 + +~~~ +a.txt b.txt: + touch $@ + +~~~ +等同于下面的写法。 + +~~~ +a.txt: + touch a.txt +b.txt: + touch b.txt + +~~~ + +(2)`$<` + +`$<` 指代第一个前置条件。比如,规则为 t: p1 p2,那么`$<` 就指代p1。 + +~~~ +a.txt: b.txt c.txt + cp $< $@ + +~~~ + +等同于下面的写法。 + +~~~ +a.txt: b.txt c.txt + cp b.txt a.txt + +~~~ + +(3)`$?` + +`$?` 指代比目标更新的所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,其中 p2 的时间戳比 t 新,`$?`就指代p2。 + +(4)`$^` + +`$^` 指代所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,那么 `$^` 就指代 p1 p2 。 + +(5)`$*` + +`$*` 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,`$*` 就表示 f1。 + +(6)`$(@D)` 和 `$(@F)` + +`$(@D) `和` $(@F) `分别指向 `$@` 的目录名和文件名。比如,`$@`是 src/input.c,那么`$(@D)` 的值为 src ,`$(@F) `的值为 input.c。 + +(7)`$(`\a` 发出警告声; +> +>`\b` 删除前一个字符; +> +>`\c` 最后不加上换行符号; +> +>`\f` 换行但光标仍旧停留在原来的位置; +> +>`\n` 换行且光标移至行首; +> +>`\r` 光标移至行首,但不换行; +> +>`\t` 插入tab; +> +>`\v` 与\f相同; +> +>`\\` 插入\字符; +> +>`\nnn` 插入nnn(八进制)所代表的ASCII字符; + +`–help` 显示帮助 + +`–version` 显示版本信息 + +## 原样输出字符串 + +若需要原样输出字符串(不进行转义),请使用单引号 + +~~~ +echo '$name\"' +~~~ + +## 显示命令执行结果 + +~~~ +echo `date` +~~~ + +# printf + +printf 命令用于格式化输出, 是echo命令的增强版。它是C语言printf()库函数的一个有限的变形,并且在语法上有些不同。 + +注意:printf 由 POSIX 标准所定义,移植性要比 echo 好。 + +printf 不像 echo 那样会自动换行,必须显式添加换行符(\n)。 + +~~~ +$printf "Hello, Shell\n" +Hello, Shell +$ + +~~~ +printf 命令的语法 + +~~~ +printf format-string [arguments...] +~~~ + +format-string 为格式控制字符串,arguments 为参数列表。 + +这里仅说明与C语言printf()函数的不同: + +* printf 命令不用加括号 + +* format-string 可以没有引号,但最好加上,单引号双引号均可。 + +* 参数多于格式控制符(%)时,format-string 可以重用,可以将所有参数都转换。 + +* arguments 使用空格分隔,不用逗号。 + +注意,根据POSIX标准,浮点格式%e、%E、%f、%g与%G是“不需要被支持”。这是因为awk支持浮点预算,且有它自己的printf语句。这样Shell程序中需要将浮点数值进行格式化的打印时,可使用小型的awk程序实现。然而,内建于bash、ksh93和zsh中的printf命令都支持浮点格式。 + +# 分支 + +Shell 有三种 if ... else 语句: + +* if ... fi 语句; + +* if ... else ... fi 语句; + +* if ... elif ... else ... fi 语句。 + + +最后必须以 fi 来结尾闭合 if,fi 就是 if 倒过来拼写,后面也会遇见。 +注意:expression 和方括号([ ])之间必须有空格,否则会有语法错误。 + +## if + +~~~ +if [ expression ] +then + statements +fi +~~~ + +## if else + +~~~ +if [ expression ] +then + Statements +else + Statements +fi +~~~ + +## if elif fi + +~~~ +if [ expression 1 ] +then + Statements +elif [ expression 2 ] +then + Statements +elif [ expression 3 ] +then + Statements +else + Statements +fi +~~~ + +if ... else 语句也可以写成一行,以命令的方式来运行,经常与 test 命令结合使用 +`test` 命令用于检查某个条件是否成立,与方括号([ ])类似。 + +~~~ +if test $[2*3] -eq $[1+5]; then echo 'The two numbers are equal!'; fi; +~~~ + +# 多分枝选择 + +case ... esac 与其他语言中的 switch ... case 语句类似,是一种多分枝选择结构。 +case 语句匹配一个值或一个模式,如果匹配成功,执行相匹配的命令。 + +~~~ +case 值 in +模式1) + command1 + command2 + command3 + ;; +模式2) + command1 + command2 + command3 + ;; +*) + command1 + command2 + command3 + ;; +esac +~~~ + +取值后面必须为关键字 `in` + +每一模式必须以`)`结束 + +`;;` 与其他语言中的 break 类似 + +~~~ +# !/bin/bash + +option="${1}" +case ${option} in + -f) FILE="${2}" + echo "File name is $FILE" + ;; + -d) DIR="${2}" + echo "Dir name is $DIR" + ;; + *) + echo "`basename ${0}`:usage: [-f file] | [-d directory]" + exit 1 # Command to come out of the program with status 1 + ;; +esac +~~~ + +# 循环 + +## for + +~~~ +for 变量 in 列表 +do + command1 + command2 + ... + commandN +done +~~~ + +列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。 + +~~~ +# !/bin/bash + +for loop in 1 2 3 4 5 +do + echo "The value is: $loop" +done +~~~ + +~~~ +# !/bin/bash + +for FILE in $HOME/.bash* +do + echo $FILE +done +~~~ + +## while + +~~~ +while command +do + statements +done +~~~ + +~~~ +COUNTER=0 +while [ $COUNTER -lt 5 ] +do + COUNTER=`expr $COUNTER+1` + echo $COUNTER +done +~~~ + +while循环可用于读取键盘信息 + +~~~ +echo 'type to terminate' +echo -n 'enter your most liked film: ' +while read FILM +do + echo "Yeah! great film the $FILM" +done +~~~ + +## until + +until 循环执行一系列命令直至条件为 `true` 时`停止`。until 循环与 while 循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。 + +~~~ +until command +do + statements +done +~~~ + +## 跳出循环 + +### break + +break将终止循环体中的后续操作 + +在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环`break n` + +### continue + +continue跳出当次循环 + +同样,continue 后面也可以跟一个数字,表示跳出第几层循环`continue n` + +# 函数 + +## 定义 + +~~~ +function_name () { + list of commands + [ return value ] +} + +# 可以加上function 关键字 +function function_name () { + list of commands + [ return value ] +} +# 函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。 +~~~ + +Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:“numeric argument required”。 + +如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。 + + +~~~ +# !/bin/bash +funWithReturn(){ + echo "The function is to get the sum of two numbers..." + echo -n "Input first number: " + read aNum + echo -n "Input another number: " + read anotherNum + echo "The two numbers are $aNum and $anotherNum !" + return $(($aNum+$anotherNum)) + # 两组括号的意思是做算术运算,如果不这么写直接写$a+$b的话,是按字符串理解的,就是25+50这个字符串 +} +funWithReturn +# Capture value returnd by last command +ret=$? +echo "The sum of two numbers is $ret !" +~~~ + +调用函数只需要给出函数名,不需要加括号。 + +函数返回值在调用该函数后通过 `$?` 来获得。 + +## 嵌套 + +~~~ +# !/bin/bash + +# Calling one function from another +number_one () { + echo "Url_1 is http://see.xidian.edu.cn/cpp/shell/" + number_two +} + +number_two () { + echo "Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/" +} + +number_one +~~~ + +## 删除 + +~~~ +unset .f function_name +~~~ + +## 参数 + +在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 `$n` 的形式来获取参数的值,例如,`$1`表示第一个参数,`$2`表示第二个参数... + +~~~ +# !/bin/bash +funWithParam(){ + echo "The value of the first parameter is $1 !" + echo "The value of the second parameter is $2 !" + echo "The value of the tenth parameter is $10 !" + echo "The value of the tenth parameter is ${10} !" + echo "The value of the eleventh parameter is ${11} !" + echo "The amount of the parameters is $# !" # 参数个数 + echo "The string of the parameters is $* !" # 传递给函数的所有参数 +} +funWithParam 1 2 3 4 5 6 7 8 9 34 73 +~~~ + +注意,`$10` 不能获取第十个参数,获取第十个参数需要`${10}`。当n>=10时,需要使用`${n}`来获取参数。 + +特殊参数 + +`$# ` 传递给函数的参数个数。 + +`$*` 显示所有传递给函数的参数。 + +`$@` 与`$*`相同,但是略有区别,请查看Shell特殊变量。 + +`$?` 函数的返回值。 + +# IO重定向 + +~~~ +command > file +command < file +~~~ + +一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件: + +* 标准输入文件(`stdin`):stdin的文件描述符为`0`,Unix程序默认从stdin读取数据。 + +* 标准输出文件(`stdout`):stdout 的文件描述符为`1`,Unix程序默认向stdout输出数据。 + +* 标准错误文件(`stderr`):stderr的文件描述符为`2`,Unix程序会向stderr流中写入错误信息。 + + stderr 重定向到 file + +~~~ + command 2 > file +~~~ + +将 stdout 和 stderr 合并后重定向到 file + +~~~ +command > file 2>&1 +~~~ + +默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。 + +`command > file` 将输出重定向到 file。 + +`command < file` 将输入重定向到 file。 + +`command >> file` 将输出以追加的方式重定向到 file。 + +`n > file` 将文件描述符为 n 的文件重定向到 file。 + +`n >> file` 将文件描述符为 n 的文件以追加的方式重定向到 file。 + +`n >& m` 将输出文件 m 和 n 合并。 + +`n <& m` 将输入文件 m 和 n 合并。 + +`<< tag` 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 + +## Here Document + +~~~ +command << delimiter + document +delimiter +~~~ + +它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。 +注意: + +* 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。 + +* 开始的delimiter前后的空格会被忽略掉。 + +## /dev/null + +/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。 +屏蔽 stdout 和 stderr + +~~~ +command > /dev/null 2>&1 +~~~ + +# 文件包含 + +Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本 + +~~~ +. filename +# 或 +source filename +~~~ + +两种方式的效果相同,简单起见,一般使用点号(.),但是注意点号(.)和文件名中间有一空格 + +脚本 subscript.sh + +~~~ +url="http://see.xidian.edu.cn/cpp/view/2738.html" +~~~ + +引入当前目录下的subscript.sh脚本 + +~~~ +# !/bin/bash +. ./subscript.sh +echo $url +~~~ diff --git a/_posts/2015-02-06-closure.md b/_posts/2015-02-06-closure.md new file mode 100644 index 0000000..1d5b5c5 --- /dev/null +++ b/_posts/2015-02-06-closure.md @@ -0,0 +1,63 @@ +--- +layout: post +title: JS 闭包 +tags: closure javascript script +categories: front-end +published: false +--- + +在JS中,当内部的方法被其他对象引用,如果内部的方法使用了外部方法的变量,将造成外部方法无法释放,变量将被保持,此时将形成闭包。 + +看一个例子 + +~~~html +闭包测试1
+闭包测试2
+闭包测试3
+~~~ + +~~~javascript +function closureTest(){ + for (var i = 1; i < 4; i++) { + var element = document.getElementById('closureTest' + i); + element.onclick = function(){ + alert(i); + } + } +} +~~~ + +此时无论点击哪个超链接,弹出的都是`3`,这是因为onclick触发时,绑定函数才会去初始化`i`的值,而`i`引用自外部函数`closureTest`,在closureTest中,`i`早已递增到3。 + + +解决办法很简单,不闭包就行了。 + +~~~javascript +function badClosureExample(){ + for (var i = 1; i <4; i++) { + var element = document.getElementById('closureTest' + i); + element.onclick = clickCall(i); + } +} + +function clickCall(j){ + return function(){ + alert('您单击的是第' + j + '个链接'); + } +} +~~~ + +闭包也并非全然有害,有时我们也可以利用做些有趣的事,例如定时任务的传参 + +~~~javascript +function bind(){ + var element = document.getElementById('closureTest0'); + element.onclick = function(){ + setTimeout(function(p){ + return function(){ + alert(p); + } + }('998'), 1000); //延迟1秒弹出提示 + } +} +~~~ diff --git a/_posts/2015-02-11-webtool.md b/_posts/2015-02-11-webtool.md new file mode 100644 index 0000000..2864d7e --- /dev/null +++ b/_posts/2015-02-11-webtool.md @@ -0,0 +1,135 @@ +--- +layout: post +title: DIY的 ajax 框架 +tags: ajax javascript +categories: front-end +published: true +--- + +话不多说,直接上源码,用了传说中的伪面向对象写法 + +加了个`jsonp`用来跨域,当然了,需要服务端支持才有效 + +~~~javascript +(function(){ + var WT = { + + ID_SEL:'# ', + + CLA_SEL:'.', + + newInstance:function(){ + + var instance = {}; + + var setAttrs = function (dom,attrs){ + for(var key in attrs){ + dom.setAttribute(key,attrs[key]); + } + } + + var convertParam = function(data){ + var param = []; + for (var key in data) { + param.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); + } + return param; + } + + instance.select = function(selector){ + var h = selector.charAt(0); + var o = null; + var m = null; + if(h==WT.ID_SEL){ + m = selector.substr(1); + o = document.getElementById(m); + } + else if(h==WT.CLA_SEL){ + //TODO + //m = selector.substr(1); + //o = document.getElementsByClassName(m); + }else { + m = selector; + o = document.getElementsByTagName(m); + } + return o; + } + + instance.load = function(tagName, attrs, callback){ + var dom = document.createElement(tagName); + setAttrs(dom,attrs); + dom.onload=function(){ + typeof callback === 'function' && callback.call(this,dom); + dom = null; callback =null; + } + return dom; + } + + + instance.jsonp = function(jp){ + //---- attributes----// + var callback = jp.callback; + var url = jp.url; + var data = jp.data; + + var cn = 'jsonp'+ Date.now(); + window[cn] = callback; + var param = convertParam(data); + param.push('callback=' + cn); + url += (url.indexOf('?')==-1 ? '?' : '') + param.join('&'); + + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = script.onreadystatechange = function() { + this.parentNode.removeChild(script); + delete window[cn]; + script.onload = script.onreadystatechange = null; + } + script.src = url; + this.select('body')[0].appendChild(script); + } + + instance.ajax = function(jp){ + //---- attributes----// + var type = jp.type;type ? type : 'GET'; + var url = jp.url; + var data = jp.data; + var success = jp.success; + + var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); + xhr.open(type,url); + if (type.toUpperCase() === 'POST') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + xhr.onreadystatechange = function(){ + if(xhr.readyState==4){ + typeof success === 'function' && success(eval('(' + xhr.responseText + ')'), xhr.status); + xhr = null; + } + } + xhr.send(convertParam(data).join('&')); + } + + instance.loadImg = function (url, callback) { + /** img的onload在图片下载完成后,dom加载前发生 **/ + this.load('img',{src:url},callback); + } + + instance.loadCss = function(url,callback){ + /** css的onload 在 dom加载后发生 **/ + var dom = this.load('link',{rel:'stylesheet',type:'text/css',href:url},callback); + this.select('head')[0].appendChild(dom); + } + + instance.loadJs = function(url,callback){ + /** js的onload 在 dom加载后发生 **/ + var js = this.load('script',{type:'text/javascript',src:url},callback); + this.select('body')[0].appendChild(js); + } + + return instance; + } + }; + window['$'] = WT.newInstance(); +})(); +~~~ \ No newline at end of file diff --git a/_posts/2015-02-13-sqlappend.md b/_posts/2015-02-13-sqlappend.md new file mode 100644 index 0000000..abb2983 --- /dev/null +++ b/_posts/2015-02-13-sqlappend.md @@ -0,0 +1,341 @@ +--- +layout: post +title: SQL 拼接 +tags: sql database Java +categories: database +published: true +--- + +在java中进行SQL拼接是一件无比痛苦的工作,这是由于需要通过判断参数动态生成SQL + +而且拼接时产生满屏幕的加号或者append,使SQL几乎失去了可读性,那么丢失`WHERE`,`AND`,逗号等语法错误将随之而来 + +最近研究mybatis时发现了一个非常好用的工具类 SQL Builder [^sqlBuilder] + + +## 示例 + +~~~java + @Test + public void test(){ + String sql = new SQL(){ { + SELECT("name"); + SELECT("password"); + SELECT("sex"); + FROM("student s"); + INNER_JOIN("info i on s.id = i.id"); + WHERE(String.format("name = '%s'","HanMeiMei")); + } }.toString(); + + System.out.println(sql); + } +~~~ + +运行结果为: + +SELECT name, password, sex + +FROM student s + +INNER JOIN info i on s.id = i.id + +WHERE (name = 'HanMeiMei' AND i.sex = 1) + +SQL的拼接词都自动生成了 + +## 源码 + +事实上不必为了使用这个类而引入Mybatis,SQL Builder非常简单,简单到没有使用任何第三方类库,只需将这个类复制粘贴一下就可以直接用了。 + +SQL Builder 全部代码 + +~~~java +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractSQL { + + private static final String AND = ") \nAND ("; + private static final String OR = ") \nOR ("; + + public abstract T getSelf(); + + public T UPDATE(String table) { + sql().statementType = SQLStatement.StatementType.UPDATE; + sql().tables.add(table); + return getSelf(); + } + + public T SET(String sets) { + sql().sets.add(sets); + return getSelf(); + } + + public T INSERT_INTO(String tableName) { + sql().statementType = SQLStatement.StatementType.INSERT; + sql().tables.add(tableName); + return getSelf(); + } + + public T VALUES(String columns, String values) { + sql().columns.add(columns); + sql().values.add(values); + return getSelf(); + } + + public T SELECT(String columns) { + sql().statementType = SQLStatement.StatementType.SELECT; + sql().select.add(columns); + return getSelf(); + } + + public T SELECT_DISTINCT(String columns) { + sql().distinct = true; + SELECT(columns); + return getSelf(); + } + + public T DELETE_FROM(String table) { + sql().statementType = SQLStatement.StatementType.DELETE; + sql().tables.add(table); + return getSelf(); + } + + public T FROM(String table) { + sql().tables.add(table); + return getSelf(); + } + + public T JOIN(String join) { + sql().join.add(join); + return getSelf(); + } + + public T INNER_JOIN(String join) { + sql().innerJoin.add(join); + return getSelf(); + } + + public T LEFT_OUTER_JOIN(String join) { + sql().leftOuterJoin.add(join); + return getSelf(); + } + + public T RIGHT_OUTER_JOIN(String join) { + sql().rightOuterJoin.add(join); + return getSelf(); + } + + public T OUTER_JOIN(String join) { + sql().outerJoin.add(join); + return getSelf(); + } + + public T WHERE(String conditions) { + sql().where.add(conditions); + sql().lastList = sql().where; + return getSelf(); + } + + public T OR() { + sql().lastList.add(OR); + return getSelf(); + } + + public T AND() { + sql().lastList.add(AND); + return getSelf(); + } + + public T GROUP_BY(String columns) { + sql().groupBy.add(columns); + return getSelf(); + } + + public T HAVING(String conditions) { + sql().having.add(conditions); + sql().lastList = sql().having; + return getSelf(); + } + + public T ORDER_BY(String columns) { + sql().orderBy.add(columns); + return getSelf(); + } + + private SQLStatement sql = new SQLStatement(); + + private SQLStatement sql() { + return sql; + } + + public A usingAppender(A a) { + sql().sql(a); + return a; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sql().sql(sb); + return sb.toString(); + } + + private static class SafeAppendable { + private final Appendable a; + private boolean empty = true; + + public SafeAppendable(Appendable a) { + super(); + this.a = a; + } + + public SafeAppendable append(CharSequence s) { + try { + if (empty && s.length() > 0) empty = false; + a.append(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public boolean isEmpty() { + return empty; + } + + } + + private static class SQLStatement { + + public enum StatementType { + DELETE, INSERT, SELECT, UPDATE + } + + StatementType statementType; + List sets = new ArrayList(); + List select = new ArrayList(); + List tables = new ArrayList(); + List join = new ArrayList(); + List innerJoin = new ArrayList(); + List outerJoin = new ArrayList(); + List leftOuterJoin = new ArrayList(); + List rightOuterJoin = new ArrayList(); + List where = new ArrayList(); + List having = new ArrayList(); + List groupBy = new ArrayList(); + List orderBy = new ArrayList(); + List lastList = new ArrayList(); + List columns = new ArrayList(); + List values = new ArrayList(); + boolean distinct; + + private void sqlClause(SafeAppendable builder, String keyword, List parts, String open, String close, + String conjunction) { + if (!parts.isEmpty()) { + if (!builder.isEmpty()) + builder.append("\n"); + builder.append(keyword); + builder.append(" "); + builder.append(open); + String last = "________"; + for (int i = 0, n = parts.size(); i < n; i++) { + String part = parts.get(i); + if (i > 0 && !part.equals(AND) && !part.equals(OR) && !last.equals(AND) && !last.equals(OR)) { + builder.append(conjunction); + } + builder.append(part); + last = part; + } + builder.append(close); + } + } + + private String selectSQL(SafeAppendable builder) { + if (distinct) { + sqlClause(builder, "SELECT DISTINCT", select, "", "", ", "); + } else { + sqlClause(builder, "SELECT", select, "", "", ", "); + } + + sqlClause(builder, "FROM", tables, "", "", ", "); + sqlClause(builder, "JOIN", join, "", "", "\nJOIN "); + sqlClause(builder, "INNER JOIN", innerJoin, "", "", "\nINNER JOIN "); + sqlClause(builder, "OUTER JOIN", outerJoin, "", "", "\nOUTER JOIN "); + sqlClause(builder, "LEFT OUTER JOIN", leftOuterJoin, "", "", "\nLEFT OUTER JOIN "); + sqlClause(builder, "RIGHT OUTER JOIN", rightOuterJoin, "", "", "\nRIGHT OUTER JOIN "); + sqlClause(builder, "WHERE", where, "(", ")", " AND "); + sqlClause(builder, "GROUP BY", groupBy, "", "", ", "); + sqlClause(builder, "HAVING", having, "(", ")", " AND "); + sqlClause(builder, "ORDER BY", orderBy, "", "", ", "); + return builder.toString(); + } + + private String insertSQL(SafeAppendable builder) { + sqlClause(builder, "INSERT INTO", tables, "", "", ""); + sqlClause(builder, "", columns, "(", ")", ", "); + sqlClause(builder, "VALUES", values, "(", ")", ", "); + return builder.toString(); + } + + private String deleteSQL(SafeAppendable builder) { + sqlClause(builder, "DELETE FROM", tables, "", "", ""); + sqlClause(builder, "WHERE", where, "(", ")", " AND "); + return builder.toString(); + } + + private String updateSQL(SafeAppendable builder) { + + sqlClause(builder, "UPDATE", tables, "", "", ""); + sqlClause(builder, "SET", sets, "", "", ", "); + sqlClause(builder, "WHERE", where, "(", ")", " AND "); + return builder.toString(); + } + + public String sql(Appendable a) { + SafeAppendable builder = new SafeAppendable(a); + if (statementType == null) { + return null; + } + + String answer; + + switch (statementType) { + case DELETE: + answer = deleteSQL(builder); + break; + + case INSERT: + answer = insertSQL(builder); + break; + + case SELECT: + answer = selectSQL(builder); + break; + + case UPDATE: + answer = updateSQL(builder); + break; + + default: + answer = null; + } + + return answer; + } + } +} + + +public class SQL extends AbstractSQL { + + @Override + public SQL getSelf() { + return this; + } + +} +~~~ + +[^sqlBuilder]: [http://mybatis.github.io/mybatis-3/statement-builders.html](http://mybatis.github.io/mybatis-3/statement-builders.html) \ No newline at end of file diff --git a/_posts/2015-02-22-javajs.md b/_posts/2015-02-22-javajs.md new file mode 100644 index 0000000..2cab51c --- /dev/null +++ b/_posts/2015-02-22-javajs.md @@ -0,0 +1,47 @@ +--- +layout: post +title: Java中用js解析json +tags: ScriptEngineManager Java javascript +categories: java +published: java +--- + +在java中如何解析json?fastjson?jackson?那未免太无趣了 + +其实我们可以试试ScriptEngine + +~~~java +public class NashornTest { + + private static String json = "[{name:'A',age:'18'},{name:'B',age:'19'},{name:'C',age:'30'}]"; + + private static String script = + "function parse(json){" + + " var names = new Array();" + + " for(var i in json){" + + " names.push(json[i].name);" + + " }" + + " return names;" + + "};" + + "parse(" + json + ");"; + + public static void main(String[] args) throws ScriptException { + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName( "JavaScript" ); + + Map result = (Map)engine.eval(script); + result.forEach((k,v) -> System.out.println(v)); + } +} +~~~ + +输出: + +A + +B + +C + +> 代码中使用了 [lambda](http://blog.rainynight.top/2014-12-13/java-newfeature/# lambda) + diff --git a/_posts/2015-03-05-validator.md b/_posts/2015-03-05-validator.md new file mode 100644 index 0000000..571c1df --- /dev/null +++ b/_posts/2015-03-05-validator.md @@ -0,0 +1,142 @@ +--- +layout: post +title: Bean Validation +tags: Bean Validation Java +categories: java +published: true +--- + +[BeanValidation][BeanValidation] 可以帮助开发者方便地对数据进行校验,但它只是一个标准,只有一套接口,要想使用它的功能必须选择一种实现,`hibernate-validator`是个不错的选择 + +~~~xml + + org.hibernate + hibernate-validator + 5.0.2.Final + +~~~ + +BeanValidator 可以自动扫描到hibernate-validator,而不用进行任何配置,前提是需要将hibernate-validator放到classpath下 + +在JAVA类中可以直接得到可用的检验器实现: + +~~~ +private Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); +~~~ + +它是怎么做到的?笔者和你具有一样强的好奇心,深入源码后,笔者发现了这样一段代码 + +~~~ +private List> loadProviders(ClassLoader classloader) { + ServiceLoader loader = ServiceLoader.load( ValidationProvider.class, classloader ); + Iterator providerIterator = loader.iterator(); + List> validationProviderList = new ArrayList>(); + while ( providerIterator.hasNext() ) { + try { + validationProviderList.add( providerIterator.next() ); + } + catch ( ServiceConfigurationError e ) { + // ignore, because it can happen when multiple + // providers are present and some of them are not class loader + // compatible with our API. + } + } + return validationProviderList; +} +~~~ + +显而易见,通过`ClassLoader`可以找出所有实现了`ValidationProvider`的接口的类,这些类即为BeanValidator的实现,然后从这个列表中取第一个就行了。 + +BeanValidation 使用方法也非常简单,调用`validate`方法后会返回一个校验结果集,如果结果集长度为0则表示校验完全通过;否则将可以从`ConstraintViolation`中获取失败信息 + +~~~ +Set> constraintViolations = validator.validate(new Article()); +~~~ + +当然,前提是先要定义字段的约束,`@NotNull`表示此字段的值不可为空,注解也可以在字段上,更多预置的注解在`javax.validation.constraints`包中 + +~~~ +@NotNull +public String getTitle() { + return title; +} +~~~ + +# 与Spring集成 + +现在Spring4已经集成了BeanValidation,并增加了国际化支持,这个LocalValidatorFactoryBean可以注入到任何类中使用 + +~~~ + + + + + + + + + + + + classpath:messages/validationMessages + classpath:org/hibernate/validator/ValidationMessages + + + + + + +~~~ + +如果你正在使用 SpringMVC 集成就更简单了 + +~~~ + +~~~ + +然后在需要校验的Bean上注解`@Valid`,并紧随其后添加`BindingResult` + +~~~ +@RequestMapping(value = "release", method = RequestMethod.POST) +public String release(@Valid Article article, BindingResult bindingResult, HttpSession session, ModelMap modelMap){ + ... + ... +} +~~~ + +如果想要将校验失败的信息展示在页面上可以使用el表达式, 注意el版本必须在2.2以上, s前缀指的是spring标签 + +~~~ +<%@taglib prefix="s" uri="http://www.springframework.org/tags" %> +~~~ + +~~~ + + + 字段错误:
+ + + ${error.field}------${message}
+
+
+ + + 全局错误:
+ + + + ${message}
+
+
+
+
+~~~ + +--- + +其实 BeanValidation的内容远不止于此,它还有很多更高级的特性,如分组校验等,详情请[点我][more] + + + +[BeanValidation]:http://beanvalidation.org/ +[more]:http://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/ \ No newline at end of file diff --git a/_posts/2015-03-12-spring-security.md b/_posts/2015-03-12-spring-security.md new file mode 100644 index 0000000..4cf7525 --- /dev/null +++ b/_posts/2015-03-12-spring-security.md @@ -0,0 +1,320 @@ +--- +layout: post +title: spring security 探秘 +tags: spring security Java +categories: web +--- + +# 概述 + +[Spring Security][site]这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。 + +* TOC +{:toc} + +本文的宗旨并非描述如何从零开始搭建一个 "hello world" 级的demo,或者列举有哪些可配置项(这种类似于词典的文档,没有比[参考书][doc]更合适的了),而是简单描述spring-security项目的整体结构,设计思想,以及某些重要配置做了什么。 + +本文所有内容基于spring-security-4.0.1.RELEASE ,你可以在[Github][github]中找到它,或者使用Maven获取,引入spring-security-config是为了通过命名空间简化配置。 + +~~~xml + + org.springframework.security + spring-security-web + 4.0.1.RELEASE + + + org.springframework.security + spring-security-config + 4.0.1.RELEASE + +~~~ + + +# Filter + +spring-security的业务流程是独立于项目的,我们需要在web.xml中指定其入口,注意该过滤器必须在项目的过滤器之前。 + +~~~xml + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /* + +~~~ + +值得一提的是,该过滤器的名字具有特殊意义,没有特别需求不建议修改,我们可以在该过滤的源码中看到,其过滤行为委托给了一个`delegate`对象,该delegate对象是一个从spring容器中获取的bean,依据的beanid就是filter-name。 + +~~~java +@Override +protected void initFilterBean() throws ServletException { + synchronized (this.delegateMonitor) { + if (this.delegate == null) { + + if (this.targetBeanName == null) { + this.targetBeanName = getFilterName(); + } + + WebApplicationContext wac = findWebApplicationContext(); + if (wac != null) { + this.delegate = initDelegate(wac); + } + } + } +} +~~~ + + +# HTTP + +我们可以在security中声明多个`http`元素,每个http元素将产生一个`FilterChain`,这些FilterChain将按照声明顺序加入到`FilterChainProxy`中,而这个FilterChainProxy就是web.xml中定义的springSecurityFilterChain内部的`delegate`。 + +~~~xml + + + +~~~ + +在http元素也就是FilterChain中,以责任链的形式存在多个`Filter`,这些Filter真正执行过滤操作,http标签中的许多配置项,如` `、``等,其实就是创建指定的Filter,以下表格列举了这些Filter。 + +![filter][filter] + +利用别名,我们可以将自定义的过滤器加入指定的位置,或者替换其中的某个过滤器。 + +~~~xml + +~~~ + +整体来看,一个FilterChainProxy中可以包含有多个FilterChain,一个FilterChain中又可以包含有多个Filter,然而对于一个既定请求,只会使用其中一个FilterChain。 + +# FilterChain + +![filterChain][filterChain] + +上图列举了一些Filter, 此处将说明这些Filter的作用, 在需要插入自定义Filter时, 这些说明可以作为参考。 + +* SecurityContextPersistenceFilter + 创建一个空的SecurityContext(如果session中没有SecurityContext实例),然后持久化到session中。在filter原路返回时,还需要保存这个SecurityContext实例到session中。 + +* RequestCacheAwareFilter + 用于用户登录成功后,重新恢复因为登录被打断的请求 + +* AnonymousAuthenticationFilter + 如果之前的过滤器都没有认证成功,则为当前的SecurityContext中添加一个经过匿名认证的token, 所有与认证相关的过滤器(如CasAuthenticationFilter)都应当放在AnonymousAuthenticationFilter之前。 + +* SessionManagementFilter + 1.session固化保护-通过session-fixation-protection配置 + 2.session并发控制-通过concurrency-control配置 + +* ExceptionTranslationFilter + 主要拦截两类安全异常:认证异常、访问拒绝异常。而且仅仅是捕获后面的过滤器产生的异常。所以在自定义拦截器时,需要注意在链中的顺序。 + +* FilterSecurityInterceptor + 通过决策管理器、认证管理器、安全元数据来判断用户是否能够访问资源。 + + + +# FilterSecurityInterceptor + +如果一个http请求能够匹配security定义的规则,那么该请求将进入security处理流程,大体上,security分为三个部分: + +* AuthenticationManager 处理认证请求 +* AccessDecisionManager 提供访问决策 +* SecurityMetadataSource 元数据 + +以下代码摘自`AbstractSecurityInterceptor`, 这是`FilterSecurityInterceptor`的父类, 也正是在此处区分了web请求拦截器与方法调用拦截器。(代码有所精简) + +~~~java +protected InterceptorStatusToken beforeInvocation(Object object) { + + if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { + throw new IllegalArgumentException(); + } + + Collection attributes = + this.obtainSecurityMetadataSource().getAttributes(object); + + if (attributes == null || attributes.isEmpty()) { + if (rejectPublicInvocations) { + throw new IllegalArgumentException(); + } + publishEvent(new PublicInvocationEvent(object)); + return null; // no further work post-invocation + } + + if (SecurityContextHolder.getContext().getAuthentication() == null) { + //... + } + + Authentication authenticated = authenticateIfRequired(); + + // Attempt authorization + try { + this.accessDecisionManager.decide(authenticated, object, attributes); + } + catch (AccessDeniedException accessDeniedException) { + publishEvent(new AuthorizationFailureEvent(object, attributes, + authenticated,accessDeniedException)); + throw accessDeniedException; + } +} + + +private Authentication authenticateIfRequired() { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + + if (authentication.isAuthenticated() && !alwaysReauthenticate) { + return authentication; + } + + authentication = authenticationManager.authenticate(authentication); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + return authentication; +} +~~~ + +在FilterSecurityInterceptor的处理流程中,首先会处理认证请求,获取用户信息,然后决策处理器根据用户信息与权限元数据进行决策,同样,这三个部分都是可以自定义的。 + +~~~xml + + + + + + +~~~ + + +# AuthenticationManager + +AuthenticationManager处理认证请求,然而它并不直接处理,而是将工作委托给了一个`ProviderManager`,ProviderManager又将工作委托给了一个`AuthenticationProvider`列表,只要任何一个AuthenticationProvider认证通过,则AuthenticationManager认证通过,我们可以配置一个或者多个AuthenticationProvider,还可以对密码进行加密。 + +~~~xml + + + + + + + +~~~ + +考虑到一种常见情形,用户输入用户名密码,然后与数据比对,验证用户信息,security提供了类来处理。 + +~~~xml + + + +~~~ +JdbcDaoImpl使用内置的SQL查询数据,这些SQL以常量的形式出现在JdbcDaoImpl开头,同样可以注入修改。 + + +# AccessDecisionManager + +AccessDecisionManager提供访问决策,它同样不会直接处理,而是仅仅抽象为一种投票规则,然后决策行为委托给所有投票人。 + +~~~xml + + + + + + + + + + + + + + +~~~ + +security提供了三种投票规则: + +* AffirmativeBased 只要有一个voter同意就通过 +* ConsensusBased 只要投同意票的大于投反对票的就通过 +* UnanimousBased 需要一致同意才通过 + +以下为`AffirmativeBased`决策过程 + +~~~java +public void decide(Authentication authentication, Object object, + Collection configAttributes) throws AccessDeniedException { + int deny = 0; + + for (AccessDecisionVoter voter : getDecisionVoters()) { + int result = voter.vote(authentication, object, configAttributes); + + switch (result) { + case AccessDecisionVoter.ACCESS_GRANTED: + return; + + case AccessDecisionVoter.ACCESS_DENIED: + deny++; + + break; + + default: + break; + } + } + + if (deny > 0) { + throw new AccessDeniedException(messages.getMessage( + "AbstractAccessDecisionManager.accessDenied", "Access is denied")); + } + + // To get this far, every AccessDecisionVoter abstained + checkAllowIfAllAbstainDecisions(); +} +~~~ + + +# SecurityMetadataSource + +SecurityMetadataSource定义权限元数据(如资源与角色的关系),并提供了一个核心方法`Collection getAttributes(Object object)`来获取资源对应的角色列表,这种结构非常类似于Map。 + +security提供了`DefaultFilterInvocationSecurityMetadataSource`来进行角色读取操作,并将数据存储委托给一个`LinkedHashMap`对象。 + +~~~xml + + + + + + + + +~~~ + +DefaultFilterInvocationSecurityMetadataSource获取角色方法 + +~~~java +public Collection getAttributes(Object object) { + final HttpServletRequest request = ((FilterInvocation) object).getRequest(); + for (Map.Entry> entry : requestMap + .entrySet()) { + if (entry.getKey().matches(request)) { + return entry.getValue(); + } + } + return null; +} +~~~ + + +[site]: http://projects.spring.io/spring-security +[github]: https://github.com/spring-projects/spring-security +[filter]: {{"/spring-security-filter.png" | prepend: site.imgrepo }} +[filterChain]: {{"/spring-security-filterChain.jpg" | prepend: site.imgrepo }} +[doc]: http://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/ diff --git a/_posts/2015-03-17-servlet-encode.md b/_posts/2015-03-17-servlet-encode.md new file mode 100644 index 0000000..6c25293 --- /dev/null +++ b/_posts/2015-03-17-servlet-encode.md @@ -0,0 +1,201 @@ +--- +layout: post +title: Servlet乱码分析 +tags: servlet encode Java +categories: web +--- + +我们知道,web浏览器会将form中的内容打包成HTTP请求体,然后发送到服务端,服务端对请求体解析后可以得到传递的数据。这当中包含两个过程:`encode`与`decode`。 + +* TOC +{:toc} + + +# HTTP + +我们使用ServerSocket搭建一个小服务器来看清http请求的全貌, 该服务器只有一个功能, 就是打印请求体。 + +~~~java +public class HttpPrint { + private ServerSocket serverSocket; + + public HttpPrint() throws IOException { + serverSocket = new ServerSocket(8080); + } + + public void show() throws IOException{ + while(true){ + Socket socket = serverSocket.accept(); + byte[] buf = new byte[1024]; + InputStream is = socket.getInputStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int n = 0; + while ((n = is.read(buf)) > -1){ + os.write(buf,0,n); + } + os.close(); + is.close(); + socket.close(); + System.out.println(os); + } + } + + public static void main(String[] args) throws IOException { + new HttpPrint().show(); + } +} +~~~ + +用html页面来发送get与post请求 + +~~~html +
Test +
+ + +
+~~~ + +启动服务器后,查看打印内容,在我的机器上,请求内容如下: + +get + +![get][get] + +post + +![post][post] + +从post中的`Content-Type:application/x-www-form-urlencoded`可以看到,虽然数据为中文,但是在传递的时候,经过了一次urlEncode,这样一来,在数据交换层面就可以屏蔽编码的不一致性。 + +# UrlEncode + +`urlEncode`的任务是将form中的数据进行编码, 编码过程非常简单, 任何字符只要不是`ASCII`码, 它们都将被转换成字节形式, 每个字节都写成这种形式:一个 "%" 后面跟着两位16进制的数值。 +urlEncode只能识别ASCII码,可以想象的是,那些urlEncode不能识别的字符,也就是十六进制数,一定是依赖于特定的字符集产生的, 字符集包括unicode,iso等。 + +那么浏览器用的是什么字符集呢? 答案是:默认与`contentType`相同, form可以通过属性`accept-charset`指定。 + +例如我们通常可以在jsp中看到这样的设置: + +~~~jsp +<%@page contentType="text/html;charset=UTF-8" %> +~~~ + +或者在html中这样设置: + +~~~html + +~~~ + +这表示浏览器得到响应流之后,用contentType指定的字符集,将流中的字节转换为字符,同样地,也会用这个字符集将页面中字符转换为字节。 + +关于浏览器设定字符集的问题,我们不过多讨论,现在只需要知道有这么个过程就行了, 需要注意的是,无论浏览器使用什么字符集,服务端都是无法获知的。 +这里需要换位考虑一下,浏览器是一个客户端,应该让客户端 "迁就" 服务端, 所以浏览器请求一个服务的时候,应该让浏览器考虑服务端支持什么字符集, 得到了响应后, 用服务端告诉浏览器的字符集进行解析。 + + +# UrlDecode + +现在我们将目光转向Servlet, 并使用上面的html来请求服务,请确保请求的字符集为`unicode`, 应用服务器使用tomcat6。 + +~~~java +public class HttpServletPrint extends HttpServlet{ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println(req.getParameter("param")); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + this.doPost(req,resp); + } +} +~~~ + +get与post结果如下,果然不负众望地乱码了(如果不乱码,我还写个毛?)。 + +![param][param] + +现在我们从Servlet中看看请求体, 修改上面的Servlet代码如下: + +~~~java +public class HttpServletPrint extends HttpServlet{ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + //System.out.println(req.getParameter("param")); + byte[] buf = new byte[1024]; + int n = 0; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + InputStream is = req.getInputStream(); + while ((n = is.read(buf, 0, buf.length))>-1){ + bos.write(buf, 0, n); + } + String param = bos.toString(); + String s = URLDecoder.decode(param,"utf-8"); + System.out.println(s); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + this.doPost(req,resp); + } +} +~~~ + +get与post结果如下,Servlet将http头部解析完成后,将请求体留了下来供应用程序使用, 这是考虑到http请求可能有多种 [enctype][enctype] , 请求体的结构可能不同, +例如,`multipart/form-data`就不是这样的key=value结构,关于multipart/form-data,我在这篇 [fileupload][fileupload] 中曾有过简要分析。 + +![body-utf][body-utf] + +从图中可以看到, 设置了正确的字符集后, 服务端将能够正确地解析, get部分什么都没有,这是因为get没有请求体。 + +让我们换种字符集试试, 比如, 将servet中的"utf-8"换成"iso-8859-1"。 + +![body-iso][body-iso] + +嘿,果然如我所料,而且这个字符串好像很眼熟,和req.getParameter("param")结果是一样的,没错,事实上,tomcat默认的字符集就是`iso-8859-1`, 我们从中可以得到一个推论,tomcat使用默认的字符集,对http请求进行过一次decode。 + + + +# 方案 + +`urlDecode`的任务是将请求中的百分号码转换成字符,显而易见的是,使用与`urlEncode`时相同的字符集才能成功转换。通常的做法是,让服务端支持涵盖多国语言的"utf-8",然后让客户端也用"utf-8"请求服务。 + +指定服务端字符集的方式有两种,一是修改应用服务器的默认编码,二是添加一个过滤器进行编码转换, 方法一最方便, 但是影响了程序的可移植性, 方法二可移植, 它只需要做一件事:`requet.setCharacterEncoding("UTF-8");`, +实际上,该过滤器并没有进行任何编码转换的工作,它仅仅只是一个配置,该配置项将被后续程序使用,这些后续程序包括web服务器内置的解析程序,以及第三方解析工具等。 + +需要注意的是,requet.setCharacterEncoding("UTF-8");,只对请求体有效,也就是说,请求头不归它管,而是由web服务器采用自己配置的字符编码进行解析,此时如果url中包含中文(如get请求的参数),那么将不可避免地出现字符丢失。 +解决办法是在客户端对url进行`encodeURI`**两次**, 然后再在服务端`URLDecoder.decode(param,"utf-8");`。 + +为什么要 [encodeURI][encodeURI-link] 两次?talk is cheap, let's code! + +![encodeURI][encodeURI] + +注意观察这张图片,从中发现了什么? 没错,第一次encodeURI生成了HTTP一节的示例中一样的结果。 +我们在浏览器窗口中输入 "http://localhost:8080/hsp?param=%E4%BD%A0%E5%A5%BD%E5%85%A8%E4%B8%96%E7%95%8C", 会发现它变成了 "http://localhost:8080/hsp?param=你好全世界", +在url里,浏览器认为%是个转义字符,浏览器会把%与%之间的编码,两位两位取出后进行decode, 也就是变回 "你好全世界", 然后再用这个url发送请求, 最终实际发送的内容实际上还是`%E4%BD%A0%E5%A5%BD%E5%85%A8%E4%B8%96%E7%95%8C`。 +换言之,以明文传递的这种url会被浏览器否决一次,再换言之,在js中进行一次encodeURI等于什么都没做。 + +再注意观察第2和第3个输出,有什么规律? 是的,从第二次开始encodeURI只是将`%`变成了`%25`, +根据我们刚才总结出的规律可知,在encodeURI两次的情况下,最后发送到浏览器中的数据为`%25E4%25BD%25A0%25E5%25A5%25BD%25E5%2585%25A8%25E4%25B8%2596%25E7%2595%258C`, +理所当然的,web服务器将使用默认的字符集对其decode, 然而, 无论选择哪种字符集, 将`%25`转换成`%`总是不会出错的, decode之后,`%E4%BD%A0%E5%A5%BD%E5%85%A8%E4%B8%96%E7%95%8C` 将完整地送到Servlet手上。 + +~~~java +System.out.println(URLDecoder.decode(req.getParameter("param"),"utf-8")); +~~~ + +~~~javascript +window.location.href="http://localhost:8080/hsp?param=" + encodeURI(encodeURI('你好全世界')); +~~~ + +![world][world] + +[get]: {{"/servlet-encode/get.png" | prepend: site.imgrepo }} +[post]: {{"/servlet-encode/post.png" | prepend: site.imgrepo }} +[param]: {{"/servlet-encode/param.png" | prepend: site.imgrepo }} +[body-utf]: {{"/servlet-encode/body-utf.png" | prepend: site.imgrepo }} +[enctype]: http://www.w3school.com.cn/tags/att_form_enctype.asp +[body-iso]: {{"/servlet-encode/body-iso.png" | prepend: site.imgrepo }} +[fileupload]: http://blog.rainynight.top/2014-04-15/fileupload/ +[encodeURI]: {{"/servlet-encode/encodeURI.png" | prepend: site.imgrepo }} +[encodeURI-link]: http://www.w3school.com.cn/jsref/jsref_encodeuri.asp +[world]: {{"/servlet-encode/world.png" | prepend: site.imgrepo }} \ No newline at end of file diff --git a/_posts/2015-04-09-rmi.md b/_posts/2015-04-09-rmi.md new file mode 100644 index 0000000..c985326 --- /dev/null +++ b/_posts/2015-04-09-rmi.md @@ -0,0 +1,80 @@ +--- +layout: post +title: RMI +tags: rmi Java +categories: java +--- + +Java RMI 指的是远程方法调用 (Remote Method Invocation)。RMI能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法, 其威力体现在它强大的开发分布式网络应用的能力上,它可以被看作是RPC的Java版本。 + +与WebService相比,RMI编写代码更加简单,在小型应用开发上更加合适,相对地,RMI只能在java中使用,而WebSerce可以跨平台。 + +RMI示意图: + +![rmi][rmi] + +# Demo + +定义接口,必须继承Remote + +~~~java +import java.rmi.Remote; +import java.rmi.RemoteException; + +public interface HelloService extends Remote{ + String sayHello() throws RemoteException; +} +~~~ + +实现接口,必须继承UnicastRemoteObject并在空构造中抛出RemoteException, 方法的返回值必须为原语类型或者序列化类型. + +~~~java +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; + +public class HelloServiceImpl extends UnicastRemoteObject implements HelloService { + + protected HelloServiceImpl() throws RemoteException {} + + @Override + public String sayHello() throws RemoteException { + return "server says. 'Hey'"; + } +} +~~~ + +注册服务 + +~~~java +import java.net.MalformedURLException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; + +public class RegistryBook { + public static void main(String[] args) throws RemoteException, MalformedURLException { + Registry r = LocateRegistry.createRegistry(Registry.REGISTRY_PORT); + r.rebind("HelloService", new HelloServiceImpl()); + } +} +~~~ + +在另一台虚拟机上运行客户端代码,需要获得服务接口文件 + +~~~java +import java.net.MalformedURLException; +import java.rmi.Naming; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; + +public class Client { + public static void main(String args[]) throws RemoteException, MalformedURLException, NotBoundException { + HelloService helloService = (HelloService) Naming.lookup("rmi://localhost:1099/HelloService"); + System.out.println(helloService.sayHello()); + } +} +~~~ + +注意,如果你使用的较为古老的jdk,你可能需要通过jdk内置的`rmic`命令生成`stub`以及`skeleton`文件,并将`stub`文件提供给客户端使用。 + +[rmi]: {{"/rmi.jpg" | prepend: site.imgrepo }} diff --git a/_posts/2015-04-22-design-pattern.md b/_posts/2015-04-22-design-pattern.md new file mode 100644 index 0000000..ae5ed8b --- /dev/null +++ b/_posts/2015-04-22-design-pattern.md @@ -0,0 +1,648 @@ +--- +layout: post +title: 设计模式与原则 +tags: design pattern 设计模式 原则 面向对象 +categories: common +--- + +设计模式的定义:在某情境下,针对某问题的某种解决方案。但是满足此定义的方案并不一定是设计模式,设计模式要求解决方案必须是可复用的。 +设计模式的作用大体上是:优化结构,消除依赖,将面向过程转为面向对象。按照功能,一般可以将设计模式分为`创建型`,`行为型`,`结构型`三大类。 +本文将列举这些设计模式,并对每个设计模式进行简要描述,描述格式为:名称,定义,案例,适用性,结构,效果,应用,相关。 + +* TOC +{:toc} + +设计模式是工程师们从工作中总结出来的经验之谈,这些经验除了设计模式,还有一些设计原则,严格来讲,这些东西都是教条,它告诉我们只要按照规矩来,就不容易犯错。当遇到一特殊情况时,打破原则也没什么大不了的。 + +这些原则包括: + +>单一责任原则:一个类应该只有一个引起变化的原因。 + +>里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。 + +>依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 + +>接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 + +>最少知识原则:一个对象应该对其他对象保持最少的了解。 + +>开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。 + +以及一些叫不上名的原则: + +>找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。 + +>针对接口编程,而不是针对实现编程。 + +>多用组合少用继承。 + +# 创建型 + +## 单例模式 + +**定义** + +确保一个类只有一个实例,并提供一个全局访问点。 + +**案例** + +需要对系统的注册表进行操作,如果同时存在多个注册表对象的话,将无法对并发访问或者临界值进行控制。 + +建立一个注册表获取类,从其静态方法中获取注册表对象,单例采用双重检查法创建。 + +**适用性** + +当对象的操作目标是当前环境中的唯一资源时 + +**结构** + +![Singleton][Singleton] + +**效果** + +将可以进行并发访问控制 + +**应用** + +Runtime + +NumberFormat + +**相关** + +简单工厂 + +## 工厂模式 + +**分类** + +工厂方法,抽象工厂,(简单工厂算工厂方法的特例) + +**定义** + +工厂方法:定义了一个创建对象的接口,但由子类决定需要实例化的类是哪一个。工厂方法将类的实例化推迟到子类。 + +抽象工厂:定义一个接口,用于创建相关或依赖对象的家族,而不用明确指定具体的类。 + +**案例** + +现有系统需要获取一组批萨饼的调料,但是同样的调料在不同的地点会有不同,如海边的海鲜是新鲜的,而内地的海鲜是冷冻的,为了方便扩展,获取的调料不能依赖具体的地点。 + +解决方案:将获取调料的地点抽象,由运行时的具体地点生成一组调料。 + +**适用性** + +当需要依赖运行时上下文来决定生成的产品家族时使用抽象工厂 + +**区别** + +工厂方法:一个抽象产品,可以派生出多种具体产品;一个抽象工厂类,可以派生出多个具体工厂。 + +抽象工厂:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。一个抽象工厂类,可以派生出多个具体工厂类。每个具体工厂类可以创建多个具体产品类的实例。 + + +**结构** + +工厂方法 + +![fatocymethod][factorymethod] + +抽象工厂 + +![absfactory][absfactory] + +**效果** + +能轻松方便地构造对象实例,而不必关心构造对象实例的细节和依赖条件。 + +类数量暴增 + +**应用** + +基本装箱类 + +Collection的iterator() + +log4j + + + +# 行为型 + +## 策略模式 + +**定义** + +定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。 + +**案例** + +存在一个模拟鸭子的程序,现在需要为其增加飞翔的功能,在鸭子的抽象类上增加`fly()`方法后,飞翔能力将传播到所有的子类中。但是,本不应该会飞的 "模型鸭" 也继承了飞翔能力,如果将飞翔能力抽象为 `Flayable`接口,那么随着程序的扩展,那么所有子类都必须重新实现fly()方法,如此一来将出现大量的重复代码,如果已经现存很多扩展类,修改这些类也是很大的工作量。 + +此时的解决方案是:将飞翔行为抽象,并提供几个通用实现,将鸭子的飞翔委托给飞翔行为,在抽象类中增加set方法注册委托,这样的改动不会影响现有的结构。 + +**适用性** + +当类的某个行为随着扩展不断发生变化,而且这种变化只有有限的几种时。 + + + +**结构** + +![Strategy][Strategy] + +**效果** + +积极: + +1.类与行为可以分别扩展,而且扩展类可以自由设置行为,甚至在运行时改变。 + +2.类预置了多个算法,需要要判断调用时上下文从而选择不同算法。策略模式可以消除代码中的 if lese 将选择权交给客户 + + +消极: + +1.客户端必须知道所有的策略类,并自行决定使用哪一个策略类 +2.策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。 + + +**应用** + +`ThreadPoolExecutor`中的`RejectedExecutionHandler` + +`SpringSecurity`中的`AccessDecisionManager` + +**相关** + +模板方法模式 + +命令模式 + +状态模式 + +## 模板方法 + +**定义** + +在一个方法中一定一个算法的骨架,而将某些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 + +**案例** + +现有一个冲泡饮料的算法,泡咖啡和泡茶的步骤基本相同,但是在加调料的这一步有所区别,如何在不需要重写整个冲泡过程的前提下扩展? + +解决方案:将冲泡过程中的放调料步骤抽象出来做成hook,待具体的实现者来补全算法,而不必重写整个冲泡过程。 + +**适用性** + +当算法的整体骨架可被复用,只有其中某个步骤可变时 + +**结构** + +![templatemethod][templatemethod] + +**效果** + +复用算法骨架 + +算法可变步骤中的每一个特例都要为其创建一个新类 + +**应用** + +`Collections.sort` + +**相关** + +与策略模式不同,策略模式定义一个算法家族,算法可以互换,而模板方法定义一个算法大纲,其中个别的步骤可以有不同实现。 + + +## 状态模式 + +**定义** + +允许对象内部状态改变时改变它的行为,对象看起来好像修改了它的类。 + +**案例** + +需要模拟一个自动售货机程序,如果没有投币则只能投币;如果已投币则不能再投币,可以选择退币或者出货;选择出货后如果有货则出货并结束,如果断货则提示断货。 + +可以用大量的 if else 将如上描述写成过程话的判断语句以完成功能,然而现在需要增加一个功能,有十分之一的几率双倍出货。这样一来就需要在每个动作方法内判断当前操作者是不是幸运儿。 + +解决方案:将状态封装成独立的类,并将动作委托到代表当前状态的对象。省去了类中冗长的状态判断。 + +**适用性** + +行为因为上下文的不同而需要随之变化 + +代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。 + +**结构** + +![state][state] + + +**效果** + +它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来 + +它使得状态转换显式化 + +状态对象可被共享 + +状态模式的使用必然会增加系统类和对象的个数。 + +状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 + +**应用** + +java.util.Iterator + +动作游戏连招 + +**相关** + +是策略模式的增强版,区别在于意图,策略模式虽然也可以在运行时改变行为,但是策略模式通常有一个最适合的策略对象;而状态模式需要在多个状态对象中游走,没有所谓的最适合状态。 + + +## 命令模式 + +**定义** + +将请求封装成对象,以便使用不同请求,队列或日志来参数化其他对象,命令模式也支持可撤销的操作。 + +**案例** + +通用遥控器,当目标为电视时,上下键可以是换台,调整音量;当目标为空调时,上下键可以是调温或者调整定时;而且可以方便地扩展以兼容不同电器。 + +解决方案,将遥控器与电器接偶,将遥控器每个按钮抽象为一个命令,由使用者提供命令,遥控器呼叫此命令,由命令自身呼叫真实目标。 + +**适用性** + +当请求的真实目标不明确,或者对请求进行不明确的处理时(如队列,宏命令,日志记录等)。 + +**结构** + +![cmd][cmd] + +**效果** + +积极的: + +降低系统的耦合度:Command模式将调用操作的对象与知道如何实现该操作的对象解耦。 + +组合命令:你可将多个命令装配成一个组合命令,即可以比较容易地设计一个命令队列和宏命令。一般说来,组合命令是Composite模式的一个实例。 + +增加新的Command很容易,因为这无需改变已有的类。 + +可以方便地实现对请求的Undo和Redo。用栈存储操作序列,可以实现多层次撤销。 + +消极的: + +导致某些系统有过多的具体命令类 + +**应用** + +java.lang.Runnable + +javax.swing.Action + +游戏中的自定义键位 + +**相关** + +策略模式聚焦的是对相同请求更换解决方案的灵活性;而命令模式聚焦的是对多请求变化的封装以及对相同请求不同的请求形式解决方法的可复用性 + +组合模式 + +## 观察者模式 + +**定义** + +定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,所有所有依赖者都会接到通知并自动更新 + +**案例** + +需要在展板上实时显示当前温度,温度数据来自温度计。初步方案1,是定时询问温度计然后更新数据,这样就需要再开一个定时器线程,如果以后会有更多的功能需要获取温度计数据,那么线程数量将失去控制。数步方案2是当温度计数据发生变化时,直接通知这些数据请求者,但是当有新的请求者时,需要打开温度计类增加一个请求者。 + +结局方案:将需要获取温度计数据的请求者抽象,然后注册到温度计中形成一个列表,每当温度计数据发生变化,就依次通知这些注册的对象。 + +**适用性** + +当一个抽象模型有两个方面, 其中一个方面依赖于另一方面的状态。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。 + +当对一个对象的改变需要同时改变其它对象 , 而不知道具体有多少对象有待改变。 + +当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之 , 你不希望这些对象是紧密耦合的。 + + +**结构** + +![Observer][Observer] + +**效果** + +Observer模式允许你独立的改变目标和观察者 + +观察者模式可以实现表示层和数据逻辑层的分离 + +支持广播通信 + + +**应用** + +java.util.EventListener + +**相关** + +终结者模式 + +单例模式 + +## 迭代器模式 + +**定义** + +提供一种方法顺序访问聚合对象中的每个元素,而又不暴露其内部表示。 + +**结构** + +![Iterator][Iterator] + +**效果** + +封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。 + +增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加 + +**应用** + +java.util.Iterator +java.util.Enumeration + + +**相关** + +组合模式,常用来便利组合模式的对象图 + +备忘录模式 + +工厂方法模式,迭代器对象由实现类提供 + +# 结构型 + +## 装饰者模式 + +**定义** + +动态地将职责附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。 + +**案例** + +模拟一个冲泡咖啡的程序,调料种类未知,而且以后可能增减,每种调料价格不同,客户可自行选择调料份量,现在需要计算该咖啡的价格。初步方案是列出所有可能的组合,然后计算组合的价格,但是如果调料种类较多,那么组合将会爆炸,考虑到调料的份量有无限种可能,列举组合方式不可行。 + +解决方案:将咖啡抽象,将每种调料对应一种咖啡,如加奶的咖啡,加摩卡的咖啡等,这些调料的咖啡成分由客户自己提供。 + +**适用性** + +当需要对一个对象以可选的方式增强功能时 + +当无法使用继承的方式增强功能时 + +**结构** + +![decorater][decorater] + +抽象组件角色(Component):定义一个对象接口,以规范准备接受附加责任的对象, + +即可以给这些对象动态地添加职责。 + +具体组件角色(ConcreteComponent) :被装饰者,定义一个将要被装饰增加功能的类。 + +可以给这个类的对象添加一些职责 + +抽象装饰器(Decorator):维持一个指向构件Component对象的实例, + +并定义一个与抽象组件角色Component接口一致的接口 + +具体装饰器角色(ConcreteDecorator):向组件添加职责。 + +**效果** + +运行时扩展现有对象,比继承灵活。 + +产生很多小类 + + +**应用** + +java.io.BufferedInputStream(InputStream) + +java.io.DataInputStream(InputStream) + +java.io.BufferedOutputStream(OutputStream) + +java.util.zip.ZipOutputStream(OutputStream) + +java.util.Collections# checkedList|Map|Set|SortedSet|SortedMap + + +**相关** + +可与工厂模式和生成器模式配合使用 + +Decorator模式不同于Adapter模式,因为装饰仅改变对象的职责而不改变它的接口;而适配器将给对象一个全新的接口。 + +Strategy模式:用一个装饰你可以改变对象的外表;而Strategy模式使得你可以改变对象的内核。这是改变对象的两种途径。 + + + +## 适配器模式 + +**定义** + +将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。 + +**案例** + +插座有插头型号对不上,用适配器抓换下。 + +**适用性** + +想使用一个已经存在的类,而它的接口不符合需求时。 + +**结构** + +类适配器 + +![adapter1][adapter1] + +对象适配器 + +![adapter2][adapter2] + +**应用** + +java.util.Arrays# asList() + +java.io.InputStreamReader(InputStream) + +java.io.OutputStreamWriter(OutputStream) + +**相关** + +类似装饰者,而者都是包装对象,但是装饰者从不转换接口,只是增加职责,而适配器正是转换接口。 + +外观模式 + +## 代理模式 + +**定义** + + 为另一个对象提供一个替身或占位符以控制对这个对象的访问 + +**案例** + +需要编写一个类来监控远程服务器上的一些数据,但是希望这个类易于使用。结局方案,将远程服务上的数据抽象成一个接口,用RMI进行通信,看起来就好象直接操纵了远程对象。 + +**适用性** + +1) 远程代理(Remote Proxy)为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador) + +2) 虚拟代理(Virtual Proxy)根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 + +3) 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。 + +4) 智能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。 + +5) Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。 + + +**结构** + +![proxy][proxy] + +**效果** + +封装实现细节,只暴露数据抽象 + +延迟加载 + +权限控制 + +**应用** + +java.lang.reflect.Proxy + +RMI + +**相关** + +与装饰者很类似,然而目的不同,装饰者增加行为,代理模式控制访问 + + +## 外观模式 + +**定义** + +提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。 + +**案例** + +程序由多个子系统联合起来工作,而且这些子组件需要使用者自己组装,这样使用起来会很复杂,而且客户与子组件耦合较高。 + +解决方案,提供一套简化的接口,客户端可以直接操作此接口,而无需理会复杂的系统结构。 + +**适用性** + +当需要为一个复杂子系统提供一个简单接口时。 + + +**结构** + +![facade][facade] + +**效果** + +对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。 + +只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。 + +不能很好地限制客户使用子系统类 + +增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。 + +**应用** + +java.lang.Class + +**相关** + +适配器模式 + +## 组合模式 + +**定义** + +允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。 + +**案例** + +菜单中有很多道菜,而且其中甜点是个子菜单,现在需要打印整个菜单。 + +解决方案是:将子菜单与总顶层菜单的接口统一起来,都为Menu类型,大Menu内部维护菜单列表以及子Menu。 + +**适用性** + +想表示对象的部分-整体层次结构 + +希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。 + +需要注意的是,虽然通常情况下组合模式以树形结构体现,但其实不是一回事,组合模式拥有更抽象的语义,它是一种整体与部分一致的抽象。 + +**结构** + +![composive][composive] + +**效果** + +定义了包含基本对象和组合对象的类层次结构 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。 + +简化客户代码 客户可以一致地使用组合结构和单个对象。 + +使得更容易增加新类型的组件 + +更难以排除特定类型的组件 + +**应用** + +javax.swing.JComponent# add(Component) + +java.awt.Container# add(Component) + +java.util.Map# putAll(Map) + +java.util.List# addAll(Collection) + +java.util.Set# addAll(Collection) + +**相关** + +可与迭代器模式搭配使用 + + + + +[Strategy]: http://my.csdn.net/uploads/201205/11/1336732187_4598.jpg +[Singleton]: http://my.csdn.net/uploads/201204/26/1335439688_4086.jpg +[factorymethod]: http://hi.csdn.net/attachment/201203/15/0_1331817716F3IJ.gif +[absfactory]: http://my.csdn.net/uploads/201204/25/1335357105_2682.jpg +[templatemethod]: http://my.csdn.net/uploads/201205/14/1336965093_1048.jpg +[state]: http://my.csdn.net/uploads/201205/11/1336719144_5496.jpg +[cmd]: http://my.csdn.net/uploads/201205/09/1336547877_9980.jpg +[Observer]: http://my.csdn.net/uploads/201205/11/1336707179_9037.jpg +[Iterator]: http://my.csdn.net/uploads/201205/28/1338213169_8415.jpg +[decorater]: http://my.csdn.net/uploads/201205/03/1336034007_4657.jpg +[adapter1]: http://my.csdn.net/uploads/201205/02/1335939433_8937.jpg +[adapter2]: http://my.csdn.net/uploads/201205/02/1335939552_3860.jpg +[proxy]: http://my.csdn.net/uploads/201205/07/1336371130_8874.jpg +[facade]: http://my.csdn.net/uploads/201207/05/1341473411_7539.JPG +[composive]: http://my.csdn.net/uploads/201205/03/1336015104_5713.jpg \ No newline at end of file diff --git a/_posts/2015-05-07-js-summary.md b/_posts/2015-05-07-js-summary.md new file mode 100644 index 0000000..150db74 --- /dev/null +++ b/_posts/2015-05-07-js-summary.md @@ -0,0 +1,12 @@ +--- +layout: post +title: Javascript 总结 +tags: Javascript script +categories: front-end +--- + +超级长图,小心流量。 + +![javascript] [javascript] + +[javascript]: {{"/JavaScriptMindMap.jpg" | prepend: site.imgrepo }} \ No newline at end of file diff --git a/_posts/2015-05-21-the-scheme-eval.md b/_posts/2015-05-21-the-scheme-eval.md new file mode 100644 index 0000000..0844361 --- /dev/null +++ b/_posts/2015-05-21-the-scheme-eval.md @@ -0,0 +1,371 @@ +--- +title: scheme 解释器 +tags: lisp scheme eval +categories: lisp +--- + +前段时间针对 [scheme][scheme] 语言写了一个解释器,现在就 fork 一下当时想法,整理一下其中的脉络,做一个思维快照,以期下次用C语言来实现时可以顺利地进行。 +成品在此:[scheme-bootstrap][scheme-bootstrap]。 + +* TOC +{:toc} + +# 词法作用域 + +一条语句,在不同的函数当中可能具备不同的含义,其中变量的值取决于具体的作用域,所以对一条语句进行解释时需要考虑当时的上下文。 +然而lisp是一种函数式的编程语言,函数可以被当成数据来进行传递,这会产生一个问题,如果函数体中存在自由变量,那么这个函数的行为将产生不可控的变化, +问题重点不是变化,而是不可控,在语言的使用者不需要函数行为变化的时候,自由变量会从外部作用域取值,自动产生行为的变化。 +这种作用域叫做动态作用域 (Dynamic scope )。 + +像java,javascript之类的语言,都是在对象内部维护一个成员变量,函数中自由的局部变量根据这个成员变量的值来确定行为,虽然函数的行为都可变,但它是可控的。 +为了实现可控,我们需要让语言满足人的直觉,即作用域和手写的代码结构相同,即使一个函数会被传递,其内部变量的值也应当追溯到函数定义时的作用域中。 +这种作用域就叫做词法作用域 (Lexical scope)。 + +显然,实现词法作用域比动态作用域复杂些。实现动态作用域时,函数传递只要传递函数文本,然后插入到调用语句中进行解释就行了; +而词法作用域需要为每个被传递的函数绑定一个定义时的作用域,通常将被传递函数体与定义时的作用域打包成一个数据结构,这个结构叫做`闭包`。 +javascript就是使用闭包传递函数的。 + +# 抽象语法树 + +语言被写下来之后只是一个文本,其内容是对某种数据结构的形式化描述,在对语言进行解释前,必须先对这个文本进行解析,让其描述的结构出现在内存中,这种结构叫做抽象语法树 (AST)。 +这个过程比较乏味,无非是对字符串进行扫描,发现嵌套括号就将其作为树中的一个节点。为了跳过这个过程,我选择用scheme来写解释器,lisp 天生就适合进行这样的表处理。 +类似这样用自己来解释自己的行为叫做自举。 + +# 语法 + +解释器逐行解释语句,那么碰到不同的语法关键字时应当触发不同的解释行为。 + +- **自求值表达式** + +当碰到数字和字符串时,表示这是一个自求值表达式,即写下的文本就是值。 + +- **quote** + +```scheme +(quote foo) +``` +引号表达式的值是去掉引号后的内容,其中 `'foo` 等价于 `(quote foo)`。 + +- **begin** + +```scheme +(begin s1 s2) +``` + +begin 表达式表示其内容是顺序的多条语句,某些表达式只能支持单一的语句,用begin包裹这些语句后,就能当做一条语句使用。 + +- **if** + +```scheme +(if ) +``` + +对if的处理是非常容易的,解释器先对 `predicate` 求值,如果值为真则对 `consequent` 求值,否则对 `alternative` 求值。 + +- **lambda** + +```scheme +(lambda (x y) + (+ x y)) +``` + +lambda 表达式表示一个函数,对其求值就是将函数体,形参列表,作用域打包。 + +- **define** + +```scheme +(define var Foo) +``` + +define 表达式表示定义,处理方式是求出 `Foo` 的值然后与 `var` 绑定,放入到当前作用域中。这个过程中隐含了对函数的定义,因为 `Foo` 可能就是一个lambda。 + +- **set!** + +```scheme +(set! var Foo) +``` + +set! 语句提供了修改变量值的能力,如果没有这个关键字,scheme 就是纯粹的函数式语言,其函数将不会产生任何副作用,函数的行为将不可变。 +其处理方式类似于define,区别是一个新增,一个修改。需要注意 define 并不能代替 set!, define 定义的变量只能在某个作用域内屏蔽外部的同名变量; +而set!将会沿着作用域链一直向上寻找匹配的变量名,然后进行修改。 + +- **变量** + +```scheme +foo +``` + +对变量求值的过程与 set! 类似,解释器将沿着作用域链一直向上寻找匹配的变量名,然后取出变量的值。 + +- **函数调用** + +```scheme +(plus a b) +((lambda (x y) + (+ x y)) a b) +``` + +函数调用看似有两种形式,其实本质是一种,对变量 `plus` 的求值和对 `lambda` 的求值都将得到一个闭包,所以函数求值的真实过程是,求参数的值, +将参数传递给闭包,然后求闭包的值。对闭包求值的过程为,扩展作用域,将参数值与对应的形参名绑定并放入作用域,这个过程类似于define, +然后返回闭包中的函数过程体,该过程体为一条或多条语句,每条语句都需要被进行解释,这便产生一个递归的解释过程。 + + + + + +# 基本操作和值 + +函数并不能从无到有定义出来,其过程总会使用一些其他的函数,例如加法,那么加法从何而来?事实上,这些非常基本的操作都是无法直接定义出来的, +它们需要从解释器中直接映射的。为了实现方便,以下操作都直接从解释器中映射: +- `+` `-` `*` `/` (其实减乘除可以用加法来实现,但是没这个必要), +- `eq?` +- `car` +- `cdr` +- `cons` + +scheme中函数与数据的界限非常模糊,car cdr cons等看似基本的操作其实可以用函数来实现。 + +```scheme +(define (cons x y) + (define (dispatch tag) + (cond [(= tag 0) x] + [(= tag 1) y])) + dispatch) + +(define (car z) + (z 0)) + +(define (cdr z) + (z 1)) +``` + +基本值 + +- `true` +- `false` + +# 语法糖 + +语法糖并非增加什么新功能,而是让某些以有的功能写起来更舒服。对语法糖的处理也比较简单,就是将其结构变化成解释器能够认识的形式,然后发送给解释器重新解释。 + +- **define** + +```scheme +(define (foo bar) + ) +``` + +该表示法为函数定义的语法糖,用基本的define定义函数时,每次都要写出lambda,比较繁琐。 +该表示法等价于如下形式 + +```scheme +(define foo (lambda (bar) + )) +``` + +- **cond** + +```scheme +(cond [ ] + [ ] + [else e ]) +``` + +cond 表达式类似于常见的switch结构,但是每个case自带break,所以无法进入多个case。cond 可以转换成嵌套的if,然后将转换后的表达式转发给解释函数重新解释。 + +```scheme +(if p1 + e1 + (if p2 + e2 + e)) +``` + +- **and** + +```scheme +(and c1 c2) +``` + +短路的逻辑与可以用嵌套的if来实现 + +```scheme +(if c1 + (if c2 + true) + false) +``` + +- **or** + +```scheme +(or c1 c2) +``` + +短路的逻辑或也可以用嵌套的if来实现 + +```scheme +(if c1 + true + (if c2 + true)) +``` + + +## let,let*,letrec + +这三个语法糖提供了三种不同的方式来定义作用域,这三种表示法的作用各不相同,它们都建立在lambda的基础上。 + +- **let** + +```scheme +(let ([ ] + [ ]) + ) +``` + +let 表达式提供了定义一个作用域并绑定**互斥**变量的功能,var1 与 var2 在语义上没有先后之分,也不能相互访问。 +let 表达式等价于如下如下形式,这是一个普通的函数调用。 + +```scheme +((lambda ( ) + ) + +) +``` + +- **let\*** + +```scheme +(let* ([ ] + [ ]) + ) +``` + +let\* 表达式提供了定义一个作用域并**先后**绑定变量的功能,var1 与 var2 在语义上存在先后之分,var2 可以访问 var1,而 var1 不能访问 var2。 +let\* 表达式等价于如下形式 + +```scheme +(let ([ ]) + (let ([ ]) + )) +``` + +- **letrec** + +```scheme +(letrec ([ ] + [ ]) + ) +``` + +letrec 表达式提供了定义一个作用域并**同时**绑定变量的功能,var1 与 var2 在语义上为同时定义,var2 可以访问 var1,且 var1 可以访问 var2, +letrec 的存在意义在于屏蔽外部同名变量,假定当前作用域外部存在一个变量 `var2`,那么let和let\* 中的var1求值时如果需要访问`var2`,那么将会访问这个外部的`var2`, +而letrec不同,如果letrec的var1求值是需要访问var2,那么这个var2的值**同一作用域内**的那个`var2`的值。 +letrec 表达式等价于如下形式 + +```scheme +(let ([ **undefined**] + [ **undefined**]) + (set! var1 ) + (set! var2 + ) +``` + +解释器在对变量求值时会检查变量的值,如果其值为一个未定义标记,则会提示未定义错误。 + + +# 内部定义 + +函数的内部可以嵌套地使用define语句,但是define在写成文本时是存在先后的,但是函数内部定义的语义应当是同时定义,所以在对lambda进行解释时需要一些调整, +调整内容如下, +- 扫描出lambda体内的所有define语句,只扫描内部定义的第一层无需嵌套,然后将define的变量名和值表达式(无需求值)装配成`letrec`的kv绑定形式; +- 扫描出lambda内部的所有非定义语句将这个序列作为`letrec`的`body`; +- 用上面得到的两个部分组成一个`letrec`; +- 用新得到的letrec作为body构造一个新的 lambda 来替换原来的lambda。 + +上文描述的含义为: + +```scheme +(lambda (foo) + (define a ) + (define b ) + ) +``` + +等价于如下形式 + +```scheme +(lambda (foo) + (letrec ([a ] + [b ]) + )) +``` + +# 惰性求值 + +为了实现惰性求值,需要提供一种延迟计算一个表达式的能力,实现方式有两种,一种是将解释器改造成惰性求值解释器, +另一种是用一对关键字 `delay` `force` 提供局部的惰性和强制求值能力。 + +- **惰性求值解释器** + +对任何一个表达式求值时,都不直接求值表达式而是创建一个代理的数据结构来包裹表达式和求值时的环境,这个代理可以作为该表达式值的许诺进行传递。 +当需要使用表达式的值时,再对这个代理进求值,解释器并不会完整地求出整个表达式的值,其实这个值依旧是一个代理,解释器很懒,最多只工作到刚刚满足需求的程度。 + +理论上来讲,惰性求值的效率应当高过普通的求值器,但是它有个缺陷,惰性求值与赋值语句配合使用时经常会出现意料之外的情况,因此 `haskell` 这种惰性求值的语言同时也是纯函数式的语言。 + +因为赋值产生问题例子如下,其函数的返回值并不会发生任何变化,因为赋值表达式被惰性了,并没有真正执行。 +要解决这个问题,必须提供一个机制让语言使用者主动地强制执行该表达式。 + +```scheme +(define (f x) + (set! x 998) + x) +``` + +- **delay/force** + +用这种方式提供惰性功能并没什么理论上的变化,只是让普通的解释器增加两个case, 在碰到delay时生成代理,碰到force时强制执行。其优点是简单易用,同时缺点也非常明显, +必须为高阶函数提供两个版本,一个版本普通,一个版本惰性,而且在使用惰性功能时,代码中会遍布这两个关键字。 + +在我写的解释器中用**delay/force**的方式提供了惰性求值的功能。 + +# 尾调用优化 + +scheme语言没有循环,循环的本质就是在递归时不断对一个变量进行读写,这个变量作为终止条件便可以控制递归的次数,在没有循环时,可以用在递归函数中传递这个变化的因子来代替。 +这种递归比较特殊,它在栈中只占一帧,并不会在栈空间累积数据,要使递归能够代替循环,必须进行尾调用优化。 + +因为我的解释器是自举的,scheme语言本身提供了对尾调用的优化,在对递归的函数进行解释时,保存当前一帧中的重要数据的负担被转嫁到了解释器中, +如果编写解释器的语言恰好也支持尾调用优化,这一负担会消弭于无形。但是如果编写解释器的语言没有尾调用优化呢?那我们就需要自己处理这种优化。 + +处理尾调用优化比较复杂,需要将解释器写成寄存器风格,用栈来保存寄存器中的历史值。 +在解释一个表达式前,将寄存器值入栈,解释一个表达式后,栈中内容弹出到寄存器中,解释一个表达式的值不会在栈中留下任何数据。 +如果表达式中存在子表达式,那么这个过程也适用于子表达式,显而易见,子表达式解释前的数据入栈时将压在父表达式的数据之上。 +如果子表示的嵌套层数太深,数据将有可能撑满整个栈,递归函数通常极有可能嵌套太深。 + +基于这种解释器模型,可以选择让表达式序列中的最后一条语句在解释之前数据不入栈,这最后一条语句执行完毕后,直接修改寄存器,数据也不必出栈。 +如果最后一条语句仅仅为一个函数调用,那么这个语句将不会在栈中累积数据,即使这个函数是递归的也同样如此。 + + +# 垃圾回收 + +垃圾回收的目标是函数执行时扩展的作用域以及函数的闭包,判别一段内存是否可回收的方式是枚举根节点。 +从寄存器中的指针出发,经过一系列car,cdr能达到的对象,有可能影响未来的计算过程,那些不能达到的都是可回收的垃圾。 + +如果采用**停止并复制**的算法进行回收,其流程大致如下: + +- GC开始之前,将所有寄存器内容保存到一个预先分配好的表里,并让root寄存器指向这个表的顶部序对。 + +- GC时,先从root表开始,一个一个地复制序对,原内存中被复制的序对,car标记为“破碎的心”,cdr放置一个前向指针,指向序对复制后的新位置。 + +- 状态控制,free,scan。 +free 指向下一段可用内存,内存分配时递增的地址就是通过它来实现,通过他可以知道当前内存的使用状态。 + +- GC循环,scan初始指向一个新内存区的对象A,而该对象car,cdr仍指向旧内存区的对象,假定scan正在扫描A对象的car,此时需要检查car指向的对象是否已被复制。 +如果未复制就将其复制到free所指的位置并更新free,同时在旧对象做标记,然后更新car使其指向复制后的对象。如果car指向的对象已被复制,则car更新为将该旧对象的cdr中的前向指针。 +当scan超过free时GC结束。 + + + +[scheme]:http://baike.baidu.com/link?url=wgd84RHmek_qWtVHP9uhUL97pPelbW1iiUjF39rRuIrSHeG5ekDywMoiyWXDFgkaz3sdkYS2TRXs29CzMa7paa +[scheme-bootstrap]:https://github.com/dubuyuye/scheme-bootstrap \ No newline at end of file diff --git a/_posts/2017-05-27-mvc-validator.md b/_posts/2017-05-27-mvc-validator.md new file mode 100644 index 0000000..920baf5 --- /dev/null +++ b/_posts/2017-05-27-mvc-validator.md @@ -0,0 +1,56 @@ +--- +title: 记一次在spring-mvc中踩坑的经历 +tags: spring mvc java +categories: java +--- + +今天打算在项目中加入数据验证功能,具体可参考 [数据验证][validator] + +过程无须赘述 + +配置完成后,奇怪的现象发生了,`BindingResult.hasErrors()`永远返回false +![hasErrors][hasErrors] + +这是什么情况? validator 未指定?我赶紧检查配置: +![notNull][notNull] + +![driven][driven] +配置没有问题,日志能够正常打印,且没有错误,排除类加载失败的可能。 + +上google搜索一下! +![google][google] +翻看了第一页的回复,大多来自stackoverflow,无非是让你引入包, 加配置, 我项目里配置符合这些回复的要求, 所以需要另想办法 + +自己动手吧!我加入如下代码,以验证validator是否被正确加载 +![initBinder][initBinder] +validator果然为null, 且如果拿到validator对象并set进去,就可以解决`BindingResult.hasErrors()`永远返回false的问题, +然而,这种处理方式需要在每个Controller中写一遍initBinder, 这种方式并不完美。 + +是什么原因造成validator丢失呢? +我们知道`annotation-driven`会在spring中自动注册多个bean, 详情请移步[自动注册][自动注册] +会不会是项目中存在某些配置覆盖了其中的bean,造成了validator丢失? +仔细扫描一下配置文件, 发现果然如此, 项目中为了更换json转换器, 自定义了一个bean +![adapter][adapter] + +到了这个地步基本上找到原因了,解决方式很简单,将validator绑定起来就可以了 +![validator-config][validator-config] + + + + + + + +[validator]:http://jinnianshilongnian.iteye.com/blog/1733708 +[自动注册]:https://my.oschina.net/HeliosFly/blog/205343 +[hasErrors]:{{"/mvc-validator/hasErrors.jpg" | prepend: site.imgrepo }} +[validator-config]:{{"/mvc-validator/validator-config.jpg" | prepend: site.imgrepo }} +[google]:{{"/mvc-validator/google.jpg" | prepend: site.imgrepo }} +[initBinder]:{{"/mvc-validator/initBinder.jpg" | prepend: site.imgrepo }} +[adapter]:{{"/mvc-validator/adapter.jpg" | prepend: site.imgrepo }} +[driven]:{{"/mvc-validator/driven.jpg" | prepend: site.imgrepo }} +[notNull]:{{"/mvc-validator/notNull.jpg" | prepend: site.imgrepo }} + + + + diff --git a/_posts/2018-03-15-FullGc.md b/_posts/2018-03-15-FullGc.md new file mode 100644 index 0000000..7f20fbc --- /dev/null +++ b/_posts/2018-03-15-FullGc.md @@ -0,0 +1,164 @@ +--- +title: 大对象引起的频繁FULL GC +tags: java jvm +categories: java +--- + +最近发现了频繁FULL GC的情况,查看GC日志后,发现每次FULL GC后,老年代都能回收大半以上的空间,这意味着有很多临时对象被分配到了老年代。 + +用`jmap`命令导出堆转储文件,然后用jvisualvm导入后进行分析,发现char[]占据了最多了内存。 + +其中有大量的对象达到了3M,被直接担保分配进了老年代,因为暂时不知道这些对象是怎么产生的,最快的解决办法就是增大担保分配的阈值。 + +下面就是测试过程 + +~~~java +/** + -server + -XX:+UseConcMarkSweepGC + -Xms20M + -Xmx20M + -Xmn10M + -XX:SurvivorRatio=8 + -XX:PretenureSizeThreshold=2000000 + -XX:+PrintGC + -XX:+PrintGCDetails + -XX:+PrintGCDateStamps + -XX:+PrintHeapAtGC + -Xloggc:/var/logs/PretenureSizeThreshold.log + -XX:+HeapDumpOnOutOfMemoryError + -XX:HeapDumpPath=/var/dumps/PretenureSizeThreshold.hprof + */ +public class PretenureSizeThreshold { + + private static final int _1MB = 1000 * 1000; + + public static void main(String[] args) throws Exception{ + RuntimeMXBean mxBean = ManagementFactory.getRuntimeMXBean(); + System.out.println(mxBean.getName()); + String pid = mxBean.getName().split("@")[0]; + + byte[] allocation = new byte[3*_1MB]; + + while (true){ + Thread.sleep(1000); + } + + } +} +~~~ + +~~~text +D:\Doc\MyRepo\learning-java>jmap -heap 14000 +Attaching to process ID 14000, please wait... +Debugger attached successfully. +Server compiler detected. +JVM version is 25.121-b13 + +using parallel threads in the new generation. +using thread-local object allocation. +Concurrent Mark-Sweep GC + +Heap Configuration: + MinHeapFreeRatio = 40 + MaxHeapFreeRatio = 70 + MaxHeapSize = 20971520 (20.0MB) + NewSize = 10485760 (10.0MB) + MaxNewSize = 10485760 (10.0MB) + OldSize = 10485760 (10.0MB) + NewRatio = 2 + SurvivorRatio = 8 + MetaspaceSize = 21807104 (20.796875MB) + CompressedClassSpaceSize = 1073741824 (1024.0MB) + MaxMetaspaceSize = 17592186044415 MB + G1HeapRegionSize = 0 (0.0MB) + +Heap Usage: +New Generation (Eden + 1 Survivor Space): + capacity = 9437184 (9.0MB) + used = 2748888 (2.6215438842773438MB) + free = 6688296 (6.378456115722656MB) + 29.128265380859375% used +Eden Space: + capacity = 8388608 (8.0MB) + used = 2748888 (2.6215438842773438MB) + free = 5639720 (5.378456115722656MB) + 32.7692985534668% used +From Space: + capacity = 1048576 (1.0MB) + used = 0 (0.0MB) + free = 1048576 (1.0MB) + 0.0% used +To Space: + capacity = 1048576 (1.0MB) + used = 0 (0.0MB) + free = 1048576 (1.0MB) + 0.0% used +concurrent mark-sweep generation: + capacity = 10485760 (10.0MB) + used = 3000016 (2.8610382080078125MB) + free = 7485744 (7.1389617919921875MB) + 28.610382080078125% used + +1815 interned Strings occupying 162040 bytes. +~~~ +老年代占用的空间为3000016,这表示数组通过担保分配进入了老年代,多出来的16是对象头的体积 + +修改启动参数`-XX:PretenureSizeThreshold=4000000` + +~~~text +D:\Doc\MyRepo\learning-java>jmap -heap 2356 +Attaching to process ID 2356, please wait... +Debugger attached successfully. +Server compiler detected. +JVM version is 25.121-b13 + +using parallel threads in the new generation. +using thread-local object allocation. +Concurrent Mark-Sweep GC + +Heap Configuration: + MinHeapFreeRatio = 40 + MaxHeapFreeRatio = 70 + MaxHeapSize = 20971520 (20.0MB) + NewSize = 10485760 (10.0MB) + MaxNewSize = 10485760 (10.0MB) + OldSize = 10485760 (10.0MB) + NewRatio = 2 + SurvivorRatio = 8 + MetaspaceSize = 21807104 (20.796875MB) + CompressedClassSpaceSize = 1073741824 (1024.0MB) + MaxMetaspaceSize = 17592186044415 MB + G1HeapRegionSize = 0 (0.0MB) + +Heap Usage: +New Generation (Eden + 1 Survivor Space): + capacity = 9437184 (9.0MB) + used = 5748624 (5.4823150634765625MB) + free = 3688560 (3.5176849365234375MB) + 60.91461181640625% used +Eden Space: + capacity = 8388608 (8.0MB) + used = 5748624 (5.4823150634765625MB) + free = 2639984 (2.5176849365234375MB) + 68.52893829345703% used +From Space: + capacity = 1048576 (1.0MB) + used = 0 (0.0MB) + free = 1048576 (1.0MB) + 0.0% used +To Space: + capacity = 1048576 (1.0MB) + used = 0 (0.0MB) + free = 1048576 (1.0MB) + 0.0% used +concurrent mark-sweep generation: + capacity = 10485760 (10.0MB) + used = 0 (0.0MB) + free = 10485760 (10.0MB) + 0.0% used + +1815 interned Strings occupying 162040 bytes. + +~~~ +此时,老年代占用0,这表示数组没有达到大对象的标准,直接分配在了新生代 diff --git a/_posts/2018-03-26-large-file-diff.md b/_posts/2018-03-26-large-file-diff.md new file mode 100644 index 0000000..c34a6ff --- /dev/null +++ b/_posts/2018-03-26-large-file-diff.md @@ -0,0 +1,105 @@ +--- +title: 大文件内容对比 +tags: java 算法 algorithm 排序 +categories: algorithm +--- + +* TOC +{:toc} + +最近接到一个需求,要对两个系统的订单进行对比,找出其中的差异,对比结果有4种:一致、不一致、丢失、多余。 + +如果数据量少,处理起来就很简单,例如要对A,B两份数据进行对比: +1. 将数据A放入哈希表 +2. 遍历数据B,依据id从A中查找对应的订单 +3. 若从A中找到了对应的订单,则比较是否一致,并将此订单标记为已匹配 +4. 若从A中找不到对应的订单,则表示A丢失此订单 +5. 遍历A,筛选出所有未匹配的订单,这些订单都是A中多余的 + +但是如果数据量超大,在内存中处理就不合适了,数据需要放在磁盘上。 +基于哈希的对比算法,无法避免大量对磁盘的随机访问,因而无法使用buffer,完全的io读写性能太差,所以这个方案是不行的。 + +如果AB两个集合是有序的,那么对比将会变得容易,一次遍历就可以对比完成,例如以id排序 +1. 读取A指针对应的订单,读取B指针对应的订单 +2. 若两个订单id相等,则对比内容是否一致 +3. 若A指针订单id>B指针订单id,代表A中缺少B指针订单,记录结果,B指针移动到后一个 +4. 若A指针订单id排序->合并的思路 +1. 按行读取文件,将读到的行放入list +2. 如果已读取的行达到了1万行,对list进行排序,将排序后的list逐行写入临时文件 +3. 打开多个临时文件,从每个文件的中逐行读取,选取其中订单id最小的行,写入新的临时文件 +4. 循环以上步骤,直至剩下一个文件 + + 示例代码 + ~~~java + while (true){ + line = br.readLine(); + if(line != null){ + chunkRows.add(line); + } + if(line == null || chunkRows.size() >= initialChunkSize){ + if(chunkRows.size() > 0){ + chunkRows.sort(comparator); + Chunk chunk = initialChunk(rowNum, chunkRows, file); + chunkList.add(chunk); + rowNum += chunkRows.size(); + chunkRows.clear(); + } + } + if(line == null){ + break; + } + } + ~~~ + + ~~~java + while (true){ + List pollChunks = pollChunks(chunkQueue, currentLevel); + if(CollectionUtils.isEmpty(pollChunks)){ + currentLevel++; + continue; + } + //合并 + Chunk mergedChunk = merge(pollChunks, original); + //chunkQueue 中没有后续的元素,表示此次合并是最终合并 + if(chunkQueue.size() == 0){ + chunkQueue.add(mergedChunk); + break; + } else { + chunkQueue.add(mergedChunk); + } + } + ~~~ + + [完整代码](完整代码) + +[完整代码]:https://github.com/bit-ranger/architecture/blob/93c189b0cc69f41dc9b030f75c812388e2e20d61/core/src/main/java/com/rainyalley/architecture/core/arithmetic/sort/FileSorter.java + + +# 测试 + +在最后的多路合并中要考虑使用多少路进行合并,更少的路数,将产生更多的临时文件 + +拆分时需要考虑每个块的大小,过大的块将造成频繁的FULL GC + +IO的缓冲容量区也要考虑,这将影响IO读写的速度,以及FULL GC的频率 + +以如下配置为例,在我的个人电脑上, +cpu频率3.40GHZ,磁盘顺序读450MB/s,随机读190MB/s,顺序写250MB/s,数据宽度20为20字符 +对1千万条数据进行排序,耗时13秒。 + +~~~java +FileSorter sorter = new FileSorter((p,n) -> p.compareTo(n), + 8, 50000, 1024*1024*2, new File("/var/tmp/fileSorter"), false); +long start = System.currentTimeMillis(); +sorter.sort(file, dest); +long end = System.currentTimeMillis(); +System.out.println(end - start); +~~~ + +该排序算法既有CPU计算又有IO,CPU计算时IO空闲,IO时CPU空间,如果把该方法改写成多线程版本,理论上可以提升不少速度。 \ No newline at end of file diff --git a/_posts/2018-03-28-large-file-diff-concurrent.md b/_posts/2018-03-28-large-file-diff-concurrent.md new file mode 100644 index 0000000..8987b6b --- /dev/null +++ b/_posts/2018-03-28-large-file-diff-concurrent.md @@ -0,0 +1,97 @@ +--- +title: 大文件内容对比多线程版本 +tags: java 算法 algorithm 排序 concurrent +categories: algorithm +--- + +* TOC +{:toc} + +这是[上一篇][上一篇]的续作,对于这个算法,其中可以同时进行的部分有 +1. 拆分后对每一个块的排序可以同时进行 +2. 合并时的不同范围之间可以同时进行,例如拆分为10个小块,那么1-5小块的合并跟6-10小块的合并过程可以同时进行 +3. 合并的不同阶段之间不可以同时进行,因为不同阶段之间有先后顺序 +4. 不存在对同一条数据的修改,所以无需进行并发控制 + + +# 线程池 + +~~~java +this.threadPoolExecutor = new ThreadPoolExecutor(8, 8, 10L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(8), + new CustomizableThreadFactory("fileSorterTPE-"), + new ThreadPoolExecutor.CallerRunsPolicy()); +~~~ + +# 线程池消费拆分任务 + +~~~java +List> splitFutureList = new ArrayList<>(); +while (true){ + line = br.readLine(); + if(line != null){ + chunkRows.add(line); + } + if(line == null || chunkRows.size() >= initialChunkSize){ + if(chunkRows.size() > 0){ + final int rn = rowNum; + final List cr = chunkRows; + rowNum += chunkRows.size(); + chunkRows = new ArrayList<>(); + Future chunk = threadPoolExecutor.submit(() -> { + cr.sort(comparator); + return initialChunk(rn, cr, file); + }); + splitFutureList.add(chunk); + } + } + if(line == null){ + break; + } +} +chunkList = splitFutureList.stream().map(this::get).collect(Collectors.toList()); +~~~ + + +# 线程池消费合并任务 + +~~~java +int currentLevel = INITIAL_CHUNK_LEVEL; +List> mergeFutureList = new ArrayList<>(); +while (true) { + //从队列中获取一组chunk + List pollChunks = pollChunks(chunkQueue, currentLevel); + //未取到同级chunk, 表示此级别应合并完成 + if (CollectionUtils.isEmpty(pollChunks)) { + mergeFutureList.stream().map(this::get).forEach(chunkQueue::add); + mergeFutureList.clear(); + //chunkQueue 中只有一个元素,表示此次合并是最终合并 + if (chunkQueue.size() == 1) { + break; + } else { + currentLevel++; + continue; + } + } + Future chunk = threadPoolExecutor.submit(() -> merge(pollChunks, original)); + mergeFutureList.add(chunk); +} +~~~ + +可以看到合并任务与拆分任务有些不同,拆分任务是在循环退出后才执行`Future.get`,因为拆分不用考虑先后; +而合并任务在每次获取当前阶段的chunk结束时执行`Future.get`,这样才能避免不同的阶段之间产生混乱。 + + [完整代码][完整代码] + +[上一篇]:https://bit-ranger.github.io/blog/algorithm/large-file-diff/ +[完整代码]:https://github.com/bit-ranger/architecture/blob/d9083d2fb71763557e6d4eb6875f9c001fd41596/core/src/main/java/com/rainyalley/architecture/core/arithmetic/sort/FileSorter.java + +# 测试 + +在上一篇中,使用单线程,1千万条数据排序耗时13秒; +在同一台电脑上,使用多线程后,耗时6秒,时间减少了一半。 + +在拆分过程中,每个线程都要在内存中进行排序, +在拆分和合并过程中,每个线程都要持有自己的读写缓冲区,这无疑会增大内存的使用量。 + +究竟消耗了多少内存,我们可以使用`Java Mission Control`来观察,jdk8的bin目录下`jmc.exe`即为此工具。 \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..8753b6b Binary files /dev/null and b/favicon.ico differ diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..a6628bd --- /dev/null +++ b/feed.xml @@ -0,0 +1,30 @@ +--- +layout: null +--- + + + + {{ site.title | xml_escape }} + {{ site.description | xml_escape }} + {{ site.url }}{{ site.baseurl }}/ + + {{ site.time | date_to_rfc822 }} + {{ site.time | date_to_rfc822 }} + Jekyll v{{ jekyll.version }} + {% for post in site.posts limit:10 %} + + {{ post.title | xml_escape }} + {{ post.content | xml_escape }} + {{ post.date | date_to_rfc822 }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {% for tag in post.tags %} + {{ tag | xml_escape }} + {% endfor %} + {% for cat in post.categories %} + {{ cat | xml_escape }} + {% endfor %} + + {% endfor %} + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..387dd98 --- /dev/null +++ b/index.html @@ -0,0 +1,60 @@ +--- +layout: default +--- + + +
+
+
+
+ {% for post in paginator.posts %} + + {% endfor %} +
+ +
+ + +
+
+
\ No newline at end of file diff --git a/pages/1archive.html b/pages/1archive.html new file mode 100644 index 0000000..07aca11 --- /dev/null +++ b/pages/1archive.html @@ -0,0 +1,23 @@ +--- +layout: page +title: Archive +permalink: /archive/ +icon: octicon-repo +isNavItem: true +styles: archive.css +--- + +
+{% for post in site.posts %} +
+
+ Picture +
+ +
+

{{ post.title }}

+ {{ post.date | date: '%Y-%m-%d' }} +
+
+{% endfor %} +
\ No newline at end of file diff --git a/pages/2category.html b/pages/2category.html new file mode 100644 index 0000000..667784a --- /dev/null +++ b/pages/2category.html @@ -0,0 +1,101 @@ +--- +layout: default +title: Category +permalink: /category/ +icon: octicon-list-unordered +isNavItem: true +styles : [article-list.css,category.css] +scripts: [category.js] +--- + +
+ + +
+ + + + +
+
+ {% for post in site.posts %} + + {% endfor %} +
+ + + {% for category in site.categories %} +
+ {% for posts in category %} + {% for post in posts %} + {% if post.url %} + + {% endif %} + {% endfor %} + {% endfor %} +
+ {% endfor %} +
+ +
+
\ No newline at end of file diff --git a/pages/3tag.html b/pages/3tag.html new file mode 100644 index 0000000..efb6acc --- /dev/null +++ b/pages/3tag.html @@ -0,0 +1,36 @@ +--- +layout: page +title: Tag +permalink: /tags/ +icon: octicon-tag +isNavItem: true +styles: [tagCloud.css,tags.css] +scripts: [tagCloud.js,tags.js] +--- + +
+ {% for tag in site.tags %} + {{ tag | first }} + {% endfor %} +
+ +
+ +
+ +
+ {% for tag in site.tags %} +

{{ tag | first }}

+
    + {% for posts in tag %}{% for post in posts %}{% if post.title != null %} +
  • » {{ post.title }}
  • + {% endif %}{% endfor %}{% endfor %} +
+ {% endfor %} +
+ + diff --git a/pages/4about.md b/pages/4about.md new file mode 100644 index 0000000..211f349 --- /dev/null +++ b/pages/4about.md @@ -0,0 +1,8 @@ +--- +layout: page +title: About +permalink: /about/ +icon: octicon-heart +--- + +自我介绍什么的,最讨厌了! \ No newline at end of file diff --git a/pages/5link-external.md b/pages/5link-external.md new file mode 100644 index 0000000..e4791f1 --- /dev/null +++ b/pages/5link-external.md @@ -0,0 +1,9 @@ +--- +layout: page +title: Link +permalink: /link/ +icon: octicon-link-external + +--- + +### [github](https://github.com/bit-ranger/blog) \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..9eaf64f --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,24 @@ +--- +layout: null +--- + + + {% for post in site.posts %} + + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {{ site.time | date_to_xmlschema }} + weekly + + {% endfor %} + {% for post in site.pages %}{% if post.isNavItem %} + + {{ post.url | prepend: site.baseurl | prepend: site.url }} + {{ site.time | date_to_xmlschema }} + weekly + + {% endif %} + {% endfor %} + \ No newline at end of file diff --git a/static/bg.jpg b/static/bg.jpg new file mode 100644 index 0000000..62c38ac Binary files /dev/null and b/static/bg.jpg differ diff --git a/static/css/archive.css b/static/css/archive.css new file mode 100644 index 0000000..a186470 --- /dev/null +++ b/static/css/archive.css @@ -0,0 +1,247 @@ + +/* -------------------------------- + +Modules - reusable parts of our design + +-------------------------------- */ +.cd-container { + /* this class is used to give a max-width to the element it is applied to, and center it horizontally when it reaches that max-width */ + width: 90%; + max-width: 1170px; + margin: 0 auto; +} +.cd-container::after { + /* clearfix */ + content: ''; + display: table; + clear: both; +} + +/* -------------------------------- + +Main components + +-------------------------------- */ + +#cd-timeline { + position: relative; + padding: 2em 0; + margin-top: 2em; + margin-bottom: 2em; +} +#cd-timeline::before { + /* this is the vertical line */ + content: ''; + position: absolute; + top: 0; + left: 18px; + height: 100%; + width: 4px; + background: #d7e4ed; +} +@media only screen and (min-width: 1170px) { + #cd-timeline { + margin-top: 3em; + margin-bottom: 3em; + } + #cd-timeline::before { + left: 50%; + margin-left: -2px; + } +} + +.cd-timeline-block { + position: relative; + margin: 2em 0; +} +.cd-timeline-block:after { + content: ""; + display: table; + clear: both; +} +.cd-timeline-block:first-child { + margin-top: 0; +} +.cd-timeline-block:last-child { + margin-bottom: 0; +} +@media only screen and (min-width: 1170px) { + .cd-timeline-block { + margin: 4em 0; + } + .cd-timeline-block:first-child { + margin-top: 0; + } + .cd-timeline-block:last-child { + margin-bottom: 0; + } +} + +.cd-timeline-img { + position: absolute; + top: 0; + left: 0; + width: 40px; + height: 40px; + border-radius: 50%; + box-shadow: 0 0 0 4px white, inset 0 2px 0 rgba(0, 0, 0, 0.08), 0 3px 0 4px rgba(0, 0, 0, 0.05); +} +.cd-timeline-img img { + display: block; + width: 24px; + height: 24px; + position: relative; + left: 50%; + top: 50%; + margin-left: -12px; + margin-top: -12px; +} +.cd-timeline-img.cd-picture { + background: #75ce66; +} +.cd-timeline-img.cd-movie { + background: #c03b44; +} +.cd-timeline-img.cd-location { + background: #f0ca45; +} +@media only screen and (min-width: 1170px) { + .cd-timeline-img { + width: 40px; + height: 40px; + left: 50%; + margin-left: -20px; + /* Force Hardware Acceleration in WebKit */ + -webkit-transform: translateZ(0); + -webkit-backface-visibility: hidden; + } + .cssanimations .cd-timeline-img.is-hidden { + visibility: hidden; + } + .cssanimations .cd-timeline-img.bounce-in { + visibility: visible; + -webkit-animation: cd-bounce-1 0.6s; + -moz-animation: cd-bounce-1 0.6s; + animation: cd-bounce-1 0.6s; + } +} + +.cd-timeline-content { + position: relative; + margin-left: 60px; + background: white; + border-radius: 0.25em; + padding: 1em; + box-shadow: 0 3px 0 #d7e4ed; +} +.cd-timeline-content:after { + content: ""; + display: table; + clear: both; +} +.cd-timeline-content a { + color: #303e49; +} +.cd-timeline-content p, .cd-timeline-content .cd-read-more, .cd-timeline-content .cd-date { + font-size: 13px; +} +.cd-timeline-content .cd-read-more, .cd-timeline-content .cd-date { + display: inline-block; +} +.cd-timeline-content p { + margin: 1em 0; + line-height: 1.6; +} +.cd-timeline-content .cd-read-more { + float: right; + padding: .8em 1em; + background: #acb7c0; + color: white; + border-radius: 0.25em; +} +.no-touch .cd-timeline-content .cd-read-more:hover { + background-color: #bac4cb; +} +a.cd-read-more:hover{text-decoration:none; background-color: #424242; } +.cd-timeline-content .cd-date { + float: left; + padding: .8em 0; + opacity: .7; +} +.cd-timeline-content::before { + content: ''; + position: absolute; + top: 16px; + right: 100%; + height: 0; + width: 0; + border: 7px solid transparent; + border-right: 7px solid white; +} +@media only screen and (min-width: 768px) { + .cd-timeline-content p { + font-size: 16px; + } + .cd-timeline-content .cd-read-more, .cd-timeline-content .cd-date { + font-size: 14px; + } +} +@media only screen and (min-width: 1170px) { + .cd-timeline-content { + margin-left: 0; + padding: 1.6em; + width: 45%; + } + .cd-timeline-content::before { + top: 24px; + left: 100%; + border-color: transparent; + border-left-color: white; + } + .cd-timeline-content .cd-read-more { + float: left; + } + .cd-timeline-content .cd-date { + position: absolute; + width: 100%; + left: 122%; + top: 6px; + font-size: 16px; + } + .cd-timeline-block:nth-child(even) .cd-timeline-content { + float: right; + } + .cd-timeline-block:nth-child(even) .cd-timeline-content::before { + top: 24px; + left: auto; + right: 100%; + border-color: transparent; + border-right-color: white; + } + .cd-timeline-block:nth-child(even) .cd-timeline-content .cd-read-more { + float: right; + } + .cd-timeline-block:nth-child(even) .cd-timeline-content .cd-date { + left: auto; + right: 122%; + text-align: right; + } + .cssanimations .cd-timeline-content.is-hidden { + visibility: hidden; + } + .cssanimations .cd-timeline-content.bounce-in { + visibility: visible; + -webkit-animation: cd-bounce-2 0.6s; + -moz-animation: cd-bounce-2 0.6s; + animation: cd-bounce-2 0.6s; + } +} + +@media only screen and (min-width: 1170px) { + /* inverse bounce effect on even content blocks */ + .cssanimations .cd-timeline-block:nth-child(even) .cd-timeline-content.bounce-in { + -webkit-animation: cd-bounce-2-inverse 0.6s; + -moz-animation: cd-bounce-2-inverse 0.6s; + animation: cd-bounce-2-inverse 0.6s; + } +} diff --git a/static/css/article-list.css b/static/css/article-list.css new file mode 100644 index 0000000..ee08a93 --- /dev/null +++ b/static/css/article-list.css @@ -0,0 +1,75 @@ +article{ + padding:6px 0px 8px 0px; + margin-top: 3em; +} +article header{ + text-decoration: none; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1em; + margin-left: 5px; + line-height: 1em; + text-shadow: 0 1px #fff; + font-weight: bold; + display: block; +} + +article header a{ + color: #666; +} + +article .module { + position: relative; + width: 100%; + overflow: hidden; + padding: 25px; + border-bottom: 1px solid #b1b1b1; + background: #f8f8fd; + + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; + -o-box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; + box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; +} +article .title{ + font-size: 1.6em; + font-weight: 500; + display: block; + margin-bottom: 10px; + + margin-left: -25px; + padding-left: 20px; + border-left: 5px solid #2ca6cb; +} + +article .readmore{ + display: inline-block; + line-height: 1em; + padding: 6px 15px; + -webkit-border-radius: 15px; + border-radius: 15px; + background: #eee; + color: #999; + text-shadow: 0 1px #fff; + text-decoration: none; + margin-top: 10px; + margin-bottom: 20px; +} +article .readmore:hover{ + background: #258fb8; + color: #fff; + text-decoration: none; + text-shadow: 0 1px #1e7293; +} + +article footer{ + font-size: .85em; + line-height: 1.6em; + border-top: 1px solid #ddd; + padding-top: 1.6em; + margin: 0; +} +article footer a{ + color: #999; + margin-right: 15px; +} \ No newline at end of file diff --git a/static/css/category.css b/static/css/category.css new file mode 100644 index 0000000..caa431b --- /dev/null +++ b/static/css/category.css @@ -0,0 +1,30 @@ +section.category-slice{ + display: none; +} +.categories-item{ + margin-top: 0.5em; +} +.categories-item .categories-badge{ + font-size: 10px; + color: #fff; + background-color: #999; + padding: 0 7px 1px 7px; + border-radius: 9px; + float: right; + transition:0.4s ease; + -webkit-transition:0.4s ease; + -moz-transition:0.4s ease; + -o-transition:0.4s ease; +} +.categories-item:hover .categories-badge{ + -webkit-transform:rotate(360deg) scale(1.2); + -moz-transform:rotate(360deg) scale(1.2); + -o-transform:rotate(360deg) scale(1.2); + -ms-transform:rotate(360deg) scale(1.2); + transform:rotate(360deg) scale(1.2); +} + +.dropdown .btn, +.dropdown .dropdown-menu{ + font-size: 17px; +} diff --git a/static/css/gitment.css b/static/css/gitment.css new file mode 100644 index 0000000..5414f78 --- /dev/null +++ b/static/css/gitment.css @@ -0,0 +1,1146 @@ +.gitment-container { + font-family: sans-serif; + font-size: 14px; + line-height: 1.5; + color: #333; + word-wrap: break-word; +} + +.gitment-container * { + box-sizing: border-box; +} + +.gitment-container *:disabled { + cursor: not-allowed; +} + +.gitment-container a, +.gitment-container a:visited { + cursor: pointer; + text-decoration: none; +} + +.gitment-container a:hover { + text-decoration: underline; +} + +.gitment-container .gitment-hidden { + display: none; +} + +.gitment-container .gitment-spinner-icon { + fill: #333; + + -webkit-animation: gitment-spin 1s steps(12) infinite; + animation: gitment-spin 1s steps(12) infinite; +} + +@-webkit-keyframes gitment-spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg) + } +} + +@keyframes gitment-spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg) + } +} + +.gitment-root-container { + margin: 19px 0; +} + +.gitment-header-container { + margin: 19px 0; +} + +.gitment-header-like-btn, +.gitment-comment-like-btn { + cursor: pointer; +} + +.gitment-comment-like-btn { + float: right; +} + +.gitment-comment-like-btn.liked { + color: #F44336; +} + +.gitment-header-like-btn svg { + vertical-align: middle; + height: 30px; +} + +.gitment-comment-like-btn svg { + vertical-align: middle; + height: 20px; +} + +.gitment-header-like-btn.liked svg, +.gitment-comment-like-btn.liked svg { + fill: #F44336; +} + +a.gitment-header-issue-link, +a.gitment-header-issue-link:visited { + float: right; + line-height: 30px; + color: #666; +} + +a.gitment-header-issue-link:hover { + color: #666; +} + +.gitment-comments-loading, +.gitment-comments-error, +.gitment-comments-empty { + text-align: center; + margin: 50px 0; +} + +.gitment-comments-list { + list-style: none; + padding-left: 0; + margin: 0 0 38px; +} + +.gitment-comment, +.gitment-editor-container { + position: relative; + min-height: 60px; + padding-left: 60px; + margin: 19px 0; +} + +.gitment-comment-avatar, +.gitment-editor-avatar { + float: left; + margin-left: -60px; +} + +.gitment-comment-avatar, +.gitment-comment-avatar-img, +.gitment-comment-avatar, +.gitment-editor-avatar-img, +.gitment-editor-avatar svg { + width: 44px; + height: 44px; + border-radius: 3px; +} + +.gitment-editor-avatar .gitment-github-icon { + fill: #fff; + background-color: #333; +} + +.gitment-comment-main, +.gitment-editor-main { + position: relative; + border: 1px solid #CFD8DC; + border-radius: 0; +} + +.gitment-editor-main::before, +.gitment-editor-main::after, +.gitment-comment-main::before, +.gitment-comment-main::after { + position: absolute; + top: 11px; + left: -16px; + display: block; + width: 0; + height: 0; + pointer-events: none; + content: ""; + border-color: transparent; + border-style: solid solid outset; +} + +.gitment-editor-main::before, +.gitment-comment-main::before { + border-width: 8px; + border-right-color: #CFD8DC; +} + +.gitment-editor-main::after, +.gitment-comment-main::after { + margin-top: 1px; + margin-left: 2px; + border-width: 7px; + border-right-color: #fff; +} + +.gitment-comment-header { + margin: 12px 15px; + color: #666; + background-color: #fff; + border-radius: 3px; +} + +.gitment-editor-header { + padding: 0; + margin: 0; + border-bottom: 1px solid #CFD8DC; +} + +a.gitment-comment-name, +a.gitment-comment-name:visited { + font-weight: 600; + color: #666; +} + +.gitment-editor-tabs { + margin-bottom: -1px; + margin-left: -1px; +} + +.gitment-editor-tab { + display: inline-block; + padding: 11px 12px; + font-size: 14px; + line-height: 20px; + color: #666; + text-decoration: none; + background-color: transparent; + border-width: 0 1px; + border-style: solid; + border-color: transparent; + border-radius: 0; + + white-space: nowrap; + cursor: pointer; + user-select: none; + + outline: none; +} + +.gitment-editor-tab.gitment-selected { + color: #333; + background-color: #fff; + border-color: #CFD8DC; +} + +.gitment-editor-login { + float: right; + margin-top: -30px; + margin-right: 15px; +} + +a.gitment-footer-project-link, +a.gitment-footer-project-link:visited, +a.gitment-editor-login-link, +a.gitment-editor-login-link:visited { + color: #2196F3; +} + +a.gitment-editor-logout-link, +a.gitment-editor-logout-link:visited { + color: #666; +} + +a.gitment-editor-logout-link:hover { + color: #2196F3; + text-decoration: none; +} + +.gitment-comment-body { + position: relative; + margin: 12px 15px; + overflow: hidden; + border-radius: 3px; +} + +.gitment-comment-body-folded { + cursor: pointer; +} + +.gitment-comment-body-folded::before { + display: block !important; + content: ""; + position: absolute; + width: 100%; + left: 0; + top: 0; + bottom: 50px; + pointer-events: none; + background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, .9)); + background: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .9)); +} + +.gitment-comment-body-folded::after { + display: block !important; + content: "Click to Expand" !important; + text-align: center; + color: #666; + position: absolute; + width: 100%; + height: 50px; + line-height: 50px; + left: 0; + bottom: 0; + pointer-events: none; + background: rgba(255, 255, 255, .9); +} + +.gitment-editor-body { + margin: 0; +} + +.gitment-comment-body > *:first-child, +.gitment-editor-preview > *:first-child { + margin-top: 0 !important; +} + +.gitment-comment-body > *:last-child, +.gitment-editor-preview > *:last-child { + margin-bottom: 0 !important; +} + +.gitment-editor-body textarea { + display: block; + width: 100%; + min-height: 150px; + max-height: 500px; + padding: 16px; + resize: vertical; + + max-width: 100%; + margin: 0; + font-size: 14px; + line-height: 1.6; + + background-color: #fff; + + color: #333; + vertical-align: middle; + border: none; + border-radius: 0; + outline: none; + box-shadow: none; + + overflow: visible; +} + +.gitment-editor-body textarea:focus { + background-color: #fff; +} + +.gitment-editor-preview { + min-height: 150px; + + padding: 16px; + background-color: transparent; + + width: 100%; + font-size: 14px; + + line-height: 1.5; + word-wrap: break-word; +} + +.gitment-editor-footer { + padding: 0; + margin-top: 10px; +} + +.gitment-editor-footer::after { + display: table; + clear: both; + content: ""; +} + +a.gitment-editor-footer-tip { + display: inline-block; + padding-top: 10px; + font-size: 12px; + color: #666; +} + +a.gitment-editor-footer-tip:hover { + color: #2196F3; + text-decoration: none; +} + +.gitment-comments-pagination { + list-style: none; + text-align: right; + border-radius: 0; + margin: -19px 0 19px 0; +} + +.gitment-comments-page-item { + display: inline-block; + cursor: pointer; + border: 1px solid #CFD8DC; + margin-left: -1px; + padding: .25rem .5rem; +} + +.gitment-comments-page-item:hover { + background-color: #f5f5f5; +} + +.gitment-comments-page-item.gitment-selected { + background-color: #f5f5f5; +} + +.gitment-editor-submit, +.gitment-comments-init-btn { + color: #fff; + background-color: #00BCD4; + + position: relative; + display: inline-block; + padding: 7px 13px; + font-size: 14px; + font-weight: 600; + line-height: 20px; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-size: 110% 110%; + border: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.gitment-editor-submit:hover, +.gitment-comments-init-btn:hover { + background-color: #00ACC1; +} + +.gitment-comments-init-btn:disabled, +.gitment-editor-submit:disabled { + color: rgba(255,255,255,0.75); + background-color: #4DD0E1; + box-shadow: none; +} + +.gitment-editor-submit { + float: right; +} + +.gitment-footer-container { + margin-top: 30px; + margin-bottom: 20px; + text-align: right; + font-size: 12px; +} + +/* + * Markdown CSS + * Copied from https://github.com/sindresorhus/github-markdown-css + */ +.gitment-markdown { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.5; + color: #333; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.gitment-markdown .pl-c { + color: #969896; +} + +.gitment-markdown .pl-c1, +.gitment-markdown .pl-s .pl-v { + color: #0086b3; +} + +.gitment-markdown .pl-e, +.gitment-markdown .pl-en { + color: #795da3; +} + +.gitment-markdown .pl-smi, +.gitment-markdown .pl-s .pl-s1 { + color: #333; +} + +.gitment-markdown .pl-ent { + color: #63a35c; +} + +.gitment-markdown .pl-k { + color: #a71d5d; +} + +.gitment-markdown .pl-s, +.gitment-markdown .pl-pds, +.gitment-markdown .pl-s .pl-pse .pl-s1, +.gitment-markdown .pl-sr, +.gitment-markdown .pl-sr .pl-cce, +.gitment-markdown .pl-sr .pl-sre, +.gitment-markdown .pl-sr .pl-sra { + color: #183691; +} + +.gitment-markdown .pl-v, +.gitment-markdown .pl-smw { + color: #ed6a43; +} + +.gitment-markdown .pl-bu { + color: #b52a1d; +} + +.gitment-markdown .pl-ii { + color: #f8f8f8; + background-color: #b52a1d; +} + +.gitment-markdown .pl-c2 { + color: #f8f8f8; + background-color: #b52a1d; +} + +.gitment-markdown .pl-c2::before { + content: "^M"; +} + +.gitment-markdown .pl-sr .pl-cce { + font-weight: bold; + color: #63a35c; +} + +.gitment-markdown .pl-ml { + color: #693a17; +} + +.gitment-markdown .pl-mh, +.gitment-markdown .pl-mh .pl-en, +.gitment-markdown .pl-ms { + font-weight: bold; + color: #1d3e81; +} + +.gitment-markdown .pl-mq { + color: #008080; +} + +.gitment-markdown .pl-mi { + font-style: italic; + color: #333; +} + +.gitment-markdown .pl-mb { + font-weight: bold; + color: #333; +} + +.gitment-markdown .pl-md { + color: #bd2c00; + background-color: #ffecec; +} + +.gitment-markdown .pl-mi1 { + color: #55a532; + background-color: #eaffea; +} + +.gitment-markdown .pl-mc { + color: #ef9700; + background-color: #ffe3b4; +} + +.gitment-markdown .pl-mi2 { + color: #d8d8d8; + background-color: #808080; +} + +.gitment-markdown .pl-mdr { + font-weight: bold; + color: #795da3; +} + +.gitment-markdown .pl-mo { + color: #1d3e81; +} + +.gitment-markdown .pl-ba { + color: #595e62; +} + +.gitment-markdown .pl-sg { + color: #c0c0c0; +} + +.gitment-markdown .pl-corl { + text-decoration: underline; + color: #183691; +} + +.gitment-markdown .octicon { + display: inline-block; + vertical-align: text-top; + fill: currentColor; +} + +.gitment-markdown a { + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +.gitment-markdown a:active, +.gitment-markdown a:hover { + outline-width: 0; +} + +.gitment-markdown strong { + font-weight: inherit; +} + +.gitment-markdown strong { + font-weight: bolder; +} + +.gitment-markdown h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.gitment-markdown img { + border-style: none; +} + +.gitment-markdown svg:not(:root) { + overflow: hidden; +} + +.gitment-markdown code, +.gitment-markdown kbd, +.gitment-markdown pre { + font-family: monospace, monospace; + font-size: 1em; +} + +.gitment-markdown hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +.gitment-markdown input { + font: inherit; + margin: 0; +} + +.gitment-markdown input { + overflow: visible; +} + +.gitment-markdown [type="checkbox"] { + box-sizing: border-box; + padding: 0; +} + +.gitment-markdown * { + box-sizing: border-box; +} + +.gitment-markdown input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.gitment-markdown a { + color: #0366d6; + text-decoration: none; +} + +.gitment-markdown a:hover { + text-decoration: underline; +} + +.gitment-markdown strong { + font-weight: 600; +} + +.gitment-markdown hr { + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; +} + +.gitment-markdown hr::before { + display: table; + content: ""; +} + +.gitment-markdown hr::after { + display: table; + clear: both; + content: ""; +} + +.gitment-markdown table { + border-spacing: 0; + border-collapse: collapse; +} + +.gitment-markdown td, +.gitment-markdown th { + padding: 0; +} + +.gitment-markdown h1, +.gitment-markdown h2, +.gitment-markdown h3, +.gitment-markdown h4, +.gitment-markdown h5, +.gitment-markdown h6 { + margin-top: 0; + margin-bottom: 0; +} + +.gitment-markdown h1 { + font-size: 32px; + font-weight: 600; +} + +.gitment-markdown h2 { + font-size: 24px; + font-weight: 600; +} + +.gitment-markdown h3 { + font-size: 20px; + font-weight: 600; +} + +.gitment-markdown h4 { + font-size: 16px; + font-weight: 600; +} + +.gitment-markdown h5 { + font-size: 14px; + font-weight: 600; +} + +.gitment-markdown h6 { + font-size: 12px; + font-weight: 600; +} + +.gitment-markdown p { + margin-top: 0; + margin-bottom: 10px; +} + +.gitment-markdown blockquote { + margin: 0; +} + +.gitment-markdown ul, +.gitment-markdown ol { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} + +.gitment-markdown ol ol, +.gitment-markdown ul ol { + list-style-type: lower-roman; +} + +.gitment-markdown ul ul ol, +.gitment-markdown ul ol ol, +.gitment-markdown ol ul ol, +.gitment-markdown ol ol ol { + list-style-type: lower-alpha; +} + +.gitment-markdown dd { + margin-left: 0; +} + +.gitment-markdown code { + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + font-size: 12px; +} + +.gitment-markdown pre { + margin-top: 0; + margin-bottom: 0; + font: 12px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; +} + +.gitment-markdown .octicon { + vertical-align: text-bottom; +} + +.gitment-markdown .pl-0 { + padding-left: 0 !important; +} + +.gitment-markdown .pl-1 { + padding-left: 4px !important; +} + +.gitment-markdown .pl-2 { + padding-left: 8px !important; +} + +.gitment-markdown .pl-3 { + padding-left: 16px !important; +} + +.gitment-markdown .pl-4 { + padding-left: 24px !important; +} + +.gitment-markdown .pl-5 { + padding-left: 32px !important; +} + +.gitment-markdown .pl-6 { + padding-left: 40px !important; +} + +.gitment-markdown::before { + display: table; + content: ""; +} + +.gitment-markdown::after { + display: table; + clear: both; + content: ""; +} + +.gitment-markdown>*:first-child { + margin-top: 0 !important; +} + +.gitment-markdown>*:last-child { + margin-bottom: 0 !important; +} + +.gitment-markdown a:not([href]) { + color: inherit; + text-decoration: none; +} + +.gitment-markdown .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.gitment-markdown .anchor:focus { + outline: none; +} + +.gitment-markdown p, +.gitment-markdown blockquote, +.gitment-markdown ul, +.gitment-markdown ol, +.gitment-markdown dl, +.gitment-markdown table, +.gitment-markdown pre { + margin-top: 0; + margin-bottom: 16px; +} + +.gitment-markdown hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +.gitment-markdown blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; +} + +.gitment-markdown blockquote>:first-child { + margin-top: 0; +} + +.gitment-markdown blockquote>:last-child { + margin-bottom: 0; +} + +.gitment-markdown kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #c6cbd1; + border-bottom-color: #959da5; + border-radius: 0; + box-shadow: inset 0 -1px 0 #959da5; +} + +.gitment-markdown h1, +.gitment-markdown h2, +.gitment-markdown h3, +.gitment-markdown h4, +.gitment-markdown h5, +.gitment-markdown h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.gitment-markdown h1 .octicon-link, +.gitment-markdown h2 .octicon-link, +.gitment-markdown h3 .octicon-link, +.gitment-markdown h4 .octicon-link, +.gitment-markdown h5 .octicon-link, +.gitment-markdown h6 .octicon-link { + color: #1b1f23; + vertical-align: middle; + visibility: hidden; +} + +.gitment-markdown h1:hover .anchor, +.gitment-markdown h2:hover .anchor, +.gitment-markdown h3:hover .anchor, +.gitment-markdown h4:hover .anchor, +.gitment-markdown h5:hover .anchor, +.gitment-markdown h6:hover .anchor { + text-decoration: none; +} + +.gitment-markdown h1:hover .anchor .octicon-link, +.gitment-markdown h2:hover .anchor .octicon-link, +.gitment-markdown h3:hover .anchor .octicon-link, +.gitment-markdown h4:hover .anchor .octicon-link, +.gitment-markdown h5:hover .anchor .octicon-link, +.gitment-markdown h6:hover .anchor .octicon-link { + visibility: visible; +} + +.gitment-markdown h1 { + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid #eaecef; +} + +.gitment-markdown h2 { + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid #eaecef; +} + +.gitment-markdown h3 { + font-size: 1.25em; +} + +.gitment-markdown h4 { + font-size: 1em; +} + +.gitment-markdown h5 { + font-size: 0.875em; +} + +.gitment-markdown h6 { + font-size: 0.85em; + color: #6a737d; +} + +.gitment-markdown ul, +.gitment-markdown ol { + padding-left: 2em; +} + +.gitment-markdown ul ul, +.gitment-markdown ul ol, +.gitment-markdown ol ol, +.gitment-markdown ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.gitment-markdown li>p { + margin-top: 16px; +} + +.gitment-markdown li+li { + margin-top: 0.25em; +} + +.gitment-markdown dl { + padding: 0; +} + +.gitment-markdown dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.gitment-markdown dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.gitment-markdown table { + display: block; + width: 100%; + overflow: auto; +} + +.gitment-markdown table th { + font-weight: 600; +} + +.gitment-markdown table th, +.gitment-markdown table td { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +.gitment-markdown table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.gitment-markdown table tr:nth-child(2n) { + background-color: #f5f5f5; +} + +.gitment-markdown img { + max-width: 100%; + box-sizing: content-box; + background-color: #fff; +} + +.gitment-markdown code { + padding: 0; + padding-top: 0.2em; + padding-bottom: 0.2em; + margin: 0; + font-size: 85%; + background-color: rgba(27,31,35,0.05); + border-radius: 0; +} + +.gitment-markdown code::before, +.gitment-markdown code::after { + letter-spacing: -0.2em; + content: "\00a0"; +} + +.gitment-markdown pre { + word-wrap: normal; +} + +.gitment-markdown pre>code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.gitment-markdown .highlight { + margin-bottom: 16px; +} + +.gitment-markdown .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.gitment-markdown .highlight pre, +.gitment-markdown pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f5f5f5; + border-radius: 0; +} + +.gitment-markdown pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.gitment-markdown pre code::before, +.gitment-markdown pre code::after { + content: normal; +} + +.gitment-markdown .full-commit .btn-outline:not(:disabled):hover { + color: #005cc5; + border-color: #005cc5; +} + +.gitment-markdown kbd { + display: inline-block; + padding: 3px 5px; + font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fcfcfc; + border: solid 1px #c6cbd1; + border-bottom-color: #959da5; + border-radius: 0; + box-shadow: inset 0 -1px 0 #959da5; +} + +.gitment-markdown :checked+.radio-label { + position: relative; + z-index: 1; + border-color: #0366d6; +} + +.gitment-markdown .task-list-item { + list-style-type: none; +} + +.gitment-markdown .task-list-item+.task-list-item { + margin-top: 3px; +} + +.gitment-markdown .task-list-item input { + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; +} + +.gitment-markdown hr { + border-bottom-color: #eee; +} \ No newline at end of file diff --git a/static/css/highlight.css b/static/css/highlight.css new file mode 100644 index 0000000..a95b3fd --- /dev/null +++ b/static/css/highlight.css @@ -0,0 +1,63 @@ +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { color: #000000; font-weight: bold } /* Keyword */ +.highlight .o { color: #000000; font-weight: bold } /* Operator */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #d01040 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #0086B3 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sb { color: #d01040 } /* Literal.String.Backtick */ +.highlight .sc { color: #d01040 } /* Literal.String.Char */ +.highlight .sd { color: #d01040 } /* Literal.String.Doc */ +.highlight .s2 { color: #d01040 } /* Literal.String.Double */ +.highlight .se { color: #d01040 } /* Literal.String.Escape */ +.highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ +.highlight .si { color: #d01040 } /* Literal.String.Interpol */ +.highlight .sx { color: #d01040 } /* Literal.String.Other */ +.highlight .sr { color: #009926 } /* Literal.String.Regex */ +.highlight .s1 { color: #d01040 } /* Literal.String.Single */ +.highlight .ss { color: #990073 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ +.highlight .lineno{ color: rgba(0,0,0,0.3); border-right: solid 1px rgba(0,0,0,0.3); padding-right: 5px;} + + diff --git a/static/css/post.css b/static/css/post.css new file mode 100644 index 0000000..c2109bb --- /dev/null +++ b/static/css/post.css @@ -0,0 +1,164 @@ +.post table{ + border-top:2px solid #777; + border-bottom: 2px solid #777; + margin: 8px 0; +} +.post table thead{ + border-bottom: 1px dashed #777; + background-color: #aaa; + color:#fff; +} +.post table th{ + padding: 2px 10px; +} +.post table tr:nth-child(2n){ + background-color: #E5EAED; +} +.post table td{ + padding: 2px 10px; + vertical-align: top; +} + +/* post + * post articles area + */ +.post header{ + margin-left: -30px; + padding-left: 25px; + border-left: 5px solid #2ca6cb; + line-height: 1.5; +} +.post header h2{ + color: #2ca6cb; +} +.post header p.post-meta{ + color: #817c7c; +} + +.post header p.post-tag a{ + display: inline-block; + background: #2ca6cb; + color: #fff; + padding: 2px 5px 0px 5px; + border: 1px solid #2ca6cb; + margin-top: 2px; +} + +.post img{ + max-width: 100%; + vertical-align: middle; + + -webkit-box-shadow: 0 0 5px 2px rgba(0,0,0,0.1); + -moz-box-shadow: 0 0 5px 2px rgba(0,0,0,0.1); + -o-box-shadow: 0 0 5px 2px rgba(0,0,0,0.1); + box-shadow: 0 0 5px 2px rgba(0,0,0,0.1); +} +/** + * Blockquotes + */ +.post blockquote { + border-left: 5px solid #D6DBDF; + background: rgba(112,138,153,.1); + font-size: 100%; +} +.post blockquote:last-child { + margin-bottom: 0; +} + +.post pre { + overflow: auto; + word-wrap: normal; + white-space: pre; +} + +.post pre code{ + white-space: pre; +} + +@media screen and (min-width: 1200px){ + /** + * 文章目录相关 + */ + .post ul#markdown-toc { + display: none!important; + } +} + +@media screen and (max-width: 1199px){ + .content-navigation{ + display: none!important; + } + + .post pre code{ + font-size: 85%; + } +} + +@media screen and (max-width: 991px){ + .post pre code{ + font-size: 70%; + } +} + +@media screen and (max-width: 767px){ + .post header{ + margin-left: -10px; + padding-left: 5px; + } + .post pre code{ + font-size: 55%; + } +} + + +.content-navigation a:link {color: #333;text-decoration: none;} /* 未访问的链接 */ +.content-navigation a:visited {color: #99b } /* 已访问的链接 */ +.content-navigation a:hover {color: #99b;} /* 鼠标移动到链接上 */ +.content-navigation a:active {color: #99b} /* 选定的链接 */ + +.content-navigation{ + max-width: 292px; + padding-left: 30px; +} +.content-navigation-header { + display: block; + border-bottom: .1875em solid #ccc; + font-size: 20px; + color: #2ca6cb;; + margin-left: 15px; + margin-right: 15px; + padding-top: 15px; + padding-right: 15px; +} +.content-navigation .content-navigation-list{ + padding: 10px 0; +} + +.content-navigation .content-navigation-list ul li{ + line-height: 25px; + word-break: break-all; +} + +.boundary{ + border-bottom: .1875em solid #ccc +} + + +.clickable-header { + cursor:pointer; +} +.clickable-header:hover:after { + content: '\f05c'; + margin-left: 10px; + + font: normal normal normal smaller/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..791707a --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,264 @@ +--- +layout: null +--- + +.navbar-tiffany, +.banner{ + background: -webkit-linear-gradient(45deg, rgba(0 , 0, 0, 0) 48%, rgba(0 , 0, 0, 0.1) 50%, rgba(0 , 0, 0, 0) 52%), + -webkit-linear-gradient(135deg, rgba(0 , 0, 0, 0) 48%, rgba(0 , 0, 0, 0.1) 50%, rgba(0 , 0, 0, 0) 52%); + background: linear-gradient(45deg, rgba(0 , 0, 0, 0) 48%, rgba(0 , 0, 0, 0.1) 50%, rgba(0 , 0, 0, 0) 52%), + linear-gradient(-45deg, rgba(0 , 0, 0, 0) 48%, rgba(0 , 0, 0, 0.1) 50%, rgba(0 , 0, 0, 0) 52%); + -webkit-background-size: 1em 1em; + background-size: 1em 1em; +} + +.navbar-tiffany{ + background-color: #2ca6cb; + border-top: 1px solid rgba(255,255,255,0.2); + border-bottom: 1px solid rgba(0,0,0,0.1); + box-shadow: 2px 4px 5px rgba(3,3,3,0.2); +} + +.footnote-tiffany{ + background: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0) 48%, rgba(255, 255, 255, 0.03) 50%, rgba(255, 255, 255, 0) 52%), + -webkit-linear-gradient(135deg, rgba(255, 255, 255, 0) 48%, rgba(255, 255, 255, 0.03) 50%, rgba(255, 255, 255, 0) 52%); + background: linear-gradient(45deg, rgba(255, 255, 255, 0) 48%, rgba(255, 255, 255, 0.03) 50%, rgba(255, 255, 255, 0) 52%), + linear-gradient(-45deg, rgba(255, 255, 255, 0) 48%, rgba(255, 255, 255, 0.03) 50%, rgba(255, 255, 255, 0) 52%); + -webkit-background-size: 1em 1em; + background-size: 1em 1em; +} +.footnote-tiffany{ + background-color: #333333; + border-color: #2ca6cb; + box-shadow: 2px -4px 5px rgba(3,-3,3,0.2); +} + + +.navbar-tiffany .navbar-brand, +.navbar-tiffany .navbar-text, +.navbar-tiffany .nav>li>a{ + color: #fff; +} + + +.navbar-tiffany .nav>li>a:hover, +.navbar-tiffany .nav>li>a:focus, +.navbar-tiffany .nav>li.active>a { + background: rgba(0, 0, 0, 0.2); + color: #fff; +} +.navbar-tiffany .navbar-toggle{ + border-color: #fff; +} + +.navbar-tiffany .navbar-toggle .icon-bar{ + background-color: #fff; +} + +.navbar-nav>li>a{ + line-height: 1.42857143; +} + +.footnote-tiffany a, +.footnote-tiffany span{ + color:#fff; + +} +.footnote .foot-item{ + display: inline-block; + width: 28px; + height: 28px; + line-height: 28px; + font-size: 18px; + text-align: center; + background: rgba(255,255,255,0.1); + border-radius: 3px 3px; + -webkit-transition: background .4s ease-in-out; + -moz-transition: background .4s ease-in-out; + -ms-transition: background .4s ease-in-out; + -o-transition: background .4s ease-in-out; + transition: background .4s ease-in-out; +} + +.footnote .foot-item:hover{ + background-color: #2ca6cb; + -webkit-transition: background .4s ease-in-out; + -moz-transition: background .4s ease-in-out; + -ms-transition: background .4s ease-in-out; + -o-transition: background .4s ease-in-out; + transition: background .4s ease-in-out; +} + +/* footer */ +.footnote{ + min-height: 50px; + line-height: 50px; + margin-top: -50px; + text-align: center; +} + +html{ + height: 100%; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; +} +body { + background-image: url({{ "/static/bg.jpg" | prepend: site.baseurl | prepend: site.url }}); + height: 100%; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; +} + +body,code,pre{ + font-family: "Menlo", "Monaco", "Consolas", "Courier New", "monospace", "Helvetica", "Tahoma", "Arial", + "WenQuanYi Micro Hei", "文泉驿微米黑", "STXihei", "华文细黑", "Microsoft YaHei", "微软雅黑", "SimSun", "宋体", "Heiti", "黑体", sans-serif; +} + +body,.sheet,pre,code{ + font-size: 17px; +} + + +a:link {text-decoration: none} /* 未访问的链接 */ +a:visited {text-decoration: none} /* 已访问的链接 */ +a:hover {text-decoration: none} /* 鼠标移动到链接上 */ +a:active {text-decoration: none} /* 选定的链接 */ + +.main{ + min-height: 100%; + padding-bottom: 130px; +} + +.content{ + padding-right: 0px; + padding-left: 0px; +} + +.sheet{ + word-break: break-all; + background-color:#f8f8fd; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; + -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; + -o-box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; + box-shadow: 0 1px 2px rgba(0,0,0,0.4),0 0 30px rgba(10,10,0,0.1) inset; +} + +.sheet{ + padding:10px 30px; +} + +@media screen and (max-width: 767px) { + .sheet{ + padding-left: 10px; + padding-right: 10px; + } +} + +/** + * back to top + */ +.page-scrollTop { + position: fixed; + right: 10px; + bottom: 10px; + width: 60px; + height: 60px; + background-color: #bbb; + border-radius:7px; + opacity: 0.7; + display: none; + z-index: 888; +} +.page-scrollTop .arrow{ + margin:0 auto; + padding-top:11px; + width:0; + height: 0; + border-right: 15px solid transparent; + border-left: 15px solid transparent; + border-bottom: 15px solid #7f7f7f; + +} +.page-scrollTop .stick{ + margin:0 auto; + padding-bottom:21px; + width: 13px; + border-bottom: 13px solid #bbb; + background-color: #7f7f7f; +} + +.shadow-bottom-center{ + background-color:#f8f8fd; + -webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.4),0 0 20px rgba(0,0,0,0.1) inset; + -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.4),0 0 20px rgba(0,0,0,0.1) inset; + -o-box-shadow: 0 1px 5px rgba(0,0,0,0.4),0 0 20px rgba(0,0,0,0.1) inset; + box-shadow: 0 1px 5px rgba(0,0,0,0.4),0 0 20px rgba(0,0,0,0.1) inset; +} + +.word-keep{ + display: inline-block; + word-break: keep-all; +} + +.rectangle{ + border-radius: 0px; +} + + +@media (min-width: 1200px){ + .col-lg-offset-1_5 { + margin-left: 12.5%; + } +} + +.pad-min, +.pad-mid, +.pad-lar{ + display: block; +} +.pad-min{ + height: 20px; +} +.pad-mid{ + height: 50px; +} +.pad-lar{ + height: 80px; +} +.scriptHub{ + display: none!important; +} + +.st-ui-stamp{ + display: none!important; +} + +/*** banner ***/ +.banner { + width: 100%; + padding-top: 15px; + padding-bottom: 15px; + background-color: #2ca6cb; + display: none; +} + +.banner h1{ + font-size: 50px; + font-weight: bold; +} + + +.banner h1, +.banner h3{ + color: #ffffff; + text-align: center; +} +.banner h3{ + line-height: 1.5; +} + +@media screen and (max-width: 767px) { + .banner h1{ + font-size: 36px; + } +} diff --git a/static/css/tagCloud.css b/static/css/tagCloud.css new file mode 100644 index 0000000..43f7901 --- /dev/null +++ b/static/css/tagCloud.css @@ -0,0 +1,24 @@ +.tagCloud { + position:relative; + display: block; + width: 400px; + height: 400px; + margin: 20px auto 0; +} +.tagCloud a { + position:absolute; + top:0px; + left:0px; + text-decoration:none; + padding: 3px 6px; + color: #2ca6cb; +} +.tagCloud a:hover{ + color: orchid; +} + +@media screen and (max-width: 768px){ + .tagCloud { + display: none; + } +} \ No newline at end of file diff --git a/static/css/tags.css b/static/css/tags.css new file mode 100644 index 0000000..5a15a91 --- /dev/null +++ b/static/css/tags.css @@ -0,0 +1,40 @@ +.tag-box { + list-style: none; + margin: 0; + padding: 4px 0; + overflow: hidden; + *zoom: 1; +} + +.tag-box:before, .tag-box:after { + display: table; + content: ""; + line-height: 0; +} + +.tag-box:after { + clear: both; +} + +.tag-box.inline li { + float: left; + font-size: 14px; + font-size: 1em; + line-height: 2.1; +} + +.tag-box a { + padding: 4px 6px; + margin: 2px; + background-color: #e6e6e6; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 2px; + text-decoration: none; +} + +.tag-box a span { + vertical-align: super; + font-size: 10px; + font-size: 0.875rem; +} \ No newline at end of file diff --git a/static/img/JavaScriptMindMap.jpg b/static/img/JavaScriptMindMap.jpg new file mode 100644 index 0000000..256f908 Binary files /dev/null and b/static/img/JavaScriptMindMap.jpg differ diff --git a/static/img/bg-canvas_bg.jpg b/static/img/bg-canvas_bg.jpg new file mode 100644 index 0000000..bed2eca Binary files /dev/null and b/static/img/bg-canvas_bg.jpg differ diff --git a/static/img/bitmap.jpg b/static/img/bitmap.jpg new file mode 100644 index 0000000..3624270 Binary files /dev/null and b/static/img/bitmap.jpg differ diff --git a/static/img/boundary-A.png b/static/img/boundary-A.png new file mode 100644 index 0000000..a19d71f Binary files /dev/null and b/static/img/boundary-A.png differ diff --git a/static/img/boundary-B.png b/static/img/boundary-B.png new file mode 100644 index 0000000..6d591f5 Binary files /dev/null and b/static/img/boundary-B.png differ diff --git a/static/img/boundary-C.png b/static/img/boundary-C.png new file mode 100644 index 0000000..f4a44ba Binary files /dev/null and b/static/img/boundary-C.png differ diff --git a/static/img/http-get.png b/static/img/http-get.png new file mode 100644 index 0000000..cf2b320 Binary files /dev/null and b/static/img/http-get.png differ diff --git a/static/img/http-post.png b/static/img/http-post.png new file mode 100644 index 0000000..53aa4d9 Binary files /dev/null and b/static/img/http-post.png differ diff --git a/static/img/i0.jpg b/static/img/i0.jpg new file mode 100644 index 0000000..507cdd5 Binary files /dev/null and b/static/img/i0.jpg differ diff --git a/static/img/icon-picture.svg b/static/img/icon-picture.svg new file mode 100644 index 0000000..26ff50e --- /dev/null +++ b/static/img/icon-picture.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/static/img/io-match-2.jpg b/static/img/io-match-2.jpg new file mode 100644 index 0000000..ecb90cd Binary files /dev/null and b/static/img/io-match-2.jpg differ diff --git a/static/img/io-match.jpg b/static/img/io-match.jpg new file mode 100644 index 0000000..0df286b Binary files /dev/null and b/static/img/io-match.jpg differ diff --git a/static/img/ip.jpg b/static/img/ip.jpg new file mode 100644 index 0000000..af0fe08 Binary files /dev/null and b/static/img/ip.jpg differ diff --git a/static/img/java-system.png b/static/img/java-system.png new file mode 100644 index 0000000..3964ead Binary files /dev/null and b/static/img/java-system.png differ diff --git a/static/img/jvm-arg0.png b/static/img/jvm-arg0.png new file mode 100644 index 0000000..0327d42 Binary files /dev/null and b/static/img/jvm-arg0.png differ diff --git a/static/img/jvm-arg1.png b/static/img/jvm-arg1.png new file mode 100644 index 0000000..ffb9bd5 Binary files /dev/null and b/static/img/jvm-arg1.png differ diff --git a/static/img/jvm-gc.png b/static/img/jvm-gc.png new file mode 100644 index 0000000..0891743 Binary files /dev/null and b/static/img/jvm-gc.png differ diff --git a/static/img/jvm-handle.png b/static/img/jvm-handle.png new file mode 100644 index 0000000..c2a63d6 Binary files /dev/null and b/static/img/jvm-handle.png differ diff --git a/static/img/jvm-point.png b/static/img/jvm-point.png new file mode 100644 index 0000000..190561c Binary files /dev/null and b/static/img/jvm-point.png differ diff --git a/static/img/jvm-runtime.png b/static/img/jvm-runtime.png new file mode 100644 index 0000000..7371698 Binary files /dev/null and b/static/img/jvm-runtime.png differ diff --git a/static/img/mvc-validator/adapter.jpg b/static/img/mvc-validator/adapter.jpg new file mode 100644 index 0000000..a9c0294 Binary files /dev/null and b/static/img/mvc-validator/adapter.jpg differ diff --git a/static/img/mvc-validator/driven.jpg b/static/img/mvc-validator/driven.jpg new file mode 100644 index 0000000..7e898a2 Binary files /dev/null and b/static/img/mvc-validator/driven.jpg differ diff --git a/static/img/mvc-validator/google.jpg b/static/img/mvc-validator/google.jpg new file mode 100644 index 0000000..920fd5b Binary files /dev/null and b/static/img/mvc-validator/google.jpg differ diff --git a/static/img/mvc-validator/hasErrors.jpg b/static/img/mvc-validator/hasErrors.jpg new file mode 100644 index 0000000..4a7cc0b Binary files /dev/null and b/static/img/mvc-validator/hasErrors.jpg differ diff --git a/static/img/mvc-validator/initBinder.jpg b/static/img/mvc-validator/initBinder.jpg new file mode 100644 index 0000000..76ad847 Binary files /dev/null and b/static/img/mvc-validator/initBinder.jpg differ diff --git a/static/img/mvc-validator/notNull.jpg b/static/img/mvc-validator/notNull.jpg new file mode 100644 index 0000000..c5ba297 Binary files /dev/null and b/static/img/mvc-validator/notNull.jpg differ diff --git a/static/img/mvc-validator/validator-config.jpg b/static/img/mvc-validator/validator-config.jpg new file mode 100644 index 0000000..4028151 Binary files /dev/null and b/static/img/mvc-validator/validator-config.jpg differ diff --git a/static/img/namespace.png b/static/img/namespace.png new file mode 100644 index 0000000..4860130 Binary files /dev/null and b/static/img/namespace.png differ diff --git a/static/img/rmi.jpg b/static/img/rmi.jpg new file mode 100644 index 0000000..3e82c9a Binary files /dev/null and b/static/img/rmi.jpg differ diff --git a/static/img/servlet-encode/body-iso.png b/static/img/servlet-encode/body-iso.png new file mode 100644 index 0000000..282ab03 Binary files /dev/null and b/static/img/servlet-encode/body-iso.png differ diff --git a/static/img/servlet-encode/body-utf.png b/static/img/servlet-encode/body-utf.png new file mode 100644 index 0000000..8c44780 Binary files /dev/null and b/static/img/servlet-encode/body-utf.png differ diff --git a/static/img/servlet-encode/encodeURI.png b/static/img/servlet-encode/encodeURI.png new file mode 100644 index 0000000..ad4de42 Binary files /dev/null and b/static/img/servlet-encode/encodeURI.png differ diff --git a/static/img/servlet-encode/get.png b/static/img/servlet-encode/get.png new file mode 100644 index 0000000..84d5a2d Binary files /dev/null and b/static/img/servlet-encode/get.png differ diff --git a/static/img/servlet-encode/param.png b/static/img/servlet-encode/param.png new file mode 100644 index 0000000..1e7d67d Binary files /dev/null and b/static/img/servlet-encode/param.png differ diff --git a/static/img/servlet-encode/post.png b/static/img/servlet-encode/post.png new file mode 100644 index 0000000..5e5e158 Binary files /dev/null and b/static/img/servlet-encode/post.png differ diff --git a/static/img/servlet-encode/world.png b/static/img/servlet-encode/world.png new file mode 100644 index 0000000..f6ba1c8 Binary files /dev/null and b/static/img/servlet-encode/world.png differ diff --git a/static/img/shell-trash.png b/static/img/shell-trash.png new file mode 100644 index 0000000..b36fc47 Binary files /dev/null and b/static/img/shell-trash.png differ diff --git a/static/img/spring-security-filter.png b/static/img/spring-security-filter.png new file mode 100644 index 0000000..df7b7aa Binary files /dev/null and b/static/img/spring-security-filter.png differ diff --git a/static/img/spring-security-filterChain.jpg b/static/img/spring-security-filterChain.jpg new file mode 100644 index 0000000..a2bfb7f Binary files /dev/null and b/static/img/spring-security-filterChain.jpg differ diff --git a/static/img/xml-data-type.jpg b/static/img/xml-data-type.jpg new file mode 100644 index 0000000..ff4acf1 Binary files /dev/null and b/static/img/xml-data-type.jpg differ diff --git a/static/img/xml-field-type.jpg b/static/img/xml-field-type.jpg new file mode 100644 index 0000000..915850e Binary files /dev/null and b/static/img/xml-field-type.jpg differ diff --git a/static/js/category.js b/static/js/category.js new file mode 100644 index 0000000..6333f20 --- /dev/null +++ b/static/js/category.js @@ -0,0 +1,39 @@ +/** + * 页面ready方法 + */ +$(document).ready(function() { + categoryDisplay(); +}); + +/** + * 分类展示 + * 点击右侧的分类展示时 + * 左侧的相关裂变展开或者收起 + * @return {[type]} [description] + */ +function categoryDisplay() { + selectCategory(); + $('.categories-item').click(function() { + window.location.hash = "#" + $(this).attr("cate"); + selectCategory(); + }); +} + +function selectCategory(){ + var exclude = ["",undefined]; + var thisId = window.location.hash.substring(1); + var allow = true; + for(var i in exclude){ + if(thisId == exclude[i]){ + allow = false; + break; + } + } + if(allow){ + var cate = thisId; + $("section[post-cate!='" + cate + "']").hide(200); + $("section[post-cate='" + cate + "']").show(200); + } else { + $("section[post-cate='All']").show(); + } +} \ No newline at end of file diff --git a/static/js/gitment.js b/static/js/gitment.js new file mode 100644 index 0000000..6bcaeb0 --- /dev/null +++ b/static/js/gitment.js @@ -0,0 +1,3751 @@ +var Gitment = + /******/ (function(modules) { // webpackBootstrap + /******/ // The module cache + /******/ var installedModules = {}; + /******/ + /******/ // The require function + /******/ function __webpack_require__(moduleId) { + /******/ + /******/ // Check if module is in cache + /******/ if(installedModules[moduleId]) + /******/ return installedModules[moduleId].exports; + /******/ + /******/ // Create a new module (and put it into the cache) + /******/ var module = installedModules[moduleId] = { + /******/ i: moduleId, + /******/ l: false, + /******/ exports: {} + /******/ }; + /******/ + /******/ // Execute the module function + /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + /******/ + /******/ // Flag the module as loaded + /******/ module.l = true; + /******/ + /******/ // Return the exports of the module + /******/ return module.exports; + /******/ } + /******/ + /******/ + /******/ // expose the modules object (__webpack_modules__) + /******/ __webpack_require__.m = modules; + /******/ + /******/ // expose the module cache + /******/ __webpack_require__.c = installedModules; + /******/ + /******/ // identity function for calling harmony imports with the correct context + /******/ __webpack_require__.i = function(value) { return value; }; + /******/ + /******/ // define getter function for harmony exports + /******/ __webpack_require__.d = function(exports, name, getter) { + /******/ if(!__webpack_require__.o(exports, name)) { + /******/ Object.defineProperty(exports, name, { + /******/ configurable: false, + /******/ enumerable: true, + /******/ get: getter + /******/ }); + /******/ } + /******/ }; + /******/ + /******/ // getDefaultExport function for compatibility with non-harmony modules + /******/ __webpack_require__.n = function(module) { + /******/ var getter = module && module.__esModule ? + /******/ function getDefault() { return module['default']; } : + /******/ function getModuleExports() { return module; }; + /******/ __webpack_require__.d(getter, 'a', getter); + /******/ return getter; + /******/ }; + /******/ + /******/ // Object.prototype.hasOwnProperty.call + /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; + /******/ + /******/ // __webpack_public_path__ + /******/ __webpack_require__.p = ""; + /******/ + /******/ // Load entry module and return exports + /******/ return __webpack_require__(__webpack_require__.s = 5); + /******/ }) +/************************************************************************/ +/******/ ([ + /* 0 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + + + Object.defineProperty(exports, "__esModule", { + value: true + }); + var LS_ACCESS_TOKEN_KEY = exports.LS_ACCESS_TOKEN_KEY = 'gitment-comments-token'; + var LS_USER_KEY = exports.LS_USER_KEY = 'gitment-user-info'; + + var NOT_INITIALIZED_ERROR = exports.NOT_INITIALIZED_ERROR = new Error('Comments Not Initialized'); + + /***/ }), + /* 1 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + /* WEBPACK VAR INJECTION */(function(global) { + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var __extends = undefined && undefined.__extends || function () { + var extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { + d.__proto__ = b; + } || function (d, b) { + for (var p in b) { + if (b.hasOwnProperty(p)) d[p] = b[p]; + } + }; + return function (d, b) { + extendStatics(d, b); + function __() { + this.constructor = d; + } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + }(); + Object.defineProperty(exports, "__esModule", { value: true }); + registerGlobals(); + exports.extras = { + allowStateChanges: allowStateChanges, + deepEqual: deepEqual, + getAtom: getAtom, + getDebugName: getDebugName, + getDependencyTree: getDependencyTree, + getAdministration: getAdministration, + getGlobalState: getGlobalState, + getObserverTree: getObserverTree, + isComputingDerivation: isComputingDerivation, + isSpyEnabled: isSpyEnabled, + onReactionError: onReactionError, + resetGlobalState: resetGlobalState, + shareGlobalState: shareGlobalState, + spyReport: spyReport, + spyReportEnd: spyReportEnd, + spyReportStart: spyReportStart, + setReactionScheduler: setReactionScheduler + }; + if ((typeof __MOBX_DEVTOOLS_GLOBAL_HOOK__ === "undefined" ? "undefined" : _typeof(__MOBX_DEVTOOLS_GLOBAL_HOOK__)) === "object") { + __MOBX_DEVTOOLS_GLOBAL_HOOK__.injectMobx(module.exports); + } + module.exports.default = module.exports; + var actionFieldDecorator = createClassPropertyDecorator(function (target, key, value, args, originalDescriptor) { + var actionName = args && args.length === 1 ? args[0] : value.name || key || ""; + var wrappedAction = action(actionName, value); + addHiddenProp(target, key, wrappedAction); + }, function (key) { + return this[key]; + }, function () { + invariant(false, getMessage("m001")); + }, false, true); + var boundActionDecorator = createClassPropertyDecorator(function (target, key, value) { + defineBoundAction(target, key, value); + }, function (key) { + return this[key]; + }, function () { + invariant(false, getMessage("m001")); + }, false, false); + var action = function action(arg1, arg2, arg3, arg4) { + if (arguments.length === 1 && typeof arg1 === "function") return createAction(arg1.name || "", arg1); + if (arguments.length === 2 && typeof arg2 === "function") return createAction(arg1, arg2); + if (arguments.length === 1 && typeof arg1 === "string") return namedActionDecorator(arg1); + return namedActionDecorator(arg2).apply(null, arguments); + }; + exports.action = action; + action.bound = function boundAction(arg1, arg2, arg3) { + if (typeof arg1 === "function") { + var action_1 = createAction("", arg1); + action_1.autoBind = true; + return action_1; + } + return boundActionDecorator.apply(null, arguments); + }; + function namedActionDecorator(name) { + return function (target, prop, descriptor) { + if (descriptor && typeof descriptor.value === "function") { + descriptor.value = createAction(name, descriptor.value); + descriptor.enumerable = false; + descriptor.configurable = true; + return descriptor; + } + return actionFieldDecorator(name).apply(this, arguments); + }; + } + function runInAction(arg1, arg2, arg3) { + var actionName = typeof arg1 === "string" ? arg1 : arg1.name || ""; + var fn = typeof arg1 === "function" ? arg1 : arg2; + var scope = typeof arg1 === "function" ? arg2 : arg3; + invariant(typeof fn === "function", getMessage("m002")); + invariant(fn.length === 0, getMessage("m003")); + invariant(typeof actionName === "string" && actionName.length > 0, "actions should have valid names, got: '" + actionName + "'"); + return executeAction(actionName, fn, scope, undefined); + } + exports.runInAction = runInAction; + function isAction(thing) { + return typeof thing === "function" && thing.isMobxAction === true; + } + exports.isAction = isAction; + function defineBoundAction(target, propertyName, fn) { + var res = function res() { + return executeAction(propertyName, fn, target, arguments); + }; + res.isMobxAction = true; + addHiddenProp(target, propertyName, res); + } + function autorun(arg1, arg2, arg3) { + var name, view, scope; + if (typeof arg1 === "string") { + name = arg1; + view = arg2; + scope = arg3; + } else { + name = arg1.name || "Autorun@" + getNextId(); + view = arg1; + scope = arg2; + } + invariant(typeof view === "function", getMessage("m004")); + invariant(isAction(view) === false, getMessage("m005")); + if (scope) view = view.bind(scope); + var reaction = new Reaction(name, function () { + this.track(reactionRunner); + }); + function reactionRunner() { + view(reaction); + } + reaction.schedule(); + return reaction.getDisposer(); + } + exports.autorun = autorun; + function when(arg1, arg2, arg3, arg4) { + var name, predicate, effect, scope; + if (typeof arg1 === "string") { + name = arg1; + predicate = arg2; + effect = arg3; + scope = arg4; + } else { + name = "When@" + getNextId(); + predicate = arg1; + effect = arg2; + scope = arg3; + } + var disposer = autorun(name, function (r) { + if (predicate.call(scope)) { + r.dispose(); + var prevUntracked = untrackedStart(); + effect.call(scope); + untrackedEnd(prevUntracked); + } + }); + return disposer; + } + exports.when = when; + function autorunAsync(arg1, arg2, arg3, arg4) { + var name, func, delay, scope; + if (typeof arg1 === "string") { + name = arg1; + func = arg2; + delay = arg3; + scope = arg4; + } else { + name = arg1.name || "AutorunAsync@" + getNextId(); + func = arg1; + delay = arg2; + scope = arg3; + } + invariant(isAction(func) === false, getMessage("m006")); + if (delay === void 0) delay = 1; + if (scope) func = func.bind(scope); + var isScheduled = false; + var r = new Reaction(name, function () { + if (!isScheduled) { + isScheduled = true; + setTimeout(function () { + isScheduled = false; + if (!r.isDisposed) r.track(reactionRunner); + }, delay); + } + }); + function reactionRunner() { + func(r); + } + r.schedule(); + return r.getDisposer(); + } + exports.autorunAsync = autorunAsync; + function reaction(expression, effect, arg3) { + if (arguments.length > 3) { + fail(getMessage("m007")); + } + if (isModifierDescriptor(expression)) { + fail(getMessage("m008")); + } + var opts; + if ((typeof arg3 === "undefined" ? "undefined" : _typeof(arg3)) === "object") { + opts = arg3; + } else { + opts = {}; + } + opts.name = opts.name || expression.name || effect.name || "Reaction@" + getNextId(); + opts.fireImmediately = arg3 === true || opts.fireImmediately === true; + opts.delay = opts.delay || 0; + opts.compareStructural = opts.compareStructural || opts.struct || false; + effect = action(opts.name, opts.context ? effect.bind(opts.context) : effect); + if (opts.context) { + expression = expression.bind(opts.context); + } + var firstTime = true; + var isScheduled = false; + var nextValue; + var r = new Reaction(opts.name, function () { + if (firstTime || opts.delay < 1) { + reactionRunner(); + } else if (!isScheduled) { + isScheduled = true; + setTimeout(function () { + isScheduled = false; + reactionRunner(); + }, opts.delay); + } + }); + function reactionRunner() { + if (r.isDisposed) return; + var changed = false; + r.track(function () { + var v = expression(r); + changed = valueDidChange(opts.compareStructural, nextValue, v); + nextValue = v; + }); + if (firstTime && opts.fireImmediately) effect(nextValue, r); + if (!firstTime && changed === true) effect(nextValue, r); + if (firstTime) firstTime = false; + } + r.schedule(); + return r.getDisposer(); + } + exports.reaction = reaction; + function createComputedDecorator(compareStructural) { + return createClassPropertyDecorator(function (target, name, _, __, originalDescriptor) { + invariant(typeof originalDescriptor !== "undefined", getMessage("m009")); + invariant(typeof originalDescriptor.get === "function", getMessage("m010")); + var adm = asObservableObject(target, ""); + defineComputedProperty(adm, name, originalDescriptor.get, originalDescriptor.set, compareStructural, false); + }, function (name) { + var observable = this.$mobx.values[name]; + if (observable === undefined) return undefined; + return observable.get(); + }, function (name, value) { + this.$mobx.values[name].set(value); + }, false, false); + } + var computedDecorator = createComputedDecorator(false); + var computedStructDecorator = createComputedDecorator(true); + var computed = function computed(arg1, arg2, arg3) { + if (typeof arg2 === "string") { + return computedDecorator.apply(null, arguments); + } + invariant(typeof arg1 === "function", getMessage("m011")); + invariant(arguments.length < 3, getMessage("m012")); + var opts = (typeof arg2 === "undefined" ? "undefined" : _typeof(arg2)) === "object" ? arg2 : {}; + opts.setter = typeof arg2 === "function" ? arg2 : opts.setter; + return new ComputedValue(arg1, opts.context, opts.compareStructural || opts.struct || false, opts.name || arg1.name || "", opts.setter); + }; + exports.computed = computed; + computed.struct = computedStructDecorator; + function createTransformer(transformer, onCleanup) { + invariant(typeof transformer === "function" && transformer.length < 2, "createTransformer expects a function that accepts one argument"); + var objectCache = {}; + var resetId = globalState.resetId; + var Transformer = function (_super) { + __extends(Transformer, _super); + function Transformer(sourceIdentifier, sourceObject) { + var _this = _super.call(this, function () { + return transformer(sourceObject); + }, undefined, false, "Transformer-" + transformer.name + "-" + sourceIdentifier, undefined) || this; + _this.sourceIdentifier = sourceIdentifier; + _this.sourceObject = sourceObject; + return _this; + } + Transformer.prototype.onBecomeUnobserved = function () { + var lastValue = this.value; + _super.prototype.onBecomeUnobserved.call(this); + delete objectCache[this.sourceIdentifier]; + if (onCleanup) onCleanup(lastValue, this.sourceObject); + }; + return Transformer; + }(ComputedValue); + return function (object) { + if (resetId !== globalState.resetId) { + objectCache = {}; + resetId = globalState.resetId; + } + var identifier = getMemoizationId(object); + var reactiveTransformer = objectCache[identifier]; + if (reactiveTransformer) return reactiveTransformer.get(); + reactiveTransformer = objectCache[identifier] = new Transformer(identifier, object); + return reactiveTransformer.get(); + }; + } + exports.createTransformer = createTransformer; + function getMemoizationId(object) { + if (object === null || (typeof object === "undefined" ? "undefined" : _typeof(object)) !== "object") throw new Error("[mobx] transform expected some kind of object, got: " + object); + var tid = object.$transformId; + if (tid === undefined) { + tid = getNextId(); + addHiddenProp(object, "$transformId", tid); + } + return tid; + } + function expr(expr, scope) { + if (!isComputingDerivation()) console.warn(getMessage("m013")); + return computed(expr, { context: scope }).get(); + } + exports.expr = expr; + function extendObservable(target) { + var properties = []; + for (var _i = 1; _i < arguments.length; _i++) { + properties[_i - 1] = arguments[_i]; + } + return extendObservableHelper(target, deepEnhancer, properties); + } + exports.extendObservable = extendObservable; + function extendShallowObservable(target) { + var properties = []; + for (var _i = 1; _i < arguments.length; _i++) { + properties[_i - 1] = arguments[_i]; + } + return extendObservableHelper(target, referenceEnhancer, properties); + } + exports.extendShallowObservable = extendShallowObservable; + function extendObservableHelper(target, defaultEnhancer, properties) { + invariant(arguments.length >= 2, getMessage("m014")); + invariant((typeof target === "undefined" ? "undefined" : _typeof(target)) === "object", getMessage("m015")); + invariant(!isObservableMap(target), getMessage("m016")); + properties.forEach(function (propSet) { + invariant((typeof propSet === "undefined" ? "undefined" : _typeof(propSet)) === "object", getMessage("m017")); + invariant(!isObservable(propSet), getMessage("m018")); + }); + var adm = asObservableObject(target); + var definedProps = {}; + for (var i = properties.length - 1; i >= 0; i--) { + var propSet = properties[i]; + for (var key in propSet) { + if (definedProps[key] !== true && hasOwnProperty(propSet, key)) { + definedProps[key] = true; + if (target === propSet && !isPropertyConfigurable(target, key)) continue; + var descriptor = Object.getOwnPropertyDescriptor(propSet, key); + defineObservablePropertyFromDescriptor(adm, key, descriptor, defaultEnhancer); + } + } + } + return target; + } + function getDependencyTree(thing, property) { + return nodeToDependencyTree(getAtom(thing, property)); + } + function nodeToDependencyTree(node) { + var result = { + name: node.name + }; + if (node.observing && node.observing.length > 0) result.dependencies = unique(node.observing).map(nodeToDependencyTree); + return result; + } + function getObserverTree(thing, property) { + return nodeToObserverTree(getAtom(thing, property)); + } + function nodeToObserverTree(node) { + var result = { + name: node.name + }; + if (hasObservers(node)) result.observers = getObservers(node).map(nodeToObserverTree); + return result; + } + function intercept(thing, propOrHandler, handler) { + if (typeof handler === "function") return interceptProperty(thing, propOrHandler, handler);else return interceptInterceptable(thing, propOrHandler); + } + exports.intercept = intercept; + function interceptInterceptable(thing, handler) { + return getAdministration(thing).intercept(handler); + } + function interceptProperty(thing, property, handler) { + return getAdministration(thing, property).intercept(handler); + } + function isComputed(value, property) { + if (value === null || value === undefined) return false; + if (property !== undefined) { + if (isObservableObject(value) === false) return false; + var atom = getAtom(value, property); + return isComputedValue(atom); + } + return isComputedValue(value); + } + exports.isComputed = isComputed; + function isObservable(value, property) { + if (value === null || value === undefined) return false; + if (property !== undefined) { + if (isObservableArray(value) || isObservableMap(value)) throw new Error(getMessage("m019"));else if (isObservableObject(value)) { + var o = value.$mobx; + return o.values && !!o.values[property]; + } + return false; + } + return isObservableObject(value) || !!value.$mobx || isAtom(value) || isReaction(value) || isComputedValue(value); + } + exports.isObservable = isObservable; + var deepDecorator = createDecoratorForEnhancer(deepEnhancer); + var shallowDecorator = createDecoratorForEnhancer(shallowEnhancer); + var refDecorator = createDecoratorForEnhancer(referenceEnhancer); + var deepStructDecorator = createDecoratorForEnhancer(deepStructEnhancer); + var refStructDecorator = createDecoratorForEnhancer(refStructEnhancer); + function createObservable(v) { + if (v === void 0) { + v = undefined; + } + if (typeof arguments[1] === "string") return deepDecorator.apply(null, arguments); + invariant(arguments.length <= 1, getMessage("m021")); + invariant(!isModifierDescriptor(v), getMessage("m020")); + if (isObservable(v)) return v; + var res = deepEnhancer(v, undefined, undefined); + if (res !== v) return res; + return observable.box(v); + } + var IObservableFactories = function () { + function IObservableFactories() {} + IObservableFactories.prototype.box = function (value, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("box"); + return new ObservableValue(value, deepEnhancer, name); + }; + IObservableFactories.prototype.shallowBox = function (value, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowBox"); + return new ObservableValue(value, referenceEnhancer, name); + }; + IObservableFactories.prototype.array = function (initialValues, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("array"); + return new ObservableArray(initialValues, deepEnhancer, name); + }; + IObservableFactories.prototype.shallowArray = function (initialValues, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowArray"); + return new ObservableArray(initialValues, referenceEnhancer, name); + }; + IObservableFactories.prototype.map = function (initialValues, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("map"); + return new ObservableMap(initialValues, deepEnhancer, name); + }; + IObservableFactories.prototype.shallowMap = function (initialValues, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowMap"); + return new ObservableMap(initialValues, referenceEnhancer, name); + }; + IObservableFactories.prototype.object = function (props, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("object"); + var res = {}; + asObservableObject(res, name); + extendObservable(res, props); + return res; + }; + IObservableFactories.prototype.shallowObject = function (props, name) { + if (arguments.length > 2) incorrectlyUsedAsDecorator("shallowObject"); + var res = {}; + asObservableObject(res, name); + extendShallowObservable(res, props); + return res; + }; + IObservableFactories.prototype.ref = function () { + if (arguments.length < 2) { + return createModifierDescriptor(referenceEnhancer, arguments[0]); + } else { + return refDecorator.apply(null, arguments); + } + }; + IObservableFactories.prototype.shallow = function () { + if (arguments.length < 2) { + return createModifierDescriptor(shallowEnhancer, arguments[0]); + } else { + return shallowDecorator.apply(null, arguments); + } + }; + IObservableFactories.prototype.deep = function () { + if (arguments.length < 2) { + return createModifierDescriptor(deepEnhancer, arguments[0]); + } else { + return deepDecorator.apply(null, arguments); + } + }; + IObservableFactories.prototype.struct = function () { + if (arguments.length < 2) { + return createModifierDescriptor(deepStructEnhancer, arguments[0]); + } else { + return deepStructDecorator.apply(null, arguments); + } + }; + return IObservableFactories; + }(); + exports.IObservableFactories = IObservableFactories; + var observable = createObservable; + exports.observable = observable; + Object.keys(IObservableFactories.prototype).forEach(function (key) { + return observable[key] = IObservableFactories.prototype[key]; + }); + observable.deep.struct = observable.struct; + observable.ref.struct = function () { + if (arguments.length < 2) { + return createModifierDescriptor(refStructEnhancer, arguments[0]); + } else { + return refStructDecorator.apply(null, arguments); + } + }; + function incorrectlyUsedAsDecorator(methodName) { + fail("Expected one or two arguments to observable." + methodName + ". Did you accidentally try to use observable." + methodName + " as decorator?"); + } + function createDecoratorForEnhancer(enhancer) { + invariant(!!enhancer, ":("); + return createClassPropertyDecorator(function (target, name, baseValue, _, baseDescriptor) { + assertPropertyConfigurable(target, name); + invariant(!baseDescriptor || !baseDescriptor.get, getMessage("m022")); + var adm = asObservableObject(target, undefined); + defineObservableProperty(adm, name, baseValue, enhancer); + }, function (name) { + var observable = this.$mobx.values[name]; + if (observable === undefined) return undefined; + return observable.get(); + }, function (name, value) { + setPropertyValue(this, name, value); + }, true, false); + } + function observe(thing, propOrCb, cbOrFire, fireImmediately) { + if (typeof cbOrFire === "function") return observeObservableProperty(thing, propOrCb, cbOrFire, fireImmediately);else return observeObservable(thing, propOrCb, cbOrFire); + } + exports.observe = observe; + function observeObservable(thing, listener, fireImmediately) { + return getAdministration(thing).observe(listener, fireImmediately); + } + function observeObservableProperty(thing, property, listener, fireImmediately) { + return getAdministration(thing, property).observe(listener, fireImmediately); + } + function toJS(source, detectCycles, __alreadySeen) { + if (detectCycles === void 0) { + detectCycles = true; + } + if (__alreadySeen === void 0) { + __alreadySeen = []; + } + function cache(value) { + if (detectCycles) __alreadySeen.push([source, value]); + return value; + } + if (isObservable(source)) { + if (detectCycles && __alreadySeen === null) __alreadySeen = []; + if (detectCycles && source !== null && (typeof source === "undefined" ? "undefined" : _typeof(source)) === "object") { + for (var i = 0, l = __alreadySeen.length; i < l; i++) { + if (__alreadySeen[i][0] === source) return __alreadySeen[i][1]; + } + } + if (isObservableArray(source)) { + var res = cache([]); + var toAdd = source.map(function (value) { + return toJS(value, detectCycles, __alreadySeen); + }); + res.length = toAdd.length; + for (var i = 0, l = toAdd.length; i < l; i++) { + res[i] = toAdd[i]; + }return res; + } + if (isObservableObject(source)) { + var res = cache({}); + for (var key in source) { + res[key] = toJS(source[key], detectCycles, __alreadySeen); + }return res; + } + if (isObservableMap(source)) { + var res_1 = cache({}); + source.forEach(function (value, key) { + return res_1[key] = toJS(value, detectCycles, __alreadySeen); + }); + return res_1; + } + if (isObservableValue(source)) return toJS(source.get(), detectCycles, __alreadySeen); + } + return source; + } + exports.toJS = toJS; + function transaction(action, thisArg) { + if (thisArg === void 0) { + thisArg = undefined; + } + deprecated(getMessage("m023")); + return runInTransaction.apply(undefined, arguments); + } + exports.transaction = transaction; + function runInTransaction(action, thisArg) { + if (thisArg === void 0) { + thisArg = undefined; + } + return executeAction("", action); + } + function log(msg) { + console.log(msg); + return msg; + } + function whyRun(thing, prop) { + switch (arguments.length) { + case 0: + thing = globalState.trackingDerivation; + if (!thing) return log(getMessage("m024")); + break; + case 2: + thing = getAtom(thing, prop); + break; + } + thing = getAtom(thing); + if (isComputedValue(thing)) return log(thing.whyRun());else if (isReaction(thing)) return log(thing.whyRun()); + return fail(getMessage("m025")); + } + exports.whyRun = whyRun; + function createAction(actionName, fn) { + invariant(typeof fn === "function", getMessage("m026")); + invariant(typeof actionName === "string" && actionName.length > 0, "actions should have valid names, got: '" + actionName + "'"); + var res = function res() { + return executeAction(actionName, fn, this, arguments); + }; + res.originalFn = fn; + res.isMobxAction = true; + return res; + } + function executeAction(actionName, fn, scope, args) { + var runInfo = startAction(actionName, fn, scope, args); + try { + return fn.apply(scope, args); + } finally { + endAction(runInfo); + } + } + function startAction(actionName, fn, scope, args) { + var notifySpy = isSpyEnabled() && !!actionName; + var startTime = 0; + if (notifySpy) { + startTime = Date.now(); + var l = args && args.length || 0; + var flattendArgs = new Array(l); + if (l > 0) for (var i = 0; i < l; i++) { + flattendArgs[i] = args[i]; + }spyReportStart({ + type: "action", + name: actionName, + fn: fn, + object: scope, + arguments: flattendArgs + }); + } + var prevDerivation = untrackedStart(); + startBatch(); + var prevAllowStateChanges = allowStateChangesStart(true); + return { + prevDerivation: prevDerivation, + prevAllowStateChanges: prevAllowStateChanges, + notifySpy: notifySpy, + startTime: startTime + }; + } + function endAction(runInfo) { + allowStateChangesEnd(runInfo.prevAllowStateChanges); + endBatch(); + untrackedEnd(runInfo.prevDerivation); + if (runInfo.notifySpy) spyReportEnd({ time: Date.now() - runInfo.startTime }); + } + function useStrict(strict) { + invariant(globalState.trackingDerivation === null, getMessage("m028")); + globalState.strictMode = strict; + globalState.allowStateChanges = !strict; + } + exports.useStrict = useStrict; + function isStrictModeEnabled() { + return globalState.strictMode; + } + exports.isStrictModeEnabled = isStrictModeEnabled; + function allowStateChanges(allowStateChanges, func) { + var prev = allowStateChangesStart(allowStateChanges); + var res; + try { + res = func(); + } finally { + allowStateChangesEnd(prev); + } + return res; + } + function allowStateChangesStart(allowStateChanges) { + var prev = globalState.allowStateChanges; + globalState.allowStateChanges = allowStateChanges; + return prev; + } + function allowStateChangesEnd(prev) { + globalState.allowStateChanges = prev; + } + var BaseAtom = function () { + function BaseAtom(name) { + if (name === void 0) { + name = "Atom@" + getNextId(); + } + this.name = name; + this.isPendingUnobservation = true; + this.observers = []; + this.observersIndexes = {}; + this.diffValue = 0; + this.lastAccessedBy = 0; + this.lowestObserverState = IDerivationState.NOT_TRACKING; + } + BaseAtom.prototype.onBecomeUnobserved = function () {}; + BaseAtom.prototype.reportObserved = function () { + reportObserved(this); + }; + BaseAtom.prototype.reportChanged = function () { + startBatch(); + propagateChanged(this); + endBatch(); + }; + BaseAtom.prototype.toString = function () { + return this.name; + }; + return BaseAtom; + }(); + exports.BaseAtom = BaseAtom; + var Atom = function (_super) { + __extends(Atom, _super); + function Atom(name, onBecomeObservedHandler, onBecomeUnobservedHandler) { + if (name === void 0) { + name = "Atom@" + getNextId(); + } + if (onBecomeObservedHandler === void 0) { + onBecomeObservedHandler = noop; + } + if (onBecomeUnobservedHandler === void 0) { + onBecomeUnobservedHandler = noop; + } + var _this = _super.call(this, name) || this; + _this.name = name; + _this.onBecomeObservedHandler = onBecomeObservedHandler; + _this.onBecomeUnobservedHandler = onBecomeUnobservedHandler; + _this.isPendingUnobservation = false; + _this.isBeingTracked = false; + return _this; + } + Atom.prototype.reportObserved = function () { + startBatch(); + _super.prototype.reportObserved.call(this); + if (!this.isBeingTracked) { + this.isBeingTracked = true; + this.onBecomeObservedHandler(); + } + endBatch(); + return !!globalState.trackingDerivation; + }; + Atom.prototype.onBecomeUnobserved = function () { + this.isBeingTracked = false; + this.onBecomeUnobservedHandler(); + }; + return Atom; + }(BaseAtom); + exports.Atom = Atom; + var isAtom = createInstanceofPredicate("Atom", BaseAtom); + var ComputedValue = function () { + function ComputedValue(derivation, scope, compareStructural, name, setter) { + this.derivation = derivation; + this.scope = scope; + this.compareStructural = compareStructural; + this.dependenciesState = IDerivationState.NOT_TRACKING; + this.observing = []; + this.newObserving = null; + this.isPendingUnobservation = false; + this.observers = []; + this.observersIndexes = {}; + this.diffValue = 0; + this.runId = 0; + this.lastAccessedBy = 0; + this.lowestObserverState = IDerivationState.UP_TO_DATE; + this.unboundDepsCount = 0; + this.__mapid = "#" + getNextId(); + this.value = undefined; + this.isComputing = false; + this.isRunningSetter = false; + this.name = name || "ComputedValue@" + getNextId(); + if (setter) this.setter = createAction(name + "-setter", setter); + } + ComputedValue.prototype.onBecomeStale = function () { + propagateMaybeChanged(this); + }; + ComputedValue.prototype.onBecomeUnobserved = function () { + invariant(this.dependenciesState !== IDerivationState.NOT_TRACKING, getMessage("m029")); + clearObserving(this); + this.value = undefined; + }; + ComputedValue.prototype.get = function () { + invariant(!this.isComputing, "Cycle detected in computation " + this.name, this.derivation); + if (globalState.inBatch === 0) { + startBatch(); + if (shouldCompute(this)) this.value = this.computeValue(false); + endBatch(); + } else { + reportObserved(this); + if (shouldCompute(this)) if (this.trackAndCompute()) propagateChangeConfirmed(this); + } + var result = this.value; + if (isCaughtException(result)) throw result.cause; + return result; + }; + ComputedValue.prototype.peek = function () { + var res = this.computeValue(false); + if (isCaughtException(res)) throw res.cause; + return res; + }; + ComputedValue.prototype.set = function (value) { + if (this.setter) { + invariant(!this.isRunningSetter, "The setter of computed value '" + this.name + "' is trying to update itself. Did you intend to update an _observable_ value, instead of the computed property?"); + this.isRunningSetter = true; + try { + this.setter.call(this.scope, value); + } finally { + this.isRunningSetter = false; + } + } else invariant(false, "[ComputedValue '" + this.name + "'] It is not possible to assign a new value to a computed value."); + }; + ComputedValue.prototype.trackAndCompute = function () { + if (isSpyEnabled()) { + spyReport({ + object: this.scope, + type: "compute", + fn: this.derivation + }); + } + var oldValue = this.value; + var newValue = this.value = this.computeValue(true); + return isCaughtException(newValue) || valueDidChange(this.compareStructural, newValue, oldValue); + }; + ComputedValue.prototype.computeValue = function (track) { + this.isComputing = true; + globalState.computationDepth++; + var res; + if (track) { + res = trackDerivedFunction(this, this.derivation, this.scope); + } else { + try { + res = this.derivation.call(this.scope); + } catch (e) { + res = new CaughtException(e); + } + } + globalState.computationDepth--; + this.isComputing = false; + return res; + }; + ; + ComputedValue.prototype.observe = function (listener, fireImmediately) { + var _this = this; + var firstTime = true; + var prevValue = undefined; + return autorun(function () { + var newValue = _this.get(); + if (!firstTime || fireImmediately) { + var prevU = untrackedStart(); + listener({ + type: "update", + object: _this, + newValue: newValue, + oldValue: prevValue + }); + untrackedEnd(prevU); + } + firstTime = false; + prevValue = newValue; + }); + }; + ComputedValue.prototype.toJSON = function () { + return this.get(); + }; + ComputedValue.prototype.toString = function () { + return this.name + "[" + this.derivation.toString() + "]"; + }; + ComputedValue.prototype.valueOf = function () { + return toPrimitive(this.get()); + }; + ; + ComputedValue.prototype.whyRun = function () { + var isTracking = Boolean(globalState.trackingDerivation); + var observing = unique(this.isComputing ? this.newObserving : this.observing).map(function (dep) { + return dep.name; + }); + var observers = unique(getObservers(this).map(function (dep) { + return dep.name; + })); + return "\nWhyRun? computation '" + this.name + "':\n * Running because: " + (isTracking ? "[active] the value of this computation is needed by a reaction" : this.isComputing ? "[get] The value of this computed was requested outside a reaction" : "[idle] not running at the moment") + "\n" + (this.dependenciesState === IDerivationState.NOT_TRACKING ? getMessage("m032") : " * This computation will re-run if any of the following observables changes:\n " + joinStrings(observing) + "\n " + (this.isComputing && isTracking ? " (... or any observable accessed during the remainder of the current run)" : "") + "\n\t" + getMessage("m038") + "\n\n * If the outcome of this computation changes, the following observers will be re-run:\n " + joinStrings(observers) + "\n"); + }; + return ComputedValue; + }(); + ComputedValue.prototype[primitiveSymbol()] = ComputedValue.prototype.valueOf; + var isComputedValue = createInstanceofPredicate("ComputedValue", ComputedValue); + var IDerivationState; + (function (IDerivationState) { + IDerivationState[IDerivationState["NOT_TRACKING"] = -1] = "NOT_TRACKING"; + IDerivationState[IDerivationState["UP_TO_DATE"] = 0] = "UP_TO_DATE"; + IDerivationState[IDerivationState["POSSIBLY_STALE"] = 1] = "POSSIBLY_STALE"; + IDerivationState[IDerivationState["STALE"] = 2] = "STALE"; + })(IDerivationState || (IDerivationState = {})); + exports.IDerivationState = IDerivationState; + var CaughtException = function () { + function CaughtException(cause) { + this.cause = cause; + } + return CaughtException; + }(); + function isCaughtException(e) { + return e instanceof CaughtException; + } + function shouldCompute(derivation) { + switch (derivation.dependenciesState) { + case IDerivationState.UP_TO_DATE: + return false; + case IDerivationState.NOT_TRACKING: + case IDerivationState.STALE: + return true; + case IDerivationState.POSSIBLY_STALE: + { + var prevUntracked = untrackedStart(); + var obs = derivation.observing, + l = obs.length; + for (var i = 0; i < l; i++) { + var obj = obs[i]; + if (isComputedValue(obj)) { + try { + obj.get(); + } catch (e) { + untrackedEnd(prevUntracked); + return true; + } + if (derivation.dependenciesState === IDerivationState.STALE) { + untrackedEnd(prevUntracked); + return true; + } + } + } + changeDependenciesStateTo0(derivation); + untrackedEnd(prevUntracked); + return false; + } + } + } + function isComputingDerivation() { + return globalState.trackingDerivation !== null; + } + function checkIfStateModificationsAreAllowed(atom) { + var hasObservers = atom.observers.length > 0; + if (globalState.computationDepth > 0 && hasObservers) fail(getMessage("m031") + atom.name); + if (!globalState.allowStateChanges && hasObservers) fail(getMessage(globalState.strictMode ? "m030a" : "m030b") + atom.name); + } + function trackDerivedFunction(derivation, f, context) { + changeDependenciesStateTo0(derivation); + derivation.newObserving = new Array(derivation.observing.length + 100); + derivation.unboundDepsCount = 0; + derivation.runId = ++globalState.runId; + var prevTracking = globalState.trackingDerivation; + globalState.trackingDerivation = derivation; + var result; + try { + result = f.call(context); + } catch (e) { + result = new CaughtException(e); + } + globalState.trackingDerivation = prevTracking; + bindDependencies(derivation); + return result; + } + function bindDependencies(derivation) { + var prevObserving = derivation.observing; + var observing = derivation.observing = derivation.newObserving; + derivation.newObserving = null; + var i0 = 0, + l = derivation.unboundDepsCount; + for (var i = 0; i < l; i++) { + var dep = observing[i]; + if (dep.diffValue === 0) { + dep.diffValue = 1; + if (i0 !== i) observing[i0] = dep; + i0++; + } + } + observing.length = i0; + l = prevObserving.length; + while (l--) { + var dep = prevObserving[l]; + if (dep.diffValue === 0) { + removeObserver(dep, derivation); + } + dep.diffValue = 0; + } + while (i0--) { + var dep = observing[i0]; + if (dep.diffValue === 1) { + dep.diffValue = 0; + addObserver(dep, derivation); + } + } + } + function clearObserving(derivation) { + var obs = derivation.observing; + var i = obs.length; + while (i--) { + removeObserver(obs[i], derivation); + }derivation.dependenciesState = IDerivationState.NOT_TRACKING; + obs.length = 0; + } + function untracked(action) { + var prev = untrackedStart(); + var res = action(); + untrackedEnd(prev); + return res; + } + exports.untracked = untracked; + function untrackedStart() { + var prev = globalState.trackingDerivation; + globalState.trackingDerivation = null; + return prev; + } + function untrackedEnd(prev) { + globalState.trackingDerivation = prev; + } + function changeDependenciesStateTo0(derivation) { + if (derivation.dependenciesState === IDerivationState.UP_TO_DATE) return; + derivation.dependenciesState = IDerivationState.UP_TO_DATE; + var obs = derivation.observing; + var i = obs.length; + while (i--) { + obs[i].lowestObserverState = IDerivationState.UP_TO_DATE; + } + } + var persistentKeys = ["mobxGuid", "resetId", "spyListeners", "strictMode", "runId"]; + var MobXGlobals = function () { + function MobXGlobals() { + this.version = 5; + this.trackingDerivation = null; + this.computationDepth = 0; + this.runId = 0; + this.mobxGuid = 0; + this.inBatch = 0; + this.pendingUnobservations = []; + this.pendingReactions = []; + this.isRunningReactions = false; + this.allowStateChanges = true; + this.strictMode = false; + this.resetId = 0; + this.spyListeners = []; + this.globalReactionErrorHandlers = []; + } + return MobXGlobals; + }(); + var globalState = new MobXGlobals(); + function shareGlobalState() { + var global = getGlobal(); + var ownState = globalState; + if (global.__mobservableTrackingStack || global.__mobservableViewStack) throw new Error("[mobx] An incompatible version of mobservable is already loaded."); + if (global.__mobxGlobal && global.__mobxGlobal.version !== ownState.version) throw new Error("[mobx] An incompatible version of mobx is already loaded."); + if (global.__mobxGlobal) globalState = global.__mobxGlobal;else global.__mobxGlobal = ownState; + } + function getGlobalState() { + return globalState; + } + function registerGlobals() {} + function resetGlobalState() { + globalState.resetId++; + var defaultGlobals = new MobXGlobals(); + for (var key in defaultGlobals) { + if (persistentKeys.indexOf(key) === -1) globalState[key] = defaultGlobals[key]; + }globalState.allowStateChanges = !globalState.strictMode; + } + function hasObservers(observable) { + return observable.observers && observable.observers.length > 0; + } + function getObservers(observable) { + return observable.observers; + } + function invariantObservers(observable) { + var list = observable.observers; + var map = observable.observersIndexes; + var l = list.length; + for (var i = 0; i < l; i++) { + var id = list[i].__mapid; + if (i) { + invariant(map[id] === i, "INTERNAL ERROR maps derivation.__mapid to index in list"); + } else { + invariant(!(id in map), "INTERNAL ERROR observer on index 0 shouldnt be held in map."); + } + } + invariant(list.length === 0 || Object.keys(map).length === list.length - 1, "INTERNAL ERROR there is no junk in map"); + } + function addObserver(observable, node) { + var l = observable.observers.length; + if (l) { + observable.observersIndexes[node.__mapid] = l; + } + observable.observers[l] = node; + if (observable.lowestObserverState > node.dependenciesState) observable.lowestObserverState = node.dependenciesState; + } + function removeObserver(observable, node) { + if (observable.observers.length === 1) { + observable.observers.length = 0; + queueForUnobservation(observable); + } else { + var list = observable.observers; + var map_1 = observable.observersIndexes; + var filler = list.pop(); + if (filler !== node) { + var index = map_1[node.__mapid] || 0; + if (index) { + map_1[filler.__mapid] = index; + } else { + delete map_1[filler.__mapid]; + } + list[index] = filler; + } + delete map_1[node.__mapid]; + } + } + function queueForUnobservation(observable) { + if (!observable.isPendingUnobservation) { + observable.isPendingUnobservation = true; + globalState.pendingUnobservations.push(observable); + } + } + function startBatch() { + globalState.inBatch++; + } + function endBatch() { + if (--globalState.inBatch === 0) { + runReactions(); + var list = globalState.pendingUnobservations; + for (var i = 0; i < list.length; i++) { + var observable_1 = list[i]; + observable_1.isPendingUnobservation = false; + if (observable_1.observers.length === 0) { + observable_1.onBecomeUnobserved(); + } + } + globalState.pendingUnobservations = []; + } + } + function reportObserved(observable) { + var derivation = globalState.trackingDerivation; + if (derivation !== null) { + if (derivation.runId !== observable.lastAccessedBy) { + observable.lastAccessedBy = derivation.runId; + derivation.newObserving[derivation.unboundDepsCount++] = observable; + } + } else if (observable.observers.length === 0) { + queueForUnobservation(observable); + } + } + function invariantLOS(observable, msg) { + var min = getObservers(observable).reduce(function (a, b) { + return Math.min(a, b.dependenciesState); + }, 2); + if (min >= observable.lowestObserverState) return; + throw new Error("lowestObserverState is wrong for " + msg + " because " + min + " < " + observable.lowestObserverState); + } + function propagateChanged(observable) { + if (observable.lowestObserverState === IDerivationState.STALE) return; + observable.lowestObserverState = IDerivationState.STALE; + var observers = observable.observers; + var i = observers.length; + while (i--) { + var d = observers[i]; + if (d.dependenciesState === IDerivationState.UP_TO_DATE) d.onBecomeStale(); + d.dependenciesState = IDerivationState.STALE; + } + } + function propagateChangeConfirmed(observable) { + if (observable.lowestObserverState === IDerivationState.STALE) return; + observable.lowestObserverState = IDerivationState.STALE; + var observers = observable.observers; + var i = observers.length; + while (i--) { + var d = observers[i]; + if (d.dependenciesState === IDerivationState.POSSIBLY_STALE) d.dependenciesState = IDerivationState.STALE;else if (d.dependenciesState === IDerivationState.UP_TO_DATE) observable.lowestObserverState = IDerivationState.UP_TO_DATE; + } + } + function propagateMaybeChanged(observable) { + if (observable.lowestObserverState !== IDerivationState.UP_TO_DATE) return; + observable.lowestObserverState = IDerivationState.POSSIBLY_STALE; + var observers = observable.observers; + var i = observers.length; + while (i--) { + var d = observers[i]; + if (d.dependenciesState === IDerivationState.UP_TO_DATE) { + d.dependenciesState = IDerivationState.POSSIBLY_STALE; + d.onBecomeStale(); + } + } + } + var Reaction = function () { + function Reaction(name, onInvalidate) { + if (name === void 0) { + name = "Reaction@" + getNextId(); + } + this.name = name; + this.onInvalidate = onInvalidate; + this.observing = []; + this.newObserving = []; + this.dependenciesState = IDerivationState.NOT_TRACKING; + this.diffValue = 0; + this.runId = 0; + this.unboundDepsCount = 0; + this.__mapid = "#" + getNextId(); + this.isDisposed = false; + this._isScheduled = false; + this._isTrackPending = false; + this._isRunning = false; + } + Reaction.prototype.onBecomeStale = function () { + this.schedule(); + }; + Reaction.prototype.schedule = function () { + if (!this._isScheduled) { + this._isScheduled = true; + globalState.pendingReactions.push(this); + runReactions(); + } + }; + Reaction.prototype.isScheduled = function () { + return this._isScheduled; + }; + Reaction.prototype.runReaction = function () { + if (!this.isDisposed) { + startBatch(); + this._isScheduled = false; + if (shouldCompute(this)) { + this._isTrackPending = true; + this.onInvalidate(); + if (this._isTrackPending && isSpyEnabled()) { + spyReport({ + object: this, + type: "scheduled-reaction" + }); + } + } + endBatch(); + } + }; + Reaction.prototype.track = function (fn) { + startBatch(); + var notify = isSpyEnabled(); + var startTime; + if (notify) { + startTime = Date.now(); + spyReportStart({ + object: this, + type: "reaction", + fn: fn + }); + } + this._isRunning = true; + var result = trackDerivedFunction(this, fn, undefined); + this._isRunning = false; + this._isTrackPending = false; + if (this.isDisposed) { + clearObserving(this); + } + if (isCaughtException(result)) this.reportExceptionInDerivation(result.cause); + if (notify) { + spyReportEnd({ + time: Date.now() - startTime + }); + } + endBatch(); + }; + Reaction.prototype.reportExceptionInDerivation = function (error) { + var _this = this; + if (this.errorHandler) { + this.errorHandler(error, this); + return; + } + var message = "[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: '" + this; + var messageToUser = getMessage("m037"); + console.error(message || messageToUser, error); + if (isSpyEnabled()) { + spyReport({ + type: "error", + message: message, + error: error, + object: this + }); + } + globalState.globalReactionErrorHandlers.forEach(function (f) { + return f(error, _this); + }); + }; + Reaction.prototype.dispose = function () { + if (!this.isDisposed) { + this.isDisposed = true; + if (!this._isRunning) { + startBatch(); + clearObserving(this); + endBatch(); + } + } + }; + Reaction.prototype.getDisposer = function () { + var r = this.dispose.bind(this); + r.$mobx = this; + r.onError = registerErrorHandler; + return r; + }; + Reaction.prototype.toString = function () { + return "Reaction[" + this.name + "]"; + }; + Reaction.prototype.whyRun = function () { + var observing = unique(this._isRunning ? this.newObserving : this.observing).map(function (dep) { + return dep.name; + }); + return "\nWhyRun? reaction '" + this.name + "':\n * Status: [" + (this.isDisposed ? "stopped" : this._isRunning ? "running" : this.isScheduled() ? "scheduled" : "idle") + "]\n * This reaction will re-run if any of the following observables changes:\n " + joinStrings(observing) + "\n " + (this._isRunning ? " (... or any observable accessed during the remainder of the current run)" : "") + "\n\t" + getMessage("m038") + "\n"; + }; + return Reaction; + }(); + exports.Reaction = Reaction; + function registerErrorHandler(handler) { + invariant(this && this.$mobx && isReaction(this.$mobx), "Invalid `this`"); + invariant(!this.$mobx.errorHandler, "Only one onErrorHandler can be registered"); + this.$mobx.errorHandler = handler; + } + function onReactionError(handler) { + globalState.globalReactionErrorHandlers.push(handler); + return function () { + var idx = globalState.globalReactionErrorHandlers.indexOf(handler); + if (idx >= 0) globalState.globalReactionErrorHandlers.splice(idx, 1); + }; + } + var MAX_REACTION_ITERATIONS = 100; + var reactionScheduler = function reactionScheduler(f) { + return f(); + }; + function runReactions() { + if (globalState.inBatch > 0 || globalState.isRunningReactions) return; + reactionScheduler(runReactionsHelper); + } + function runReactionsHelper() { + globalState.isRunningReactions = true; + var allReactions = globalState.pendingReactions; + var iterations = 0; + while (allReactions.length > 0) { + if (++iterations === MAX_REACTION_ITERATIONS) { + console.error("Reaction doesn't converge to a stable state after " + MAX_REACTION_ITERATIONS + " iterations." + (" Probably there is a cycle in the reactive function: " + allReactions[0])); + allReactions.splice(0); + } + var remainingReactions = allReactions.splice(0); + for (var i = 0, l = remainingReactions.length; i < l; i++) { + remainingReactions[i].runReaction(); + } + } + globalState.isRunningReactions = false; + } + var isReaction = createInstanceofPredicate("Reaction", Reaction); + function setReactionScheduler(fn) { + var baseScheduler = reactionScheduler; + reactionScheduler = function reactionScheduler(f) { + return fn(function () { + return baseScheduler(f); + }); + }; + } + function isSpyEnabled() { + return !!globalState.spyListeners.length; + } + function spyReport(event) { + if (!globalState.spyListeners.length) return; + var listeners = globalState.spyListeners; + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i](event); + } + } + function spyReportStart(event) { + var change = objectAssign({}, event, { spyReportStart: true }); + spyReport(change); + } + var END_EVENT = { spyReportEnd: true }; + function spyReportEnd(change) { + if (change) spyReport(objectAssign({}, change, END_EVENT));else spyReport(END_EVENT); + } + function spy(listener) { + globalState.spyListeners.push(listener); + return once(function () { + var idx = globalState.spyListeners.indexOf(listener); + if (idx !== -1) globalState.spyListeners.splice(idx, 1); + }); + } + exports.spy = spy; + function hasInterceptors(interceptable) { + return interceptable.interceptors && interceptable.interceptors.length > 0; + } + function registerInterceptor(interceptable, handler) { + var interceptors = interceptable.interceptors || (interceptable.interceptors = []); + interceptors.push(handler); + return once(function () { + var idx = interceptors.indexOf(handler); + if (idx !== -1) interceptors.splice(idx, 1); + }); + } + function interceptChange(interceptable, change) { + var prevU = untrackedStart(); + try { + var interceptors = interceptable.interceptors; + if (interceptors) for (var i = 0, l = interceptors.length; i < l; i++) { + change = interceptors[i](change); + invariant(!change || change.type, "Intercept handlers should return nothing or a change object"); + if (!change) break; + } + return change; + } finally { + untrackedEnd(prevU); + } + } + function hasListeners(listenable) { + return listenable.changeListeners && listenable.changeListeners.length > 0; + } + function registerListener(listenable, handler) { + var listeners = listenable.changeListeners || (listenable.changeListeners = []); + listeners.push(handler); + return once(function () { + var idx = listeners.indexOf(handler); + if (idx !== -1) listeners.splice(idx, 1); + }); + } + function notifyListeners(listenable, change) { + var prevU = untrackedStart(); + var listeners = listenable.changeListeners; + if (!listeners) return; + listeners = listeners.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i](change); + } + untrackedEnd(prevU); + } + function asReference(value) { + deprecated("asReference is deprecated, use observable.ref instead"); + return observable.ref(value); + } + exports.asReference = asReference; + function asStructure(value) { + deprecated("asStructure is deprecated. Use observable.struct, computed.struct or reaction options instead."); + return observable.struct(value); + } + exports.asStructure = asStructure; + function asFlat(value) { + deprecated("asFlat is deprecated, use observable.shallow instead"); + return observable.shallow(value); + } + exports.asFlat = asFlat; + function asMap(data) { + deprecated("asMap is deprecated, use observable.map or observable.shallowMap instead"); + return observable.map(data || {}); + } + exports.asMap = asMap; + function isModifierDescriptor(thing) { + return (typeof thing === "undefined" ? "undefined" : _typeof(thing)) === "object" && thing !== null && thing.isMobxModifierDescriptor === true; + } + exports.isModifierDescriptor = isModifierDescriptor; + function createModifierDescriptor(enhancer, initialValue) { + invariant(!isModifierDescriptor(initialValue), "Modifiers cannot be nested"); + return { + isMobxModifierDescriptor: true, + initialValue: initialValue, + enhancer: enhancer + }; + } + function deepEnhancer(v, _, name) { + if (isModifierDescriptor(v)) fail("You tried to assign a modifier wrapped value to a collection, please define modifiers when creating the collection, not when modifying it"); + if (isObservable(v)) return v; + if (Array.isArray(v)) return observable.array(v, name); + if (isPlainObject(v)) return observable.object(v, name); + if (isES6Map(v)) return observable.map(v, name); + return v; + } + function shallowEnhancer(v, _, name) { + if (isModifierDescriptor(v)) fail("You tried to assign a modifier wrapped value to a collection, please define modifiers when creating the collection, not when modifying it"); + if (v === undefined || v === null) return v; + if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v)) return v; + if (Array.isArray(v)) return observable.shallowArray(v, name); + if (isPlainObject(v)) return observable.shallowObject(v, name); + if (isES6Map(v)) return observable.shallowMap(v, name); + return fail("The shallow modifier / decorator can only used in combination with arrays, objects and maps"); + } + function referenceEnhancer(newValue) { + return newValue; + } + function deepStructEnhancer(v, oldValue, name) { + if (deepEqual(v, oldValue)) return oldValue; + if (isObservable(v)) return v; + if (Array.isArray(v)) return new ObservableArray(v, deepStructEnhancer, name); + if (isES6Map(v)) return new ObservableMap(v, deepStructEnhancer, name); + if (isPlainObject(v)) { + var res = {}; + asObservableObject(res, name); + extendObservableHelper(res, deepStructEnhancer, [v]); + return res; + } + return v; + } + function refStructEnhancer(v, oldValue, name) { + if (deepEqual(v, oldValue)) return oldValue; + return v; + } + var MAX_SPLICE_SIZE = 10000; + var safariPrototypeSetterInheritanceBug = function () { + var v = false; + var p = {}; + Object.defineProperty(p, "0", { set: function set() { + v = true; + } }); + Object.create(p)["0"] = 1; + return v === false; + }(); + var OBSERVABLE_ARRAY_BUFFER_SIZE = 0; + var StubArray = function () { + function StubArray() {} + return StubArray; + }(); + StubArray.prototype = []; + var ObservableArrayAdministration = function () { + function ObservableArrayAdministration(name, enhancer, array, owned) { + this.array = array; + this.owned = owned; + this.lastKnownLength = 0; + this.interceptors = null; + this.changeListeners = null; + this.atom = new BaseAtom(name || "ObservableArray@" + getNextId()); + this.enhancer = function (newV, oldV) { + return enhancer(newV, oldV, name + "[..]"); + }; + } + ObservableArrayAdministration.prototype.intercept = function (handler) { + return registerInterceptor(this, handler); + }; + ObservableArrayAdministration.prototype.observe = function (listener, fireImmediately) { + if (fireImmediately === void 0) { + fireImmediately = false; + } + if (fireImmediately) { + listener({ + object: this.array, + type: "splice", + index: 0, + added: this.values.slice(), + addedCount: this.values.length, + removed: [], + removedCount: 0 + }); + } + return registerListener(this, listener); + }; + ObservableArrayAdministration.prototype.getArrayLength = function () { + this.atom.reportObserved(); + return this.values.length; + }; + ObservableArrayAdministration.prototype.setArrayLength = function (newLength) { + if (typeof newLength !== "number" || newLength < 0) throw new Error("[mobx.array] Out of range: " + newLength); + var currentLength = this.values.length; + if (newLength === currentLength) return;else if (newLength > currentLength) { + var newItems = new Array(newLength - currentLength); + for (var i = 0; i < newLength - currentLength; i++) { + newItems[i] = undefined; + }this.spliceWithArray(currentLength, 0, newItems); + } else this.spliceWithArray(newLength, currentLength - newLength); + }; + ObservableArrayAdministration.prototype.updateArrayLength = function (oldLength, delta) { + if (oldLength !== this.lastKnownLength) throw new Error("[mobx] Modification exception: the internal structure of an observable array was changed. Did you use peek() to change it?"); + this.lastKnownLength += delta; + if (delta > 0 && oldLength + delta + 1 > OBSERVABLE_ARRAY_BUFFER_SIZE) reserveArrayBuffer(oldLength + delta + 1); + }; + ObservableArrayAdministration.prototype.spliceWithArray = function (index, deleteCount, newItems) { + var _this = this; + checkIfStateModificationsAreAllowed(this.atom); + var length = this.values.length; + if (index === undefined) index = 0;else if (index > length) index = length;else if (index < 0) index = Math.max(0, length + index); + if (arguments.length === 1) deleteCount = length - index;else if (deleteCount === undefined || deleteCount === null) deleteCount = 0;else deleteCount = Math.max(0, Math.min(deleteCount, length - index)); + if (newItems === undefined) newItems = []; + if (hasInterceptors(this)) { + var change = interceptChange(this, { + object: this.array, + type: "splice", + index: index, + removedCount: deleteCount, + added: newItems + }); + if (!change) return EMPTY_ARRAY; + deleteCount = change.removedCount; + newItems = change.added; + } + newItems = newItems.map(function (v) { + return _this.enhancer(v, undefined); + }); + var lengthDelta = newItems.length - deleteCount; + this.updateArrayLength(length, lengthDelta); + var res = this.spliceItemsIntoValues(index, deleteCount, newItems); + if (deleteCount !== 0 || newItems.length !== 0) this.notifyArraySplice(index, newItems, res); + return res; + }; + ObservableArrayAdministration.prototype.spliceItemsIntoValues = function (index, deleteCount, newItems) { + if (newItems.length < MAX_SPLICE_SIZE) { + return (_a = this.values).splice.apply(_a, [index, deleteCount].concat(newItems)); + } else { + var res = this.values.slice(index, index + deleteCount); + this.values = this.values.slice(0, index).concat(newItems, this.values.slice(index + deleteCount)); + return res; + } + var _a; + }; + ObservableArrayAdministration.prototype.notifyArrayChildUpdate = function (index, newValue, oldValue) { + var notifySpy = !this.owned && isSpyEnabled(); + var notify = hasListeners(this); + var change = notify || notifySpy ? { + object: this.array, + type: "update", + index: index, newValue: newValue, oldValue: oldValue + } : null; + if (notifySpy) spyReportStart(change); + this.atom.reportChanged(); + if (notify) notifyListeners(this, change); + if (notifySpy) spyReportEnd(); + }; + ObservableArrayAdministration.prototype.notifyArraySplice = function (index, added, removed) { + var notifySpy = !this.owned && isSpyEnabled(); + var notify = hasListeners(this); + var change = notify || notifySpy ? { + object: this.array, + type: "splice", + index: index, removed: removed, added: added, + removedCount: removed.length, + addedCount: added.length + } : null; + if (notifySpy) spyReportStart(change); + this.atom.reportChanged(); + if (notify) notifyListeners(this, change); + if (notifySpy) spyReportEnd(); + }; + return ObservableArrayAdministration; + }(); + var ObservableArray = function (_super) { + __extends(ObservableArray, _super); + function ObservableArray(initialValues, enhancer, name, owned) { + if (name === void 0) { + name = "ObservableArray@" + getNextId(); + } + if (owned === void 0) { + owned = false; + } + var _this = _super.call(this) || this; + var adm = new ObservableArrayAdministration(name, enhancer, _this, owned); + addHiddenFinalProp(_this, "$mobx", adm); + if (initialValues && initialValues.length) { + adm.updateArrayLength(0, initialValues.length); + adm.values = initialValues.map(function (v) { + return enhancer(v, undefined, name + "[..]"); + }); + adm.notifyArraySplice(0, adm.values.slice(), EMPTY_ARRAY); + } else { + adm.values = []; + } + if (safariPrototypeSetterInheritanceBug) { + Object.defineProperty(adm.array, "0", ENTRY_0); + } + return _this; + } + ObservableArray.prototype.intercept = function (handler) { + return this.$mobx.intercept(handler); + }; + ObservableArray.prototype.observe = function (listener, fireImmediately) { + if (fireImmediately === void 0) { + fireImmediately = false; + } + return this.$mobx.observe(listener, fireImmediately); + }; + ObservableArray.prototype.clear = function () { + return this.splice(0); + }; + ObservableArray.prototype.concat = function () { + var arrays = []; + for (var _i = 0; _i < arguments.length; _i++) { + arrays[_i] = arguments[_i]; + } + this.$mobx.atom.reportObserved(); + return Array.prototype.concat.apply(this.peek(), arrays.map(function (a) { + return isObservableArray(a) ? a.peek() : a; + })); + }; + ObservableArray.prototype.replace = function (newItems) { + return this.$mobx.spliceWithArray(0, this.$mobx.values.length, newItems); + }; + ObservableArray.prototype.toJS = function () { + return this.slice(); + }; + ObservableArray.prototype.toJSON = function () { + return this.toJS(); + }; + ObservableArray.prototype.peek = function () { + return this.$mobx.values; + }; + ObservableArray.prototype.find = function (predicate, thisArg, fromIndex) { + if (fromIndex === void 0) { + fromIndex = 0; + } + this.$mobx.atom.reportObserved(); + var items = this.$mobx.values, + l = items.length; + for (var i = fromIndex; i < l; i++) { + if (predicate.call(thisArg, items[i], i, this)) return items[i]; + }return undefined; + }; + ObservableArray.prototype.splice = function (index, deleteCount) { + var newItems = []; + for (var _i = 2; _i < arguments.length; _i++) { + newItems[_i - 2] = arguments[_i]; + } + switch (arguments.length) { + case 0: + return []; + case 1: + return this.$mobx.spliceWithArray(index); + case 2: + return this.$mobx.spliceWithArray(index, deleteCount); + } + return this.$mobx.spliceWithArray(index, deleteCount, newItems); + }; + ObservableArray.prototype.spliceWithArray = function (index, deleteCount, newItems) { + return this.$mobx.spliceWithArray(index, deleteCount, newItems); + }; + ObservableArray.prototype.push = function () { + var items = []; + for (var _i = 0; _i < arguments.length; _i++) { + items[_i] = arguments[_i]; + } + var adm = this.$mobx; + adm.spliceWithArray(adm.values.length, 0, items); + return adm.values.length; + }; + ObservableArray.prototype.pop = function () { + return this.splice(Math.max(this.$mobx.values.length - 1, 0), 1)[0]; + }; + ObservableArray.prototype.shift = function () { + return this.splice(0, 1)[0]; + }; + ObservableArray.prototype.unshift = function () { + var items = []; + for (var _i = 0; _i < arguments.length; _i++) { + items[_i] = arguments[_i]; + } + var adm = this.$mobx; + adm.spliceWithArray(0, 0, items); + return adm.values.length; + }; + ObservableArray.prototype.reverse = function () { + this.$mobx.atom.reportObserved(); + var clone = this.slice(); + return clone.reverse.apply(clone, arguments); + }; + ObservableArray.prototype.sort = function (compareFn) { + this.$mobx.atom.reportObserved(); + var clone = this.slice(); + return clone.sort.apply(clone, arguments); + }; + ObservableArray.prototype.remove = function (value) { + var idx = this.$mobx.values.indexOf(value); + if (idx > -1) { + this.splice(idx, 1); + return true; + } + return false; + }; + ObservableArray.prototype.move = function (fromIndex, toIndex) { + function checkIndex(index) { + if (index < 0) { + throw new Error("[mobx.array] Index out of bounds: " + index + " is negative"); + } + var length = this.$mobx.values.length; + if (index >= length) { + throw new Error("[mobx.array] Index out of bounds: " + index + " is not smaller than " + length); + } + } + checkIndex.call(this, fromIndex); + checkIndex.call(this, toIndex); + if (fromIndex === toIndex) { + return; + } + var oldItems = this.$mobx.values; + var newItems; + if (fromIndex < toIndex) { + newItems = oldItems.slice(0, fromIndex).concat(oldItems.slice(fromIndex + 1, toIndex + 1), [oldItems[fromIndex]], oldItems.slice(toIndex + 1)); + } else { + newItems = oldItems.slice(0, toIndex).concat([oldItems[fromIndex]], oldItems.slice(toIndex, fromIndex), oldItems.slice(fromIndex + 1)); + } + this.replace(newItems); + }; + ObservableArray.prototype.toString = function () { + this.$mobx.atom.reportObserved(); + return Array.prototype.toString.apply(this.$mobx.values, arguments); + }; + ObservableArray.prototype.toLocaleString = function () { + this.$mobx.atom.reportObserved(); + return Array.prototype.toLocaleString.apply(this.$mobx.values, arguments); + }; + return ObservableArray; + }(StubArray); + declareIterator(ObservableArray.prototype, function () { + return arrayAsIterator(this.slice()); + }); + makeNonEnumerable(ObservableArray.prototype, ["constructor", "intercept", "observe", "clear", "concat", "replace", "toJS", "toJSON", "peek", "find", "splice", "spliceWithArray", "push", "pop", "shift", "unshift", "reverse", "sort", "remove", "move", "toString", "toLocaleString"]); + Object.defineProperty(ObservableArray.prototype, "length", { + enumerable: false, + configurable: true, + get: function get() { + return this.$mobx.getArrayLength(); + }, + set: function set(newLength) { + this.$mobx.setArrayLength(newLength); + } + }); + ["every", "filter", "forEach", "indexOf", "join", "lastIndexOf", "map", "reduce", "reduceRight", "slice", "some"].forEach(function (funcName) { + var baseFunc = Array.prototype[funcName]; + invariant(typeof baseFunc === "function", "Base function not defined on Array prototype: '" + funcName + "'"); + addHiddenProp(ObservableArray.prototype, funcName, function () { + this.$mobx.atom.reportObserved(); + return baseFunc.apply(this.$mobx.values, arguments); + }); + }); + var ENTRY_0 = { + configurable: true, + enumerable: false, + set: createArraySetter(0), + get: createArrayGetter(0) + }; + function createArrayBufferItem(index) { + var set = createArraySetter(index); + var get = createArrayGetter(index); + Object.defineProperty(ObservableArray.prototype, "" + index, { + enumerable: false, + configurable: true, + set: set, get: get + }); + } + function createArraySetter(index) { + return function (newValue) { + var adm = this.$mobx; + var values = adm.values; + if (index < values.length) { + checkIfStateModificationsAreAllowed(adm.atom); + var oldValue = values[index]; + if (hasInterceptors(adm)) { + var change = interceptChange(adm, { + type: "update", + object: adm.array, + index: index, newValue: newValue + }); + if (!change) return; + newValue = change.newValue; + } + newValue = adm.enhancer(newValue, oldValue); + var changed = newValue !== oldValue; + if (changed) { + values[index] = newValue; + adm.notifyArrayChildUpdate(index, newValue, oldValue); + } + } else if (index === values.length) { + adm.spliceWithArray(index, 0, [newValue]); + } else throw new Error("[mobx.array] Index out of bounds, " + index + " is larger than " + values.length); + }; + } + function createArrayGetter(index) { + return function () { + var impl = this.$mobx; + if (impl) { + if (index < impl.values.length) { + impl.atom.reportObserved(); + return impl.values[index]; + } + console.warn("[mobx.array] Attempt to read an array index (" + index + ") that is out of bounds (" + impl.values.length + "). Please check length first. Out of bound indices will not be tracked by MobX"); + } + return undefined; + }; + } + function reserveArrayBuffer(max) { + for (var index = OBSERVABLE_ARRAY_BUFFER_SIZE; index < max; index++) { + createArrayBufferItem(index); + }OBSERVABLE_ARRAY_BUFFER_SIZE = max; + } + reserveArrayBuffer(1000); + var isObservableArrayAdministration = createInstanceofPredicate("ObservableArrayAdministration", ObservableArrayAdministration); + function isObservableArray(thing) { + return isObject(thing) && isObservableArrayAdministration(thing.$mobx); + } + exports.isObservableArray = isObservableArray; + var ObservableMapMarker = {}; + var ObservableMap = function () { + function ObservableMap(initialData, enhancer, name) { + if (enhancer === void 0) { + enhancer = deepEnhancer; + } + if (name === void 0) { + name = "ObservableMap@" + getNextId(); + } + this.enhancer = enhancer; + this.name = name; + this.$mobx = ObservableMapMarker; + this._data = {}; + this._hasMap = {}; + this._keys = new ObservableArray(undefined, referenceEnhancer, this.name + ".keys()", true); + this.interceptors = null; + this.changeListeners = null; + this.merge(initialData); + } + ObservableMap.prototype._has = function (key) { + return typeof this._data[key] !== "undefined"; + }; + ObservableMap.prototype.has = function (key) { + if (!this.isValidKey(key)) return false; + key = "" + key; + if (this._hasMap[key]) return this._hasMap[key].get(); + return this._updateHasMapEntry(key, false).get(); + }; + ObservableMap.prototype.set = function (key, value) { + this.assertValidKey(key); + key = "" + key; + var hasKey = this._has(key); + if (hasInterceptors(this)) { + var change = interceptChange(this, { + type: hasKey ? "update" : "add", + object: this, + newValue: value, + name: key + }); + if (!change) return this; + value = change.newValue; + } + if (hasKey) { + this._updateValue(key, value); + } else { + this._addValue(key, value); + } + return this; + }; + ObservableMap.prototype.delete = function (key) { + var _this = this; + this.assertValidKey(key); + key = "" + key; + if (hasInterceptors(this)) { + var change = interceptChange(this, { + type: "delete", + object: this, + name: key + }); + if (!change) return false; + } + if (this._has(key)) { + var notifySpy = isSpyEnabled(); + var notify = hasListeners(this); + var change = notify || notifySpy ? { + type: "delete", + object: this, + oldValue: this._data[key].value, + name: key + } : null; + if (notifySpy) spyReportStart(change); + runInTransaction(function () { + _this._keys.remove(key); + _this._updateHasMapEntry(key, false); + var observable = _this._data[key]; + observable.setNewValue(undefined); + _this._data[key] = undefined; + }); + if (notify) notifyListeners(this, change); + if (notifySpy) spyReportEnd(); + return true; + } + return false; + }; + ObservableMap.prototype._updateHasMapEntry = function (key, value) { + var entry = this._hasMap[key]; + if (entry) { + entry.setNewValue(value); + } else { + entry = this._hasMap[key] = new ObservableValue(value, referenceEnhancer, this.name + "." + key + "?", false); + } + return entry; + }; + ObservableMap.prototype._updateValue = function (name, newValue) { + var observable = this._data[name]; + newValue = observable.prepareNewValue(newValue); + if (newValue !== UNCHANGED) { + var notifySpy = isSpyEnabled(); + var notify = hasListeners(this); + var change = notify || notifySpy ? { + type: "update", + object: this, + oldValue: observable.value, + name: name, newValue: newValue + } : null; + if (notifySpy) spyReportStart(change); + observable.setNewValue(newValue); + if (notify) notifyListeners(this, change); + if (notifySpy) spyReportEnd(); + } + }; + ObservableMap.prototype._addValue = function (name, newValue) { + var _this = this; + runInTransaction(function () { + var observable = _this._data[name] = new ObservableValue(newValue, _this.enhancer, _this.name + "." + name, false); + newValue = observable.value; + _this._updateHasMapEntry(name, true); + _this._keys.push(name); + }); + var notifySpy = isSpyEnabled(); + var notify = hasListeners(this); + var change = notify || notifySpy ? { + type: "add", + object: this, + name: name, newValue: newValue + } : null; + if (notifySpy) spyReportStart(change); + if (notify) notifyListeners(this, change); + if (notifySpy) spyReportEnd(); + }; + ObservableMap.prototype.get = function (key) { + key = "" + key; + if (this.has(key)) return this._data[key].get(); + return undefined; + }; + ObservableMap.prototype.keys = function () { + return arrayAsIterator(this._keys.slice()); + }; + ObservableMap.prototype.values = function () { + return arrayAsIterator(this._keys.map(this.get, this)); + }; + ObservableMap.prototype.entries = function () { + var _this = this; + return arrayAsIterator(this._keys.map(function (key) { + return [key, _this.get(key)]; + })); + }; + ObservableMap.prototype.forEach = function (callback, thisArg) { + var _this = this; + this.keys().forEach(function (key) { + return callback.call(thisArg, _this.get(key), key, _this); + }); + }; + ObservableMap.prototype.merge = function (other) { + var _this = this; + if (isObservableMap(other)) { + other = other.toJS(); + } + runInTransaction(function () { + if (isPlainObject(other)) Object.keys(other).forEach(function (key) { + return _this.set(key, other[key]); + });else if (Array.isArray(other)) other.forEach(function (_a) { + var key = _a[0], + value = _a[1]; + return _this.set(key, value); + });else if (isES6Map(other)) other.forEach(function (value, key) { + return _this.set(key, value); + });else if (other !== null && other !== undefined) fail("Cannot initialize map from " + other); + }); + return this; + }; + ObservableMap.prototype.clear = function () { + var _this = this; + runInTransaction(function () { + untracked(function () { + _this.keys().forEach(_this.delete, _this); + }); + }); + }; + ObservableMap.prototype.replace = function (values) { + var _this = this; + runInTransaction(function () { + _this.clear(); + _this.merge(values); + }); + return this; + }; + Object.defineProperty(ObservableMap.prototype, "size", { + get: function get() { + return this._keys.length; + }, + enumerable: true, + configurable: true + }); + ObservableMap.prototype.toJS = function () { + var _this = this; + var res = {}; + this.keys().forEach(function (key) { + return res[key] = _this.get(key); + }); + return res; + }; + ObservableMap.prototype.toJSON = function () { + return this.toJS(); + }; + ObservableMap.prototype.isValidKey = function (key) { + if (key === null || key === undefined) return false; + if (typeof key === "string" || typeof key === "number" || typeof key === "boolean") return true; + return false; + }; + ObservableMap.prototype.assertValidKey = function (key) { + if (!this.isValidKey(key)) throw new Error("[mobx.map] Invalid key: '" + key + "', only strings, numbers and booleans are accepted as key in observable maps."); + }; + ObservableMap.prototype.toString = function () { + var _this = this; + return this.name + "[{ " + this.keys().map(function (key) { + return key + ": " + ("" + _this.get(key)); + }).join(", ") + " }]"; + }; + ObservableMap.prototype.observe = function (listener, fireImmediately) { + invariant(fireImmediately !== true, getMessage("m033")); + return registerListener(this, listener); + }; + ObservableMap.prototype.intercept = function (handler) { + return registerInterceptor(this, handler); + }; + return ObservableMap; + }(); + exports.ObservableMap = ObservableMap; + declareIterator(ObservableMap.prototype, function () { + return this.entries(); + }); + function map(initialValues) { + deprecated("`mobx.map` is deprecated, use `new ObservableMap` or `mobx.observable.map` instead"); + return observable.map(initialValues); + } + exports.map = map; + var isObservableMap = createInstanceofPredicate("ObservableMap", ObservableMap); + exports.isObservableMap = isObservableMap; + var ObservableObjectAdministration = function () { + function ObservableObjectAdministration(target, name) { + this.target = target; + this.name = name; + this.values = {}; + this.changeListeners = null; + this.interceptors = null; + } + ObservableObjectAdministration.prototype.observe = function (callback, fireImmediately) { + invariant(fireImmediately !== true, "`observe` doesn't support the fire immediately property for observable objects."); + return registerListener(this, callback); + }; + ObservableObjectAdministration.prototype.intercept = function (handler) { + return registerInterceptor(this, handler); + }; + return ObservableObjectAdministration; + }(); + function asObservableObject(target, name) { + if (isObservableObject(target)) return target.$mobx; + invariant(Object.isExtensible(target), getMessage("m035")); + if (!isPlainObject(target)) name = (target.constructor.name || "ObservableObject") + "@" + getNextId(); + if (!name) name = "ObservableObject@" + getNextId(); + var adm = new ObservableObjectAdministration(target, name); + addHiddenFinalProp(target, "$mobx", adm); + return adm; + } + function defineObservablePropertyFromDescriptor(adm, propName, descriptor, defaultEnhancer) { + if (adm.values[propName]) { + invariant("value" in descriptor, "The property " + propName + " in " + adm.name + " is already observable, cannot redefine it as computed property"); + adm.target[propName] = descriptor.value; + return; + } + if ("value" in descriptor) { + if (isModifierDescriptor(descriptor.value)) { + var modifierDescriptor = descriptor.value; + defineObservableProperty(adm, propName, modifierDescriptor.initialValue, modifierDescriptor.enhancer); + } else if (isAction(descriptor.value) && descriptor.value.autoBind === true) { + defineBoundAction(adm.target, propName, descriptor.value.originalFn); + } else if (isComputedValue(descriptor.value)) { + defineComputedPropertyFromComputedValue(adm, propName, descriptor.value); + } else { + defineObservableProperty(adm, propName, descriptor.value, defaultEnhancer); + } + } else { + defineComputedProperty(adm, propName, descriptor.get, descriptor.set, false, true); + } + } + function defineObservableProperty(adm, propName, newValue, enhancer) { + assertPropertyConfigurable(adm.target, propName); + if (hasInterceptors(adm)) { + var change = interceptChange(adm, { + object: adm.target, + name: propName, + type: "add", + newValue: newValue + }); + if (!change) return; + newValue = change.newValue; + } + var observable = adm.values[propName] = new ObservableValue(newValue, enhancer, adm.name + "." + propName, false); + newValue = observable.value; + Object.defineProperty(adm.target, propName, generateObservablePropConfig(propName)); + notifyPropertyAddition(adm, adm.target, propName, newValue); + } + function defineComputedProperty(adm, propName, getter, setter, compareStructural, asInstanceProperty) { + if (asInstanceProperty) assertPropertyConfigurable(adm.target, propName); + adm.values[propName] = new ComputedValue(getter, adm.target, compareStructural, adm.name + "." + propName, setter); + if (asInstanceProperty) { + Object.defineProperty(adm.target, propName, generateComputedPropConfig(propName)); + } + } + function defineComputedPropertyFromComputedValue(adm, propName, computedValue) { + var name = adm.name + "." + propName; + computedValue.name = name; + if (!computedValue.scope) computedValue.scope = adm.target; + adm.values[propName] = computedValue; + Object.defineProperty(adm.target, propName, generateComputedPropConfig(propName)); + } + var observablePropertyConfigs = {}; + var computedPropertyConfigs = {}; + function generateObservablePropConfig(propName) { + return observablePropertyConfigs[propName] || (observablePropertyConfigs[propName] = { + configurable: true, + enumerable: true, + get: function get() { + return this.$mobx.values[propName].get(); + }, + set: function set(v) { + setPropertyValue(this, propName, v); + } + }); + } + function generateComputedPropConfig(propName) { + return computedPropertyConfigs[propName] || (computedPropertyConfigs[propName] = { + configurable: true, + enumerable: false, + get: function get() { + return this.$mobx.values[propName].get(); + }, + set: function set(v) { + return this.$mobx.values[propName].set(v); + } + }); + } + function setPropertyValue(instance, name, newValue) { + var adm = instance.$mobx; + var observable = adm.values[name]; + if (hasInterceptors(adm)) { + var change = interceptChange(adm, { + type: "update", + object: instance, + name: name, newValue: newValue + }); + if (!change) return; + newValue = change.newValue; + } + newValue = observable.prepareNewValue(newValue); + if (newValue !== UNCHANGED) { + var notify = hasListeners(adm); + var notifySpy = isSpyEnabled(); + var change = notify || notifySpy ? { + type: "update", + object: instance, + oldValue: observable.value, + name: name, newValue: newValue + } : null; + if (notifySpy) spyReportStart(change); + observable.setNewValue(newValue); + if (notify) notifyListeners(adm, change); + if (notifySpy) spyReportEnd(); + } + } + function notifyPropertyAddition(adm, object, name, newValue) { + var notify = hasListeners(adm); + var notifySpy = isSpyEnabled(); + var change = notify || notifySpy ? { + type: "add", + object: object, name: name, newValue: newValue + } : null; + if (notifySpy) spyReportStart(change); + if (notify) notifyListeners(adm, change); + if (notifySpy) spyReportEnd(); + } + var isObservableObjectAdministration = createInstanceofPredicate("ObservableObjectAdministration", ObservableObjectAdministration); + function isObservableObject(thing) { + if (isObject(thing)) { + runLazyInitializers(thing); + return isObservableObjectAdministration(thing.$mobx); + } + return false; + } + exports.isObservableObject = isObservableObject; + var UNCHANGED = {}; + var ObservableValue = function (_super) { + __extends(ObservableValue, _super); + function ObservableValue(value, enhancer, name, notifySpy) { + if (name === void 0) { + name = "ObservableValue@" + getNextId(); + } + if (notifySpy === void 0) { + notifySpy = true; + } + var _this = _super.call(this, name) || this; + _this.enhancer = enhancer; + _this.hasUnreportedChange = false; + _this.value = enhancer(value, undefined, name); + if (notifySpy && isSpyEnabled()) { + spyReport({ type: "create", object: _this, newValue: _this.value }); + } + return _this; + } + ObservableValue.prototype.set = function (newValue) { + var oldValue = this.value; + newValue = this.prepareNewValue(newValue); + if (newValue !== UNCHANGED) { + var notifySpy = isSpyEnabled(); + if (notifySpy) { + spyReportStart({ + type: "update", + object: this, + newValue: newValue, oldValue: oldValue + }); + } + this.setNewValue(newValue); + if (notifySpy) spyReportEnd(); + } + }; + ObservableValue.prototype.prepareNewValue = function (newValue) { + checkIfStateModificationsAreAllowed(this); + if (hasInterceptors(this)) { + var change = interceptChange(this, { object: this, type: "update", newValue: newValue }); + if (!change) return UNCHANGED; + newValue = change.newValue; + } + newValue = this.enhancer(newValue, this.value, this.name); + return this.value !== newValue ? newValue : UNCHANGED; + }; + ObservableValue.prototype.setNewValue = function (newValue) { + var oldValue = this.value; + this.value = newValue; + this.reportChanged(); + if (hasListeners(this)) { + notifyListeners(this, { + type: "update", + object: this, + newValue: newValue, + oldValue: oldValue + }); + } + }; + ObservableValue.prototype.get = function () { + this.reportObserved(); + return this.value; + }; + ObservableValue.prototype.intercept = function (handler) { + return registerInterceptor(this, handler); + }; + ObservableValue.prototype.observe = function (listener, fireImmediately) { + if (fireImmediately) listener({ + object: this, + type: "update", + newValue: this.value, + oldValue: undefined + }); + return registerListener(this, listener); + }; + ObservableValue.prototype.toJSON = function () { + return this.get(); + }; + ObservableValue.prototype.toString = function () { + return this.name + "[" + this.value + "]"; + }; + ObservableValue.prototype.valueOf = function () { + return toPrimitive(this.get()); + }; + return ObservableValue; + }(BaseAtom); + ObservableValue.prototype[primitiveSymbol()] = ObservableValue.prototype.valueOf; + var isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue); + exports.isBoxedObservable = isObservableValue; + function getAtom(thing, property) { + if ((typeof thing === "undefined" ? "undefined" : _typeof(thing)) === "object" && thing !== null) { + if (isObservableArray(thing)) { + invariant(property === undefined, getMessage("m036")); + return thing.$mobx.atom; + } + if (isObservableMap(thing)) { + var anyThing = thing; + if (property === undefined) return getAtom(anyThing._keys); + var observable_2 = anyThing._data[property] || anyThing._hasMap[property]; + invariant(!!observable_2, "the entry '" + property + "' does not exist in the observable map '" + getDebugName(thing) + "'"); + return observable_2; + } + runLazyInitializers(thing); + if (isObservableObject(thing)) { + if (!property) return fail("please specify a property"); + var observable_3 = thing.$mobx.values[property]; + invariant(!!observable_3, "no observable property '" + property + "' found on the observable object '" + getDebugName(thing) + "'"); + return observable_3; + } + if (isAtom(thing) || isComputedValue(thing) || isReaction(thing)) { + return thing; + } + } else if (typeof thing === "function") { + if (isReaction(thing.$mobx)) { + return thing.$mobx; + } + } + return fail("Cannot obtain atom from " + thing); + } + function getAdministration(thing, property) { + invariant(thing, "Expecting some object"); + if (property !== undefined) return getAdministration(getAtom(thing, property)); + if (isAtom(thing) || isComputedValue(thing) || isReaction(thing)) return thing; + if (isObservableMap(thing)) return thing; + runLazyInitializers(thing); + if (thing.$mobx) return thing.$mobx; + invariant(false, "Cannot obtain administration from " + thing); + } + function getDebugName(thing, property) { + var named; + if (property !== undefined) named = getAtom(thing, property);else if (isObservableObject(thing) || isObservableMap(thing)) named = getAdministration(thing);else named = getAtom(thing); + return named.name; + } + function createClassPropertyDecorator(onInitialize, _get, _set, enumerable, allowCustomArguments) { + function classPropertyDecorator(target, key, descriptor, customArgs, argLen) { + if (argLen === void 0) { + argLen = 0; + } + invariant(allowCustomArguments || quacksLikeADecorator(arguments), "This function is a decorator, but it wasn't invoked like a decorator"); + if (!descriptor) { + var newDescriptor = { + enumerable: enumerable, + configurable: true, + get: function get() { + if (!this.__mobxInitializedProps || this.__mobxInitializedProps[key] !== true) typescriptInitializeProperty(this, key, undefined, onInitialize, customArgs, descriptor); + return _get.call(this, key); + }, + set: function set(v) { + if (!this.__mobxInitializedProps || this.__mobxInitializedProps[key] !== true) { + typescriptInitializeProperty(this, key, v, onInitialize, customArgs, descriptor); + } else { + _set.call(this, key, v); + } + } + }; + if (arguments.length < 3 || arguments.length === 5 && argLen < 3) { + Object.defineProperty(target, key, newDescriptor); + } + return newDescriptor; + } else { + if (!hasOwnProperty(target, "__mobxLazyInitializers")) { + addHiddenProp(target, "__mobxLazyInitializers", target.__mobxLazyInitializers && target.__mobxLazyInitializers.slice() || []); + } + var value_1 = descriptor.value, + initializer_1 = descriptor.initializer; + target.__mobxLazyInitializers.push(function (instance) { + onInitialize(instance, key, initializer_1 ? initializer_1.call(instance) : value_1, customArgs, descriptor); + }); + return { + enumerable: enumerable, configurable: true, + get: function get() { + if (this.__mobxDidRunLazyInitializers !== true) runLazyInitializers(this); + return _get.call(this, key); + }, + set: function set(v) { + if (this.__mobxDidRunLazyInitializers !== true) runLazyInitializers(this); + _set.call(this, key, v); + } + }; + } + } + if (allowCustomArguments) { + return function () { + if (quacksLikeADecorator(arguments)) return classPropertyDecorator.apply(null, arguments); + var outerArgs = arguments; + var argLen = arguments.length; + return function (target, key, descriptor) { + return classPropertyDecorator(target, key, descriptor, outerArgs, argLen); + }; + }; + } + return classPropertyDecorator; + } + function typescriptInitializeProperty(instance, key, v, onInitialize, customArgs, baseDescriptor) { + if (!hasOwnProperty(instance, "__mobxInitializedProps")) addHiddenProp(instance, "__mobxInitializedProps", {}); + instance.__mobxInitializedProps[key] = true; + onInitialize(instance, key, v, customArgs, baseDescriptor); + } + function runLazyInitializers(instance) { + if (instance.__mobxDidRunLazyInitializers === true) return; + if (instance.__mobxLazyInitializers) { + addHiddenProp(instance, "__mobxDidRunLazyInitializers", true); + instance.__mobxDidRunLazyInitializers && instance.__mobxLazyInitializers.forEach(function (initializer) { + return initializer(instance); + }); + } + } + function quacksLikeADecorator(args) { + return (args.length === 2 || args.length === 3) && typeof args[1] === "string"; + } + function iteratorSymbol() { + return typeof Symbol === "function" && Symbol.iterator || "@@iterator"; + } + var IS_ITERATING_MARKER = "__$$iterating"; + function arrayAsIterator(array) { + invariant(array[IS_ITERATING_MARKER] !== true, "Illegal state: cannot recycle array as iterator"); + addHiddenFinalProp(array, IS_ITERATING_MARKER, true); + var idx = -1; + addHiddenFinalProp(array, "next", function next() { + idx++; + return { + done: idx >= this.length, + value: idx < this.length ? this[idx] : undefined + }; + }); + return array; + } + function declareIterator(prototType, iteratorFactory) { + addHiddenFinalProp(prototType, iteratorSymbol(), iteratorFactory); + } + var messages = { + "m001": "It is not allowed to assign new values to @action fields", + "m002": "`runInAction` expects a function", + "m003": "`runInAction` expects a function without arguments", + "m004": "autorun expects a function", + "m005": "Warning: attempted to pass an action to autorun. Actions are untracked and will not trigger on state changes. Use `reaction` or wrap only your state modification code in an action.", + "m006": "Warning: attempted to pass an action to autorunAsync. Actions are untracked and will not trigger on state changes. Use `reaction` or wrap only your state modification code in an action.", + "m007": "reaction only accepts 2 or 3 arguments. If migrating from MobX 2, please provide an options object", + "m008": "wrapping reaction expression in `asReference` is no longer supported, use options object instead", + "m009": "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'. It looks like it was used on a property.", + "m010": "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'", + "m011": "First argument to `computed` should be an expression. If using computed as decorator, don't pass it arguments", + "m012": "computed takes one or two arguments if used as function", + "m013": "[mobx.expr] 'expr' should only be used inside other reactive functions.", + "m014": "extendObservable expected 2 or more arguments", + "m015": "extendObservable expects an object as first argument", + "m016": "extendObservable should not be used on maps, use map.merge instead", + "m017": "all arguments of extendObservable should be objects", + "m018": "extending an object with another observable (object) is not supported. Please construct an explicit propertymap, using `toJS` if need. See issue #540", + "m019": "[mobx.isObservable] isObservable(object, propertyName) is not supported for arrays and maps. Use map.has or array.length instead.", + "m020": "modifiers can only be used for individual object properties", + "m021": "observable expects zero or one arguments", + "m022": "@observable can not be used on getters, use @computed instead", + "m023": "Using `transaction` is deprecated, use `runInAction` or `(@)action` instead.", + "m024": "whyRun() can only be used if a derivation is active, or by passing an computed value / reaction explicitly. If you invoked whyRun from inside a computation; the computation is currently suspended but re-evaluating because somebody requested its value.", + "m025": "whyRun can only be used on reactions and computed values", + "m026": "`action` can only be invoked on functions", + "m028": "It is not allowed to set `useStrict` when a derivation is running", + "m029": "INTERNAL ERROR only onBecomeUnobserved shouldn't be called twice in a row", + "m030a": "Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: ", + "m030b": "Side effects like changing state are not allowed at this point. Are you trying to modify state from, for example, the render function of a React component? Tried to modify: ", + "m031": "Computed values are not allowed to not cause side effects by changing observables that are already being observed. Tried to modify: ", + "m032": "* This computation is suspended (not in use by any reaction) and won't run automatically.\n Didn't expect this computation to be suspended at this point?\n 1. Make sure this computation is used by a reaction (reaction, autorun, observer).\n 2. Check whether you are using this computation synchronously (in the same stack as they reaction that needs it).", + "m033": "`observe` doesn't support the fire immediately property for observable maps.", + "m034": "`mobx.map` is deprecated, use `new ObservableMap` or `mobx.observable.map` instead", + "m035": "Cannot make the designated object observable; it is not extensible", + "m036": "It is not possible to get index atoms from arrays", + "m037": "Hi there! I'm sorry you have just run into an exception.\nIf your debugger ends up here, know that some reaction (like the render() of an observer component, autorun or reaction)\nthrew an exception and that mobx caught it, to avoid that it brings the rest of your application down.\nThe original cause of the exception (the code that caused this reaction to run (again)), is still in the stack.\n\nHowever, more interesting is the actual stack trace of the error itself.\nHopefully the error is an instanceof Error, because in that case you can inspect the original stack of the error from where it was thrown.\nSee `error.stack` property, or press the very subtle \"(...)\" link you see near the console.error message that probably brought you here.\nThat stack is more interesting than the stack of this console.error itself.\n\nIf the exception you see is an exception you created yourself, make sure to use `throw new Error(\"Oops\")` instead of `throw \"Oops\"`,\nbecause the javascript environment will only preserve the original stack trace in the first form.\n\nYou can also make sure the debugger pauses the next time this very same exception is thrown by enabling \"Pause on caught exception\".\n(Note that it might pause on many other, unrelated exception as well).\n\nIf that all doesn't help you out, feel free to open an issue https://github.com/mobxjs/mobx/issues!\n", + "m038": "Missing items in this list?\n 1. Check whether all used values are properly marked as observable (use isObservable to verify)\n 2. Make sure you didn't dereference values too early. MobX observes props, not primitives. E.g: use 'person.name' instead of 'name' in your computation.\n" + }; + function getMessage(id) { + return messages[id]; + } + var EMPTY_ARRAY = []; + Object.freeze(EMPTY_ARRAY); + function getGlobal() { + return global; + } + function getNextId() { + return ++globalState.mobxGuid; + } + function fail(message, thing) { + invariant(false, message, thing); + throw "X"; + } + function invariant(check, message, thing) { + if (!check) throw new Error("[mobx] Invariant failed: " + message + (thing ? " in '" + thing + "'" : "")); + } + var deprecatedMessages = []; + function deprecated(msg) { + if (deprecatedMessages.indexOf(msg) !== -1) return false; + deprecatedMessages.push(msg); + console.error("[mobx] Deprecated: " + msg); + return true; + } + function once(func) { + var invoked = false; + return function () { + if (invoked) return; + invoked = true; + return func.apply(this, arguments); + }; + } + var noop = function noop() {}; + function unique(list) { + var res = []; + list.forEach(function (item) { + if (res.indexOf(item) === -1) res.push(item); + }); + return res; + } + function joinStrings(things, limit, separator) { + if (limit === void 0) { + limit = 100; + } + if (separator === void 0) { + separator = " - "; + } + if (!things) return ""; + var sliced = things.slice(0, limit); + return "" + sliced.join(separator) + (things.length > limit ? " (... and " + (things.length - limit) + "more)" : ""); + } + function isObject(value) { + return value !== null && (typeof value === "undefined" ? "undefined" : _typeof(value)) === "object"; + } + function isPlainObject(value) { + if (value === null || (typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false; + var proto = Object.getPrototypeOf(value); + return proto === Object.prototype || proto === null; + } + function objectAssign() { + var res = arguments[0]; + for (var i = 1, l = arguments.length; i < l; i++) { + var source = arguments[i]; + for (var key in source) { + if (hasOwnProperty(source, key)) { + res[key] = source[key]; + } + } + } + return res; + } + function valueDidChange(compareStructural, oldValue, newValue) { + if (typeof oldValue === 'number' && isNaN(oldValue)) { + return typeof newValue !== 'number' || !isNaN(newValue); + } + return compareStructural ? !deepEqual(oldValue, newValue) : oldValue !== newValue; + } + var prototypeHasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwnProperty(object, propName) { + return prototypeHasOwnProperty.call(object, propName); + } + function makeNonEnumerable(object, propNames) { + for (var i = 0; i < propNames.length; i++) { + addHiddenProp(object, propNames[i], object[propNames[i]]); + } + } + function addHiddenProp(object, propName, value) { + Object.defineProperty(object, propName, { + enumerable: false, + writable: true, + configurable: true, + value: value + }); + } + function addHiddenFinalProp(object, propName, value) { + Object.defineProperty(object, propName, { + enumerable: false, + writable: false, + configurable: true, + value: value + }); + } + function isPropertyConfigurable(object, prop) { + var descriptor = Object.getOwnPropertyDescriptor(object, prop); + return !descriptor || descriptor.configurable !== false && descriptor.writable !== false; + } + function assertPropertyConfigurable(object, prop) { + invariant(isPropertyConfigurable(object, prop), "Cannot make property '" + prop + "' observable, it is not configurable and writable in the target object"); + } + function getEnumerableKeys(obj) { + var res = []; + for (var key in obj) { + res.push(key); + }return res; + } + function deepEqual(a, b) { + if (a === null && b === null) return true; + if (a === undefined && b === undefined) return true; + if ((typeof a === "undefined" ? "undefined" : _typeof(a)) !== "object") return a === b; + var aIsArray = isArrayLike(a); + var aIsMap = isMapLike(a); + if (aIsArray !== isArrayLike(b)) { + return false; + } else if (aIsMap !== isMapLike(b)) { + return false; + } else if (aIsArray) { + if (a.length !== b.length) return false; + for (var i = a.length - 1; i >= 0; i--) { + if (!deepEqual(a[i], b[i])) return false; + }return true; + } else if (aIsMap) { + if (a.size !== b.size) return false; + var equals_1 = true; + a.forEach(function (value, key) { + equals_1 = equals_1 && deepEqual(b.get(key), value); + }); + return equals_1; + } else if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object" && (typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") { + if (a === null || b === null) return false; + if (isMapLike(a) && isMapLike(b)) { + if (a.size !== b.size) return false; + return deepEqual(observable.shallowMap(a).entries(), observable.shallowMap(b).entries()); + } + if (getEnumerableKeys(a).length !== getEnumerableKeys(b).length) return false; + for (var prop in a) { + if (!(prop in b)) return false; + if (!deepEqual(a[prop], b[prop])) return false; + } + return true; + } + return false; + } + function createInstanceofPredicate(name, clazz) { + var propName = "isMobX" + name; + clazz.prototype[propName] = true; + return function (x) { + return isObject(x) && x[propName] === true; + }; + } + function isArrayLike(x) { + return Array.isArray(x) || isObservableArray(x); + } + exports.isArrayLike = isArrayLike; + function isMapLike(x) { + return isES6Map(x) || isObservableMap(x); + } + function isES6Map(thing) { + if (getGlobal().Map !== undefined && thing instanceof getGlobal().Map) return true; + return false; + } + function primitiveSymbol() { + return typeof Symbol === "function" && Symbol.toPrimitive || "@@toPrimitive"; + } + function toPrimitive(value) { + return value === null ? null : (typeof value === "undefined" ? "undefined" : _typeof(value)) === "object" ? "" + value : value; + } + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4))) + + /***/ }), + /* 2 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + var _icons = __webpack_require__(6); + + var _constants = __webpack_require__(0); + + function renderHeader(_ref, instance) { + var meta = _ref.meta, + user = _ref.user, + reactions = _ref.reactions; + + var container = document.createElement('div'); + container.lang = "en-US"; + container.className = 'gitment-container gitment-header-container'; + + var likeButton = document.createElement('span'); + var likedReaction = reactions.find(function (reaction) { + return reaction.content === 'heart' && reaction.user.login === user.login; + }); + likeButton.className = 'gitment-header-like-btn'; + likeButton.innerHTML = '\n ' + _icons.heart + '\n ' + (likedReaction ? 'Unlike' : 'Like') + '\n ' + (meta.reactions && meta.reactions.heart ? ' \u2022 ' + meta.reactions.heart + ' Liked' : '') + '\n '; + + if (likedReaction) { + likeButton.classList.add('liked'); + likeButton.onclick = function () { + return instance.unlike(); + }; + } else { + likeButton.classList.remove('liked'); + likeButton.onclick = function () { + return instance.like(); + }; + } + container.appendChild(likeButton); + + var commentsCount = document.createElement('span'); + commentsCount.innerHTML = '\n ' + (meta.comments ? ' \u2022 ' + meta.comments + ' Comments' : '') + '\n '; + container.appendChild(commentsCount); + + var issueLink = document.createElement('a'); + issueLink.className = 'gitment-header-issue-link'; + issueLink.href = meta.html_url; + issueLink.target = '_blank'; + issueLink.innerText = 'Issue Page'; + container.appendChild(issueLink); + + return container; + } + + function renderComments(_ref2, instance) { + var meta = _ref2.meta, + comments = _ref2.comments, + commentReactions = _ref2.commentReactions, + currentPage = _ref2.currentPage, + user = _ref2.user, + error = _ref2.error; + + var container = document.createElement('div'); + container.lang = "en-US"; + container.className = 'gitment-container gitment-comments-container'; + + if (error) { + var errorBlock = document.createElement('div'); + errorBlock.className = 'gitment-comments-error'; + + if (error === _constants.NOT_INITIALIZED_ERROR && user.login && user.login.toLowerCase() === instance.owner.toLowerCase()) { + var initHint = document.createElement('div'); + var initButton = document.createElement('button'); + initButton.className = 'gitment-comments-init-btn'; + initButton.onclick = function () { + initButton.setAttribute('disabled', true); + instance.init().catch(function (e) { + initButton.removeAttribute('disabled'); + alert(e); + }); + }; + initButton.innerText = 'Initialize Comments'; + initHint.appendChild(initButton); + errorBlock.appendChild(initHint); + } else { + errorBlock.innerText = error; + } + container.appendChild(errorBlock); + return container; + } else if (comments === undefined) { + var loading = document.createElement('div'); + loading.innerText = 'Loading comments...'; + loading.className = 'gitment-comments-loading'; + container.appendChild(loading); + return container; + } else if (!comments.length) { + var emptyBlock = document.createElement('div'); + emptyBlock.className = 'gitment-comments-empty'; + emptyBlock.innerText = 'No Comment Yet'; + container.appendChild(emptyBlock); + return container; + } + + var commentsList = document.createElement('ul'); + commentsList.className = 'gitment-comments-list'; + + comments.forEach(function (comment) { + var createDate = new Date(comment.created_at); + var updateDate = new Date(comment.updated_at); + var commentItem = document.createElement('li'); + commentItem.className = 'gitment-comment'; + commentItem.innerHTML = '\n \n \n \n
\n
\n \n ' + comment.user.login + '\n \n commented on\n ' + createDate.toDateString() + '\n ' + (createDate.toString() !== updateDate.toString() ? ' \u2022 edited' : '') + '\n
' + _icons.heart + ' ' + (comment.reactions.heart || '') + '
\n
\n
' + comment.body_html + '
\n
\n '; + var likeButton = commentItem.querySelector('.gitment-comment-like-btn'); + var likedReaction = commentReactions[comment.id] && commentReactions[comment.id].find(function (reaction) { + return reaction.content === 'heart' && reaction.user.login === user.login; + }); + if (likedReaction) { + likeButton.classList.add('liked'); + likeButton.onclick = function () { + return instance.unlikeAComment(comment.id); + }; + } else { + likeButton.classList.remove('liked'); + likeButton.onclick = function () { + return instance.likeAComment(comment.id); + }; + } + + // dirty + // use a blank image to trigger height calculating when element rendered + var imgTrigger = document.createElement('img'); + var markdownBody = commentItem.querySelector('.gitment-comment-body'); + imgTrigger.className = 'gitment-hidden'; + imgTrigger.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + imgTrigger.onload = function () { + if (markdownBody.clientHeight > instance.maxCommentHeight) { + markdownBody.classList.add('gitment-comment-body-folded'); + markdownBody.style.maxHeight = instance.maxCommentHeight + 'px'; + markdownBody.title = 'Click to Expand'; + markdownBody.onclick = function () { + markdownBody.classList.remove('gitment-comment-body-folded'); + markdownBody.style.maxHeight = ''; + markdownBody.title = ''; + markdownBody.onclick = null; + }; + } + }; + commentItem.appendChild(imgTrigger); + + commentsList.appendChild(commentItem); + }); + + container.appendChild(commentsList); + + if (meta) { + var pageCount = Math.ceil(meta.comments / instance.perPage); + if (pageCount > 1) { + var pagination = document.createElement('ul'); + pagination.className = 'gitment-comments-pagination'; + + if (currentPage > 1) { + var previousButton = document.createElement('li'); + previousButton.className = 'gitment-comments-page-item'; + previousButton.innerText = 'Previous'; + previousButton.onclick = function () { + return instance.goto(currentPage - 1); + }; + pagination.appendChild(previousButton); + } + + var _loop = function _loop(i) { + var pageItem = document.createElement('li'); + pageItem.className = 'gitment-comments-page-item'; + pageItem.innerText = i; + pageItem.onclick = function () { + return instance.goto(i); + }; + if (currentPage === i) pageItem.classList.add('gitment-selected'); + pagination.appendChild(pageItem); + }; + + for (var i = 1; i <= pageCount; i++) { + _loop(i); + } + + if (currentPage < pageCount) { + var nextButton = document.createElement('li'); + nextButton.className = 'gitment-comments-page-item'; + nextButton.innerText = 'Next'; + nextButton.onclick = function () { + return instance.goto(currentPage + 1); + }; + pagination.appendChild(nextButton); + } + + container.appendChild(pagination); + } + } + + return container; + } + + function renderEditor(_ref3, instance) { + var user = _ref3.user, + error = _ref3.error; + + var container = document.createElement('div'); + container.lang = "en-US"; + container.className = 'gitment-container gitment-editor-container'; + + var shouldDisable = user.login && !error ? '' : 'disabled'; + var disabledTip = user.login ? '' : 'Login to Comment'; + container.innerHTML = '\n ' + (user.login ? '\n \n ' : user.isLoggingIn ? '
' + _icons.spinner + '
' : '\n ' + _icons.github + '\n ') + '\n \n
\n
\n \n \n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n \n '; + if (user.login) { + container.querySelector('.gitment-editor-logout-link').onclick = function () { + return instance.logout(); + }; + } + + var writeField = container.querySelector('.gitment-editor-write-field'); + var previewField = container.querySelector('.gitment-editor-preview-field'); + + var textarea = writeField.querySelector('textarea'); + textarea.oninput = function () { + textarea.style.height = 'auto'; + var style = window.getComputedStyle(textarea, null); + var height = parseInt(style.height, 10); + var clientHeight = textarea.clientHeight; + var scrollHeight = textarea.scrollHeight; + if (clientHeight < scrollHeight) { + textarea.style.height = height + scrollHeight - clientHeight + 'px'; + } + }; + + var _container$querySelec = container.querySelectorAll('.gitment-editor-tab'), + _container$querySelec2 = _slicedToArray(_container$querySelec, 2), + writeTab = _container$querySelec2[0], + previewTab = _container$querySelec2[1]; + + writeTab.onclick = function () { + writeTab.classList.add('gitment-selected'); + previewTab.classList.remove('gitment-selected'); + writeField.classList.remove('gitment-hidden'); + previewField.classList.add('gitment-hidden'); + + textarea.focus(); + }; + previewTab.onclick = function () { + previewTab.classList.add('gitment-selected'); + writeTab.classList.remove('gitment-selected'); + previewField.classList.remove('gitment-hidden'); + writeField.classList.add('gitment-hidden'); + + var preview = previewField.querySelector('.gitment-editor-preview'); + var content = textarea.value.trim(); + if (!content) { + preview.innerText = 'Nothing to preview'; + return; + } + + preview.innerText = 'Loading preview...'; + instance.markdown(content).then(function (html) { + return preview.innerHTML = html; + }); + }; + + var submitButton = container.querySelector('.gitment-editor-submit'); + submitButton.onclick = function () { + submitButton.innerText = 'Submitting...'; + submitButton.setAttribute('disabled', true); + instance.post(textarea.value.trim()).then(function (data) { + textarea.value = ''; + textarea.style.height = 'auto'; + submitButton.removeAttribute('disabled'); + submitButton.innerText = 'Comment'; + }).catch(function (e) { + alert(e); + submitButton.removeAttribute('disabled'); + submitButton.innerText = 'Comment'; + }); + }; + + return container; + } + + function renderFooter() { + var container = document.createElement('div'); + container.lang = "en-US"; + container.className = 'gitment-container gitment-footer-container'; + container.innerHTML = '\n Powered by\n \n Gitment\n \n '; + return container; + } + + function render(state, instance) { + var container = document.createElement('div'); + container.lang = "en-US"; + container.className = 'gitment-container gitment-root-container'; + container.appendChild(instance.renderHeader(state, instance)); + container.appendChild(instance.renderComments(state, instance)); + container.appendChild(instance.renderEditor(state, instance)); + container.appendChild(instance.renderFooter(state, instance)); + return container; + } + + exports.default = { render: render, renderHeader: renderHeader, renderComments: renderComments, renderEditor: renderEditor, renderFooter: renderFooter }; + + /***/ }), + /* 3 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.http = exports.Query = exports.isString = undefined; + + var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + + exports.getTargetContainer = getTargetContainer; + + var _constants = __webpack_require__(0); + + var isString = exports.isString = function isString(s) { + return toString.call(s) === '[object String]'; + }; + + function getTargetContainer(container) { + var targetContainer = void 0; + if (container instanceof Element) { + targetContainer = container; + } else if (isString(container)) { + targetContainer = document.getElementById(container); + } else { + targetContainer = document.createElement('div'); + } + + return targetContainer; + } + + var Query = exports.Query = { + parse: function parse() { + var search = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.search; + + if (!search) return {}; + var queryString = search[0] === '?' ? search.substring(1) : search; + var query = {}; + queryString.split('&').forEach(function (queryStr) { + var _queryStr$split = queryStr.split('='), + _queryStr$split2 = _slicedToArray(_queryStr$split, 2), + key = _queryStr$split2[0], + value = _queryStr$split2[1]; + + if (key) query[key] = value; + }); + + return query; + }, + stringify: function stringify(query) { + var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '?'; + + var queryString = Object.keys(query).map(function (key) { + return key + '=' + encodeURIComponent(query[key] || ''); + }).join('&'); + return queryString ? prefix + queryString : ''; + } + }; + + function ajaxFactory(method) { + return function (apiPath) { + var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var base = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'https://api.github.com'; + + var req = new XMLHttpRequest(); + var token = localStorage.getItem(_constants.LS_ACCESS_TOKEN_KEY); + + var url = '' + base + apiPath; + var body = null; + if (method === 'GET' || method === 'DELETE') { + url += Query.stringify(data); + } + + var p = new Promise(function (resolve, reject) { + req.addEventListener('load', function () { + var contentType = req.getResponseHeader('content-type'); + var res = req.responseText; + if (!/json/.test(contentType)) { + resolve(res); + return; + } + var data = req.responseText ? JSON.parse(res) : {}; + if (data.message) { + reject(new Error(data.message)); + } else { + resolve(data); + } + }); + req.addEventListener('error', function (error) { + return reject(error); + }); + }); + req.open(method, url, true); + + req.setRequestHeader('Accept', 'application/vnd.github.squirrel-girl-preview, application/vnd.github.html+json'); + if (token) { + req.setRequestHeader('Authorization', 'token ' + token); + } + if (method !== 'GET' && method !== 'DELETE') { + body = JSON.stringify(data); + req.setRequestHeader('Content-Type', 'application/json'); + } + + req.send(body); + return p; + }; + } + + var http = exports.http = { + get: ajaxFactory('GET'), + post: ajaxFactory('POST'), + delete: ajaxFactory('DELETE'), + put: ajaxFactory('PUT') + }; + + /***/ }), + /* 4 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var g; + +// This works in non-strict mode + g = function () { + return this; + }(); + + try { + // This works if eval is allowed (see CSP) + g = g || Function("return this")() || (1, eval)("this"); + } catch (e) { + // This works if the window reference is available + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object") g = window; + } + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + + module.exports = g; + + /***/ }), + /* 5 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _mobx = __webpack_require__(1); + + var _constants = __webpack_require__(0); + + var _utils = __webpack_require__(3); + + var _default = __webpack_require__(2); + + var _default2 = _interopRequireDefault(_default); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var scope = 'public_repo'; + + function extendRenderer(instance, renderer) { + instance[renderer] = function (container) { + var targetContainer = (0, _utils.getTargetContainer)(container); + var render = instance.theme[renderer] || instance.defaultTheme[renderer]; + + (0, _mobx.autorun)(function () { + var e = render(instance.state, instance); + if (targetContainer.firstChild) { + targetContainer.replaceChild(e, targetContainer.firstChild); + } else { + targetContainer.appendChild(e); + } + }); + + return targetContainer; + }; + } + + var Gitment = function () { + _createClass(Gitment, [{ + key: 'accessToken', + get: function get() { + return localStorage.getItem(_constants.LS_ACCESS_TOKEN_KEY); + }, + set: function set(token) { + localStorage.setItem(_constants.LS_ACCESS_TOKEN_KEY, token); + } + }, { + key: 'loginLink', + get: function get() { + var oauthUri = 'https://github.com/login/oauth/authorize'; + var redirect_uri = this.oauth.redirect_uri || window.location.href; + + var oauthParams = Object.assign({ + scope: scope, + redirect_uri: redirect_uri + }, this.oauth); + + return '' + oauthUri + _utils.Query.stringify(oauthParams); + } + }]); + + function Gitment() { + var _this = this; + + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + _classCallCheck(this, Gitment); + + this.defaultTheme = _default2.default; + this.useTheme(_default2.default); + + Object.assign(this, { + id: window.location.href, + title: window.document.title, + link: window.location.href, + desc: '', + labels: [], + theme: _default2.default, + oauth: {}, + perPage: 20, + maxCommentHeight: 250 + }, options); + + this.useTheme(this.theme); + + var user = {}; + try { + var userInfo = localStorage.getItem(_constants.LS_USER_KEY); + if (this.accessToken && userInfo) { + Object.assign(user, JSON.parse(userInfo), { + fromCache: true + }); + } + } catch (e) { + localStorage.removeItem(_constants.LS_USER_KEY); + } + + this.state = (0, _mobx.observable)({ + user: user, + error: null, + meta: {}, + comments: undefined, + reactions: [], + commentReactions: {}, + currentPage: 1 + }); + + var query = _utils.Query.parse(); + if (query.code) { + var _oauth = this.oauth, + client_id = _oauth.client_id, + client_secret = _oauth.client_secret; + + var code = query.code; + delete query.code; + var search = _utils.Query.stringify(query); + var replacedUrl = '' + window.location.origin + window.location.pathname + search + window.location.hash; + history.replaceState({}, '', replacedUrl); + + Object.assign(this, { + id: replacedUrl, + link: replacedUrl + }, options); + + this.state.user.isLoggingIn = true; + _utils.http.post('https://auth.baixiaotu.cc', { + code: code, + client_id: client_id, + client_secret: client_secret + }, '').then(function (data) { + _this.accessToken = data.access_token; + _this.update(); + }).catch(function (e) { + _this.state.user.isLoggingIn = false; + alert(e); + }); + } else { + this.update(); + } + } + + _createClass(Gitment, [{ + key: 'init', + value: function init() { + var _this2 = this; + + return this.createIssue().then(function () { + return _this2.loadComments(); + }).then(function (comments) { + _this2.state.error = null; + return comments; + }); + } + }, { + key: 'useTheme', + value: function useTheme() { + var _this3 = this; + + var theme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.theme = theme; + + var renderers = Object.keys(this.theme); + renderers.forEach(function (renderer) { + return extendRenderer(_this3, renderer); + }); + } + }, { + key: 'update', + value: function update() { + var _this4 = this; + + return Promise.all([this.loadMeta(), this.loadUserInfo()]).then(function () { + return Promise.all([_this4.loadComments().then(function () { + return _this4.loadCommentReactions(); + }), _this4.loadReactions()]); + }).catch(function (e) { + return _this4.state.error = e; + }); + } + }, { + key: 'markdown', + value: function markdown(text) { + return _utils.http.post('/markdown', { + text: text, + mode: 'gfm' + }); + } + }, { + key: 'createIssue', + value: function createIssue() { + var _this5 = this; + + var id = this.id, + owner = this.owner, + repo = this.repo, + title = this.title, + link = this.link, + desc = this.desc, + labels = this.labels; + + + return _utils.http.post('/repos/' + owner + '/' + repo + '/issues', { + title: title, + labels: labels.concat(['gitment', id]), + body: link + '\n\n' + desc + }).then(function (meta) { + _this5.state.meta = meta; + return meta; + }); + } + }, { + key: 'getIssue', + value: function getIssue() { + if (this.state.meta.id) return Promise.resolve(this.state.meta); + + return this.loadMeta(); + } + }, { + key: 'post', + value: function post(body) { + var _this6 = this; + + return this.getIssue().then(function (issue) { + return _utils.http.post(issue.comments_url, { body: body }, ''); + }).then(function (data) { + _this6.state.meta.comments++; + var pageCount = Math.ceil(_this6.state.meta.comments / _this6.perPage); + if (_this6.state.currentPage === pageCount) { + _this6.state.comments.push(data); + } + return data; + }); + } + }, { + key: 'loadMeta', + value: function loadMeta() { + var _this7 = this; + + var id = this.id, + owner = this.owner, + repo = this.repo; + + return _utils.http.get('/repos/' + owner + '/' + repo + '/issues', { + creator: owner, + labels: id + }).then(function (issues) { + if (!issues.length) return Promise.reject(_constants.NOT_INITIALIZED_ERROR); + _this7.state.meta = issues[0]; + return issues[0]; + }); + } + }, { + key: 'loadComments', + value: function loadComments() { + var _this8 = this; + + var page = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.currentPage; + + return this.getIssue().then(function (issue) { + return _utils.http.get(issue.comments_url, { page: page, per_page: _this8.perPage }, ''); + }).then(function (comments) { + _this8.state.comments = comments; + return comments; + }); + } + }, { + key: 'loadUserInfo', + value: function loadUserInfo() { + var _this9 = this; + + if (!this.accessToken) { + this.logout(); + return Promise.resolve({}); + } + + return _utils.http.get('/user').then(function (user) { + _this9.state.user = user; + localStorage.setItem(_constants.LS_USER_KEY, JSON.stringify(user)); + return user; + }); + } + }, { + key: 'loadReactions', + value: function loadReactions() { + var _this10 = this; + + if (!this.accessToken) { + this.state.reactions = []; + return Promise.resolve([]); + } + + return this.getIssue().then(function (issue) { + if (!issue.reactions.total_count) return []; + return _utils.http.get(issue.reactions.url, {}, ''); + }).then(function (reactions) { + _this10.state.reactions = reactions; + return reactions; + }); + } + }, { + key: 'loadCommentReactions', + value: function loadCommentReactions() { + var _this11 = this; + + if (!this.accessToken) { + this.state.commentReactions = {}; + return Promise.resolve([]); + } + + var comments = this.state.comments; + var comentReactions = {}; + + return Promise.all(comments.map(function (comment) { + if (!comment.reactions.total_count) return []; + + var owner = _this11.owner, + repo = _this11.repo; + + return _utils.http.get('/repos/' + owner + '/' + repo + '/issues/comments/' + comment.id + '/reactions', {}); + })).then(function (reactionsArray) { + comments.forEach(function (comment, index) { + comentReactions[comment.id] = reactionsArray[index]; + }); + _this11.state.commentReactions = comentReactions; + + return comentReactions; + }); + } + }, { + key: 'login', + value: function login() { + window.location.href = this.loginLink; + } + }, { + key: 'logout', + value: function logout() { + localStorage.removeItem(_constants.LS_ACCESS_TOKEN_KEY); + localStorage.removeItem(_constants.LS_USER_KEY); + this.state.user = {}; + } + }, { + key: 'goto', + value: function goto(page) { + this.state.currentPage = page; + this.state.comments = undefined; + return this.loadComments(page); + } + }, { + key: 'like', + value: function like() { + var _this12 = this; + + if (!this.accessToken) { + alert('Login to Like'); + return Promise.reject(); + } + + var owner = this.owner, + repo = this.repo; + + + return _utils.http.post('/repos/' + owner + '/' + repo + '/issues/' + this.state.meta.number + '/reactions', { + content: 'heart' + }).then(function (reaction) { + _this12.state.reactions.push(reaction); + _this12.state.meta.reactions.heart++; + }); + } + }, { + key: 'unlike', + value: function unlike() { + var _this13 = this; + + if (!this.accessToken) return Promise.reject(); + + var _state = this.state, + user = _state.user, + reactions = _state.reactions; + + var index = reactions.findIndex(function (reaction) { + return reaction.user.login === user.login; + }); + return _utils.http.delete('/reactions/' + reactions[index].id).then(function () { + reactions.splice(index, 1); + _this13.state.meta.reactions.heart--; + }); + } + }, { + key: 'likeAComment', + value: function likeAComment(commentId) { + var _this14 = this; + + if (!this.accessToken) { + alert('Login to Like'); + return Promise.reject(); + } + + var owner = this.owner, + repo = this.repo; + + var comment = this.state.comments.find(function (comment) { + return comment.id === commentId; + }); + + return _utils.http.post('/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions', { + content: 'heart' + }).then(function (reaction) { + _this14.state.commentReactions[commentId].push(reaction); + comment.reactions.heart++; + }); + } + }, { + key: 'unlikeAComment', + value: function unlikeAComment(commentId) { + if (!this.accessToken) return Promise.reject(); + + var reactions = this.state.commentReactions[commentId]; + var comment = this.state.comments.find(function (comment) { + return comment.id === commentId; + }); + var user = this.state.user; + + var index = reactions.findIndex(function (reaction) { + return reaction.user.login === user.login; + }); + + return _utils.http.delete('/reactions/' + reactions[index].id).then(function () { + reactions.splice(index, 1); + comment.reactions.heart--; + }); + } + }]); + + return Gitment; + }(); + + module.exports = Gitment; + + /***/ }), + /* 6 */ + /***/ (function(module, exports, __webpack_require__) { + + "use strict"; + + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /** + * Modified from https://github.com/evil-icons/evil-icons + */ + + var close = exports.close = ''; + var github = exports.github = ''; + var heart = exports.heart = ''; + var spinner = exports.spinner = ''; + + /***/ }) + /******/ ]); +//# sourceMappingURL=gitment.browser.js.map \ No newline at end of file diff --git a/static/js/post.js b/static/js/post.js new file mode 100644 index 0000000..b4641ff --- /dev/null +++ b/static/js/post.js @@ -0,0 +1,45 @@ +--- + layout: null +--- + +/** + * 页面ready方法 + */ +$(document).ready(function() { + generateContent(); + // share(); + gitment(); +}); + +/** + * 侧边目录 + */ +function generateContent() { + var $mt = $('.toc'); + var toc = $(".post ul#markdown-toc").clone().get(0); + $mt.each(function(i,o){ + $(o).html(toc); + }); +} + +function share(){ + window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"1","bdSize":"24"},"share":{}}; + with(document)0[getElementsByTagName("script")[0].parentNode.appendChild(createElement('script')).src='//bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)]; +} + + +function gitment() { + var gitment = new Gitment({ + id: window.location.pathname, + owner: '{{site.github.username}}', + repo: '{{site.gitment.repo}}', + oauth: { + client_id: '{{site.gitment.client_id}}', + client_secret: '{{site.gitment.client_secret}}', + }, + }); + gitment.render('post-comment') + $("#post-comment").removeClass('hidden'); +} + + diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..2118707 --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,55 @@ +--- + layout: null +--- + +/** + * 页面ready方法 + */ +$(document).ready(function() { + + console.log("你不乖哦,彼此之间留点神秘感不好吗?"); + + backToTop(); + search(); +}); + +/** + * 回到顶部 + */ +function backToTop() { + $("[data-toggle='tooltip']").tooltip(); + var st = $(".page-scrollTop"); + var $window = $(window); + var topOffset; + //滚页面才显示返回顶部 + $window.scroll(function() { + var currnetTopOffset = $window.scrollTop(); + if (currnetTopOffset > 0 && topOffset > currnetTopOffset) { + st.fadeIn(500); + } else { + st.fadeOut(500); + } + topOffset = currnetTopOffset; + }); + + //点击回到顶部 + st.click(function() { + $window.scrollTop(0) + }); + + +} + +function search(){ + (function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){ + (w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t); + e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.appendChild(s); + })(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st'); + + _st('install','{{site.swiftype.searchId}}','2.0.0'); +} + + + + + diff --git a/static/js/tagCloud.js b/static/js/tagCloud.js new file mode 100644 index 0000000..2d8dc14 --- /dev/null +++ b/static/js/tagCloud.js @@ -0,0 +1,221 @@ +/* + * @copyright http://blog.rainynight.top/ + * Licensed under MIT + */ +(function($){ + $.fn.tagCloud = function(){ + + var $this = $(this); + + for(var i=0; i<$this.length;i++){ + execute($this.get(i)); + } + + return $this; + + function execute(shell){ + //半径 + var radius = 120; + //是否活动 + var active = true; + //是否分散 + var scatter = true; + //旋转速度 + var speed = 2; + //右旋偏移 + var rightOffset = 50; + //下旋偏移 + var downOffset = 0; + //与眼睛的距离 + var distance=300; + + var items = shell.getElementsByTagName('a'); + var itemWraps = []; + for(var i=0; ivItem2.z) + { + return -1; + } + else if(vItem1.zJump to...', + minimumHeaders: 3, + headers: 'h1, h2, h3, h4, h5, h6', + listType: 'ol', // values: [ol|ul] + showEffect: 'show', // values: [show|slideDown|fadeIn|none] + showSpeed: 'slow' // set to 0 to deactivate effect + }, + settings = $.extend(defaults, options); + + function fixedEncodeURIComponent (str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); + } + + var headers = $(settings.headers).filter(function() { + // get all headers with an ID + var previousSiblingName = $(this).prev().attr( "name" ); + if (!this.id && previousSiblingName) { + this.id = $(this).attr( "id", previousSiblingName.replace(/\./g, "-") ); + } + return this.id; + }), output = $(this); + if (!headers.length || headers.length < settings.minimumHeaders || !output.length) { + return; + } + + if (0 === settings.showSpeed) { + settings.showEffect = 'none'; + } + + var render = { + show: function() { output.hide().html(html).show(settings.showSpeed); }, + slideDown: function() { output.hide().html(html).slideDown(settings.showSpeed); }, + fadeIn: function() { output.hide().html(html).fadeIn(settings.showSpeed); }, + none: function() { output.html(html); } + }; + + var get_level = function(ele) { return parseInt(ele.nodeName.replace("H", ""), 10); } + var highest_level = headers.map(function(_, ele) { return get_level(ele); }).get().sort()[0]; + var return_to_top = ' '; + + var level = get_level(headers[0]), + this_level, + html = settings.title + " <"+settings.listType+">"; + headers.on('click', function() { + if (!settings.noBackToTopLinks) { + window.location.hash = this.id; + } + }) + .addClass('clickable-header') + .each(function(_, header) { + this_level = get_level(header); + if (!settings.noBackToTopLinks && this_level === highest_level) { + $(header).addClass('top-level-header').after(return_to_top); + } + if (this_level === level) // same level as before; same indenting + html += "
  • " + header.innerHTML + ""; + else if (this_level <= level){ // higher level than before; end parent ol + for(i = this_level; i < level; i++) { + html += "
  • " + } + html += "
  • " + header.innerHTML + ""; + } + else if (this_level > level) { // lower level than before; expand the previous to contain a ol + for(i = this_level; i > level; i--) { + html += "<"+settings.listType+">
  • " + } + html += "" + header.innerHTML + ""; + } + level = this_level; // update for the next one + }); + html += ""; + if (!settings.noBackToTopLinks) { + $(document).on('click', '.back-to-top', function() { + $(window).scrollTop(0); + window.location.hash = ''; + }); + } + + render[settings.showEffect](); + }; +})(jQuery); \ No newline at end of file diff --git a/static/octicons/LICENSE.txt b/static/octicons/LICENSE.txt new file mode 100644 index 0000000..69aa0d5 --- /dev/null +++ b/static/octicons/LICENSE.txt @@ -0,0 +1,9 @@ +(c) 2012-2015 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files diff --git a/static/octicons/README.md b/static/octicons/README.md new file mode 100644 index 0000000..1007073 --- /dev/null +++ b/static/octicons/README.md @@ -0,0 +1 @@ +If you intend to install Octicons locally, install `octicons-local.ttf`. It should appear as “github-octicons” in your font list. It is specially designed not to conflict with GitHub's web fonts. diff --git a/static/octicons/octicons-local.ttf b/static/octicons/octicons-local.ttf new file mode 100644 index 0000000..5a0a79f Binary files /dev/null and b/static/octicons/octicons-local.ttf differ diff --git a/static/octicons/octicons.css b/static/octicons/octicons.css new file mode 100644 index 0000000..9b86765 --- /dev/null +++ b/static/octicons/octicons.css @@ -0,0 +1,236 @@ +@font-face { + font-family: 'octicons'; + src: url('octicons.eot?#iefix') format('embedded-opentype'), + url('octicons.woff') format('woff'), + url('octicons.ttf') format('truetype'), + url('octicons.svg#octicons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* + +.octicon is optimized for 16px. +.mega-octicon is optimized for 32px but can be used larger. + +*/ +.octicon, .mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { font-size: 32px; } + +.octicon-alert:before { content: '\f02d'} /*  */ +.octicon-alignment-align:before { content: '\f08a'} /*  */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ +.octicon-alignment-unalign:before { content: '\f08b'} /*  */ +.octicon-arrow-down:before { content: '\f03f'} /*  */ +.octicon-arrow-left:before { content: '\f040'} /*  */ +.octicon-arrow-right:before { content: '\f03e'} /*  */ +.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ +.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ +.octicon-arrow-small-right:before { content: '\f071'} /*  */ +.octicon-arrow-small-up:before { content: '\f09f'} /*  */ +.octicon-arrow-up:before { content: '\f03d'} /*  */ +.octicon-beer:before { content: '\f069'} /*  */ +.octicon-book:before { content: '\f007'} /*  */ +.octicon-bookmark:before { content: '\f07b'} /*  */ +.octicon-briefcase:before { content: '\f0d3'} /*  */ +.octicon-broadcast:before { content: '\f048'} /*  */ +.octicon-browser:before { content: '\f0c5'} /*  */ +.octicon-bug:before { content: '\f091'} /*  */ +.octicon-calendar:before { content: '\f068'} /*  */ +.octicon-check:before { content: '\f03a'} /*  */ +.octicon-checklist:before { content: '\f076'} /*  */ +.octicon-chevron-down:before { content: '\f0a3'} /*  */ +.octicon-chevron-left:before { content: '\f0a4'} /*  */ +.octicon-chevron-right:before { content: '\f078'} /*  */ +.octicon-chevron-up:before { content: '\f0a2'} /*  */ +.octicon-circle-slash:before { content: '\f084'} /*  */ +.octicon-circuit-board:before { content: '\f0d6'} /*  */ +.octicon-clippy:before { content: '\f035'} /*  */ +.octicon-clock:before { content: '\f046'} /*  */ +.octicon-cloud-download:before { content: '\f00b'} /*  */ +.octicon-cloud-upload:before { content: '\f00c'} /*  */ +.octicon-code:before { content: '\f05f'} /*  */ +.octicon-color-mode:before { content: '\f065'} /*  */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /*  */ +.octicon-comment-discussion:before { content: '\f04f'} /*  */ +.octicon-credit-card:before { content: '\f045'} /*  */ +.octicon-dash:before { content: '\f0ca'} /*  */ +.octicon-dashboard:before { content: '\f07d'} /*  */ +.octicon-database:before { content: '\f096'} /*  */ +.octicon-device-camera:before { content: '\f056'} /*  */ +.octicon-device-camera-video:before { content: '\f057'} /*  */ +.octicon-device-desktop:before { content: '\f27c'} /*  */ +.octicon-device-mobile:before { content: '\f038'} /*  */ +.octicon-diff:before { content: '\f04d'} /*  */ +.octicon-diff-added:before { content: '\f06b'} /*  */ +.octicon-diff-ignored:before { content: '\f099'} /*  */ +.octicon-diff-modified:before { content: '\f06d'} /*  */ +.octicon-diff-removed:before { content: '\f06c'} /*  */ +.octicon-diff-renamed:before { content: '\f06e'} /*  */ +.octicon-ellipsis:before { content: '\f09a'} /*  */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /*  */ +.octicon-file-binary:before { content: '\f094'} /*  */ +.octicon-file-code:before { content: '\f010'} /*  */ +.octicon-file-directory:before { content: '\f016'} /*  */ +.octicon-file-media:before { content: '\f012'} /*  */ +.octicon-file-pdf:before { content: '\f014'} /*  */ +.octicon-file-submodule:before { content: '\f017'} /*  */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ +.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ +.octicon-file-text:before { content: '\f011'} /*  */ +.octicon-file-zip:before { content: '\f013'} /*  */ +.octicon-flame:before { content: '\f0d2'} /*  */ +.octicon-fold:before { content: '\f0cc'} /*  */ +.octicon-gear:before { content: '\f02f'} /*  */ +.octicon-gift:before { content: '\f042'} /*  */ +.octicon-gist:before { content: '\f00e'} /*  */ +.octicon-gist-secret:before { content: '\f08c'} /*  */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /*  */ +.octicon-git-commit:before { content: '\f01f'} /*  */ +.octicon-git-compare:before { content: '\f0ac'} /*  */ +.octicon-git-merge:before { content: '\f023'} /*  */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /*  */ +.octicon-globe:before { content: '\f0b6'} /*  */ +.octicon-graph:before { content: '\f043'} /*  */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /*  */ +.octicon-home:before { content: '\f08d'} /*  */ +.octicon-horizontal-rule:before { content: '\f070'} /*  */ +.octicon-hourglass:before { content: '\f09e'} /*  */ +.octicon-hubot:before { content: '\f09d'} /*  */ +.octicon-inbox:before { content: '\f0cf'} /*  */ +.octicon-info:before { content: '\f059'} /*  */ +.octicon-issue-closed:before { content: '\f028'} /*  */ +.octicon-issue-opened:before { content: '\f026'} /*  */ +.octicon-issue-reopened:before { content: '\f027'} /*  */ +.octicon-jersey:before { content: '\f019'} /*  */ +.octicon-jump-down:before { content: '\f072'} /*  */ +.octicon-jump-left:before { content: '\f0a5'} /*  */ +.octicon-jump-right:before { content: '\f0a6'} /*  */ +.octicon-jump-up:before { content: '\f073'} /*  */ +.octicon-key:before { content: '\f049'} /*  */ +.octicon-keyboard:before { content: '\f00d'} /*  */ +.octicon-law:before { content: '\f0d8'} /*  */ +.octicon-light-bulb:before { content: '\f000'} /*  */ +.octicon-link:before { content: '\f05c'} /*  */ +.octicon-link-external:before { content: '\f07f'} /*  */ +.octicon-list-ordered:before { content: '\f062'} /*  */ +.octicon-list-unordered:before { content: '\f061'} /*  */ +.octicon-location:before { content: '\f060'} /*  */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /*  */ +.octicon-logo-github:before { content: '\f092'} /*  */ +.octicon-mail:before { content: '\f03b'} /*  */ +.octicon-mail-read:before { content: '\f03c'} /*  */ +.octicon-mail-reply:before { content: '\f051'} /*  */ +.octicon-mark-github:before { content: '\f00a'} /*  */ +.octicon-markdown:before { content: '\f0c9'} /*  */ +.octicon-megaphone:before { content: '\f077'} /*  */ +.octicon-mention:before { content: '\f0be'} /*  */ +.octicon-microscope:before { content: '\f089'} /*  */ +.octicon-milestone:before { content: '\f075'} /*  */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /*  */ +.octicon-mortar-board:before { content: '\f0d7'} /*  */ +.octicon-move-down:before { content: '\f0a8'} /*  */ +.octicon-move-left:before { content: '\f074'} /*  */ +.octicon-move-right:before { content: '\f0a9'} /*  */ +.octicon-move-up:before { content: '\f0a7'} /*  */ +.octicon-mute:before { content: '\f080'} /*  */ +.octicon-no-newline:before { content: '\f09c'} /*  */ +.octicon-octoface:before { content: '\f008'} /*  */ +.octicon-organization:before { content: '\f037'} /*  */ +.octicon-package:before { content: '\f0c4'} /*  */ +.octicon-paintcan:before { content: '\f0d1'} /*  */ +.octicon-pencil:before { content: '\f058'} /*  */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /*  */ +.octicon-pin:before { content: '\f041'} /*  */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ +.octicon-playback-pause:before { content: '\f0bb'} /*  */ +.octicon-playback-play:before { content: '\f0bf'} /*  */ +.octicon-playback-rewind:before { content: '\f0bc'} /*  */ +.octicon-plug:before { content: '\f0d4'} /*  */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /*  */ +.octicon-podium:before { content: '\f0af'} /*  */ +.octicon-primitive-dot:before { content: '\f052'} /*  */ +.octicon-primitive-square:before { content: '\f053'} /*  */ +.octicon-pulse:before { content: '\f085'} /*  */ +.octicon-puzzle:before { content: '\f0c0'} /*  */ +.octicon-question:before { content: '\f02c'} /*  */ +.octicon-quote:before { content: '\f063'} /*  */ +.octicon-radio-tower:before { content: '\f030'} /*  */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /*  */ +.octicon-repo-clone:before { content: '\f04c'} /*  */ +.octicon-repo-force-push:before { content: '\f04a'} /*  */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /*  */ +.octicon-repo-pull:before { content: '\f006'} /*  */ +.octicon-repo-push:before { content: '\f005'} /*  */ +.octicon-rocket:before { content: '\f033'} /*  */ +.octicon-rss:before { content: '\f034'} /*  */ +.octicon-ruby:before { content: '\f047'} /*  */ +.octicon-screen-full:before { content: '\f066'} /*  */ +.octicon-screen-normal:before { content: '\f067'} /*  */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /*  */ +.octicon-server:before { content: '\f097'} /*  */ +.octicon-settings:before { content: '\f07c'} /*  */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /*  */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /*  */ +.octicon-split:before { content: '\f0c6'} /*  */ +.octicon-squirrel:before { content: '\f0b2'} /*  */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /*  */ +.octicon-steps:before { content: '\f0c7'} /*  */ +.octicon-stop:before { content: '\f08f'} /*  */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /*  */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /*  */ +.octicon-telescope:before { content: '\f088'} /*  */ +.octicon-terminal:before { content: '\f0c8'} /*  */ +.octicon-three-bars:before { content: '\f05e'} /*  */ +.octicon-thumbsdown:before { content: '\f0db'} /*  */ +.octicon-thumbsup:before { content: '\f0da'} /*  */ +.octicon-tools:before { content: '\f031'} /*  */ +.octicon-trashcan:before { content: '\f0d0'} /*  */ +.octicon-triangle-down:before { content: '\f05b'} /*  */ +.octicon-triangle-left:before { content: '\f044'} /*  */ +.octicon-triangle-right:before { content: '\f05a'} /*  */ +.octicon-triangle-up:before { content: '\f0aa'} /*  */ +.octicon-unfold:before { content: '\f039'} /*  */ +.octicon-unmute:before { content: '\f0ba'} /*  */ +.octicon-versions:before { content: '\f064'} /*  */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /*  */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/static/octicons/octicons.eot b/static/octicons/octicons.eot new file mode 100644 index 0000000..659adc4 Binary files /dev/null and b/static/octicons/octicons.eot differ diff --git a/static/octicons/octicons.less b/static/octicons/octicons.less new file mode 100644 index 0000000..301a113 --- /dev/null +++ b/static/octicons/octicons.less @@ -0,0 +1,235 @@ +@octicons-font-path: "."; +@octicons-version: "345f8bad9c5003db196d08f05e7f030fd2a32ff6"; + +@font-face { + font-family: 'octicons'; + src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')", + ~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')", + ~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')", + ~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')"; + font-weight: normal; + font-style: normal; +} + +// .octicon is optimized for 16px. +// .mega-octicon is optimized for 32px but can be used larger. +.octicon, .mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { font-size: 32px; } + +.octicon-alert:before { content: '\f02d'} /*  */ +.octicon-alignment-align:before { content: '\f08a'} /*  */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ +.octicon-alignment-unalign:before { content: '\f08b'} /*  */ +.octicon-arrow-down:before { content: '\f03f'} /*  */ +.octicon-arrow-left:before { content: '\f040'} /*  */ +.octicon-arrow-right:before { content: '\f03e'} /*  */ +.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ +.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ +.octicon-arrow-small-right:before { content: '\f071'} /*  */ +.octicon-arrow-small-up:before { content: '\f09f'} /*  */ +.octicon-arrow-up:before { content: '\f03d'} /*  */ +.octicon-beer:before { content: '\f069'} /*  */ +.octicon-book:before { content: '\f007'} /*  */ +.octicon-bookmark:before { content: '\f07b'} /*  */ +.octicon-briefcase:before { content: '\f0d3'} /*  */ +.octicon-broadcast:before { content: '\f048'} /*  */ +.octicon-browser:before { content: '\f0c5'} /*  */ +.octicon-bug:before { content: '\f091'} /*  */ +.octicon-calendar:before { content: '\f068'} /*  */ +.octicon-check:before { content: '\f03a'} /*  */ +.octicon-checklist:before { content: '\f076'} /*  */ +.octicon-chevron-down:before { content: '\f0a3'} /*  */ +.octicon-chevron-left:before { content: '\f0a4'} /*  */ +.octicon-chevron-right:before { content: '\f078'} /*  */ +.octicon-chevron-up:before { content: '\f0a2'} /*  */ +.octicon-circle-slash:before { content: '\f084'} /*  */ +.octicon-circuit-board:before { content: '\f0d6'} /*  */ +.octicon-clippy:before { content: '\f035'} /*  */ +.octicon-clock:before { content: '\f046'} /*  */ +.octicon-cloud-download:before { content: '\f00b'} /*  */ +.octicon-cloud-upload:before { content: '\f00c'} /*  */ +.octicon-code:before { content: '\f05f'} /*  */ +.octicon-color-mode:before { content: '\f065'} /*  */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /*  */ +.octicon-comment-discussion:before { content: '\f04f'} /*  */ +.octicon-credit-card:before { content: '\f045'} /*  */ +.octicon-dash:before { content: '\f0ca'} /*  */ +.octicon-dashboard:before { content: '\f07d'} /*  */ +.octicon-database:before { content: '\f096'} /*  */ +.octicon-device-camera:before { content: '\f056'} /*  */ +.octicon-device-camera-video:before { content: '\f057'} /*  */ +.octicon-device-desktop:before { content: '\f27c'} /*  */ +.octicon-device-mobile:before { content: '\f038'} /*  */ +.octicon-diff:before { content: '\f04d'} /*  */ +.octicon-diff-added:before { content: '\f06b'} /*  */ +.octicon-diff-ignored:before { content: '\f099'} /*  */ +.octicon-diff-modified:before { content: '\f06d'} /*  */ +.octicon-diff-removed:before { content: '\f06c'} /*  */ +.octicon-diff-renamed:before { content: '\f06e'} /*  */ +.octicon-ellipsis:before { content: '\f09a'} /*  */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /*  */ +.octicon-file-binary:before { content: '\f094'} /*  */ +.octicon-file-code:before { content: '\f010'} /*  */ +.octicon-file-directory:before { content: '\f016'} /*  */ +.octicon-file-media:before { content: '\f012'} /*  */ +.octicon-file-pdf:before { content: '\f014'} /*  */ +.octicon-file-submodule:before { content: '\f017'} /*  */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ +.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ +.octicon-file-text:before { content: '\f011'} /*  */ +.octicon-file-zip:before { content: '\f013'} /*  */ +.octicon-flame:before { content: '\f0d2'} /*  */ +.octicon-fold:before { content: '\f0cc'} /*  */ +.octicon-gear:before { content: '\f02f'} /*  */ +.octicon-gift:before { content: '\f042'} /*  */ +.octicon-gist:before { content: '\f00e'} /*  */ +.octicon-gist-secret:before { content: '\f08c'} /*  */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /*  */ +.octicon-git-commit:before { content: '\f01f'} /*  */ +.octicon-git-compare:before { content: '\f0ac'} /*  */ +.octicon-git-merge:before { content: '\f023'} /*  */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /*  */ +.octicon-globe:before { content: '\f0b6'} /*  */ +.octicon-graph:before { content: '\f043'} /*  */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /*  */ +.octicon-home:before { content: '\f08d'} /*  */ +.octicon-horizontal-rule:before { content: '\f070'} /*  */ +.octicon-hourglass:before { content: '\f09e'} /*  */ +.octicon-hubot:before { content: '\f09d'} /*  */ +.octicon-inbox:before { content: '\f0cf'} /*  */ +.octicon-info:before { content: '\f059'} /*  */ +.octicon-issue-closed:before { content: '\f028'} /*  */ +.octicon-issue-opened:before { content: '\f026'} /*  */ +.octicon-issue-reopened:before { content: '\f027'} /*  */ +.octicon-jersey:before { content: '\f019'} /*  */ +.octicon-jump-down:before { content: '\f072'} /*  */ +.octicon-jump-left:before { content: '\f0a5'} /*  */ +.octicon-jump-right:before { content: '\f0a6'} /*  */ +.octicon-jump-up:before { content: '\f073'} /*  */ +.octicon-key:before { content: '\f049'} /*  */ +.octicon-keyboard:before { content: '\f00d'} /*  */ +.octicon-law:before { content: '\f0d8'} /*  */ +.octicon-light-bulb:before { content: '\f000'} /*  */ +.octicon-link:before { content: '\f05c'} /*  */ +.octicon-link-external:before { content: '\f07f'} /*  */ +.octicon-list-ordered:before { content: '\f062'} /*  */ +.octicon-list-unordered:before { content: '\f061'} /*  */ +.octicon-location:before { content: '\f060'} /*  */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /*  */ +.octicon-logo-github:before { content: '\f092'} /*  */ +.octicon-mail:before { content: '\f03b'} /*  */ +.octicon-mail-read:before { content: '\f03c'} /*  */ +.octicon-mail-reply:before { content: '\f051'} /*  */ +.octicon-mark-github:before { content: '\f00a'} /*  */ +.octicon-markdown:before { content: '\f0c9'} /*  */ +.octicon-megaphone:before { content: '\f077'} /*  */ +.octicon-mention:before { content: '\f0be'} /*  */ +.octicon-microscope:before { content: '\f089'} /*  */ +.octicon-milestone:before { content: '\f075'} /*  */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /*  */ +.octicon-mortar-board:before { content: '\f0d7'} /*  */ +.octicon-move-down:before { content: '\f0a8'} /*  */ +.octicon-move-left:before { content: '\f074'} /*  */ +.octicon-move-right:before { content: '\f0a9'} /*  */ +.octicon-move-up:before { content: '\f0a7'} /*  */ +.octicon-mute:before { content: '\f080'} /*  */ +.octicon-no-newline:before { content: '\f09c'} /*  */ +.octicon-octoface:before { content: '\f008'} /*  */ +.octicon-organization:before { content: '\f037'} /*  */ +.octicon-package:before { content: '\f0c4'} /*  */ +.octicon-paintcan:before { content: '\f0d1'} /*  */ +.octicon-pencil:before { content: '\f058'} /*  */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /*  */ +.octicon-pin:before { content: '\f041'} /*  */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ +.octicon-playback-pause:before { content: '\f0bb'} /*  */ +.octicon-playback-play:before { content: '\f0bf'} /*  */ +.octicon-playback-rewind:before { content: '\f0bc'} /*  */ +.octicon-plug:before { content: '\f0d4'} /*  */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /*  */ +.octicon-podium:before { content: '\f0af'} /*  */ +.octicon-primitive-dot:before { content: '\f052'} /*  */ +.octicon-primitive-square:before { content: '\f053'} /*  */ +.octicon-pulse:before { content: '\f085'} /*  */ +.octicon-puzzle:before { content: '\f0c0'} /*  */ +.octicon-question:before { content: '\f02c'} /*  */ +.octicon-quote:before { content: '\f063'} /*  */ +.octicon-radio-tower:before { content: '\f030'} /*  */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /*  */ +.octicon-repo-clone:before { content: '\f04c'} /*  */ +.octicon-repo-force-push:before { content: '\f04a'} /*  */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /*  */ +.octicon-repo-pull:before { content: '\f006'} /*  */ +.octicon-repo-push:before { content: '\f005'} /*  */ +.octicon-rocket:before { content: '\f033'} /*  */ +.octicon-rss:before { content: '\f034'} /*  */ +.octicon-ruby:before { content: '\f047'} /*  */ +.octicon-screen-full:before { content: '\f066'} /*  */ +.octicon-screen-normal:before { content: '\f067'} /*  */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /*  */ +.octicon-server:before { content: '\f097'} /*  */ +.octicon-settings:before { content: '\f07c'} /*  */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /*  */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /*  */ +.octicon-split:before { content: '\f0c6'} /*  */ +.octicon-squirrel:before { content: '\f0b2'} /*  */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /*  */ +.octicon-steps:before { content: '\f0c7'} /*  */ +.octicon-stop:before { content: '\f08f'} /*  */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /*  */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /*  */ +.octicon-telescope:before { content: '\f088'} /*  */ +.octicon-terminal:before { content: '\f0c8'} /*  */ +.octicon-three-bars:before { content: '\f05e'} /*  */ +.octicon-thumbsdown:before { content: '\f0db'} /*  */ +.octicon-thumbsup:before { content: '\f0da'} /*  */ +.octicon-tools:before { content: '\f031'} /*  */ +.octicon-trashcan:before { content: '\f0d0'} /*  */ +.octicon-triangle-down:before { content: '\f05b'} /*  */ +.octicon-triangle-left:before { content: '\f044'} /*  */ +.octicon-triangle-right:before { content: '\f05a'} /*  */ +.octicon-triangle-up:before { content: '\f0aa'} /*  */ +.octicon-unfold:before { content: '\f039'} /*  */ +.octicon-unmute:before { content: '\f0ba'} /*  */ +.octicon-versions:before { content: '\f064'} /*  */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /*  */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/static/octicons/octicons.svg b/static/octicons/octicons.svg new file mode 100644 index 0000000..23faa86 --- /dev/null +++ b/static/octicons/octicons.svg @@ -0,0 +1,200 @@ + + + + +(c) 2012-2015 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/octicons/octicons.ttf b/static/octicons/octicons.ttf new file mode 100644 index 0000000..921111a Binary files /dev/null and b/static/octicons/octicons.ttf differ diff --git a/static/octicons/octicons.woff b/static/octicons/octicons.woff new file mode 100644 index 0000000..b49b54c Binary files /dev/null and b/static/octicons/octicons.woff differ diff --git a/static/octicons/sprockets-octicons.scss b/static/octicons/sprockets-octicons.scss new file mode 100644 index 0000000..1e8d1ca --- /dev/null +++ b/static/octicons/sprockets-octicons.scss @@ -0,0 +1,232 @@ +@font-face { + font-family: 'octicons'; + src: font-url('octicons.eot?#iefix') format('embedded-opentype'), + font-url('octicons.woff') format('woff'), + font-url('octicons.ttf') format('truetype'), + font-url('octicons.svg#octicons') format('svg'); + font-weight: normal; + font-style: normal; +} + +// .octicon is optimized for 16px. +// .mega-octicon is optimized for 32px but can be used larger. +.octicon, .mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { font-size: 32px; } + +.octicon-alert:before { content: '\f02d'} /*  */ +.octicon-alignment-align:before { content: '\f08a'} /*  */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ +.octicon-alignment-unalign:before { content: '\f08b'} /*  */ +.octicon-arrow-down:before { content: '\f03f'} /*  */ +.octicon-arrow-left:before { content: '\f040'} /*  */ +.octicon-arrow-right:before { content: '\f03e'} /*  */ +.octicon-arrow-small-down:before { content: '\f0a0'} /*  */ +.octicon-arrow-small-left:before { content: '\f0a1'} /*  */ +.octicon-arrow-small-right:before { content: '\f071'} /*  */ +.octicon-arrow-small-up:before { content: '\f09f'} /*  */ +.octicon-arrow-up:before { content: '\f03d'} /*  */ +.octicon-beer:before { content: '\f069'} /*  */ +.octicon-book:before { content: '\f007'} /*  */ +.octicon-bookmark:before { content: '\f07b'} /*  */ +.octicon-briefcase:before { content: '\f0d3'} /*  */ +.octicon-broadcast:before { content: '\f048'} /*  */ +.octicon-browser:before { content: '\f0c5'} /*  */ +.octicon-bug:before { content: '\f091'} /*  */ +.octicon-calendar:before { content: '\f068'} /*  */ +.octicon-check:before { content: '\f03a'} /*  */ +.octicon-checklist:before { content: '\f076'} /*  */ +.octicon-chevron-down:before { content: '\f0a3'} /*  */ +.octicon-chevron-left:before { content: '\f0a4'} /*  */ +.octicon-chevron-right:before { content: '\f078'} /*  */ +.octicon-chevron-up:before { content: '\f0a2'} /*  */ +.octicon-circle-slash:before { content: '\f084'} /*  */ +.octicon-circuit-board:before { content: '\f0d6'} /*  */ +.octicon-clippy:before { content: '\f035'} /*  */ +.octicon-clock:before { content: '\f046'} /*  */ +.octicon-cloud-download:before { content: '\f00b'} /*  */ +.octicon-cloud-upload:before { content: '\f00c'} /*  */ +.octicon-code:before { content: '\f05f'} /*  */ +.octicon-color-mode:before { content: '\f065'} /*  */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /*  */ +.octicon-comment-discussion:before { content: '\f04f'} /*  */ +.octicon-credit-card:before { content: '\f045'} /*  */ +.octicon-dash:before { content: '\f0ca'} /*  */ +.octicon-dashboard:before { content: '\f07d'} /*  */ +.octicon-database:before { content: '\f096'} /*  */ +.octicon-device-camera:before { content: '\f056'} /*  */ +.octicon-device-camera-video:before { content: '\f057'} /*  */ +.octicon-device-desktop:before { content: '\f27c'} /*  */ +.octicon-device-mobile:before { content: '\f038'} /*  */ +.octicon-diff:before { content: '\f04d'} /*  */ +.octicon-diff-added:before { content: '\f06b'} /*  */ +.octicon-diff-ignored:before { content: '\f099'} /*  */ +.octicon-diff-modified:before { content: '\f06d'} /*  */ +.octicon-diff-removed:before { content: '\f06c'} /*  */ +.octicon-diff-renamed:before { content: '\f06e'} /*  */ +.octicon-ellipsis:before { content: '\f09a'} /*  */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /*  */ +.octicon-file-binary:before { content: '\f094'} /*  */ +.octicon-file-code:before { content: '\f010'} /*  */ +.octicon-file-directory:before { content: '\f016'} /*  */ +.octicon-file-media:before { content: '\f012'} /*  */ +.octicon-file-pdf:before { content: '\f014'} /*  */ +.octicon-file-submodule:before { content: '\f017'} /*  */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ +.octicon-file-symlink-file:before { content: '\f0b0'} /*  */ +.octicon-file-text:before { content: '\f011'} /*  */ +.octicon-file-zip:before { content: '\f013'} /*  */ +.octicon-flame:before { content: '\f0d2'} /*  */ +.octicon-fold:before { content: '\f0cc'} /*  */ +.octicon-gear:before { content: '\f02f'} /*  */ +.octicon-gift:before { content: '\f042'} /*  */ +.octicon-gist:before { content: '\f00e'} /*  */ +.octicon-gist-secret:before { content: '\f08c'} /*  */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /*  */ +.octicon-git-commit:before { content: '\f01f'} /*  */ +.octicon-git-compare:before { content: '\f0ac'} /*  */ +.octicon-git-merge:before { content: '\f023'} /*  */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /*  */ +.octicon-globe:before { content: '\f0b6'} /*  */ +.octicon-graph:before { content: '\f043'} /*  */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /*  */ +.octicon-home:before { content: '\f08d'} /*  */ +.octicon-horizontal-rule:before { content: '\f070'} /*  */ +.octicon-hourglass:before { content: '\f09e'} /*  */ +.octicon-hubot:before { content: '\f09d'} /*  */ +.octicon-inbox:before { content: '\f0cf'} /*  */ +.octicon-info:before { content: '\f059'} /*  */ +.octicon-issue-closed:before { content: '\f028'} /*  */ +.octicon-issue-opened:before { content: '\f026'} /*  */ +.octicon-issue-reopened:before { content: '\f027'} /*  */ +.octicon-jersey:before { content: '\f019'} /*  */ +.octicon-jump-down:before { content: '\f072'} /*  */ +.octicon-jump-left:before { content: '\f0a5'} /*  */ +.octicon-jump-right:before { content: '\f0a6'} /*  */ +.octicon-jump-up:before { content: '\f073'} /*  */ +.octicon-key:before { content: '\f049'} /*  */ +.octicon-keyboard:before { content: '\f00d'} /*  */ +.octicon-law:before { content: '\f0d8'} /*  */ +.octicon-light-bulb:before { content: '\f000'} /*  */ +.octicon-link:before { content: '\f05c'} /*  */ +.octicon-link-external:before { content: '\f07f'} /*  */ +.octicon-list-ordered:before { content: '\f062'} /*  */ +.octicon-list-unordered:before { content: '\f061'} /*  */ +.octicon-location:before { content: '\f060'} /*  */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /*  */ +.octicon-logo-github:before { content: '\f092'} /*  */ +.octicon-mail:before { content: '\f03b'} /*  */ +.octicon-mail-read:before { content: '\f03c'} /*  */ +.octicon-mail-reply:before { content: '\f051'} /*  */ +.octicon-mark-github:before { content: '\f00a'} /*  */ +.octicon-markdown:before { content: '\f0c9'} /*  */ +.octicon-megaphone:before { content: '\f077'} /*  */ +.octicon-mention:before { content: '\f0be'} /*  */ +.octicon-microscope:before { content: '\f089'} /*  */ +.octicon-milestone:before { content: '\f075'} /*  */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /*  */ +.octicon-mortar-board:before { content: '\f0d7'} /*  */ +.octicon-move-down:before { content: '\f0a8'} /*  */ +.octicon-move-left:before { content: '\f074'} /*  */ +.octicon-move-right:before { content: '\f0a9'} /*  */ +.octicon-move-up:before { content: '\f0a7'} /*  */ +.octicon-mute:before { content: '\f080'} /*  */ +.octicon-no-newline:before { content: '\f09c'} /*  */ +.octicon-octoface:before { content: '\f008'} /*  */ +.octicon-organization:before { content: '\f037'} /*  */ +.octicon-package:before { content: '\f0c4'} /*  */ +.octicon-paintcan:before { content: '\f0d1'} /*  */ +.octicon-pencil:before { content: '\f058'} /*  */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /*  */ +.octicon-pin:before { content: '\f041'} /*  */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ +.octicon-playback-pause:before { content: '\f0bb'} /*  */ +.octicon-playback-play:before { content: '\f0bf'} /*  */ +.octicon-playback-rewind:before { content: '\f0bc'} /*  */ +.octicon-plug:before { content: '\f0d4'} /*  */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /*  */ +.octicon-podium:before { content: '\f0af'} /*  */ +.octicon-primitive-dot:before { content: '\f052'} /*  */ +.octicon-primitive-square:before { content: '\f053'} /*  */ +.octicon-pulse:before { content: '\f085'} /*  */ +.octicon-puzzle:before { content: '\f0c0'} /*  */ +.octicon-question:before { content: '\f02c'} /*  */ +.octicon-quote:before { content: '\f063'} /*  */ +.octicon-radio-tower:before { content: '\f030'} /*  */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /*  */ +.octicon-repo-clone:before { content: '\f04c'} /*  */ +.octicon-repo-force-push:before { content: '\f04a'} /*  */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /*  */ +.octicon-repo-pull:before { content: '\f006'} /*  */ +.octicon-repo-push:before { content: '\f005'} /*  */ +.octicon-rocket:before { content: '\f033'} /*  */ +.octicon-rss:before { content: '\f034'} /*  */ +.octicon-ruby:before { content: '\f047'} /*  */ +.octicon-screen-full:before { content: '\f066'} /*  */ +.octicon-screen-normal:before { content: '\f067'} /*  */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /*  */ +.octicon-server:before { content: '\f097'} /*  */ +.octicon-settings:before { content: '\f07c'} /*  */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /*  */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /*  */ +.octicon-split:before { content: '\f0c6'} /*  */ +.octicon-squirrel:before { content: '\f0b2'} /*  */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /*  */ +.octicon-steps:before { content: '\f0c7'} /*  */ +.octicon-stop:before { content: '\f08f'} /*  */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /*  */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /*  */ +.octicon-telescope:before { content: '\f088'} /*  */ +.octicon-terminal:before { content: '\f0c8'} /*  */ +.octicon-three-bars:before { content: '\f05e'} /*  */ +.octicon-thumbsdown:before { content: '\f0db'} /*  */ +.octicon-thumbsup:before { content: '\f0da'} /*  */ +.octicon-tools:before { content: '\f031'} /*  */ +.octicon-trashcan:before { content: '\f0d0'} /*  */ +.octicon-triangle-down:before { content: '\f05b'} /*  */ +.octicon-triangle-left:before { content: '\f044'} /*  */ +.octicon-triangle-right:before { content: '\f05a'} /*  */ +.octicon-triangle-up:before { content: '\f0aa'} /*  */ +.octicon-unfold:before { content: '\f039'} /*  */ +.octicon-unmute:before { content: '\f0ba'} /*  */ +.octicon-versions:before { content: '\f064'} /*  */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /*  */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */