Stream篇(四)

2023-11-05

FileStream

如何去理解FileStream?

通过前3章的学习相信大家对于Stream已经有一定的了解,但是又如何去理解FileStream呢?请看下图   
这里写图片描述
我们磁盘的中任何文件都是通过2进制组成,最为直观的便是记事本了,当我们新建一个记事本时,它的大小是0KB, 我们每次输入一个数字或

字母时文件便会自动增大4kb,可见随着我们输入的内容越来越多,文件也会相应增大,同理当我们删除文件内容时,文件也会相应减小,对了,

聪明的你肯定会问:谁将内容以怎么样的形式放到文件中去了?好问题,还记得第一篇流的概念么?对了,真实世界的一群鱼可以通过河流来

往于各个地方,FileStream也是一样,byte可以通过FileStream进行传输,这样我们便能在计算机上对任何文件进行一系列的操作了。

FileStream 的重要性

FileStream 顾名思义文件流,我们电脑上的文件都可以通过文件流进行操作,例如文件的复制,剪切,粘贴,删除, 本地文件上传,下载,等许

多重要的功能都离不开文件流,所以文件流不仅在本机上非常重要,在如今的网络世界也是万万不能缺少的,想象一下我们开启虚机后,直接从本

地复制一个文件到虚机上,是多么方便,如果没有文件流,这个将难以想象。(大家别误解,文件流无法直接通过网络进行传输,而是

通过网络流将客户端上传的文件传到服务器端接收,然后通过文件流进行处理,下载正好相反)

FileStream 常用构造函数介绍(可能理解上有点复杂,请大家务必深刻理解)

*1: FileStream(SafeFileHandle, FileAccess)

           非托管参数SafeFileHandle简单介绍

                       SafeFileHandle :是一个文件安全句柄,这样的解释可能大家一头雾水,
  • 1
  • 2
  • 3
  • 4

别急,大家先不要去理睬这深邃的含义,只要知道这个类型是c#非托管资源,

也就是说它能够调用非托管资源的方法,而且不属于c#回收机制,所以我们必须

使用GC手动或其他方式(Finalize 或Dispose方法)进行非托管资源的回收,所以

SafeFileHandle是个默默无闻的保镖 ,一直暗中保护FileStream和文件的安全

为了能让大家更好的理解这个保镖,请看第一段代码: 
这里写图片描述
会什么会报错呢?其实程序被卡在 Console.ReadLine()这里,FileStream并没有

被释放,系统不知道这个文件是否还有用﹐所以帮我们保护这个文件

(那个非托管资源SafeFileHandle所使用的内存还被程序占用着)

所以SafeFileHandled 在内部保护了这个文件从而报出了这个异常

这里写图片描述
可以看见stream.SafeFileHandle的IsClose属性变成true了,也就是说这时候可以安全的删除文件了

所以又回到了一个老问题上面,我们每次使用完FileStream后都必须将他关闭并释放资源 
*2: FileStream(String, FileMode)

String 参数表示文件所在的地址,FIleMode是个枚举,表示确定如何打开或创建文件。

FileMode枚举参数包含以下内容: 
这里写图片描述
这里写图片描述

参数ownsHandle:也就是类似于前面和大家介绍的SafeFileHandler,有2点必须注意:

  1对于指定的文件句柄,操作系统不允许所请求的 access,例如,当 access 为 Write 或 ReadWrite 而文件句柄设置为只读访问时,会报出异常。

   所以 ownsHandle才是老大,FileAccess的权限应该在ownsHandle的范围之内

        2. FileStream 假定它对句柄有独占控制权。当 FileStream 也持有句柄时,读取、写入或查找可能会导致数据破坏。为了数据的安全,请使用

        句柄前调用 Flush,并避免在使用完句柄后调用 Close 以外的任何方法。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里写图片描述
*5: FileStream(String, FileMode, FileAccess, FileShare, Int32, Boolean async )

Int32:这是一个缓冲区的大小,大家可以按照自己的需要定制,

Boolean async:是否异步读写,告诉FileStream示例,是否采用异步读写

*6: FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions)

  FileOptions:这是类似于FileStream对于文件操作的高级选项
  • 1
  • 2

FileStream 常用属性介绍

*1:CanRead :指示FileStream是否可以读操作

*2:CanSeek:指示FileStream是否可以跟踪查找流操作

*3:IsAsync:FileStream是否同步工作还是异步工作

*4:Name:FileStream的名字 只读属性

*5:ReadTimeout :设置读取超时时间

*6:SafeFileHandle : 文件安全句柄 只读属性

*7:position:当前FileStream所在的流位置

FileStream 常用方法介绍

以下方法重写了Stream的一些虚方法(**这里大家点击这里可以参考第一篇来温故下,这里不再叙述)

1:IAsyncResult BeginRead 异步读取

2:IAsyncResult BeginWrite 异步写

3:void Close 关闭当前FileStream

4:void EndRead 异步读结束

5:void EndWrite 异步写结束

6:void Flush 立刻释放缓冲区,将数据全部导出到基础流(文件中)

7:int Read 一般读取

8:int ReadByte 读取单个字节

9:long Seek 跟踪查找流所在的位置

10:void SetLength 设置FileStream的长度

11:void Write 一般写

12:void WriteByte写入单个字节

属于FileStream独有的方法

*1:FileSecurity GetAccessControl()

这个不是很常用,FileSecurity 是文件安全类,直接表达当前文件的访问控制列表(ACL)的符合当前文件权限的项目,ACL大家有个了解就行,以后会单独和大家讨论下ACL方面的知识

*2: void Lock(long position,long length)

这个Lock方法和线程中的Look关键字很不一样,它能够锁住文件中的某一部分,非常的强悍!用了这个方法我们能够精确锁定住我们需要锁住的文件的部分内容

*3: void SetAccessControl(FileSecurity fileSecurity)

和GetAccessControl很相似,ACL技术会在以后单独介绍

*4: void Unlock (long position,long length)

正好和lock方法相反,对于文件部分的解锁

文件的新建和拷贝(主要演示文件同步和异步操作)

