当前位置: 首页 > 编程日记 > 正文

深入.NET DataTable

1ADO.NET相关对象一句话介绍
1)DataAdapter
DataAdapter实际是一个SQL语句集合,因为对Database的操作最终需要归结到SQL语句。
2)Dataset
DataSet可以理解成若干DataTable的集合,DataSet在内存里面维护一个表集合包括表间关系。对于.NET Framework 2.0之前的版本,DataSet在ADO.NET中拥有至关重要的作用,但在其后的版本中,由于DataTable类的完备(例如与XML相关的几个方法以及Merge方法),其作用稍有削弱,甚至于有些情况下你去初始化一个DataSet对象本身就是多余的。
3)DataView
与数据库中的视图在概念上是类似的。DataView本身并不真正包含数据行,而只是包含指向源DataTable中数据行的引用,这一点你可以通过object.ReferenceEquals()方法来验证。
4)DataTable
ADO.NET的核心对象。它是位于内存中的一张表,是你执行SQL查询之后的结果集,可以形象地把它理解为一张包含若干行若干列的表格。
 
2、如何更新数据到Database
从本质上来说,你对Database操作总是归结到SQL语句,但是从表面上我们可以作一点区分,
1)直接使用SQL命令
在.NET中,最常见的是拼接SQL字符串,使用Command对象来执行此命令以达到操作Database的目的,例如,

Code
string sql = "update table1 set fvalue=" + this.textBox1.Text + " where fname='x'";
SqlCommand cmd = new SqlCommand(sql,conn);
cmd.ExecuteNonQuery();

这是一种最直接浅显的方式,因为SQL语句就在你眼前,反过来说,这需要你对SQL命令有一定的了解。

2)
使用DataAdapter.Update()
另外一种方式,是使用DataAdapter.Update()方法,这并不是说我们不需要SQL语句了,只是SQL语句拼接的工作已经交给了DataAdapter(实际上是交给了CommandBuilder)来完成(以参数的形式),例如,

Code
string c = "select fname,fvalue from table1";
SqlCommand cmd = new SqlCommand(c,conn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
SqlCommandBuilder scb = new SqlCommandBuilder(da); //(1)
DataTable dt = new DataTable();
da.Fill(dt);
dt.Rows[0].Delete();//(2)
da.Update(dt);

在这里,你看不到SQL语句,因为在你初始化SqlCommandBuilder的过程中,将自动根据表结构(基于你的Select语句)构造insert,update,delete语句。对于上面的代码,你可以获得SQL语句内容,
DELETE FROM [table1] WHERE (([fname] = @p1) AND ((@p2 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p3)))
而执行时候,会传入相应的参数值,
exec sp_executesql N'DELETE FROM [table1] WHERE (([fname] = @p1) AND ((@p2 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p3)))',N'@p1 varchar(1),@p2 int,@p3 int',@p1='a',@p2=0,@p3=100

xec sp_executesql N'DELETE FROM [table1] WHERE (([fname] = @p1) AND ((@p2 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p3)))',N'@p1 varchar(1),@p2 int,@p3 int',@p1='b',@p2=1,@p3=NULL
由于表中只有两个列,列fname为主键列,fvalue列可空,至于为什么会出现三个参数,看看上面的SQL你就会明白了。
以下则分别是update语句、insert语句,
UPDATE [table1] SET [fname] = @p1, [fvalue] = @p2 WHERE (([fname] = @p3) AND ((@p4 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p5)))
INSERT INTO [table1] ([fname], [fvalue]) VALUES (@p1, @p2)
另外,上述C#代码中的dt.Rows[0].Delete()行写在这里只是示例作用,实际的系统中,你可能会有一个叫“Delete”的按钮,这样你可以在按钮的事件中执行Delete()操作,然后叫某个叫“Save”的按钮里写上Update(),这很常见,不多说了。
再另外,由于这些语句的构造过程中依赖于你的Select语句,所以你的Select语句中必须包含主键列,否则无法正常生成其它SQL命令。
以下我们的讨论,将主要针对第二种方式,即使用Update()进行数据更新过程中涉及的各种问题。
 
3、行状态
为了后续的数据操作,DataTable中引入了一个“行状态”的概念(事实上该属性属于DataRow类)。每一个DataRow都有一个状态标志,你可以通过DataTable.Rows[i].RowState查看,对DataRow的不同操作将导致该行处于不同的状态,同时,不同的状态又导致保存数据时的不同行为。参见下图,
 

1)
初始状态差异
从数据库中查询并通过DataAdapter.Fill()方法填充的DataTable,其所有行的状态初始都为Unchanged(我们可以认为在Fill()方法的内部调用了AcceptChanges()方法),然而对于在程序中手工构造并添加的数据行,在未接受AcceptChanges()方法前,都为Added(行状态的不同在DataTable中是一个比较隐蔽的但又需要十分关注的问题,后续会有相应的说明),参见以下代码。

