C# 流Stream详解(3)——FileStream源码

news2024/12/23 11:35:17

【FileStream】

构造函数

如果创建一个FileStream,常见的参数例如路径Path、操作方式FileMode、权限FileAccess。

这里说下FileShare和SafeFileHandle。

我们知道在读取文件时,通常会有两个诉求:一是如何更快的读取文件内容;二是如何减少读取文件的消耗。常见的加快读取文件的方式是多线程读取,每个线程读取文件的一部分,这就涉及到文件的共享,有以下几种模式:

  • None:拒绝共享,其他线程(进程)将不能打开、写入、删除该文件
  • Read:允许读取,其他线程可以自己new一个FileStream实例来读取文件,线程之间读文件不影响,其中一个线程的FileStream释放后,不影响其他线程读文件。
  • Write:允许写入,其他线程之间并行写入,需要注意的是,不同线程要在不同的流位置和流区间写入,不要重叠,否则重叠的部分是串行的。
  • ReadWrite:允许读写,这种方式不常用,在并行时最需要保证的是不同线程读写文件的不同区间,不要重叠。
  • Delete:允许删除
  • Ineritable:允许文件句柄由子进程继承

SafeFileHandle用的很少,一般来多语言交互的时候用到,比如在C++打开了一个文件,要把引用传递给C#,C#这边来读取文件,或者C#打开文件,传递给C++读取文件。这种情况出现的很少,如果真的需要C++和C#传递文件数据,一般会将文件路径传递,打开读取文件在一端进行,或者在一端打开读取文件后将包含数据的buffer传递到另一端。如果要用的话,示例如下:

    [DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
    static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
      uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
      uint dwFlagsAndAttributes, IntPtr hTemplateFile);

public void ReadFile()
{
SafeFileHandle fileHandle = CreateFile(
"example.txt",
GENERIC_READ,
FILE_SHARE_READ,
IntPtr.Zero,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
IntPtr.Zero
);

byte[] buffer = new byte[1024];
using (FileStream fileStream = new FileStream(fileHandle, FileAccess.Read))
{
int bytesRead = fileStream.Read(buffer, 0, buffer.Length);
//FileStream.SafeFileHandle.DangerousGetHandle() 获取文件句柄
}
}

方法

Read、Write、Dispose和Close没什么好说的,很常用。这里看看不常用的其他方法。

  • Flush:读写文件是一个很复杂的过程,当我们调用write方法尝试将数据写到磁盘上时,即使我们调用了同步的方法,也不是立即写入磁盘,而是先写入FileStream的buffer中。FileStream buffer的默认大小为4kb,我们可以在实例化的时候指定buffer大小。当调用Write方法时,会先将数据写入buffer,如果buffer满了,就将数据写入操作系统的buffer中。(buffer在写入时会分配内存,如果第一次写入的count大于buffersize,那么直接写入操作系统buffer中,否则即使count>buffersize,也是先把buffer填满,再写入操作系统中,这里是个优化的点)。Flush()相当于Flush(false),会立即将buffer中的数据写入操作系统的buffer,并清空buffer中;Flush(true)会将操作系统的buffer也清空,并执行写入磁盘的操作。
  • Lock:锁定文件流void Lock (long position, long length),将文件流的一部分锁定进行独占访问。与之对应的是UnLock。
  • Read(Span<Byte>):如果不了解Span,可以先了解下。Span表示一段连续的内存,有时我们希望直接操作一段内存,安全的方式是先将这段内存的内容copy出来,但是这样性能不高。想要高性能,就要使用指针去访问,但这样不安全。Span提供安全高效的内存访问。用该方法,可以直接将读取的字节序列放到Span引用的连续内存中。

  • ReadExactly:其与Read的区别是,在读取一定的字节序列后,会推进流的位置,也即改变Position属性的值。

源码(反编译来的)

对于上层调用者来说,FileStream提供了一个中间缓存层。每多一个中间层,就需要在中间层中处理好中间层和下层的共同属性之间的关系,这里指Position的关系。

读取数据