首先我们尝试DIY一个IFileConfig

     /// <summary>
    /// 文件配置接口
    /// </summary>
    public interface IFileConfig
    {
        string FileName { get; set; }
        bool IsAsync { get; set; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

创建文件配置类CreateFileConfig,用于添加文件一些配置设置,实现添加文件的操作

/// <summary>
    /// 创建文件配置类
    /// </summary>
    public class CreateFileConfig : IFileConfig
    {
        // 文件名
        public string FileName { get; set; }
        //是否异步操作
        public bool IsAsync { get; set; }
        //创建文件所在url
        public string CreateUrl { get; set; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

让我们定义一个文件流测试类:FileStreamTest 来实现文件的操作

  /// <summary>
    /// FileStreamTest 类
    /// </summary>
    public class FileStreamTest
  • 1
  • 2
  • 3
  • 4
  • 5

在该类中实现一个简单的Create方法用来同步或异步的实现添加文件,FileStream会根据配置类去选择相应的构造函数,实现异步或同步的添加方式

/// <summary>
        /// 添加文件方法
        /// </summary>
        /// <param name="config"> 创建文件配置类</param>
        public void Create(IFileConfig config)
        {
            lock (_lockObject)
            {
                //得到创建文件配置类对象
                var createFileConfig = config as CreateFileConfig;
                //检查创建文件配置类是否为空
                if (this.CheckConfigIsError(config)) return;
                //假设创建完文件后写入一段话,实际项目中无需这么做,这里只是一个演示
                char[] insertContent = "HellowWorld".ToCharArray();
                //转化成 byte[]
                byte[] byteArrayContent = Encoding.Default.GetBytes(insertContent, 0, insertContent.Length);
                 //根据传入的配置文件中来决定是否同步或异步实例化stream对象
                FileStream stream = createFileConfig.IsAsync ?
                    new FileStream(createFileConfig.CreateUrl, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true)
                    : new FileStream(createFileConfig.CreateUrl, FileMode.Create);
                using (stream)
                {
                    // 如果不注释下面代码会抛出异常,google上提示是WriteTimeout只支持网络流
                   // stream.WriteTimeout = READ_OR_WRITE_TIMEOUT;
                    //如果该流是同步流并且可写
                    if (!stream.IsAsync && stream.CanWrite)
                        stream.Write(byteArrayContent, 0, byteArrayContent.Length);
                    else if (stream.CanWrite)//异步流并且可写
                        stream.BeginWrite(byteArrayContent, 0, byteArrayContent.Length, this.End_CreateFileCallBack, stream);

                    stream.Close();
                }
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

如果采用异步的方式则最后会进入End_CreateFileCallBack回调方法,result.AsyncState对象就是上图stream.BeginWrite()方法的最后一个参数

还有一点必须注意的是每一次使用BeginWrite()方法事都要带上EndWrite()方法,Read方法也一样

/// <summary>
        ///  异步写文件callBack方法
        /// </summary>
        /// <param name="result">IAsyncResult</param>
        private void End_CreateFileCallBack(IAsyncResult result)
        {
            //从IAsyncResult对象中得到原来的FileStream
            var stream = result.AsyncState as FileStream;
            //结束异步写

            Console.WriteLine("异步创建文件地址:{0}", stream.Name);
            stream.EndWrite(result);
            Console.ReadLine();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

文件复制的方式思路比较相似,首先定义复制文件配置类,由于在异步回调中用到该配置类的属性,所以新增了文件流对象和相应的字节数组

/// <summary>
    /// 文件复制
    /// </summary>
    public class CopyFileConfig : IFileConfig
    {
        // 文件名
        public string FileName { get; set; }
        //是否异步操作
        public bool IsAsync { get; set; }
        //原文件地址
        public string OrginalFileUrl { get; set; }
        //拷贝目的地址
        public string DestinationFileUrl { get; set; }
        //文件流,异步读取后在回调方法内使用
        public FileStream OriginalFileStream { get; set; }
        //原文件字节数组,异步读取后在回调方法内使用
        public byte[] OriginalFileBytes { get; set; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后在FileStreamTest 类中新增一个Copy方法实现文件的复制功能

/// <summary>
        /// 复制方法
        /// </summary>
        /// <param name="config">拷贝文件复制</param>
        public void Copy(IFileConfig config)
        {
            lock (_lockObject)
            {
                //得到CopyFileConfig对象
                var copyFileConfig = config as CopyFileConfig;
                // 检查CopyFileConfig类对象是否为空或者OrginalFileUrl是否为空
                if (CheckConfigIsError(copyFileConfig) || !File.Exists(copyFileConfig.OrginalFileUrl)) return;
                //创建同步或异步流
                FileStream stream = copyFileConfig.IsAsync ?
                 new FileStream(copyFileConfig.OrginalFileUrl, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)
                 : new FileStream(copyFileConfig.OrginalFileUrl, FileMode.Open);
                //定义一个byte数组接受从原文件读出的byte数据
                byte[] orignalFileBytes = new byte[stream.Length];
                using (stream)
                {
                   // stream.ReadTimeout = READ_OR_WRITE_TIMEOUT;
                    //如果异步流
                    if (stream.IsAsync)
                    {
                        //将该流和读出的byte[]数据放入配置类,在callBack中可以使用
                        copyFileConfig.OriginalFileStream = stream;
                        copyFileConfig.OriginalFileBytes = orignalFileBytes;
                        if (stream.CanRead)
                         //异步开始读取,读完后进入End_ReadFileCallBack方法,该方法接受copyFileConfig参数
                        stream.BeginRead(orignalFileBytes, 0, orignalFileBytes.Length, End_ReadFileCallBack, copyFileConfig);
                    }
                    else//否则同步读取
                    {
                        if (stream.CanRead)
                        {
                            //一般读取原文件
                            stream.Read(orignalFileBytes, 0, orignalFileBytes.Length);
                        }
                        //定义一个写流,在新位置中创建一个文件
                        FileStream copyStream = new FileStream(copyFileConfig.DestinationFileUrl, FileMode.CreateNew);
                        using (copyStream)
                        {
                          //  copyStream.WriteTimeout = READ_OR_WRITE_TIMEOUT;
                            //将源文件的内容写进新文件
                            copyStream.Write(orignalFileBytes, 0, orignalFileBytes.Length);
                            copyStream.Close();
                        }
                    }
                    stream.Close();
                    Console.ReadLine();
                }
            }


        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

最后,如果采用异步的方式,则会进入End_ReadFileCallBack回调函数进行异步读取和异步写操作

/// <summary>
        /// 异步读写文件方法
        /// </summary>
        /// <param name="result"></param>
        private void End_ReadFileCallBack(IAsyncResult result) 
        {
            //得到先前的配置文件
            var config = result.AsyncState as CopyFileConfig;
            //结束异步读
            config.OriginalFileStream.EndRead(result);
            //异步读后立即写入新文件地址
            if (File.Exists(config.DestinationFileUrl)) File.Delete(config.DestinationFileUrl);
            FileStream copyStream = new FileStream(config.DestinationFileUrl, FileMode.CreateNew);
            using (copyStream)
            {
                Console.WriteLine("异步复制原文件地址:{0}", config.OriginalFileStream.Name);
                Console.WriteLine("复制后的新文件地址:{0}", config.DestinationFileUrl);
                //调用异步写方法CallBack方法为End_CreateFileCallBack,参数是copyStream
                copyStream.BeginWrite(config.OriginalFileBytes, 0, config.OriginalFileBytes.Length, this.End_CreateFileCallBack,copyStream);
                copyStream.Close();

            }

        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

最后让我们在main函数调用下:

static void Main(string[] args)
        {
            FileStreamTest test = new FileStreamTest();
            //创建文件配置类
            CreateFileConfig createFileConfig = new CreateFileConfig { CreateUrl = @"d:\MyFile.txt", IsAsync = true };
            //复制文件配置类
            CopyFileConfig copyFileConfig = new CopyFileConfig
            {
                OrginalFileUrl = @"d:\8.jpg",
                DestinationFileUrl = @"d:\9.jpg",
                IsAsync = true
            };
            test.Create(createFileConfig);
            test.Copy(copyFileConfig);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里写图片描述

这里写图片描述

/// <summary>
    /// 分段上传例子
    /// </summary>
    public class UpFileSingleTest
    {
        //我们定义Buffer为1000
        public const int BUFFER_COUNT = 1000;

        /// <summary>
        /// 将文件上传至服务器(本地),由于采取分段传输所以,
        /// 每段必须有一个起始位置和相对应该数据段的数据
        /// </summary>
        /// <param name="filePath">服务器上文件地址</param>
        /// <param name="startPositon">分段起始位置</param>
        /// <param name="btArray">每段的数据</param>
        private void WriteToServer(string filePath,int startPositon,byte[] btArray) 
        {
            FileStream fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
            using (fileStream) 
            {
                //将流的位置设置在该段起始位置
                fileStream.Position = startPositon;
                //将该段数据通过FileStream写入文件中,每次写一段的数据,就好比是个水池,分段蓄水一样,直到蓄满为止
                fileStream.Write(btArray, 0, btArray.Length);
            }
        }


        /// <summary>
        /// 处理单独一段本地数据上传至服务器的逻辑,根据客户端传入的startPostion
        /// 和totalCount来处理相应段的数据上传至服务器(本地)
        /// </summary>
        /// <param name="localFilePath">本地需要上传的文件地址</param>
        /// <param name="uploadFilePath">服务器(本地)目标地址</param>
        /// <param name="startPostion">该段起始位置</param>
        /// <param name="totalCount">该段最大数据量</param>
        public void UpLoadFileFromLocal(string localFilePath,string uploadFilePath,int startPostion,int totalCount) 
        {
            //if(!File.Exists(localFilePath)){return;}
           //每次临时读取数据数
            int tempReadCount = 0;
            int tempBuffer = 0;
            //定义一个缓冲区数组
            byte[] bufferByteArray = new byte[BUFFER_COUNT];
            //定义一个FileStream对象
            FileStream fileStream = new FileStream(localFilePath,FileMode.Open);
            //将流的位置设置在每段数据的初始位置
            fileStream.Position = startPostion;
            using (fileStream)
            {
                //循环将该段数据读出在写入服务器中
                while (tempReadCount < totalCount)
                {

                    tempBuffer = BUFFER_COUNT;
                    //每段起始位置+每次循环读取数据的长度
                    var writeStartPosition = startPostion + tempReadCount;
                    //当缓冲区的数据加上临时读取数大于该段数据量时,
                    //则设置缓冲区的数据为totalCount-tempReadCount 这一段的数据
                    if (tempBuffer + tempReadCount > totalCount) 
                    {
                        //缓冲区的数据为totalCount-tempReadCount 
                        tempBuffer = totalCount-tempReadCount;
                        //读取该段数据放入bufferByteArray数组中
                        fileStream.Read(bufferByteArray, 0, tempBuffer);
                        if (tempBuffer > 0) 
                        {
                            byte[] newTempBtArray = new byte[tempBuffer];
                            Array.Copy(bufferByteArray, 0, newTempBtArray, 0, tempBuffer);
                            //将缓冲区的数据上传至服务器
                            this.WriteToServer(uploadFilePath, writeStartPosition, newTempBtArray);
                        }

                    }
                    //如果缓冲区的数据量小于该段数据量,并且tempBuffer=设定BUFFER_COUNT时,通过
                    //while 循环每次读取一样的buffer值的数据写入服务器中,直到将该段数据全部处理完毕
                    else if (tempBuffer == BUFFER_COUNT) 
                    {
                        fileStream.Read(bufferByteArray, 0, tempBuffer);
                        this.WriteToServer(uploadFilePath, writeStartPosition, bufferByteArray);
                    }

                    //通过每次的缓冲区数据,累计增加临时读取数
                    tempReadCount += tempBuffer;
                }
            }
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

一切准备就绪,我们剩下的就是将文件切成几段进行上传了

static void Main(string[] args)
        {
            UpFileSingleTest test=new UpFileSingleTest();
            FileInfo info = new FileInfo(@"G:\\Skyrim\20080204173728108.torrent");
            //取得文件总长度
            var fileLegth = info.Length;
            //假设将文件切成5段
            var divide = 5;
            //取到每个文件段的长度
            var perFileLengh = (int)fileLegth / divide;
            //表示最后剩下的文件段长度比perFileLengh小
            var restCount = (int)fileLegth % divide;
            //循环上传数据
            for (int i = 0; i < divide+1; i++)
            {
                //每次定义不同的数据段,假设数据长度是500,那么每段的开始位置都是i*perFileLength
                var startPosition = i * perFileLengh;
                //取得每次数据段的数据量
                var totalCount = fileLegth - perFileLengh * i > perFileLengh ? perFileLengh : (int)(fileLegth - perFileLengh * i);
                //上传该段数据
                test.UpLoadFileFromLocal(@"G:\\Skyrim\\20080204173728108.torrent", @"G:\\Skyrim\\20080204173728109.torrent", startPosition, i == divide ? divide : totalCount);
            }

        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这里写图片描述

http://www.cnblogs.com/JimmyZheng/archive/2012/04/03/2430585.html



from:http://blog.csdn.net/younghaiqing/article/details/54729606




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Stream篇(四) 的相关文章

  • Python爬虫副业真的可行吗?能赚多少?

    首先回答你 python爬虫能当副业 副业的方式比较多 等下我会讲几种 到哪个层次能接单 主要看你是接什么样的单 爬一些资料 视频这种简单的学一两个月就没什么问题 复杂的那就需要系统的学习 爬虫原理 html相关知识 urllib urll
  • python实现爬取微博相册所有图片

    微博相册的批量爬取 文章目录 前言 一 分析实现思路 二 编写代码 1 引入库 2 多进程的编写 3 主函数的编写 结果 前言 微博有相册功能 那么我们如何批量下载相册中的所有照片呢 提示 以下是本篇文章正文内容 下面案例可供参考 一 分析
  • python中统计单词出现的次数_python统计文本中每个单词出现的次数

    python统计文本中每个单词出现的次数 coding utf 8 author zcg import collections import os with open abc txt as file1 打开文本文件 str1 file1 r
  • myBatis入门程序介绍

    前言 Mybaits框架也称为对象关系映射 ORM 框架 它通过描述java对象与数据库表之间映射关系 自动的将java应用程序中的对象持久化到关系型数据库中 这些功能的完成主要依赖两种配置文件 主配置文件 通常叫做 mybaits con
  • 【云原生之Docker实战】使用docker部署mm-wiki文档系统

    云原生之Docker实战 使用docker部署mm wiki文档系统 一 mm wiki介绍 1 mm wiki简介 2 mm wiki特点 二 检查本地docker环境 1 检查本地docker版本 2 检查docker状态 3 检查do
  • 京东到家技术解密

    京东到家技术解密 京东到家相信很多人都听过 是一个短短两年内依靠技术与产品崛起的公司 其中项目的划分 技术的选型又是怎么样子呢 本课程目标用户群体是工作过几年的攻城狮 在项目中取得了一定的成就 但是缺少O2O 电商 新零售领域的工作经验 却
  • oracle存储过程----case条件控制语句的用法

    上一篇 oracle存储过程 赋值 相等 分支的写法 oracle存储过程 case条件控制语句的用法 今天又看到了另一种条件控制的方法case case语句可以分为两种类型 一种是简单的case语句 它给出一个表达式 并把表达式结果同提供
  • Vue + EChart之饼图详解

    Vue EChart之饼图详解 1 安装echarts项目依赖 npm install echarts save 或者 npm install echarts S 2 创建图表
  • 为什么需要三次握手?

    为什么需要三次握手 三次握手保证了数据能在收发双方之间传递接收 三次握手最主要的目的就是 双方确认 自己与对方的发送和接收是正常的 第一次握手 客户端什么也确定不了 服务端确认了对方发送正常 自己接收正常 第二次握手 客户端 确认了自己发送
  • 【20220815】新板子第一次调试的错误大多是硬件的疏忽

    1 看门狗没有按照外设芯片的要求接VDD 而是被硬件工程师NC处理了 2 拨码开关输入没有进行上拉处理 3 连接器P3没有GND端口 Layout没问题 主要是硬件工程师绘制原理图的时候的一些判断上的问题 当他不知道项目需要什么接线方法的时
  • Python3之面向对象

    一 面向对象技术简介 类 class 用来描述具有相同属性和方法的对象集合 定义了该集合中每个对象所共有的属性和方法 对象是类的实例 方法 即类中定义的函数 类变量 类变量即在类中声明的变量 其在整个实例化的对象中是公用的 类变量定义在类中
  • MVSNet (pytorch版) 搭建环境 运行dtu数据集重建 实操教程(图文并茂、超详细)

    文章目录 1 准备工作 1 1 下载源码 1 2 测试集下载 2 配置环境 3 dtu数据集 重建演示 3 1 重建效果查看 4 补充解释 4 1 bash 脚本文件超参数解释 4 2 lists dtu解释 5 Meshlab查看三维点云
  • angular基础5-8【指令&管道&组件通讯&组件生命周期】

    5 指令 Directive 指令是 Angular 提供的操作 DOM 的途径 指令分为属性指令和结构指令 属性指令 修改现有元素的外观或行为 使用 包裹 结构指令 增加 删除 DOM 节点以修改布局 使用 作为指令前缀 5 1 内置指令
  • 小程序用什么开发?

    近年来 随着智能手机的普及和移动互联网的发展 小程序成为了一种备受关注的新型应用 那么 小程序用什么开发呢 首先 小程序可以使用多种技术进行开发 其中比较流行的有两种方式 一种是借助微信开发者工具使用各种前端框架和语言进行开发 另一种是使用
  • Unity通过Addressable + ILRuntime 实现代码和资产的热更新(案例+图文详情+源码)

    Unity通过Addressable ILRuntime 实现代码和资产的热更新 图文详情 源码 前言 一 思路概述 二 Unity主工程部分 1 GameLunch的源码 2 ResMgr的源码 3 ILRuntimeWrapper的源码
  • spark报错:ROW FORMAT SERDE is incompatible with format ‘orc‘

    错误样例 CREATE TABLE wangriyu bugtt1 task no int exec ip int ROW FORMAT SERDE org apache hadoop hive ql io orc OrcSerde STO

随机推荐