Code
private void button1_Click(object sender, EventArgs e)
{
       try
       {
              dataAdapter1.Fill(dt);
              DataRowState s = dt.Rows[0].RowState;//unchanged
       }
       catch
       {
       }
}
private void button2_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhang", 100);
       DataRowState s = dt.Rows[0].RowState;//added
}


 2)理解Delete()
此方法并未真正移除DataRow(除非此行原状态为Added),而只是将RowState状态变成了Deleted(当然这会导致你无法使用正常的索引方式访问此行的数据)。对于Added状态的行执行Delete()操作,将导致DataTable行数减少,这点需要注意,因为它可能导致你在使用for循环遍历时出现索引越界异常。

Code
private void button7_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhang", 100);
       //
       dt.Rows[0].Delete();
       int c = dt.Rows.Count;//0
}
private void button8_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhang", 100);
       //
       dt.AcceptChanges();
       dt.Rows[0].Delete();
       int c = dt.Rows.Count;//1
}


3)Exception:Deleted row information cannot be accessed through the row.

Code
private void button8_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhang", 100);
       //
       dt.AcceptChanges();
       dt.Rows[0].Delete();
       DataRow dr = dt.Rows[0]; //No error
       object o = dt.Rows[0]["fvalue"];//Exception,row can be accessed,but row data cannot
}


4)理解AcceptChanges()
此方法容易给人误解,以为在调用它之后对DataTable所做的所有更改将会被提交到Database。事实上,此方法跟Database没有直接的关系(注意),它只直接影响各DataRow的RowState(具体地说来是将所有状态为Deleted的行真正移除,所有状态为Added或Modified的行都变成Unchanged)。与Database有直接相关的是DataAdapter.Update()方法,它是真正负责执行相关SQL命令的地方。
但是,从另一方面来说,没有直接的影响,言外之意就是有间接的影响,由于它影响了所有DataRow的RowState,而DataAdapter.Update()方法在执行SQL命令时必须依据RowState以确定使用insert、update、或delete 命令。举个例子,如果你在DataAdapter.Update()调用之前执行AcceptChanges()方法,这将阻止所有对Database的更改,因此对这两个方法调用的顺序应有充分的考虑。
另外,DataSet、DataTable、DataRow都有AcceptChanges()方法,这些方法除了影响的范围大小不同之外,没有本质的区别。
 
5)DataRowStateUpdate()
不同的数据行状态,将导致最终DataAdapter.Update()出现不同的行为,例如对于Added状态的行,将导致insert操作、Modified状态将导致update操作、Deleted状态将导致delete操作。
 
6)使用DataRowState
除了Update()方法内部使用DataRowState外,在我们自己写的代码中,也可以将它与GetChanges()方法配合使用,以获取DataTable的当前变化,参见以下代码,在你获得所有发生更新的行后,实际上你可以自己构造Update SQL命令,而不使用CommandBuilder,当然这需要用到稍后会提到的DataRowVersion。

Code
private void button4_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhang", 100);
       dt.AcceptChanges();
       dt.Rows[0]["fvalue"] = 101
       //get all Modified rows,then you can use UPDATE SQL to save data.
       DataTable dt1 = dt.GetChanges(DataRowState.Modified);
}


7)状态Detached
除了上图中给出的几种行状态外,还有一种特殊的状态Detached,这种状态表示已初始化但未添加到DataTable中的数据行,此状态我们不必太关心。参见,

Code
private void button3_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       DataRow dr = dt.NewRow();
       DataRowState s = dr.RowState;//detached
}


