python 取array并集_Python内置数据结构原理与性能简易分析

6636e7a5199b9def7c70f1abe1eceac1.png
ins @ngladc

文末左下方阅读原文指向了本人博客链接,不含广告。参考资料中的相关链接,可以在博客文章的最下方获取。推荐苹果手机用户使用浅色模式观看。

前言

对于一些算法题,可以使用Python自带的内置函数解决。但很多时候用就用了,根本不知道内部的细节。这样的话,算时间复杂度和空间复杂度就很有问题。

因此,我最近几天查阅了网上相关资料,并进行归纳和整理。开始我以为复制粘贴就行了,但是呢,我发现有很多东西都没解释得清楚与透彻,在研读的过程中,我经常很懵逼,更有时候,我都怀疑自己智商了。

最后不得不逼自己读相关源码。越看源码,越发现有很多可以分析的,但是考虑到篇幅和时间,就先打住,以后再整个进阶版。

整理完这个以后,我认为呀,不管什么东西还是得追本溯源,这样才靠谱。

目录

  • 前提说明

  • 性能总结

  • 1、列表(list)

    • 列表实现原理

    • 列表函数的时间复杂度

    • 列表函数讲解

    • 性能分析

  • 2、双端队列

    • 双端队列实现原理

    • 双端队列时间复杂度

    • 双端队列函数讲解

    • 性能分析

  • 3、字典(dict)

    • 字典实现原理

    • 字典函数的时间复杂度

    • 字典函数说明

    • 字典性能分析

  • 4、集合(set)

    • 集合函数的时间复杂度

    • 集合性能分析

  • 给自己留一个坑

  • 参考资料

  • 附录

    • Cpython collections 部分源码注释

    • Cpython dict源码部分注释

前提说明

时间复杂度是参考官网:

https://wiki.python.org/moin/TimeComplexity

此页面记录了当前CPython中各种操作的时间复杂度(又名“Big O”或“大欧”)。其他Python实现(或CPython的旧版本或仍在开发版本)可能具有略微不同的性能特征。但是, 通常可以安全地假设它们的速度不超过O(log n)

在所有即将介绍的表格中,n是容器中当前元素的数量,k是参数的值或参数中的元素数。

本文先上结论再进行分析,有助于带着问题去思考答案。

性能总结

1、Python 字典中使用了 hash table,因此查找操作的复杂度为 O(1),而 list 实际是个数组,在 list 中,查找需要遍历整个 list,其复杂度为 O(n),因此对成员的查找访问等操作字典要比 list 更快。

2、set 的 union, intersection,difference 操作要比 list 的迭代要快。因此如果涉及到求 list 交集,并集或者差的问题可以转换为 set 来操作。

3、需要频繁在两端插入或者删除元素,可以选择双端队列。

1、列表(list)

可直接使用,无须调用。

列表实现原理

列表是以数组(Array)实现的,这个数组是 over-allocate 数组。顾名思义,当底层数组容量满了而需要扩充的时候,python依据规则会扩充多个位置出来。比如初始化列表array=[1, 2, 3, 4],向其中添加元素23,此时array对应的底层数组,扩充后的容量不是5,而是8。这就是over-allocate的意义,即扩充容量的时候会多分配一些存储空间。如图1,展示了l.insert(1,5) 的操作。

43b6e2dded391eebc7ecb516a4e555a6.png
图1. insert操作

这里说下,列表的增长模式为:0,4,8,16,25,35,46,58,72,88...

列表函数的时间复杂度

如果要更好地理解列表,就必须熟悉数组这种数据结构。如图 2所示,为列表相关函数的时间复杂度。

e03fdcfc5dc068084eb884d51ba52da7.png
图2. 列表函数的时间复杂度

列表函数讲解

  • append()方法是指在列表末尾增加一个数据项,这里的表强调的是插入1个元素,即没有扩容。
  • extend()方法是指在列表末尾增加一个数据集合;
  • insert()方法是指在某个特定位置前面增加一个数据项,需要移动其他元素位置;
  • len()方法获取列表内元素的个数,因为在列表实现中,其内部维护了一个 Py_ssize_t 类型的变量表示列表内元素的个数,因此时间复杂度为O(1);
  • sort()方法是排序,网上有原理讲解,使用的是 Timesort 排序,该排序结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,它在现实中有很好的效率。空间复杂度为O(n)。其排序的过程大致为,对输入的数字进行分区,然后再进行合并;