public override int Read([In][Out] byte[] array, int offset, int count)
{
    if (array == null)
    {
        throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer"));
    }

    if (offset < 0)
    {
        throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (array.Length - offset < count)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
    }

    if (_handle.IsClosed)//这是SafeFileHandle
    {
        __Error.FileNotOpen();
    }

    bool flag = false;
    int num = _readLen - _readPos;//read和write共用一个buffer,readlen表示用于读数据的长度,这里求的是读数据buffer的剩余可用大小
    if (num == 0)//用于读的buffer的长度和位置相等,表示用于读的buffer用满了
    {
        if (!CanRead)
        {
            __Error.ReadNotSupported();
        }

        if (_writePos > 0)//此时需要清空写入的buffer
        {
            FlushWrite(calledFromFinalizer: false);
        }

        if (!CanSeek || count >= _bufferSize)//如果要求的count大于buffer的大小时,会直接将读出来的数据放入到指定的array中,少了copy的步骤
        {                                   //如果FilsStream只是用于读数据,可以指定小的buffersize,将读到的数据直接放入指定的array中,减少从buffer到array的拷贝
            num = ReadCore(array, offset, count);
            _readPos = 0;
            _readLen = 0;
            return num;
        }

        if (_buffer == null)//实例化buffer
        {
            _buffer = new byte[_bufferSize];
        }

        num = ReadCore(_buffer, 0, _bufferSize);//注意,如果指定的count小于buffer,那么实际是按照buffersize的大小来读取数据的
        if (num == 0)                           //这样做是为了减少IO消耗,底层在读取磁盘数据时,会一次性读取扇区里的全部内容,而不是按照上层指定的读取只读取几个字节
                                                //因此,读文件的操作不一定真的有IO消耗,如果每次读的小,会用到这里的缓存数据
        {
            return 0;
        }

        flag = (num < _bufferSize);//这种情况表示文件大小(或者文件剩余大小)小于指定的buffer大小,buffer大小默认4kb
        _readPos = 0;
        _readLen = num;
    }

    if (num > count)
    {
        num = count;
    }

    Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, num);//将buffer里的数据copy到指定的array中
    _readPos += num;//读数据时流的位置会增加
    if (!_isPipe && num < count && !flag)//isPipe一般为false,这种情况表示指定的count比读数据buffer的可用数据大
    {                                  //步骤是先将buffer里的数据copy到指定的array中,然后再从文件中读剩余(count-num)个数据
        int num2 = ReadCore(array, offset + num, count - num);
        num += num2;
        _readPos = 0;//读数据的Buffer被读取完了,流的位置和大小都回置为0
        _readLen = 0;
    }

    return num;
}

写入数据

public override void Write(byte[] array, int offset, int count)
{
    if (array == null)
    {
        throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer"));
    }

    if (offset < 0)
    {
        throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (array.Length - offset < count)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
    }

    if (_handle.IsClosed)
    {
        __Error.FileNotOpen();
    }

    if (_writePos == 0
    {
        if (!CanWrite)
        {
            __Error.WriteNotSupported();
        }

        if (_readPos < _readLen)//这种情况表示要写数据时,buffer中还有一些数据没被上层读完,需要情况,回退读的位置
        {                       //读写共用一个buffer,读时清空写的数据,写时清空读的数据
            FlushRead();
        }

        _readPos = 0;
        _readLen = 0;
    }

    if (_writePos > 0)
    {
        int num = _bufferSize - _writePos;//计算剩余可写入大小,
        if (num > 0)
        {
            if (num > count)
            {
                num = count;
            }

            Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, num);//将array中的数据copy了一份到buffer中,不是立即写入,会在关闭流的时候再写入
            _writePos += num;//更新写入流的位置
            if (count == num)
            {
                return;//这种情况是剩余的写入大小大于指定的count
            }

            offset += num;
            count -= num;
        }

        if (_isAsync)
        {
            IAsyncResult asyncResult = BeginWriteCore(_buffer, 0, _writePos, null, null);
            EndWrite(asyncResult);
        }
        else
        {
            WriteCore(_buffer, 0, _writePos);//能走到这里,是因为count>num,此时buffer中数据已满,需要写入磁盘中
        }

        _writePos = 0;
    }

    if (count >= _bufferSize)//如果剩余的count或首次的count大于buffer大小,直接写入
    {                       //这里写入是先将buffer填满再写入,多余的直接写入
        WriteCore(array, offset, count);
    }
    else if (count != 0)
    {
        if (_buffer == null)
        {
            _buffer = new byte[_bufferSize];
        }

        Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count);//如果写入指定的count小于剩余的写入大小,只是将array中的数据copy了一份到buffer中,不是立即写入,会在关闭流的时候再写入
        _writePos = count;//更新写入流的位置
    }
}

