[转载]C# 泛型

mikel阅读(1061)

[转载]C# 泛型 – 夜里的烟 – 博客园.

泛型的好处

泛型是C#中的一个非常重要的语法,泛型的好处可以归结为一下几点:性能;类型安全;二进制重用;防止代码臃肿;命名规范

性能:性能是泛型最大的好处之一,当在非泛型的类中使用值类型的时候要涉及到装箱和拆箱。值类型是放在栈上的,引用类型是放在堆上的。C#类是 引用类型,结构是值类型。.net提供了值类型到引用类型的转换,这个转换的过程叫做装箱,当将一个值类型传递给一个需要引用类型作为参数的方法的时候装 箱会自动进行,相反,拆箱就是将引用类型转换为值类型,当使用拆箱的时候需要强制转换。如下面的代码:

var list = new ArrayList();
list.Add(44); // boxing — convert a value type to a reference type
int i1 = (int)list[0]; // unboxing — convert a reference type to
// a value type
foreach (int i2 in list)
{
  Console.WriteLine(i2); // unboxing
}

装箱和拆箱虽然容易使用,但是非常耗费性能。泛型允许在使用的时候才定义类型,这样就不用拆箱和装箱了。如下面的代码

var list = new List < int > ();
list.Add(44); // no boxing — value types are stored in the List < int >
int i1 = list[0]; // no unboxing, no cast needed
foreach (int i2 in list)
{
  Console.WriteLine(i2);
}

  类型安全:使用泛型的另外一个好处是可以保证类型安全。先看下面的代码:

var list = new ArrayList();
list.Add(44);
list.Add("mystring");
list.Add(new MyClass());

这段代码的编译是没有问题的,因为int和string都可以装箱为object对象,但是在使用这个数组的时候,却会出现运行时错误,编译的时候却不会出错:

foreach (int i in list)
{
  Console.WriteLine(i);
}

但是使用泛型就不会出现这样的情况,因为在向里面插入值的时候IDE就会提示错误:

var list = new List();
list.Add(44);
list.Add(“mystring”); // compile time error
list.Add(new MyClass()); // compile time error

  二进制代码重用:泛型可以更好的重用代码,因为一个泛型类可以被定义一次,然后被多种不同类型的对象使用,如下面的代码:

var list = new List();
list.Add(44);
var stringList = new List();
stringList.Add(“mystring”);
var myClassList = new List();
myClassList.Add(new MyClass());

同时,泛型类型可以在定义后被.NET平台的其他语言使用

  防止代码臃肿:当使用类型声明了不同类型的实例的时候,是否会生成大量重复的代码呢?因为泛型是定义在程序集内部,用特定类型实例泛型的时候并不会重复这些类的IL代码。但是在将IL代码通过JIT转化为本地代码的时候,还是会为不同的类型分别创建代码。引用类型在本地代码中还是会分享相同的代码,因为引用类型值需要四个字节的内存空间地址来在泛型初始化的时候引用实例化的类型。但是因为值类型不同的类型对内存的要求不一样,因此值类型的每一次泛型实例都或重新创建一个完整的类型。

  命名规范:使用泛型的时候,好的命名规范可以帮助区别泛型和非泛型,通常遵循下面的原则,泛型类型的名字用T作为前缀;如果对泛型类型没有任何特殊要求,那么直接使用T来代表泛型类型,如public class List{} public class LinkedList{};如果对泛型类型有一些要求,比如需要继承自某个类,或者涉及到两个或者多个类型,那么需要使用描述性的命名例如:

public delegate void EventHandler(object sender, TEventArgs e);
public delegate TOutput Converter(TInput from);
public class SortedList { }

创建泛型类

  首先创建一个简单的LinkedListNode和LinkedList普通类,代码如下:

public class LinkedListNode
{
  public LinkedListNode(object value)
  {
    this.Value = value;
  }
  public object Value { get; private set; }
  public LinkedListNode Next { get; internal set; }
  public LinkedListNode Prev { get; internal set; }
}

public class LinkedList: IEnumerable
{
  public LinkedListNode First { get; private set; }
  public LinkedListNode Last { get; private set; }
  public LinkedListNode AddLast(object node)
  {
    var newNode = new LinkedListNode(node);
    if (First == null)
    {
      First = newNode;
      Last = First;
    }
    else
    {
      Last.Next = newNode;
      Last = newNode;
    }
    return newNode;
  }
  public IEnumerator GetEnumerator()
  {
    LinkedListNode current = First;
    while (current != null)
    {
      yield return current.Value;
      current = current.Next;
    }
  }
}

现在使用这个通用类,书写如下代码,不仅仅会发生装箱和拆箱动作,而且还会在运行的时候报错:

var list1 = new LinkedList();
list1.AddLast(2);
list1.AddLast(4);
list1.AddLast("6");
foreach (int i in list1)
{
  Console.WriteLine(i);
}

下面我们来创建LinkedListNode和LinkedList的泛型类,使用T来代替object类型,代码如下:

public class LinkedListNode <T>
{
  public LinkedListNode(T value)
  {
    this.Value = value;
  }
  public T Value { get; private set; }
  public LinkedListNode <T> Next { get; internal set; }
  public LinkedListNode <T> Prev { get; internal set; }
}

public class LinkedList <T> : IEnumerable <T>
{
  public LinkedListNode <T> First { get; private set; }
  public LinkedListNode <T> Last { get; private set; }
  public LinkedListNode <T> AddLast(T node)
  {
    var newNode = new LinkedListNode <T> (node);
    if (First == null)
    {
      First = newNode;
      Last = First;
    }
    else
    {
      Last.Next = newNode;
      Last = newNode;
    }
    return newNode;
  }
  public IEnumerator <T> GetEnumerator()
  {
    LinkedListNode <T> current = First;
    while (current != null)
    {
      yield return current.Value;
      current = current.Next;
    }
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return GetEnumerator();
  }
}

现在你就可以声明不同类型的LinkedList对象了,如下:

var list2 = new LinkedList < int > ();
list2.AddLast(1);
list2.AddLast(3);
list2.AddLast(5);
foreach (int i in list2)
{
  Console.WriteLine(i);
}

var list3 = new LinkedList < string > ();
list3.AddLast("2");
list3.AddLast("four");
list3.AddLast("foo");
foreach (string s in list3)
{
  Console.WriteLine(s);
}

泛型特性

  泛型包含了一些特性用来对泛型进行一些约束,这些约束包括默认值;约束;继承和静态变量。首先创建一个文档管理的泛型,用作下面的示例的基础:

using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Generics
{
  public class DocumentManager <T>
  {
    private readonly Queue <T> documentQueue = new Queue <T> ();
    public void AddDocument(T doc)
    {
      lock (this)
      {
        documentQueue.Enqueue(doc);
      }
    }
    public bool IsDocumentAvailable
    {
      get { return documentQueue.Count > 0; }
    }
  }
}

  默认值:在DocumentManager类中的GetDocument方法中的类型T有可能为null,但是实际上不能对泛型赋予null值,因为null是针对引用类型的,而泛型有可能分配一个值类型。因此,为了解决这个问题,可以使用default关键字,通过default关键字,可以针对引用类型分配一个null,对值类型分配一个0,如:

public T GetDocument()
{
  T doc = default(T);
  lock (this)
  {
    doc = documentQueue.Dequeue();
  }
  return doc;
}

现在我创建一个document类型,集成了IDocument接口,这个接口集成了title和content属性,那么我在需要使用这两个属性的时候依然需要进行类型转换,看如下代码:

public interface IDocument
{
  string Title { get; set; }
  string Content { get; set; }
}
public class Document: IDocument
{
  public Document()
  {
  }
  public Document(string title, string content)
  {
    this.Title = title;
    this.Content = content;
  }
  public string Title { get; set; }
  public string Content { get; set; }
}
public void DisplayAllDocuments()
{
  foreach (T doc in documentQueue)
  {
    Console.WriteLine(((IDocument)doc).Title);
  }
}

类型转换是一方面,而且如果存入的类型并没有继承IDoucment继承,那么将会在运行时出现错误。一个好的解决方法是在定义泛型的时候规定类型必须继承自接口IDocument,这样就不用进行类型转直接可以使用,如下面的代码:

public class DocumentManager<TDocument>
where TDocument: IDocument
{}

public void DisplayAllDocuments()
{
  foreach (TDocument doc in documentQueue)
  {
    Console.WriteLine(doc.Title);
  }
}

类似的约束还有以下这些:where T:struct规定类型T必须是一个值类型;where T:class规定了类型T必须是引用类型;where T:IFoo规定了类型T必须继承自IFoo接口;whereT:Foo规定了类型T必须继承自Foo基类;where T:new规定了类型T必须有默认的构造器;where T1:T2规定了泛型T1必须继承自泛型T2。还可以对泛型进行多重限制,例如:public class MyClass where T: IFoo, new(){}

  继承:泛型类可以继承自泛型接口,泛型类也可以继承自泛型基类。要求是接口的泛型类型必须一样或者基类的泛型类型被指定,如:

public class LinkedList<T>: IEnumerable<T>
public class Base<T>
{
}
public class Derived<T>: Base<T>
{
}
public class Base<T>
{
}
public class Derived<T>: Base<string>
{
}
public abstract class Calc<T>
{
  public abstract T Add(T x, T y);
  public abstract T Sub(T x, T y);
}
public class IntCalc: Calc<int>
{
  public override int Add(int x, int y)
  {
    return x + y;
  }
  public override int Sub(int x, int y)
  {
    return x — y;
  }
}

  静态成员:泛型类的静态成员需要特别注意,泛型类的静态变量只被同一个类型的实例所共享:

public class StaticDemo<T>
{
  public static int x;
}
StaticDemo<string>.x = 4;
StaticDemo<int>.x = 5;
Console.WriteLine(StaticDemo<string>.x); // writes 4

泛型接口

  在老的.NET平台中还没有泛型接口的时候,很多类都需要进行类型转换你例如:

public class Person: IComparable
{
  public int CompareTo(object obj)
  {
    Person other = obj as Person;
    return this.lastname.CompareTo(other.LastName);
  }
}

但是如果使用泛型接口,可以在使用的时候定义类型,这样就不用再进行类型转欢了,如下:

public interface IComparable<in T>
{
  int CompareTo(T other);
}
public class Person: IComparable<Person>
{
  public int CompareTo(Person other)
  {
    return this.LastName.CompareTo(other.LastName);
  }
}

  协变和逆变:我们都知道里氏替换原则,就是子类可以替换父类,这称之为协变,如果是父类替换子类,我们称之为逆变。在.NET 4.0之前的版本中,泛型接口是不支持协变和逆变的,但是.NET 4中扩展泛型接口的协变和逆变特性,为了下面的示例代码,首先定义一个基类和派生类:

public class Shape
{
  public double Width { get; set; }
  public double Height { get; set; }
  public override string ToString()
  {
    return String.Format("Width: {0}, Height: {1}", Width, Height);
  }
}
public class Rectangle: Shape
{
}

  泛型接口的协变:如果定义泛型的时候在T的前面加上了out关键字,那么这个泛型接口支持协变,也就是说类型T只允许作为返回类型,如下面的示例:

public interface IIndex < out T >
{
  T this[int index] { get; }
  int Count { get; }
}

public class RectangleCollection: IIndex<Rectangle>
{
  private Rectangle[] data = new Rectangle[3]
  {
    new Rectangle { Height=2, Width=5},
    new Rectangle { Height=3, Width=7},
    new Rectangle { Height=4.5, Width=2.9}
  };
  public static RectangleCollection GetRectangles()
  {
    return new RectangleCollection();
  }
  public Rectangle this[int index]
  {
    get
    {
      if (index < 0 || index > data.Length)
        throw new ArgumentOutOfRangeException("index");
      return data[index];
    }
  }
  public int Count
  {
    get
    {
      return data.Length;
    }
  }
}

static void Main()
{
  IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();
  IIndex<Shape> shapes = rectangles;
  for (int i = 0; i < shapes.Count; i++)
  {
    Console.WriteLine(shapes[i]);
  }
}

上面的例子中,因为泛型T进允许作为返回类型,因此可以把子类接口赋值给父类接口,也就是

IIndex rectangles = RectangleCollection.GetRectangles();
IIndex shapes = rectangles;

  泛型接口的逆变:如果泛型被in关键字定义,那么这个泛型接口就是逆变的,也就是接口进允许使用泛型T作为方法的输入,如下面的代码:

public interface IDisplay<in T>
{
  void Show(T item);
}
public class ShapeDisplay: IDisplay<Shape>
{
  public void Show(Shape s)
  {
    Console.WriteLine("{0} Width: {1}, Height: {2}", s.GetType().Name,s.Width, s.Height);
  }
}

这里,因为泛型T仅仅允许作为方法的输入参数,因此就可以把父类型的接口赋值给子类型的接口,如下:

static void Main()
{
  //...
  IDisplay<Shape> shapeDisplay = new ShapeDisplay();
  IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
  rectangleDisplay.Show(rectangles[0]);
}

泛型结构

  同类一样,结构也可以作为泛型类型,除了不能继承之外其余的同类都相似。这里我们使用泛型结构Nullable为例,这个Nullable是.NET平台提供的一个泛型结构。

  在处理数据库或者XML向实体类转换的时候,数据库中的字段可以为空,但是实体中的int等值类型不能为空,这样会造成转换的不方便,一种解决方法是将数据库中的数字映射为引用类型,但是这样仍然会引起资源的不必要消耗。有了Nullable就可以很方便的解决这个问题,下面是一个Nullable精简版本

public struct Nullable<T> where T: struct
{
  public Nullable(T value)
  {
    this.hasValue = true;
    this.value = value;
  }
  private bool hasValue;
  public bool HasValue
  {
    get
    {
      return hasValue;
    }
  }
  private T value;
  public T Value
  {
    get
    {
      if (!hasValue)
      {
        throw new InvalidOperationException("no value");
      }
      return value;
    }
  }
  public static explicit operator T(Nullable<T> value)
  {
    return value.Value;
  }
  public static implicit operator Nullable<T>(T value)
  {
    return new Nullable<T>(value);
  }
  public override string ToString()
  {
    if (!HasValue)
      return String.Empty;
    return this.value.ToString();
  }
}

Nullable的使用方式如下:

Nullable<int> x;
x = 4;
x += 3;
if (x.HasValue)
{
  int y = x.Value;
}
x = null;

Nullable < int > x1;
int? x2;
int? x = GetNullableType();
if (x == null)
{
  Console.WriteLine("x is null");
}
else if (x < 0)
{
  Console.WriteLine("x is smaller than 0");
}

int? x1 = GetNullableType();
int? x2 = GetNullableType();
int? x3 = x1 + x2;

int y1 = 4;
int? x1 = y1;

int? x1 = GetNullableType();
int y1 = (int)x1;

int? x1 = GetNullableType();
int y1 = x1 ?? 0;

泛型方法

  除了定义泛型类之外,还可以定义泛型方法,在方法声明的时候定义泛型,在非泛型类中可以定义泛型方法。下面是一个泛型方法的示例,首先是会用到的Account类和一个一般的方法:

public class Account
{
  public string Name { get; private set; }
  public decimal Balance { get; private set; }
  public Account(string name, Decimal balance)
  {
    this.Name = name;
    this.Balance = balance;
  }
}

var accounts = new List<Account>()
{
  new Account("Christian", 1500),
  new Account("Stephanie", 2200),
  new Account("Angela", 1800)
};

public static class Algorithm
{
  public static decimal AccumulateSimple(IEnumerable<Account> source)
  {
    decimal sum = 0;
    foreach (Account a in source)
    {
      sum += a.Balance;
    }
    return sum;
  }
}

decimal amount = Algorithm.AccumulateSimple(accounts);

 受约束的泛型方法:上面的方法只能针对Account对象,如果使用泛型方法可以避免这个缺点,如下:

public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source) where TAccount: IAccount
{
  decimal sum = 0;
  foreach (TAccount a in source)
  {
    sum += a.Balance;
  }
  return sum;
}

public interface IAccount
{
  decimal Balance { get; }
  string Name { get; }
}

public class Account: IAccount
{
  //...
}

decimal amount = Algorithm.Accumulate<Account>(accounts);

  带委托的泛型方法:上面的方法要求泛型必须继承自IAccount,这样的方法限制太大,下面的例子使用的委托类改变Accumulate方法。Accumulate方法使用了两个泛型参数,T1和T2,T1用来作为IEnumerable类型的参数,T2使用了泛型委托Func,第二个和第三个是相同的T2类型,需要传递这个方法作为参数,方法有两个参数,T1和T2,返回类型为T2,如下面的代码:

public static T2 Accumulate<T1, T2>(IEnumerable<T1> source,Func<T1, T2, T2> action)
{
  T2 sum = default(T2);
  foreach (T1 item in source)
  {
    sum = action(item, sum);
  }
  return sum;
}

decimal amount = Algorithm.Accumulate<Account, decimal>(accounts, (item, sum) => sum += item.Balance);

  使用说明的泛型方法:泛型方法可以通过定义特定类型的说明来进行重载,对于拥有泛型参数的方法也同样适用。下面的Foo方法有两个版本,第一个版本是用泛型作为参数,第二个版本是说明用int作为参数。在编译的时候,如果传递的是int类型,直接用第二个版本,如果参数是其他类型,则使用其他的版本:

public class MethodOverloads
{
  public void Foo<T>(T obj)
  {
    Console.WriteLine("Foo<T>(T obj), obj type: {0}", obj.GetType().Name);
  }
  public void Foo(int x)
  {
    Console.WriteLine("Foo(int x)");
  }
  public void Bar<T>(T obj)
  {
    Foo(obj);
  }
}

static void Main()
{
  var test = new MethodOverloads();
  test.Foo(33);
  test.Foo("abc");
}

需要注意的一点是,方法的选择或者调用是在编译的时候就决定了,不是运行时,可以通过以下方法来验证一下,运行下面的代码:

static void Main()
{
  var test = new MethodOverloads();
  test.Bar(44);
}

我们可以看到虽然我们传递的是int类型的参数,但是显示的是调用了Foo的泛型方法。原因就是编译器在编译的时候选择Bar的方法,Bar方法定义了一个泛型参数,而被调用的Foo方法又有满足泛型参数的方法,因此Foo的泛型方法被调用,在运行的时候这个选择是不会被改变的。

[转载]SQLite快速上手版笔记之注意和高级篇

mikel阅读(1032)

转载SQLite快速上手版笔记之注意和高级篇 – 明留 – 博客园.

相信看了我博客的上篇的博友或路过者,都会觉得,SQLite小巧,易学,简单。下面是我再把我的笔记上传博客中,以帮助一些人和我自己,今后翻阅方便。因为我知道有这回事。

  前几天看到有人回复帖子问了SQLite数据库用在哪些方面,其实这样的问题,都可以维基或是百度下的,什么都会出来,不过SQLite是小型的数据库,主要用在嵌入式设备中如手机等。下面是对这sqlite3的一些注意和高级部分的笔记:

  1、类型自动分类:具体的值比如sql语句部分的带双引号或单引号的文字被定为Text;如果文字没带引号和小数点或指 数则被定义为INTEGER;如果文字没带引号但有小数点或指数则被定义为REAL;如果值是空则被定义为空值;BLOB数据使用符号X’ABCD’标 识。

  2、sqlite3.0中,值被定义为什么类型只和值自身有关,和列、变量都没关系,我们称其其为“弱类型”。所有其他 的数据库引擎都收静态类型系统的限制,其中的所有值的类型是由其所属列的属性决定的,而和值无关。不过为了最大限度的增加sqlite和其他数据库的兼容 性,sqlite提出了一个“类型亲和性”,意思就是说它支持列的“类型亲和性”。列的“类型亲和性”是为该列所存储的数据建议一个类型。我们还要注意是建议不是强迫。在理论上讲,任何列上可以存储任何类型的数据。只是针对某些列,如果给建议类型的话,数据库将按所建议的类型存储,而这个被优先使用的数数据类型则被称为“亲和类型”。

  (1)、sqlite3.0中,数据库的每一列都被定义为以下亲和类型的一种:文本、数字(实数)、整数、无。(这点的理解是这样的,就是说我给一个整数类型的值填充到文本字段上,此时整数类型的数据被当作文本存储。)

    规则:

    (1)、 如果数据类型包括字符串”INT”那么它被定义成具有整数亲和性.

        (2)、 如果列中的数据类型包括以下任何的字符串 “CHAR”, “CLOB”, or “TEXT”    那么这个列则具有文本亲和性.要注意VARCHAR 类型包括字符串”CHAR”因此也具有文本类型亲和性.

            (3)、如果一个列的数据类型包括字符串”BLOB”或者如果数据类型被具体化了,那么这个列具有无类型亲和性.

            (4)、否则就具有数字(实数)类型亲和性.

  3、 运算符

   所有的数学运算符(所有的运算符而不是连锁作用标记符”||”)运算对象首先具有数字亲和性, 如果一个或是两个都不 能被转换为数字那么操作的结果将是空值。 对于连接作用操作符,所有操作符将首先具有文本亲和性。如果其中任何一个操作符不能被转换为文本(因为它是空值 或是 BLOB)连接作用操作符将是空值。

  4、 分类,排序,混合挑选

  当用子句 ORDER挑选值时,空值首先被挑选出来, 然后是整数和实数按顺序被挑选出来, 然后是文本值按 memcmp()顺序被挑选出来, 最后是BLOB值按memcmp()顺序被挑选出来.在挑选之前, 没有存储类型的值都被转换了。

  5、用户定义的校对顺序

  BINARY -使用memcmp()比较字符串数据,  不考虑文本编码。(默认比较顺序)   

  REVERSE -用倒序比较二进制文本。   

  NOCASE –  和二进制一样,但在比较之前,26 位的大写字母要被转换成小写字母。【这个改变可以通过编写sqlite的自定义函数自己实现一些数据库函数就可以,而且这些自定义函数,在sqlite api中都有。写好了后,进行编译,生成.exe文件就可以。】

  对于二进制比较符(=, <, >, <= and >=),如果操作数是一列的话,那么该列 的默认比较类型决定于所使用的比较顺序. 如果两个操作数都是列的话,那么左边的操作数的默认比较类型决定了所要使用的比较顺序.如果两个操作数都不是一 列,将使用二进制来比较。

  6、SQLite  不支持的  SQL92  特性

  这个列表的顺序关系到何时一个特性可能被加入到 SQLite。接近列表顶部的特性更可能在不远的将来加入。接近列表底部的特性尚且没有直接的计划。

  7、sqlite不支持成群的索引(简单来说,就是索引使数据库中的数据存入时索引的顺序是什么样,数据怎样放置),这 意味着,如果你的索引的是整数顺序,记录就会把数据库中的数据按整数顺序安排,先1后2后3。(设置sqlite页面缓 存: pragma page_size=大小;)

      看个例子:create table wibble2 as select * from wibble;

          Delete from wibble;

          Insert into wibble select * from wibble2 order by key;(这行的写法,在其他数据库试过,可行(Oracle/SQLServer2012),sqlite3中也支持)

          Drop table wibble2;

   8、sqlite中如何使用触发器执行取消和重做逻辑:思路是创建一个特殊表格, 保存数据库撤销和重做变化所需的信 息。因为数据库中的每个表格都需要参与撤销和重做,每个delete、insert、update都生成了触发器,而且它们可以在撤销日记表格中生成登记 项,这个登记项将撤销操作。撤销表格中的登记项由一般的sql语句组成,为了完成撤销,sql语句可以重新运行。