性能分析

通过对上表的分析可以发现,列表不太适合做元素的查找删除插入等操作,因为这些都要遍历列表,对应的时间复杂度为O(n)。

访问某个索引的元素、尾部添加元素(append)或删除(pop last)元素这些操作比较适合用列表做,对应的时间复杂度为O(1)。

根据官方上说,列表最大的开销发生在超过了当前所分配的列表大小,这是因为,所有元素都需要移动;或者是在起始位置附近插入或者删除元素,这种情况下所有在该位置后面的元素都需要移动。如果你需要在一个队列的两端进行增删的操作,应当使用collections.deque

如果我们要在业务开发中,判断一个value是否在一个数据集中,如果数据集用列表存储,那此时的判断操作就很耗时,如果我们用hash table(set or dict)来存储,则比较轻松。

2、双端队列

使用时,需要导入:

from collection import deque

双端队列实现原理

deque(双端队列)是以双向链表的形式实现的。(好吧, 一个数组列表而不是对象, 以提高效率)。

为了更好地理解这种结构,可以参照 GitHub 上 CPython collections 模块的第二个 commit 的源码。注释在文末的附录下面。

这里根据注释,我画了一个不太准确的图,其实leftblock和rightblock都是要存储数据的。但在下图,没有标明。

982f44f3a253eb9306476d618e2d928f.png
图3. 存储图

参考资料4,单个block的结构体示意图如下:

daea7b293fe1d16233c073b725572769.png
图4. block

总结来说,deque 内部将一组内存块组织成双向链表的形式,每个内存块可以看成一个 Python 对象的数组, 这个数组与普通数据不同,它是从数组中部往头尾两边填充数据,而平常所见数组大都是从头往后。正因为这个特性,所以叫双端队列。

双端队列时间复杂度

如图所示,为双端队列的相关函数的时间复杂度。

8bb40e8c21d5e3ffe2add8ad52bdaae6.png
图5. 双端队列时间复杂度

双端队列函数讲解

在这种数据结构下,append方法是怎么实现的呢?

  1. 如果 rightblock 可以容纳更多的元素,则放在 rightblock 中
  2. 如果不能,就新建一个 block,然后更新若干指针,将元素放在更新后的 rightblock 中。

性能分析

得益于 deque 这样的结构,它的 pop/popleft/append/appendleft 四种操作的时间复杂度均是 O(1), 用它来实现队列、栈会非常方便和高效。

虽然双端队列中的元素可以从两端弹出,并且队列任意一端都可以入队和出队,但其限定插入和删除操作在表的两端进行。由于这样,查找双端队列中间的元素较为缓慢, 增删元素就更慢了。

3、字典(dict)

可直接使用,无须调用。

字典实现原理

在Python中,字典是通过哈希表实现的。也就是说,字典是一个数组,而数组的索引是经过哈希函数处理后得到的。要理解字典,必须对哈希表这种数据结构比较熟悉。下图6为哈希表的一个逻辑判断:

46685752f959706f59aa6998ea12d104.png
图6. 哈希表判断

这里要注意几点:

  • 使用散列值的一部分进行定位
  • 散列冲突时,使用散列值的另一部分,如果这一部分是包含原始Key的信息,那么不同的Key通过比较就能区分出来。

你可能会问,取哈希值的一部分是怎么取得呢?下图7给了一个种方式,就是将计算得到哈希值 & 数组的长度。

同时,由这张图,我们可以发现Python的哈希函数在键彼此连续的时候表现得很理想,这主要是考虑到通常情况下处理的都是这类形式的数据。然而,一旦我们添加了键'z'就会出现冲突,因为这个键值并不毗邻其他键,且相距较远。

27ac144e21c9563b208e951d7cd547b1.png
图7. 哈希映射

先要声明的是,针对python的不同版本,dict的实现还有所不同,较为详细的介绍请参考资料[6]。老字典只使用一张hash,而新字典还使用了一张Indices表来辅助。这里的indices才是真正的散列表哦,下来列出新的结构:

indices = [None, None, index, None, index, None, index]enteies = [ [hash0, key0, value0],  [hash1, key1, value1],  [hash2, key2, value2]]

