什么是内存页
- 内存页是系统刻意划分的存储单元,一个内存页中的空间共用一个地址存储。
- 内存页属于逻辑结构,物理上并不存在。
- 例如假设计算机一个内存页大小为4kb,即1个4kb大小只能存一个变量(不管什么数据类型,
int
,double
…)。如果存储多个变量,则无法定位到第二个变量的地址。 - 这样每4kb的空间就只需要记录一个地址即可。(变量的结尾可以通过
\0
等特殊符号来确定) - 这样做的目的是在CPU传输数据的过程减少传输次数,提升速度。
- 内存页的存储单元越大,浪费空间越多;存储单元越小,地址占用空间就越大,在CPU传输过程中速度越慢。这个机制所有的编程语言是通用的。
- 内存和硬盘中存储页的大小为4kb,内存中存储页的大小不能更改,但是硬盘可以,CPU中的内存页称为缓存行,大小为64字节。(目前大多数电脑的配置)
- 由此可见,数组类型数据的性能是比较好的,因为数组数据只需要记录首地址就可以存放高密度的数据。
引用类型数据如何存储
- 基本数据类型的大小固定,无论值取多少,数据占据空间的大小始终固定;引用数据类型的大小会随着值的改变而改变,例如String类型,当值发生改变时会重新开辟一块空间来存储,不存在上限。
- 因此引用类型数据重新赋值都会消耗新的内存页。
- 对于弱类型语言(例如python,js)由于不区分数据类型,一个数组中可以存放不同数据类型的元素,因此无法用一个内存页存储一个数组,因此对空间的消耗非常大,时间复杂度也会很大。
- C语言可以进行数组越界操作,高级语言不行,因此C语言可以通过数组越界操纵一个内存页中的空白部分,但是Java,python等语言不行。
什么是缓冲区
- 缓冲区(buffer):事先申请一个足够大的数组(在一个内存页内),并记录下数据的结尾,每次增删操作在数组内进行从而避免每次操作都要开辟一个新的内存页,大大降低了内存的消耗和申请新内存页的时间消耗。
- buffer原理都是通过这种足够大的数组来实现的,用来解决系统内存页的问题。
String,StringBuilder,StringBuffer的区别
- StringBuilder底层原理就是buffer缓冲区原理;StringBuffer功能类似,因为加了线程锁,在多线程操作下更加安全
- 在单线程下操作时,StringBuilder更加高效,StringBuffer类是其前身
类型 | 特点 | 使用场景 |
---|
String | 大小不可变,线程安全,执行速度慢 | 操作少量数据或不需要操作数据 |
StringBuilder | 大小可变,线程不安全;执行速度最快 | 需要频繁操作数据,且不用考虑线程安全 |
StringBuffer | 大小可变,线程安全;性能较低 | 需要频繁操作数据,且需要考虑线程安全 |
代码测试
public class Test {
public static void main(String[] args) {
long start1 = System.currentTimeMillis();
String x1 = "";
for (int i = 0; i < 100000; i ++)
x1 += "好";
long end1 = System.currentTimeMillis();
System.out.println("String消耗的毫秒数:" + (end1 - start1));
long start2 = System.currentTimeMillis();
StringBuilder x2 = new StringBuilder();
for (int i = 0; i < 100000; i ++)
x2.append("好");
long end2 = System.currentTimeMillis();
System.out.println("StringBuilder消耗的毫秒数:" + (end2 - start2));
long start3 = System.currentTimeMillis();
StringBuffer x3 = new StringBuffer();
for (int i = 0; i < 100000; i ++)
x3.append("好");
long end3 = System.currentTimeMillis();
System.out.println("StringBuffer消耗的毫秒数:" + (end3 - start3));
}
}