为文件输入和输出创建新的 System object
此示例说明如何创建和使用两个不同的 System object,以支持通过流方式将数据传入传出 MATLAB®:TextFileReader
和 TextFileWriter
。
此示例中讨论的对象适用于许多实际用例,并且可以对它们进行自定义以实现更高级和更专业的任务。
简介
System object 是派生自 matlab.System
的 MATLAB 类。因此,System object 都继承一个通用公共接口,其中包括标准方法:
setup
- 初始化对象,通常在仿真开始时reset
- 清除对象的内部状态,将其还原到默认的初始化后的状态release
- 释放对象内部使用的任何资源(内存、硬件或特定于操作系统的资源)
当您创建新种类的 System object 时,您需要为所有前面的方法提供特定实现来确定其行为。
在此示例中,我们讨论以下两个 System object 的内部结构和用法:
TextFileReader
TextFileWriter
为了创建这些 System object 以支持通过流方式将数据传入传出 MATLAB,此示例使用 MATLAB 中可用的标准低级文件 I/O 函数(如 fscanf
、fread
、fprintf
和 fwrite
)。抽象化这些函数的大部分使用细节旨在使读取和写入流数据的任务更简单、更高效。
此示例包括使用许多高级构造来创建 System object。有关创建 System object 的更基本的介绍,请参阅创建 System object。
类 TextFileReader
的定义
TextFileReader
类包括类定义、公共和私有属性、构造函数、从 matlab.System
基类覆盖的受保护方法以及私有方法。TextFileWriter
类的结构类似。
类定义
类定义规定,TextFileReader
类同时派生自 matlab.System
和 matlab.system.mixin.FiniteSource
。
classdef (StrictDefaults)TextFileReader < matlab.System & matlab.system.mixin.FiniteSource
matlab.System
是必需的,并且是所有 System object 的基类matlab.system.mixin.FiniteSource
表示该类是具有有限数量数据采样的信号源。对于这种类型的类,除了通常的接口,System object™ 还将公开isDone
函数。当isDone
返回 true 时,对象到达可用数据的末尾。
公共属性
用户可以更改公共属性,以根据用户的特定应用调整对象的行为。TextFileReader
有两个不可调的公共属性(只能在第一次调用对象之前更改)和四个可调的公共属性。所有公共属性都有默认值。当用户没有指定除默认值以外的其他内容时,默认值赋给对应的属性。
properties (Nontunable) Filename = 'tempfile.txt' HeaderLines = 4 end
properties DataFormat = '%g' Delimiter = ',' SamplesPerFrame = 1024 PlayCount = 1 end
私有属性
私有属性对用户不可见,可以有多种用途,包括
保留仅偶尔计算并用于算法的后续调用中的值。例如,当调用
setup
时或首次调用对象时,在初始化时使用的值。这可以避免在运行时对其重新计算,并提高核心功能的性能定义对象的内部状态。例如,
pNumEofReached
存储到达文件末尾的指示符的次数:
properties(Access = private) pFID = -1 pNumChannels pLineFormat pNumEofReached = 0 end
构造函数
定义构造函数是为了使用名称-值对组构造 TextFileReader
对象。创建 TextDataReader
的新实例时,将调用该构造函数。构造函数中对 setProperties
的调用允许在构造时使用名称-值对组设置属性。构造函数中不应指定其他初始化任务。在这种情况下,请使用 setupImpl
方法。
methods function obj = TextFileReader(varargin) setProperties(obj, nargin, varargin{:}); end end
覆盖 matlab.System
基类受保护方法
所有 System object 共有的公共方法都有对应的内部调用的受保护方法。这些受保护方法的名称都包括 Impl
后缀。它们可以在定义类以对 System object 的行为编程时实现。
有关标准公共方法及其内部实现之间对应关系的详细信息,请参考调用序列摘要。
例如,TextFileReader
会覆盖下列 Impl
方法:
setupImpl
resetImpl
stepImpl
releaseImpl
isDoneImpl
processTunedPropertiesImpl
loadObjectImpl
saveObjectImpl
私有方法
私有方法只能从同一类的其他方法中访问。它们可用于提高其余代码的可读性。它们还可以通过在不同例程下组合在类的不同部分多次使用的代码来提高代码的可重用性。对于 TextFileReader
,创建的私有方法用于:
getWorkingFID
goToStartOfData
peekCurrentLine
lockNumberOfChannelsUsingCurrentLine
readNDataRows
写入和读取数据
此示例说明如何通过以下方式使用 TextFileReader
和 TextFileWriter
:
使用
TextFileWriter
创建包含两个不同正弦信号采样的文本文件使用
TextFileReader
从文本文件中读取。
创建简单文本文件
创建一个新文件来存储频率分别为 50 Hz 和 60 Hz 的两个正弦信号。对于每个信号,存储的数据由 800 个采样组成,采样率为 8 kHz。
创建数据采样:
fs = 8000; tmax = 0.1; t = (0:1/fs:tmax-1/fs)'; N = length(t); f = [50,60]; data = sin(2*pi*t*f);
构成一个标题字符串,以可读方式描述数据以供将来使用(可选步骤):
fileheader = sprintf(['The following contains %d samples of two ',... 'sinusoids,\nwith frequencies %d Hz and %d Hz and a sample rate of',... ' %d kHz\n\n'], N, f(1),f(2),fs/1000);
要将信号存储到文本文件中,请创建 TextFileWriter
对象。TextFileWriter
的构造函数需要目标文件的名称和一些可选参数,这些参数可以作为名称-值对组传入。
TxtWriter = TextFileWriter('Filename','sinewaves.txt','Header',fileheader)
TxtWriter = TextFileWriter with properties: Filename: 'sinewaves.txt' Header: 'The following contains 800 samples of two sinusoids,...' DataFormat: '%.18g' Delimiter: ','
TextFileWriter
将数据写入以分隔符分隔的 ASCII 文件。其公共属性包括:
Filename
- 要写入的文件的名称。如果同名文件已存在,它将被覆盖。当操作开始时,对象会紧接着文件中的头字串后写入数据。在此之后,对象的每次后续调用会追加新数据,直到它被释放。调用重置将从文件的开头继续写入。Header
- 字符串,通常由多行组成,以换行符 (\n
) 终止。这是由用户指定的,可以修改以嵌入描述实际数据的人工可读信息。DataFormat
- 用于存储每个数据采样的格式。这可以采用在formatSpec
字符串中可指定为转换设定符的任何值,该字符串由内置的 MATLAB 函数fprintf
使用。DataFormat
应用于写入文件的所有通道。此属性的默认值为'%.18g'
,它允许以全精度保存双精度浮点数据。Delimiter
- 用于分隔同一时刻不同通道的采样的字符。写入文件的每行都映射到一个时刻,并且它包括的采样数与输入通道数量(也就是矩阵输入中传递给对象的列数)一样。
要将所有可用数据写入文件,可以使用以下单个调用。
TxtWriter(data)
通过调用 release
函数释放对文件的控制。
release(TxtWriter)
数据现在存储在新文件中。要直观地检查文件,请键入:
edit('sinewaves.txt')
由于标题占用三行,数据从行 4
开始。
在这种简单的情况下,整个信号的长度很小,可轻松放入系统内存。因此,可以在一个步骤中一次性创建所有数据并将其写入文件。
在某些情况下,这种方式无法实现或不切实际。例如,数据可能太大,无法放入单一 MATLAB 变量(太大而无法放入系统内存)。数据也可以循环创建,或从外部数据源流式传入 MATLAB。在所有这些情况下,将数据流式传入文件中可以通过类似于以下示例的方式来完成。
使用流式正弦波生成器为每个循环创建一帧数据。运行所需的迭代次数来创建数据并将其存储到文件中:
frameLength = 32; tmax = 10; t = (0:1/fs:tmax-1/fs)'; N = length(t); data = sin(2*pi*t*f); numCycles = N/frameLength; for k = 1:10 % Long running loop when you replace 10 with numCycles. dataFrame = sin(2*pi*t*f); TxtWriter(dataFrame) end release(TxtWriter)
从现有文本文件中读取
要从文本文件中读取,请创建 TextFileReader
的一个实例。
TxtReader = TextFileReader('Filename','sinewaves.txt','HeaderLines',3,'SamplesPerFrame',frameLength)
TxtReader = TextFileReader with properties: Filename: 'sinewaves.txt' HeaderLines: 3 DataFormat: '%g' Delimiter: ',' SamplesPerFrame: 32 PlayCount: 1
TextFileReader
从以分隔符分隔的 ASCII 文件中读取数值数据。其属性类似于 TextFileWriter
的属性。存在的一些差异如下:
HeaderLines
- 在Filename
中指定的文件中,标题使用的行数。对该对象的第一次调用从行号HeaderLines+1
开始读取。对该对象的后续调用继续从紧接先前读取行之后的行中读取。调用reset
将继续从行HeaderLines+1
读取。Delimiter
- 用于分隔同一时刻不同通道的采样的字符。在本例中,分隔符还用于确定文件中存储的数据通道数量。当对象第一次运行时,该对象计算第HeaderLines+1
行中Delimiter
字符的数量,即numDel
。然后,对于每个时刻,该对象使用格式DataFormat
读取numChan = numDel+1
个数值。该算法返回的矩阵大小为SamplesPerFrame
×numChan
。SamplesPerFrame
- 每次调用对象时读取的行数。该值也是作为输出返回的矩阵的行数。当到达最后可用的数据行时,可能会少于所需的SamplesPerFrame
。在这种情况下,用零填充可用数据,以获得大小为SamplesPerFrame
×numChan
的矩阵。一旦所有数据读取完毕,算法直接返回zeros(SamplesPerFrame,numChan)
,直到调用了reset
或release
。PlayCount
- 以循环方式读取文件中数据的次数。如果对象到达文件的末尾,并且对文件的读取次数尚未达到PlayCount
次,则从数据的开头(行HeaderLines+1
)继续读取。如果文件的最后几行提供的采样不足以构成大小为SamplesPerFrame
×numChan
的完整输出矩阵,则使用初始数据来完成帧。一旦对文件读取了PlayCount
次,则对算法返回的输出矩阵用 0 进行填充,并且对isDone
的所有调用都返回真 true,除非调用了reset
或release
。要无限循环遍历可用数据,可将PlayCount
设置为Inf
。
为了从文本文件中读取数据,使用更通用的流传输方式。这种数据读取方式也适用于处理非常大的数据文件。预分配一个具有 frameLength
行和 2 列的数据帧。
dataFrame = zeros(frameLength,2,'single');
当源文本文件中存在数据时,从文本文件中读取并写入二进制文件。请注意如何使用 isDone
方法来控制 while 循环的执行。
while(~isDone(TxtReader)) dataFrame(:) = TxtReader(); end release(TxtReader)
摘要
此示例说明如何创建和使用 System object 来读取和写入数值数据文件。您可以编辑 TextFileReader
和 TextFileWriter
以执行特殊目的的文件读写操作。您还可以将这些自定义 System object 与内置 System object 相结合,如 dsp.BinaryFileWriter
和 dsp.BinaryFileReader
。
有关为自定义算法创建 System object 的详细信息,请参阅创建 System object。