第一个版本:

[code=’c#’]
public static byte[] Serialize(object o)
{
MemoryStream ms = new MemoryStream();
GZipStream compressedStream =
new GZipStream(ms, CompressionMode.Compress, true);
BinaryFormatter b = new BinaryFormatter();
b.Serialize(compressedStream, o);

byte[] buffer = new byte[ms.Length];
ms.Read(buffer, 0, (int)ms.Length);

compressedStream.Close();
ms.Close();
return buffer;
}
[/code]

第一个版本的问题是,buffer的长度不正确,同样的对象如果序列化到文件再读文件出来,长度总是和MemoryStream差那么十几个byte。甚是郁闷。

第二个版本:

[code=’c#’]
public static byte[] Serialize(object o)
{
MemoryStream ms = new MemoryStream();
GZipStream compressedStream =
new GZipStream(ms, CompressionMode.Compress, true);
BinaryFormatter b = new BinaryFormatter();
b.Serialize(compressedStream, o);
compressedStream.Flush();
compressedStream.Close();

byte[] buffer = new byte[ms.Length];
ms.Read(buffer, 0, (int)ms.Length);

ms.Close();
return buffer;
}
[/code]

这样MemoryStream读取出来的byte组至少在长度上正确了。但是带来一个问题,读出来的byte数组里全是0,没读到数据。Read返回也是0。

第三个版本:

[code=’c#’]
public static byte[] Serialize(object o)
{
MemoryStream ms = new MemoryStream();
GZipStream compressedStream =
new GZipStream(ms, CompressionMode.Compress, true);
BinaryFormatter b = new BinaryFormatter();
b.Serialize(compressedStream, o);
compressedStream.Flush();
compressedStream.Close();

ms.Position = 0;
byte[] buffer = new byte[ms.Length];
ms.Read(buffer, 0, (int)ms.Length);

ms.Close();
return buffer;
}
[/code]

这样终于正常了。这个事情告诉我们一下几个事实:

  • 凡是与Stream打交道时遇到的奇奇怪怪的问题多半与缓存有关,试试Flush()会有奇效。“自从缓存发明之后,计算机科学里就再也没有牛B的概念产生了。”
  • 使用MemoryStream时要打破思维定势,记得读取MemoryStream时要首先设置Position到合适的位置再读。
  • 没有做不到的事情。

.NET远程处理层(Remoting)是一个比较古老的概念吧,Framework 1.0就存在。远程处理是两个对象跨应用程序域进行通信的行为,两个对象可以在同一计算机,也可以不在;在同一计算机也可以存在于不同的进程和应用程序域(AppDomain)

优点:

  • 便于我们进行分布式开发
  • TCP通道的Remoting速度非常快
  • 虽然是远程的,但是非常接近于本地调用对象
  • 可以做到保持对象的状态
  • 没有应用程序限制,可以是控制台,Windows Form,IIS,Windows服务承载远程对象

缺点:

  • 属于非标准的应用,因此有平台限制
  • 脱离IIS的话需要有自己的安全机制

公共DLL中定义的消息类:(定义在公共DLL中,供客户端和服务器端引用,避免循环编译依赖)
[code=’c#’]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RemotingLibrary
{
public class RemoteMessageObject : MarshalByRefObject
{
private int m_MessageCount = 0;

public RemoteMessageObject()
{
Console.WriteLine(“Constructing RemoteMessageObject.”);
}

public void DisplayMessage(string message)
{
m_MessageCount++;
Console.WriteLine(“[{0}]Message is: {1}”, m_MessageCount.ToString(), message);
}

public string ReturnMessage()
{
return “Remoting Server Message Count: ” + m_MessageCount.ToString();
}
}
}
[/code]

服务器端:
[code=’c#’]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using RemotingLibrary;

namespace CLRRemoting
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“========= Server Started =======”);

HttpChannel c = new HttpChannel(30001);
ChannelServices.RegisterChannel(c, false);

RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemoteMessageObject),
“RemoteMessageObject.soap”,
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
}
[/code]

客户端:
[code=’c#’]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using RemotingLibrary;

namespace RemotingClient
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“========= Client Started =======”);
HttpChannel c = new HttpChannel();
ChannelServices.RegisterChannel(c, true);

object remoteObject = Activator.GetObject(
typeof(RemoteMessageObject),
“http://itsserver2:30001/RemoteMessageObject.soap”);
RemoteMessageObject sample = remoteObject as RemoteMessageObject;
if (sample != null)
{
for (int i = 0; i < 10; i++) { sample.DisplayMessage("Client Message" + i.ToString()); Console.WriteLine(sample.ReturnMessage()); } Console.ReadLine(); } } } } [/code] 代码简单,就不介绍了。我把Server部署在局域网远程的服务器上。 远程对象被定义为WKO,只有在第一次访问的时候才构造。5分钟没有访问对象时,远程对象会被垃圾收集器收集,再次访问的时候,编号和构造函数都重新开始。 我的想法,我最近想写一个比较大的东西,最近在仔细研究WCF,估计Remoting将是我的并行程序的核心。