字典存储过程:

  • 计算key的hash值 ( hash(key) ),再和mask做与操作 ( mask=字典最小长度(IndicesDictMinSize)- 1 ),运算后会得到一个数字index,这个index就是要插入的indices的下标位置(注:具体算法与Python版本相关,并不一定一样);
  • 得到index后,会找到indices的位置,但是此位置不是存的hash值,而是存的len(enteies),表示该值在enteies中的位置;
  • 如果出现hash冲突,则会继续向下寻找空位置(略有变化的开放寻址),一直到找到剩余空位为止。

字典查找过程:

  • 计算 hash(key),得到hash_value ;
  • 计算 hash_value & ( len(indices) - 1),得到一个数字index ;
  • 计算 indices[index] 的值,得到 entry_index ;
  • 计算 enteies[entey_index] 的值 ,为最终值。

为方便理解,这里我做了一个图,可以看到 indices 起到一个桥梁的作用。画完这个图,再感叹一句,设计还是挺巧妙的。

18e961d0804e05db93e3f6304ad290fd.png
图8. 字典示意图

这里补充下,关于哈希冲突,是怎么寻找下一个数组位置的。源码中用到的是以下公式:

j = ((5*j) + 1) mod 2**i

这里的 j 有两层含义,赋值号左边的为数组的下一个下标,赋值号右边的是当前发生冲突的下标。而 2 ** i可以理解数组长度。举例说明,对于要给size大小为2 ** 3来说:

j_prev = 0 ; j_next = ((5 * 0) + 1) mod 8 = 1  j_prev = 1 ; j_next = ((5 * 1) + 1) mod 8  = 6j_prev = 6 ; j_next = ((5 * 6) + 1) mod 8  = 7j_prev = 7 ; j_next = ((5 * 7) + 1) mod  8 = 4j_prev = 4 ; j_next = ((5 * 4 ) + 1) mod 8 = 5

以此类推,最后回到起点为0。以下就是哈希冲突的轨迹

0 -> 1 -> 6 -> 7 -> 4 -> 5 -> 2 -> 3 -> 0

字典函数的时间复杂度

下列字典的平均情况基于以下假设:

  1. 对象的散列函数足够撸棒(robust), 不会发生冲突。
  2. 字典的键是从所有可能的键的集合中随机选择的。
c61dfec0fa9d596579b8cfbfccab0660.png
图9. 字典函数的时间复杂度

小窍门:只使用字符串作为字典的键。这么做虽然不会影响算法的时间复杂度, 但会对常数项产生显著的影响, 这决定了你的一段程序能多快跑完。

字典函数说明

  1. 这些操作依赖于“摊销最坏情况”的“摊销”部分。根据容器的历史, 个别动作可能需要很长时间。
  2. 对于这些操作, 最坏的情况n是容器达到的最大尺寸, 而不仅仅是当前的大小。例如, 如果一个N个元素的字典, 然后删除N-1个元素, 这个字典会重新为N个元素调整大小, 而不是当前的一个元素, 所以时间复杂度是O(n)。

字典性能分析

字典的查询、添加、删除的平均时间复杂度都是O(1),相比列表与元祖,性能更优。但是,如果发生散列冲突,或者容器需要扩充,那么时间复杂度就要考虑最差的情况 O(n)。所以说字典及其依赖哈希算法,真正要灵活运用词典时,还需要查看底层的哈希算法。

4、集合(set)

dict与set实现原理是一样的,都是将实际的值放到list中。唯一不同的在于hash函数操作的对象,对于dict,hash函数操作的是其key,而对于set是直接操作的它的元素。

假设操作内容为x,其作为因变量,放入hash函数,通过运算后取list的余数,转化为一个list的下标,此下标位置对于set而言用来放其本身。

而对于dict则是创建了两个list,一个listf存储哈希表对应的下标,另一个list中存储哈希表具体对应的值。

这里为了更好地理解,对比上面字典那个图,我尝试画一个图。

f70723e847f820cc3a85b5103d2bf07f.png
图10. 集合映射

集合函数的时间复杂度

下图是函数的时间复杂度:

4bbecdb8e7e86d36ac7dc37532fdd6cc.png
图11. 集合时间复杂度

集合性能分析