4、行状态、行版本、行数据版本
行版本(DataRowVersion)描述数据行的版本;
行数据版本(DataViewRowState)描述数据行中数据的版本。
这两个概念令人困惑,我认为可以仅仅从用法上对它们进行了解,毕竟我们使用它们的机会并非很大。  

1)
使用DataRowVersion
关于DataRowVersion,以状态为Modified的行为例,它包含两个DataRowVersion(即存储了两行数据):Current,Original,分别存储该行修改后与修改前的数据,也就是说,行版本实际可以帮助RejectChanges()等方法实现一个类似于“回滚”的功能。 

Code
private void button4_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhang", 100);
       dt.AcceptChanges();
       dt.Rows[0]["fvalue"] = 101;
       int i = Convert.ToInt32(dt.Rows[0]["fvalue", DataRowVersion.Original]);//100
       int i2 = Convert.ToInt32(dt.Rows[0]["fvalue", DataRowVersion.Current]);//101
}
同理你可以借助DataRowVersion来访问Deleted的数据,前面我们提到了对于Deleted的数据,使用dt.Rows[0]["fvalue"]访问将引发异常,可以使用
dt.Rows[0]["fvalue", DataRowVersion.Original]。

2) DataRowVersion
Update()
现在我们回想一下,当我们使用CommandBuilder构造完Update,Insert,Delete命令之后,那些SQL命令中的参数怎么办?我们知道在SQL命令执行之前,我们必须为所有输入参数指定参数值,那么Update()方法内部是如何工作的?这就有赖于DataRowVersion了。
 
我们可以简单看一下Update()方法使用过程中涉及的相关.NET源码,
System.Data.Common.DbDataAdapter
protected virtual int Update(DataRow[] dataRows, DataTableMapping tableMapping);
在Update()方法中,调用了ParameterInput(),下面是该方法的摘要,
Code
System.Data.Common.DbDataAdapter 
private void ParameterInput(IDataParameterCollection parameters, StatementType typeIndex, DataRow row, DataTableMapping mappings)
{
    foreach (IDataParameter parameter in parameters)
    {
        if (column != null)
        {
            DataRowVersion parameterSourceVersion = GetParameterSourceVersion(typeIndex, parameter);
            parameter.Value = row[column, parameterSourceVersion];
        }
    }
}

在ParameterInput()方法中,调用了GetParameterSourceVersion()方法,

Code
System.Data.Common.DbDataAdapter 
private static DataRowVersion GetParameterSourceVersion(StatementType statementType, IDataParameter parameter)
{
    switch (statementType)
    {
        case StatementType.Select:
        case StatementType.Batch:
            throw ADP.UnwantedStatementType(statementType);
        case StatementType.Insert:
            return DataRowVersion.Current;
        case StatementType.Update:
            return parameter.SourceVersion;
        case StatementType.Delete:
            return DataRowVersion.Original;
    }
    throw ADP.InvalidStatementType(statementType);
}

以行被更新的情况为例,在为参数的赋值的过程中,系统会将相应要更新的DataRow一并传入,同时对于Update语句,
UPDATE [table1] SET [fname] = @p1, [fvalue] = @p2 WHERE (([fname] = @p3) AND ((@p4 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p5)))
我们要了解的一点是,5个参数中@p1,@p2是一类,@p3, @p5是一类,它们的区别在于,前一类的SourceVersion是Current,而后一类的SourceVersion是Original,这在上述的GetParameterSourceVersion()方法中被用到,所以!!,针对传入的需要更新的DataRow,Update()方法内部将使用当前值(即修改后的值)填充@p1,@p2,而使用原始值(即修改前的值)填充@p3, @p5。Insert,delete同理。

3)理解DataRowVersion.Default
对于Added、Modified、Deleted状态的行,其Default版本实际是Current版本,对于Unchanged则无甚区别。
 
4)使用DataViewRowState
(1)配合DataTable.Select()

