【spring】通过GZIP压缩提高网络传输效率(可以实现任何资源的gzip压缩、包括AJAX)

gzip是http协议中使用的一种加密算法,客户端向web服务器端发出了请求后,通常情况下服务器端会将页面文件和其他资源,返回到客户端,客户端加载后渲染呈现,这种情况文件一般都比较大,如果开启Gzip ,那么服务器端响应后,会将页面,JS,CSS等文本文件或者其他文件通过高压缩算法将其压缩,然后传输到客户端,由客户端的浏览器负责解压缩与呈现。通常能节省40%以上的流量(一般都有60%左右),一些PHP,JSP文件也能够进行压缩。

1、通过WEB服务器打开GZIP压缩服务

目前大多数主流WEB中间件都支持GZIP压缩、下面以Tomcat 为例进行说明:

找到Tomcat 目录下的conf下的server.xml,并找到如下信息

      <Connector port = "8080" maxHttpHeaderSize = "8192" maxThreads = "150" minSpareThreads = "25"
              maxSpareThreads = "75" enableLookups = "false" redirectPort = "8443" acceptCount = "100"
              connectionTimeout = "20000" disableUploadTimeout = "true"

将它改成如下的形式(其实在上面代码的下面已经有了,将他们打开而已。):

      <Connector port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25"
             maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100"
             connectionTimeout="20000" disableUploadTimeout="true"
             compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata"
             compressableMimeType="text/html,text/xml" >

这样,就能够对html和xml进行压缩了,如果要压缩css 和 js,那么需要将

compressableMimeType=”text/html,text/xml”加入css和js:

           <Connector port="8080" ......... compressableMimeType="text/html,text/xml,text/css,text/javascript" >

一般文本类型的静态文件可以通过这种方式压缩后传输、提高传输效率。

已压缩过的静态文件(如图片)进行gzip压缩后大小基本无变化、所以一般不进行压缩。