由源码得知, 求差集(s-t, 或s.difference(t))运算与更新为差集(s.difference_uptate(t))运算的时间复杂度并不相同!

  • 第一个是O(len(s))(对于s中的每个元素, 如果不在t中, 将它添加到新集合中)。
  • 第二个是O(len(t))(对于t中的每个元素, 将其从s中删除)。

因此, 必须注意哪个是首选, 取决于哪一个是最长的集合以及是否需要新的集合。

集合的s-t运算中, s和t都要是set类型。如果t不是set类型, 但是是可迭代的, 你可以使用等价的方法达到目的, 比如 s.difference(l), l是个list类型。

另外,列表的一些集合运算,可以转成集合类型来操作,速度更快。

给自己留一个坑

自己也尝试读了一下一些数据结构的源码,虽然很多看不懂,但是抓到一些关键信息。比如下面的代码和图片。

static Py_ssize_tlist_length(PyListObject *a){return Py_SIZE(a);}

下图为dictobject.c里的一个函数:

00dd49d584987606f8f3125167504ca9.png
图12. 集合时间复杂度

参考资料

[1]  Python内置方法的时间复杂度

[2] TimeComplexity

[3] python list 之时间复杂度分析

[4] How collections.deque works?,

[5] 深入 Python 列表的内部实现;

[6] Python字典dict实现原理

附录

Cpython list 部分源码注释

源码地址传送门:https://github.com/python/cpython/blob/master/Objects/listobject.c

/* This over-allocates proportional to the list size, making room* for additional growth. The over-allocation is mild, but is* enough to give linear-time amortized behavior over a long* sequence of appends() in the presence of a poorly-performing* system realloc().* Add padding to make the allocated size multiple of 4.* The growth pattern is: 0, 4, 8, 16, 24, 32, 40, 52, 64, 76, ...* Note: new_allocated won't overflow because the largest possible value* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.*/

Cpython collections 部分源码注释

源码地址传送门:https://github.com/python/cpython/blob/master/Modules/_collectionsmodule.c

/* The block length may be set to any number over 1.  Larger numbers* reduce the number of calls to the memory allocator but take more* memory.  Ideally, BLOCKLEN should be set with an eye to the* length of a cache line.*/#define BLOCKLEN 62#define CENTER ((BLOCKLEN - 1) / 2)/* A `dequeobject` is composed of a doubly-linked list of `block` nodes.* This list is not circular (the leftmost block has leftlink==NULL,* and the rightmost block has rightlink==NULL).  A deque d's first* element is at d.leftblock[leftindex] and its last element is at* d.rightblock[rightindex]; note that, unlike as for Python slice* indices, these indices are inclusive on both ends.  By being inclusive* on both ends, algorithms for left and right operations become* symmetrical which simplifies the design.* The list of blocks is never empty, so d.leftblock and d.rightblock* are never equal to NULL.* The indices, d.leftindex and d.rightindex are always in the range*     0 <= index < BLOCKLEN.* Their exact relationship is:*     (d.leftindex + d.len - 1) % BLOCKLEN == d.rightindex.* Empty deques have d.len == 0; d.leftblock==d.rightblock;* d.leftindex == CENTER+1; and d.rightindex == CENTER.* Checking for d.len == 0 is the intended way to see whether d is empty.* Whenever d.leftblock == d.rightblock,*     d.leftindex + d.len - 1 == d.rightindex.* However, when d.leftblock != d.rightblock, d.leftindex and d.rightindex* become indices into distinct blocks and either may be larger than the* other.*/

Cpython dict源码部分注释

源码地址传送门:https://github.com/python/cpython/blob/master/Objects/dictobject.c