Code
private void button9_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhao", 100);
       dt.Rows.Add("qian", 100);
       dt.Rows.Add("sun", 100);
       dt.AcceptChanges();
       //
       dt.Rows[1]["fvalue"] = 101;
       dt.Rows[2].Delete();
       dt.Rows.Add("li", 100);
       //object o = dt.Rows[2]["fvalue", DataRowVersion.Original];
       //
       StringBuilder sb = new StringBuilder();
       DataRow[] drs = dt.Select(null, null, DataViewRowState.Added);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("Added:");
       for (int i = 0; i < drs.Length; i++)
       {
              sb.AppendLine(drs[i]["fname"].ToString() +": "+ drs[i]["fvalue"].ToString());
       }
       drs = dt.Select(null, null, DataViewRowState.CurrentRows);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("CurrentRows:");
       for (int i = 0; i < drs.Length; i++)
       {
              sb.AppendLine(drs[i]["fname"].ToString() + ": " + drs[i]["fvalue"].ToString());
       }
       drs = dt.Select(null, null, DataViewRowState.Deleted);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("Deleted:");
       for (int i = 0; i < drs.Length; i++)
       {
              sb.AppendLine(drs[i]["fname", DataRowVersion.Original].ToString() + ": " + drs[i]["fvalue", DataRowVersion.Original].ToString());
       }
       drs = dt.Select(null, null, DataViewRowState.ModifiedCurrent);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("ModifiedCurrent:");
       for (int i = 0; i < drs.Length; i++)
       {
              sb.AppendLine(drs[i]["fname"].ToString() + ": " + drs[i]["fvalue"].ToString());
       }
       drs = dt.Select(null, null, DataViewRowState.ModifiedOriginal);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("ModifiedOriginal:");
       for (int i = 0; i < drs.Length; i++)
       {
              sb.AppendLine(drs[i]["fname"].ToString() + ": " + drs[i]["fvalue"].ToString());
       }
       drs = dt.Select(null, null, DataViewRowState.OriginalRows);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("OriginalRows:");
       for (int i = 0; i < drs.Length; i++)
       {
              if (drs[i].RowState == DataRowState.Deleted)
              {
                     sb.AppendLine(drs[i]["fname", DataRowVersion.Original].ToString() + ": " + drs[i]["fvalue", DataRowVersion.Original].ToString());
              }
              else
              {
                     sb.AppendLine(drs[i]["fname"].ToString() + ": " + drs[i]["fvalue"].ToString());
              }
       }
       drs = dt.Select(null, null, DataViewRowState.Unchanged);
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("Unchanged:");
       for (int i = 0; i < drs.Length; i++)
       {
              sb.AppendLine(drs[i]["fname"].ToString() + ": " + drs[i]["fvalue"].ToString());
       }
       MessageBox.Show(sb.ToString());
}


结果输出:
-----------------------------------------------
Added:
li: 100
-----------------------------------------------
CurrentRows:
zhao: 100
qian: 101
li: 100
-----------------------------------------------
Deleted:
sun: 100
-----------------------------------------------
ModifiedCurrent:
qian: 101
-----------------------------------------------
ModifiedOriginal:
qian: 101
-----------------------------------------------
OriginalRows:
zhao: 100
qian: 101
sun: 100
-----------------------------------------------
Unchanged:
zhao: 100
 
(2)配合DataView.RowFilter

Code
private void button10_Click(object sender, EventArgs e)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname");
       dt.Columns.Add("fvalue");
       dt.Rows.Add("zhao", 100);
       dt.Rows.Add("qian", 100);
       dt.Rows.Add("sun", 100);
       dt.AcceptChanges();
       //
       dt.Rows[1]["fvalue"] = 101;
       dt.Rows[2].Delete();
       dt.Rows.Add("li", 100);
       //
       DataView dv = new DataView(dt);
       dv.RowStateFilter = DataViewRowState.Added | DataViewRowState.ModifiedCurrent;
       StringBuilder sb = new StringBuilder();
       sb.AppendLine("-----------------------------------------------");
       sb.AppendLine("Added & ModifiedCurrent:");
       for (int i = 0; i < dv.Count; i++)
       {
              sb.AppendLine(dv[i]["fname"].ToString() + ": " + dv[i]["fvalue"].ToString());
       }
       sb.AppendLine("-----------------------------------------------");
       MessageBox.Show(sb.ToString());
}

//----------------------------------------------- 
Added & ModifiedCurrent:
qian: 101
li: 100
-----------------------------------------------
 
5)DataViewRowState中的“复合版本”
DataViewRowState包含多个枚举成员,我可以给出每个枚举成员对应的int值,
Added                     4
CurrentRow              22
Deleted                   8
ModifiedCurrent        16
ModifiedOriginal        32
None                       0
OriginalRow              42
Unchanged              2
你可以发现,其中的两个状态CurrentRow、OriginalRow实际是经由其它几种状态二进制或运算的结果,
CurrentRow=Added|ModifiedCurrent|Unchanged,
OriginalRow=Deleted|ModifiedOriginal|Unchanged。
 
