我想知道将DateTimeOffset
序列化为二进制流(使用BinaryWriter
)并再次反序列化(使用BinaryReader
)的最佳方法是什么。
要序列化DateTime
,我有:
public void WriteValue(DateTime value)
{
_writer.Write(value.ToBinary());
}
和
public DateTime ReadDateTime()
{
return DateTime.FromBinary(_reader.ReadInt64());
}
在性能和存储大小方面序列化/反序列化DateTimeOffset
的最佳方法是什么?
答案 0 :(得分:2)
根据Hans Passant的评论,我提出了以下解决方案。序列化:
public void WriteValue(DateTimeOffset value)
{
WriteValue(value.DateTime);
WriteValue((short)value.Offset.TotalMinutes);
}
反序列化:
public DateTimeOffset ReadDateTimeOffset()
{
var dateTime = ReadDateTime();
var minutes = ReadInt16();
return new DateTimeOffset(dateTime, TimeSpan.FromMinutes(minutes));
}
因此,这些方法会调用问题中概述的DateTime
的现有序列化方法。
我仍然想知道这是否是最有效的方法。调用TotalMinutes
和TimeSpan.FromMinutes
的速度有多快?
答案 1 :(得分:1)
不是说我的回答增加了很多,但我已经编写了这个小实用程序来打包和解压缩DateTimeOffset:
public static class DateTimeOffsetExtensions
{
/// <summary>
/// Packs DateTimeOffset to bytes
/// </summary>
/// <param name="dateTimeOffset"></param>
/// <returns>10 byte packed bytearray</returns>
public static byte[] GetBytes(this DateTimeOffset dateTimeOffset)
{
return BitConverter.GetBytes(dateTimeOffset.Ticks).
Concat(BitConverter.GetBytes((Int16)dateTimeOffset.Offset.TotalMinutes)).ToArray();
}
/// <summary>
/// Reads 10 bytes from a buffer and turns back to DateTimeOffset
/// </summary>
/// <param name="bytes">Buffer</param>
/// <param name="offset">Offset to read from</param>
/// <returns></returns>
public static DateTimeOffset FromBytes(byte[] bytes, int offset)
{
var ticks = BitConverter.ToInt64(bytes, offset);
var offsetMinutes = BitConverter.ToInt16(bytes, offset + 8);
return new DateTimeOffset(ticks, TimeSpan.FromMinutes(offsetMinutes));
}
}
答案 2 :(得分:-1)
序列化和反序列化Date-Time对象(任何语言)的正确方法是使用时区和夏令时独立值,例如UTC时间。在反序列化时,要么保留在UTC中(如果仅由代码使用),要么选择当前的本地时区偏移量(如果由人类读取)。这样可以避免不同时区的问题,即使值保留在一个国家/地区,也可以避免在序列化/反序列化之间夏令时发生变化时出现问题。
DateTimeOffset.Ticks
属性(64位整数)是二进制序列化器的理想选择。请注意不要使用DateTimeOffset dto = DateTimeOffset.Now;
using (var w = new BinaryWriter(...))
{
w.Write(dto.UtcTicks); // do not use dto.Ticks!
}
,因为此属性包含任何偏移。
DateTimeOffset
反序列化看起来很笨拙,因为没有简单的方法可以从UTC刻度创建using (var r = new BinaryReader(...))
{
long utcTicks = r.ReadInt64();
dto = new DateTimeOffset(utcTicks, TimeSpan.Zero).ToLocalTime();
}
。如果对象从未向人类显示,则可以跳过转换为当地时间。
DateTimeOffset dto = DateTimeOffset.Now;
using (var w = new BinaryWriter(...))
{
w.Write(dto.Ticks);
w.Write(dto.Offset.Ticks);
}
using (var r = new BinaryReader(...))
{
long ticks = r.ReadInt64();
long offsetTicks = r.ReadInt64();
dto = new DateTimeOffset(ticks, new TimeSpan(offsetTicks);
}
就存储而言,您需要8个字节= 64位。我不确定反序列化性能(未经测试),但我认为它应该尽可能快。
编辑:保留时区(偏移)信息,
package testjpanelexample;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class TestJPanelExample {
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
// TODO code application logic here
TestJPanelExample test = new TestJPanelExample("Test");
}
private final int FRAME_WIDTH = 500;
private final int FRAME_HEIGHT = 500;
private final JFrame gameWindow;
private final TestDrawingPanel myDrawingPanel;
private final TestStartGame startGame;
private final JPanel cards;
private final CardLayout cl1;
private static final String DRAWINGPANEL = "DrawingPanel";
private static final String STARTGAME = "StartGame";
//creates a JFrame and all the JPanels and adds them to a CardLayout
public TestJPanelExample(String title)
{
gameWindow = new JFrame(title);
gameWindow.setSize(FRAME_WIDTH, FRAME_HEIGHT);
cards = new JPanel(new CardLayout());
startGame = new TestStartGame(FRAME_WIDTH, FRAME_HEIGHT);
startGame.addAL(new StartButton());
myDrawingPanel = new TestDrawingPane(FRAME_WIDTH, FRAME_HEIGHT);
gameWindow.add(cards);
cards.add(startGame, STARTGAME);
cards.add(myDrawingPanel, DRAWINGPANEL);
cl1 = (CardLayout)(cards.getLayout());
gameWindow.setVisible(true);
gameWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
//starts the game running
public void startTheGame()
{
cl1.show(cards, DRAWINGPANEL);
update();
}
//switches back to the start screen
public void update()
{
cl1.show(cards, STARTGAME);
}
//an action listener to be added to the start screen
private class StartButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
startTheGame();
}
}
//The opening screen
class TestStartGame extends JPanel
{
private JPanel buttons;
public JButton start;
//creates a JPanel with a single button to start the game
public TestStartGame(int width, int height)
{
setSize(width, height);
setBackground(Color.GREEN);
start = new JButton("Start");
add(start);
}
//adds an action listener to the button
public void addAL(ActionListener al)
{
start.addActionListener(al);
}
}
//The game screen (currently just a blank blue screen)
class TestDrawingPanel extends JPanel{
//creates the drawing panel
public TestDrawingPanel(int width, int height)
{
setSize(width, height);
this.setBackground(Color.CYAN);
}
}
}