寻找位置

public override long Position
{
    [SecuritySafeCritical]
    get
    {
        if (_handle.IsClosed)
        {
            __Error.FileNotOpen();
        }

        if (!CanSeek)
        {
            __Error.SeekNotSupported();
        }

        if (_exposedHandle)//该值一般为false,get文件句柄时会被设置为true,此时文件流的位置,需要重新确定
        {
            VerifyOSHandlePosition();
        }
        //这里获取的不是真正的文件流的位置,而是上层调用者认为的流的位置
        return _pos + (_readPos - _readLen + _writePos);
    }
    set
    {
        if (value < 0)
        {
            throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
        }

        if (_writePos > 0)//如果有需要写入的内容,会将内容先写入
        {
            FlushWrite(calledFromFinalizer: false);
        }

        _readPos = 0;
        _readLen = 0;
        Seek(value, SeekOrigin.Begin);//Position属性表示以流开始为起点的位置
    }
}


public override long Seek(long offset, SeekOrigin origin)
{
    if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin"));
    }

    if (_handle.IsClosed)
    {
        __Error.FileNotOpen();
    }

    if (!CanSeek)
    {
        __Error.SeekNotSupported();
    }

    if (_writePos > 0)
    {
        FlushWrite(calledFromFinalizer: false);
    }
    else if (origin == SeekOrigin.Current)
    {
        offset -= _readLen - _readPos;//如果以当前为起点,_readPos表示FilsStream的buffer的Pos,而不是文件流的真正的Position
    }                                   //offset表示调用者认为的Position,(_readLen - _readPos)表示文件流真正的Position和调用者认为的Position之间的差值

    if (_exposedHandle)
    {
        VerifyOSHandlePosition();//实际上时调用SeekCore(0L, SeekOrigin.Current),重新定位下文件流的位置
    }

    long num = _pos + (_readPos - _readLen);//计算出来的文件流的真正位置
    long num2 = SeekCore(offset, origin);//重新定位文件流的位置
    if (_appendStart != -1 && num2 < _appendStart)
    {
        SeekCore(num, SeekOrigin.Begin);
        throw new IOException(Environment.GetResourceString("IO.IO_SeekAppendOverwrite"));
    }

    if (_readLen > 0)//这表示之前已经读取了部分文件信息
    {
        if (num == num2)//一般是这种情况
        {
            if (_readPos > 0)//以readPos为分界线,把buffer中后面的数据拷贝到前面,这样readPos为0了,readLen减少了
            {
                Buffer.InternalBlockCopy(_buffer, _readPos, _buffer, 0, _readLen - _readPos);
                _readLen -= _readPos;
                _readPos = 0;
            }

            if (_readLen > 0)//恢复文件流的真正位置
            {
                SeekCore(_readLen, SeekOrigin.Current);
            }
        }
        else if (num - _readPos < num2 && num2 < num + _readLen - _readPos)//表示重新定位的文件流的位置小于计算出来的文件流的位置
        {                                                               //大于上次读取文件流时得位置
            int num3 = (int)(num2 - num);
            Buffer.InternalBlockCopy(_buffer, _readPos + num3, _buffer, 0, _readLen - (_readPos + num3));
            _readLen -= _readPos + num3;
            _readPos = 0;
            if (_readLen > 0)
            {
                SeekCore(_readLen, SeekOrigin.Current);
            }
        }
        else
        {
            _readPos = 0;
            _readLen = 0;
        }
    }

    return num2;
}
//可以看到Seek流程很复杂,为了提高性能,应该避免再读数据时比默认buffersize大,直接读到指定的array中,不要经过FileStream的buffer。
//一定要避免写数据和读数据交叉进行
//因为filsStream的设计考虑了通用,当我们按照一定的规范去使用时,可以减少很多为通用情况而做的耗费性能的设计

【MemoryStream】

构造函数

只需要关注红色方框里的两个构造函数即可,其他的都是重载。

memorystream也有一个buffer来缓存数据,在new的时候可以指定这个buffer的大小,那么这个buffer的实例化在new的时候完成,如果在写数据时这个buffer的大小不够用,则会自动扩容。