5、了解其它几个方法
1)Delete()Remove()Clear()
DataRow.Delete();
DataRowCollection.Remove();
DataTable.Clear()、DataSet.Clear()
正如前面所述,对于DataRow的Delete()方法,其内部的处理并未真正删除此行,而只是将行标识为Deleted,并“移除”了它的Current版本。这样,当使用DataAdapter的Update()进行更新时,其内部机制可以根据仍然存在的Original版本数据,为DeleteCommand填充参数,完成更新数据库的操作。
而Clear()方法则完全删除了堆上的数据行对象,并且将对数据引用置空(这点可以参见Clear()方法的反编译代码),这种情况下无法生成可执行的DeleteCommand,这就是说,当你用Clear()方法“清空”DataTable后,使用Update()方法并不能像你预想的一样将对应的数据库表数据删除。
另外,需要注意一点是Delete()并不导致数据行减少(除非原行是Added状态),当然,如果是对Added状态的行执行Delete(),则导致行数减少。当你使用for循环时,这可能会造成问题。
另外,我们还有一个方法:DataRowCollection.Remove(),其作用类似于Clear(),是彻底地移除行,假设你是使用DataAdapter.Update()方法更新Database,那么你将没有机会将你的删除操作同步到Database中。

2)Copy()Clone()
.NET中有两类拷贝,浅拷贝(Shadow copy)、深拷贝(Deep copy),对于大多数我们所见的类(比如常见的集合类等等),没有深拷贝方法,大多数会有一个浅拷贝方法Clone()。我唯一所见的一个深拷贝方法是DataTable.Copy(),同时,DataTable.Clone()方法也比较特殊,因为它并非是浅拷贝方法,而是拷贝DataTable的结构(不包含数据)。
顺便提一下深、浅拷贝的区别,浅拷贝创建原对象类型的一个新实例,复制原对象的所有值类型成员,对于引用类型成员,则只复制该值的指针。深拷贝则复制原对象的所有成员,对于引用类型成员,亦复制其所指的堆上的对象。

Code
static void Main(string[] args)
{
       DataTable dt = new DataTable();
       dt.Columns.Add("fname", typeof(System.String));
       dt.Columns.Add("fvalue", typeof(System.Int32));
       for (int i = 1; i <= 10; i++)

       {
              dt.Rows.Add("p" + i, i);
       }
       dt.AcceptChanges();
       //
       DataTable dtc = dt.Copy();
       //
       bool b = object.ReferenceEquals(dt.Rows[0], dtc.Rows[0]);//false
       DataRowState s = dtc.Rows[0].RowState;//Unchanged
       //Clone() and ImportRow()
       DataTable dtc2 = dt.Clone();
       for (int i = 0; i < 5; i++)
       {
              dtc2.ImportRow(dt.Rows[i]);
       }
       bool b2 = object.ReferenceEquals(dt.Rows[0], dtc2.Rows[0]);//false
       DataRowState s2 = dtc2.Rows[0].RowState;//Unchanged
       //ItemArray
       DataTable dtc3 = dt.Clone();
       for (int i = 0; i < 5; i++)
       {
              dtc3.Rows.Add(dt.Rows[i].ItemArray);
              //dtc3.Rows.Add(dt.Rows[i]);//run time exception
       }
       bool b5 = object.ReferenceEquals(dt.Rows[0], dtc3.Rows[0]);//false
       DataRowState s5 = dtc3.Rows[0].RowState;//Added
       //
       ArrayList al = new ArrayList();
       al.Add("xy");
       ArrayList alc = al.Clone() as ArrayList;
       if (alc != null)
       {
              bool b3 = object.ReferenceEquals(al, alc);//false
              bool b4 = object.ReferenceEquals(al[0], alc[0]);//true
       }
}


3)Select()Compute()
这两个方法在很多情况下都有助于你简化代码,避免每一次使用循环遍历DataTable,参见以下,
对于这两个方法中可用的表达式,参见,
http://msdn.microsoft.com/en-us/library/system.data.datacolumn.expression.aspx

