简体   繁体   English

如何将部分预缓存的 MemoryStream 与 FileStream 结合起来?

[英]How to combine partially pre-cached MemoryStream with FileStream?

For a time-critical media presentation application, it is important that media files be presented right at the instance when the user selects it.对于时间紧迫的媒体呈现应用程序,重要的是在用户选择媒体文件时立即呈现媒体文件。 Those files reside in a truly humongous directory structure, comprised of thousands of media files.这些文件驻留在一个真正庞大的目录结构中,由数千个媒体文件组成。

Clearly, caching the media files in a MemoryStream is the way to go;显然,在MemoryStream缓存媒体文件是要走的路; however, due to the sheer amount of files, it's not feasible to cache each file entirely.然而,由于文件数量庞大,完全缓存每个文件是不可行的。 Instead, my idea is to pre-cache a certain buffer of each file, and once the file is presented, play from that cache until the rest of the file is loaded from the hard disk.相反,我的想法是预先缓存每个文件的某个缓冲区,一旦文件出现,就从该缓存播放,直到文件的其余部分从硬盘加载。

What I don't see is how to “concatenate” both the MemoryStream and the FileStream so as to provide a seamless playback experience.我没有看到的是如何“连接” MemoryStreamFileStream以提供无缝播放体验。 I'm not very strong in data streams (yet), and I see several problems:我在数据流方面不是很强大(还),我看到了几个问题:

  • How does one keep track of the current read position within the MemoryStream and provide that to the FileStream without the MemoryStream reading more than that?如何跟踪MemoryStream中的当前读取位置并将其提供给FileStreamMemoryStream不会读取更多内容?
  • How does one switch from one stream to the other without having both streams either partially overlap each other or creating a “playback break”?如何在不让两个流彼此部分重叠或创建“播放中断”的情况下从一个流切换到另一个流?
  • If using a queue of streams (as suggested in How do I concatenate two System.Io.Stream instances into one? ), how can I specify that the second stream has to be ready for read access instantaneously after the first stream is done?如果使用流队列(如如何将两个 System.Io.Stream 实例连接成一个?中所建议的那样? ),我如何指定第二个流必须在第一个流完成后立即准备好进行读取访问? Here, in particular, I don't see how the MemoryStream would help at all, since the FileStream , as second one in the queue, would only begin accessing the hard disk once it's actually used.在这里,特别是,我根本看不到MemoryStream什么帮助,因为FileStream作为队列中的第二个,只有在实际使用后才开始访问硬盘。
  • Is it really a feasible approach to have literally hundreds, if not thousands of open streams at once?一次拥有数百个甚至数千个开放流真的是一种可行的方法吗?

Note that I don't need write access—reading is fully sufficient for the problem at hand.请注意,我不需要写访问权限——阅读就足以解决手头的问题。 Also, this question is similar to Composite Stream Wrapper providing partial MemoryStream and full original Stream , but the solution provided there is a bug fix for Windows Phone 8 that doesn't apply in my case.此外,这个问题类似于Composite Stream Wrapper 提供部分 MemoryStream 和完整的原始 Stream ,但提供的解决方案有 Windows Phone 8 的错误修复,不适用于我的情况。

I'd very much like to widen my rather limited understanding of this, so any help is greatly appreciated.我非常想扩大我对这一点相当有限的理解,因此非常感谢任何帮助。

I would suggest something like the following solution:我会建议类似以下解决方案:

  • Inherit your own CachableFileStream from FileStreamFileStream继承你自己的CachableFileStream
  • Implement a very simple Cache which uses a data structure you prefer (like a Queue )实现一个非常简单的Cache ,它使用您喜欢的数据结构(如Queue
  • Allow Preload ing data into the internal cache允许将数据Preload到内部缓存中
  • Allow Reload ing data into the internal cache允许将数据Reload到内部缓存中
  • Modify the original Read behaviour in a way, that your cache is used以某种方式修改原始Read行为,即使用您的缓存

To give you an idea of my idea I would suggest some implementation like the following one:为了让您了解我的想法,我会建议一些实现,如下所示:

The usage could be like that:用法可能是这样的:

CachableFileStream cachedStream = new CachableFileStream(...)
{
    PreloadSize = 8192,
    ReloadSize = 4096,
};

// Force preloading data into the cache
cachedStream.Preload();

...
cachedStream.Read(buffer, 0, buffer.Length);
...

Warning: The code below is neither correctly tested nor ideal - this shall just give you an idea!警告:下面的代码既没有经过正确的测试,也没有达到理想的效果——这只是给你一个想法!

The CachableFileStream class: 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();
    }
}

The Cache class: 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;
    }
}

I hope this helps, have fun ;-)我希望这会有所帮助,玩得开心;-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM