精华内容
下载资源
问答
  • 文件存储格式

    千次阅读 2020-06-24 19:59:42
    文件存储格式:文件存储格式是数据在磁盘上的组织方式,直接决定了数据存取效率以及被上层分布式计算集成的容易程度。 1.2 常见存储格式 创建的存储格式包括 行式存储 和 列式存储 两种: 行式存储 文本格式...

    目录

     

    一、前言

    1.1 概述

    1.2 常见存储格式

    1.3 行列存储数据组织方式

    1.4 行列存储优缺点对比

    1.5 Hadoop 的 数据存储格式实现

    二、行存储格式

    2.1 文本格式(Text File)

    2.2 Sequence File

    三、列式存储格式

    3.1 ORC 文件

    3.2 Parquet 文件

    3.3 CarbonData 文件

    3.4 ORC、Parquet 和 CarbonData 对比


    一、前言

    1.1 概述

    文件存储格式:文件存储格式是数据在磁盘上的组织方式,直接决定了数据存取效率以及被上层分布式计算集成的容易程度。

    1.2 常见存储格式

    创建的存储格式包括 行式存储 和 列式存储 两种:

    行式存储 以 文本格式Text File、key/value 二进制存储格式 Sequence File 为典型代表。

    列式存储 以 ORC、Parquet 和 Carbon Data 三种文件格式为代表。

    1.3 行列存储数据组织方式

        数据(每一行由若干列构成)在行存储和列存储系统中组织方式如下图所示。行存储以行为单位进行存储,读写过程是一致的,都是连续读取或写入同一行的所有列;列存储写数据时将数据拆分成列,并以列为单位存储(相同列存储在一起),读数据时,分别读取对应的列,并拼装成行。

    1.4 行列存储优缺点对比

    1.5 Hadoop 的 数据存储格式实现

        大数据应用场景下,通常需要在计算层并行处理文件,为了应对这种场景,Hadoop 将文件读取和写入模块抽象成 InputFormat 和 OutputFormat 组件,其中 InputFormat 可将数据文件逻辑上划分成多个可并行处理的 InputSplit,OutputFormat 可将数据以指定的格式写入输出文件。Hadoop 为常见的数据存储格式分别设计了 InputFormat 和 OutputFormat 实现,以方便像 MapReduce、Spark 等上层计算框架使用。

    二、行存储格式

    2.1 文本格式(Text File)

        文本格式是以文本字符串方式保存数据,具有简单、易查看等优点,是使用最广泛的行式存储格式,几乎所有的编程语言均提供了文本文件的读写编程接口。

    2.2 Sequence File

        Sequence File 是 Hadoop 中提供的简单 key/value 二进制行式存储格式,可用于存储文本格式无法存储的数据,比如二进制对象、图片、视频等。为了方便上层计算框架并行处理,Sequence File 物理存储上是分块的,根据压缩方式不同,Sequence File 存在三种存储格式,具体如下:

    (1)未压缩的 Sequence File

        未压缩的 Sequence File 组织方式如下图所示,由头部开始,顺序跟着一系列record(一条行记录),为了便于对数据分块和按块压缩,每个一定数目的record会写入一个同部位 SyncMark。头部中包含版本、key对应类、value对应类、是否压缩等信息;每条record以 key/value 的形式组织。

    (2)行级压缩的 Sequence File

        行级压缩与未压缩的 Sequence File 类似,如下图所示,区别在于头部加入了压缩相关的信息(压缩标记位置设为true,并记录了压缩器类),而 record 中的value值是经压缩后存储的。

    (3)块级压缩的 Sequence File

        块级压缩的 Sequence File 是以块为单位组织 record 的,如下图所示,在每块数据中,所有 record 的 key 长度、key值,value 长度和 value 值将分别被存放在一起,并一起压缩存储。

    在实际应用时,Sequence File 中的 record 的 value 值可以是普通字符串,也可以是 Java对象,或Thrift、Protobuf 、Avro对象。

    三、列式存储格式

        在实际应用场景中,数据的列数往往非常多(几十列到上百列),而每次处理数据时只用到少数几列,此时采用列式存储是非常合适的。列式存储格式的代表有 ORC(Optimized Row Columnar)、Parquet 和 Carbondata 三种,ORC 前身是 RC File,诞生于 Apache Hive,支持全部的 Hive 类型(比如map、list等),Parquet 是 Google Dremel 中列式存储格式的开源实现,可与 Thrift、Protobuf、Avro等序列化框架使用,CarbonData是华为开源的支持索引的高效列式存储格式,它们均是Apache顶级项目。

    3.1 ORC 文件

        ORC 是专为 Hadoop 设计的字描述的列式存储格式(Apache Hive 0.11版本引入),重点关注提高数据处理系统效率和降低数据存储空间,它支持复杂数据类型、ACID 及 内置索引支持,非常适合质量数据的存储。

    ORC File 由 stripe、footer 和 postscript 三部分构成:

        1> stripe 是数据存储单元,一定数目的行数据组成一个stripe,每个stripe大小约为250MB,stripe是一个逻辑处理单元,可由一个任务单独处理。每个stripe包含索引域、数组域和尾部域三部分,其中索引域记录每列最大值、最小值等信息,数据域以列为单元组织数据,尾部域存储了每列数据在数据域中的位置、编码方式等。

        2> footer 记录了ORC File 文件主体的布局,包括 schema 信息,行总数、每行的统计信息等。

        3> postscript 记录了ORC文件级别的源信息,包括footer长度、ORC版本号、采用的压缩算法等。

    (1)支持复杂数据类型

        ORC 是 Hortonworks 公司为了提高 Hive 处理效率和降低数据存储空间而设计的存储格式,它支持 Hive 所有数据类型,包括 int、string、date 等基本类型,也包括 struct、list、map 和 union 等复杂数据类型,尤其是对复杂数据类型的支持使得ORC能够定义非结构化数据。ORC 以列为单位存储数据,并根据列的类型进行编码,比如对整数列采用变长编码和差值编码,对字符串采用字典编码等,列式存储与数据编码的引入,使得ORC文件可达到很高的压缩比。Hortonworks 技术人员使用 TPC-DS Scala 500 数据集对比测试Text、RCFile、Parquet 以及 ORCFile 占用的磁盘空间,如下图所示。结果表明,想比如其他存储格式,ORCFile 能达到更高的压缩比。尽管ORC已经独立称为一个Apache项目,但由于它提供的编程 API 对复杂数据集(比如多层潜逃数据)不够友好,目前定义和创建ORC文件主要通过HQL(Hive Query Language)完成的。

    (2)支持 ACID

        Hive 在 ORC File 基础上,基于“base file + delta file”的模型实现了对 ACID 的支持。即数据首先被写入一个base file中,之后的修改数据被写入一个delta file,Hive将定期合并这两个文件。但需要注意的是,Hive ORC ACID并不是为OLTP场景设计的,它能较好地之际一个事物中更新上百万(甚至更多)条记录,但难以应对一小时内上百万个事物的场景。

    (3)内置索引

    ORC提供了 file、stripe 以及 row 级别的索引,方便用户查找定位目标数据:

    1> file 级别索引:文件级别的统计信息(如最大值,最小值等)。

    2> stripe 级别索引:ORC文件将数据划分成若干个固定大小的stripe,每个stripe可定义记录内部数据统计信息。

    3> row 级别索引:每个stripe 内部每10000行会生成数据索引信息和统计信息。

    ORC 是按列存储数据的,支持投影操作,结合各级别索引,ORC可轻易过滤掉查询无关的数据行和数据列。ORC 也允许用户根据自己的需要,在各级别索引中添加自定义信息。

    3.2 Parquet 文件

        Parquet 是由 Twitter 实现并开源的,解决 Twitter 内部日益增长的数据存储和处理问题。据有关资料介绍,当时 Twitter 的日增数据量达到压缩之后 100TB+,存储在HDFS上,采用各种计算框架(MapReduce、Spark等)和分析工具(Hive等)进行处理,其中最主要的是日志数据。日志数据结构是复杂的嵌套数据类型,例如一个典型的日志的schema有87列,嵌套了七层。所以需要设计一种列式存储格式能支持复杂的嵌套类型数据,也能够适配主流数据处理框架,而Parquet正是在这种背景下实现的。

        Parquet灵感源于 Google Dremel 的列式存储格式,它使用“record shredding and assembly algorithm”来分解和组装复杂的嵌套数据类型同时辅以按列的高效压缩和编码技术,从而达到降低存储空间,提高IO效率的目的。

        Parquet 文件存储格式的设计与ORC较为类似,是一种自描述的列式存储格式,如下图所示,它先按照行切分数据,形成一个Row Group,在每个Row Group内部,以列为单位存储数据。

    1> Row Group:一组行数据,内部以列为单位存储这些行。当向Parquet文件写入数据时,Row Group 中的数据会缓冲到内存中直到达到预定大小,之后才会刷新到磁盘上;当从Parquet文件读取数据时,每个 Row Group 可作为一个独立数据单元由一个任务处理,通常大小在 10MB 到 1GB 之间。

    2> Column Chunk:每个Column Chunk 由若干个Page构成,读取数据时,可选择性跳过不感兴趣的Page。

    3> Data Page:数据压缩的基本单元,数据读取的最小单元,通常一个Page 大小在 8KB 到 100KB 之间。

    相比于 ORC File,Parquet能更好地适配各种查询引擎(比如Hive、Impala等),计算框架(比如MapReduce、Spark等)和序列化框架(比如Thrift、Protobuf、Avro等),如下图所示,为了实现该目标,Parquet项目被分成三部分。

    1> 存储格式:定义Parquet内部存储格式、数据类型等,这一部分的主要目的是提供一种通用的,与语言无关的列式存储格式。该部分由项目parquet-format实现。

    2> 对象模型转换器:将外部对象模型映射成Parquet内部类型,该部分由parquet-mr实现,目前已经支持Hive、Pig、MapReduce、Cascading、Crunch、Impala、Thrift、Protobug、Arvo等。

    3> 对象模型:内存中数据表现形式,序列化框架,包括Thrift、Protobuf、Avro等,均属于对象模型。

    3.3 CarbonData 文件

        CarbonData 是华为开源的一种新的高性能数据存储格式,针对当前大数据领域分析场景需求各异而导致的存储冗余问题,CarbonData 提供了一种新的融合数据存储方案,以一份数据同时支持“任意维度组合的过滤查询、快速扫描、详单查询等”多种应用场景,并通过多级索引、字典编码、列存储等特性提升了IO扫描和计算性能,实现百亿级数据秒级响应。相比Parquet 和 ORC,CarbonData 的优势在于以下几点:

    (1)独特的数据组织方式:更加灵活的行列式存储格式,对于经常一起出现的列,可以将其设置为列组,设置为列组的列会按行的方式进行存储,然后与其他列或列组一起按列存储。

    (2)多层次索引:CarbonData 实现了文件和数据块级别的索引,同时引入多维主键和倒排索引实现行级别和列级别的索引。

    (3)全局字典编码:CarbonData 实现了字符串的全局字典编码,这样仅需要在磁盘上保存较小的整数而无需保存原始字符串,这大大增加了数据处理速度,并减少了存储空间。

    (4)支持数据的更新与删除:CarbonData 支持批量和离线数据更新和加载,以更好地支持OLAP场景。

        CarbonData 文件中每一个存储单元称为一个 Blocklet,对应于 Parquet 的一个 Row Group。每个 Blocklet 内按列分为多个 Column Chunk,用以存储某列数据,而每个 Column Chunk 由连续的多个 Page 组成,每个 Page 中包含 32000行数据。

    3.4 ORC、Parquet 和 CarbonData 对比

        一般而言,ORC 通常作为数据表的数据格式应用在 Hive 和 Presto 等计算引擎中,它们对 ORC 读写进行了优化;而 Parquet 提供了非常易用的读写API,用户可在应用程序(比如 Spark 或 MapReduce 等分布式程序)中直接读写 Parquet 格式的文件;而 CarbonData 在索引和数据更新等方面有良好的支持,可用在多维 OLAP 分析等场景中。

     

                                                                                           ---内容主要来自于《大数据技术体系详解:原理、架构与实践》

     

     

     

    展开全文
  • 即使始终为它们提供服务并将它们修改到服务器内存中,Redis也会将它们存储在磁盘上。 这意味着Redis速度很快,但它也是非易失性的。 数据结构的实现强调内存效率,因此与使用高级编程语言建模的相同数据结构相比,...
  • 它是在文件系统与块设备(例如:磁盘驱动器)之间。 2.文件级概念: 文件级是指文件系统,单个文件可能由于一个或多个逻辑块组成,且逻辑块之间是不连续分布。逻辑块大于或等于物理块整数倍, 3.物理块与文件系统...

    块存储,文件系统存储,对象存储区别

    概念层级的区别

    1.块级概念:

    块级是指以扇区为基础,一个或我连续的扇区组成一个块,也叫物理块。它是在文件系统与块设备(例如:磁盘驱动器)之间。

    2.文件级概念:

    文件级是指文件系统,单个文件可能由于一个或多个逻辑块组成,且逻辑块之间是不连续分布。逻辑块大于或等于物理块整数倍,

    3.物理块与文件系统之间的关系图:

    映射关系:扇区→物理块→逻辑块→文件系统

    【块存储】

    典型设备:磁盘阵列,硬盘,虚拟硬盘

    传统的文件系统,是直接访问存储数据的硬件介质的。介质不关心也无法去关心这些数据的组织方式以及结构,因此用的是最简单粗暴的组织方式:所有数据按照固定的大小分块,每一块赋予一个用于寻址的编号。以大家比较熟悉的机械硬盘为例,一块就是一个扇区,老式硬盘是512字节大小,新硬盘是4K字节大小。老式硬盘用柱面-磁头-扇区号(CHS,Cylinder-Head-Sector)组成的编号进行寻址,现代硬盘用一个逻辑块编号寻址(LBA,Logical Block Addressing)。所以,硬盘往往又叫块设备(Block Device),当然,除了硬盘还有其它块设备,例如不同规格的软盘,各种规格的光盘,磁带等。

    【文件存储】
    典型设备:FTP、NFS服务器,SamBa

    把存储介质上的数据组织成目录-子目录-文件这种形式的数据结构,用于从这个结构中寻找、添加、修改、删除文件的程序,以及用于维护这个结构的程序,组成的系统有一个专用的名字:文件系统(File System)。

    【对象存储】
    典型设备:内置大容量硬盘的分布式服务器

    分布式存储的应用场景相对于其存储接口,现在流行分为三种:
    对象存储: 也就是通常意义的键值存储,其接口就是简单的GET、PUT、DEL和其他扩展,如七牛、又拍、Swift、S3

    管理这些文件和底层介质的组织结构,然后给每个文件一个唯一的标识,其它系统需要访问某个文件,直接提供文件的标识就可以了。存储系统可以用更高效的数据组织方式来管理这些标识以及其对应的存储介质上的块。

    之所以出现了对象存储这种东西,是为了克服块存储与文件存储各自的缺点,发扬它俩各自的优点。简单来说块存储读写快,不利于共享,文件存储读写慢,利于共享。能否弄一个读写快,利 于共享的出来呢。于是就有了对象存储。

    使用上的区别

    这三种存储,分别对应了不同的访问协议,这也就决定了他们的本质差别。

    先说一下文件存储,主要操作对象是文件和文件夹。以 NFS 为例,文件相关的接口包括:LOOKUP/ACCESS/READ/WRITE/CREATE/REMOVE/RENAME 等等,文件夹相关的接口包括:MKDIR/RMDIR/READDIR 等等。同时也会有 FSSTAT/FSINFO 等接口用于提供文件系统级别的信息。POSIX,SAMBA 等也是文件存储协议。协议更注重接口的灵活,以及访问权限控制。

    块存储,主要操作对象是磁盘。以 SCSI 为例,主要接口有 Read/Write/Read Capacity/Inquiry 等等。FC,iSCSI,也是块存储协议。和文件存储相比,没有文件和目录树的概念,一般协议也不会定义磁盘的创建和删除操作。协议更注重传输控制。

    对象存储,主要操作对象是对象(Object)。以 S3 为例,主要接口有 PUT/GET/DELETE 等。和文件和对象存储相比,没有随机读写的接口。和文件存储相比,没有目录树的概念。协议更注重简洁。

    分布式文件存储,块存储,对象存储

    文件、块和对象是三种以不同的方式来保存、整理和呈现数据的存储格式。这些格式各有各的功能和限制。文件存储会以文件和文件夹的层次结构来整理和呈现数据;块存储会将数据拆分到任意划分且大小相同的卷中; 对象存储会管理数据并将其链接至关联的元数据。


    什么是文件存储?

    img

    文件存储也称为文件级存储或基于文件的存储,且正如您所想:数据会以单条信息的形式存储在文件夹中,正如您将几张纸放入一个马尼拉文件夹中一样。当您需要访问该数据时,您的计算机需要知道相应的查找路径。(注意,这可能会是一条蜿蜒冗长的路径。)存储在文件中的数据会根据数量有限的元数据来进行整理和检索,这些元数据会告诉计算机文件所在的确切位置。它就像是数据文件的库卡目录。

    请试想一下塞满文件柜的储藏室。每个文档都会按照某种类型的逻辑层次结构来排放 ——按文件柜、抽屉、文件夹,然后再是纸张。“分层存储”这个术语就是这么来的,而这就是文件存储。它是适用于直接和网络附加存储系统的最古老且运用最为广泛的一种数据存储系统;而且,这种系统您可能已经用了数十年。只要您访问保存在个人计算机上的文件中的文档,您就是在使用文件存储。文件存储具有丰富多样的功能,几乎可以存储任何内容。它非常适合用来存储一系列复杂文件,并且有助于用户快速导航。

    问题是,就像文件柜一样,虚拟抽屉只能打开到一定的程度。基于文件的存储系统必须通过添置更多系统来进行横向扩展,而不是通过增添更多容量来进行纵向扩展。


    什么是块存储?

    img

    块存储会将数据拆分成块,并单独存储各个块。每个数据块都有一个唯一标识符,所以存储系统能将较小的数据存放在最方便的位置。这意味着有些数据可以存储在 Linux 环境中,有些则可以存储在 Windows 单元中。

    块存储通常会被配置为将数据与用户环境分离,并会将数据分布到可以更好地为其提供服务的多个环境中。然后,当用户请求数据时,底层存储软件会重新组装来自这些环境的数据块,并将它们呈现给用户。它通常会部署在存储区域网络 (SAN) 环境中,而且必须绑定到正常运行的服务器。

    由于块存储不依赖于单条数据路径(和文件存储一样),因此可以实现快速检索。每个块都独立存在,且可进行分区,因此可以通过不同的操作系统进行访问,这使得用户可以完全自由地配置数据。它是一种高效可靠的数据存储方式,且易于使用和管理。它适用于要执行大型事务的企业和部署了大型数据库的企业。这意味着,需要存储的数据越多,就越适合使用块存储。

    但是,块存储有一些缺点。块存储的成本高昂。它处理元数据的能力有限;这意味着,它需要在应用或数据库级别进行处理 — 开发人员或系统管理员又多了一件事要担忧。


    什么是对象存储?

    img

    对象存储,也称为基于对象的存储,是一种扁平结构,其中的文件被拆分成多个部分并散布在多个硬件间。在对象存储中,数据会被分解为称为“对象”的离散单元,并保存在单个存储库中,而不是作为文件夹中的文件或服务器上的块来保存。

    对象存储卷会作为模块化单元来工作:每个卷都是一个自包含式存储库,均含有数据、允许在分布式系统上找到对象的唯一标识符以及描述数据的元数据。元数据很重要,其包括年龄、隐私/安全信息和访问突发事件等详细信息。对象存储元数据也可以非常详细,并且能够存储与视频拍摄地点、所用相机和各个帧中特写的演员有关的信息。为了检索数据,存储操作系统会使用元数据和标识符,这样可以更好地分配负载,并允许管理员应用策略来执行更强大的搜索。

    对象存储需要一个简单的 HTTP 应用编程接口 (API),以供大多数客户端(各种语言)使用。对象存储经济高效:您只需为已用的内容付费。它可以轻松扩展,因而是公共云存储的理想之选。它是一个非常适用于静态数据的存储系统,其灵活性和扁平性意味着它可以通过扩展来存储极大量的数据。对象具有足够的信息供应用快速查找数据,并且擅长存储非结构化数据。

    当然,它也存在缺点。无法修改对象 — 您必须一次性完整地写入对象。对象存储也不能很好地与传统数据库搭配使用,因为编写对象是一个缓慢的过程,编写应用以使用对象存储 API 并不像使用文件存储那么简单。

    展开全文
  • 磁盘文件系统管理详解】

    千次阅读 2016-04-14 11:16:13
    磁盘文件系统管理详解 【原创】版权所有翻者必究。 【参考文献:马哥视频】 目前市场主流的磁盘是机械式硬盘  u盘,光盘,软盘,硬盘,磁带   机械式硬盘硬盘内部由一个个同心圆组成】如下图: 硬盘内部...

    磁盘及文件系统管理详解

    【原创】版权所有翻者必究。

    【参考文献:马哥视频】

    目前市场上主流的磁盘是机械式硬盘

             u盘,光盘,软盘,硬盘,磁带

     

    机械式硬盘

    【硬盘内部由一个个同心圆组成】如下图:


    硬盘内部所有盘片都固定在一根“轴”上,所以:所有盘面都是同步运动。如下图

    硬盘这里面还有个读写的磁头,用来改变盘面中磁块的磁性变化。从而来存储0或1,

    其中0代表磁块没有磁性,1代表磁块有磁性


    在硬盘中说以磁盘都是双面可读写的,说以每一个盘面都有一个磁头,一个磁盘中就要两个磁头。每个磁头都悬浮在盘面上,距离盘面非常近几乎只有几微米的距离

    图下为硬盘的外部结构:

    盘面的结构如图下:

    在盘面中有很多磁道,每个磁道由内而外每个磁道的周长都不一样。因此外面的磁道要比里面的磁道能存储更多的数据。为了便于数据管理,在每个磁盘上磁道的空间是划分一个一个的扇区即:Secto

    Secto(扇区):用来存储用户的数据并且包含自己扇区的编号已经扇区的位置等信息。

             所以对于一个原本有512个字节的扇区可以就只有500个字节可以用来存放数据,还有12个字节是记录扇区自己的信息。这就 为什么我没通常购买的500G的硬盘实际上没有500G的原因。以及购买的16G的u盘也没有16G的原因。

             在硬盘运作时,所有盘片的所有盘面都是同时工作的,为了提高存储速度,对于一个文件很有可能在每个盘面,都存储了文件的一部分。并且存储时是按照整个磁道(一个圆)来进行数据存储的

    图上鼠标所指的位置,位于不同磁盘相同编号的磁道所组成的一个立体的圆叫做“柱面”(Cylinder)

    【柱面(Cylinder)】:不同盘面上相同编号的磁道组成柱面

             通常磁盘在使用时都要划分分区,如果不划分分区,就意味着整个磁盘只能存储一个“文件系统”。如果不划分分区,每一个分区都可以是一个独立的文件系统

    【分区】:在磁盘中建立起来的逻辑边界

     

    在磁盘刚出厂时,厂商会对磁盘做一次低级格式化:低级格式化就是用来划分磁道的,把整个磁盘按照磁盘的旋转速度,物理属性,等一些信息来划分好磁道,扇区等。但磁盘里面并没有任何分区。

    【低级格式化】:用来划分磁道

    【分区(Partition)】:把整个磁盘划分成多个不同的逻辑组成结构,每个组成结构里面可以创建不同的“文件系统”。所以分区是用来创建“文件系统”的

     

    每一个磁盘都有个MBR(MasterBoot Record)或者(Main Boot Record)

    【MBR】:主引导记录

             主引导记录(MBR)在整个磁盘的第0盘面0磁道第0扇区,一共是512个字节,这512个字节不属于任何操作系统即(你在磁盘上安装操作系统后你的系统也无法正常访问这512个字节),MBR是属于磁盘的,是全局的,是独立在操作系统之外的。

             在MBR中一共划分为三段:

    【第一段:446bytes】:BootLoader,是一段程序即(主引导记录)即使你磁盘上有操作系统,没有这个446个字节的主引导记录也是无法启动操作系统的。

    【第二段:64bytes:】在这个里面每16byte可以标示一个分区所有一个磁盘最多可以划分4个主分区

    【第三段:2bytes】Magic Number(摸数)主要是标记MBR是否有效

             注意:在硬盘中MBR当中的BootLoader是启动操作系统的重中之重

     

    当电脑刚开启时,类存条中的的数据时空的,cpu所读写的数据都来自于内存,所有当电脑刚开机是BIOS中有一段程序代码能完成电脑自身健康状况的检查(即电脑的自检)BIOS中设定了如何检查自身的健康状况。

    在开机后,BIOS通电后能将自己的那段代码映射到到内存条最开始处的一段空间中。

    电脑开机后cpu就开始试图去加载内存条中最开始的一段程序代码。这段代码刚好是BIOS那么接下来就执行BIOS指令。BIOS指令执行完成后,BIOS发现系统自检没有任何问题了,接下来就更具BIOS中设定的启动次序挨个的去寻找启动设备的MBR(假如第一是光盘,那么就找光盘的MBR如果光盘没有MBR就找第二个设备的MBR,如果有的设备有MBR但MBR已经损坏,那么系统就开始报错。告诉你启动不了。(注意:系统启动时是在第一个启动设备没有的情况下才会去寻找第二个启动设备))如果开始启动硬盘,那么就开始读取硬盘的MBR。BIOS就会将硬盘中的MBR加载到内存条中,先开始运行446个字节的BootLoader这段程序运行完成后然后在运行64个字节的分区表。然后根据分区表去读取对应的操作系统。(因此,如果没有分区表,那么就无法寻找我们的操作系统了)所有BootLoader加载进来后BIOS就退出了,BIOS就将执行的指令交给了BootLoader(即告诉cpu接下来执行BootLoader)。于是cup运行BootLoader后根据我们自己安装的操作系统,他会找到那段特定分区,于是BootLoader就会去对应的分区上寻找对应操作系统的内核,如果找到操作系统的内核,就将内核读取进入内存条中。当内核读取进入内存条并解压缩运行完成以后,BootLoader就将cpu的控制权交给操作系统的内核。接着操作系统的内核就开始启动自身,根据我们的配置能够找到“文件系统”在什么地方,找到操作系统所需要运行的什么程序。终止操作系统启动完成。(即看到桌面)

           所有无论是硬盘中无论是446字节的BootLoader坏了还是64字节的分区表中哪一个坏了,我们硬盘中的操作系统都是无法启动。不管如此如果硬盘中的64字节的分区表丢失,将硬盘连接到其他主机上硬盘里面的文件也是无法读取。

           所有MBR对于硬盘是相当重要的

     

             虽然64字节存放分区表的位置只能创建4个主分区,但是我们可以拿一个16个字节用来标记。这个标记不是标记分区信息,而是用来存放指针,指向硬盘中一个更大的位置来告诉计算机这个地方还有分区信息,让计算机来读取。所有在哪个更大的空间中就能存储更多的磁盘分区信息。而这个创建为指针的分区叫做:“扩展分区“。所有通过这种方式就能在磁盘中创建更多的分区

             例如:假如硬盘有200G,本来就可以存放4个主分区的位置你将第第一个主分区标记为:20G的空间,第二个主分区标记为:30G的空间,第三个主分区标记为40G的空间,第四个也创建主分区标记为50G的空间,那么还有200-30-40-50=80G的空间没有标记出来,那么你这个200G的硬盘就只能使用120G了,剩下的80G就无法访问了,因为你没有做分区标记,你自能创建4个主分区。如果(如下图):你将最后一个16字节的位置用作指针指向另外一块空间,那么在哪个更大的空间中就还可以继续创建更多的分区信息了。(即在最后一个用来创建扩展分区)

             说以硬盘一般来讲都是三个主分区一个扩展分区,或者一个主分区一个扩展分区。

    即主+扩展<=4,而扩展分区在一个系统上只能有一个(这里指的扩展分区是16个字节去指向一个更大的空间。不是说在哪个更大空间中创建的分区)

    注意:磁盘分区是按照“柱面”(柱面(Cylinder):不同盘面上相同编号的磁道组成柱面

    )进行的即:按柱面存储。

    【按柱面存储】硬盘存储一个文件为了加速文件的存储也是按照柱面来存储。即:存储一个数据会在每个盘面都存储,由于盘片的运转速度,运转方式,运转次序都是一致的而且读写数据的磁头都是固定在一个机械臂上,所以它们都是同步运动的。因此在存储数据的时候,它们每一个盘面上的磁头都在不同盘面上对应的相同位置(如下图)。这就是所谓的“按柱面存储”

    所以,磁盘上的分区也是按照柱面进行的。一般来讲,柱面的编号按照盘面来说,最外面的为第0柱面,依次向里递增。由于磁盘是按照同心旋转的速度来实现数据的读取和存储。

    那么磁盘的读写速度就取决于盘面旋转的速度和磁头的旋转(跨越磁道)的速度。这就叫做磁盘的读写延迟。

             所有衡量一个硬盘的指标就包括它的“平均寻道时间“,那么盘片的转速越快,那么硬盘的性能越好。笔记本的硬盘一般都是5400转每分钟(RPM),台式机硬盘一般都是7200转没分钟。工业基本的硬盘比如:SCSI有10000转每分钟(即10k转),15k的转速。所以硬盘的转速越快,性能越好,但同时磁盘的发热量就越大。

             对于5400转/分钟的转速已经相当快了,如果磁盘直接在空气中旋转,一定会烧起来。所以在硬盘内部是真空的,也不能有任何灰层,应为我们知道磁头距离盘面的距离只有几微米,所以一粒灰层在上面绝对相当于一座山。所以要是你拆开了硬盘核心部件然后再合上运行,绝对一股白烟硬盘就没了。当然把里面做成真空主要是避免灰层。

             由于磁盘旋转是同轴的,即固定角速度,那么在同一个时间段里,磁头在里面和外面所划过的距离也就不一样。因此磁道越靠外,其读写速度越高。所有我们经常频繁访问到的数据最好放在最外面的磁道。通常我们划分磁盘时C盘都在最外面的磁道上,然后接下来的是D盘….

             通常我们硬盘上划分好分区后,就可在分区上存储文件了,那么接下来就要提到“文件系统”

             在存储文件过程中,对于磁盘来讲存储的都是0101代码,(即:0代表磁块没有磁性,1代表磁块有磁性)那么怎么区别一个文件的起始点和结束点,就要对磁盘里面数据做一些逻辑的边界。(即从哪个地方起始,哪个地方结束)即:能够有一个寻址定位的方法。

    为了快速定位去找到对应的文件,那么就要建立一个编目即:Metadata:

    【Metadata】编目:为了检索文件,标识文件的一种数据。

             所有创建完分区后为了检索文件,快速的定位文件,就要在这个分区上创建“文件系统“,文件系统简单来说类似于一个软件,本身并不是在磁盘分区上,但是文件系统中的数据是在文件系统所管理的分区上。文件系统能把一个分区成两块,第一块为:源数据存储区,第二块为:数据存储区。而数据存储区又划分为一个个逻辑单元,在源数据中记录文件存放在那些单元中(即文件地址),以及文件编号。(如下图)即每存储一个文件就要在编目中添加一条记录,记录文件的编号和文件的地址。我们此文当中将这条信息称为:条目即(索引结点:inode)。

    当然,上面的比如只是很粗糙。真正的文件系统不止这些。

    比如:在上面中系统分了很多很多磁盘块,系统为了知道那些磁盘块已经使用了,那些磁盘块没有使用。就要建立“快位图:bitmap“。快位图也是存放在源数据中。简单说,就是在块位图(bitmap)中模拟了一个数据存储区,里面标示了数据存储区所分割的逻辑单元,哪一个已经使用了,哪一个还没有使用,分别用(0101)来标示。那么系统为了寻找整个分区中有那个块位图还没有使用,就可以通过直接扫描块位图来获取,就不必整个分区做一次全盘扫描,这样就大大提高了查询文件的速度。

    【块位图bitmap】:即每一个块中标记了一个逻辑单元(即一个bit标记一个逻辑单元)存储位为1表示已经使用,存储位为0表示没有使用。是加速查找空闲磁盘块的一种机制。

             其实在源数据存储区中其间已经制定了一段固定区域来存储“编目“。假设文件特别多,当我们新建一个文件时,就要在”编目”中保存对于的位置信息。假设一个编目的空间有10M,那么要知道这个编目中有哪些空间是已经标记了文件,哪些空间还没有标记文件。就为编目也要创建一个位图:那么位图中对应了编目中的每一个条目即”索引结点inode”:

    【inode(inodexnode)索引结点】:inode中存放了文件的ID号,对应文件的权限,属主和属组,大小,时间戳等。唯独没有的就是“文件名“。

             假如一个文件特别大,在inode一个条目中无法全部都标识该文件所使用的所有逻辑单元(也称磁盘块),那么inode中还可以向创建磁盘分区一样标记一个指针来指向下个inode号,说明该inode号中所标识的逻辑单元(也称磁盘块)也是和他同一个文件。那么这样的inode条目就像扩展分区一样。相当于一个间接目录,在文件系统中有二级间接目录,三级间接目录…….那么在一个条目里面,无论通过直接的或间接引用,最终能引用多少个间接inode决定了单个文件最终有多大。即,一个条目的存储空间中能存放多少条索引结点(inode)号,一个索引结点最多能引用多少个逻辑单元。

             在有些分区上所说的最大支持单个文件大小最大为多少多少G就是靠这个来决定的。

    向FAT32文件系统最大支持为4G

             我们通常所说的目录(即文件夹)也是文件,那么上面inode号中没有存储的文件名都存放在目录(即文件夹)中,文件夹自身也有大小(这里的大小不是文件夹里面存放文件的大小和,而是文件夹名,和文件名所占字节的大小和)。这里要注意的是目录也是个文件,所有目录也需要占据磁盘块即(上面说的逻辑单元)。在目录的磁盘块(逻辑单元)中所存储的信息分为两段,第一段为inode号码即:(inode编目中的一个编目号码(相当于一个文件的索引)),这个inode号码对应的文件名。如下图:

             在linux系统中根是自引用的,即cpu可以直接访问到根。那么要寻找\tmp\wenjian.txt个文件,则cpu先找到根即(\)目录(即文件夹)所对应的磁盘块(逻辑单元),然后然后在这个磁盘块中(逻辑单元)找到tmp文件名所对应的inode号码,然后拿着这个号码去编目中寻找inode(索引结点)号码所对应的磁盘块地址(即逻辑单元),这个磁盘块(逻辑单元)中找到wenjian.txt文件名所对应的inode(索引结点)号码,再更具这个inode(索引结点)号码去编目中寻找inode(索引结点)所对应的磁盘块(逻辑单元)(可能有多个磁盘块),然后将这些磁盘块中所记录的二级制0101代码。就是该文件的内容。即wenjian.txt的内容。

             注意:在文件系统中每一个文件都对应一个inode(索引结点)。

             【创建文件原理】

    有了上面的逻辑,假设要在/tem/ftp/目录下创建一个文件叫wenjian.txt。那么程序首先要找到tmp目录的磁盘块,然后在根据ftp所对应的inode号码找到ftp目录的磁盘块,在该磁盘块中添加一条信息,记录系统给该文件在编目中申请的inode编号,和该文件的文件名。在编目中除了记录inode编号外还要记录系统给该文件通过扫描“块位图bitmap“给改文件分配的空闲磁盘块地址。然后再将文件内容写入到该文件inode号码所对应的磁盘块中。

    【复制文件原理】

    有了上面创建文件的原理,那么复制文件的原理几乎和创建文件的原理一样,这里不再做说明。

    【删除文件原理】

    删除一个文件,则只需要在该文件所在目录的磁盘块中删除此目录对应的inode号所对应的文件名那条记录删除,然后去编目中删除该文件对应的inode号的记录删除,已经在快位图bitmap中将inode好所对应的磁盘块重新标记为0(即未使用)便可完成删除。所有删除文件是没有真正抹掉该文件所对应的所有磁盘块中的二进制代码的。所在在系统上删除文件一般都很快。

    【同一个分区中移动文件原理】

    同理,移动文件只需要将文件原有目录中标记的文件信息删除,在新的目录中添加该文件的一条记录。就ok了,所有我们通常在同一个分区中移动文件的速度相当快。

    所以为什么会有文件恢复工具,能恢复你之前在磁盘上删除的文件,原因就在这里。向360上面的文件粉碎机,就是在你该文件所对应的数据磁盘块中,写入一大堆随机产生的二级制代码覆盖掉你原有的文件。比方说,向那些磁盘块中全部填0,那么你的文件要想找回来只是比较困难了,并不一定找不回来。至于怎么找回来,本文档不做解释。

     

    事实上,在文件系统中远不止我们上面所说的那么简单,你要想,要是每个文件都建立一个inode号,那么在那么多的inode号中寻找一个文件依然是很麻烦的。真正的文件系统上我们上面所说的那个算一个“块组“(block group)。即:每一个快组都有我们上面所说的功能。那么一个磁盘上会创建多少个这样的”块组”(block group)事实上是不确定的。

    【block group块组】

             在文件系统中每一个块组(block group)都有一个块位图,一个编目。

    在这些块组中怎么管理这些块组的方法和上面说道的单个块组的管理方法一样,一样有这些块组的一个快位图,这个块位图可以叫做“超级块位图”。这就是文件系统。

     

     

    【格式化】:

             格式化分为:

                       低级格式化:用来划分磁道,一般有厂商完成。

                       高级格式化:就是我们通常说的格式化,即创建文件系统。

    文件系统属于内核的功能

    文件系统有

                       windows常用的文件系统:FAT32,NTFS

                       光盘文件系统:ISO9660

                       网络文件系统:CIFS(通用互联网文件系统)

                       linux常用的文件系统:ext2,ext3,ext4,xfs,reiserfs,   jfs(日志文件系统)

                       liux常用的网络文件系统:nfs

                                                            集群文件系统:ocfs2

                                                            全局文件系统:gfs2

                       linux 上FAT32叫做vfat. 

     

    我们上面说到的哪个文件系统属于ext系列的文件系统,不过其他文件系统在管理上都大致是这样的思路。大体上都是近似的。

     

    至于文件操作系统是怎么识别不同格式的文件系统的,这里不做解释。

                      对于文件系统来讲,数据都分为两类:

                       一类是:源数据:即要存储的文件属性信息(比方说,文件大小之类……)

                       另一类是:文件的路径,即目录(文件夹)

    经过上面讲的文件系统底层逻辑分析大致上在一块硬盘中我们首先要将磁盘分成一个个块即:block,这些块组合起来就叫块组(block group)。

    【block块】:磁盘块,是文件系统管理磁盘空间的逻辑结构,和磁盘本身没有关系。每一个块都有一个编号。从0开始编号。

    【inode】:index node记录文件属性,权限。等…..

    所以磁盘中一个分区在格式化后,大小都会发生变化,其中有一部分空间划分出来,给“源数据”预留的。

             由此我们知道,每个磁盘块可能属于一个文件,而一个文件可能对应多个磁盘块。所有inode的个数和磁盘块的个数并不是一一对应的。

    【block size】块大小

    对文件系统来讲块(block size)大小是确定的,指的是2的n次方个字节。一般来讲只能是1024bit,2048bit,4096bit。

             在内存中内存中的空间是用“页框”来存储的。页框的大小通常是4k,即若磁盘是1K的大小则一个页匡就可以存放4个磁盘块。若是4k的则存放一个磁盘块。但是这个页框的大小对于不同cpu来讲32位和64位的大小就不一样。

             不管怎么样,我们现在至少知道块(block)是有块大小。我们说过一个磁盘块只能属于一个文件,假如一个磁盘块是1K的,那么你创建该文件后,这个文件默认的最小大小就为1k。如果这文件只占用了一个字节,但这一个磁盘块都必须给它。所以4k的磁盘块和1k的磁盘块浪费的程度就不一样。所以在windows上点击文件看属性时,会告诉你一共有多少个空间占据了多少个空间,一般来讲占据的空间都要比实际空间大。就是这个道理。(如图)

    说以一个文件如果是1个字节,第二个文件如果是1个字节,那么两个文件的大小占据的空间就是2K(前提是磁盘块的大小为1k),如果像图上磁盘块的大小为4k那么这两个文件占据的大小就为8k。这就是占据空间和实际文件大小的区别。

             所以占据磁盘块的文件一般都是如下两类

                       普通文件:文件内容

                       目录文件:存放文件名称和inode的对应关系

    【bitmap位图】:加快某种特定数据结构中数据查找速度的一种非常重要的数据结构

    因此位图中为了提高文件的查找速度,实际上在数据存储区域还划分了块组即(block group)

    一般每个位图都是用一个磁盘块来标示,比如一个块是1k的即1024*8为这个位图所能标示的磁盘位。那么一个位图所能标示的磁盘位最后也可以确定。那么一个块组中包含了多少个块,还要取决于块的大小。哪不管怎么讲,这就决定了在不同的磁盘分区上只要块的空间大小(block size)不一样,那么块组中有多少个块也就不一样了。所有对于当前分区中有多少个块组还得要根据块的空间大小(block size)来确定。由于块组的数量之多,那么在一个分区中也保存了这些块组的信息即:超级块(super block)

    【super block】:超级块,保存了整个分区中的全局信息,包括

    1、  块组的数目

    2、 每个块组中包含多少块

    3、 块大小(block size)

    4、 空闲磁盘块

    5、 已用磁盘块

    6、 空闲inode

    7、 已用inode

             那么如果一个磁盘分区中的超级块坏了,那么这个磁盘中的数据也就意味着无法访问了。为了防止这一事情的发生,超级块(super block)可以有多个备份。

             由于块组的数量太多,而超级块也只是一个块,记录不了所有块组的信息,那么在系统中还有一个东西:块组描述符

    【GDT快组描述符】:这里面保留了当前系统上有多少个块组,每一个块组从第几个块开始到第几个块结束。等等这样的相关信息。

             那么“块组描述符”损坏了,整个分区中的文件也同样无法查看了,同样也要备份。

    如上图:假如图1代表一个分区,Block Group0, BlockGroup 1….. BlockGroup n分别代表这个分区中所有的块组(block group)。那么每个块组(block group n)中都有一个图2

             在分区中

    【Boot Block】:为任何一各分区中编号为0的一个块,这个快是不能被使用的,存放的是“引导块“,假如这个分区上如果安装了操作系统,那么这个操作系统的BootLoader就存放在这个块中。

    注意:一个磁盘的MBR是一个扇区(sector),那个扇区不属于任何一个分区,这个扇区里面而且只能是512个字节,而Boot Block则是任何一个分区中的第一个块,一般在多系统互存的时候会用到这第一个块。即你的电脑上要安装双系统时才会用到。如果你只有一个操作系统,这个操作系统的引导信息是安装在硬盘的MBR中的。如果你安装了两个操作系统,那么第二个操作系统的引导信息就安装在这个第一个操作系统自己所在分区的Boot Block中。一般来讲任何操作系统启动都需要boot Block而boot Block 是一段程序得安装在MBR中。如果没安装在MBR中,那么就不能作为开机启动的操作系统。如果MBR已经被一个操作系统占据了,而第二个操作系统的引导信息可以安装在这个第一个操作系统自己所在分区的Boot Block中。但是一定要记得,放在分区中的Boot Block中是没有意义的,要是MBR损坏了,即使你分区中的Boot Block中有启动程序也是启动不了的。那就意味着分区中的Boot Block是被MBR中的BootLoader引用的。

             所有除了第一个块中,其他的块就按照源数据直接被划分为块组了。(并不是按照前面写的划分成什么源数据区和数据区。)

    那么在块组中

    【super Block】超级块,存放在每个组中第1个块中,为了防止损坏,早期每个块组中的第1个块,都是super Block。后来为了节省空间,只是在第0个块组,第3个,第7个…里面做了备份。只存个3-5份。

             默认情况,系统寻找的都是第0个块组中第1个块中的SuperBlock,如果这个块组坏了,就去寻找备份的块组。如果不会,自己可以手动修复,因为有备份。

    【GDT】块组描述表,块组描述表中存的是当前这个分区中每个块组的块组名,每一个块组的起始磁盘块,结束块的编号。即块组边界等信息。所有这个信息也是不能被损坏的,也是需要备份多次。根据块组(block Group)的个数不同,GDT所占用的空间大小未必是一个磁盘块。

    【BlockBitMap】当前块组的块位图

    【inodeBitmap】当前块组的inode位图

    【inode Table】当前块组的inode表

    【Data Blocks】当前块组的所有块

    到这里,文件系统是怎么运作的,以及组成基本就说清楚了。

    那么“目录”中是存放文件的inode号和文件名等信息,即格式为

    在目录中每一行用来引用一个文件,而没一行的格式为:

             【Inode】:文件的inode号

             【rec_len】:最近一次的访问时间长度

             【name_len】文件名称长度

             【file_type】文件类型

             【name】文件名

    那么如果磁盘中文件太大,一个inode存不下,那么就按照下面直接引用和间接引用来调用。即:直接指针,一级间接指针,二级间接指针。

     

     

     

     

     

    展开全文
  • 即使始终为它们提供服务并将它们修改到服务器内存中,Redis也会将它们存储在磁盘上。 这意味着Redis速度很快,但它也是非易失性的。 数据结构的实现强调内存效率,因此与使用高级编程语言建模的相同数据结构相比,...
  • 文件存储前言文件存储内存内部存储外部存储内部存储操作API读写操作外部存储操作公共目录私有目录私有文件私有缓存 前言 众所周知,数据存储在每个应用中都会用到,那所用到的技术应该怎么选呢,这里Android给...

    前言

    众所周知,数据存储在每个应用中都会用到,那所用到的技术应该怎么选呢,这里Android给开发者提供了几种方法去保存常用应用数据,至于你想选择哪一种方式,取决于你的特定需求;例如这个数据是本应用私有的还是跟其它应用共享以及数据存储所需的内存空间等

    • Shared Preferences:这种方式通常用来保存私有原始数据,以键值对形式存储;这也就意味着这些数据只能由本应用访问
    • Internal Storage:这种方式是将私有数据保存在内部存储(设备内存)中,实际上是使用文件流进行读写
    • External Storage:这种方式是将公共数据存储在共享外部存储上;也就是将数据存储在SD卡上,存储在这上面说明数据是开放的,其它应用可以直接访问;这跟上面一种都是平常所说的文件存储
    • SQLite Databases:这种方式是将结构化数据存储在私有数据库中;这也就是常说的数据库存储,使用SQLite保存数据,这些数据是私有的
    • Network Connection:这种方式是将数据存储在Web服务器上,也就是通常所说的网络存储

    笔者上一篇文章讲述了Shared Preferences的工作原理及使用封装,这篇文章来掰掰第二种和第三种方式

    文件存储

    本文所含代码随时更新,可从这里下载最新代码
    传送门Mango

    说到文件存储,就必须说下Android世界中的文件系统了

    大家使用Android手机的时候,多多少少都去过设置->应用界面,选择一个应用打开,可以看到上面有清除数据,清除缓存两个按钮;有的手机可能做的页面不一样了,只有一个清除数据按钮,但是还是能看到列举出来的类型;那你知道这两个按钮是清除的哪里的数据吗?

    我们经常听到内存,内部存储,外部存储这几个概念,那它们分别表示什么呢?我们开发者存储数据的时候应该把数据存储到什么地方呢?

    内存

    内存指的是手机在运行应用程序时需要的存储空间,也称为RAM,即运行内存,这个值基本上在手机出厂后就确定了;当你买手机的时候,销售员跟你说这个手机配备8G内存+128G存储等配置时,这个8G指的就是RAM,也就是应用运行时理论上能利用到的最大内存了;这个有点类似于电脑上的内存条,只不过电脑内存条可以后期增加,理论上这个值越大,基本上手机运行的就越流畅

    内部存储

    英文称为Internal Storage,我们可以将文件直接保存在设备的内部存储中。 默认情况下,保存到内部存储的文件对应用程序是私有的,而其他应用程序无法访问它们(用户也无法访问,除非root),使用内部存储不需要额外的权限; 当用户卸载应用程序时,将删除这些文件;从技术上来讲如果你在创建内部存储文件的时候将文件属性设置成可读,其他app能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。 内部存储空间十分有限,因而显得可贵,另外,它也是系统本身和系统应用程序主要的数据存储所在地,一旦内部存储空间耗尽,手机也就无法使用了。所以对于内部存储空间,我们要尽量避免使用

    我们开发中经常说某些文件在data目录下,这个data文件夹就是我们常说的内部存储,里面有两个比较重要的子文件夹

    • app文件夹:这里存放应用的apk文件
    • data文件夹:这里有很多以应用包名命名的文件夹,存放着应用数据

    打开其中一个应用文件夹,通常有如下文件

    data/data/包名/shared_prefs //该目录下存放很多SharedPreferences数据,都是一些xml文件
    data/data/包名/databases //该目录下存放db格式的文件,也就是数据库数据
    data/data/包名/files //该目录下存放普通数据
    data/data/包名/cache //该目录下存放缓存文件

    外部存储

    英文称为External Storage,每个Android兼容设备都支持可用于保存文件的共享“外部存储”,它可能是可移除的存储介质(典型如SD卡),也可能是不可移除的存储介质(如现在很多一体机内置的存储器);外部存储是相对于内部存储而言的,不过存储在这上面的文件是所有者可见的,所有人都有权限操作,不过前提是需要申请权限

    要在外部存储上读取或写入文件,您的应用必须获取READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE系统权限;如果您需要同时读取和写入文件,则只需要请求WRITE_EXTERNAL_STORAGE权限,因为它也隐式地要求读取权限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

    注意:android6.0以后引入了运行时权限的概念,要注意只在AndroidManifest.xml申请是不够的
    注意:从Android 4.4(api19)开始,如果您在外部存储上只读取或写入应用程序专用的文件(即在私有目录读写),则不需要这些权限

    外部存储的最外层目录是storage文件夹,也可能是mnt文件夹,这个根据厂家不同有不同的结果
    一般来说,在storage文件夹中有一个sdcard文件夹,这个文件夹中的文件又分为两类,一类是公共目录,还有一类是私有目录

    • 公共目录:有九大类,比如DCIM、Download、Music、Movies、Pictures、Ringtones等这种系统为我们创建的文件夹;这些目录里的文件所有应用可以分享
    • 私有目录:就是Android这个文件夹,这个文件夹打开之后里边有一个data文件夹,打开这个data文件夹,里边有许多包名组成的文件夹,这个文件夹存放了应用私有数据

    私有目录如下

    storage/sdcard/Android/data/包名/files
    storage/sdcard/Android/data/包名/cache

    经过上面的分析,应该对这些有一个大致的了解了,RAM内存是我们开发的过程中需要注意的,不要内存泄漏,不要无限new内存,因为那样会使得手机运行内存紧张,运行卡顿;通常来说我们很少去操作内部存储空间,因为没有root权限,我们动不了这块,内部存储通常是由系统来维护的,不过在代码中Google还是给了我们API访问这些文件夹

    通常情况下内部存储空间都很有限,在开发中我们操作的最多的还是外部存储,Google官方也建议开发者应该将数据存储在外部存储的私有目录中该APP包名命名的文件夹下,这样某些情况下其它应用是无法对你的应用文件进行写操作;同时当应用被卸载后,相关数据也会被一起清除掉,同时也不会引起用户的反感;如果我们把数据存储在公有目录和storage/sdcard目录下,数据是不会随着应用的卸载而删除的


    内部存储操作

    API

    操作内部存储的api都是Context类的

    • getFilesDir():返回文件系统上特定应用程序的文件目录的绝对路径;返回一个File对象,它的目录是 data/data/包名/files
    • fileList():返回应用程序当前保存的文件数组;返回一个字符串数组,即由data/data/包名/files目录下文件的文件名组成的
    • deleteFile(String name):删除保存在内部存储上的文件;该文件位于data/data/包名/files目录下,返回一个boolean值表示是否删除成功
    • getCacheDir():返回文件系统上应用程序的缓存目录的绝对路径;返回一个File对象,它的目录是 data/data/包名/cache
    • getDir(String name, int mode):在内部存储空间中创建(或打开现有)目录;返回一个File对象;请注意,通过File对象创建的文件只能由您自己的应用程序访问; 您只能设置整个目录的模式,而不能设置单个文件的模式;该文件夹是在data/data/包名 目录上创建的
    • openFileOutput(String name, int mode):打开内部存储中与本应用程序包关联的私有文件以进行写入,如果文件尚不存在,则创建该文件;返回一个输出流FileOutputStream;该文件位于data/data/包名/files目录下
    • openFileInput(String name):打开内部存储中与本应用程序包关联的私有文件以进行读取;返回一个输入流FileInputStream,该文件位于data/data/包名/files目录下

    其中openFileOutput方法第二个参数有如下几个可选值:

    • Context.MODE_PRIVATE:默认模式,创建文件(或替换同名文件),只能由调用程序(或共享相同用户ID的所有应用程序)访问
    • Context.MODE_WORLD_READABLE:允许所有其他应用程序对创建的文件具有读的访问权限,使用这个模式Android N(7.0)开始将抛出SecurityException,这个模式从API17已标记被弃用,创建全局可读文件非常危险,可能引发安全漏洞
    • Context.MODE_WORLD_WRITEABLE:允许所有其他应用程序具有对创建文件的写访问权,,使用这个模式Android N(7.0)开始将抛出SecurityException,这个模式从API17已标记被弃用,创建全局可写文件非常危险,可能引发安全漏洞
    • Context.MODE_APPEND:创建文件,如果文件已存在,则将数据写入现有文件的末尾而不是抹掉它。

    注意:如果您想缓存某些数据,而不是持久存储它们,则应使用getCacheDir()打开一个File,该File表示应用程序应保存临时缓存文件的内部目录;当设备内部存储空间不足时,Android可能会删除这些缓存文件以恢复空间。 但是,您不应该依赖系统来清理这些文件。 您应该始终自己维护缓存文件并保持合理的空间限制,例如1MB。 当用户卸载您的应用程序时,将删除这些文件

    读写操作

    /**
         * 获取文件内容
         * @param fileName 内部存储中文件名
         * @return 按行读取文件内容
         */
        public List<String> getStringFromInternalStorage(String fileName){
    
            List<String> content = new ArrayList<>();
            InputStream is = null;
            BufferedReader br = null;
            try {
                is = mContext.get().openFileInput(fileName);
                br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
                String line ;
                while ( (line = br.readLine()) != null){
                    content.add(line);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
    
                closeInputStream(is);
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            return content;
        }
    
    
        /**
         * 获取内部存储文件数据
         * @param fileName 内部存储中文件名
         * @return 返回文件二进制数据,以便传输
         */
        public byte[] getDataFromInternalStorage(String fileName){
    
            BufferedInputStream bis = null;
            FileInputStream fis = null;
            ByteArrayOutputStream bos = null;
            try {
                fis = mContext.get().openFileInput(fileName);
                bis = new BufferedInputStream(fis);
                bos = new ByteArrayOutputStream();
                byte[] buff = new byte[8*1024];
                int len ;
                while ((len = bis.read(buff)) != -1){
                    bos.write(buff,0,len);
                }
                return bos.toByteArray();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeInputStream(fis);
                closeInputStream(bis);
                closeOutputStream(bos);
            }
            return null;
        }
    
        /**
         * 保存字符串数据到内部存储
         * @param content 保存的内容
         * @param fileName 文件名
         * @param mode 访问模式
         */
        public void putStringToInternalStorage(String content,String fileName,int mode){
    
            FileOutputStream fos = null;
            try {
                fos = mContext.get().openFileOutput(fileName,mode);
                fos.write(content.getBytes());
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    
        /**
         * 保存数据到内部存储
         * @param content 保存的内容
         * @param fileName 文件名
         * @param mode 访问模式
         */
        public void putDataToInternalStorage(byte[] content,String fileName,int mode){
    
            FileOutputStream fos = null;
            try {
                fos = mContext.get().openFileOutput(fileName,mode);
                fos.write(content);
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    

    外部存储操作

    做外部存储操作之前一定要判断外部存储状态

    /**
         * 判断sd卡是否处于就绪状态 可读可写
         * MEDIA_UNKNOWN:未知状态
         * MEDIA_REMOVED:移除状态(外部存储不存在)
         * MEDIA_UNMOUNTED:未装载状态(外部存储存在但是没有装载)
         * MEDIA_CHECKING:磁盘检测状态
         * MEDIA_NOFS:外部存储存在,但是磁盘为空或使用了不支持的文件系统
         * MEDIA_MOUNTED:就绪状态(可读、可写)
         * MEDIA_MOUNTED_READ_ONLY:只读状态
         * MEDIA_SHARED:共享状态(外部存储存在且正通过USB共享数据)
         * MEDIA_BAD_REMOVAL:异常移除状态(外部存储还没有正确卸载就被移除了)
         * MEDIA_UNMOUNTABLE:不可装载状态(外部存储存在但是无法被装载,一般是磁盘的文件系统损坏造成的)
         * @return
         */
        public boolean isSdCardMount(){
            return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        }
    

    上面说到外部存储的时候讲到过这里有公共目录和私有目录之分

    公共目录

    通常情况下,用户通过我们的应用获取的比如多媒体文件,像图片、视频、铃声等文件,应该放在这些公共目录中,例如Music /,Pictures /和Ringtones /;将文件保存到相应的媒体类型目录,系统的媒体扫描程序可以正确地对系统中的文件进行分类(例如,铃声在系统设置中显示为铃声,而不是音乐);同时用户可以轻松地从设备找到并复制它们,也能更好的与其它应用分享;即使应用被卸载,这些文件依然保留

    如果要将文件存储在相应的公共目录,可以通过调用getExternalStoragePublicDirectory(String type),向其传递所需的目录类型,例如DIRECTORY_MUSIC,DIRECTORY_PICTURES,DIRECTORY_RINGTONES或其他,参数不可为null,返回一个File对象,如果不存在,即创建

    /**
         * 获取公共目录
         * @param type DIRECTORY_MUSIC 音乐类型
         *             DIRECTORY_RINGTONES 铃声类型
         *             DIRECTORY_PODCASTS 播客音频类型
         *             DIRECTORY_ALARMS 闹钟提示音类型
         *             DIRECTORY_NOTIFICATIONS 通知提示音类型
         *             DIRECTORY_PICTURES 图片类型
         *             DIRECTORY_MOVIES 电影类型
         *             DIRECTORY_DOWNLOADS 下载文件类型
         *             DIRECTORY_DCIM 相机照片类型
         *             DIRECTORY_DOCUMENTS 文档类型
         * @return 相应类型目录文件
         */
        public File getExternalStoragePublicDirectory(String type){
            File file = Environment.getExternalStoragePublicDirectory(type);
            if (!file.exists()) {
                file.mkdir();
            }
            return file;
        }
    

    获取到的目录类似于/storage/sdcard0/Music

    假如不想存放在这些目录里,需要存放在一些自定义的目录中,那就通过Environment.getExternalStorageDirectory()获取外部存储的根目录,通常是SD卡的根目录,比如/storage/sdcard0,然后你就可以在这个目录上新建目录


    私有目录

    私有文件

    如果您正在处理不适合其他应用程序使用的文件(例如仅由您的应用程序使用的文件),则应通过调用getExternalFilesDir()在外部存储上使用专用存储目录。 此方法还接收类型参数来指定子目录的类型(例如DIRECTORY_MOVIES),如果目录不存在Android会创建。 如果您不需要特定的媒体目录,请传递null以接收应用程序私有文件目录的根目录(/storage/sdcard0/Android/data/包名/files/)。

    这在作用上有点类似于内部存储中的getFilesDir()方法,同时都是属于Context的API

    /**
         * 获取私有指定类型目录
         * @param type DIRECTORY_MUSIC 音乐类型
         *             DIRECTORY_PODCASTS 播客音频类型
         *             DIRECTORY_RINGTONES 铃声类型
         *             DIRECTORY_ALARMS 闹钟提示音类型
         *             DIRECTORY_NOTIFICATIONS 通知提示音类型
         *             DIRECTORY_PICTURES 图片类型
         *             DIRECTORY_MOVIES 电影类型
         * @return 相应类型目录文件 例如/storage/sdcard0/Android/data/com.mango.datasave/files/Music
         */
        public File getExternalStoragePrivateDirectory(String type){
            File file = mContext.get().getExternalFilesDir(type);
            return file;
        }
    

    注意:某些移动设备可能既提供了内置存储器作为外部存储空间(通常是手机自带的),同时又提供了SD卡作为外部存储空间。也就是说,在这些设备中外部存储实际上包含了两块磁盘。在Android 4.3(API 18)及以下,Context的getExternalFilesDir方法仅仅会返回内置存储器对应的外部存储空间,而无法访问SD卡对应的存储空间。从Android 4.4(API 19)开始,Context新增了getExternalFilesDirs方法。这个方法的返回值是一个File数组,包含两个对象(可能为null),这样就可以实现对内置存储器和SD卡的访问。数组的第一个对象默认是内置存储器,官方的开发建议是除非这个位置已满或不可用,否则应该使用这个位置

    私有缓存

    同内部存储一样,外部存储也有保存缓存文件的目录,可以通过Context的getExternalCacheDir方法访问缓存文件目录,返回值是一个File对象,对应的目录是 /storage/sdcard0/Android/data/com.mango.datasave/cache ,其中com.mango.datasave是我的测试应用包名;如果目录不存在Android会创建

    如上面的注意点,外部存储可能同时包含内置存储器和SD卡两个存储空间,因此在Android 4.4(API 19)及以上还可以通过Context的getExternalCacheDirs方法访问这两个存储空间。这个方法会返回一个File数组,包含两个对象,第一个对象默认是外部存储内置存储器的缓存文件目录

    File getExternalCacheDir()
    File[] getExternalCacheDirs()
    
    

    注意:当用户卸载您的应用程序时,将删除 /storage/sdcard0/Android/data/包名 目录及其所有内容。 此外,系统扫描程序不读取这些目录中的文件,因此无法从MediaStore内容提供程序访问它们。 因此,您不应将这些目录用于存放属于用户的媒体,例如使用您的应用程序捕获或编辑的照片,或用户使用您的应用程序购买的音乐 - 这些文件应保存在公共目录中

    文件各种操作封装

    /**
     * @Description TODO(文件操作辅助类)
     * @author cxy
     * @Date 2018/10/30 16:26
     */
    public class FileStorageTools {
    
        private String TAG = FileStorageTools.class.getSimpleName();
    
        private WeakReference<Context> mContext ;
        private static FileStorageTools instance;
    
        private FileStorageTools(Context context){
            mContext = new WeakReference<>(context);
        }
        public static FileStorageTools getInstance(Context context){
            if(instance == null){
                instance = new FileStorageTools(context);
            }
            return instance;
        }
    
        //获取ram可用内存
        public String getRAMAvailMem(){
            ActivityManager am=(ActivityManager)mContext.get().getSystemService(Context.ACTIVITY_SERVICE);
            ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
            am.getMemoryInfo(mi);
            return reviseFileSize(mi.availMem);
        }
    
        //获取ram总内存
        public String getRAMTotalMem(){
            ActivityManager am=(ActivityManager)mContext.get().getSystemService(Context.ACTIVITY_SERVICE);
            ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
            am.getMemoryInfo(mi);
            return reviseFileSize(mi.totalMem);
        }
    
        //获取sd卡总大小
        public String getSDTotalSize(){
            if(isSdCardMount()){
                File file=Environment.getExternalStorageDirectory();
                StatFs statFs=new StatFs(file.getPath());
                long blockSize=statFs.getBlockSizeLong();
                long totalBlocks=statFs.getBlockCountLong();
                return reviseFileSize(totalBlocks*blockSize);
            }else {
                return null;
            }
        }
    
        //获取sd卡可用大小
        public String getSDAvailableSize(){
            if(isSdCardMount()){
                File file=Environment.getExternalStorageDirectory();
                StatFs statFs=new StatFs(file.getPath());
                long blockSize=statFs.getBlockSizeLong();
                long availableBlocks=statFs.getFreeBlocksLong();
                return reviseFileSize(availableBlocks*blockSize);
            }else {
                return null;
            }
        }
    
    
        /**================================================内部存储操作=================================================================**/
    
        /**
         * 获取文件内容
         * @param fileName 内部存储中文件名
         * @return 按行读取文件内容
         */
        public List<String> getStringFromInternalStorage(String fileName){
    
            List<String> content = new ArrayList<>();
            InputStream is = null;
            BufferedReader br = null;
            try {
                is = mContext.get().openFileInput(fileName);
                br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
                String line ;
                while ( (line = br.readLine()) != null){
                    content.add(line);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
    
                closeInputStream(is);
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            return content;
        }
    
    
        /**
         * 获取内部存储文件数据
         * @param fileName 内部存储中文件名
         * @return 返回文件二进制数据,以便传输
         */
        public byte[] getDataFromInternalStorage(String fileName){
    
            BufferedInputStream bis = null;
            FileInputStream fis = null;
            ByteArrayOutputStream bos = null;
            try {
                fis = mContext.get().openFileInput(fileName);
                bis = new BufferedInputStream(fis);
                bos = new ByteArrayOutputStream();
                byte[] buff = new byte[8*1024];
                int len ;
                while ((len = bis.read(buff)) != -1){
                    bos.write(buff,0,len);
                }
                return bos.toByteArray();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeInputStream(fis,bis);
                closeOutputStream(bos);
            }
            return null;
        }
    
        /**
         * 保存字符串数据到内部存储
         * @param content 保存的内容
         * @param fileName 文件名
         * @param mode 访问模式
         */
        public void putStringToInternalStorage(String content,String fileName,int mode){
    
            FileOutputStream fos = null;
            try {
                fos = mContext.get().openFileOutput(fileName,mode);
                fos.write(content.getBytes());
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    
        /**
         * 保存数据到内部存储
         * @param content 保存的内容
         * @param fileName 文件名
         * @param mode 访问模式
         */
        public void putDataToInternalStorage(byte[] content,String fileName,int mode){
    
            FileOutputStream fos = null;
            try {
                fos = mContext.get().openFileOutput(fileName,mode);
                fos.write(content);
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    
        /**
         * 将内容保存到内部存储的缓存目录 尽量别将文件保存在这里,内存有限
         * @param content
         * @param fileName
         * @param append 是否追加到文件尾部
         */
        public void putInternalStorageCache(String content,String fileName,boolean append){
    
            FileOutputStream fos = null;
            try {
                File cache = new File(mContext.get().getCacheDir(),fileName);
                fos = new FileOutputStream(cache,append);
                fos.write(content.getBytes());
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    
        /**====================================================外部存储操作=======================================================**/
    
        /**====================================构建目录=================================================**/
    
        /**
         * 获取公共目录
         * @param type DIRECTORY_MUSIC 音乐类型
         *             DIRECTORY_RINGTONES 铃声类型
         *             DIRECTORY_PODCASTS 播客音频类型
         *             DIRECTORY_ALARMS 闹钟提示音类型
         *             DIRECTORY_NOTIFICATIONS 通知提示音类型
         *             DIRECTORY_PICTURES 图片类型
         *             DIRECTORY_MOVIES 电影类型
         *             DIRECTORY_DOWNLOADS 下载文件类型
         *             DIRECTORY_DCIM 相机照片类型
         *             DIRECTORY_DOCUMENTS 文档类型
         * @return 相应类型目录文件 例如/storage/sdcard0/Music
         */
        public File getExternalStoragePublicDirectory(String type){
            File file = Environment.getExternalStoragePublicDirectory(type);
            if (!file.exists()) {
                file.mkdir();
            }
            return file;
        }
    
        /**
         * 获取私有文件目录
         * @param type DIRECTORY_MUSIC 音乐类型
         *             DIRECTORY_PODCASTS 播客音频类型
         *             DIRECTORY_RINGTONES 铃声类型
         *             DIRECTORY_ALARMS 闹钟提示音类型
         *             DIRECTORY_NOTIFICATIONS 通知提示音类型
         *             DIRECTORY_PICTURES 图片类型
         *             DIRECTORY_MOVIES 电影类型
         * @return 相应类型目录文件 例如/storage/sdcard0/Android/data/com.mango.datasave/files/Music
         */
        public File getExternalStoragePrivateDirectory(String type){
            File file = mContext.get().getExternalFilesDir(type);
            return file;
        }
    
        /**
         * 获取私有缓存目录
         * @return /storage/sdcard0/Android/data/com.mango.datasave/cache
         */
        public File getExternalStoragePrivateCache(){
            File file = mContext.get().getExternalCacheDir();
            return file;
        }
    
        /**
         * 构建文件目录
         * @param path 例如 /file/movie
         * @return 返回完整目录 /storage/sdcard0/file/movie
         */
        public String makeFilePath(String path){
            if (StringTools.isEmpty(path)) throw new NullPointerException("path cant be null");
            return Environment.getExternalStorageDirectory().getAbsolutePath() + path;
        }
    
    
        /**
         * 创建文件
         * @param base
         * @param fileName
         * @return
         */
        public File makeFile(File base,String fileName){
            if (StringTools.isEmpty(fileName)) throw new NullPointerException("fileName cant be null");
            if(fileName.indexOf(File.separator) < 0){
                return new File(base,fileName);
            }
            throw new IllegalArgumentException(
                    "File " + fileName + " contains a path separator");
        }
    
        /**====================================保存数据=================================================**/
    
        /**
         * 将内容写到外部存储文件
         * @param content 内容
         * @param parent 目标文件父目录
         * @param fileName 文件名
         * @param append 内容是追加到文件末尾还是覆盖
         */
        public void putStringToExternalStorage(String content,File parent, String fileName,boolean append){
    
            FileOutputStream fos = null;
    
            File file = makeFile(parent,fileName);
            try {
                fos = new FileOutputStream(file,append);
                fos.write(content.getBytes());
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    
        /**
         * 将内容写到外部存储文件
         * @param parent 目标文件父目录
         * @param fileName 文件名
         * @param content 内容
         *                @String.getBytes()
         */
        public void putDataToExternalStorage(File parent, String fileName,byte[] content){
    
            FileOutputStream fos = null;
            try {
                File file = makeFile(parent,fileName);
                fos = new FileOutputStream(file);
                fos.write(content);
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(fos);
            }
        }
    
        /**
         * 将流数据保存到外部存储文件
         * @param parent 目标文件父目录
         * @param fileName 文件名
         * @param is 流
         */
        public void putStreamToExternalStorage(File parent, String fileName,InputStream is){
    
            BufferedOutputStream bos = null;
            BufferedInputStream bis = null;
    
            try {
                File file = makeFile(parent,fileName);
                bis = new BufferedInputStream(is);
                bos = new BufferedOutputStream(new FileOutputStream(file));
                byte[] buff = new byte[8*1024];
                int len;
                while ( (len = bis.read(buff)) != -1) {
                    bos.write(buff,0,len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeInputStream(is,bis);
                closeOutputStream(bos);
            }
    
        }
    
        /**
         * 将bitmap保存到外部存储文件
         * @param parent 目标文件父目录
         * @param fileName 文件名
         * @param bitmap 图片
         */
        public void putBitmapToExternalStorage(File parent, String fileName, Bitmap bitmap){
    
            BufferedOutputStream bos = null;
    
            File bit = makeFile(parent,fileName);
            try {
                bos = new BufferedOutputStream(new FileOutputStream(bit));
                if (fileName.contains(".png") || fileName.contains(".PNG")) {
                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
                } else {
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
                }
                bos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeOutputStream(bos);
            }
    
        }
    
    
        /**====================================读取数据=================================================**/
    
        /**
         * 读取外部文件数据
         * @param path 文件路径
         * @return 文件的字节数组
         */
        public byte[] getDataFromExternalStorage(String path){
    
            byte[] data = null;
            File file = new File(path);
            if (!file.exists()) return null;
    
            BufferedInputStream bis = null;
            ByteArrayOutputStream bos = null;
            try {
                bis = new BufferedInputStream(new FileInputStream(file));
                bos = new ByteArrayOutputStream();
                byte[] buff = new byte[8*1024];
                int len;
                while ((len = bis.read(buff)) != -1) {
                    bos.write(buff,0,len);
                }
                data = bos.toByteArray();
                bos .flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeInputStream(bis);
                closeOutputStream(bos);
            }
            return data;
        }
    
        /**
         * 按行读取文件内容
         * @param path 文件路径
         * @return
         */
        public List<String> getStringFromExternalStorage(String path){
    
            File file = new File(path);
            if (!file.exists()) return null;
    
            List<String> data = new ArrayList<>();
            InputStreamReader isr = null;
            BufferedReader br = null;
            try {
                isr = new InputStreamReader(new FileInputStream(file),"utf-8");
                br = new BufferedReader(isr);
                data.add(br.readLine());
                isr.close();
                br.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return data;
        }
    
        /**====================================文件拷贝操作=================================================**/
    
        /**
         * 单个文件复制
         * @param oldFile 原文件目录
         * @param newFile 新文件
         */
        public void copyFile(String oldFile,File newFile){
    
            File oldF = new File(oldFile);
            if(!oldF.exists()) return;
    
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                fis = new FileInputStream(oldF);
                fos = new FileOutputStream(newFile);
                byte[] buff = new byte[8*1024];
                int len;
                while ((len = fis.read(buff)) != -1) {
                    fos.write(buff,0,len);
                }
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeInputStream(fis);
                closeOutputStream(fos);
            }
        }
    
        /**====================================资源文件操作=================================================**/
    
       /**
         * 获取raw目录下文件数据流 调用 getDataFromRaw(R.raw.mango)
         * @param resourceID R.raw.mango
         * @return
         */
        public InputStream getStreamFromRaw(int resourceID){
    
            InputStream in = null;
            try {
                in = mContext.get().getResources().openRawResource(resourceID);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return in;
        }
    
        /**
         * 获取assert目录下文件数据流
         * 调用getDataFromAssets("mango.txt")
         * 如果多层目录就要带上父级目录 getStreamFromAssets("today/day.txt")
         * @param fileName 文件全名,包括后缀
         * @return 数据流
         */
        public InputStream getStreamFromAssets(String fileName){
            InputStream in = null;
            try {
                in = mContext.get().getResources().getAssets().open(fileName);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return in;
        }
    
        /**
         * 从资源目录下读取数据
         * @param is 数据流
         * @return
         */
        public byte[] getDataFromResource(InputStream is){
    
            if(is == null) return null;
    
            try {
                int lenght = is.available();
                byte[]  buffer = new byte[lenght];
                //将文件中的数据读到byte数组中
                is.read(buffer);
                is.close();
                return buffer;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 将资源文件拷贝到外部存储
         * @param file 输出目标文件
         * @param is 资源文件流
         */
        public  void moveResourceFileToExternalStorage(File file, InputStream is){
    
            if(is == null) return;
            FileOutputStream os = null;
            try {
                os = new FileOutputStream(file);
                byte[] buffer = new byte[2*1024];
                int len;
                while ((len = is.read(buffer)) != -1){
                    os.write(buffer, 0, len);
                }
                os.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                closeInputStream(is);
                closeOutputStream(os);
            }
    
        }
    
    
        /**====================================文件普通操作=================================================**/
    
    	private List<File> fList = new ArrayList<>();
    
        public void clearFlist(){
            fList.clear();
        }
       
        /**
         * 给文件重命名
         * @param oldPath 原文件
         * @param newPath 新文件
         * @return
         */
        public boolean renameFile(String oldPath,String newPath){
            File oldFile = new File(oldPath);
            File newFile = new File(newPath);
            return oldFile.renameTo(newFile);
        }
    
        /**
         * 遍历目录
         * @param path 文件夹目录
         * @return
         */
        public List<File> listFile(String path){
    
            File file = new File(path);
            if (!file.exists()) {
                return null;
            }
            File[] data = file.listFiles();
            if(data == null) return null;
    
            for(int i=0; i<data.length; i++){
                File child = data[i];
                if (child.isFile()) {
                    fList.add(child);
                } else {
                    listFile(child.getAbsolutePath());
                }
            }
            return fList;
        }
    
        /**
         *
         * @param path
         */
        public void delFile(String path){
    
            List<File> file = listFile(path);
            if(file == null)return;
            for(int i=0; i<file.size(); i++){
                file.get(i).delete();
            }
        }
    
        /**
         * 获取文件的文件名(不包括扩展名)
         */
        public String getFileNameWithoutExtension(String path) {
            if(path == null) {
                return null;
            }
            int separatorIndex = path.lastIndexOf(File.separator);
            if(separatorIndex < 0) {
                separatorIndex = 0;
            }
            int dotIndex = path.lastIndexOf(".");
            if(dotIndex < 0) {
                dotIndex = path.length();
            } else if(dotIndex < separatorIndex) {
                dotIndex = path.length();
            }
            return path.substring(separatorIndex + 1, dotIndex);
        }
    
        /**
         * 获取文件名
         */
        public String getFileName(String path) {
            if(path == null) {
                return null;
            }
            int separatorIndex = path.lastIndexOf(File.separator);
            return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
        }
    
    
        public void closeInputStream(InputStream... is){
    
            for (int i=0; i<is.length; i++){
                if (is[i] != null) {
                    try {
                        is[i].close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
        public void closeOutputStream(OutputStream... os){
    
            for (int i=0; i<os.length; i++){
                if (os[i] != null) {
                    try {
                        os[i].close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
        /**
         * 判断sd卡是否处于就绪状态 可读可写
         * MEDIA_UNKNOWN:未知状态
         * MEDIA_REMOVED:移除状态(外部存储不存在)
         * MEDIA_UNMOUNTED:未装载状态(外部存储存在但是没有装载)
         * MEDIA_CHECKING:磁盘检测状态
         * MEDIA_NOFS:外部存储存在,但是磁盘为空或使用了不支持的文件系统
         * MEDIA_MOUNTED:就绪状态(可读、可写)
         * MEDIA_MOUNTED_READ_ONLY:只读状态
         * MEDIA_SHARED:共享状态(外部存储存在且正通过USB共享数据)
         * MEDIA_BAD_REMOVAL:异常移除状态(外部存储还没有正确卸载就被移除了)
         * MEDIA_UNMOUNTABLE:不可装载状态(外部存储存在但是无法被装载,一般是磁盘的文件系统损坏造成的)
         * @return
         */
        public boolean isSdCardMount(){
            return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        }
    
    
        public String reviseFileSize(long size){
            String str="KB";
            float reviseSize = 0f;
            if(size>1024){
                reviseSize = size/1024f;
                if(reviseSize>1024){
                    str="M";
                    reviseSize = reviseSize/1024f;
                    if (reviseSize>1024) {
                        str="G";
                        reviseSize = reviseSize/1024f;
                    }
                }
            }
    
            DecimalFormat formatter=new DecimalFormat();
            formatter.setGroupingSize(3);
            String result = formatter.format(reviseSize) + str;
            return result;
        }
    
    }
    

    后续文件其它操作再陆续补充

    展开全文
  • 磁盘原理理解文件读写优化

    千次阅读 2019-10-02 21:45:10
    摘要:本次案例主要讲述java高性能读写文件,如何提高文件IO 关键词:java 读写文件 IO NIO 文件碎片 小文件 文件刷盘 顺序与随即读写
  • Elasticsearch文档存储过程

    千次阅读 2018-03-17 22:35:09
    参考:ES官方文档分片内部原理:https://www.elastic.co/guide/cn/elasticsearch/guide/current/inside-a-shard.htmlES分片原理:http://blog.csdn.net/mgxcool/article/details/49250341索引的解释:名词的索引:指...
  • 研究图片服务器问题时,了解到现在很多大公司基本都是用分布式文件系统来存储海量小文件,比如Facebook有haystack,淘宝有TFS,京东有JFS。最近研究TFS,结合之前学习的linux下的inode相关知识,了解到ext...
  • 数据存储方式之 TXT 文本

    千次阅读 2018-10-15 13:29:57
    最后网络爬虫实战案例,讲解其具体的使用方式。 输入流、输出流简介 Java 中,流是从源到目的地的字节的有序序列。Java 中有两种基本的流——输入流和输出流。输入流与输出流提供了一条通道,使用该通道可以...
  • 本文按照自己的理解从硬件磁盘文件系统的逐层网上的思路开展,从操作系统的角度详解Linux文件系统层次、文件系统分类、文件系统的存储结构、不同存储介质的区别(RAM、ROM、Flash)、存储节点inode。
  • 一篇介绍了传统存储的几个常用类型,本篇主要介绍云平台用到的常用存储类型,分别是文件存储、块存储和对象存储。 这种分类是数据存储方式来命名的,体现了不同的数据存储格式。文件存储以文件和文件夹的...
  • 文件、块和对象是三种不同的方式来保存、整理和呈现数据的存储格式。这些格式各有各的功能和限制。文件存储以文件和文件夹的层次结构来整理和呈现数据;块存储会将数据拆分到任意划分且大小相同的卷中; 对象存储...
  • 计算机磁盘是如何实现存储的?

    万次阅读 多人点赞 2017-08-15 08:03:51
    硬盘储存数据的原理和盒式磁带类似,只不过盒式磁带上存储是模拟格式的音乐,而硬盘上存储的是数字格式的数据。写入时,磁头线圈加电,周围产生磁场,磁化其下的磁性材料;电流的方向不同,所以磁场的方向也不同...
  • Ceph 是一个开源的分布式存储系统,包括对象存储、块设备、文件系统。它可靠性高、管理方便、伸缩性强,能够轻松应对PB、EB级别数据。Ceph 存储体系中,核心为 RADOS,它是一个高可用分布式对象存储,该模块负责对...
  • 对象存储出现之前,存储主要依靠以下三种方式:传统存储方式1、硬盘无论是DVR、DVS后挂硬盘还是服务器后面直接连接扩展柜的方式,都是采用硬盘进行存储方式。应该说采用硬盘方式进行的存储,并不能算作严格意义...
  • Mongodb,分布式文档存储数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种。它许多场景下可用于...
  • 操作系统中文件存储空间的管理

    万次阅读 2015-03-24 21:30:04
    由于文件存储设备是分成若干个大小相等的物理块,并块为单位来交换信息的,因此,文件存储空间的管理实质是一个空闲块的组织和管理问题,它包括空闲块组织,空闲块的分配和空闲块的回收等几个问题。 二、文件...
  • 从理论看,并行传输效率很高,但是由于线路的物理原因,它的传输频率不能太高,所以实际的传输速度并不和并行度成正比,甚至可能更差。 (2) 串行接口,指的是串行传输的接口,同样是0~9十个数字,用1条传输线,...
  • Minio 文件服务(1)—— Minio部署使用及存储机制分析 Minio 文件服务(2)—— Minio用Nginx做负载均衡 本文参考Minio官方文档,使用细节里面说的很详细,本文主要讲解文档中较少涉及的Minio存储机制。以及我...
  • 块储存、对象存储文件存储

    千次阅读 2018-08-07 19:40:46
    通常来讲,磁盘阵列都是基于Block块的存储,而所有的NAS产品都是文件存储讲这三者的内容和区别之前,先介绍一下结构化数据,半结构化数据和非结构化数据。 1、结构化数据 结构化的数据是指可以使用关系型...
  • 文件系统和数据存储

    2013-09-27 17:15:53
    随着计算机技术的发展,计算机的主要应用领域已经从科学计算转到信息处理,有统计资料表明,当今80%以上的计算机将主要用于信息处理。信息作为一种社会资源自古就有...4.1 信息、数据和文件  信息社会中,由于信息
  • 计算机磁盘存储实现原理

    千次阅读 2018-12-26 17:01:37
    硬盘储存数据的原理和盒式磁带类似,只不过盒式磁带上存储是模拟格式的音乐,而硬盘上存储的是数字格式的数据。写入时,磁头线圈加电,周围产生磁场,磁化其下的磁性材料;电流的方向不同,所以磁场的方向也不同...
  • 存储Java客户端上传文件

    千次阅读 2016-09-20 14:17:02
    什么是云存储准确来说应该称为对象存储。云存储是比较流行的一种云服务,提供图片、视频等文件存储服务。具有代表性的产品有:七...碰到此类需求,传统的做法是自备服务器和存储,将上传的文件存入自己的存储上,再来
  • 主机与存储知识(实习杂记)

    千次阅读 2019-02-21 10:30:47
    Nas(network attached storage)网络存储基于标准网络协议实现数据传输,为网络中的windows/linux/mac os等各种不同操作系统的计算机提供文件共享和数据备份。支持24小时不断电BT下载,FTP,HTTP,eMale及NZB下载;...
  • 文件、块和对象是三种不同的方式来保存、整理和呈现数据的存储格式。这些格式各有各的功能和限制。 文件存储以文件和文件夹的层次结构来整理和呈现数据; 块存储会将数据拆分到任意划分且大小相同的卷中; 对象...
  • 目录 MBR和GPT MBR的局限性 GPT的优势 主分区、扩展分区和逻辑分区 挂接卷 Legacy、UEFI引导和GRUB引导 文件系统(FAT16、32、NTFS和EXT2、3、4、Xfs、Tmpfs) Windows中的文件系统 Linux中的文件系统 inod...
  • LabVIEW(四):数据存储文件IO

    千次阅读 2018-09-25 08:42:00
    TDMS文件中的描述信息无需涉及一个定制文件头的情况下,提供了一种方便的存档方式,当我们的文档需求更为复杂时,我们无需重新设计应用程序,只需扩展TDMS数据模型来满足我们特定的需求即可。与其他文件I/O函数相...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 84,481
精华内容 33,792
关键字:

以文档方式存储在磁盘上的文件称为