也可以自己实例化一个buffer,并在new的时候通过index和count来指定memorystream可以用这个buffer的哪一部分。通过这种方式new时,如果写数据时大小不够用,是不能扩容的。

writable表示是否可以写入

publiclyVisible表示是否可以拿到memorystream内部的buffer

方法

写入数据

public override void Write(byte[] buffer, int offset, int count)
{
    if (buffer == null)
    {
        throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
    }

    if (offset < 0)
    {
        throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (buffer.Length - offset < count)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
    }

    if (!_isOpen)
    {
        __Error.StreamIsClosed();
    }

    EnsureWriteable();//确保可以写入,不能不能写入会报错,不继续执行了
    int num = _position + count; //计算加入全部写入时流的位置
    if (num < 0) //超出int可表示的最大值的检查,可以注意下,一般自己写代码时很少会做这种检查,虽然一般情况下也不需要
    {              //MemoryStream的最大容量是int.MaxValue
        throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
    }

    if (num > _length)//全部写入时流的位置大于buffer的长度,就需要扩容了
    {
        bool flag = _position > _length;
        if (num > _capacity && EnsureCapacity(num))
        {
            flag = false;
        }

        if (flag)//流的位置大于Buffer长度,需要清除多余的部分
        {
            Array.Clear(_buffer, _length, num - _length);
        }

        _length = num;
    }

    if (count <= 8 && buffer != _buffer) //当字节小于8时则一个个读
    {
        int num2 = count;
        while (--num2 >= 0)
        {
            _buffer[_position + num2] = buffer[offset + num2];
        }
    }
    else//将提供的buffer数据拷贝到MemoryStream的buffer种
    {  //Buffer.BlockCopy比Array.Copy更快
       //https://stackoverflow.com/questions/1389821/array-copy-vs-buffer-blockcopy
        Buffer.InternalBlockCopy(buffer, offset, _buffer, _position, count);
    }

   _position = num;//更新流的位置
}


private bool EnsureCapacity(int value)
{
    if (value < 0)
    {
        throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
    }

    if (value > _capacity)
    {
        int num = value;
        if (num < 256)
        {
            num = 256;//容量小于256时,会被规范为256
        }

        if (num < _capacity * 2)
        {
            num = _capacity * 2;//两倍扩容
        }

        if ((uint)(_capacity * 2) > 2147483591u)//处理超限
        {
            num = ((value > 2147483591) ? value : 2147483591);
        }

        Capacity = num;
        return true;
    }

    return false;
}

public virtual int Capacity
{
    [__DynamicallyInvokable]
    get
    {
        if (!_isOpen)
        {
            __Error.StreamIsClosed();
        }

        return _capacity - _origin;
    }
    [__DynamicallyInvokable]
    set
    {
        if (value < Length)
        {
            throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity"));
        }

        if (!_isOpen)
        {
            __Error.StreamIsClosed();
        }

        if (!_expandable && value != Capacity)
        {
            __Error.MemoryStreamNotExpandable();
        }

        if (!_expandable || value == _capacity)//new时指定了buffer就不能扩容了
        {
            return;
        }

        if (value > 0)
        {
            byte[] array = new byte[value];
            if (_length > 0)//扩容时会将原来的数据copy到新的buffer种
            {
                Buffer.InternalBlockCopy(_buffer, 0, array, 0, _length);
            }

            _buffer = array;
        }
        else
        {
            _buffer = null;
        }

        _capacity = value;
    }
}

读取数据

public override int Read([In][Out] byte[] buffer, int offset, int count)//理解了write后,read方法很简单
{
    if (buffer == null)
    {
        throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer"));
    }

    if (offset < 0)
    {
        throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
    }

    if (buffer.Length - offset < count)
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
    }

    if (!_isOpen)
    {
        __Error.StreamIsClosed();
    }

    int num = _length - _position;
    if (num > count)
    {
        num = count;
    }

    if (num <= 0)
    {
        return 0;
    }

    if (num <= 8)
    {
        int num2 = num;
        while (--num2 >= 0)
        {
            buffer[offset + num2] = _buffer[_position + num2];
        }
    }
    else
    {
        Buffer.InternalBlockCopy(_buffer, _position, buffer, offset, num);//将数据拷贝到指定的buffer中
    }

    _position += num;//读完后,流的position增加
    return num;
}