Code
class Program
{
       static void Main(string[] args)
       {
              DataTable dt = new DataTable();
              dt.Columns.Add("fname",typeof(System.String));
              dt.Columns.Add("fvalue",typeof(System.Int32));
              for (int i = 1; i <= 10; i++)
              {
                     dt.Rows.Add("p" + i, i);
              }
              dt.AcceptChanges();
              //
              DataRow[] drs = dt.Select("fvalue>6");
              PrindRows(drs);
              //
              drs = dt.Select("fvalue>6 and fvalue<9");//AND OR NOT
              PrindRows(drs);
              //
              drs = dt.Select("fname like 'p1%'");
              PrindRows(drs);
              //
              drs = dt.Select("fname in ('p1','p3')");//< > <= >= <> = IN LIKE
              PrindRows(drs);
              //
              drs = dt.Select("fvalue=max(fvalue)");//SUM AVG MIN MAX COUNT STDEV VAR
              PrindRows(drs);
              //
              drs = dt.Select("fvalue%2=0");//+ - * / %
              PrindRows(drs);
              //
              drs = dt.Select("len(fname)=3");//LEN(expression) ISNULL(expression, replacementvalue) IIF(expr, truepart, falsepart) TRIM(expression) SUBSTRING(expression, start, length)
              PrindRows(drs);

              object o = dt.Compute("count(fname)", "fvalue>6");//4 
              Console.WriteLine(o.ToString());
              Console.Read();
       }

       static void PrindRows(DataRow[] pDrs)
       {
              Console.WriteLine("----------------------------------");
              foreach (DataRow dr in pDrs)
              {
                     Console.WriteLine(dr["fname"].ToString().PadRight(8, ' ') + dr["fvalue"].ToString());
              }
              Console.WriteLine("----------------------------------");
       }
}


----------------------------------
p7      7
p8      8
p9      9
p10     10
----------------------------------
 
----------------------------------
p7      7
p8      8
----------------------------------
 
----------------------------------
p1      1
p10     10
----------------------------------
 
----------------------------------
p1      1
p3      3
----------------------------------
 
----------------------------------
p10     10
----------------------------------
 
----------------------------------
p2      2
p4      4
p6      6
p8      8
p10     10
----------------------------------
 
----------------------------------
p10     10
----------------------------------

转载于:https://www.cnblogs.com/30ErLi/archive/2010/09/19/1830855.html

相关文章:

天平称球问题-转

称球问题一般会有以下3种变形&#xff1a; 1、N个球&#xff0c;其中有一个坏的&#xff0c;知道是轻还是重&#xff0c;用天平称出坏球来。 2、N个球&#xff0c;其中有一个坏的&#xff0c;不知是轻还是重&#xff0c;用天平称出坏球来。 3、N个球&#xff0c;其中有一个坏…

Java虚拟机垃圾收集算法

1、标记-清除算法 标记-清除算法分为 “标记” 和 “清除” 两个步骤&#xff1a;首先标记出所有需要回收的对象&#xff0c;然后在标记完成后统一回收所有被标记的对象&#xff0c;是垃圾收集算法中的最基础的收集算法。 缺点&#xff1a;一、标记和清除两个步骤效率都不高&am…

WMIC的用法

获得系统版本信息wmic datafile where Namec:\\windows\\explorer.exe get Manufacturer,Version,Filename获得信筒进程 wmic process list full 注意&#xff1a;这里的full也可以换成brief&#xff08;简洁&#xff09;获得硬件信息&#xff08;这里以cpu为例&#xf…

洛谷P2763 试题库问题

题目&#xff1a;https://www.luogu.org/problemnew/show/P2763 题目描述 问题描述&#xff1a; 假设一个试题库中有n道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷…

梦断代码阅读笔记03

经过几天的阅读&#xff0c;终于将这本书看完了&#xff0c;读完了整个故事&#xff0c;我进行了简单的总结&#xff0c;感觉不仅仅是在写代码与计算机或软件交流&#xff0c;更多的是做事行为。 首先是做事得有目标。无论做什么事情都要有目标和动力&#xff0c;这样做起事来无…

能让导师喜欢的学生(转自论坛)