2、通过过滤器实现gzip压缩

  1. package com.tyyd.framework.web;
  2. import java.io.IOException;
  3. import javax.servlet.Filter;
  4. import javax.servlet.FilterChain;
  5. import javax.servlet.FilterConfig;
  6. import javax.servlet.ServletException;
  7. import javax.servlet.ServletOutputStream;
  8. import javax.servlet.ServletRequest;
  9. import javax.servlet.ServletResponse;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import org.apache.commons.io.FilenameUtils;
  13. import org.apache.commons.lang.StringUtils;
  14. import com.tyyd.framework.core.AcwsInfo;
  15. import com.tyyd.framework.core.AcwsMonitorLog;
  16. import com.tyyd.framework.core.BufferedResponse;
  17. import com.tyyd.framework.core.util.ZipUtil;
  18. /**
  19. * HTTP访问过滤器
  20. */
  21. public class PageVisitFilter2 implements Filter {
  22. @Override
  23. public void init(FilterConfig filterConfig) throws ServletException {
  24. }
  25. @Override
  26. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  27. //性能监控
  28. long startTime = System.currentTimeMillis();
  29. HttpServletRequest request = (HttpServletRequest)req;
  30. HttpServletResponse response = (HttpServletResponse)res;
  31. String uri = request.getRequestURI();
  32. String ext = FilenameUtils.getExtension(uri);
  33. try{
  34. response.setHeader("Pragma", "No-cache");
  35. response.setHeader("Cache-Control", "no-cache");
  36. response.setDateHeader("Expires", -1);
  37. request.setCharacterEncoding("UTF-8");
  38. response.setCharacterEncoding("UTF-8");
  39. response.setHeader("renderer", "webkit");
  40. response.setHeader("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0 user-scalable=no");
  41. if(isGZipEncoding(request)){
  42. //需要过滤的扩展名:.htm,.html,.jsp,.js,.ajax,.css
  43. String gzippPattern=",.htm,.html,.jsp,.js,.ajax,.css,";
  44. if(StringUtils.indexOf(gzippPattern, ",."+ext+",")!=-1){
  45. BufferedResponse gzipResponse = new BufferedResponse(response);
  46. chain.doFilter(request, gzipResponse);
  47. byte[] srcData = gzipResponse.getResponseData();
  48. byte[] outData = null;
  49. if(srcData.length > 512){
  50. byte[] gzipData = ZipUtil.toGzipBytes(srcData);
  51. response.addHeader("Content-Encoding", "gzip");
  52. response.setContentLength(gzipData.length);
  53. outData = gzipData;
  54. } else {
  55. outData = srcData;
  56. }
  57. ServletOutputStream output = response.getOutputStream();
  58. output.write(outData);
  59. output.flush();
  60. } else {
  61. chain.doFilter(request, response);
  62. }
  63. return;
  64. }
  65. chain.doFilter(request, response);
  66. }catch(Exception e){
  67. }finally{
  68. AcwsMonitorLog.warnHttpVisit(startTime, request);
  69. }
  70. }
  71. @Override
  72. public void destroy() {
  73. }
  74. /**
  75. * 判断浏览器是否支持GZIP
  76. * @param request
  77. * @return
  78. */
  79. private boolean isGZipEncoding(HttpServletRequest request){
  80. boolean flag=false;
  81. String encoding=request.getHeader("Accept-Encoding");
  82. if(encoding.indexOf("gzip")!=-1){
  83. flag=true;
  84. }
  85. return flag;
  86. }
  87. }
  1. package com.tyyd.framework.core;
  2. import java.io.IOException;
  3. import java.io.OutputStreamWriter;
  4. import java.io.PrintWriter;
  5. import javax.servlet.ServletOutputStream;
  6. import javax.servlet.http.HttpServletResponse;
  7. import javax.servlet.http.HttpServletResponseWrapper;
  8. public class BufferedResponse extends HttpServletResponseWrapper {
  9. public static final int OT_NONE = 0, OT_WRITER = 1, OT_STREAM = 2;
  10. private BufferedOutputStream outputStream = null;
  11. private PrintWriter writer = null;
  12. private int outputType = OT_NONE;
  13. public BufferedResponse(HttpServletResponse response) {
  14. super(response);
  15. outputStream = new BufferedOutputStream();
  16. }
  17. public PrintWriter getWriter() throws IOException {
  18. if (outputType == OT_STREAM)
  19. throw new IllegalStateException();
  20. else if (outputType == OT_WRITER)
  21. return writer;
  22. else {
  23. outputType = OT_WRITER;
  24. writer = new PrintWriter(new OutputStreamWriter(outputStream,
  25. getCharacterEncoding()), true);
  26. return writer;
  27. }
  28. }
  29. public ServletOutputStream getOutputStream() throws IOException {
  30. if (outputType == OT_WRITER)
  31. throw new IllegalStateException();
  32. else if (outputType == OT_STREAM)
  33. return outputStream;
  34. else {
  35. outputType = OT_STREAM;
  36. return outputStream;
  37. }
  38. }
  39. public void flushBuffer() throws IOException {
  40. try{writer.flush();}catch(Exception e){}
  41. try{outputStream.flush();}catch(Exception e){}
  42. }
  43. public void reset() {
  44. outputType = OT_NONE;
  45. outputStream.reset();
  46. }
  47. public byte[] getResponseData() throws IOException {
  48. flushBuffer();
  49. return outputStream.toByteArray();
  50. }
  51. }
  1. /**
  2. * 版权所有:
  3. * 项目名称:框架
  4. * 创建者: Wangdf
  5. * 创建日期: 2015-2-27
  6. * 文件说明: AJAX 缓存输出流
  7. */
  8. package com.tyyd.framework.core;
  9. import java.io.ByteArrayOutputStream;
  10. import java.io.IOException;
  11. import javax.servlet.ServletOutputStream;
  12. public class BufferedOutputStream extends ServletOutputStream {
  13. private ByteArrayOutputStream outputStream = null;
  14. public BufferedOutputStream(){
  15. outputStream = new ByteArrayOutputStream(1024);
  16. }
  17. /**
  18. * Writes the specified byte to this output stream. The general
  19. * contract for <code>write</code> is that one byte is written
  20. * to the output stream. The byte to be written is the eight
  21. * low-order bits of the argument <code>b</code>. The 24
  22. * high-order bits of <code>b</code> are ignored.
  23. * <p>
  24. * Subclasses of <code>OutputStream</code> must provide an
  25. * implementation for this method.
  26. *
  27. * @param      b   the <code>byte</code>.
  28. * @exception  IOException  if an I/O error occurs. In particular,
  29. *             an <code>IOException</code> may be thrown if the
  30. *             output stream has been closed.
  31. */
  32. public void write(int b) throws IOException {
  33. outputStream.write(b);
  34. }
  35. /**
  36. * Writes <code>b.length</code> bytes from the specified byte array
  37. * to this output stream. The general contract for <code>write(b)</code>
  38. * is that it should have exactly the same effect as the call
  39. * <code>write(b, 0, b.length)</code>.
  40. *
  41. * @param      b   the data.
  42. * @exception  IOException  if an I/O error occurs.
  43. * @see        java.io.OutputStream#write(byte[], int, int)
  44. */
  45. public void write(byte b[]) throws IOException {
  46. outputStream.write(b);
  47. }
  48. /**
  49. * Writes <code>len</code> bytes from the specified byte array
  50. * starting at offset <code>off</code> to this output stream.
  51. * The general contract for <code>write(b, off, len)</code> is that
  52. * some of the bytes in the array <code>b</code> are written to the
  53. * output stream in order; element <code>b[off]</code> is the first
  54. * byte written and <code>b[off+len-1]</code> is the last byte written
  55. * by this operation.
  56. * <p>
  57. * The <code>write</code> method of <code>OutputStream</code> calls
  58. * the write method of one argument on each of the bytes to be
  59. * written out. Subclasses are encouraged to override this method and
  60. * provide a more efficient implementation.
  61. * <p>
  62. * If <code>b</code> is <code>null</code>, a
  63. * <code>NullPointerException</code> is thrown.
  64. * <p>
  65. * If <code>off</code> is negative, or <code>len</code> is negative, or
  66. * <code>off+len</code> is greater than the length of the array
  67. * <code>b</code>, then an <tt>IndexOutOfBoundsException</tt> is thrown.
  68. *
  69. * @param      b     the data.
  70. * @param      off   the start offset in the data.
  71. * @param      len   the number of bytes to write.
  72. * @exception  IOException  if an I/O error occurs. In particular,
  73. *             an <code>IOException</code> is thrown if the output
  74. *             stream is closed.
  75. */
  76. public void write(byte b[], int off, int len) throws IOException {
  77. outputStream.write(b, off, len);
  78. }
  79. /**
  80. * Writes a <code>String</code> to the client,
  81. * without a carriage return-line feed (CRLF)
  82. * character at the end.
  83. *
  84. *
  85. * @param s         the <code>String</code> to send to the client
  86. *
  87. * @exception IOException   if an input or output exception occurred
  88. *
  89. */
  90. public void print(String s) throws IOException {
  91. print(s, "UTF-8");
  92. }
  93. public void print(String s, String charsetName) throws IOException {
  94. /*
  95. * 解决中文乱码问题
  96. */
  97. outputStream.write(s.getBytes(charsetName));
  98. }
  99. /**
  100. * Writes a <code>boolean</code> value to the client,
  101. * with no carriage return-line feed (CRLF)
  102. * character at the end.
  103. *
  104. * @param b         the <code>boolean</code> value
  105. *              to send to the client
  106. *
  107. * @exception IOException   if an input or output exception occurred
  108. *
  109. */
  110. public void print(boolean b) throws IOException {
  111. print(b?"true":"false");
  112. }
  113. /**
  114. * Writes a character to the client,
  115. * with no carriage return-line feed (CRLF)
  116. * at the end.
  117. *
  118. * @param c         the character to send to the client
  119. *
  120. * @exception IOException   if an input or output exception occurred
  121. *
  122. */
  123. public void print(char c) throws IOException {
  124. print(String.valueOf(c));
  125. }
  126. /**
  127. *
  128. * Writes an int to the client,
  129. * with no carriage return-line feed (CRLF)
  130. * at the end.
  131. *
  132. * @param i         the int to send to the client
  133. *
  134. * @exception IOException   if an input or output exception occurred
  135. *
  136. */
  137. public void print(int i) throws IOException {
  138. print(String.valueOf(i));
  139. }
  140. /**
  141. *
  142. * Writes a <code>long</code> value to the client,
  143. * with no carriage return-line feed (CRLF) at the end.
  144. *
  145. * @param l         the <code>long</code> value
  146. *              to send to the client
  147. *
  148. * @exception IOException   if an input or output exception
  149. *              occurred
  150. *
  151. */
  152. public void print(long l) throws IOException {
  153. print(String.valueOf(l));
  154. }
  155. /**
  156. *
  157. * Writes a <code>float</code> value to the client,
  158. * with no carriage return-line feed (CRLF) at the end.
  159. *
  160. * @param f         the <code>float</code> value
  161. *              to send to the client
  162. *
  163. * @exception IOException   if an input or output exception occurred
  164. *
  165. *
  166. */
  167. public void print(float f) throws IOException {
  168. print(String.valueOf(f));
  169. }
  170. /**
  171. *
  172. * Writes a <code>double</code> value to the client,
  173. * with no carriage return-line feed (CRLF) at the end.
  174. *
  175. * @param d         the <code>double</code> value
  176. *              to send to the client
  177. *
  178. * @exception IOException   if an input or output exception occurred
  179. *
  180. */
  181. public void print(double d) throws IOException {
  182. print(String.valueOf(d));
  183. }
  184. /**
  185. * Writes a carriage return-line feed (CRLF)
  186. * to the client.
  187. *
  188. *
  189. *
  190. * @exception IOException   if an input or output exception occurred
  191. *
  192. */
  193. public void println() throws IOException {
  194. print("\r\n");
  195. }
  196. /**
  197. * Writes a <code>String</code> to the client,
  198. * followed by a carriage return-line feed (CRLF).
  199. *
  200. *
  201. * @param s         the <code>String</code> to write to the client
  202. *
  203. * @exception IOException   if an input or output exception occurred
  204. *
  205. */
  206. public void println(String s){
  207. println(s, "UTF-8");
  208. }
  209. public void println(String s, String charsetName){
  210. /*
  211. * 解决中文乱码问题
  212. */
  213. try {
  214. print(s,charsetName);
  215. println();
  216. } catch (IOException e) {
  217. throw new RuntimeException(e);
  218. }
  219. }
  220. /**
  221. *
  222. * Writes a <code>boolean</code> value to the client,
  223. * followed by a
  224. * carriage return-line feed (CRLF).
  225. *
  226. *
  227. * @param b         the <code>boolean</code> value
  228. *              to write to the client
  229. *
  230. * @exception IOException   if an input or output exception occurred
  231. *
  232. */
  233. public void println(boolean b) throws IOException {
  234. print(b);
  235. println();
  236. }
  237. /**
  238. *
  239. * Writes a character to the client, followed by a carriage
  240. * return-line feed (CRLF).
  241. *
  242. * @param c         the character to write to the client
  243. *
  244. * @exception IOException   if an input or output exception occurred
  245. *
  246. */
  247. public void println(char c) throws IOException {
  248. print(c);
  249. println();
  250. }
  251. /**
  252. *
  253. * Writes an int to the client, followed by a
  254. * carriage return-line feed (CRLF) character.
  255. *
  256. *
  257. * @param i         the int to write to the client
  258. *
  259. * @exception IOException   if an input or output exception occurred
  260. *
  261. */
  262. public void println(int i) throws IOException {
  263. print(i);
  264. println();
  265. }
  266. /**
  267. *
  268. * Writes a <code>long</code> value to the client, followed by a
  269. * carriage return-line feed (CRLF).
  270. *
  271. *
  272. * @param l         the <code>long</code> value to write to the client
  273. *
  274. * @exception IOException   if an input or output exception occurred
  275. *
  276. */
  277. public void println(long l) throws IOException {
  278. print(l);
  279. println();
  280. }
  281. /**
  282. *
  283. * Writes a <code>float</code> value to the client,
  284. * followed by a carriage return-line feed (CRLF).
  285. *
  286. * @param f         the <code>float</code> value
  287. *              to write to the client
  288. *
  289. *
  290. * @exception IOException   if an input or output exception
  291. *              occurred
  292. *
  293. */
  294. public void println(float f) throws IOException {
  295. print(f);
  296. println();
  297. }
  298. /**
  299. *
  300. * Writes a <code>double</code> value to the client,
  301. * followed by a carriage return-line feed (CRLF).
  302. *
  303. *
  304. * @param d         the <code>double</code> value
  305. *              to write to the client
  306. *
  307. * @exception IOException   if an input or output exception occurred
  308. *
  309. */
  310. public void println(double d) throws IOException {
  311. print(d);
  312. println();
  313. }
  314. /**
  315. * Flushes this output stream and forces any buffered output bytes
  316. * to be written out. The general contract of <code>flush</code> is
  317. * that calling it is an indication that, if any bytes previously
  318. * written have been buffered by the implementation of the output
  319. * stream, such bytes should immediately be written to their
  320. * intended destination.
  321. * <p>
  322. * If the intended destination of this stream is an abstraction provided by
  323. * the underlying operating system, for example a file, then flushing the
  324. * stream guarantees only that bytes previously written to the stream are
  325. * passed to the operating system for writing; it does not guarantee that
  326. * they are actually written to a physical device such as a disk drive.
  327. * <p>
  328. * The <code>flush</code> method of <code>OutputStream</code> does nothing.
  329. *
  330. * @exception  IOException  if an I/O error occurs.
  331. */
  332. public void flush() throws IOException {
  333. outputStream.flush();
  334. }
  335. /**
  336. * Closes this output stream and releases any system resources
  337. * associated with this stream. The general contract of <code>close</code>
  338. * is that it closes the output stream. A closed stream cannot perform
  339. * output operations and cannot be reopened.
  340. * <p>
  341. * The <code>close</code> method of <code>OutputStream</code> does nothing.
  342. *
  343. * @exception  IOException  if an I/O error occurs.
  344. */
  345. public void close() throws IOException {
  346. outputStream.close();
  347. }
  348. /**
  349. * Resets the <code>count</code> field of this byte array output
  350. * stream to zero, so that all currently accumulated output in the
  351. * output stream is discarded. The output stream can be used again,
  352. * reusing the already allocated buffer space.
  353. *
  354. * @see     java.io.ByteArrayInputStream#count
  355. */
  356. public void reset() {
  357. outputStream.reset();
  358. }
  359. public byte[] toByteArray() {
  360. return outputStream.toByteArray();
  361. }
  362. }