/*layout:+---------------+| dk_refcnt         || dk_size            || dk_lookup       || dk_usable        || dk_nentries      |+---------------+| dk_indices       ||                         |+---------------+| dk_entries       ||                     |+---------------+dk_indices is actual hashtable. It holds index in entries, or DKIX_EMPTY(-1) orDKIX_DUMMY(-2).dk_entries is array of PyDictKeyEntry. Its size is USABLE_FRACTION(dk_size).DK_ENTRIES(dk) can be used to get pointer to entries.The first half of collision resolution is to visit table indices via thisrecurrence:But catering to unusual cases should not slow the usual ones, so we just take the last i bits anyway. It's up to collision resolution to do the rest. Ifwe *usually* find the key we're looking for on the first try (and, it turns out, we usually do -- the table load factor is kept under 2/3, so the oddsare solidly in our favor), then it makes best sense to keep the initial index computation dirt cheap.j = ((5*j) + 1) mod 2**iFor any initial j in range(2**i), repeating that 2**i times generates eachint in range(2**i) exactly once (see any text on random-number generation forproof). By itself, this doesn't help much: like linear probing (settingj += 1, or j -= 1, on each loop trip), it scans the table entries in a fixedorder. This would be bad, except that's not the only thing we do, and it'sactually *good* in the common cases where hash keys are consecutive.In an example that's really too small to make this entirely clear, for a table ofsize 2**3 the order of indices is:0 -> 1 -> 6 -> 7 -> 4 -> 5 -> 2 -> 3 -> 0 [and here it's repeating]*/

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

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

相关文章

ae合成复制脚本_稀缺资源—这几个AE脚本使用频率很高,赶紧收藏吧!

「第442期」毫无疑问&#xff0c;AE已经成为目前制作短视频比较主流的软件&#xff0c;效果的多样化深受很多创作者的喜爱。随着对软件的熟悉&#xff0c;越发觉得AE主要是基于多图层控制的软件。如果制作一些简单的效果&#xff0c;几个图层几个滤镜就可以搞定&#xff0c;但如…

程序员的幸福感和颈椎病

脖子一直疼&#xff01; 去医院检查&#xff0c;拍片子的医生在造影室里冲我喊&#xff1a; “小伙子&#xff0c;你多大年纪啦&#xff1f;” 我说&#xff1a;“我三十来岁&#xff0c;咋啦” 医生说&#xff1a;“怎么这么年轻就得这种病啊&#xff01;” 我当时腿就有点软&…

python实现词语相似度计算分析_相似度计算的方法及Python实现

现实生活中&#xff0c;我们经常提到距离这个词&#xff0c;本文谈的相似度就是基于距离定义的&#xff0c;当两个向量之间的距离特别小时&#xff0c;就说这俩个向量相似度高&#xff0c;反之相似度不高。所以&#xff0c;衡量相似度的指标就是距离度量。经常使用的相似度计算…

poll函数_I/O复用 - 三组I/O复用函数的比较

在之前的文章中 I/O复用 - epoll 和 I/O复用 - select&poll 中我们讨论了三组I/O复用的系统调用&#xff0c;这3组系统调用都能同时监听多个文件描述符。它们将等待由timeout参数指定的超时时间&#xff0c;直到一个或多个文件描述符上有事件发生时返回&#xff0c;返回值是…

css3画图那些事(三角形、圆形、梯形等)

闲来无事&#xff0c;写写图形。当时巩固一下css3吧.。前端小白&#xff0c;写的不好还请前辈多指教。 三角形 { width: 0;height: 0;border-bottom: 140px solid red ;border-right: 70px solid transparent;border-left: 70px solid transparent; } 圆形 {width: 0px;height…

docker 数据库 mysql_在Docker中体验数据库之MySql

在上一篇在Docker中体验数据库之Mongodb之后&#xff0c;这次记录一下在docker中安装mysql。过程要比Mongodb麻烦一点……参考网址&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/linux-installation-docker.htmlhttps://hub.docker.com/r/mysql/mysql-server/安装过程如…

使用JMeter对异步HTTP / REST服务进行压力/负载测试

尽管我一直在使用JMeter进行Web应用程序的压力测试和负载测试好几次&#xff0c;但我们还是花了一些时间才弄清楚如何使用该工具测试基于异步HTTP / REST的服务。 在我们这里&#xff0c;我是指一名程序员&#xff0c; Holger Staudacher &#xff0c;我很荣幸能与当前的一个项…

web前端学习之ruby标记和rt/rp标记

ruby 标记定义ruby注释&#xff08;中文注音或字符&#xff09;。ruby标记与rt标记一同使用。ruby标记由一个或多个字符&#xff08;需要一个解释/发音&#xff09;和一个提供该信息的rt 标记组成&#xff0c;还包括可选的rp标记&#xff0c;定义当浏览器不支持ruby 标记时显示…

作为一名程序员,聊聊我们的现状和未来