【BinaryWriter】

构造函数

 Stream参数,FileStream、MemoryStream都继承自Stream,这里传递进来主要是要用这些Stream的buffer

Encoding 编码类型,默认是new UTF8Encoding()

leaveOpen:表示close时要不要把stream要不要保持打开,默认为false,会将stream也close 

方法

//可以每次写入实际是将数据写入Stream的buffer中,BinaryWriter将数据序列化了,这里只提供基本数据类型的序列化
public virtual void Write(bool value)//写入bool
{
    _buffer[0] = (byte)(value ? 1u : 0u);//bool也是byte
    OutStream.Write(_buffer, 0, 1);//BinaryWriter也有有个buffer,固定的长度,为16,之所以为16是因为有个decimal类型要有16个字节表示
}

public virtual void Write(byte value)//byte直接写入
{
    OutStream.WriteByte(value);
}

public virtual void Write(byte[] buffer)
{
    if (buffer == null)
    {
        throw new ArgumentNullException("buffer");
    }

    OutStream.Write(buffer, 0, buffer.Length);//byte[]一样是直接写入到stream的buffer中
}

public virtual void Write(short value)
{
    _buffer[0] = (byte)value;//先取到低八位
    _buffer[1] = (byte)(value >> 8);//右移取到高八位
    OutStream.Write(_buffer, 0, 2);
}

public virtual void Write(int value)
{
    _buffer[0] = (byte)value;
    _buffer[1] = (byte)(value >> 8);
    _buffer[2] = (byte)(value >> 16);
    _buffer[3] = (byte)(value >> 24);
    OutStream.Write(_buffer, 0, 4);
}

public virtual void Write(long value)
{
    _buffer[0] = (byte)value;
    _buffer[1] = (byte)(value >> 8);
    _buffer[2] = (byte)(value >> 16);
    _buffer[3] = (byte)(value >> 24);
    _buffer[4] = (byte)(value >> 32);
    _buffer[5] = (byte)(value >> 40);
    _buffer[6] = (byte)(value >> 48);
    _buffer[7] = (byte)(value >> 56);
    OutStream.Write(_buffer, 0, 8);
}

public unsafe virtual void Write(string value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }

    int byteCount = _encoding.GetByteCount(value);
    Write7BitEncodedInt(byteCount);//会先写入字符串的byte长度
    if (_largeByteBuffer == null)
    {
        _largeByteBuffer = new byte[256];//先尝试用一个256长度的Buffer做首次尝试
        _maxChars = _largeByteBuffer.Length / _encoding.GetMaxByteCount(1);//获取该编码格式下,最大的字符需要多少byte,maxChar表示largeByteBuffer最多可以容纳多少个字符
    }

    if (byteCount <= _largeByteBuffer.Length)//小256,就直接将编码得到的bytes放入largeByteBuffer,再copy到Stream的buffer中
    {
        _encoding.GetBytes(value, 0, value.Length, _largeByteBuffer, 0);
        OutStream.Write(_largeByteBuffer, 0, byteCount);
        return;
    }

    int num = 0;
    int num2 = value.Length;
    while (num2 > 0)//字符串的长度大于128时,将字符串拆分转为bytes,分别写入largeByteBuffer
    {
        int num3 = (num2 > _maxChars) ? _maxChars : num2;
        if (num < 0 || num3 < 0 || checked(num + num3) > value.Length)
        {
            throw new ArgumentOutOfRangeException("charCount");
        }

        int bytes2;
        fixed (char* ptr = value)
        {
            fixed (byte* bytes = _largeByteBuffer)
            {
                //因为字符串不可修改,要使用指针读取,这个方法表示的意思和之前的_encoder.GetBytes是一样的,
                bytes2 = _encoder.GetBytes((char*)checked(unchecked((nuint)ptr) + unchecked((nuint)checked(unchecked((nint)num) * (nint)2))), num3, bytes, _largeByteBuffer.Length, num3 == num2);
            }
        }

        OutStream.Write(_largeByteBuffer, 0, bytes2);
        num += num3;
        num2 -= num3;
    }
}