CREATE TRIGGER 触发器名称

[BEFORE|AFTER数据库事件 ON [数据库名称].表名

[FOR EACH ROW][ WHEN expression]

BEGIN

触发器执行动作

END

数据库事件:

DELETE INSERT

UPDATE

UPDATE OF 字段列表

    下面写个脚本:

      Create table ex1(a INTEGER, b TEXT, c REAL);

      Create trigger trigger_ex1_it

      after insert on ex1

      Begin

        Insert into undolog values(NULL, ‘delete from ex1 where rowid=’||new.rowid);

      End;

        Create trigger trigger_ex1_ut

      after update on ex1

      Begin

         Insert into undolog values(NULL,’update ex1 set   a=’||quote(old.a)||’,b=’||quote(old.b)||’, c=’||quote(old.c)||’ where rowid=’||old.rowid);

      End;

       Create trigger trigger_ex1_dt

      Before delete on ex1

      Begin

         Insert into undolog values(NULL, ‘insert into ex1(rowid, a, b, c) values(‘||old.rowid||’, ‘||quote(old.a)||’, ‘||quote(old.b)||’,’||quote(old.c)                             ||’)’);

      End;

  在ex1 表格中执行每个INSERT 后,the _ex1_it 触发器生成 DELETE语句的文本,它将撤 销 INSERT操作。The _ex1_ut触发器生成 UPDATE语句,这语句将取消一个 UPDATE所产生的作用。 trigger_ex1_dt触发器生成一个语句,这语句将取消一个 DELETE所具有的作用。

要注意 quote()函数在这些触发器中的使用。quote()函数在 SQLite中是标准的。它把它的参数转换成一种 适合被包含在 SQL 语句中的形式。数字值不改变。单个的 quotes 被加在字符串之前或之后,任何内在的单个quotes 都被逃逸。 quote()函数被加入 SQLite是 为了执行撤销和重做操作。

  9、SQLite3 C/C++ 开发接口(API 函数)

  Sqlite3是建立在sqlite2.8之上开发的,是为了支持UTF-16编码、自定义的文本排序方法、对blobs字段建立索引。3.0版和2.x版的api非常相似,只不过前缀改成sqlite3。

SQLite使用了普通的 void* 类型来指向 UTF-16编码的字符串. 客户端使用过程中可以把 void*映射成适合他们的系统的任何数据类型。

  SQLite 3.0 一共有83个API函数,此外还有一些数据结构和预定义(#defines). (完整的API 介绍请参看另一份文档.) 不过你们可以放心,这些接口使用起来不会像它的数量所暗示的那么复杂. 最简单的程序仍然使用三个函数就可以完 成: sqlite3_open(), sqlite3_exec(), 和 sqlite3_close()。

  要是想更好的控制数据库引擎的执行,可以使用提供的 sqlite3_prepare()函数把 SQL语句编译成字节 码,然后在使用 sqlite3_step()函数来执行编译后的字节码。以sqlite3_column_开头的一组API函数用来获取查询结果集中的 信息. 许多接口函数都是成对出现的,同时有UTF-8和UTF-16两个版本。 并且提供了一组函数用来执行用户自定义的 SQL函数和文本排序函数。 【附加体外话:其实有关数据库的操作,java的api或是c/c++库中都供普通操作数据库的api或是方法。因为我对这几门语言熟悉点,所以就顺便说 下,这语言之间的一些共性。】

  9.1  如何打开关闭数据库

     typedef struct sqlite3 sqlite3;

     int sqlite3_open(const char*, sqlite3**);

     int sqlite3_open16(const void*, sqlite3**);

     int sqlite3_close(sqlite3*);

     const char *sqlite3_errmsg(sqlite3*);

     const void *sqlite3_errmsg16(sqlite3*);

     int sqlite3_errcode(sqlite3*);

  说明:sqlite3_open() 函数返回一个整数来标识状态,而不是像第二版中一样返回一个指向 sqlite3 结构体的指针。 sqlite3_open() 和 sqlite3_open16() 的不同之处在于 sqlite3_open16() 使用 UTF-16编码(使用本地主机字节顺序)传递数据库文件名。 如果要连接新数据库, sqlite3_open16() 将内部文本转换为 UTF- 16编码, 反之 sqlite3_open() 将文本转换为 UTF-8编码。

  打开或者创建数据库的命令会被缓存,直到这个数据库真正被调用的时候才会被执行。而且允许使用PRAGMA 声明来设置如本地文本编码或默认内存页面大小等选项和参数。

sqlite3_errcode() 通常用来获取最近调用的 API接口返回的错误代码。 sqlite3_errmsg() 则用来得到这些错误代码所对应的文字说明。这些错误信息将以 UTF-8 的编码返回,并且在下一次调用任何 SQLite API 函数的时候被清除。sqlite3_errmsg16() 和 sqlite3_errmsg() 大体上相同,除了返回的错误信 息将以 UTF-16 本机字节顺序编码。

  SQLite3 的错误代码相比SQLite2 没有任何的改变,它们分别是:

  #define SQLITE_OK          0   /* Successful result */

  #define SQLITE_ERROR       1   /* SQL error or missing database */

  #define SQLITE_INTERNAL    2   /* An internal logic error in SQLite */

  #define SQLITE_PERM        3   /* Access permission denied */

  #define SQLITE_ABORT       4   /* Callback routine requested an abort */

  #define SQLITE_BUSY        5   /* The database file is locked */

  #define SQLITE_LOCKED      6   /* A table in the database is locked */

  #define SQLITE_NOMEM       7   /* A malloc() failed */

  #define SQLITE_READONLY    8   /* Attempt to write a readonly database */

  #define SQLITE_INTERRUPT   9   /* Operation terminated by sqlite_interrupt()

  #define SQLITE_IOERR       10   /* Some kind of disk I/O error occurred */

  #define SQLITE_CORRUPT     11   /* The database disk image is malformed */

  #define SQLITE_NOTFOUND    12   /* (Internal Only) Table or record not found

  #define SQLITE_FULL        13   /* Insertion failed because database is full

  #define SQLITE_CANTOPEN    14   /* Unable to open the database file */

  #define SQLITE_PROTOCOL    15   /* Database lock protocol error */

  #define SQLITE_EMPTY       16   /* (Internal Only) Database table is empty */

  #define SQLITE_SCHEMA      17   /* The database schema changed */

  #define SQLITE_TOOBIG      18   /* Too much data for one row of a table */

  #define SQLITE_CONSTRAINT  19   /* Abort due to contraint violation */

  #define SQLITE_MISMATCH    20   /* Data type mismatch */

  #define SQLITE_MISUSE      21   /* Library used incorrectly */

  #define SQLITE_NOLFS       22   /* Uses OS features not supported on host */

  #define SQLITE_AUTH        23   /* Authorization denied */

  #define SQLITE_ROW         100  /* sqlite_step() has another row ready */

  #define SQLITE_DONE        101  /* sqlite_step() has finished executing */

  9.2  执行  SQL  语句

    typedef int (*sqlite_callback)(void*,int,char**, char**);

    int sqlite3_exec(sqlite3*, const char * sql, sqlite_callback, void*, char**);

  sqlite3_exec 函数依然像它在 SQLite2 中一样承担着很多的工作。 该函数的第二个参数中可以编译和执行零个SQL 语句. 查询的结果返回给回调函数。 更多地信息可以查看 API 参考。

  typedef struct sqlite3_stmt sqlite3_stmt;

  int sqlite3_prepare(sqlite3*, const char*, int, sqlite3_stmt**, const char**);

  int sqlite3_prepare16(sqlite3*, const void*, int, sqlite3_stmt**, const void**);

  int sqlite3_finalize(sqlite3_stmt*);

  int sqlite3_reset(sqlite3_stmt*);

  sqlite3_prepare 接口把一条 SQL 语句编译成字节码留给后面的执行函数sqlite3—_exec()。 使用该接口访问数据库是当前比较好的的一种方法。

sqlite3_prepare() 处理的SQL语句应该是UTF-8编码的。而 sqlite3_prepare16() 则要求是UTF-16编码的。输入的参数中只有第一个 SQL 语句会被编译。 第四个参数则用来指向输入参数 中下一个需要编译的 SQL 语句存放的 SQLite statement 对象的指针, 任何时候如果调 用 sqlite3_finalize() 将销毁一个准备好的 SQL 声明。 在数据库关闭之前,所有准备好的声明都必须被释放销 毁。 sqlite3_reset() 函数用来重置一个 SQL 声明的状态,使得它可以被再次执行。

  SQL 声明可以包含一些型如”?” 或 “?nnn” 或 “:aaa”的标记, 其中”nnn” 是一个整 数,”aaa” 是一个字符串。这些标记代表一些不确定的字符值(或者说是通配符),可以在后面用 sqlite3_bind 接口来填充这些值。每一个 通配符都被分配了一个编号(由它在 SQL 声明中的位置决定,从 1 开始),此外也可以用 “nnn” 来表示 “?nnn” 这种情况。允许相同的 通配符在同一个 SQL 声明中出现多次, 在这种情况下所有相同的通配符都会被替换成相同的值。没有被绑定的通配符将自动取 NULL 值。

  int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));

  int sqlite3_bind_double(sqlite3_stmt*, int, double);

  int sqlite3_bind_int(sqlite3_stmt*, int, int);

  int sqlite3_bind_int64(sqlite3_stmt*, int, long long int);

  int sqlite3_bind_null(sqlite3_stmt*, int);

  int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));

  int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int n, void(*)(void*));

  int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);

  以上是 sqlite3_bind 所包含的全部接口,它们是用来给 SQL 声明中的通配符赋值的。没有绑定的通配符 则被认为是空值。绑定上的值不会被 sqlite3_reset()函数重置。但是在调用了 sqlite3_reset()之后所有的通配符都可以被重 新赋值。