前言&#xff1a;互联网这个高速发展的新兴行业&#xff0c;注定是敢想敢干敢创新&#xff0c;耐劳耐操耐折腾年轻人的天下&#xff1f; 我们所在的互联网行业&#xff0c;不断地有新的公司冒出&#xff0c;有新的商业模式成形&#xff0c;有新的产品形态影响着大家的生活日常&…

ubuntu dhcp ping 不通 自己_??2、DHCP安装和配置

DHCP动态主机设置协议&#xff0c;是一个局域网的网络协议&#xff0c;使用UDP协议工作&#xff0c;可以快速分配IP地址&#xff0c;解决内网IP不足、手动配置IP造成IP冲突以及内网机器多手工配置比较麻烦的问题。1.把win2008和win2003设置同一网段&#xff0c;网络适配器—配置…

选择您的Java EE 6应用服务器

我被问到的第一个问题是&#xff1a;“我们应该使用哪个Java EE应用服务器&#xff1f;”。 随着Java EE 6的日益普及&#xff0c;新的兼容应用程序服务器获得了认证。 当前的官方兼容性和认证矩阵列出了针对完全配置文件&#xff0c;Web配置文件或两者认证的12种不同产品。 如…

HTML表格属性及简单实例

这里主要总结记录下表格的一些属性和简单的样式&#xff0c;方便以后不时之需。 1、<table> 用来定义HTML的表格&#xff0c;具有本地属性 border 表示边框&#xff0c;border属性的值必须为1或空字符串("")。该属性不会控制边框的样式&#xff0c;而是由CSS来…

linux mysql启动_MySQL 安装(二)

MySQL 安装所有平台的Mysql下载地址为&#xff1a;MySQL 下载 . 挑选你需要的 MySQL Community Server 版本及对应的平台。Linux/UNIX上安装MySQLLinux平台上推荐使用RPM包来安装MySQL&#xff0c;MySQL AB提供了以下RPM包的下载地址&#xff1a;MySQL - MySQL服务器。你需要该…

谁在偷你的记忆? 应用服务器版

您创建了一个了不起的应用程序。 您将其投入生产。 您会发现您没有足够的可用内存。 即使您的所有测量结果&#xff08;可能是借助我们的小型公用事业公司进行的测量 &#xff09;都表明您应该还不错。 我们计划发布一系列博客文章&#xff0c;研究堆消失的位置&#xff0c;并…

遗忘的html标签

1 <span>x</span><sup>2</sup><span> y10</span> 2 <br> 3 <span>H</span><sub>2</sub><span>O</span> <sup> 标签可定义上标文本。 包含在 <sup> 标签和其结束标签 …

mysql数据库索引页号为什么从3开始_MySQL数据库快问快答

原标题&#xff1a;MySQL数据库快问快答前言今天楼主给大家列一下关于数据库几个常见问题的要点&#xff0c;如果大家对其中的问题感兴趣&#xff0c;可以自行扩展研究。1. UNION ALL 与 UNION 的区别UNION和UNION ALL关键字都是将两个结果集合并为一个。UNION在进行表链接后会…

java arraylist排序_最全Java集合笔记

集合概述什么是集合集合框架&#xff1a;用于存储数据的容器。集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容&#xff1a;对外的接口、接口的实现和对集合运算的算法。接口&#xff1a;表示集合的抽象数据类型。接口允许我们操作…

CachedIntrospectionResults 初始化

转载于:https://www.cnblogs.com/xiluhua/p/7862985.html

为什么有些内联(行内)元素可以设置宽高?

为什么有些内联&#xff08;行内&#xff09;元素如img、input可以设置宽高&#xff1f; 在说明之前我们先来了解一些定义。 块级元素和内联元素&#xff1a; ①块级元素总是独占一行&#xff0c;表现为另起一行开始&#xff0c;而且其后的元素也必须另起一行显示。 宽度(w…

在Eclipse中高效运行HTTP / REST集成测试

最近&#xff0c;我有机会使用由我亲爱的Holger Staudacher编写的OSGi-JAX-RS-Connector库。 通过连接器&#xff0c;您可以通过将Path注释的类型注册为OSGi服务来轻松发布资源-实际上&#xff0c;它工作得很好。 对于我来说&#xff0c;使用普通的JUnit测试编写驱动的服务类测…