第一&#xff0c;明辨是非&#xff0c;从善如流。研究生是未来的高级知识分子&#xff0c;作为国家栋梁&#xff0c;将来很可能为国家和社会发展献计献策&#xff0c;如果不能明辨是非&#xff0c;如何做出正确建议和决策&#xff1f;如果不能从善如流&#xff0c;如何保证自己…

使用SpringBoot发送邮件 在本地测试是好的 放到服务器连接超时问题

原因 原来是ECS基于安全考虑&#xff0c;禁用了端口25。改成465就可以发邮件了。 原始配置 本地可发送 #spring.mail.hostsmtp.qq.com #spring.mail.usernameqq #spring.mail.passwordpassword #spring.mail.properties.mail.smtp.starttls.enabletrue #spring.mail.prope…

电脑中所有exe文件无法运行解决方案

电脑中所有exe文件无法运行。通过系统恢复无法解决毛病&#xff0c;后来才想起肯定是exe文件关联被改动&#xff0c;只有通过修改注册表才能改回来。要修改注册表就要运行regedit.exe文件&#xff0c;这也是一个exe文件&#xff0c;也无法运行。后来查资料找到了解决方法。现在…

[转]后期-快速消除痘痘,完美修复MM肌肤

是面对美景&#xff0c;即使皮肤不好也得露个脸啊!那MM的面子问题怎么办呢?简单&#xff0c;咱就通过Photoshop后期处理来<?xml:namespace prefix o />给MM打造完美水嫩的肌肤!远景照片 简单还原MM容颜日常拍摄的照片&#xff0c;很多时候是远景的拍摄&#xff0c;人物…

[Android] Android MVP 架构下 最简单的 代码实现

Android MVP 架构下 最简单的 代码实现 首先看图&#xff1a; 上图是MVP&#xff0c;下图是MVC MVP和MVC的区别&#xff0c;在于以前的View层不仅要和model层交互&#xff0c;还要和controller层交互。而在mvp中&#xff0c;view层只和presenter层交互&#xff0c;而model层也…

java项目上有个红色感叹号(在project Explorer视图下)

启动项目时一直报错&#xff0c;检查也没问题&#xff0c;最后看到项目上有个红色感叹号&#xff0c;发现是jar包路径不对&#xff0c;把错误路径的jar包移除&#xff0c;然后再重新添加即可。转载于:https://www.cnblogs.com/sanhao/p/8059257.html

配置jdk环境 windows

java 配置环境 JAVA_HOME D:\Program Files\Java\jdk1.8.0_181 path %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; CLASSPATH .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

再谈Linux修改应用程序获得root权限

我之前写过一篇关于怎样就可以使你的应用程序获得root权限运行&#xff0c;那个对于一些测试程序或小工程的程序时比较实用&#xff0c;但如果你的工程文件多达几十个甚至上百&#xff0c;那么这种方法就不太适用了。在Ubuntu下面&#xff0c;我选择适用了codelite&#xff0c;…

JBPM4常见错误汇总

1.在tomcat6.0下布署错误 基于JBPM4的web项目jsp页面发布出错现象&#xff1a; javax.servlet.ServletException: java.lang.LinkageError: loader constraint violation: when resolving interfacemethod "javax.servlet.jsp.JspApplicationContext.getExpressionFacto…

Day 3 上午

内容提要&#xff1a; 动态规划 数位DP 树形DP 区间DP 动态规划 斐波那契数列f(0)0,f(1)1,......,f(n)f(n-1)f(n-2)0,1,1,2,3,5,8,13......他和动态规划有什么关系&#xff1f;首先&#xff0c;他有一个边界条件&#xff0c;就是f(0)0,f(1)1&#xff0c;相当于它不是从正无穷到…

SpringBoot------添加保存时自动编译插件

1.右键Java项目2.选择“Spring Tools”3.选择“Add Boot DevTools”4.每次使用Ctrl S键时就会自动编译了 实际上是在Pom.xml文件中添加了如下Java包 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring…

设置PLSQ 连接oracle数据库

1 、instantclient_12_1 的设置 配置文件内容 tnsnames.ora # tnsnames.ora Network Configuration File: C:\oracle\product\10.2.0\db_1\network\admin\tnsnames.ora # Generated by Oracle configuration tools.ORCL (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 221.…

《星辰变OL》估计很多人看过这书

