浏览代码

改进压缩算法

James 5 年之前
父节点
当前提交
4edbde801b
共有 1 个文件被更改,包括 55 次插入90 次删除
  1. 55 90
      src/main/java/com/jfinal/template/stat/Compressor.java

+ 55 - 90
src/main/java/com/jfinal/template/stat/Compressor.java

@@ -17,19 +17,26 @@
 package com.jfinal.template.stat;
 
 /**
- * 对 Text 节点进行压缩
+ * Compressor
  * 
- * 1:为追求性能极致,只压缩 Text 节点,所以压缩结果会存在一部分空白字符
- * 2:每次读取一行,按行进行压缩
- * 3:第一行左侧空白不压缩
- * 4:最后一行右侧空白不压缩(注意:最后一行以字符 '\n' 结尾时不算最后一行)
- * 5:第一行、最后一行以外的其它行左右侧都压缩
- * 6:文本之内的空白不压缩,例如字符串 "abc  def" 中的 "abc" 与 "def" 之间的空格不压缩
- * 7:压缩分隔符默认配置为 '\n',还可配置为 ' '。如果模板中含有 javascript 脚本,需配置为 '\n'
- * 8:可通过 Engine.setCompressor(Compressor) 来定制自己的实现类
- *    可使用第三方的压缩框架来定制,例如使用 google 的压缩框架:
- *      压缩 html: com.googlecode.htmlcompressor:htmlcompressor
- *      压缩 javascript: com.google.javascript:closure-compiler
+ * 压缩规则:
+ * 1:为追求性能极致只压缩模板中的静态文本内容,指令输出的内容不压缩,例如 #(blog.content) 输出的内容不会被压缩
+ *   由于模板静态内容会被缓存,所以只需压缩一次,性能被最大化
+ *    
+ * 2:多个连续空格字符压缩为一个空格字符
+ * 
+ * 3:空格字符与 '\n' 的组合,压缩为一个 separator 字符。separator 为 Compressor 类中的属性值
+ *    组合是指 0 个或多个空格字符与 1 个或多个 '\n' 字符组成的连续字符串
+ *    组合不区分字符出现的次序,例如下面的四个字符串都满足该组合条件:
+ *        "\n"    " \n "    "\n \n"    "\n \n  "
+ * 
+ * 注意事项:
+ * 1:html 模板中存在 javascript 时分隔字符要配置为 '\n',分符字符 ' ' 不支持 js 压缩
+ * 
+ * 2:由于多个连续的空格字符会被压缩为一个空格字符,所以当模板静态文本内容本身需要保持其多空格字符
+ *    不被压缩为一个时,不能使用该压缩功能,例如:
+ *      <input value="hello  ">
+ *    上例中的字符串 "hello  " 中的两个空格会被压缩为一个空格
  */
 public class Compressor {
 	
@@ -46,94 +53,52 @@ public class Compressor {
 	
 	public StringBuilder compress(StringBuilder content) {
 		int len = content.length();
+		StringBuilder ret = new StringBuilder(len);
 		
-		// 仅包含一个字符 '\n',需单独处理,否则会返回空字符串 ""
-		// 测试用例: "#date()\n#date()" "#(1)\n#(2)"
-		if (len == 1) {
-			// 换行字符 '\n' 替换为 separator。空格除外的空白字符替换为 ' ' 压缩效果更好,例如 '\t'
-			if (content.charAt(0) == '\n') {
-				content.setCharAt(0, separator);
-			} else if (content.charAt(0) < ' ') {
-				content.setCharAt(0, ' ');
-			}
-			return content;
-		}
-		
+		char ch;
+		boolean hasLineFeed;
 		int begin = 0;
 		int forward = 0;
-		int lineType = 1;		// 1 表示第一行
-		StringBuilder result = null;
+		
 		while (forward < len) {
-			if (content.charAt(forward) == '\n') {
-				if (result == null) {
-					result = new StringBuilder(len);		// 延迟创建
+			// 扫描空白字符
+			hasLineFeed = false;
+			while (forward < len) {
+				ch = content.charAt(forward);
+				if (ch <= ' ') {			// 包含换行字符在内的空白字符
+					if (ch == '\n') {		// 包含换行字符
+						hasLineFeed = true;
+					}
+					forward++;
+				} else {					// 非空白字符
+					break ;
 				}
-				compressLine(content, begin, forward - 1, lineType, result);
-				
-				begin = forward + 1;
-				forward = begin;
-				lineType = 2;	// 2 表示中间行
-			} else {
-				forward++;
-			}
-		}
-		
-		if (lineType == 1) {	// 此时为 1,表示既是第一行也是最后一行
-			return content;
-		}
-		
-		lineType = 3;			// 3 表示最后一行
-		compressLine(content, begin, forward - 1, lineType, result);
-		
-		return result;
-	}
-	
-	/**
-	 * 按行压缩
-	 * 
-	 * 只压缩文本前后的空白字符,文本内部的空白字符不压缩,极大简化压缩算法、提升压缩效率,并且压缩结果也不错
-	 * 
-	 * @param content 被处理行文本所在的 StringBuilder 对象
-	 * @param start 被处理行文本的开始下标
-	 * @param end 被处理行文本的结束下标(注意 end 下标所指向的字符被包含在处理的范围之内)
-	 * @param lineType 1 表示第一行,2 表示中间行,3 表示最后一行
-	 * @param result 存放压缩结果
-	 */
-	protected void compressLine(StringBuilder content, int start, int end, int lineType, StringBuilder result) {
-		// 第一行不压缩左侧空白
-		if (lineType != 1) {
-			while (start <= end && content.charAt(start) <= ' ') {
-				start++;
-			}
-		}
-		
-		// 最后一行不压缩右侧空白
-		if (lineType != 3) {
-			while (end >= start && content.charAt(end) <= ' ') {
-				end--;
-			}
-		}
-		
-		// 空白行可出现 start 大于 end 的情况
-		if (start <= end) {
-			for (int i = start; i <= end; i++) {
-				result.append(content.charAt(i));
 			}
 			
-			// 最后一行右侧未压缩,不能添加分隔字符。最后一行以 '\n' 结尾时 lineType 一定不为 3
-			if (lineType != 3) {
-				result.append(separator);
+			// 压缩空白字符
+			if (begin != forward) {
+				if (hasLineFeed) {
+					ret.append(separator);
+				} else {
+					ret.append(' ');
+				}
 			}
-		}
-		// 空白行,且是第一行,需要添加分隔字符,否则会被压缩去除掉该空行
-		// 测试用例:"id=#(123)\nand"    "id=#(123)   \nand"
-		else {
-			if (lineType == 1) {
-				// 第一行不压缩左侧空白的规则,是针对其为 "非空白行",所以此处没有原样保留空白字符
-				// 最后一行不压缩右侧空白的规则,也是针对其为 "非空白行"
-				result.append(separator);
+			
+			// 复制非空白字符
+			while (forward < len) {
+				ch = content.charAt(forward);
+				if (ch > ' ') {
+					ret.append(ch);
+					forward++;
+				} else {
+					break ;
+				}
 			}
+			
+			begin = forward;
 		}
+		
+		return ret;
 	}
 }