在 SQL 声明准备好之后(其中绑定的步骤是可选的), 需要调用以下的方法来执行:

        int sqlite3_step(sqlite3_stmt*);

  如果 SQL 返回了一个单行结果集,sqlite3_step() 函数将返回 SQLITE_ROW , 如 果 SQL 语句执行成功或者正常将返回SQLITE_DONE , 否则将返回错误代码. 如果不能打开数据库文件则会返 回 SQLITE_BUSY . 如果函数的返回值是SQLITE_ROW, 那么下边的这些方法可以用来获得记录集行中的数据:

        const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);

        int sqlite3_column_bytes(sqlite3_stmt*, int iCol);

  int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);

  nt sqlite3_column_count(sqlite3_stmt*);

  const char *sqlite3_column_decltype(sqlite3_stmt *, int iCol);

  const void *sqlite3_column_decltype16(sqlite3_stmt *, int iCol);

  double sqlite3_column_double(sqlite3_stmt*, int iCol);

  int sqlite3_column_int(sqlite3_stmt*, int iCol);

  long long int sqlite3_column_int64(sqlite3_stmt*, int iCol);

  const char *sqlite3_column_name(sqlite3_stmt*, int iCol);

  const void *sqlite3_column_name16(sqlite3_stmt*, int iCol);

  const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);

  const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);

  int sqlite3_column_type(sqlite3_stmt*, int iCol);

  sqlite3_column_count()函数返回结果集中包含的列数。 sqlite3_column_count() 可以在执行了 sqlite3_prepare()之后的任何时刻调用。 sqlite3_data_count()除了必需要在 sqlite3_step()之后调用之外,其他 跟 sqlite3_column_count() 大同小异。如果调用 sqlite3_step() 返回值是 SQLITE_DONE 或者一个错 误代码, 则此时调用 sqlite3_data_count() 将返回 0 ,然而 sqlite3_column_count() 仍然会返回结果 集中包含的列数。返回的记录集通过使用其它的几个 sqlite3_column_***() 函数来提取, 所有的这些函数都把列的编号作为第二个参 数。列编号从左到右以零起始。

    sqlite3_column_type()函数返回第 N 列的值的数据类型。 具体的返回值如下:

             #define SQLITE_INTEGER   1

             #define SQLITE_FLOAT     2

             #define SQLITE_TEXT      3

             #define SQLITE_BLOB      4

             #define SQLITE_NULL      5

  sqlite3_column_decltype() 则用来返回该列在 CREATE TABLE 语句中声明的类 型。 它可以用在当返回类型是空字符串的时候。 sqlite3_column_name() 返回第 N 列的字段 名。 sqlite3_column_bytes() 用来返回 UTF-8 编码的 BLOBs 列的字节数或者 TEXT 字符串的字节 数。 sqlite3_column_bytes16() 对于 BLOBs 列返回同样的结果,但是对于 TEXT 字符串则按 UTF-16 的编码 来计算字节数。 sqlite3_column_blob() 返回 BLOB 数据。 sqlite3_column_text() 返回 UTF- 8 编码的 TEXT 数据。 sqlite3_column_text16() 返回 UTF-16 编码的 TEXT 数 据。 sqlite3_column_int() 以本地主机的整数格式返回一个整数值。 sqlite3_column_int64() 返回一 个 64 位的整数。 最后, sqlite3_column_double() 返回浮点数。

  10、SQLite 常见问题解答

  (1)、如何建立自动增长的字段:可以在字段上声明为INTEGER PRIMARY KEY AUTOINCREATE。

  (2)、SQLite 支持何种数据类型:参见 http://www.sqlite.org/datatype3.html。

  (3)、Sqlite允许一个列中插入任何类型的字段,(前面我们说过sqlite是种弱类型,不想其他的语言,如 java),但任何类型的字段会被自动转换该列所需的类型,转换不了的则按任何类型对应的类型插入。(这就是我在前面说的类型亲和性)有一种除外,就是标 志为INTEGER PRIMARY KEY 的列只能存储 64位整数, 当向这种列中插数据除整数以外的数据时,将会产生错误。

  (4)、SQLite 不允许在同一个表不同的两行上使用 0 和0.0 作主键,因为这两者sqlite认为是相等,既然相等就是不唯一。

  (5)、多个进程可同时打开同一个数据库。多个进程可以同时进行 SELECT (读)操作,但在任一时刻,只能有一个 进程对数据库进行更改(写)。看到这么一句话这样说的,我觉得很有借鉴性,所以摘入到此,虽不是原话,大致意思是这样的, 对于网络文件,文件锁的实现有 好多 Bug,是靠不住的。如果他们说的是对的, 那么在两台或多台 Windows机器间共享数据库可能会引起不期望的问题(关系数据库两外算)。 SQLite允许多个进程同时打开一个数据库, 同时读一个数据库。当有任何进程想要写时,它必须在更新过程中锁住数据库文件。 但那通常只是几毫秒的时 间。其它进程只需等待写进程干完活结束。 当SQLite 试图访问一个被其它进程锁住的文件时,缺省的行为是返回 SQLITE_BUSY。 (sqlite3是线程安全的)

  (6)、在 SQLite 数据库中如何列出所有的表和索引:.tables和.schema和数据字典 sqlite_master; 字典结 构: type TEXT, name TEXT, tbl_name TEXT, rootpage INTEGER, sql TEXT 。

  (7)、SQLite 数据库有已知的大小限制吗? 见 limits.html 。

  (8)、在 SQLite 中,如何在一个表上添加或删除一列?Sqlite没有提供完整的alter支持,可以使用它 来在表的末尾增加一列,可更改表的名称。 如果需要对表结构做更复杂的改变,则必须重新建表。 重建时可以先将已存在的数据放到一个临时表中,删除原 表, 创建新表,然后将数据从临时表中复制回来。  如,假设有一个 t1 表,其中有 “a”, “b”, “c” 三列, 如果要删除列 c ,以下 过程描述如何做:

  BEGIN TRANSACTION;

  CREATE TEMPORARY TABLE t1_backup(a,b); 临时表

  INSERT INTO t1_backup SELECT a,b FROM t1; 临时表拷贝表t1数据

  DROP TABLE t1;

  CREATE TABLE t1(a,b);

  INSERT INTO t1 SELECT a,b FROM t1_backup;

  DROP TABLE t1_backup;

  END TRANSACTION;

  COMMIT;

[转载]ASP.NET MVC:自定义 Route 让你的 Url 更优雅

mikel阅读(920)

[转载]ASP.NET MVC:自定义 Route 让你的 Url 更优雅 – 鹤冲天 – 博客园.

 

如今,互联网越来越注重简单优雅的 Url,对比下面两个:

我相信大多数朋友会更喜欢第二种方式:小写,使用负(减)号作为连字符。

本文使用自定义 Route 来达到方式二的效果,只需增加几个类和简单修改下 global.asax 文件。

Route 是双向的

Route 的基本概念我就不多说了,这里重点强调一下 ASP.NET MVC 中 Route 是双向的,这可以从 RouteBase 类的定义可以看出:

1
2
3
4
public abstract class RouteBase{
    public abstract RouteData GetRouteData(HttpContextBase httpContext);
    public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
}

GetRouteData 用于解析 Url,GetVirtualPath 用于生成 Url。

在开始编写我们的 Route 类之前,我们需要一个用于处理字符串的静态类:

StringElegantHelper 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
internal static class StringElegantHelper {

    public static readonly char minus = '-';

    public static string Elegant(string s) {
        var builder = new StringBuilder();
        var index = 0;
        foreach (var c in s) {
            if (c >= 'A' && c <= 'Z') {
                if (index > 0) builder.Append(minus);
                builder.Append(char.ToLower(c));
            }
            else if (c == minus) {
                builder.Append(minus);
                builder.Append(minus);
            }
            else
                builder.Append(c);
            index++;
        }
        return builder.ToString();
    }

    public static string DeElegant(string s) {
        var builder = new StringBuilder();
        var iterator = s.GetEnumerator();
        while (iterator.MoveNext()) {
            if (iterator.Current != minus) {
                builder.Append(iterator.Current);
                continue;
            }
            if (!iterator.MoveNext()) {
                builder.Append(minus);
                break;
            }
            if (iterator.Current == minus)
                builder.Append(minus);
            else
                builder.Append(iterator.Current);
        }
        return builder.ToString();
    }
}

StringElegantHelper 中有两个方法 Elegant 和 DeElegant,将分别用在 GetVirtualPath 和 GetRouteData 方法中,用于生成和解析优雅的 Url。

这两方法也可以使用正则表达式来实现,但我认为效率不高,就采用了上面这种最朴实的方式。

有了 StringElegantHelper 类,写出 ElegantRoute 就简单多了:

ElegantRoute 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ElegantRoute : Route {

    public static readonly string[] ToElegant =  new [] { "controller", "action" };

    public ElegantRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
        : base(url, defaults, constraints, dataTokens, routeHandler) { }

    public override RouteData GetRouteData(HttpContextBase httpContext) {
        var result = base.GetRouteData(httpContext);
        if (result == null) return null;

        foreach (var key in ToElegant)
            HandleItem(result.Values, key, StringElegantHelper.DeElegant);
        return result;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) {
        var elegantValues = new RouteValueDictionary(values);
        foreach (var key in ToElegant)
            HandleItem(elegantValues, key, StringElegantHelper.Elegant);
        return base.GetVirtualPath(requestContext, elegantValues);
    }

    private void HandleItem(RouteValueDictionary dict, string key, Func<string, string> handler) {
        if (!dict.ContainsKey(key)) return;
        //
        var value = dict[key];
        if (!(value is string)) return;
        //
        dict[key] = handler(value as string);
    }
}

注意,上面代码中只对 controller 和 action 的路由值进行了“优雅”处理,其它值并没有,为什么呢?

大家可以考虑以下网址:

~/customers/details/ANTON

全部处理的话会变成:

~/customers/details/a-n-t-o-n

代码其他部分比较简单,不多说了。

最后,还需要几个扩展方法,以方便在 global.asax 文件中使用:

ElegantRouteExtensions 类

1
2
3
4
5
6
7
8
9
10
11
public static class ElegantRouteExtensions {
    public static ElegantRoute MapElegantRoute(this RouteCollection routes, string name, string url, object defaults) {
        var route = new ElegantRoute(url,
            new RouteValueDictionary(defaults),
            new RouteValueDictionary(), //constraints
            new RouteValueDictionary(), //dataTokens
            new MvcRouteHandler());
        routes.Add(name, route);
        return route;
    }
}

我只写出一个,大家可根据需要添加。

使用 ElegantRouter

借助上面的扩展方法,使用就很相当简单了。

global.asax 文件中,把 routes.MapRoute 替换为 routes.MapElegantRoute 即可:

1
2
3
4
5
routes.MapElegantRoute(  // MapRoute
    "Default", 
    "{controller}/{action}/{id}", 
    new { controller = "Home", action = "Index", id = UrlParameter.Optional } 
);

运行起来验证,点下右上角的 [ Log On ]  链接:

image

ElegantRoute 生成和解析通过。

 

本文代码未严格测试,如有 bug,请在回复中告知,不胜感激!

 

源码下载:ElegantRouteDemo.rar (75KB)

在线演示:http://demo.highsoft.cc/products

[转载]android 通过wifi 热点实现手机摄像头数据共享

mikel阅读(1165)

[转载]android 通过wifi 热点实现手机摄像头数据共享 – pengqinping – 博客园.

最近一直在在学习通过两个Android手机通过wifi共享摄像头的数据。弄了好久有了点头目。具体有下面几个步骤:

1.对手机相机的开发,自定义surfaceView来定义自己的相机类。主要是显示手机摄像头的画面。

2.对自定义相机的预览画面的数据的获取。然后对数据进行解析。

3.在两台Android手机通过wifi建立传输数据的连接。

4.将数据的时时的传输在两个手机之间。

一.自定义相机类很简单定义一个surfaceView,在Activity中,通过实现surfaceHodler.callBack接口重写的 OnSurfaceCreate()中添加打开相机的camera.open即可获得一个Camera对象。设置Camera的预览对 象。 mCamera.setPreviewDisplay(holder);holder是我们surfaceView的hodler。可以通过 surfaceView.getHodler()获取。这样就可在surfaceView中显示我们手机的预览的画面。

代码如下:

package com.weipu.camera;

import java.io.IOException;

import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;

/**
*   接口SurfaceHolder.Callback被用来接收摄像头预览界面变化的信息。它实现了三个方法:   surfaceChanged
*   当预览界面的格式和大小发生改变时,该方法被调用。   surfaceCreated   初次实例化,预览界面被创建时,该方法被调用。
*   surfaceDestroyed   当预览界面被关闭时,该方法被调用。
*
* @author pengqinping
*/

@TargetApi(9)
public class MyCameraActivity extends Activity implements
SurfaceHolder.Callback, OnClickListener
{

private SurfaceHolder mSurfaceHolder = null;

private SurfaceView mSurfaceView = null;

private Camera mCamera = null;

@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// 初始化组件,SurfaceView
mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView01);
/**
* 通过代码,我们从surfaceview中获得了holder,并增加callback功能到“this”。
* 这意味着我们的操作(activity)将可以管理这个surfaceview。
*/
mSurfaceHolder = mSurfaceView.getHolder();// 获取surfaceView的Holder对象
mSurfaceHolder.addCallback(this);// 为surfaceView的holder对象添加回调函数,
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

@Override
protected void onStart()
{
super.onStart();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width1,
int height1)
{
if (mCamera == null)
{
System.out.println("没有相机设备");
return;
}
if (mCamera != null)
{
//初始化相机。设置参数
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(width1, height1); // 设置预览照片的大小
parameters.setPreviewFpsRange(20, 30); // 每秒显示20~30帧
parameters.setPictureFormat(ImageFormat.NV21); // 设置图片格式
parameters.setPictureSize(width1, height1); // 设置照片的大小
}
try
{
mCamera.setPreviewDisplay(holder);
}
catch (IOException e)
{
System.out.println("设置预览出错......");
}
// 通过SurfaceView显示取景画面
mCamera.startPreview(); // 开始预览
mCamera.autoFocus(null); // 自动对焦

}

@Override
public void surfaceCreated(SurfaceHolder holder)
{
try
{
mCamera = Camera.open();
}
catch (Exception e)
{
System.out.println("000000000");
return;
}

}

@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
mCamera.stopPreview();
mCamera.release();
}

@Override
public void onClick(View v)
{

}

}

这里在对应的Activity申明的时候需要在AndroidManifest.xml中添加

android:configChanges=”keyboardHidden|orientation”

android:screenOrientation=”landscape”

例:(这样是设置他的横屏和横竖屏的改变Activity做出的改变)





在权限方面最好是加上网络权限:


&nbsp;

二.手机预览数据的获取,手机相机的数据怎么获取了?通过回调手机预览函数: mCamera.setPreviewCallback(streamIt); // streamIt回调的接口的对象。 这样我们就可以获取相机预览的时时的数据。 获取的是原生态YUV格式的数据。这一步的关键是对数据的解析如何解码。网上有牛人写的我也是copy过来用的。重要的是根据自己的需要来决定使用的方式,我使用的是第二种,比较不容易出错。前一种的效果比较差,这里代码我就不弄出来了。(只用好的…..>>>>哈哈呵呵呵)

代码如下:

private Camera.PreviewCallback streamIt = new Camera.PreviewCallback()
{

@Override
public void onPreviewFrame(byte[] data, Camera camera)
{

// method1:使用解码

//method2: 使用系统自带的类来发送图片
Size size = camera.getParameters().getPreviewSize();//获取大小
try
{
//调用image.compressToJpeg()将YUV格式图像数据data转为jpg格式
YuvImage image = new YuvImage(data, ImageFormat.NV21,
size.width, size.height, null);
//使用handler发送图片出去
ByteArrayOutputStream outputSteam = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, size.width, size.height),
,
outputSteam);
}
catch (Exception ex)
{
Log.e("Sys", "Error:" + ex.getMessage());
}
}
};

三,两个手机之间建立连接。通过WIFI局域网,不一定是wifi.只要在一个网段就可以,这步我由于设备的原因走了很多弯路。开始我是用的是模拟 器来操作的,因为模拟器没有指定Ip还必须通过端口的映射来完成,好不容易把端口映射弄好了,他两台模拟器都用端口映射好像不太合适,因为在测试的时候模 拟器自己使用的端口是同一个,问题比较多。我失败了很多次,后来直接使用真机测试,通过借助WIFI,还的感谢我同事提供自己的设备测试。这步需要注意的 是Android手机开启一个端口的时候其中有一个堵塞的方法我们最好放在线程中来开启,接受数据后我们不能再接线程中更新界面的数据,这是 android中比较头疼的位置。我们还需要通过线程帮助类来完成。

四.数据共享的思路。

手机数据发动端(连接指定的ip端);开启一个线程来发送数据,因为在android中直接发送会导致主线程出现很多问题。

手机接受数据端(也就是开启端口等待接受数据的设备)。可以一个线程来接受数据,接受到数据之后通过handler来跟新手机上的显示的画面。

 

效果图:

 

呵呵 不要以为是两个手机都开了摄像头在同一个位置……>>>>>>>ok..记得是原创哦。

[转载]基于lucene的搜索服务器

mikel阅读(1154)

[转载]基于lucene的搜索服务器 – Norman_ZL – 博客园.

最近在项目经理Jack.Wang的带领下,写了一个基于Lucene的搜索服务器,学到了很多,在此记录一下。

目的:

这是一个用Lucene建立索引并搜索的服务器,用于项目的整站搜索,对数据库或者文档的全文索引。

 

优点:

1、多项目使用:因为使用了restful服务,多个项目可以同时使用一个搜索服务器。

2、使用简单,灵活:只需给出一个目标项目的model层的dll文件,再根据所要建立索引的实体类配置xml;或者如果你的项目是有C#的反射特 性,可以在model层的类上添加指定的特性,这样也起到了配置的作用,不在需要xml文件。这样目标项目下哪个类需要建立索引,哪个类下的哪些字段需要 索引都不再是写死的了。

3、跨语言:因为使用了restful服务,最后搜索时,对外提供的是一个url,所以只需要写一个客户端发送url请求即可。没有C#反射特性,就用xml配置。

 

 实现思路:

首先说一下建立索引端吧:

首先需要的是一个dll文件,然后通过PraseModel类,配合XML配置文件或者自身的反射特性,得到 List<TypeModel>。TypeModel用于存储解析后的数据,放了这个dll文件中哪些类需要建立索引,类中的哪些字段需要建 立索引,如何建立等信息。其中还需要去查一下MySQL数据库,MySQL数据库用于记录每次建立索引的信息。这样对比一下正要建立的类和字段,就知道本 次建立索引是新建的索引呢,还是在已有索引的基础上更改,添加,删除等。

然后在对每一个TypeModel,用PraseModel类解析,解析出GenericModel。GenericModel除了记录一些建立索引的配置信息外,还要去目标项目的数据库查出具体的内容,或者是抽取出具体路劲对应文档的内容。

接下来就是把GenericModel传给Lucene,用Lucene建立索引了。因为内容大部分是中文的,所以不用他自己的分词,用盘古分词解析。

以上的每个步骤都用Log类包装的log4net记录日志。

整个建立索引的任务,由同事开发的一个任务调度来执行,安排一定的时间间隔,传入指定的参数即可。

因为lucene建立索引的时候,会把索引文件锁起来,所以我自己在建立索引的时候在外层加了锁,免得使用lucene运行时报错。

 

这样对于每个项目,哪些类需要建立索引,类其中的哪些字段需要建立索引,是否唯一主键,是否分词,是否存储等,都变成可配置,不用为了单一的一个项目写死。比较通用。

 

结束:

前文链接:搜索服务器-开篇介绍: http://www.cnblogs.com/JackWang/archive/2012/07/27/2612594.html

写得比较菜,呵呵,不够专业。先这样,下次再写Restful搜索端的实现和RestSharp搜索客户端的使用。

 

下文:http://www.cnblogs.com/Norman-ZL/archive/2012/09/07/2674826.html

[转载]jquery实现图片延时加载

mikel阅读(983)

[转载]jquery实现图片延时加载 – Smile城 – 博客园.

原理:通过 JQuery 插件 lazyload 使得在浏览器可见区域外的图片不会被加载,当图片被用户滚动到浏览器可见区域时图片才进行加载,有点类似使用 Google 搜索图片时的效果。很明显,通过使用图片延时加载可以提高页面的加载速度。

 

实现过程:

首先是引入JQuery文件和插件文件。jQuery文件在这里我就不再赘述了。下面我贴出插件文件,我们将该文件命名为jQuery.lazyload.js