这是《编程之美——微软技术面试心得》里的第一个题,后来我推荐到微软亚洲研究院的一个实习生面试的时候做的正是这个题。
其实这个题难度适中,现在机器都是多核CPU,照书里的答案去做早就不行了。
下面我给出一个适用于双核CPU上在两个核都形成正弦曲线的做法吧。

[code=’c#’]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace CPU
{
public class Sin1ThreadParam
{
public int AffinityMask = 0x00000000;

public Sin1ThreadParam(int AffinityMask)
{
this.AffinityMask = AffinityMask;
}
}

class Sin1
{
public void DoWork(object o)
{
Sin1ThreadParam param = o as Sin1ThreadParam;
if (param != null)
{
// 设置线程CPU亲和性
IntPtr nHD = new IntPtr(Thread.CurrentThread.ManagedThreadId);
Win32.SetThreadAffinityMask(nHD, param.AffinityMask);

// 采样率
const double SPLIT = 0.01;
// 采样总数
const int COUNT = 200;
const double PI = 3.14159265;
// 扫描速度,控制曲线波长
const int INTERVAL = 300;

// 忙循环时间长度
int[] busySpan = new int[COUNT];
// 闲循环时间长度
int[] idleSpan = new int[COUNT];
int half = INTERVAL / 2;
// X
double radian = 0.0;

// 构成一个具有COUNT个采样点的忙、闲循环
for (int i = 0; i < COUNT; i++) { busySpan[i] = (int)((half + (Math.Sin(PI * radian) * half))); idleSpan[i] = INTERVAL - busySpan[i]; radian += SPLIT; } int startTime = 0; int j = 0; // 按照忙闲循环比率跑死循环和Sleep while (true) { j = j % COUNT; startTime = Environment.TickCount; while ((Environment.TickCount - startTime) <= busySpan[j]) { } Thread.Sleep(idleSpan[j]); j++; } } } } } [/code] 调用的时候用两个线程去跑,分别用Windows API的SetThreadAffinityMask指定不同的CPU亲和性。 [code='c#'] using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; using System.Runtime.InteropServices; namespace CPU { class Win32 { [DllImport("Kernel32.dll", CharSet = CharSet.Auto)] public static extern int SetThreadAffinityMask(IntPtr hWnd, int nIndex); } class Program { static void Main(string[] args) { Sin1 sin11 = new Sin1(); Thread t1 = new Thread(new ParameterizedThreadStart(sin11.DoWork)); Sin1ThreadParam p1 = new Sin1ThreadParam(0); Thread t2 = new Thread(new ParameterizedThreadStart(sin11.DoWork)); Sin1ThreadParam p2 = new Sin1ThreadParam(1); t1.Start(p1); t2.Start(p2); } } } [/code] 结果是这样子的: [singlepic=15904]

C#中CLR提供了一个线程池(Thread Pool)的实现,叫做ThreadPool。CLR线程池的主要好处是:

  • 线程池减少了线程创建、开始和停止的次数,提高了效率。
  • 使用线程池可以使得程序员不必考虑多线程编程,而将注意力集中在业务逻辑上。

也有缺点:

  • 线程池中的线程总是后台线程(主线程退出,则所有后台线程也停止,程序结束),而且不能设置优先级(总是ThreadPriority.Normal)。
  • 不能使用一个标识来识别一个特定的线程。

[code=’c#’]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ThreadPoolTest
{
// 执行工作的类
class Worker
{
// 执行工作的函数
public void DoWork()
{
int threadCode = Thread.CurrentThread.GetHashCode();
Console.WriteLine(
string.Format(“[Thread {0}]: Work Starts.”, threadCode));
Thread.Sleep(1000);
Console.WriteLine(
string.Format(“[Thread {0}]: Work Ends.”, threadCode));
}
}

class Program
{
static void Main(string[] args)
{
// 主线程打印信息
Console.WriteLine(
“Main Thread: ” + Thread.CurrentThread.GetHashCode());
// 构造工作函数
Worker worker = new Worker();
// 设置工作入口
WaitCallback workItem = new WaitCallback(DoCompute);

// 向CLR线程池提交20个工作
for (int i = 0; i < 20; i++) { ThreadPool.QueueUserWorkItem(workItem, worker); } Console.WriteLine("All Works Queued."); Console.ReadLine(); } // 线程池入口委托的实现 static void DoCompute(object state) { Worker worker = state as Worker; if (worker != null) { // 执行工作 worker.DoWork(); } } } } [/code]

C#线程间参数传递办法挺多,这里用了一种方式。
传入参数使用ParameterizedThreadStart,传出使用委托,委托也使用ParameterizedThreadStart方式传入线程。

[code=’c#’]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication
{
// 用于保存要传入Thread的参数
class ValueParams
{
public int IntValue;
public string StringValue;
public ReportResultDelegate ReportResult;
public ValueParams(int intValue, string stringValue, ReportResultDelegate ReportResult)
{
this.IntValue = intValue;
this.StringValue = stringValue;
this.ReportResult = ReportResult;
}
}

// 用于向主线程报告结果
public delegate void ReportResultDelegate(string result);

class Program
{
static void Main(string[] args)
{
ValueParams param = new ValueParams(10, “Test Thread”, ReportResult);
Thread thread = new Thread(new ParameterizedThreadStart(DoPrintValues));
thread.Start(param);
}

// 处理工作线程回报的结果
private static void ReportResult(string result)
{
Console.WriteLine(result);
}

// 必须接受object型参数以符合ParameterizedThreadStart委托的要求
public static void DoPrintValues(object param)
{
if (param is ValueParams)
{
ValueParams thisParam = (ValueParams)param;
for (int i = 0; i < thisParam.IntValue; i++) { thisParam.ReportResult(thisParam.StringValue); } } } } } [/code]

数码设备在生成JPG照片的时候会保存Exif信息,这些信息包含照片拍摄的一些参数,包括从拍摄时间到拍摄设备、曝光参数等等很多信息。
C#中从文件打开图像文件的时候,可以取得PropertyItem数组,里面记录了Exif参数的Dictionary。

[code=’c#’]
Image img = Image.FromFile(filePath);
PropertyItem[] pt = img.PropertyItems;
[/code]

PropertyItem类包含四个属性:

Type的定义:

  • 1 指定 Value 为字节数组。
  • 2 指定 Value 为空终止 ASCII 字符串。如果将类型数据成员设置为 ASCII 类型,则应该将 Len 属性设置为包括空终止的字符串长度。例如,字符串“Hello”的长度为 6。
  • 3 指定 Value 为无符号的短(16 位)整型数组。
  • 4 指定 Value 为无符号的长(32 位)整型数组。
  • 5 指定 Value 数据成员为无符号的长整型对数组。每一对都表示一个分数;第一个整数是分子,第二个整数是分母。
  • 6 指定 Value 为可以包含任何数据类型的值的字节数组。
  • 7 指定 Value 为有符号的长(32 位)整型数组。
  • 10 指定 Value 为有符号的长整型对数组。每一对都表示一个分数;第一个整数是分子,第二个整数是分母。

不同的类型都需要不同的解析方式。后面我给出两个附件,包含这些解析的函数的一个类。
其中cs文件是我整理过的,直接可以用,zip文件是参考代码。

[code=’c#’]
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace JPGCompact
{
class TestExif
{
public void DoTest(string filePath)
{
Image img = Image.FromFile(filePath);
PropertyItem[] pt = img.PropertyItems;

for (int i = 0; i < pt.Length; i++) { PropertyItem p = pt[i]; // 设备制造商 if (pt[i].Id == 0x010F) { //tbExifCamera.Text = ascii.GetString(pt[i].Value); } // 设备型号 if (pt[i].Id == 0x0110) { tbExifCamera.Text = ExifMeta.FormatValue(p); } // 拍照时间 if (pt[i].Id == 0x0132) { tbExifTime.Text = ExifMeta.FormatValue(p); } // 光圈 if (pt[i].Id == 0x9202) { tbExifAperture.Text = GetAperture(ExifMeta.FormatValue(p)); } // ISO if (pt[i].Id == 0x8827) { tbExifISO.Text = ExifMeta.FormatValue(p); } // 模式 if (pt[i].Id == 0x8822) { tbExifMode.Text = GetProgram(ExifMeta.FormatValue(p)); } } } } } [/code]

程序员懒吧?呵呵。
只要能找到一个好用的,我是绝对不会自己写一个出来的。因为WordPress的图库插件NextGEN Gallery破算法不能压缩太大的文件,PHP执行内存不足,同样的照片跑在同一环境下的Gallery2处理起来很轻松,还能一下执行N张呢…
没办法只好先把照片压缩下在传上去了。找个好几个批量压缩软件都不如意,没办法只好自己写一个了。

界面是这样子的:

[singlepic=15854,700,510]

核心算法其实非常简单:

[code=’c#’]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;

namespace JPGCompact
{
public partial class MainForm : Form
{
///

/// 保存JPG时用
///

/// 文件类型 /// 得到指定mimeType的ImageCodecInfo
private static ImageCodecInfo GetCodecInfo(string mimeType)
{
ImageCodecInfo[] CodecInfo = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo ici in CodecInfo)
{
if (ici.MimeType == mimeType) return ici;
}
return null;
}

///

/// 保存为JPEG格式,支持压缩质量选项
///

/// 原始位图 /// 新文件地址 /// 压缩质量,越大越好,文件也越大(0-100) /// 成功标志
public static bool SaveAsJPEG(Bitmap bmp, string FileName, int Qty)
{
try
{
EncoderParameter p;
EncoderParameters ps;
ps = new EncoderParameters(1);
p = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, Qty);
ps.Param[0] = p;
bmp.Save(FileName, GetCodecInfo(“image/jpeg”), ps);
return true;
}
catch
{
return false;
}
}

private bool CompressPicture(string sourcePath, string targetPath)
{
try
{
// 大小比率
double sizeRate = double.Parse(cbSizeRate.Text) / 100;
// 品质比率
int qualityRate = int.Parse(cbQualityRate.Text);

Image sourceImage = Image.FromFile(sourcePath);
// 调整图片大小
Bitmap bmp = new Bitmap(
sourceImage,
new Size(
(int)(sourceImage.Width * sizeRate),
(int)(sourceImage.Height * sizeRate)));
// 压缩图片
SaveAsJPEG(bmp, targetPath, qualityRate);

GC.Collect();

return true;
}
catch
{
return false;
}
}
}
}
[/code]

比较有技术含量的是那个Exif信息的读取,一会儿我写一篇说说那个怎么做。

下载地址:http://download.nocoo.us/Download/Archive/JPGCompact.rar