[英]How to combine partially pre-cached MemoryStream with FileStream?
對於時間緊迫的媒體呈現應用程序,重要的是在用戶選擇媒體文件時立即呈現媒體文件。 這些文件駐留在一個真正龐大的目錄結構中,由數千個媒體文件組成。
顯然,在MemoryStream
緩存媒體文件是要走的路; 然而,由於文件數量龐大,完全緩存每個文件是不可行的。 相反,我的想法是預先緩存每個文件的某個緩沖區,一旦文件出現,就從該緩存播放,直到文件的其余部分從硬盤加載。
我沒有看到的是如何“連接” MemoryStream
和FileStream
以提供無縫播放體驗。 我在數據流方面不是很強大(還),我看到了幾個問題:
MemoryStream
中的當前讀取位置並將其提供給FileStream
而MemoryStream
不會讀取更多內容?MemoryStream
什么幫助,因為FileStream
作為隊列中的第二個,只有在實際使用后才開始訪問硬盤。請注意,我不需要寫訪問權限——閱讀就足以解決手頭的問題。 此外,這個問題類似於Composite Stream Wrapper 提供部分 MemoryStream 和完整的原始 Stream ,但提供的解決方案有 Windows Phone 8 的錯誤修復,不適用於我的情況。
我非常想擴大我對這一點相當有限的理解,因此非常感謝任何幫助。
我會建議類似以下解決方案:
FileStream
繼承你自己的CachableFileStream
Cache
,它使用您喜歡的數據結構(如Queue
)Preload
到內部緩存中Reload
到內部緩存中Read
行為,即使用您的緩存為了讓您了解我的想法,我會建議一些實現,如下所示:
用法可能是這樣的:
CachableFileStream cachedStream = new CachableFileStream(...)
{
PreloadSize = 8192,
ReloadSize = 4096,
};
// Force preloading data into the cache
cachedStream.Preload();
...
cachedStream.Read(buffer, 0, buffer.Length);
...
警告:下面的代碼既沒有經過正確的測試,也沒有達到理想的效果——這只是給你一個想法!
CachableFileStream
類:
using System;
using System.IO;
using System.Threading.Tasks;
/// <summary>
/// Represents a filestream with cache.
/// </summary>
public class CachableFileStream : FileStream
{
private Cache<byte> cache;
private int preloadSize;
private int reloadSize;
/// <summary>
/// Gets or sets the amount of bytes to be preloaded.
/// </summary>
public int PreloadSize
{
get
{
return this.preloadSize;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The specified preload size must not be smaller than or equal to zero.");
this.preloadSize = value;
}
}
/// <summary>
/// Gets or sets the amount of bytes to be reloaded.
/// </summary>
public int ReloadSize
{
get
{
return this.reloadSize;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), "The specified reload size must not be smaller than or equal to zero.");
this.reloadSize = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CachableFileStream"/> class with the specified path and creation mode.
/// </summary>
/// <param name="path">A relative or absolute path for the file that the current CachableFileStream object will encapsulate</param>
/// <param name="mode">A constant that determines how to open or create the file.</param>
/// <exception cref="System.ArgumentException">
/// Path is an empty string (""), contains only white space, or contains one or more invalid characters.
/// -or- path refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in an NTFS environment.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// Path refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in a non-NTFS environment.
/// </exception>
/// <exception cref="System.ArgumentNullException">
/// Path is null.
/// </exception>
/// <exception cref="System.Security.SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="System.IO.FileNotFoundException">
/// The file cannot be found, such as when mode is FileMode.Truncate or FileMode.Open, and the file specified by path does not exist.
/// The file must already exist in these modes.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error, such as specifying FileMode.CreateNew when the file specified by path already exists, occurred.-or-The stream has been closed.
/// </exception>
/// <exception cref="System.IO.DirectoryNotFoundException">
/// The specified path is invalid, such as being on an unmapped drive.
/// </exception>
/// <exception cref="System.IO.PathTooLongException">
/// The specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Mode contains an invalid value
/// </exception>
public CachableFileStream(string path, FileMode mode) : base(path, mode)
{
this.cache = new Cache<byte>();
this.cache.CacheIsRunningLow += CacheIsRunningLow;
}
/// <summary>
/// Reads a block of bytes from the stream and writes the data in a given buffer.
/// </summary>
/// <param name="array">
/// When this method returns, contains the specified byte array with the values between
/// offset and (offset + count - 1) replaced by the bytes read from the current source.
/// </param>
/// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <returns>
/// The total number of bytes read into the buffer. This might be less than the number
/// of bytes requested if that number of bytes are not currently available, or zero
/// if the end of the stream is reached.
/// </returns>
/// <exception cref="System.ArgumentNullException">
/// Array is null.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// Offset or count is negative.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The stream does not support reading.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="System.ArgumentException">
/// Offset and count describe an invalid range in array.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// Methods were called after the stream was closed.
/// </exception>
public override int Read(byte[] array, int offset, int count)
{
int readBytesFromCache;
for (readBytesFromCache = 0; readBytesFromCache < count; readBytesFromCache++)
{
if (this.cache.Size == 0)
break;
array[offset + readBytesFromCache] = this.cache.Read();
}
if (readBytesFromCache < count)
readBytesFromCache += base.Read(array, offset + readBytesFromCache, count - readBytesFromCache);
return readBytesFromCache;
}
/// <summary>
/// Preload data into the cache.
/// </summary>
public void Preload()
{
this.LoadBytesFromStreamIntoCache(this.PreloadSize);
}
/// <summary>
/// Reload data into the cache.
/// </summary>
public void Reload()
{
this.LoadBytesFromStreamIntoCache(this.ReloadSize);
}
/// <summary>
/// Loads bytes from the stream into the cache.
/// </summary>
/// <param name="count">The number of bytes to read.</param>
private void LoadBytesFromStreamIntoCache(int count)
{
byte[] buffer = new byte[count];
int readBytes = base.Read(buffer, 0, buffer.Length);
this.cache.AddRange(buffer, 0, readBytes);
}
/// <summary>
/// Represents the event handler for the CacheIsRunningLow event.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="e">Event arguments.</param>
private void CacheIsRunningLow(object sender, EventArgs e)
{
this.cache.WarnIfRunningLow = false;
new Task(() =>
{
Reload();
this.cache.WarnIfRunningLow = true;
}).Start();
}
}
Cache
類:
using System;
using System.Collections.Concurrent;
/// <summary>
/// Represents a generic cache.
/// </summary>
/// <typeparam name="T">Defines the type of the items in the cache.</typeparam>
public class Cache<T>
{
private ConcurrentQueue<T> queue;
/// <summary>
/// Is executed when the number of items within the cache run below the
/// specified warning limit and WarnIfRunningLow is set.
/// </summary>
public event EventHandler CacheIsRunningLow;
/// <summary>
/// Gets or sets a value indicating whether the CacheIsRunningLow event shall be fired or not.
/// </summary>
public bool WarnIfRunningLow
{
get;
set;
}
/// <summary>
/// Gets or sets a value that represents the lower warning limit.
/// </summary>
public int LowerWarningLimit
{
get;
set;
}
/// <summary>
/// Gets the number of items currently stored in the cache.
/// </summary>
public int Size
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="Cache{T}"/> class.
/// </summary>
public Cache()
{
this.queue = new ConcurrentQueue<T>();
this.Size = 0;
this.LowerWarningLimit = 1024;
this.WarnIfRunningLow = true;
}
/// <summary>
/// Adds an item into the cache.
/// </summary>
/// <param name="item">The item to be added to the cache.</param>
public void Add(T item)
{
this.queue.Enqueue(item);
this.Size++;
}
/// <summary>
/// Adds the items of the specified array to the end of the cache.
/// </summary>
/// <param name="items">The items to be added.</param>
public void AddRange(T[] items)
{
this.AddRange(items, 0, items.Length);
}
/// <summary>
/// Adds the specified count of items of the specified array starting
/// from offset to the end of the cache.
/// </summary>
/// <param name="items">The array that contains the items.</param>
/// <param name="offset">The offset that shall be used.</param>
/// <param name="count">The number of items that shall be added.</param>
public void AddRange(T[] items, int offset, int count)
{
for (int i = offset; i < count; i++)
this.Add(items[i]);
}
/// <summary>
/// Reads one item from the cache.
/// </summary>
/// <returns>The item that has been read from the cache.</returns>
/// <exception cref="System.InvalidOperationException">
/// The cache is empty.
/// </exception>
public T Read()
{
T item;
if (!this.queue.TryDequeue(out item))
throw new InvalidOperationException("The cache is empty.");
this.Size--;
if (this.WarnIfRunningLow &&
this.Size < this.LowerWarningLimit)
{
this.CacheIsRunningLow?.Invoke(this, EventArgs.Empty);
}
return item;
}
/// <summary>
/// Peeks the next item from cache.
/// </summary>
/// <returns>The item that has been read from the cache (without deletion).</returns>
/// <exception cref="System.InvalidOperationException">
/// The cache is empty.
/// </exception>
public T Peek()
{
T item;
if (!this.queue.TryPeek(out item))
throw new InvalidOperationException("The cache is empty.");
return item;
}
}
我希望這會有所幫助,玩得開心;-)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.