// JavaScript Document
/*
* Lazy Load - jQuery plugin for lazy loading images
*
* Copyright (c) 2007-2012 Mika Tuupola
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* http://www.appelsiini.net/projects/lazyload
*
* Version: 1.8.0
*
*/
(function($, window) {
var $window = $(window);

$.fn.lazyload = function(options) {
var elements = this;
var $container;
var settings = {
threshold : 0,
failure_limit : 0,
event : "scroll",
effect : "show",
container : window,
data_attribute : "original",
skip_invisible : true,
appear : null,
load : null
};

function update() {
var counter = 0;

elements.each(function() {
var $this = $(this);
if (settings.skip_invisible &amp;&amp; !$this.is(":visible")) {
return;
}
if ($.abovethetop(this, settings) ||
$.leftofbegin(this, settings)) {
/* Nothing. */
} else if (!$.belowthefold(this, settings) &amp;&amp;
!$.rightoffold(this, settings)) {
$this.trigger("appear");
} else {
if (++counter &gt; settings.failure_limit) {
return false;
}
}
});

}

if(options) {
/* Maintain BC for a couple of versions. */
if (undefined !== options.failurelimit) {
options.failure_limit = options.failurelimit;
delete options.failurelimit;
}
if (undefined !== options.effectspeed) {
options.effect_speed = options.effectspeed;
delete options.effectspeed;
}

$.extend(settings, options);
}

/* Cache container as jQuery as object. */
$container = (settings.container === undefined ||
settings.container === window) ? $window : $(settings.container);

/* Fire one scroll event per scroll. Not one scroll event per image. */
if (0 === settings.event.indexOf("scroll")) {
$container.bind(settings.event, function(event) {
return update();
});
}

this.each(function() {
var self = this;
var $self = $(self);

self.loaded = false;

/* When appear is triggered load original image. */
$self.one("appear", function() {
if (!this.loaded) {
if (settings.appear) {
var elements_left = elements.length;
settings.appear.call(self, elements_left, settings);
}
$("<img alt="" />")
.bind("load", function() {
$self
.hide()
.attr("src", $self.data(settings.data_attribute))
[settings.effect](settings.effect_speed);
self.loaded = true;

/* Remove image from array so it is not looped next time. */
var temp = $.grep(elements, function(element) {
return !element.loaded;
});
elements = $(temp);

if (settings.load) {
var elements_left = elements.length;
settings.load.call(self, elements_left, settings);
}
})
.attr("src", $self.data(settings.data_attribute));
}
});

/* When wanted event is triggered load original image */
/* by triggering appear. */
if (0 !== settings.event.indexOf("scroll")) {
$self.bind(settings.event, function(event) {
if (!self.loaded) {
$self.trigger("appear");
}
});
}
});

/* Check if something appears when window is resized. */
$window.bind("resize", function(event) {
update();
});

/* Force initial check if images should appear. */
update();

return this;
};

/* Convenience methods in jQuery namespace. */
/* Use as $.belowthefold(element, {threshold : 100, container : window}) */

$.belowthefold = function(element, settings) {
var fold;

if (settings.container === undefined || settings.container === window) {
fold = $window.height() + $window.scrollTop();
} else {
fold = $(settings.container).offset().top + $(settings.container).height();
}

return fold};

$.rightoffold = function(element, settings) {
var fold;

if (settings.container === undefined || settings.container === window) {
fold = $window.width() + $window.scrollLeft();
} else {
fold = $(settings.container).offset().left + $(settings.container).width();
}

return fold = $(element).offset().top + settings.threshold + $(element).height();
};

$.leftofbegin = function(element, settings) {
var fold;

if (settings.container === undefined || settings.container === window) {
fold = $window.scrollLeft();
} else {
fold = $(settings.container).offset().left;
}

return fold &gt;= $(element).offset().left + settings.threshold + $(element).width();
};

$.inviewport = function(element, settings) {
return !$.rightofscreen(element, settings) &amp;&amp; !$.leftofscreen(element, settings) &amp;&amp;
!$.belowthefold(element, settings) &amp;&amp; !$.abovethetop(element, settings);
};

/* Custom selectors for your convenience. */
/* Use as $("img:below-the-fold").something() */

$.extend($.expr[':'], {
"below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0}); },
"above-the-top" : function(a) { return !$.belowthefold(a, {threshold : 0}); },
"right-of-screen": function(a) { return $.rightoffold(a, {threshold : 0}); },
"left-of-screen" : function(a) { return !$.rightoffold(a, {threshold : 0}); },
"in-viewport" : function(a) { return !$.inviewport(a, {threshold : 0}); },
/* Maintain BC for couple of versions. */
"above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0}); },
"right-of-fold" : function(a) { return $.rightoffold(a, {threshold : 0}); },
"left-of-fold" : function(a) { return !$.rightoffold(a, {threshold : 0}); }
});

})(jQuery, window);

下面我们来看引入方式。

       你必须改变你的HTML代码。将SRC属性的占位符图像。演示页面使用1×1像素的灰度GIF 的真实图像的URL必须投入原始数据的属性。

  

  下面我们来看在html代码中需要书写些什么,这是调用方式

<script type="text/javascript">// <![CDATA[
$(document).ready(function() {            $("img").lazyload({                   effect: "fadeIn"            });        });
// ]]></script>

到这里,其实已经实现了我们想要的效果了,图片将会以 fadeIn 形式被载入。

但是,我们在这里还是不得不提一下,这个插件的其他的功能。

1.可以设置阀值来控制 灵敏度,下边代码把阀值设置成200 表明当图片没有看到之前先load 200像素。


 

1 $("img").lazyload({ threshold : 200 });

 

2.可以通过设置占位符图片和自定事件来触发加载图片事件


 

1 $("img").lazyload({ 2       placeholder: "img/grey.gif",      event: "click" 
3  });

 

3.事件触发加载,事件可以是任何 jQuery 时间, 如: click 和 mouSEOver. 你还可以使用自定义的事件, 如: sporty 和 foobar. 默认情况下处于等待状态, 直到用户滚动到窗口上图片所在位置. 在灰色占位图片被点击之前阻止加载图片, 你可以这样做:

1 $("img").lazyload({ 2       placeholder: "img/grey.gif",      event: "click" 
3  });

 

延迟加载图片,Lazy Load 插件的一个不完整的功能, 但是这也能用来实现图片的延迟加载. 下面的代码实现了页面加载完成后再加载. 页面加载完成 5 秒后, 指定区域内的图片会自动进行加载.

$(function() {
$("img:below-the-fold").lazyload({
placeholder: "img/grey.gif",
event: "sporty"
});
});
$(window).bind("load", function() {
var timeout = setTimeout(function() {
$("img").trigger("sporty") }, 5000);
});

更多的方法和资料,请参见官网http://www.appelsiini.net/projects/lazyload

这里需要特别说明一下,jquery的版本不要太低哦,1.3的就不会出效果的。

这是我写的一个小demo,拿去参考一下还是极好的。

下载地址:http://files.cnblogs.com/wscdzl/demo.rar

  

[转载]ASP.NET MVC的Razor引擎:View编译原理

mikel阅读(1249)

[转载]ASP.NET MVC的Razor引擎:View编译原理 – Artech – 博客园.

通过.cshtml或者.vb.html文件定义的View能够被执行,必须先被编译成存在于某个程序集的类型,ASP.NET MVC采用动态编译的方式对View文件实施编译。当我们在对ASP.NET MVC进行部署的时候,需要对.cshtml或者.vb.html文件进行打包。针对某个View的第一次访问会触发针对它的编译,一个View对应着一 个类型。我们可以对.cshtml或者.vb.html进行修改,View文件修改后的第一次访问将会导致View的再一次编译。和ASP.NET 传统的编译方式一样,针对View的编译默认是基于目录的,也就是说同一个目录下的多个View文件被编译到同一个程序集中。[本文已经同步到《How ASP.NET MVC Works?》中]

为 了让读者对ASP.NET MVC对View文件的编译机制具有一个深刻的认识,我们通过一个简单的实例来确定View文件最终都被编译成什么类型,所在的程序集又是哪一个。我们在 一个ASP.NET MVC应用中为HtmlHelper定义了如下一个扩展方法ListViewAssemblies,该方法用于获取当前被加载的包含View类型的程序集 (程序集名称以“App_Web_”为前缀)。
1: public static class HtmlHelperExtensions

2: {

3: public static MvcHtmlString ListViewAssemblies(this HtmlHelper helper)

4: {

5: TagBuilder ul = new TagBuilder("ul");

6: foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a=&gt;a.FullName.StartsWith("App_Web_")))

7: {

8: TagBuilder li = new TagBuilder("li");

9: li.InnerHtml = assembly.FullName;

10: ul.InnerHtml+= li.ToString();

11: }

12: return new MvcHtmlString(ul.ToString());

13: }

14: }

然后我们定义了如下两个Controller类型(FooController和BarController),它们之中各自定义了两个Action方法Action1和Action2。

1: public class FooController : Controller

2: {

3: public ActionResult Action1()

4: {

5: return View();

6: }

7: public ActionResult Action2()

8: {

9: return View();

10: }

11: }

12:

13: public class BarController : Controller

14: {

15: public ActionResult Action1()

16: {

17: return View();

18: }

19: public ActionResult Action2()

20: {

21: return View();

22: }

23: }

接下来我们为定义在FooController和BarController的四个Action创建对应的View(对应文件路为:“~/Views/Foo/Action1.cshtml”、“~/Views/Foo/Action2.cshtml”、“~/Views/Bar/Action1.cshtml”和“~/Views/Bar/Action2.cshtml”)。它们具有如下相同的定义,我们在View中显示自身的类型和当前加载的基于View的程序集。

1:
<div>当前View类型:@this.GetType().AssemblyQualifiedName</div>
2:
<div>当前加载的View程序集:</div>
3: @Html.ListViewAssemblies()

现在运行我们的程序并在浏览器中通过输入相应的地址“依次”(“Foo/Action1”、“Foo/Action2”、“Bar /Action1”和“Bar/Action2”)访问定义在FooController和BarController的四个Action,四次访问得到 的输出结果下图所示。

image

输出结果至少可以反映三个问题:

  • ASP.NET MVC对View文件进行动态编译生成的类型名称基于View文件的虚拟路径(比如文件路径为“~/Views/Foo/Action1.cshtml” 的View对应的类型为“ASP._Page_Views_foo_Action1_cshtml”)。
  • ASP.NET MVC是按照目录进行编译的(“~/Views/Foo/”下的两个View文件最终都被编译到程序集“App_Web_j04xtjsy”中)。
  • 程序集按需加载,即第一次访问“~/View/Foo/”目录下的View并不会加载针对“~/View/Bar/”目录的程序集(实际上此时该程序集尚未生成)。

我们可以通过BuildManager类型的静态方法GetCompiledType和GetCompiledAssembly(如下面的代码片断所示)根据View文件的虚拟路径得到对应的类型和程序集。

1: public sealed class BuildManager

2: {

3: //其他成员

4: public static Type GetCompiledType(string virtualPath);

5: public static Assembly GetCompiledAssembly(string virtualPath);

6: }

在现有演示实例的基础上我们创建了如下一个HomeController,默认的Action方法Index中通过调用BuildManager的静态方法GetCompiledType得到并呈现出四个View文件对应的类型名称。

1: public class HomeController : Controller