在web.xml中配置 PageVisitFilter,当我们访问应用中以.htm,.html,.jsp,.js,.ajax,.css结尾的资源的使用,服务器端就开启http gzip压缩,将压缩后的信息通过http 协议传递给浏览器.

  1. <filter>
  2. <filter-name>Page Visit Filter</filter-name>
  3. <filter-class>com.tyyd.framework.web.PageVisitFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>Page Visit Filter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

3、AJAX也可以通过这种方式压缩

只需知道ajax请求的后缀添加到下面的代码中即可:

//需要过滤的扩展名:.htm,.html,.jsp,.js,.ajax,.css

String gzippPattern=",.htm,.html,.jsp,.js,.ajax,.css,";

GZIP压缩提高网络传输效率的更多相关文章

  1. 网页启用Gzip压缩 提高浏览速度

    启用Gzip压缩的好处 它的好处显而易见,提高网页浏览速度,无论是之前说的精简代码.压缩图片都不如启用Gzip来的实在.下图为启用Gzip后的效果. Gzip压缩效率非常高,通常可以达到70%的压缩率 ...

  2. 170306、wamp中的Apache开启gzip压缩提高网站的响应速度

    一个网站的响应速度决定该网站的人气和质量,所以wamp配置的服务器也需要支持giz压缩来提高网站的响应速度,如何开启wamp的gzip压缩呢,经过在网站查找资料结合自己服务器中的配置,现在将这个方法分 ...

  3. Nginx开启Gzip压缩提高页面加载速度

    本文转自http://www.veryhuo.com/a/view/51706.html,如有侵权,请及时联系转载人删除! 在实际运维中,为了提高web页面的访问加载速度,一般会把静态资源(比如js. ...

  4. Okhttp3请求网络开启Gzip压缩

    前沿 首先OkHttp3是支持Gzip解压缩的,不过我们要明白,它是支持我们在发起请求的时候自动加入header,Accept-Encoding: gzip,而我们的服务器返回的时候header中有C ...

  5. 聊聊HTTP gzip压缩与常见的Android网络框架

    版权声明: 欢迎转载,但请保留文章原始出处 作者:GavinCT 出处:http://www.cnblogs.com/ct2011/p/5835990.html 进入主题之前,我们先来看一下客户端与服 ...

  6. Nginx网络架构实战学习笔记(三):nginx gzip压缩提升网站速度、expires缓存提升网站负载、反向代理实现nginx+apache动静分离、nginx实现负载均衡

    文章目录 nginx gzip压缩提升网站速度 expires缓存提升网站负载 反向代理实现nginx+apache动静分离 nginx实现负载均衡 nginx gzip压缩提升网站速度 网页内容的压 ...

  7. 在windows、linux中开启nginx的Gzip压缩大大提高页面、图片加载速度<转>

    为了降低tomcat服务的压力,把页面上的图片采用windows版的nginx进行加载,由于有些图片比较大,加载特别的慢,所以在nginx中打开了gzip的压缩功能.加载图片的速度快了很多. 通过站长 ...

  8. Java Web 减少网络 IO、静态资源磁盘 IO 有效的办法--响应使用 GZIP( 压缩http请求与响应gzip压缩)

    (转载http://blog.csdn.net/hylclxy/article/details/7779662) 出于节约流量考虑, 客户端在向服务端发送request的时候对post数据进行gzip ...

  9. nginx优化:配置gzip压缩页面提高访问速度(nginx1.18.0)

    一,为什么nginx要使用gzip 1,压缩的作用: 页面使用gzip压缩之后, 页面大小可以压缩到原来的1/7左右, 传输速度和页面打开时间都可以大幅度提高, 有利于用户访问页面体验的提升 2,Ng ...

随机推荐

  1. tortoisegit安装

    1.下载tortoisegit:https://tortoisegit.org/download/ 2.下载git 64位 3. 双击开始安装,选择默认,点击下一步 4.接着是选择安装目录,可以保持默 ...

  2. lfs(systemd版本)学习笔记-第2页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第1页 的地址:https://www.cnblogs.com/renren-study-no ...

  3. Application.Current的使用

    来源 http://www.cnblogs.com/symons/archive/2010/03/15/1686200.html Application.Current的使用 WPF程序对应一个App ...

  4. axure工具的使用总结

    ---恢复内容开始--- Axure工具的使用 axure是什么? Axure RP是一款快速原型设计工具,它不需要任何编程或写代码基础,就可以设计出交互效果良好的产品原型,常用于互联网产品设计.网页 ...

  5. vue权威指南笔记01——样式的设置

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  6. The stacking context

    文档中的层叠上下文由满足以下任意一个条件的元素形成: 1. z-index 值不为 "auto"的 绝对/相对定位. 2. position位fixed. 3. opacity 属 ...

  7. python第一百零五天 ---Django 基础 路由系统 URL 模板语言 ORM 操作

    一 路由系统 URL 1 url(r'^index/',views.index) url(r'^home/', views.Home.as_view()) 2 url(r'^detail-(\d+). ...

  8. Spring boot+ maven + thymeleaf + HTML 实现简单的web项目

    第一步: 创建一个SpringBoot应用 第二步: 创建一个实体,用来存储数据,在src/main/java/com/example/first下创建包entity , 在entity下创建Pers ...

  9. 12种不宜使用的javascript的语法

    1. == Javascript有两组相等运算符,一组是==和!=,另一组是===和!==.前者只比较值的相等,后者除了值以外,还比较类型是否相同. 请尽量不要使用前一组,永远只使用===和!==.因 ...

  10. 罗马数字转整数的golang实现

    罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I V X L C D M 例如, 罗马数字 2 写做 II ,即为两个并列的 1.12 写做 XII ,即为 X + ...