這是一種在允許Xml序列化的同時大量減少內存開銷的技術。
更新:orignal內聯鏈表思路對於1和2條目比使用count構造的標準列表更高效,但對於零個,一個和兩個情況使用固定大小的optionals更加高效。
限制性條款:
這個的前提是你知道你真的需要刮鬍子的內存,這樣 (因爲你沒有做任何編碼還)這很可能是一個大規模過早 優化。
此外,這種設計是可選字段爲非常難得。
我使用double作爲「佔位符」,無論哪種格式最好允許您表示應使用的精度/單位。
public class WayPoint
{
// consumes IntPtr.Size fixed cost
private IOptional optional = OptionalNone.Default;
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Vertical
{
get { return optional.Get<double>("Vertical") ?? 0.0; }
set { optional = optional.Set<double>("Vertical", value); }
}
[XmlIgnore] // need this pair for every value type
public bool VerticalSpecified
{
get { return optional.Get<double>("Vertical").HasValue; }
}
public void ClearVertical()
{
optional = optional.Clear<double>("Vertical");
}
public string Description // setting to null clears it
{
get { return optional.GetRef<string>("Description"); }
set { optional = optional.SetRef<string>("Description", value); }
}
// Horizontal, Position, DilutionOfPrecision etc.
}
真正的繁重都是在這裏完成:
internal interface IOptional
{
T? Get<T>(string id) where T : struct;
T GetRef<T>(string id) where T : class;
IOptional Set<T>(string id, T value);
IOptional Clear(string id);
}
internal sealed class OptionalNone : IOptional
{
public static readonly OptionalNone Default = new OptionalNone();
public T? Get<T>(string id) where T : struct
{
return null;
}
public T GetRef<T>(string id) where T : class
{
return null;
}
public IOptional Set<T>(string id, T value)
{
if (value == null)
return Clear(id);
return new OptionalWithOne<T>(id, value);
}
public IOptional Clear(string id)
{
return this; // no effect
}
}
固定大小的人變得更有趣寫的,沒有一點寫這些的結構,因爲它們將被裝箱放置內WayPoint類中的可選字段。
internal sealed class OptionalWithOne<X> : IOptional
{
private string id1;
private X value1;
public OptionalWithOne(string id, X value)
{
this.id1 = id;
this.value1 = value;
}
public T? Get<T>(string id) where T : struct
{
if (string.Equals(id, this.id1))
return (T)(object)this.value1;
return null;
}
public T GetRef<T>(string id) where T : class
{
if (string.Equals(id, this.id1))
return (T)(object)this.value1;
return null;
}
public IOptional Set<T>(string id, T value)
{
if (string.Equals(id, this.id1))
{
if (value == null)
return OptionalNone.Default;
this.value1 = (X)(object)value;
return this;
}
else
{
if (value == null)
return this;
return new OptionalWithTwo<X,T>(this.id1, this.value1, id, value);
}
}
public IOptional Clear(string id)
{
if (string.Equals(id, this.id1))
return OptionalNone.Default;
return this; // no effect
}
}
然後兩(你可以儘可能你想擴展這個想法,但你可以看到代碼很快變得不愉快。
internal sealed class OptionalWithTwo<X,Y> : IOptional
{
private string id1;
private X value1;
private string id2;
private Y value2;
public OptionalWithTwo(
string id1, X value1,
string id2, Y value2)
{
this.id1 = id1;
this.value1 = value1;
this.id2 = id2;
this.value2 = value2;
}
public T? Get<T>(string id) where T : struct
{
if (string.Equals(id, this.id1))
return (T)(object)this.value1;
if (string.Equals(id, this.id2))
return (T)(object)this.value2;
return null;
}
public T GetRef<T>(string id) where T : class
{
if (string.Equals(id, this.id1))
return (T)(object)this.value1;
if (string.Equals(id, this.id2))
return (T)(object)this.value2;
return null;
}
public IOptional Set<T>(string id, T value)
{
if (string.Equals(id, this.id1))
{
if (value == null)
return Clear(id);
this.value1 = (X)(object)value;
return this;
}
else if (string.Equals(id, this.id2))
{
if (value == null)
return Clear(id);
this.value2 = (Y)(object)value;
return this;
}
else
{
if (value == null)
return this;
return new OptionalWithMany(
this.id1, this.value1,
this.id2, this.value2,
id, value);
}
}
public IOptional Clear(string id)
{
if (string.Equals(id, this.id1))
return new OptionalWithOne<Y>(this.id2, this.value2);
if (string.Equals(id, this.id2))
return new OptionalWithOne<X>(this.id1, this.value1);
return this; // no effect
}
}
之前最後與相對低效
internal sealed class OptionalWithMany : IOptional
{
private List<string> ids = new List<string>();
// this boxes, if you had a restricted set of data types
// you could do a per type list and map between them
// it is assumed that this is sufficiently uncommon that you don't care
private List<object> values = new List<object>();
public OptionalWithMany(
string id1, object value1,
string id2, object value2,
string id3, object value3)
{
this.ids.Add(id1);
this.values.Add(value1);
this.ids.Add(id2);
this.values.Add(value2);
this.ids.Add(id3);
this.values.Add(value3);
}
public T? Get<T>(string id) where T : struct
{
for (int i= 0; i < this.values.Count;i++)
{
if (string.Equals(id, this.ids[i]))
return (T)this.values[i];
}
return null;
}
public T GetRef<T>(string id) where T : class
{
for (int i= 0; i < this.values.Count;i++)
{
if (string.Equals(id, this.ids[i]))
return (T)this.values[i];
}
return null;
}
public IOptional Set<T>(string id, T value)
{
for (int i= 0; i < this.values.Count;i++)
{
if (string.Equals(id, this.ids[i]))
{
if (value == null)
return Clear(id);
this.values[i] = value;
return this;
}
}
if (value != null)
{
this.ids.Add(id);
this.values.Add(value);
}
return this;
}
public IOptional Clear(string id)
{
for (int i= 0; i < this.values.Count;i++)
{
if (string.Equals(id, this.ids[i]))
{
this.ids.RemoveAt(i);
this.values.RemoveAt(i);
return ShrinkIfNeeded();
}
}
return this; // no effect
}
private IOptional ShrinkIfNeeded()
{
if (this.ids.Count == 2)
{
//return new OptionalWithTwo<X,Y>(
// this.ids[0], this.values[0],
// this.ids[1], this.values[1]);
return (IOptional)
typeof(OptionalWithTwo<,>).MakeGenericType(
// this is a bit risky.
// your value types may not use inhertence
this.values[0].GetType(),
this.values[1].GetType())
.GetConstructors().First().Invoke(
new object[]
{
this.ids[0], this.values[0],
this.ids[1], this.values[1]
});
}
return this;
}
}
結束
OptionalWithMany可以寫得比這更好,但它給了你想法。 有了受限制的類型支持,你可以做一個全局的Key - > value map類型如下:
internal struct Key
{
public readonly OptionalWithMany;
public readonly string Id;
// define equality and hashcode as per usual
}
然後,只需在OptionalToMany中存儲當前正在使用的Id的列表。收縮會稍微複雜一點(但從類型的角度來看更好,因爲您會掃描每個全局'堆',直到找到匹配項並使用堆的類型構造OptionalWithTwo。這將允許屬性值中的多態性。
無論內部結構如何,WayPoint類的公共表面都完全隱藏了這一切。
然後,您可以通過屬性IXmlSerializable(這將消除惱人的xxxSpecified屬性的需要)設置類,但是您想要序列化。
我在示例中使用了字符串作爲Id。
如果你真的關心大小和速度,你應該改變ID爲枚舉。考慮到打包行爲,即使你可以將所有需要的值放入一個字節中,這也不會節省你很多,但它會給你編譯時間的完整性檢查。這些字符串都是編譯時常量,所以佔用空間的旁邊(但檢查相等性較慢)。
我強烈建議你在之後只做這樣的事情,你檢查是否需要。好的一面是,這不會限制你的xml序列化,所以你可以將它塑造成你想要的任何格式。此外,「數據包」的公開表面可以保持清潔(xxxSpecified垃圾除外)。
如果你想避免xxxSpecified麻煩,你知道你有一些「帶外」的值,可以使用下面的技巧:
[DefaultValue(double.MaxValue)]
public double Vertical
{
get { return optional.Get<double>("Vertical") ?? double.MaxValue; }
set { optional = optional.Set<double>("Vertical", value); }
}
public void ClearVertical()
{
optional = optional.ClearValue<double>("Vertical");
}
但是您API的其餘部分必須能夠檢測這些特殊的價值。一般來說,我會說指定的路線更好。
如果一組特定的屬性在某些設備上變爲「始終可用」,或者在某些模式下,您應該切換到替代類,其中的屬性是簡單類。由於xml表單將是相同的,這意味着它們可以簡單方便地進行互操作,但在這些情況下的內存使用量會少得多。
如果這些羣體的數量變大,你甚至可以考慮代碼生成方案(在運行時甚至,雖然這大大增加您的支持負擔)
爲什麼實施IXmlSerializable是一個壞主意?選項結構分離的想法聽起來很吸引我。 – djna 2009-09-04 20:57:02
所以,你想減少開銷,但同時你想保存你的數據爲XML? :-) – 2009-09-04 20:58:21
請不要誤解我的意思。我並不是說實現IXmlSerializable是個壞主意。我想知道這是不是一個好主意。 一些更多的信息:我們正在談論一個運行在普通Windows Mobile PDA上的gps記錄器,並且能夠顯示瓷磚地圖。我們的RAM很短,但對於xml文件,我們有1到16 GB的SD卡。請幫我一個忙,不要質疑這個問題。 – PVitt 2009-09-04 21:24:53