2: {

3: public void Index()

4: {

5: Response.Write(BuildManager.GetCompiledType("~/Views/Foo/Action1.cshtml") + "
");

6: Response.Write(BuildManager.GetCompiledType("~/Views/Foo/Action2.cshtml") + "
");

7: Response.Write(BuildManager.GetCompiledType("~/Views/Bar/Action1.cshtml") + "
");

8: Response.Write(BuildManager.GetCompiledType("~/Views/Bar/Action2.cshtml") + "
");

9: }

10: }

直接运行我们的程序后会在浏览器中得到代表四个View文件编译类型名称的字符串,具体显示效果下图所示。与上图显示的View类型名称相比较,我们会发现它们是一致的。

image

上面我们简单地介绍ASP.NET MVC以目录为单位的动态View编译,有人可能会问一个问题:编译生成的程序集存放在哪里?在默认情况下,View文件被动态编译后生成的程序集被临时 存放在ASP.NET的临时目录“%WinDir%\Microsoft.NET\Framework\{Version No}\Temporary ASP.NET Files\”下,不过我们可以通过如下所示的配置节<system.web>/<compilation>的 tempDirectory 属性来改变动态编译的临时目录。如果我们改变了这个临时目录,需要确保工作进程运行帐号具有访问该目录的权限。
1:

2:

3:

4:

一个寄宿于IIS的Web应用会在上述的临时目录下创建一个与Web应用同名的子目录,所以我们很容易地找到应用对应的编译目录。但是对于将 Visual Studio Development Server作为宿主的Web应用都会编译到名称为Roor的子目录下。如果这样的应用太多,我们往往不太容易准确地找到基于某个应用的编译目录。有时候 我们可以根据目录最后的修改时间来找到它,但是我个人倾向于直接删除整个Root目录,然后运行我们的程序后会重新生成一个只包含该应用编译目录的 Root目录。

对于上面演示的实例,我将Web应用寄宿于IIS下并且命名为MvcApp,我本机的目录“C:\Windows\Microsoft.NET \Framework\v4.0.30319\Temporary ASP.NET Files\mvcapp\c4ea0afa\
a83bd407”下可以找到动态编译的生成的文件。如下图所示,两个View目录(“~/Views/Foo”和“~/Views/Bar”)编译生成的程序集就在这个目录下面。

image

读者一定很好奇一个View文件通过动态编译最终会生成一个怎样的类型?对应前面演示的实例,我们已经知道了四个View文件编译生成的类型名称和 所在的程序集,我们只需要通过Reflector打开对应的程序集就能得到View文件编译类型的定义。如下所示的是View文件“~/Views /Foo/Action.cshtml”编译后生成的ASP._Page_Views_Foo_Action1_cshtml类型的定义。

1: [Dynamic(new bool[] { false, true })]

2: public class _Page_Views_Foo_Action1_cshtml : WebViewPage<object></object>

3: {

4: public override void Execute()

5: {

6: this.WriteLiteral("
<div>当前View类型:</div>
\r\n
<div>

");

7: this.Write(base.GetType().AssemblyQualifiedName);

8: this.WriteLiteral("

</div>
\r\n
<div>当前加载的View程序集:</div>
\r\n");

9: this.Write(base.Html.ListViewAssemblies());

10: }

11:

12: protected global_asax ApplicationInstance

13: {

14: get

15: {

16: return (global_asax) this.Context.ApplicationInstance;

17: }

18: }

19: }

ASP.NET MVC的Razor引擎:View编译原理
ASP.NET MVC的Razor引擎:RazorView
ASP.NET MVC的Razor引擎:RazorViewEngine

[转载]Sharing cookie Across domain 跨域cookie访问 cookie跨域

mikel阅读(1146)

[转载]基于android的远程视频监控系统(已开放源码) – fei菲菲 – 博客园.

Sharing cookie Across domain 跨域cookie访问 cookie跨域
今天研究一下cookie跨域的问题,主要有两种情况,
一种是允许跨子域访问,这种通过给Domain属性赋值的方法可以轻松实现。
另一种则是允许完全不相关的域可以访问同一个cookie。

这里主要介绍一下第二种的情况,
比如
http://www.blog.com/ 有 Login.aspx 页面
http://www.bbs.com/ 有 Login.aspx other.aspx页面
现在要求在用户在blog的登陆后,直接到bbs不要求再登陆了。

这里采用一取巧的方法,比较简单的实现这个功能,从另一种思路解决cookie跨域的问题,

先看一下blog的Login.aspx页面的代码

private void Login(string userName, string password)
    {
        bool login;

        if (CheckLoginOKAndGetUserdID(userName, password))
        {
            login = true;
            Response.Write(string.Format("<IFRAME style='WIDTH:1px;HEIGHT:1px' src='http://www.bbs.com/Login.aspx?userId={0}' frameBorder='0'></IFRAME>", userName));
        }
        else
        {
            login = false;
        }
    }

当用户在blog登陆成功后,直接调用输出IFrame,并把userId作为URL的QueryString。
IFrame调用的Url就会执行bbs域的Login.aspx的Page_Load方法:

protected void Page_Load(object sender, EventArgs e)
    {
        string userId;
        HttpContext.Current.Response.AddHeader("p3p", "CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"");//这句是关键,不能省掉
        userId = Request.QueryString["userId"];
        HttpCookie oCookie = new HttpCookie("UserID");
        oCookie.Value = userId;
        oCookie.Expires = DateTime.Now.AddDays(30);
        HttpContext.Current.Response.Cookies.Add(oCookie);

        oCookie = null;
    }

在这里,它把需要的值(userId),从URL的QueryString获得了,然后再写到当前域(bbs)的cookie里。从而实现了cookie的跨域共享,其实只不是跨域拷贝。
执行完这个方法后bbs的Other.aspx页面就可以访问”UserID”这个cookie了,从而不需要重新登陆。

请注意HttpContext.Current.Response.AddHeader(“p3p”, “CP=\”IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\””);
关键是这句。因为IE浏览器会把由IFrame应用程序写的cookie看成是第三方cookie,所以如果没有加这句,cookie会被当作第三方cookie而被删除.
php里是加 header(‘P3P:CP=”IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT”‘);

我调试的代码在IE7下通过,其它浏览器并未测试。

[转载]第三节 MemcachedProviders之SesstionStateProvider(关于Session的讨论)

mikel阅读(1109)

[转载]第三节 MemcachedProviders之SesstionStateProvider(关于Session的讨论) – 小城岁月 – 博客园.

第三节 MemcachedProviders之SesstionStateProvider(关于Session的讨论)

本节讨论问题Memcached缓存有效期及SesstionStateProvider管理Session。
  • DefaultExpireTime 和 对象序列化存储
  • SesstionStateProvider
MemcachedProvider是如何控制存储数据的有效期的
一、DefaultExpireTime 和 对象序列化存储
配置文件方式
View Code

<!--?xml version="1.0" encoding="utf-8" ?-->

<section name="cacheProvider" type="MemcachedProviders.Cache.CacheProviderSection, MemcachedProviders" allowdefinition="MachineToApplication" restartonexternalchanges="true"></section><section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching"></section><section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"></section><!-- put your own server(s) here-->

<!-- Define some output appenders -->

<!--<threshold value="OFF" />-->

<!-- Setup the root category, add the appenders and set the default priority -->

在配置文件中可以看到使用了DistCache使用用的缓存策略是MemcachedCacheProvider,如果不需要分布式缓存我们利用WebCache可以继承CacheProvider重写一个缓存Provider注入到Discache中。
keySuffix代表 Key后缀 即给你的Key加后缀,以方便使用MemcachedCacheProvider的不同客户端区分各自的缓存数据
DefaultExpireTime 缓存数据有效期 毫秒为单位,虽然匹配了但默认不使用的,必须通过指定的方法实现
看一下测试代码

View Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MemcachedProviders.Cache;
using MemcachedProviders.Session;
using System.Threading;

namespace DemoTest
{
class Program
{
static void Main(string[] args)
{
//服务端缓存通行证数据10秒
DistCache.DefaultExpireTime = 10000;

//假如用户登录成功
User user = new User();
user.Name = "小怜香";
user.Age = 20;
user.Birthday = Convert.ToDateTime("1991-1-1");
user.Sex = false;
user.IsLoginSucess = true;

//生成通过证
string passCode = Guid.NewGuid().ToString();
Console.WriteLine("首次缓存数据-------------------8秒后读取");
//缓存用户信息(10秒) ,并将PassCode保存到Cookie中
DistCache.Add(passCode, user, true);

//如何让用户在操作期间 缓存不过期呢?
Thread.Sleep(8000);
Console.WriteLine(DistCache.Get(passCode).Name);

Console.WriteLine("重新缓存--------------9秒后再读取");
//从Cookie中获取PassCode ,通过PassCode从缓存中读取用户数据后重新缓存
//User newUser = DistCache.Get(passCode);
//DistCache.Remove(passCode);
DistCache.Add(passCode, user, DistCache.DefaultExpireTime); //Set方式重新缓存 (MemcacheProvider封装不是很好)用第一方式设置还是Add

Thread.Sleep(8000); //如果上面的代码没有刷新缓存至10秒 那下面的代码肯定会超时
if(DistCache.Get(passCode)!=null)
Console.WriteLine(DistCache.Get(passCode).Name);
else
Console.WriteLine("因为没有操作,缓存未刷新,导致数据过期");

Console.WriteLine("3秒后再次读取数据----------");
Thread.Sleep(3000);
if (DistCache.Get(passCode) != null)
Console.WriteLine(DistCache.Get(passCode).Name);
else
Console.WriteLine("因为没有操作,缓存未刷新,导致数据过期");

//以上服务端的过程 即服务端用户登录数据先过期,PassCode取不到用户登录数据,可以判定为登录过期
//还有一种情况客户端Cookie先过期 导致无法取到PassCode,因此服务端就无法获取用户数据,可以判定为登录过期。
Console.ReadLine();
}

[Serializable]
public class User
{
public string Name { set; get; }
public bool Sex { set; get; }
public int Age { set; get; }
public DateTime Birthday { set; get; }
public bool IsLoginSucess { set; get; }
}

}
}
结果
可以看到通过DistCache.Add(“Name2”, “小凤仙我不认识”, true); 存储的数据在3秒后再取时已经过期清除了。
另外我们在使用MemcachedCacheProvider方法存储对象时,该对象一定要标记为可序列化Serializable。当然你也 可以不使用MemcachedCacheProvider,自己先将对象先序列化再存储,取出来的时候自己在反序列化。(没有压缩功能)
既然MemcachedCacheProvider有缓存过期功能,是不是可以利用这一点+Cookie实现一个自定义Session功能呢
当用户登录了系统,这个时候生成一个GUID的PassCode通过行证做为缓存Key,并将用户登录信息保存到缓存中,同时PassCode 根据请求输出到客户端Cookie中保存。此时Cookie也设置了有效期,服务端缓存也设置了有效期,因此可通过这两种情况来验证用户是否登录过期。
当Cookie中PassCode未过期,服务端可以从Cookie中获取PassCode 去缓存中读取用户登录数据,如果用户登录数据有效,刷重新设置用户数据有效期,并充许执行相关操作。当用户数据过期时则认为用户登录过期需重新登录。
当Cookie中PassCode过期,则服务端无法读取PassCode,则认为用户登录过期。 看一下服务端示例代码,因为客户端只要判断一下PassCode是否存在就可以了。
View Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MemcachedProviders.Cache;
using MemcachedProviders.Session;
using System.Threading;

namespace DemoTest
{
class Program
{
static void Main(string[] args)
{
//服务端缓存通行证数据10秒
DistCache.DefaultExpireTime = 10000;

//假如用户登录成功
User user = new User();
user.Name = "小怜香";
user.Age = 20;
user.Birthday = Convert.ToDateTime("1991-1-1");
user.Sex = false;
user.IsLoginSucess = true;

//生成通过证
string passCode = Guid.NewGuid().ToString();
Console.WriteLine("首次缓存数据-------------------9秒后读取");
//缓存用户信息(10秒) ,并将PassCode保存到Cookie中
DistCache.Add(passCode, user, true);

//如何让用户在操作期间 缓存不过期呢?当用户每一次操作或访问就刷新一下服务端的缓存
Thread.Sleep(9000);
Console.WriteLine(DistCache.Get(passCode).Name);

Console.WriteLine("取出数据重新缓存--------------9秒后再读取");
//从Cookie中获取PassCode ,通过PassCode从缓存中读取用户数据后重新缓存
User newUser = DistCache.Get(passCode);
DistCache.Remove(passCode);
DistCache.Add(passCode, newUser, true);

Thread.Sleep(9000); //如果上面的代码没有刷新缓存至10秒 那下面的代码肯定会超时
if(DistCache.Get(passCode)!=null)
Console.WriteLine(DistCache.Get(passCode).Name);
else
Console.WriteLine("因为没有操作,缓存未刷新,导致数据过期");

Console.WriteLine("持续11秒没有刷新操作,再次读取数据----------");
Thread.Sleep(2000);
if (DistCache.Get(passCode) != null)
Console.WriteLine(DistCache.Get(passCode).Name);
else
Console.WriteLine("因为没有操作,缓存未刷新,导致数据过期");

//以上服务端的过程 即服务端用户登录数据先过期,PassCode取不到用户登录数据,可以判定为登录过期
//还有一种情况客户端Cookie先过期 导致无法取到PassCode,因此服务端就无法获取用户数据,可以判定为登录过期。
Console.ReadLine();
}

[Serializable]
public class User
{
public string Name { set; get; }
public bool Sex { set; get; }
public int Age { set; get; }
public DateTime Birthday { set; get; }
public bool IsLoginSucess { set; get; }
}

}
}

上面处理实现Session会话(不是ASP.NET的Session)的优点:
1.解决跨域,跨窗口的会话认证(针对ASP.NET的Session不跨窗口),解决A站点转B站点或重新打开浏览器访问的Session认证问题。
2.分布式缓存Session或PassCode,实现认证服务器负载均横。
3.效率要高于数据库。
缺点:
1.一台缓存服务器Down掉后,该服务器上所有Session或PassCode丢失,导致用户访问过期。
2.受分布式缓存读取数据命中率影响,一旦未命中则导致用户访问过期。
3.分布式缓存效率要低于传统缓存。
4.Cookie欺骗,伪造在有效期内的PassCode去骗取服务器认证。(这个问题无法避免,即使你用ASP.NET Session)
缺点的第1,2两条,我们可以通过重写MemcachedCacheProvider,增加写入数据库功能,同时这样做了也会因反复读写更新数据库而导致性能降低。当用户Cookie的PassCode还在有效期内,通过PassCode去找查缓存服务器中的用户登录信息,如果未查到(数据已过期或未命中亦或服务器Down掉),则去数据库中查找并确认是否已过期,如果未过期则读取并写入缓存中(针对服务器Down掉和丢失两种情况)。第3条是没有办法,实现分布式总是损失部分性能的。至于第4条可以通过加密解密的方式处理。 个人觉得在不是特别的情况下没必要增加数据库备份功能,因为你要反复读写更新数据库中这个缓存的有效期,以及过期后删除等操作。如果一定要保证Session不丢失,那建议还是直接保存数据库中,并在每次操作时更新数据的有效期。
Cookies跨域:正常的cookie只能在一个应用中共享,即一个cookie只能由创建它的应用获得。关于如何跨域读取Cookie方法很多,这里不介绍了。
URL后缀跨域:可将PassCode或SessionID通过URL后缀转到其它站点。(相比Cookie就暴露了,虽然可以加密弥补但是还是无法防止别人借用)
下面来看一下Memcahed实现Asp.net的Session缓存功能,这个是即实现了写入缓存中,又写入数据库中并实时更新有效期。
二、SesstionStateProvider
Session是由应用服务器维持的一个服务器端的存储空间,用户在连接服务器时,会由服务器生成一个唯一的SessionID,用该SessionID 为标识符来存取服务器端的Session存储空间。而SessionID这一数据则是保存到客户端,用Cookie保存的,用户提交页面时,会将这一 SessionID提交到服务器端,来存取Session数据。这一过程,是不用开发人员干预的。所以一旦客户端禁用Cookie,那么Session可能会失效,也可能会传Url传递即xxx.aspx?SessionID=xxxxxxxxx的形式。

这么看来,Session的原理其实和我们前面的设计的Cookie+PassCode+缓存功能是一致的。即打开浏览器请求后会产生一个Session会话,该会话有一个唯一标识SessionID(相当于PassCode),当用户登录后成功后,将用户登录信息缓存至对应用SessionID的Session存储中(相当于缓存)。当响应返回客户端后,SessionID被保存在Cookie中。当下一次请求回来时,服务端会根据Cookie中的SessionID从Session存储中取出对应的Session,绑到上下文中。然后从上下文中的Session获取相应的用户信息。
既然原理相同,那不可避免的都会碰到同样类似的问题:
1.Session丢失
2.Cookie或URL后缀参数SessionID一样可以欺骗
3.跨站点,跨浏览器,跨窗口等等访问一样无法通行。
有人说利用Cookie+Session ,即Session存储的用户信息及其它可能更多的数据保存到Cookie中。
1.在Cookie有效期内Session丢失了,即从Cookie中取回所有数据绑回HttpContext的Session中。
2.跨站点,跨浏览器,跨窗口时,在Cookie有效期内将Session绑至其它站点。
3.加密解决欺骗问题
这个时候Cookie除了传递认证标识,还兼具了弥补Session丢失和跨域的一个存储载体了,即取代了Session备份数据库的功能。那谁主导数据有效期控制呢?Session本身过期了,而Cookie没有过期,这样导致了客户端一直有权限访问站点。并且大量的信息被保存在客户端中,虽然加密了,但仍可借用骗取通行。因此不提倡这种做法,Cookie本身是轻量型的,最好只担当SessionID或PassCode等标识或一些客户端自定义性的数据的保存和传递工作。所归根到底,关于会话凭据及会话信息的备份工作是由谁来做?无疑这一块还是交给数据库来做最放心了,但实时的验证工作还是交给缓存或Session存储本身来处理。

当然你可以选择使用Cookie+asp.net Session+SQL备份兼跨域,或直接使用Cookie+PassCode+分布式缓存跨域+SQL备份的方案。
甚至也可以两者接合或 Cookie+asp.net Session+分布式缓存跨域+SQL备份。终于扯到了下面的内容了:
Asp.net 本身提供了sessionState的策略注入方案,看一下web.config的配置文件

View Code




通过继承SessionStateStoreProviderBase重写Provider可以实现对Session的进一步处理。即HttpApplication管线进入HttpHandler的时候,SessionStateStoreProviderBase会重新创建Session绑定到HttpContext中,并根据SessionID从对应的存储体读取数据初始化这个Session,当HttpHandler结束请求时,又会调用SessionStateStoreProviderBase将从HttpContext中读取新进Session的数据保存到对应的存储体(字典集合,缓存,数据库,分布式缓存等),并且清除掉过期的数据,至此Session清除,整个过程是循环往复的,也就是说Session只存在HttpHandler请求期间。
MemcachedProviders/SessionStateProvider在HttpHandler处理请求结束前,从HttpContext读取Session并写入或更新到Memcahed缓存服务器中,同时清除掉Memcahed中过期的数据,同时也触发了数据库相应的操作(更新缓存有效期,删除过期数据)。当下次请求至HttpHandler阶段,Memcached又根SessionID读取缓存中的Session绑回到HttpContext中。所以我们从HttpContext读取的Session,都是从Memcahed缓存中读取的。
可以看到providers的节点中MemcachedSessionProvider的属性connectionStringName=”SqlSessionServices” 指向了一个数据库连接。



MemcachedProvider提供该数据库的脚本,一张表tblSessions和相关操作存储过程
proc_CleanExpiredData是清除过期数据的存储过程。由于过期的数据或者直接关闭浏览器导致Session没有清除,而MemcachedSessionProvider也没有自动清除这些Session,所以只能通过调用此存储过程定期清除掉一些过期的会话。我们也可以通过SQL代理实现这一功能。
看一下Web访问之后的数据表结果
当同一个Session会话持续不断,Expires和LockDate会不断的刷新,以防止过期。Timeout为默认或设置的过期时间(分钟)。
规则是这样,当存储的数据过了有效期后,当有访问这些数据发生时,则MemcahedSesstionProvider会从表里清除这条对应的SessionID记录。同时系统弹出登录过期,要求用户重新登录,这个时候只要用户不是关闭浏览器登录进来的,还是会用之前的SessionID再写入tblSessions表中。关闭浏览器IE会自动清除Cookie上的SessionID,所以这个时候再打开浏览器就新产生了一个SessionID。通过SessionStateProvider在Memcahed中保存了Session,同时也会实时更新至数据库。
既然Session都写入数据库了,完全可以在跨域时或者丢失从数据库里读取在有效期内的Session,而没必要通过分布式缓存读取。当不是跨域时访问时,又可以直接利用Session存储而没有必要通过Memcached读取。(以上是误解)事实是这样的吗? 通过对SessionStateProvider源码分析,SessionStateProvider继承重写了SessionStateStoreProviderBase,使用Memcached已经替代了原先Session的存储机制,而数据库则是充当Memcahed的备份。SessionStateProvider并没有画蛇添足,并且确实考虑了所有情况,确实是个很完美的方案。
相反另一个问题就来了,为什么说Session会丢失,Session是在什么情况下丢失的?高负载,高并发?我没有碰到这些情况,假如这些情况容易发生,相信放到Memcached中也会碰到这种问题,如果是这样那在不注重效率的情况下,看来只有直接利数据库存储Session是最安全的了。web.config SessionState默认支持存入数据库存中的,只要配置一下就可以了。


另外微软也提供了一种会话分布式缓存解决方案:
Windows Server AppFabric 为 ASP.NET Web 应用程序提供了自定义的会话状态提供程序,Web 应用程序可以分散缓存群集中的会话对象,从而提供可伸缩性。鉴于 AppFabric 缓存功能的性质,您放入会话中的对象必须可序列化。有时间在写一篇关于Windows Server AppFabric

[转载]Asp.net MVC 3 Framework: SportsStore源码

mikel阅读(1538)

[转载]Asp.net MVC 3 Framework: SportsStore源码 – 叶华斌 – 博客园.

   由于项目的要求,开始抽时间看些ASP.NET mvc方面的资料,主要参考的书籍是Pro ASP.NET MVC 3 Frameworkpdf下载) 。跟着里面的讲解将SportsStore做了一遍以加深理解。有需要代码的童鞋可以在下面下载以供学习参考。

我使用的开发工具是visual studio 2012+SQL server 2008

 

项目组织结构:

 

SportsStore 前台首页:

SportsStore 分类显示:

SportsStore 购物车功能:

SportsStore 提交订单:

 

 

后台登录页面:

 

SportsStore 产品管理:

 

点击下载》》