protected void Write7BitEncodedInt(int value)//用于将整数值编码为7位压缩格式并写入流中。
{                                           //它通常用于数据序列化或网络通信过程中,以减小整数值的存储空间和传输开销。编码过程中,整数值按照7位的块进行分割,并将每个块的最高位设置为1,表示后面还有更多的块。每个块的其余7位用于存储整数值的一部分。
    uint num;                               //这样,较小的整数值可以用较少的字节进行编码,而较大的整数值则需要更多的字节。
    for (num = (uint)value; num >= 128; num >>= 7) //这种方式相比于之前的Write(int value),会减少存储空间
    {
        Write((byte)(num | 0x80));//0x80是 1000 0000,这里直接舍去了num的后七位
    }

    Write((byte)num);
}

【BinaryReader】

构造函数

 方法

//可以发现真正读数据都是Stream完成的,BinaryReader将读出来的数据反序列化了,这里只提供byte到基本数据类型的反序列化
public virtual byte ReadByte()//读byte
{
    if (m_stream == null)
    {
        __Error.FileNotOpen();
    }

    int num = m_stream.ReadByte();//实际调用的是Stream的ReadByte方法,最低读8位
    if (num == -1)
    {
        __Error.EndOfFile();
    }

    return (byte)num;
}

public virtual short ReadInt16()//读short
{
    FillBuffer(2);//16/8=2
    return (short)(m_buffer[0] | (m_buffer[1] << 8));//低位在前面,高位在后面,这是小端模式存储
}

public virtual int ReadInt32()//读int
{
    if (m_isMemoryStream)//new BinaryStream会判断下Stream是不是MemoryStream
    {
        if (m_stream == null)
        {
            __Error.FileNotOpen();
        }

        MemoryStream memoryStream = m_stream as MemoryStream;
        return memoryStream.InternalReadInt32();
    }

    FillBuffer(4);//32/8=4
    return m_buffer[0] | (m_buffer[1] << 8) | (m_buffer[2] << 16) | (m_buffer[3] << 24);
}

public unsafe virtual float ReadSingle()//读取float
{
    FillBuffer(4);
    uint num = (uint)(m_buffer[0] | (m_buffer[1] << 8) | (m_buffer[2] << 16) | (m_buffer[3] << 24));
    return *(float*)(&num);
}

public unsafe virtual double ReadDouble()//读取double
{
    FillBuffer(8);
    uint num = (uint)(m_buffer[0] | (m_buffer[1] << 8) | (m_buffer[2] << 16) | (m_buffer[3] << 24));
    uint num2 = (uint)(m_buffer[4] | (m_buffer[5] << 8) | (m_buffer[6] << 16) | (m_buffer[7] << 24));
    ulong num3 = ((ulong)num2 << 32) | num;
    return *(double*)(&num3);
}


public virtual string ReadString()
{
    if (m_stream == null)
    {
        __Error.FileNotOpen();
    }

    int num = 0;
    int num2 = Read7BitEncodedInt();//读取字符串长度
    if (num2 < 0)
    {
        throw new IOException(Environment.GetResourceString("IO.IO_InvalidStringLen_Len", num2));
    }

    if (num2 == 0)
    {
        return string.Empty;
    }

    if (m_charBytes == null)
    {
        m_charBytes = new byte[128];//这里是存储读取到的字节数组
    }

    if (m_charBuffer == null)
    {
        m_charBuffer = new char[m_maxCharsSize];//这里存储的是字符数组  m_maxCharsSize = encoding.GetMaxCharCount(128);maxCharSize是该编码下128个字符的最大大小
    }

    StringBuilder stringBuilder = null;
    do
    {
        int count = (num2 - num > 128) ? 128 : (num2 - num);
        int num3 = m_stream.Read(m_charBytes, 0, count);
        if (num3 == 0)
        {
            __Error.EndOfFile();
        }
        //每次将charBytes数组中起始节点为0,数量为num3的数据解码放大charBuffer中,在charBuffer中的起始地址为0
        int chars = m_decoder.GetChars(m_charBytes, 0, num3, m_charBuffer, 0);
        if (num == 0 && num3 == num2)
        {
            return new string(m_charBuffer, 0, chars);//字符串长度小于128的就直接返回了
        }

        if (stringBuilder == null)
        {
            stringBuilder = StringBuilderCache.Acquire(Math.Min(num2, 360));
        }

        stringBuilder.Append(m_charBuffer, 0, chars);
        num += num3;
    }
    while (num < num2);
    return StringBuilderCache.GetStringAndRelease(stringBuilder);
}