瓜瓜小说论坛《星辰变OL》估计很多人看过这书&#xff0c;也估计很多人都不知道这游戏就快开始运行了。 本人2009-2010最期待的游戏了。 咩羊大大你千万注意下&#xff0c;这游戏一有封测&#xff0c;内测一类。一定要给我留个号。 下面看视频。 一定要给我留号啊~咩羊&#xf…

Ubuntu16.04 搭建nexus 私服 学习步骤以及安装maven和git

1、下载安装maven wget https://www-us.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz 1、创建maven仓库位置 2、修改setting.xml文件 添加东西如下 export M2_HOME/software/maven/apache-maven-3.6.0 export CLASSPATH$CLASSPATH:$M2_H…

Asp.net(C#)给图片加上水印效果(转自园上的Seven Eleven)

Asp.net(C#)给图片加上水印效果 private void Btn_Upload_Click(object sender, System.EventArgs e) { if(UploadFile.PostedFile.FileName.Trim()!"") { //上传文件 string extension Path.GetExten…

pip install失败报错解决方案

cmd pip install 某些包时报错 pip install Consider using the --user option or check the permissions. 只需要pip install --user package就可以解决转载于:https://www.cnblogs.com/webRobot/p/10799270.html

12.19冲刺总结

第二天冲刺&#xff1a; 昨天完成的任务&#xff1a; 主界面布局 遇到的问题&#xff1a; 登录界面弹窗 今日任务&#xff1a; 主界面编写转载于:https://www.cnblogs.com/mhj666/p/8349629.html

Win2003 防木马、权限设置、IIS服务器安全配置整理

原贴http://hi.baidu.com/zzxap/blog/item/18180000ff921516738b6564.html2009-02-10 10:45一、系统的安装   &#xff11;、按照Windows2003安装光盘的提示安装&#xff0c;默认情况下2003没有把IIS6.0安装在系统里面。&#xff12;、IIS6.0的安装开始菜单—>控制面板—&…

Js面试题(一)--js实现数组去重怎么实现?

方法1、创建一个新的临时数组来保存数组中已有的元素方法2、使用哈希表存储已有元素方法3、使用indexof判断数组元素第一次出现的位置是否为当前位置方法4、先排序再去重第一种方法和第三种方法都使用了indexof()&#xff0c;这个函数的执行机制也会遍历数组第二种使用了哈希表…

使用Maven 打包项目 生成XXX.tar.gz 文件

1、在项目中创建assembly文件夹 创建如图的一个assembly.xml文件 内容如下 <assemblyxmlns"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"h…

整理了一下SQL Server里面可能经常会用到的日期格式转换方法

select getdate() 2004-09-12 11:06:08.177 举例如下: select CONVERT(varchar, getdate(), 120 ) 2004-09-12 11:06:08 select replace(replace(replace(CONVERT(varchar, getdate(), 120 ),-,), ,),:,) 20040912110608 select CONVERT(varchar(12) , getdate(), 111 ) 2004/…

java使用Cookie判断用户登录情况

1.判断是否登录 public boolean isLogin() {Set<Cookie> cookies this.browser.getCookies();String JSESSIONIDID "JSESSIONID";String sessionIdID "sessionId";String loginID "login";String JSESSIONIDIDValue "";Str…

桌面菜单背景修改

只能修改资源管理器里的右键菜单&#xff0c;即桌面、文件夹和各种文件上的右键菜单&#xff0c;其它如标题栏和任务栏的是没效果的&#xff0c;可惜了。修改其实只是注册了一个dll文件&#xff0c;然后修改这个dll里面的背景图片就可以了。1.首先下载ContextBG.dll。2.然后下载…

全浏览器兼容的DIV拖动效果

测试过下列浏览器 IE6、IE7、IE8、Chrome 5、FF 3.6、Opera 10、Safari 5 拖动效果的脚本网上都有&#xff0c;但网上找到的脚本有个问题 这是在网上随便找的代码(原出处不知道&#xff0c;很多类似代码的文章都没写出处&#xff0c;实在不知道到底出至哪里...) 代码 1 <!DO…

SpringSecurity使用 配置文件 和wen.xml 文件配置

目录 1、web.xml 文件配置 2、spring-security 普通 为使用自己创建的认证类 1、web.xml 文件配置 !-- 配置SpringSecurity的拦截器 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/spring-se…