XML数据库的尝试
首先祝大家新年快乐.身体健康,平安就是福气.
对于一般的个人迷你项目,数据量不大的时候,完全没有必要使用数据库,管理数据使用XML就可以了.
自己尝试写了一个XML数据库,插入1w条小记录,大概3M大小,然后将一半数据进行更新,大约耗时3秒钟.
XML数据库其实就是一个内存数据库,数据都在内存里面,速度不慢.
然后由于是XML序列化的,其实ORM也不需要了.每个数据库文件保存一种格式的数据.
废话不说,上代码
先是数据模型:
/*
* Created by SharpDevelop.
* User: scs
* Date: 2014/12/30
* Time: 14:07
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
using System;
using DevKit.Common;
namespace DevKit.HtmlUtility
{
/// <summary>
/// Description of Class1.
/// </summary>
[Serializable]
public class CodeSnap
{
/// <summary>
/// 标题
/// </summary>
public string Title = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string Descrpition = string.Empty;
/// <summary>
/// 类别
/// </summary>
public string Catalog = string.Empty;
/// <summary>
/// Tag
/// </summary>
public string Tag = string.Empty;
/// <summary>
/// 代码
/// </summary>
public string Code = string.Empty;
/// <summary>
/// 检索
/// </summary>
/// <param name="strKeyword">检索关键字</param>
/// <returns></returns>
Boolean Search(string strKeyword)
{
return false;
} }
}
数据模型是领域的数据,但是删除标志,更新时间这些数据库使用的字段也是需要的.由于需要序列化,必须打上[Serializable]
/// <summary>
/// 数据库记录
/// </summary>
[Serializable]
public class Model<T>
{
/// <summary>
/// 删除标志
/// </summary>
public Boolean IsDel;
/// <summary>
/// 统一编号
/// </summary>
public string DBId;
/// <summary>
/// 最后更新时间
/// </summary>
public DateTime LastUpdate;
/// <summary>
/// 数据
/// </summary>
public T DataRec;
}
最后是数据库引擎的代码,这里用到了深拷贝
/// <summary>
/// 数据库引擎
/// </summary>
public class XmlDataBase<T>
{
/// <summary>
/// 数据库状态
/// </summary>
public string Status = "Close";
/// <summary>
/// 数据表
/// </summary>
List<Model<T>> list = new List<Model<T>>();
/// <summary>
/// 数据库文件
/// </summary>
string DBfilename = string.Empty;
/// <summary>
/// 数据库记录数[Without IsDel]
/// </summary>
/// <returns></returns>
public int getCount()
{
return list.Count(x => {
return !x.IsDel;
});
}
/// <summary>
/// 数据库记录数[With IsDel]
/// </summary>
/// <returns></returns>
public int getCountWithDel()
{
return list.Count;
}
/// <summary>
/// 新建并且打开数据库
/// </summary>
/// <param name="xmlfilename"></param>
public XmlDataBase(string xmlfilename)
{
DBfilename = xmlfilename;
if (System.IO.File.Exists(xmlfilename)) {
list = Utility.LoadObjFromXml<List<Model<T>>>(DBfilename);
}
}
/// <summary>
/// 压缩数据库
/// </summary>
public void Compress()
{
var Compresslist = new List<Model<T>>();
Func<Model<T>,Boolean> inner = (x) => {
return (!x.IsDel);
};
Compresslist = list.FindAll(new Predicate<Model<T>>(inner));
list = Compresslist;
Commit();
}
/// <summary>
/// 添加
/// </summary>
/// <param name="rec"></param>
public void AppendRec(T rec)
{
var dbrec = new Model<T>();
dbrec.DBId = Guid.NewGuid().ToString();
dbrec.DataRec = Common.Utility.DeepCopy(rec);
dbrec.LastUpdate = DateTime.Now;
list.Add(dbrec);
}
/// <summary>
/// 删除
/// </summary>
/// <param name="rec"></param>
public void DelRec(Model<T> rec)
{
rec.IsDel = true;
UpdateDB(Utility.DeepCopy(rec));
}
/// <summary>
/// 更新
/// </summary>
/// <param name="rec"></param>
public void UpdataRec(Model<T> rec)
{
UpdateDB(Utility.DeepCopy(rec));
}
/// <summary>
/// 数据的修改
/// </summary>
/// <param name="rec">传递过来对象的深拷贝</param>
void UpdateDB(Model<T> rec)
{
for (int i = ; i < list.Count; i++) {
if (rec.DBId == list[i].DBId) {
rec.LastUpdate = DateTime.Now;
//不允许内部数据使用外部数据的指针引用
//这里使用深拷贝
list[i] = rec;
break;
}
}
}
/// <summary>
/// 提交更新
/// </summary>
public void Commit()
{
Utility.SaveObjAsXml(DBfilename, list);
}
/// <summary>
/// 检索
/// </summary>
/// <param name="SearchMethod"></param>
/// <returns>数据对象的深拷贝</returns>
public List<Model<T>> Search(Func<T,Boolean> SearchMethod)
{
Func<Model<T>,Boolean> inner = (x) => {
return (SearchMethod(x.DataRec) && !x.IsDel);
};
List<Model<T>> t = new List<Model<T>>();
foreach (Model<T> element in list.FindAll(new Predicate<Model<T>>(inner))) {
//这里也是将数据的副本给与外部
t.Add(Utility.DeepCopy(element));
}
return t;
}
}
数据库内部有一个列表,列表里面存放着数据记录,每个数据记录包括[业务数据]和[数据库信息]
数据的读取,给外部的是一个数据的深拷贝,这样的话,保证了外部对于数据的修改不会影响内部数据.
在传统的数据库中,一般都是通过TCP协议交换数据的,所以,数据其实也是一个深拷贝.
读取如此,保存数据也是将列表替换为一个外部对象的深拷贝.
每次保存数据的时候,其实是将所有的数据都写入数据XML文件中,当然,数据量少的前提下,这样做是可以的.
下面是一个使用的例子:数据库的New语句
Common.XmlDataBase<CodeSnap> db= new Common.XmlDataBase<CodeSnap>(@"C:\中和软件\CodeSnap.xml");;
void BtnAppendClick(object sender, EventArgs e)
{
Stopwatch x = new Stopwatch();
x.Start();
for (int i = ; i < ; i++) {
var r = new CodeSnap();
r.Title = "Title" + i.ToString();
r.Descrpition = "Descrpition";
r.Tag = "Tag";
r.Code = "Code";
db.AppendRec(r);
}
db.Commit();
var t = db.Search((m) => {
return true;
});
for (int i = ; i < t.Count; i++) {
if (i % == ) {
t[i].DataRec.Title = "New Title";
db.UpdataRec(t[i]);
}
}
db.Commit();
x.Stop();
MessageBox.Show(x.Elapsed.ToString());
}
这个只是一个XML数据的雏形,原代码基本上都在这里了.
可以改进的地方大概如下:NameSpace这些XML特有的属性的去除.
<ArrayOfModelOfCodeSnap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
现在Key是用GUID的,这个东西也蛮大的,如果不考虑压缩数据库的问题,可以使用数字连番.
(如果使用数字连番的话,由于压缩数据库会改变数据记录数,可能出现主健重复的问题)
其他压缩,例如时间,现在使用标准的DateTime.Now,所以时间也很冗长.以后可以将时间格式化后,保存为文字列.
<IsDel>false</IsDel>
<DBId>ef65bff8-4951-464d-bd8f-432f1148b9f8</DBId>
<LastUpdate>2014-12-31T11:02:43.5750566+08:00</LastUpdate>
当然,XML也可以换成JSON的,这样的话,数据可以更小,但是JSON操作还不是NET内置的功能,所以暂时不使用.
里面用到的XML操作和深拷贝代码如下
}
/// <summary>
/// 保存对象
/// </summary>
public static void SaveObjAsXml<T>(string filename, T Obj)
{
var xml = new XmlSerializer(typeof(T));
var writer = new StreamWriter(filename);
xml.Serialize(writer, Obj);
writer.Close();
}
/// <summary>
/// 读取对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="filename"></param>
/// <returns></returns>
public static T LoadObjFromXml<T>(string filename)
{
var xml = new XmlSerializer(typeof(T));
var reader = new StreamReader(filename);
T obj = (T)xml.Deserialize(reader);
reader.Close();
return obj;
}
/// <summary>
/// 深拷贝
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static T DeepCopy<T>(T obj){
BinaryFormatter bFormatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
bFormatter.Serialize(stream, obj);
stream.Seek(, SeekOrigin.Begin);
return (T)bFormatter.Deserialize(stream);
}
出处:https://www.cnblogs.com/TextEditor/p/4195361.html
完整代码参考:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml; namespace ConsoleApplication1.xml
{
/// <summary>
/// 数据库记录
/// </summary>
[Serializable]
public class Model<T>
{
/// <summary>
/// 删除标志
/// </summary>
public Boolean IsDel;
/// <summary>
/// 统一编号
/// </summary>
public string DBId;
/// <summary>
/// 最后更新时间
/// </summary>
public DateTime LastUpdate;
/// <summary>
/// 数据
/// </summary>
public T DataRec;
}
/// <summary>
/// 数据库引擎
/// </summary>
public class XmlDataBase<T>
{
/// <summary>
/// 数据表
/// </summary>
List<Model<T>> list = new List<Model<T>>(); /// <summary>
/// 数据库文件
/// </summary>
string DBfilename = string.Empty; /// <summary>
/// 数据库状态
/// </summary>
public string Status = "Close"; /// <summary>
/// 数据库记录数[Without IsDel]
/// </summary>
/// <returns></returns>
public int getCountWithoutDel()
{
Refresh();
return list.Count(x => !x.IsDel); } /// <summary>
/// 数据库记录数[With All]
/// </summary>
/// <returns></returns>
public int getCountWithAll()
{
Refresh();
return list.Count;
} /// <summary>
/// 新建并且打开数据库
/// </summary>
/// <param name="xmlfilename"></param>
public XmlDataBase(string xmlfilename)
{
DBfilename = xmlfilename;
if (System.IO.File.Exists(DBfilename))
{
list = LoadObjFromXml<List<Model<T>>>(DBfilename);
}
} /// <summary>
/// 刷新数据
/// </summary>
public void Refresh()
{
//非静态,所以,可能在其他地方发生了数据更新
if (System.IO.File.Exists(DBfilename))
{
list = LoadObjFromXml<List<Model<T>>>(DBfilename);
}
} /// <summary>
/// 压缩数据库,清理已经删除的记录
/// </summary>
public void Compress()
{
var Compresslist = new List<Model<T>>();
Func<Model<T>, Boolean> inner = (x) => (!x.IsDel);
Compresslist = list.FindAll(new Predicate<Model<T>>(inner));
list = Compresslist;
Commit();
} /// <summary>
/// 添加
/// </summary>
/// <param name="rec"></param>
public void AppendRec(T rec)
{
var dbrec = new Model<T>();
dbrec.DBId = Guid.NewGuid().ToString();
dbrec.DataRec = DeepCopy(rec);
dbrec.LastUpdate = DateTime.Now;
list.Add(dbrec);
} /// <summary>
/// 删除
/// </summary>
/// <param name="rec"></param>
public void DelRecord(Model<T> rec)
{
rec.IsDel = true;
UpdateDB(DeepCopy(rec));
} /// <summary>
/// 删除制定编号数据
/// </summary>
/// <param name="DBId"></param>
public void DelRecordByDBID(string DBId)
{
for (int i = ; i < list.Count; i++)
{
if (DBId == list[i].DBId)
{
list[i].IsDel = true;
list[i].LastUpdate = DateTime.Now;
break;
}
}
} /// <summary>
/// 更新
/// </summary>
/// <param name="rec"></param>
public void UpdataRec(Model<T> rec)
{
UpdateDB(DeepCopy(rec));
} /// <summary>
/// 数据的修改
/// </summary>
/// <param name="rec">传递过来对象的深拷贝</param>
private void UpdateDB(Model<T> rec)
{
for (int i = ; i < list.Count; i++)
{
if (rec.DBId == list[i].DBId)
{
rec.LastUpdate = DateTime.Now;
//不允许内部数据使用外部数据的指针引用,这里使用深拷贝
list[i] = rec;
break;
}
}
} /// <summary>
/// 提交更新
/// </summary>
public void Commit()
{
SaveObjAsXml(DBfilename, list);
} /// <summary>
/// 检索
/// </summary>
/// <param name="SearchMethod"></param>
/// <returns>数据对象的深拷贝</returns>
public List<Model<T>> SearchAsDBRecords(Func<T, Boolean> SearchMethod)
{
Refresh();
Func<Model<T>, Boolean> inner = (x) => (SearchMethod(x.DataRec) && !x.IsDel);
var t = new List<Model<T>>();
foreach (Model<T> element in list.FindAll(new Predicate<Model<T>>(inner)))
{
//这里也是将数据的副本给与外部
t.Add(DeepCopy(element));
}
return t;
} /// <summary>
/// 检索(根据数据号)
/// </summary>
/// <param name="DBID">数据号</param>
/// <returns></returns>
public Model<T> SearchAsDBRecordByDBID(string DBID)
{
Refresh();
Model<T> result = list.Find((x) => x.DBId == DBID && !x.IsDel);
if (result != null)
return DeepCopy(result);
return null;
} /// <summary>
/// 检索,(如果只是获取T对象列表)
/// </summary>
/// <param name="SearchMethod"></param>
/// <returns>数据对象的深拷贝</returns>
public List<T> SearchAsObjRecords(Func<T, Boolean> SearchMethod)
{
Refresh();
Func<Model<T>, Boolean> inner = (x) => (SearchMethod(x.DataRec) && !x.IsDel);
var t = new List<T>();
foreach (Model<T> element in list.FindAll(new Predicate<Model<T>>(inner)))
{
//这里也是将数据的副本给与外部
t.Add(DeepCopy(element.DataRec));
}
return t;
} /// <summary>
/// 检索(根据数据号)(如果只是获取T对象)
/// 调用前请使用IsRecordExist函数确认数据是否存在
/// </summary>
/// <param name="DBID">数据号</param>
/// <returns></returns>
public T SearchAsObjRecordByDBID(string DBID)
{
Refresh();
T t = default(T);
Model<T> result = list.Find((x) => x.DBId == DBID);
t =DeepCopy(result.DataRec);
return t;
} /// <summary>
/// 是否存在数据
/// </summary>
/// <param name="SearchMethod"></param>
/// <returns></returns>
public bool IsRecordExists(Func<T, Boolean> SearchMethod)
{
Func<Model<T>, Boolean> inner = (x) => (SearchMethod(x.DataRec) && !x.IsDel);
return list.FindAll(new Predicate<Model<T>>(inner)).Count != ;
} /// <summary>
/// 是否存在指定番号数据
/// </summary>
/// <param name="DBID"></param>
/// <returns></returns>
public bool IsRecordExistsByDBID(string DBID)
{
Func<Model<T>, Boolean> inner = (x) => (x.DBId == DBID && !x.IsDel);
return list.FindAll(new Predicate<Model<T>>(inner)).Count != ;
} /// <summary>
/// 保存对象
/// </summary>
/// <param name="filename"></param>
/// <param name="Obj"></param>
private static void SaveObjAsXml<T>(string filename, T Obj)
{
var settings = new XmlWriterSettings();
settings.Indent = true;
//NewLineChars对于String属性的东西无效
//这是对于XML中换行有效,
//String的换行会变成Console的NewLine /n
settings.NewLineChars = System.Environment.NewLine;
var xml = new XmlSerializer(typeof(T));
var writer = XmlWriter.Create(filename, settings);
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
xml.Serialize(writer, Obj, ns);
writer.Close();
} /// <summary>
/// 读取对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="filename"></param>
/// <returns></returns>
private static T LoadObjFromXml<T>(string filename)
{
var setting = new XmlReaderSettings();
var xml = new XmlSerializer(typeof(T));
var reader = XmlReader.Create(filename, setting);
T obj = (T)xml.Deserialize(reader);
reader.Close();
return obj;
} /// <summary>
/// 深拷贝
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private static T DeepCopy<T>(T obj)
{
var bFormatter = new BinaryFormatter();
var stream = new MemoryStream();
bFormatter.Serialize(stream, obj);
stream.Seek(, SeekOrigin.Begin);
return (T)bFormatter.Deserialize(stream);
}
}
}
对象定义格式如下:
// 模式一 using System;
namespace ConsoleApplication1.xml
{
/// <summary>
/// Description of Class1.
/// </summary>
[Serializable]
public class CodeSnap
{
/// <summary>
/// 标题
/// </summary>
public string Title = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string Descrpition = string.Empty;
/// <summary>
/// 类别
/// </summary>
public string Catalog = string.Empty;
/// <summary>
/// Tag
/// </summary>
public string Tag = string.Empty;
/// <summary>
/// 代码
/// </summary>
public string Code = string.Empty;
/// <summary>
/// 检索
/// </summary>
/// <param name="strKeyword">检索关键字</param>
/// <returns></returns>
public Boolean Search(string strKeyword)
{
return false;
} }
} // 模式二
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace ConsoleApplication1.xml
{
[Serializable]
public class CodeBook
{
private string bName; public string BookName
{
get { return bName; }
set { bName = value; }
} private string bAnthor; public string Anthor
{
get { return bAnthor; }
set { bAnthor = value; }
} private int _price; public int Price
{
get { return _price; }
set { _price = value; }
} }
}
调用方式参考:
xml.XmlDataBase<xml.CodeSnap> db = new xml.XmlDataBase<xml.CodeSnap>(@"C:\temp\CodeSnap.xml");
for (int i = ; i < ; i++)
{
var r = new xml.CodeSnap();
r.Title = "Title" + i.ToString();
r.Descrpition = "Descrpition";
r.Tag = "Tag";
r.Code = "Code";
db.AppendRec(r);
}
//db.Commit(); xml.XmlDataBase<xml.CodeBook> db1 = new xml.XmlDataBase<xml.CodeBook>(@"C:\temp\book.xml");
for (int i = ; i < ; i++)
{
var r = new xml.CodeBook();
r.Anthor = "anth " + i;
r.BookName = " book " + i;
r.Price = i;
db1.AppendRec(r);
}
//db1.Commit(); var t = db1.SearchAsDBRecords((m) =>
{
return true;
}); var bbb = t.FindAll((ss) => {return ss.DataRec.Price > ;}); for (int i = ; i < t.Count; i++)
{
if (i % == )
{
//xml.CodeBook b = t[i];
//t[i].Price += 10;
t[i].DataRec.Price += ;
//db1.UpdataRec(t[i]);
}
if (i==)
{
//db1.DelRecordByDBID(t[i].DBId);
db1.DelRecord(t[i]);
}
Console.WriteLine(db1.Status);
}
db1.Commit();
Console.WriteLine(db1.getCountWithAll());
db1.Compress();
XML数据库的尝试的更多相关文章
- 提高生产性工具(四) - XML数据库的尝试
首先祝大家新年快乐.身体健康,平安就是福气. 对于一般的个人迷你项目,数据量不大的时候,完全没有必要使用数据库,管理数据使用XML就可以了. 自己尝试写了一个XML数据库,插入1w条小记录,大概3M大 ...
- 基于XML数据库的学生信息管理系统的设计与实现
本项目是在学习之余写的,主要用来练习MVC+DAO的分层设计思想,项目基于一个简单的XML学生数据库,使用XML作为数据库的原因是其十分的小巧与方便,使用dom4j即可进行方便的解析.因为这段时间课程 ...
- 转载unity编辑器xml数据库插件
unity编辑器xml数据库插件 注:9月9日更新,其中MyXML.cs中有一句代码写错,查找功能失误,文中已经修改! 注:9月1日更新说明:xml存储结构,因为在用xml之前不知道正规的xml数据结 ...
- 关于VS连接Oracle数据库提示:“尝试加载oracle客户端时引发badimage,如果在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现此问题”的解决方案。
错误一.关于VS连接Oracle数据库提示:“尝试加载oracle客户端时引发badimage,如果在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现此问题”的解决方案. ...
- 一个C#的XML数据库访问类
原文地址:http://hankjin.blog.163.com/blog/static/33731937200942915452244/ 程序中不可避免的要用到配置文件或数据,对于数据量比较小的程序 ...
- 简单的xml数据库设计
我的需求 有时候系统需要存储一些简单的关系型属性,不考虑并发,不考虑性能(一次操作在几ms),数据库没有其他依赖引用,拿过来就可以用 为什么选xml作为数据库? 可读性好 实体的对应关系不严格 , 二 ...
- 使用MongoDB作为后台数据库的尝试
MongoDB作为一个阶层型数据库,在很短的时间里面是不可能被大面积推广使用的, 本文作为一个实验性的课题,探讨一下MongoDB作为网站数据库的可能性. 1.MongoDB作为代替关系型数据库的可能 ...
- 新入门PGSQL数据库(尝试利用PGPOOL实现分布式),摘录笔记
概念: PostgreSQL (pronounced "post-gress-Q-L") is an open source relational database managem ...
- 微信小程序开发 (资料汇总,谁还没被坑过?希望助你绕过一些坑)
最近帮人家做一个微信小程序,刚好想熟悉一下.由于牵扯到多用户使用系统,以及数据共享,所以自然架构选择了,客户端和服务器的方式. 后台服务器是windows server,后台程序是.Net WebA ...
随机推荐
- 每天一个Linux命令(62)rcp命令
rcp代表"remote file copy"(远程文件拷贝). (1)用法: 用法: rcp [参数] [源文件] [目标文件] (2)功能: ...
- Github结合Eclipse出现的问题
半年前因为学习Git花费了很长时间,半年过去了,因为不使用,基本全部忘记了,最近在公司需要使用Eclipse开发相关项目,用到前期的测试数据挖掘的小算法,又重拾Git,不过这次不再是命令行模式,而是结 ...
- 在控制台中实现“单词竞猜”游戏 C# 猜词游戏
场景 设计规则 a) 这是一个单人玩的游戏. b) 可以分三个级别,分别是高级.中级.低级.不同级别对应的单词系列也不一样.要求一旦玩家选定了要玩的级别,应当先提示它关于此级别最高分是多少,是谁创下的 ...
- vuex 入门
vuex.js 状态(数据)管理 在vue中当我们管理数据的时候比较乱,我们要用到下面的这个库,vuex.js Vuex介绍 每一个Vuex应用的核心就是store(仓库),他是用来存储数据的 &qu ...
- 定制AIX操作系统的shell环境
操作系统与外部最主要的接口就叫做shell.shell是操作系统最外面的一层.shell管理你与操作系统之间的交互:等待你输入,向操作系统解释你的输入,并且处理各种各样的操作系统的输出结果. shel ...
- Linux的XServer
Moblin Core是在Gnome Mobile的平台上建立.我以前玩Linux,提交的都和图像没有关系,连Xwindows都不用启动,开机后直接进入文本命令行,所以这方面了解得很少,需要学习一下, ...
- jQuery图片下滑切换焦点图
在线演示 本地下载
- linux下安装eclipse并使用xstart远程使用(centos7)
1 eclipse安装 1)到官网下载eclipse的linux版 http://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-deve ...
- iOS_数据存取(一)
目录: 一.沙盒机制 二.用户偏好设置 三.归档 一.沙盒机制 每个iOS应⽤都有⾃己的应用沙盒(应⽤沙盒就是⽂件系统⽬录),与其他文件系统隔离.应⽤必须待在⾃己的沙盒⾥,其他应用不能访问该应用沙盒的 ...
- SCOI2017酱油记
Day0: 虽然是8点30开始模拟赛,还是设了个7点的闹钟调节生物钟.结果硬生生睡到7点40... 打开题目:T1期望,直接弃掉(到现在都不会期望已经可以滚粗了..) T2一眼可做,恩,先写个暴力.然 ...