protected internal int Read7BitEncodedInt()
{
    int num = 0;
    int num2 = 0;
    byte b;
    do
    {
        if (num2 == 35)
        {
            throw new FormatException(Environment.GetResourceString("Format_Bad7BitInt32"));
        }

        b = ReadByte();
        num |= (b & 0x7F) << num2;
        num2 += 7;
    }
    while ((b & 0x80) != 0);
    return num;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1012712.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ATECLOUD二极管测试系统可以解决反向电流测试哪些痛点?

二极管具有单向导电性&#xff0c;但是有时候也会出现反向电流的情况。当反向电流变得很大时&#xff0c;二极管性能和寿命会受到很大影响。通过二极管测试系统对反向电流进行测试可以评估其性能和稳定性。 反向电流测试的重要性 理想情况下反向电流非常小&#xff0c;甚至可以…

2021年电工杯数学建模A题高铁牵引供电系统运行数据分析及等值建模求解全过程论文及程序

2021年电工杯数学建模 A题 高铁牵引供电系统运行数据分析及等值建模 原题再现&#xff1a; 我国是世界上电气化铁路运营里程最长、服役电力机车型号最多、运营最繁忙的国家。截至 2020 年底&#xff0c;我国铁路年消耗电量约 800 亿千瓦时&#xff0c;约占三峡年总发电量的 8…

Java实现添加文字水印、图片水印功能实战

java实现给图片添加水印实现步骤&#xff1a; 获取原图片对象信息&#xff08;本地图片或网络图片&#xff09; 添加水印&#xff08;设置水印颜色、字体、坐标等&#xff09; 处理输出目标图片 java实现给图片添加文字水印 获取原图片对象信息 第一步&#xff1a;获取需要…

人工智能安全-6-SQL注入检测

0 提纲 概述SQL注入方法SQL注入的检测方法SQL语句的特征提取天池AI上的实践 1 概述 SQLIA&#xff1a;SQL injection attack SQL 注入攻击是一个简单且被广泛理解的技术&#xff0c;它把 SQL 查询片段插入到 GET 或 POST 参数里提交到网络应用。 由于SQL数据库在Web应用中的…

【微服务实战之Docker容器】第四章-【微服务实战之Docker容器】第三章-镜像仓库

系列文章目录 【微服务实战之Docker容器】第一章-下载及安装 文章目录 系列文章目录坑&#xff1a;容器卷记得加入以下命令配置是个啥&#xff1f;能干啥&#xff1f;基本的命令读写规则映射添加说明卷的继承和共享 坑&#xff1a;容器卷记得加入以下命令配置 --privilegedtr…

van-calendar 实现移动端日历效果

<!--移动端端展示日历--><divv-if"isMobile &&tabActiveName true &&(formName 值班日历 || formName 值班编排)"><template v-if"Array.isArray(listData) && listData.length"><van-calendar:poppable&…

一生一芯14——chisel环境搭建

本人使用的ubuntu版本为22.04 anaconda 版本为23.1.0 本博客参考自https://blog.csdn.net/qq_38798111/article/details/129190615?ops_request_misc%257B%2522request%255Fid%2522%253A%2522169465704516800222836484%2522%252C%2522scm%2522%253A%252220140713.130102334…%…

芯科蓝牙BG27开发笔记8-片上Flash读写

目标 熟悉片上Flash的特点&#xff0c;知道如何使用&#xff0c;最好找到示例代码&#xff0c;有完整例程那是最好的 查找参考手册 除了768K的主空间&#xff0c;还包含&#xff1a; 1. USERDATA区域&#xff0c;用户定义数据&#xff0c;可以读写。大小只有1K。 2. 设备特…

长胜证券:十大流通股东占比例高好还是低好?

近年来&#xff0c;跟着我国本钱商场的不断发展&#xff0c;越来越多的投资者开始了解和关注股东占比这个目标。而在股东占比中&#xff0c;十大流转股东的持股份额是一个重要的目标。可是&#xff0c;关于投资者来说&#xff0c;十大流转股东占比是高好还是低好&#xff1f;本…

【SPI读取外部Flash】使用逻辑分析仪来读取FLASH Device ID

实验设备&#xff1a;25块钱的 逻辑分析仪 和 野火F429开发板 注意点&#xff0c;这个逻辑分析仪最大只能检测24M的波形&#xff0c;而SPI是在外部通道2&#xff0c;所以我们对系统时钟的分频&#xff0c;也就是给到通道2的时钟速度要在24M内&#xff0c;不然检测到的数据是有…

Peppertype.ai:人工智能内容营销平台

【产品介绍】 名称 Peppertype.ai 具体描述 Peppertype.ai是一个AI驱动的文章生成工具&#xff0c;可以帮助你在几秒钟内为各种渠道创建吸引人 的内容。无论你是想要写广告文案、社交媒体标题、博客大纲还是网站内容&#xff0c;Peppertype…

C++ PrimerPlus 复习 第五章 循环和关系表达式

第一章 命令编译链接文件 make文件 第二章 进入c 第三章 处理数据 第四章 复合类型 &#xff08;上&#xff09; 第四章 复合类型 &#xff08;下&#xff09; 第五章 循环和关系表达式 文章目录 for循环&#xff1b;基本语法重要问题和解答 基于范围的for循环&#xff08…

中秋学习Qt6

中秋学习Qt6 【1】Qt6 新增的模块【2】Qt6剔除模块和方法【3】Qt6改进 【1】Qt6 新增的模块 Qt6引入了一些新的模块&#xff0c;以便提供更多的功能和改进。以下是一些Qt6新增的模块&#xff1a; QtQuick3D&#xff1a;Qt6引入了全新的3D引擎模块QtQuick3D&#xff0c;它提供了…

脚本:python绘制七夕爱心

文章目录 效果脚本Reference 效果 脚本 import random from math import sin, cos, pi, log from tkinter import *CANVAS_WIDTH 640 # 画布的宽 CANVAS_HEIGHT 640 # 画布的高 CANVAS_CENTER_X CANVAS_WIDTH / 2 # 画布中心的X轴坐标 CANVAS_CENTER_Y CANVAS_HEIGHT /…

ABB 1TGE120010R1300 控制主板模块

ABB 1TGE120010R1300 控制主板模块是一种用于控制和监测电力设备的模块&#xff0c;具有以下功能&#xff1a; 控制和监测电力设备&#xff1a;该模块可以通过与电力设备连接来控制和监测设备的性能和状态&#xff0c;例如启停设备、调节电压和功率等。 通信功能&#xff1a;该…

学Python的漫画漫步进阶 -- 第十六步

学Python的漫画漫步进阶 -- 第十六步 十六、多线程16.1 线程相关的知识16.1.1 进程16.1.2 线程16.1.3 主线程 16.2 线程模块——threading16.3 创建子线程16.3.1 自定义函数实现线程体16.3.2 自定义线程类实现线程体 16.4 线程管理16.4.1 等待线程结束16.4.2 线程停止 16.5 动动…

【ant-design-vue】ant-design-vue在uniapp使用时,auto-import失败报错

前言 在我的 uniapp vue3 vite 项目中&#xff0c;使用了 ant-design-vue 4.x 组件库&#xff0c;同时我还使用了 vite 插件 auto-import 用于自动导入vue3的组合式api。当我全局引用antd-vue时&#xff0c;开发模式下可以正常运行&#xff0c;却不能 npm run build 正常打包…

港联证券:“保险+期货”快速落地生花 涉及品种累计达18个

从普洱火车站出发&#xff0c;乘车3小时40分钟&#xff0c;经过250公里山路之后&#xff0c;能够到达云南西南边境的孟连县。孟连县全称孟连傣族拉祜族佤族自治县&#xff0c;“孟连”音自傣语&#xff0c;意为“寻找到的好地方”。这里属亚热带气候&#xff0c;热区资源丰富&a…

与社交媒体结合:视频直播美颜sdk在社交平台上的应用

为了让直播内容更吸引人&#xff0c;视频直播美颜sdk&#xff08;Software Development Kit&#xff09;正逐渐崭露头角&#xff0c;为社交媒体用户提供了卓越的美颜效果和互动体验。 一、什么是视频直播美颜sdk&#xff1f; 在深入讨论如何将视频直播美颜sdk整合到社交媒体平…

vue2 项目中嵌入视频

案例&#xff1a; 代码&#xff1a; <template><div class"schematicDiagramIndex"><el-container><el-aside width"20rem"><!-- <h4 style"font-size: 18px">视频演示</h4>--><div style…