• 关于

    数据缓冲是干嘛的

    的搜索结果

回答

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。是的,如果不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,类似于衣柜,系统磁盘等等,为了方便查找,我们会进行分区分类。另外如果不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个原因,就是为了方便内存的回收。如果不分,回收内存需要全部内存扫描,那就慢死了,内存根据不同的使用功能分成不同的区域,那么内存回收也就可以根据每个区域的特定进行回收,比如像栈内存中的栈帧,随着方法的执行栈帧进栈,方法执行完毕就出栈了,而对于像堆内存的回收就需要使用经典的回收算法来进行回收了,所以看起来分类这么麻烦,其实是大有好处的。 提到虚拟机的内存结构,可能首先想起来的就是堆栈。对象分配到堆上,栈上用来分配对象的引用以及一些基本数据类型相关的值。但是·虚拟机的内存结构远比此要复杂的多。除了我们所认识的(还没有认识完全)的堆栈以外,还有程序计数器,本地方法栈和方法区。我们平时所说的栈内存,一般是指的栈内存中的局部变量表。 从图中可以看到有5大内存区域,按照是否被线程所共享可分为两部分,一部分是线程独占区域,包括Java栈,本地方法栈和程序计数器。还有一部分是被线程所共享的,包括方法区和堆。什么是线程共享和线程独占呢,非常好理解,我们知道每一个Java进行都会有多个线程同时运行,那么线程共享区的这片区域就是被所有线程一起使用的,不管有多少个线程,这片空间始终就这一个。而线程的独占区,是每个线程都有这么一份内存空间,每个线程的这片空间都是独有的,有多少个线程就有多少个这么个空间。上图的区域的大小并不代表实际内存区域的大小,实际运行过程中,内存区域的大小也是可以动态调整的。下面来具体说说每一个区域的主要功能。 程序计数器,我们在写代码的过程中,开发工具一般都会给我们标注行号方便查看和阅读代码。那么在程序在运行过程中也有一个类似的行号方便虚拟机的执行,就是程序计数器,在c语言中,我们知道会有一个goto语句,其实就是跳转到了指定的行,这个行号就是程序计数器。存储的就是程序下一条所执行的指令。这部分区域是线程所独享的区域,我们知道线程是一个顺序执行流,每个线程都有自己的执行顺序,如果所有线程共用一个程序计数器,那么程序执行肯定就会出乱子。为了保证每个线程的执行顺序,所以程序计数器是被单个线程所独显的。程序计数器这块内存区域是唯一一个在jvm规范中没有规定内存溢出的。 java虚拟机栈,java虚拟机栈是程序运行的动态区域,每个方法的执行都伴随着栈帧的入栈和出栈。 栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中包括了局部变量表,操作数栈,方法返回地址以及额外的一些附加信息,在编译过程中,局部变量表的大小已经确定,操作数栈深度也已经确定,因此栈帧在运行的过程中需要分配多大的内存是固定的,不受运行时影响。对于没有逃逸的对象也会在栈上分配内存,对象的大小其实在运行时也是确定的,因此即使出现了栈上内存分配,也不会导致栈帧改变大小。 一个线程中,可能调用链会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是最有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的字节码指令仅对当前栈帧进行操作。Ft5rk58GfiJxcdcCzGeAt8fjkFPkMRdf 局部变量表:我们平时所说的栈内存一般就是指栈内存中的局部变量表。这里主要是存储变量所用。对于基本数据类型直接存储其值,对于引用数据类型则存储其地址。局部变量表的最小存储单位是Slot,每个Slot都能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。 既然前面提到了数据类型,在此顺便说一下,一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float、reference和returnAddress八种类型。前面六种不需要多解释,大家都认识,而后面的reference是对象的引用。虚拟机规范既没有说明它的长度,也没有明确指出这个引用应有怎样的结构,但是一般来说,虚拟机实现至少都应当能从此引用中直接或间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。而returnAddress是为字节码指令jsr、jsr_w和ret服务的,它指向了一条字节码指令的地址。 对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的Slot空间。Java语言中明确规定的64位的数据类型只有long和double两种(reference类型则可能是32位也可能是64位)。值得一提的是,这里把long和double数据类型读写分割为两次32读写的做法类似。不过,由于局部变量表建立在线程的堆栈上,是线程私有的数据,无论读写两个连续的Slot是否是原子操作,都不会引起数据安全问题。 操作数栈是一个后入先出(Last In First Out, LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到字节码文件中,关于字节码文件,后面我会具体的来描述。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。 举个例子,整数加法的字节码指令iadd在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值和并相加,然后将相加的结果入栈。 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。再以上面的iadd指令为例,这个指令用于整型数加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int型,不能出现一个long和一个float使用iadd命令相加的情况。 本地方法栈 与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。 方法区经常会被人称之为永久代,但这俩并不是一个概念。首先永久代的概念仅仅在HotSpot虚拟机中存在,不幸的是,在jdk8中,Hotspot去掉了永久代这一说法,使用了Native Memory,也就是Metaspace空间。那么方法区是干嘛的呢?我们可以这么理解,我们要运行Java代码,首先需要编译,然后才能运行。在运行的过程中,我们知道首先需要加载字节码文件。也就是说要把字节码文件加载到内存中。好了,问题就来了,字节码文件放到内存中的什么地方呢,就是方法区中。当然除了编译后的字节码之外,方法区中还会存放常量,静态变量以及及时编译器编译后的代码等数据。 堆,一般来讲堆内存是Java虚拟机中最大的一块内存区域,同方法区一样,是被所有线程所共享的区域。此区域所存在的唯一目的就存放对象的实例(对象实例并不一定全部在堆中创建)。堆内存是垃圾收集器主要光顾的区域,一般来讲根据使用的垃圾收集器的不同,堆中还会划分为一些区域,比如新生代和老年代。新生代还可以再划分为Eden,Survivor等区域。另外为了性能和安全性的角度,在堆中还会为线程划分单独的区域,称之为线程分配缓冲区。更细致的划分是为了让垃圾收集器能够更高效的工作,提高垃圾收集的效率。 如果想要了解更多的关于虚拟机的内容,可以观看录制的<深入理解Java虚拟机>这套视频教程。
zwt9000 2019-12-02 00:21:07 0 浏览量 回答数 0

回答

前言 这期我想写很久了,但是因为时间的原因一直拖到了现在,我以为一两天就写完了,结果从构思到整理资料,再到写出来用了差不多一周的时间吧。 你们也知道丙丙一直都是创作鬼才来的,所以我肯定不会一本正经的写,我想了好几个切入点,最后决定用一个完整的电商系统作为切入点,带着大家看看,我们需要学些啥,我甚至还收集配套视频和资料,暖男石锤啊,这期是呕心沥血之作,不要白嫖了。 正文 在写这个文章之前,我花了点时间,自己臆想了一个电商系统,基本上算是麻雀虽小五脏俱全,我今天就用它开刀,一步步剖析,我会讲一下我们可能会接触的技术栈可能不全,但是够用,最后给个学习路线。 Tip:请多欣赏一会,每个点看一下,看看什么地方是你接触过的,什么技术栈是你不太熟悉的,我觉得还算是比较全的,有什么建议也可以留言给我。 不知道大家都看了一下没,现在我们就要庖丁解牛了,我从上到下依次分析。 前端 你可能会会好奇,你不是讲后端学习路线嘛,为啥还有前端的部分,我只能告诉你,傻瓜,肤浅。 我们可不能闭门造车,谁告诉你后端就不学点前端了? 前端现在很多也了解后端的技术栈的,你想我们去一个网站,最先接触的,最先看到的是啥? 没错就是前端,在大学你要是找不到专门的前端同学,去做系统肯定也要自己顶一下前端的,那我觉得最基本的技术栈得熟悉和了解吧,丙丙现在也是偶尔会开发一下我们的管理系统主要是VUE和React。 在这里我列举了我目前觉得比较简单和我们后端可以了解的技术栈,都是比较基础的。 作为一名后端了解部分前端知识还是很有必要的,在以后开发的时候,公司有前端那能帮助你前后端联调更顺畅,如果没前端你自己也能顶一下简单的页面。 HTML、CSS、JS、Ajax我觉得是必须掌握的点,看着简单其实深究或者去操作的话还是有很多东西的,其他作为扩展有兴趣可以了解,反正入门简单,只是精通很难很难。 在这一层不光有这些还有Http协议和Servlet,request、response、cookie、session这些也会伴随你整个技术生涯,理解他们对后面的你肯定有不少好处。 Tip:我这里最后删除了JSP相关的技术,我个人觉得没必要学了,很多公司除了老项目之外,新项目都不会使用那些技术了。 前端在我看来比后端难,技术迭代比较快,知识好像也没特定的体系,所以面试大厂的前端很多朋友都说难,不是技术多难,而是知识多且复杂,找不到一个完整的体系,相比之下后端明朗很多,我后面就开始讲后端了。 网关层: 互联网发展到现在,涌现了很多互联网公司,技术更新迭代了很多个版本,从早期的单机时代,到现在超大规模的互联网时代,几亿人参与的春运,几千亿成交规模的双十一,无数互联网前辈的造就了现在互联网的辉煌。 微服务,分布式,负载均衡等我们经常提到的这些名词都是这些技术在场景背后支撑。 单机顶不住,我们就多找点服务器,但是怎么将流量均匀的打到这些服务器上呢? 负载均衡,LVS 我们机器都是IP访问的,那怎么通过我们申请的域名去请求到服务器呢? DNS 大家刷的抖音,B站,快手等等视频服务商,是怎么保证同时为全国的用户提供快速的体验? CDN 我们这么多系统和服务,还有这么多中间件的调度怎么去管理调度等等? zk 这么多的服务器,怎么对外统一访问呢,就可能需要知道反向代理的服务器。 Nginx 这一层做了反向负载、服务路由、服务治理、流量管理、安全隔离、服务容错等等都做了,大家公司的内外网隔离也是这一层做的。 我之前还接触过一些比较有意思的项目,所有对外的接口都是加密的,几十个服务会经过网关解密,找到真的路由再去请求。 这一层的知识点其实也不少,你往后面学会发现分布式事务,分布式锁,还有很多中间件都离不开zk这一层,我们继续往下看。 服务层: 这一层有点东西了,算是整个框架的核心,如果你跟我帅丙一样以后都是从事后端开发的话,我们基本上整个技术生涯,大部分时间都在跟这一层的技术栈打交道了,各种琳琅满目的中间件,计算机基础知识,Linux操作,算法数据结构,架构框架,研发工具等等。 我想在看这个文章的各位,计算机基础肯定都是学过的吧,如果大学的时候没好好学,我觉得还是有必要再看看的。 为什么我们网页能保证安全可靠的传输,你可能会了解到HTTP,TCP协议,什么三次握手,四次挥手。 还有进程、线程、协程,什么内存屏障,指令乱序,分支预测,CPU亲和性等等,在之后的编程生涯,如果你能掌握这些东西,会让你在遇到很多问题的时候瞬间get到点,而不是像个无头苍蝇一样乱撞(然而丙丙还做得不够)。 了解这些计算机知识后,你就需要接触编程语言了,大学的C语言基础会让你学什么语言入门都会快点,我选择了面向对象的JAVA,但是也不知道为啥现在还没对象。 JAVA的基础也一样重要,面向对象(包括类、对象、方法、继承、封装、抽象、 多态、消息解析等),常见API,数据结构,集合框架,设计模式(包括创建型、结构型、行为型),多线程和并发,I/O流,Stream,网络编程你都需要了解。 代码会写了,你就要开始学习一些能帮助你把系统变得更加规范的框架,SSM可以会让你的开发更加便捷,结构层次更加分明。 写代码的时候你会发现你大学用的Eclipse在公司看不到了,你跟大家一样去用了IDEA,第一天这是什么玩意,一周后,真香,但是这玩意收费有点贵,那免费的VSCode真的就是不错的选择了。 代码写的时候你会接触代码的仓库管理工具maven、Gradle,提交代码的时候会去写项目版本管理工具Git。 代码提交之后,发布之后你会发现很多东西需要自己去服务器亲自排查,那Linux的知识点就可以在里面灵活运用了,查看进程,查看文件,各种Vim操作等等。 系统的优化很多地方没优化的空间了,你可能会尝试从算法,或者优化数据结构去优化,你看到了HashMap的源码,想去了解红黑树,然后在算法网上看到了二叉树搜索树和各种常见的算法问题,刷多了,你也能总结出精华所在,什么贪心,分治,动态规划等。 这么多个服务,你发现HTTP请求已经开始有点不满足你的需求了,你想开发更便捷,像访问本地服务一样访问远程服务,所以我们去了解了Dubbo,Spring cloud。 了解Dubbo的过程中,你发现了RPC的精华所在,所以你去接触到了高性能的NIO框架,Netty。 代码写好了,服务也能通信了,但是你发现你的代码链路好长,都耦合在一起了,所以你接触了消息队列,这种异步的处理方式,真香。 他还可以帮你在突发流量的时候用队列做缓冲,但是你发现分布式的情况,事务就不好管理了,你就了解到了分布式事务,什么两段式,三段式,TCC,XA,阿里云的全局事务服务GTS等等。 分布式事务的时候你会想去了解RocketMQ,因为他自带了分布式事务的解决方案,大数据的场景你又看到了Kafka。 我上面提到过zk,像Dubbo、Kafka等中间件都是用它做注册中心的,所以很多技术栈最后都组成了一个知识体系,你先了解了体系中的每一员,你才能把它们联系起来。 服务的交互都从进程内通信变成了远程通信,所以性能必然会受到一些影响。 此外由于很多不确定性的因素,例如网络拥塞、Server 端服务器宕机、挖掘机铲断机房光纤等等,需要许多额外的功能和措施才能保证微服务流畅稳定的工作。 **Spring Cloud **中就有 Hystrix 熔断器、Ribbon客户端负载均衡器、Eureka注册中心等等都是用来解决这些问题的微服务组件。 你感觉学习得差不多了,你发现各大论坛博客出现了一些前沿技术,比如容器化,你可能就会去了解容器化的知识,像**Docker,Kubernetes(K8s)**等。 微服务之所以能够快速发展,很重要的一个原因就是:容器化技术的发展和容器管理系统的成熟。 这一层的东西呢其实远远不止这些的,我不过多赘述,写多了像个劝退师一样,但是大家也不用慌,大部分的技术都是慢慢接触了,工作中慢慢去了解,去深入的。 好啦我们继续沿着图往下看,那再往下是啥呢? 数据层: 数据库可能是整个系统中最值钱的部分了,在我码文字的前一天,刚好发生了微盟程序员删库跑路的操作,删库跑路其实是我们在网上最常用的笑话,没想到还是照进了现实。 这里也提一点点吧,36小时的故障,其实在互联网公司应该是个笑话了吧,权限控制没做好类似rm -rf 、fdisk、drop等等这样的高危命令是可以实时拦截掉的,备份,全量备份,增量备份,延迟备份,异地容灾全部都考虑一下应该也不至于这样,一家上市公司还是有点点不应该。 数据库基本的事务隔离级别,索引,SQL,主被同步,读写分离等都可能是你学的时候要了解到的。 上面我们提到了安全,不要把鸡蛋放一个篮子的道理大家应该都知道,那分库的意义就很明显了,然后你会发现时间久了表的数据大了,就会想到去接触分表,什么TDDL、Sharding-JDBC、DRDS这些插件都会接触到。 你发现流量大的时候,或者热点数据打到数据库还是有点顶不住,压力太大了,那非关系型数据库就进场了,Redis当然是首选,但是MongoDB、memcache也有各自的应用场景。 Redis使用后,真香,真快,但是你会开始担心最开始提到的安全问题,这玩意快是因为在内存中操作,那断点了数据丢了怎么办?你就开始阅读官方文档,了解RDB,AOF这些持久化机制,线上用的时候还会遇到缓存雪崩击穿、穿透等等问题。 单机不满足你就用了,他的集群模式,用了集群可能也担心集群的健康状态,所以就得去了解哨兵,他的主从同步,时间久了Key多了,就得了解内存淘汰机制…… 他的大容量存储有问题,你可能需要去了解Pika…. 其实远远没完,每个的点我都点到为止,但是其实要深究每个点都要学很久,我们接着往下看。 实时/离线/大数据 等你把几种关系型非关系型数据库的知识点,整理清楚后,你会发现数据还是大啊,而且数据的场景越来越多多样化了,那大数据的各种中间件你就得了解了。 你会发现很多场景,不需要实时的数据,比如你查你的支付宝去年的,上个月的账单,这些都是不会变化的数据,没必要实时,那你可能会接触像ODPS这样的中间件去做数据的离线分析。 然后你可能会接触Hadoop系列相关的东西,比如于Hadoop(HDFS)的一个数据仓库工具Hive,是建立在 Hadoop 文件系统之上的分布式面向列的数据库HBase 。 写多的场景,适合做一些简单查询,用他们又有点大材小用,那Cassandra就再合适不过了。 离线的数据分析没办法满足一些实时的常见,类似风控,那Flink你也得略知一二,他的窗口思想还是很有意思。 数据接触完了,计算引擎Spark你是不是也不能放过…… 搜索引擎: 传统关系型数据库和NoSQL非关系型数据都没办法解决一些问题,比如我们在百度,淘宝搜索东西的时候,往往都是几个关键字在一起一起搜索东西的,在数据库除非把几次的结果做交集,不然很难去实现。 那全文检索引擎就诞生了,解决了搜索的问题,你得思考怎么把数据库的东西实时同步到ES中去,那你可能会思考到logstash去定时跑脚本同步,又或者去接触伪装成一台MySQL从服务的Canal,他会去订阅MySQL主服务的binlog,然后自己解析了去操作Es中的数据。 这些都搞定了,那可视化的后台查询又怎么解决呢?Kibana,他他是一个可视化的平台,甚至对Es集群的健康管理都做了可视化,很多公司的日志查询系统都是用它做的。 学习路线 看了这么久你是不是发现,帅丙只是一直在介绍每个层级的技术栈,并没说到具体的一个路线,那是因为我想让大家先有个认知或者说是扫盲吧,我一样用脑图的方式汇总一下吧,如果图片被平台二压了。 资料/学习网站 Tip:本来这一栏有很多我准备的资料的,但是都是外链,或者不合适的分享方式,博客的运营小姐姐提醒了我,所以大家去公众号回复【路线】好了。 絮叨 如果你想去一家不错的公司,但是目前的硬实力又不到,我觉得还是有必要去努力一下的,技术能力的高低能决定你走多远,平台的高低,能决定你的高度。 如果你通过努力成功进入到了心仪的公司,一定不要懈怠放松,职场成长和新技术学习一样,不进则退。 丙丙发现在工作中发现我身边的人真的就是实力越强的越努力,最高级的自律,享受孤独(周末的歪哥)。 总结 我提到的技术栈你想全部了解,我觉得初步了解可能几个月就够了,这里的了解仅限于你知道它,知道他是干嘛的,知道怎么去使用它,并不是说深入了解他的底层原理,了解他的常见问题,熟悉问题的解决方案等等。 你想做到后者,基本上只能靠时间上的日积月累,或者不断的去尝试积累经验,也没什么速成的东西,欲速则不达大家也是知道的。 技术这条路,说实话很枯燥,很辛苦,但是待遇也会高于其他一些基础岗位。 所实话我大学学这个就是为了兴趣,我从小对电子,对计算机都比较热爱,但是现在打磨得,现在就是为了钱吧,是不是很现实?若家境殷实,谁愿颠沛流离。 但是至少丙丙因为做软件,改变了家庭的窘境,自己日子也向小康一步步迈过去。 说做程序员改变了我和我家人的一生可能夸张了,但是我总有一种下班辈子会因为我选择走这条路而改变的错觉。 我是敖丙,一个在互联网苟且偷生的工具人。 创作不易,本期硬核,不想被白嫖,各位的「三连」就是丙丙创作的最大动力,我们下次见! 本文 GitHub https://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。 该回答来自:敖丙
剑曼红尘 2020-03-06 11:35:37 0 浏览量 回答数 0

回答

struct 模块可被用来编码/解码几乎所有类型的二进制的数据结构。为了解释清楚这种数据,假设你用下面的Python数据结构 来表示一个组成一系列多边形的点的集合: polys = [ [ (1.0, 2.5), (3.5, 4.0), (2.5, 1.5) ], [ (7.0, 1.2), (5.1, 3.0), (0.5, 7.5), (0.8, 9.0) ], [ (3.4, 6.3), (1.2, 0.5), (4.6, 9.2) ], ] 现在假设这个数据被编码到一个以下列头部开始的二进制文件中去了: +------+--------+------------------------------------+ |Byte | Type | Description | +======+========+====================================+ |0 | int | 文件代码(0x1234,小端) | +------+--------+------------------------------------+ |4 | double | x 的最小值(小端) | +------+--------+------------------------------------+ |12 | double | y 的最小值(小端) | +------+--------+------------------------------------+ |20 | double | x 的最大值(小端) | +------+--------+------------------------------------+ |28 | double | y 的最大值(小端) | +------+--------+------------------------------------+ |36 | int | 三角形数量(小端) | +------+--------+------------------------------------+ 紧跟着头部是一系列的多边形记录,编码格式如下: +------+--------+-------------------------------------------+ |Byte | Type | Description | +======+========+===========================================+ |0 | int | 记录长度(N字节) | +------+--------+-------------------------------------------+ |4-N | Points | (X,Y) 坐标,以浮点数表示 | +------+--------+-------------------------------------------+ 为了写这样的文件,你可以使用如下的Python代码: import struct import itertools def write_polys(filename, polys): # Determine bounding box flattened = list(itertools.chain(*polys)) min_x = min(x for x, y in flattened) max_x = max(x for x, y in flattened) min_y = min(y for x, y in flattened) max_y = max(y for x, y in flattened) with open(filename, 'wb') as f: f.write(struct.pack('<iddddi', 0x1234, min_x, min_y, max_x, max_y, len(polys))) for poly in polys: size = len(poly) * struct.calcsize('<dd') f.write(struct.pack('<i', size + 4)) for pt in poly: f.write(struct.pack('<dd', *pt)) 将数据读取回来的时候,可以利用函数 struct.unpack() ,代码很相似,基本就是上面写操作的逆序。如下: def read_polys(filename): with open(filename, 'rb') as f: # Read the header header = f.read(40) file_code, min_x, min_y, max_x, max_y, num_polys = \ struct.unpack('<iddddi', header) polys = [] for n in range(num_polys): pbytes, = struct.unpack('<i', f.read(4)) poly = [] for m in range(pbytes // 16): pt = struct.unpack('<dd', f.read(16)) poly.append(pt) polys.append(poly) return polys 尽管这个代码可以工作,但是里面混杂了很多读取、解包数据结构和其他细节的代码。如果用这样的代码来处理真实的数据文件, 那未免也太繁杂了点。因此很显然应该有另一种解决方法可以简化这些步骤,让程序员只关注自最重要的事情。 在本小节接下来的部分,我会逐步演示一个更加优秀的解析字节数据的方案。 目标是可以给程序员提供一个高级的文件格式化方法,并简化读取和解包数据的细节。但是我要先提醒你, 本小节接下来的部分代码应该是整本书中最复杂最高级的例子,使用了大量的面向对象编程和元编程技术。 一定要仔细的阅读我们的讨论部分,另外也要参考下其他章节内容。 首先,当读取字节数据的时候,通常在文件开始部分会包含文件头和其他的数据结构。 尽管struct模块可以解包这些数据到一个元组中去,另外一种表示这种信息的方式就是使用一个类。 就像下面这样: import struct class StructField: ''' Descriptor representing a simple structure field ''' def __init__(self, format, offset): self.format = format self.offset = offset def __get__(self, instance, cls): if instance is None: return self else: r = struct.unpack_from(self.format, instance._buffer, self.offset) return r[0] if len(r) == 1 else r class Structure: def __init__(self, bytedata): self._buffer = memoryview(bytedata) 这里我们使用了一个描述器来表示每个结构字段,每个描述器包含一个结构兼容格式的代码以及一个字节偏移量, 存储在内部的内存缓冲中。在 __get__() 方法中,struct.unpack_from() 函数被用来从缓冲中解包一个值,省去了额外的分片或复制操作步骤。 Structure 类就是一个基础类,接受字节数据并存储在内部的内存缓冲中,并被 StructField 描述器使用。 这里使用了 memoryview() ,我们会在后面详细讲解它是用来干嘛的。 使用这个代码,你现在就能定义一个高层次的结构对象来表示上面表格信息所期望的文件格式。例如: class PolyHeader(Structure): file_code = StructField('<i', 0) min_x = StructField('<d', 4) min_y = StructField('<d', 12) max_x = StructField('<d', 20) max_y = StructField('<d', 28) num_polys = StructField('<i', 36) 下面的例子利用这个类来读取之前我们写入的多边形数据的头部数据: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader(f.read(40)) >>> phead.file_code == 0x1234 True >>> phead.min_x 0.5 >>> phead.min_y 0.5 >>> phead.max_x 7.0 >>> phead.max_y 9.2 >>> phead.num_polys 3 >>> 这个很有趣,不过这种方式还是有一些烦人的地方。首先,尽管你获得了一个类接口的便利, 但是这个代码还是有点臃肿,还需要使用者指定很多底层的细节(比如重复使用 StructField ,指定偏移量等)。 另外,返回的结果类同样确实一些便利的方法来计算结构的总数。 任何时候只要你遇到了像这样冗余的类定义,你应该考虑下使用类装饰器或元类。 元类有一个特性就是它能够被用来填充许多低层的实现细节,从而释放使用者的负担。 下面我来举个例子,使用元类稍微改造下我们的 Structure 类: class StructureMeta(type): ''' Metaclass that automatically creates StructField descriptors ''' def __init__(self, clsname, bases, clsdict): fields = getattr(self, '_fields_', []) byte_order = '' offset = 0 for format, fieldname in fields: if format.startswith(('<','>','!','@')): byte_order = format[0] format = format[1:] format = byte_order + format setattr(self, fieldname, StructField(format, offset)) offset += struct.calcsize(format) setattr(self, 'struct_size', offset) class Structure(metaclass=StructureMeta): def __init__(self, bytedata): self._buffer = bytedata @classmethod def from_file(cls, f): return cls(f.read(cls.struct_size)) 使用新的 Structure 类,你可以像下面这样定义一个结构: class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), ('d', 'min_x'), ('d', 'min_y'), ('d', 'max_x'), ('d', 'max_y'), ('i', 'num_polys') ] 正如你所见,这样写就简单多了。我们添加的类方法 from_file() 让我们在不需要知道任何数据的大小和结构的情况下就能轻松的从文件中读取数据。比如: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.file_code == 0x1234 True >>> phead.min_x 0.5 >>> phead.min_y 0.5 >>> phead.max_x 7.0 >>> phead.max_y 9.2 >>> phead.num_polys 3 >>> 一旦你开始使用了元类,你就可以让它变得更加智能。例如,假设你还想支持嵌套的字节结构, 下面是对前面元类的一个小的改进,提供了一个新的辅助描述器来达到想要的效果: class NestedStruct: ''' Descriptor representing a nested structure ''' def __init__(self, name, struct_type, offset): self.name = name self.struct_type = struct_type self.offset = offset def __get__(self, instance, cls): if instance is None: return self else: data = instance._buffer[self.offset: self.offset+self.struct_type.struct_size] result = self.struct_type(data) # Save resulting structure back on instance to avoid # further recomputation of this step setattr(instance, self.name, result) return result class StructureMeta(type): ''' Metaclass that automatically creates StructField descriptors ''' def __init__(self, clsname, bases, clsdict): fields = getattr(self, '_fields_', []) byte_order = '' offset = 0 for format, fieldname in fields: if isinstance(format, StructureMeta): setattr(self, fieldname, NestedStruct(fieldname, format, offset)) offset += format.struct_size else: if format.startswith(('<','>','!','@')): byte_order = format[0] format = format[1:] format = byte_order + format setattr(self, fieldname, StructField(format, offset)) offset += struct.calcsize(format) setattr(self, 'struct_size', offset) 在这段代码中,NestedStruct 描述器被用来叠加另外一个定义在某个内存区域上的结构。 它通过将原始内存缓冲进行切片操作后实例化给定的结构类型。由于底层的内存缓冲区是通过一个内存视图初始化的, 所以这种切片操作不会引发任何的额外的内存复制。相反,它仅仅就是之前的内存的一个叠加而已。 另外,为了防止重复实例化,通过使用和8.10小节同样的技术,描述器保存了该实例中的内部结构对象。 使用这个新的修正版,你就可以像下面这样编写: class Point(Structure): _fields_ = [ ('<d', 'x'), ('d', 'y') ] class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), (Point, 'min'), # nested struct (Point, 'max'), # nested struct ('i', 'num_polys') ] 令人惊讶的是,它也能按照预期的正常工作,我们实际操作下: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.file_code == 0x1234 True >>> phead.min # Nested structure <__main__.Point object at 0x1006a48d0> >>> phead.min.x 0.5 >>> phead.min.y 0.5 >>> phead.max.x 7.0 >>> phead.max.y 9.2 >>> phead.num_polys 3 >>> 到目前为止,一个处理定长记录的框架已经写好了。但是如果组件记录是变长的呢? 比如,多边形文件包含变长的部分。 一种方案是写一个类来表示字节数据,同时写一个工具函数来通过多少方式解析内容。跟6.11小节的代码很类似: class SizedRecord: def __init__(self, bytedata): self._buffer = memoryview(bytedata) @classmethod def from_file(cls, f, size_fmt, includes_size=True): sz_nbytes = struct.calcsize(size_fmt) sz_bytes = f.read(sz_nbytes) sz, = struct.unpack(size_fmt, sz_bytes) buf = f.read(sz - includes_size * sz_nbytes) return cls(buf) def iter_as(self, code): if isinstance(code, str): s = struct.Struct(code) for off in range(0, len(self._buffer), s.size): yield s.unpack_from(self._buffer, off) elif isinstance(code, StructureMeta): size = code.struct_size for off in range(0, len(self._buffer), size): data = self._buffer[off:off+size] yield code(data) 类方法 SizedRecord.from_file() 是一个工具,用来从一个文件中读取带大小前缀的数据块, 这也是很多文件格式常用的方式。作为输入,它接受一个包含大小编码的结构格式编码,并且也是自己形式。 可选的 includes_size 参数指定了字节数是否包含头部大小。 下面是一个例子教你怎样使用从多边形文件中读取单独的多边形数据: >>> f = open('polys.bin', 'rb') >>> phead = PolyHeader.from_file(f) >>> phead.num_polys 3 >>> polydata = [ SizedRecord.from_file(f, '<i') ... for n in range(phead.num_polys) ] >>> polydata [<__main__.SizedRecord object at 0x1006a4d50>, <__main__.SizedRecord object at 0x1006a4f50>, <__main__.SizedRecord object at 0x10070da90>] >>> 可以看出,SizedRecord 实例的内容还没有被解析出来。 可以使用 iter_as() 方法来达到目的,这个方法接受一个结构格式化编码或者是 Structure 类作为输入。 这样子可以很灵活的去解析数据,例如: >>> for n, poly in enumerate(polydata): ... print('Polygon', n) ... for p in poly.iter_as('<dd'): ... print(p) ... Polygon 0 (1.0, 2.5) (3.5, 4.0) (2.5, 1.5) Polygon 1 (7.0, 1.2) (5.1, 3.0) (0.5, 7.5) (0.8, 9.0) Polygon 2 (3.4, 6.3) (1.2, 0.5) (4.6, 9.2) >>> >>> for n, poly in enumerate(polydata): ... print('Polygon', n) ... for p in poly.iter_as(Point): ... print(p.x, p.y) ... Polygon 0 1.0 2.5 3.5 4.0 2.5 1.5 Polygon 1 7.0 1.2 5.1 3.0 0.5 7.5 0.8 9.0 Polygon 2 3.4 6.3 1.2 0.5 4.6 9.2 >>> 将所有这些结合起来,下面是一个 read_polys() 函数的另外一个修正版: class Point(Structure): _fields_ = [ ('<d', 'x'), ('d', 'y') ] class PolyHeader(Structure): _fields_ = [ ('<i', 'file_code'), (Point, 'min'), (Point, 'max'), ('i', 'num_polys') ] def read_polys(filename): polys = [] with open(filename, 'rb') as f: phead = PolyHeader.from_file(f) for n in range(phead.num_polys): rec = SizedRecord.from_file(f, '<i') poly = [ (p.x, p.y) for p in rec.iter_as(Point) ] polys.append(poly) return polys
哦哦喔 2020-04-17 13:26:19 0 浏览量 回答数 0

Quick BI 数据可视化分析平台

2020年入选全球Gartner ABI魔力象限,为中国首个且唯一入选BI产品

问题

第6篇 指针数组字符串(下):报错

    有了指针,数组,字符串的概念,总算可以继续说说 MVC了。前面说过。模块化的设计,接口参数针对模式,而数据是通过缓冲来传递的。那么对于MVC 的 C ࿰...
kun坤 2020-06-08 11:01:44 4 浏览量 回答数 1

云产品推荐

上海奇点人才服务相关的云产品 小程序定制 上海微企信息技术相关的云产品 国内短信套餐包 ECS云服务器安全配置相关的云产品 开发者问答 阿里云建站 自然场景识别相关的云产品 万网 小程序开发制作 视频内容分析 视频集锦 代理记账服务 阿里云AIoT