关于C#的一些基础知识点汇总
1.C#结构体可以继承接口吗?会不会产生GC?
在 C# 中,结构体不能继承类,但可以实现接口。
代码:
interface IMyInterface
{void MyMethod();
}struct MyStruct : IMyInterface
{public void MyMethod(){Console.WriteLine("MyMethod implemented in struct");}
}
当结构体实现接口并被使用时,通常不会产生垃圾回收(GC)。
因为结构体是值类型,在栈上分配内存(当作为局部变量或方法参数时)或者作为引用类型中的字段时是内联存储的。然而,如果结构体包含引用类型的字段,并且对这些引用类型进行了一些复杂的操作,可能会间接导致垃圾回收。但这不是因为结构体实现接口本身导致的,而是由于对引用类型的操作。
2.什么是内联存储?
在 C# 中,“内联存储” 通常指值类型(如结构体)在被使用时,其内存是直接分配在当前的执行环境中,而不是在托管堆上。例如,当一个结构体作为方法的局部变量或参数时,它的内存会在栈上分配,这种分配和释放的效率通常比较高,因为不需要进行垃圾回收的操作。相比之下,引用类型(如类)的实例通常是在托管堆上分配内存的。
代码:
void SomeMethod()
{MyStruct myStruct = new MyStruct(); // 结构体在栈上内联存储// 对 myStruct 进行操作
}
3.结构体实现接口和类继承接口有什么区别?
结构体实现接口和类继承接口有以下一些区别:
内存分配:
结构体是值类型,通常在栈上分配内存(当作为局部变量或方法参数时)或者作为引用类型中的字段时内联存储。
类是引用类型,在托管堆上分配内存。
继承行为:
结构体不能从类或其他结构体继承,但可以实现接口。
类可以从其他类继承,也可以实现接口。
装箱与拆箱:
当结构体作为接口类型进行操作时,如果需要转换为引用类型(如 object 或接口类型),会发生装箱操作。将引用类型转换回结构体时会进行拆箱操作。装箱和拆箱会带来一定的性能开销。
类作为接口类型使用时不存在装箱和拆箱,因为类本身就是引用类型。
构造函数:
结构体不能有显式的无参数构造函数,会有一个默认的隐式无参构造函数,它将结构体的所有字段初始化为默认值。
类可以有多个自定义的构造函数。
传递方式:
结构体作为参数传递时,通常是按值传递,会复制整个结构体的值。
类作为参数传递时,默认是按引用传递。
可变性:
结构体通常被设计为不可变(immutable),以避免意外的修改导致难以追踪的错误。虽然可以修改结构体的成员,但这可能不符合最佳实践。
类可以更灵活地处理成员的修改。
4.结构体实现接口时,是否可以在接口中定义方法?
当结构体实现接口时,接口中是可以定义方法的。
代码:
interface IMyInterface
{void MyMethod();
}struct MyStruct : IMyInterface
{public void MyMethod(){Console.WriteLine("MyMethod implemented in struct");}
}
5.堆和栈的区别
在计算机内存管理中,堆(Heap)和栈(Stack)有以下一些主要区别:
内存分配方式:
栈:由编译器自动管理,分配和释放内存。当一个函数被调用时,为其分配栈空间,函数结束时自动释放。
堆:由程序员手动分配和释放(使用 new、malloc 等),若不手动释放,会在程序结束或垃圾回收时释放。
内存增长方向:
栈:向低地址方向增长。
堆:向高地址方向增长。
内存分配效率:
栈:分配效率高,因为其操作简单,空间连续。
堆:分配效率相对较低,因为涉及复杂的内存管理机制。
存储内容:
栈:主要存储局部变量、函数参数、返回值等。
堆:用于存储对象、动态分配的数组等较大且生存期不确定的数据。
内存大小:
栈:空间一般较小。
堆:空间较大,但不是无限的。
数据生存周期:
栈:变量的生存周期与所在的函数或代码块相关,函数结束时变量销毁。
堆:直到程序员手动释放或垃圾回收时才销毁。
碎片问题:
栈:不会产生碎片。
堆:频繁的分配和释放可能导致内存碎片。
6.C#直接执行IEnumerator方法,不使用StartCoroutine会执行吗?会产生GC吗?
在 C# 中,如果直接执行一个返回IEunmerator的方法,而不使用 StartCoroutine ,它会像普通方法一样执行,但不会按照协程的逻辑进行迭代和暂停。至于垃圾回收(GC),这取决于方法内部的具体实现。如果方法中没有创建大量的临时对象或者引用类型,并且没有导致内存泄漏,通常不会直接因为这种方法的执行而引发垃圾回收。然而,如果方法内部创建了很多对象并且没有被正确释放,或者存在一些长期持有的引用导致对象无法被回收,就可能会产生 GC 压力。
代码:
internal class Program{static IEnumerator MyEnumeratorMethod(){Console.WriteLine("开始生成数字");yield return 1;Console.WriteLine("继续生成数字");yield return 2;Console.WriteLine("结束生成数字");}static void Main(string[] args){MyEnumeratorMethod(); // 直接执行,不会按照协程逻辑暂停Console.WriteLine();}}
如果执行上述代码会发现,控制台什么都没有输出,如下图:
原因:
迭代器的延迟执行特性:
当一个函数返回 IEnumerator 时,它实际上是在定义一个迭代器。迭代器的设计理念是实现延迟执行,即函数体中的代码不会在函数被调用时立即执行,而是在需要逐个获取值时才执行。
以 C# 的迭代器实现为例,编译器会对包含 yield return 语句的函数进行特殊处理。当你调用返回 IEnumerator 的函数时,编译器会生成一个状态机类。这个状态机类实现了 IEnumerator 接口,并且保存了函数执行的状态信息。
状态机的工作原理:
当函数第一次被调用时,编译器生成的状态机对象被创建,但函数体中的代码并未执行。状态机的初始状态表示函数尚未开始执行。
当调用 IEnumerator 的 MoveNext 方法时,状态机开始执行函数体中的代码,直到遇到第一个 yield return 语句。此时,yield return 语句返回一个值(如果有),并且暂停函数的执行,将状态机的状态保存下来。下次调用 MoveNext 时,状态机从上次暂停的位置继续执行,直到遇到下一个 yield return 或函数结束。
internal class Program{static IEnumerator MyEnumeratorMethod(){Console.WriteLine("开始生成数字");yield return 1;Console.WriteLine("继续生成数字");yield return 2;Console.WriteLine("结束生成数字");}static void Main(string[] args){//FrameSyncServer server = new FrameSyncServer();//server.Start();IEnumerator enumerator = MyEnumeratorMethod(); // 直接执行,不会按照协程逻辑暂停// 第一次调用MoveNextbool hasNext = enumerator.MoveNext();if (hasNext){Console.WriteLine($"获取到的值: {enumerator.Current}");}// 第二次调用MoveNexthasNext = enumerator.MoveNext();if (hasNext){Console.WriteLine($"获取到的值: {enumerator.Current}");}// 第三次调用MoveNext,此时函数结束,MoveNext返回falsehasNext = enumerator.MoveNext();Console.WriteLine();}}
结果:
与普通函数执行的区别:
普通函数在调用时,其栈帧被创建,函数体内的代码按照顺序依次执行,直到遇到 return 语句返回结果,然后栈帧被销毁。而对于返回 IEnumerator 的函数,调用它只是创建了一个状态机对象,函数体的执行被延迟,并且可以通过 MoveNext 方法分阶段执行,这种机制使得代码可以更灵活地处理复杂的迭代或异步操作。
7.除了直接执行和使用 StartCoroutine,还有哪些方式可以在 C# 中使用协程?
在 C# 中,除了直接执行和使用 StartCoroutine 来处理协程外,还可以通过以下方式使用协程:
在异步方法中使用 yield return :可以在 async 异步方法中使用 yield return 与协程结合,例如在 async Task 方法中。
结合自定义的调度器(Scheduler):创建自定义的调度器来控制协程的执行逻辑和时机。
利用第三方库或框架提供的扩展和机制:有些第三方库可能提供了特定的方式来更灵活地处理协程。
代码:
class Program
{static async Task Main(){await MyAsyncMethod();}static async Task MyAsyncMethod(){Console.WriteLine("Method started");await Task.Delay(1000); Console.WriteLine("After delay");}
}
8.结合自定义调度器使用协程的优缺点是什么?会带来安全风险吗?
结合自定义调度器使用协程具有以下优点:
优点:
精细控制执行时机:可以根据具体的业务需求精确地控制协程的执行顺序、暂停和恢复时机,更好地适配特定的应用场景。
优化资源利用:能够根据系统资源的情况,如 CPU 负载、内存使用等,更有效地安排协程的执行,提高资源利用率。
处理复杂逻辑:适用于一些复杂的并发和同步逻辑,使代码更具可读性和可维护性。
跨平台适配:有助于在不同的平台或环境中实现一致的协程行为,增强代码的可移植性。
缺点:
增加开发复杂度:需要开发者自行实现调度器的逻辑,这增加了开发的难度和出错的可能性。
调试困难:自定义的调度器可能导致调试过程变得复杂,因为协程的执行不再遵循默认的规则。
潜在的性能开销:如果调度器的实现不够高效,可能会引入额外的性能开销。
维护成本高:自定义的调度器需要持续的维护和优化,以适应项目的变化和新的需求。
结合自定义调度器使用协程可能会带来一些潜在的安全风险,例如:
竞态条件:如果调度器的逻辑不正确,可能导致多个协程同时访问共享资源,从而引发竞态条件,导致数据不一致或错误的结果。
死锁:不当的调度顺序或资源管理可能导致死锁情况,即协程相互等待对方释放资源,造成程序停滞。
异常处理不当:在自定义调度器中,如果对协程执行过程中抛出的异常处理不当,可能导致整个系统的不稳定或错误传播。
内存泄漏:如果协程没有被正确地清理或释放,可能会导致内存泄漏,尤其是在复杂的调度逻辑中容易出现。
并发安全问题:自定义调度器可能打破了默认的并发安全机制,如果没有额外的防护措施,可能会引发并发访问的错误。
9.接口与抽象类的区别
成员定义:
接口只能包含方法、属性、事件和索引器的声明,不能包含字段和方法的实现。
抽象类可以包含抽象方法(只有声明,没有实现)和普通方法(有声明也有实现),还可以包含字段。
继承限制:
一个类可以实现多个接口。
一个类只能继承一个抽象类(但可以同时实现多个接口)。
继承实现:
实现接口的类必须实现接口中定义的所有成员。
继承抽象类的类可以选择实现抽象类中的抽象方法,也可以继承抽象方法但仍然保持为抽象类。
访问修饰符:
接口成员默认是公共的,不能使用访问修饰符。
抽象类中的成员可以使用各种访问修饰符。
实例化:
接口不能被实例化。
抽象类也不能被实例化,但可以有构造函数,用于在派生类的对象创建时被调用。
10.值类型与引用类型的区别
内存分配:
值类型的变量直接包含其数据,通常在栈上分配内存(如果是作为引用类型的成员,则在堆上内联存储)。
引用类型的变量存储的是对实际数据的引用(内存地址),数据存储在托管堆上。
传递方式:
值类型在作为参数传递给方法时,默认是按值传递,即创建一个副本。
引用类型在作为参数传递时,默认是按引用传递,对参数的修改会影响到原始对象。
比较方式:
值类型通过比较其值来确定相等性。
引用类型默认通过比较引用(即是否指向同一个对象)来确定相等性,除非重写 Equals 方法进行自定义的相等性比较。
垃圾回收:
值类型不受垃圾回收器直接管理,因为它们的内存分配和释放随着其作用域结束而自动处理。
引用类型由垃圾回收器管理其内存的回收。
继承:
值类型不能继承自其他类型(除了 System.ValueType)。
引用类型可以继承自其他类或接口。
11.什么是装箱和拆箱?
在 C# 中,装箱(Boxing)是将值类型转换为引用类型的过程,拆箱(Unboxing)则是将引用类型转换回值类型的过程。装箱和拆箱操作可能会带来一些性能开销,因为涉及到内存的重新分配和数据的复制。
代码:
int num = 10;
object obj = num; // 装箱
而当从一个已装箱的对象中把值类型提取出来时,就会发生拆箱操作:
代码:
int numAgain = (int)obj; // 拆箱
12.C#中的GC优化:
减少对象创建:
避免在频繁执行的代码段中创建不必要的对象。例如,使用对象池来重复利用对象,而不是频繁创建和销毁。
字符串操作优化:
对于大量的字符串连接操作,考虑使用 StringBuilder 类,因为频繁创建新的字符串对象可能导致内存压力和 GC 开销。
缓存常用对象:
对于经常使用但创建成本较高的对象,进行缓存以减少重复创建。
避免大对象的频繁创建和销毁:
大对象(通常大于 85000 字节)被分配在大对象堆(LOH)上,LOH 的垃圾回收相对不频繁且成本较高。
代码:
class ObjectCache
{private static Dictionary<int, MyObject> cache = new Dictionary<int, MyObject>();public static MyObject GetObject(int key){if (!cache.ContainsKey(key)){cache[key] = new MyObject(key);}return cache[key];}
}class MyObject
{public int Key { get; }public MyObject(int key){Key = key;}
}
非托管资源管理:
对于使用非托管资源(如文件句柄、数据库连接等)的对象,及时释放非托管资源。实现 IDisposable 接口,并在使用完后正确调用 Dispose 方法或使用 using 语句。
控制对象的生命周期:
尽量使对象的生命周期与使用它们的逻辑范围相匹配,避免对象长时间存活但不再被使用。
选择合适的集合类型:
根据实际需求选择合适的集合类型。例如,如果需要频繁添加和删除元素,LinkedList 可能比 ArrayList 更合适,因为 ArrayList 的扩容可能导致大量的内存重新分配和对象移动。
避免短时间内大量临时对象的创建:
例如,在循环中,如果可能,尽量复用对象而不是每次循环都创建新对象。
优化数据结构:
对于大型的数据结构,如数组,如果可能的话,预先分配足够的空间,避免频繁的扩容操作。
减少引用类型字段的使用:
特别是在值类型(如结构体)中,过多的引用类型字段可能会增加内存管理的复杂性。
考虑使用弱引用(WeakReference):
在某些情况下,使用弱引用可以在内存紧张时允许垃圾回收器回收被弱引用的对象,同时提供一种在对象未被回收时访问的方式。
分代优化:
了解垃圾回收的分代机制,对于生命周期较短的对象尽量放在新生代,以便更快速地回收。
避免不必要的装箱和拆箱操作:
除了前面提到的基本注意事项外,在一些复杂的数据转换和接口调用中要特别小心。
异步和并发中的内存管理:
在多线程或异步操作中,确保正确处理共享对象的内存访问,避免竞态条件和内存泄漏。
13.C#中大对象堆是什么?
在 C# 中,大对象堆(Large Object Heap,简称 LOH)是托管堆的一部分,用于存储大于 85000 字节的对象。
与普通的托管堆(用于存储较小的对象)相比,大对象堆具有以下特点:
较少的垃圾回收:大对象堆上的垃圾回收不像普通堆那么频繁,通常在内存压力较大时才会触发。
不进行内存压缩:由于大对象的移动成本较高,在垃圾回收时一般不会对大对象堆进行内存压缩操作。
内存分配策略:大对象的分配不是连续的,可能会有一些碎片。
如果应用程序频繁地创建和销毁大对象,可能会导致性能问题和内存碎片。
代码:
class Program
{static void Main(){byte[] largeArray = new byte[100000]; // 可能分配在大对象堆上}
}
14.string 是引用类型吗?可以继承吗?
在 C# 中,string 是引用类型。但是 string 是密封类(sealed),不能被继承。
15.为什么string被设计为密封类?
字符串的不变性:string 对象在创建后其值是不可变的。将其密封可以确保这种不变性不被意外打破,有助于提高程序的稳定性和可预测性。
性能和优化:.NET 框架对字符串的操作和存储进行了大量的优化。密封类可以让这些优化得以保证,避免因继承导致的意外行为影响性能。
安全性:防止恶意或错误的继承导致字符串的行为不符合预期,从而提高了整个系统的安全性。
一致性和通用性:字符串在各种编程语言中通常都被视为基本且不可变的数据类型。将 string 密封有助于保持 C# 中字符串处理的一致性和与其他语言的兼容性。
避免错误和混淆:防止开发者在继承 string 时可能引入的错误,因为对字符串的自定义继承往往不是常见和必要的需求,反而容易导致复杂和难以理解的代码。
16.List 和 Dictionary有什么区别?
数据存储方式:
List 按照元素添加的顺序存储元素。
Dictionary 以键值对的形式存储数据,通过键来快速查找对应的值。
访问方式:
访问 List 中的元素通常通过索引。
访问 Dictionary 中的元素通过指定的键。
元素唯一性:
List 可以包含重复的元素。
Dictionary 的键必须是唯一的,但值可以重复。
查找效率:
查找特定元素时,如果知道索引,List 的查找速度较快。但如果要查找某个特定值,需要遍历整个列表,效率较低。
Dictionary 通过键进行查找,通常具有接近 O (1) 的平均查找时间,效率很高。
存储结构:
List 内部通常基于数组实现。
Dictionary 常见的实现方式如哈希表。
17.List和ArrayList有什么区别?
类型安全:
List 是强类型的,在创建时需要指定元素的类型。
ArrayList 可以存储不同类型的对象,不是类型安全的。
性能:
由于 List 的强类型特性,在操作时通常比 ArrayList 具有更好的性能,特别是在涉及类型转换时。
泛型支持:
List 是泛型集合,充分利用了泛型的优势,减少了类型转换的开销和潜在的运行时错误。
ArrayList 不是泛型的。
18.List、ArrayList、Dictionary的使用场景是什么?
List 的使用场景:
当需要存储同一种类型的元素,并且对类型安全性有要求时,比如存储一组整数、字符串等。
当性能和效率较为重要,需要避免不必要的类型转换和运行时错误检查时。
ArrayList 的使用场景:
在一些旧的代码中,如果需要存储不同类型的对象,且类型在运行时才能确定。
当需要与不支持泛型的旧代码进行交互时。
不过,在现代 C# 编程中,一般更推荐使用 List ,因为它提供了更好的类型安全性和性能。
19.List和Dictionary的使用场景是什么?
List 的使用场景:
存储有序的同类型元素集合,例如存储学生的成绩列表、商品列表等。
当需要按照元素的添加顺序进行遍历和处理时。
当需要频繁地在末尾添加或删除元素时。
Dictionary 的使用场景:
快速根据键来查找对应的值,例如根据学生的学号查找学生信息。
存储键值对形式的数据,其中键是唯一的,例如存储单词及其释义。
构建映射关系,例如将城市名称映射到对应的人口数量。
20.Dictionary的实现原理是什么?
Dictionary 在 C# 中的常见实现原理通常基于哈希表(Hash Table)。
当向 Dictionary 中添加键值对时,会计算键的哈希值。哈希值用于确定键值对在内部存储结构中的位置。
如果多个键计算出的哈希值相同(这被称为哈希冲突),Dictionary 通常会通过某种处理冲突的方法(如链表法或开放地址法)来存储这些键值对。
在查找元素时,同样先计算键的哈希值,然后快速定位到可能存储该键值对的位置,再进行比较确认是否为要查找的键。
21.在使用Dictionary时,如何处理可能出现的哈希冲突?
在 C# 的 Dictionary 中,处理哈希冲突通常采用以下几种方式:
链表法:当发生哈希冲突时,将具有相同哈希值的元素存储在一个链表中。在查找时,通过哈希值定位到链表位置,然后遍历链表来查找具体的键。
开放地址法:如果发生冲突,通过一定的探测策略(如线性探测、二次探测等)在哈希表中寻找其他空闲的位置来存储冲突的元素。
在使用 Dictionary 时,一般不需要手动处理哈希冲突,.NET 框架已经在内部实现了高效的处理机制。
但要注意以下几点来减少哈希冲突的影响:
选择合适的键类型:尽量选择具有良好哈希分布特性的键类型,以减少冲突的可能性。
控制字典的大小:避免过度填充字典,根据预期的元素数量合理调整初始容量
22.哈希冲突的解决方式有哪些?
开放定址法:
线性探测:发生冲突时,依次检查下一个位置,直到找到空闲位置。
二次探测:冲突时,按照一定的步长(如 1²、2²、3² 等)进行探测。
链地址法:将哈希值相同的元素存储在一个链表中。
再哈希法:当发生冲突时,使用另一个哈希函数再次计算哈希值,直到找到空闲位置。
建立公共溢出区:将发生冲突的元素存储在另外一个区域。
23.Redis中是字典是如何实现的?如何解决哈希冲突的?
在 Redis 中,字典(Dictionary)通常是通过哈希表实现的。
Redis 解决哈希冲突主要使用的是链地址法。当出现哈希冲突时,具有相同哈希值的键值对会被组织成一个链表。
在 Redis 中,为了优化性能,还会采取一些措施,比如当链表过长时,会将哈希表进行扩容,重新计算哈希值并重新分布元素,以减少冲突和提高查找效率。
24.Redis中字典的扩容机制是怎样的?
在 Redis 中,字典的扩容机制主要遵循以下规则:
当负载因子(已使用的节点数 / 哈希表大小)超过一定阈值时,就会触发扩容操作。
具体来说:
负载因子的计算:负载因子 = 哈希表已保存节点数量 / 哈希表大小。
触发扩容的条件:默认情况下,当负载因子大于 1 时,会进行扩容。
扩容策略:
新的哈希表大小通常是原哈希表大小的两倍。
然后逐步将原哈希表中的键值对重新计算哈希值,并迁移到新的哈希表中。
模拟扩容代码:
class RedisDictionary
{private Dictionary<int, string> dict = new Dictionary<int, string>();private int capacity = 10; // 初始容量private int size = 0; // 已存储的元素数量public void Add(int key, string value){if ((float)size / capacity > 1) // 检查负载因子{Resize(); // 进行扩容}dict.Add(key, value);size++;}private void Resize(){capacity *= 2; // 新容量为原来的两倍Dictionary<int, string> newDict = new Dictionary<int, string>(capacity);foreach (var pair in dict){newDict.Add(pair.Key, pair.Value); // 重新计算哈希并迁移元素}dict = newDict;}
}
25.一些名词解释
内存泄露(Memory Leak):指程序中动态分配的内存,在使用完毕后没有被正确释放,导致这些内存无法被再次使用,随着时间的推移,积累的不可用内存越来越多,最终可能导致系统性能下降甚至崩溃。
内存溢出(Memory Overflow):当程序向内存申请的空间超过了系统所能提供的最大内存时,就会发生内存溢出。例如,一个数组申请的空间超过了可用内存的大小。
内存雪崩:一般是指由于某些原因(比如大量缓存同时过期、缓存服务器宕机等)导致大量请求无法从缓存中获取数据,从而直接访问数据库,造成数据库压力瞬间增大,甚至可能导致数据库崩溃。
缓存命中(Cache Hit):当从缓存中成功获取到所需的数据,就称为缓存命中。缓存命中意味着可以快速获取数据,提高系统的性能。
缓存穿透:指查询一个根本不存在的数据,缓存中没有,数据库中也没有。这样每次请求都会直接打到数据库,给数据库带来压力。
缓存击穿:一个非常热点的数据,在缓存失效的瞬间,大量的并发请求直接访问数据库来获取数据。
内存抖动:频繁地进行内存的分配和回收,导致系统性能下降。
堆内存和栈内存:堆内存用于动态分配较大的对象和数据结构,由垃圾回收器管理;栈内存用于存储局部变量和方法调用信息,自动分配和释放。
直接内存:不是由 Java 虚拟机管理的内存,通过 ByteBuffer 类的 allocateDirect 方法分配,使用不当可能导致内存泄漏。
内存碎片:内存被分配和释放后,可能会产生一些不连续的、无法被有效利用的小内存区域。
弱引用和软引用:弱引用对象在垃圾回收时,只要发现就会被回收;软引用对象只有在内存不足时才会被回收。
分页内存:将内存划分为固定大小的页,便于内存管理和交换。
虚拟内存:通过将部分内存数据存储在磁盘上,扩展了程序可用的内存空间。
内存对齐:为了提高内存访问效率,数据在内存中的存储位置通常需要按照一定的字节边界对齐
26.内存碎片如何产生的?
动态内存分配和释放:当程序频繁地申请和释放不同大小的内存块时,可能会导致内存空间出现不连续的空闲区域。例如,先分配一块较大的内存,然后释放中间的一部分,就会在两端形成较小的空闲块。
内存分配策略:某些内存分配算法可能导致碎片的产生。比如首次适应算法,总是从内存的起始位置开始查找适合的空闲块,可能会使后面的较大空闲块被分割成小的、不连续的部分。
不同大小的内存请求:如果程序中既有对小内存块的请求,又有对大内存块的请求,并且它们的分配和释放顺序不规则,容易产生碎片。
27.如何解决内存碎片问题?
内存池技术:
预先分配一块较大的连续内存作为内存池。当需要内存时,从内存池中分配固定大小的块,而不是每次动态分配。减少了频繁的小内存分配和释放导致的碎片。
压缩和整理内存:
定期对内存进行扫描,将已使用的内存块移动到一起,合并空闲的碎片。
采用合适的内存分配算法:
例如,伙伴系统算法、最佳适应算法等,在一定程度上减少碎片的产生。
限制内存分配和释放的频率:
尽量复用已分配的内存,避免频繁的申请和释放。
对象复用:
对于一些经常创建和销毁的对象,使用对象池进行复用。
28.内存分配算法有哪些?怎么实现?
首次适应算法(First Fit):从内存的起始位置开始,顺序查找第一个能满足需求的空闲分区进行分配。
简单实现代码:
class FirstFitAllocator
{private int[] memoryBlocks; // 表示内存块的大小private bool[] isAllocated; // 标记是否已分配public FirstFitAllocator(int[] blockSizes){memoryBlocks = blockSizes;isAllocated = new bool[blockSizes.Length];}public int Allocate(int size){for (int i = 0; i < memoryBlocks.Length; i++){if (!isAllocated[i] && memoryBlocks[i] >= size){isAllocated[i] = true;return i;}}return -1; // 表示分配失败}
}
最佳适应算法(Best Fit):扫描整个空闲分区表,选择能满足需求且大小最小的空闲分区进行分配。
简单实现代码:
class BestFitAllocator
{private int[] memoryBlocks;private bool[] isAllocated;public BestFitAllocator(int[] blockSizes){memoryBlocks = blockSizes;isAllocated = new bool[blockSizes.Length];}public int Allocate(int size){int bestFitIndex = -1;int minDifference = int.MaxValue;for (int i = 0; i < memoryBlocks.Length; i++){if (!isAllocated[i] && memoryBlocks[i] >= size && (memoryBlocks[i] - size) < minDifference){bestFitIndex = i;minDifference = memoryBlocks[i] - size;}}if (bestFitIndex!= -1){isAllocated[bestFitIndex] = true;}return bestFitIndex;}
}
最坏适应算法(Worst Fit):选择最大的空闲分区进行分配。
简单实现代码:
class WorstFitAllocator
{private int[] memoryBlocks;private bool[] isAllocated;public WorstFitAllocator(int[] blockSizes){memoryBlocks = blockSizes;isAllocated = new bool[blockSizes.Length];}public int Allocate(int size){int worstFitIndex = -1;int maxSize = 0;for (int i = 0; i < memoryBlocks.Length; i++){if (!isAllocated[i] && memoryBlocks[i] >= size && memoryBlocks[i] > maxSize){worstFitIndex = i;maxSize = memoryBlocks[i];}}if (worstFitIndex!= -1){isAllocated[worstFitIndex] = true;}return worstFitIndex;}
}
伙伴系统算法:将内存按 2 的幂次大小进行划分和合并。
29.值类型的内存分配位置
在 C# 中,值类型(如 int、float、struct 等)的分配取决于它们的使用上下文。
如果值类型是在方法内部声明的局部变量,那么它们通常被分配在栈上。栈的分配和释放速度非常快,因为它的管理方式相对简单。
如果值类型是作为类或结构体的成员,并且该类或结构体被实例化为对象,那么值类型成员会随着对象一起分配在堆上或者作为引用类型对象的一部分。
示例代码:
class ValueTypeAllocationExample
{struct Point{public int X;public int Y;}void Method(){// 这里的 num 分配在栈上int num = 10;// 这里的 p 分配在堆上,因为包含它的对象分配在堆上Point p = new Point { X = 5, Y = 5 };}
}
30.内存,外存,栈、堆
内存(也称为主存或随机存取存储器 - RAM):
速度快,能够快速读写数据。
是计算机在运行程序时用于临时存储数据和程序指令的地方。
容量相对较小,且断电后数据丢失。
外存(如硬盘、SSD、U 盘等):
速度相对较慢,但容量通常很大。
用于长期存储数据,即使断电数据也不会丢失。
栈(Stack):
位于内存中。
由编译器自动管理,存储局部变量、函数参数和返回地址等。
遵循 “后进先出”(Last In First Out,LIFO)的原则。
分配和释放内存的操作速度很快。
代码:
void Method()
{int num = 10; // num 存储在栈上
}
堆(Heap):
也在内存中。
由程序员手动管理(通过 new 等操作分配,delete 或 Dispose 等释放),或者由垃圾回收器自动管理(如在 C# 等语言中)。
用于存储对象和较大的数据结构。
代码:
class MyClass
{// 类的实例存储在堆上MyClass obj = new MyClass();
}
内存用于快速的临时数据存储,外存用于长期的数据保存,栈适合存储短期的、自动管理的小数据,堆适合存储由程序员或语言的垃圾回收机制管理的较大、更复杂的数据结构和对象。
31.CPU Cache
CPU Cache(CPU 高速缓存)是位于 CPU 与主内存之间的一种小而快速的存储器。
它的主要作用是减少 CPU 访问主内存的时间延迟,从而提高计算机系统的性能。
CPU Cache 通常分为多个级别,如 L1 Cache、L2 Cache 和 L3 Cache。L1 Cache 距离 CPU 核心最近,速度最快,但容量较小;L2 Cache 速度稍慢,容量较大;L3 Cache 则更大但速度相对较慢。
当 CPU 需要读取数据时,首先会在 Cache 中查找,如果找到(称为 “命中”),则直接从 Cache 中获取数据,速度很快;如果未找到(称为 “未命中”),则需要从主内存中读取数据,并将其存储到 Cache 中以便后续使用。
代码示例:
int[] data = new int[10000]; // 假设存储在主内存中void ProcessData()
{// 第一次访问可能未命中 Cache,从主内存读取int value = data[0]; // 后续对附近数据的访问可能命中 Cache,速度更快int nextValue = data[1];
}
32.外存通常是什么作用,存储什么的?
外存的作用主要是用于长期、大量地存储数据和程序,即使在计算机关机或断电的情况下,数据也不会丢失。
外存通常存储以下几类信息:
操作系统和系统文件:包括启动计算机所需的核心系统文件、驱动程序等。
应用程序和软件:如办公软件、游戏、图形设计工具等。
用户数据:
文档:如文字处理文档、电子表格、演示文稿等。
图片、音频和视频文件:照片、音乐、电影等多媒体内容。
数据库文件:用于存储大量结构化数据。
备份数据:为了防止数据丢失,用户和系统的重要数据的备份通常也存储在外存中。
常见的外存设备包括硬盘驱动器(HDD)、固态硬盘(SSD)、光盘、U 盘等。
未完待续。。。
相关文章:
关于C#的一些基础知识点汇总
1.C#结构体可以继承接口吗?会不会产生GC? 在 C# 中,结构体不能继承类,但可以实现接口。 代码: interface IMyInterface {void MyMethod(); }struct MyStruct : IMyInterface {public void MyMethod(){Console.Write…...
七、敏捷开发工具:持续集成与部署工具
一、敏捷开发工具——持续集成与部署工具 持续集成(CI)与持续部署(CD)是现代敏捷开发中不可或缺的关键实践。通过自动化构建、测试和部署流程,团队可以快速反馈、提高代码质量,并加速产品交付。为此,持续集成与部署工具应运而生,它们能够帮助开发团队在整个开发周期内…...
【工具类】 Hutool 中用于生成随机数的工具类
博主介绍:✌全网粉丝22W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
vue3和vue2的组件开发有什么区别
Vue3和Vue2在组件开发上存在不少差异,下面从多个方面详细介绍: 响应式原理 Vue2:用Object.defineProperty()方法来实现响应式。打个比方,它就像给对象的每个属性都安排了一个“小管家”,属性被访问或修改时࿰…...
防御保护选路练习
拓扑 配置 IP的基本配置 r2 [R2]int g0/0/0 [R2-GigabitEthernet0/0/0]ip add 12.0.0.2 255.255.255.0 [R2]int g0/0/2 [R2-GigabitEthernet0/0/2]ip add 210.1.1.254 255.255.255.0 [R2-GigabitEthernet0/0/2]int g0/0/1 [R2-GigabitEthernet0/0/1]ip add 200.1.1.254 255.…...
SQL Server 运算符优先级
在 SQL Server 中,运算符的优先级决定了在没有使用括号明确指定计算顺序时,运算符的执行顺序。 运算符优先级列表 括号 () 一元运算符 (正号)-(负号)~(按位取反) 乘法、除法和取模…...
【RK3588嵌入式图形编程】-SDL2-构建模块化UI
构建模块化UI 文章目录 构建模块化UI1、概述2、创建UI管理器3、嵌套组件4、继承5、多态子组件6、总结在本文中,将介绍如何使用C++和SDL创建一个灵活且可扩展的UI系统,重点关注组件层次结构和多态性。 1、概述 在前面的文章中,我们介绍了应用程序循环和事件循环,这为我们的…...
用STC-ISP写延时函数
若想写出自己可以定义时长的延时函数,需要重新生成一个1ms的延时函数并稍加修改。 STC-ISP生成的1ms的延时函数代码如下: void Delay1ms(void) //12.000MHz {unsigned char data i, j;i 2;j 239;do{while (--j);} while (--i); }将上述代码改为可自定…...
DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
对于个人开发者或尝鲜者而言,本地想要部署 DeepSeek 有很多种方案,但是一旦涉及到企业级部署,则步骤将会繁琐很多。 比如我们的第一步就需要先根据实际业务场景评估出我们到底需要部署什么规格的模型,以及我们所要部署的模型&…...
使用 Docker 部署 Apache Spark 集群教程
简介 Apache Spark 是一个强大的统一分析引擎,用于大规模数据处理。本文将详细介绍如何使用 Docker 和 Docker Compose 快速部署一个包含一个 Master 节点和两个 Worker 节点的 Spark 集群。这种方法不仅简化了集群的搭建过程,还提供了资源隔离、易于扩…...
基于暗通道先验的图像去雾算法解析与实现
一、算法背景 何凯明团队于2009年提出的暗通道先验去雾算法《single image haze removal using dark channel prior》,通过统计发现:在无雾图像的局部区域中,至少存在一个颜色通道的像素值趋近于零。这一发现为图像去雾提供了重要的理论依据…...
深入内存调试:Valgrind工具的终极指南(转)
在软件开发的世界里,代码质量就是生命线,而内存管理又是这条生命线中最脆弱的一环。内存泄漏,哪怕只是微小的一处,日积月累,都可能对整个系统造成灾难性的打击,无论是大型企业级应用、实时性要求极高的嵌入…...
深入解析MediaPipe:强大的实时计算机视觉框架
深入解析MediaPipe:强大的实时计算机视觉框架 1. 引言 在计算机视觉应用的快速发展中,实时处理和低延迟成为了许多应用的关键需求。Google 开发的 MediaPipe 是一个强大的开源框架,它能够高效处理 手势识别、姿态估计、物体检测、语音处理 …...
DeepSeek 和 ChatGPT 在特定任务中的表现:逻辑推理与创意生成
🎁个人主页:我们的五年 🔍系列专栏:Linux网络编程 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 Linux网络编程笔记: https://blog.cs…...
大白话实战Sentinel
Sentinel是SpringCloudAlibaba提供的用来做服务保护的框架,而服务保护的常见手段就是限流和熔断降级。在大型分布式系统里面,由于微服务众多,所以服务之间的稳定性需要做特别关注,Sentinel的核心包就提供了从多个维度去保护服务稳定的策略,而且这些保护策略都可以连接上Se…...
【AI面板识别】
题目描述 AI识别到面板上有N(1 ≤ N ≤ 100)个指示灯,灯大小一样,任意两个之间无重叠。 由于AI识别误差,每次别到的指示灯位置可能有差异,以4个坐标值描述AI识别的指示灯的大小和位置(左上角x1,y1&#x…...
Docker安装Kafka(不依赖ZooKeeper)
创建docker-compose.yaml version: "3.9" #版本号 services:kafka:image: apache/kafka:3.9.0container_name: kafkahostname: kafkaports:- 9092:9092 # 容器内部之间使用的监听端口- 9094:9094 # 容器外部访问监听端口environment:KAFKA_NODE_ID: 1KAFKA_PROCES…...
大道至简 少字全意 易经的方式看 jvm基础 、 内存模型 、 gc、 内存异常、内存调优实战案例 、类加载机制、双亲委派模型 适用于 懂而久未用回忆 ,不懂而需明正理而用
目录 介绍 内存模型 一、线程私有区域 二、线程共享区域 1.堆Heap 2. 方法区Method Area 3.运行时常量池 Runtime constant Pool 三、直接内存(Direct Memory) 四、内存异常与调优 五、总结对比 类加载机制 一、类加载的三大阶段 二、双亲委派模型 三、类加载的特…...
【Java学习】继承
一、继承 子类继承父类,子类这个类变量的引用在原有的指向子类自己类变量空间的原有访问权限上,增加上了父类类变量空间的访问权限,此时子类类变量指向的空间变为了原来子类类变量空间加上父类类变量空间,此时子类类变量空间就变成…...
Ubuntu24安装MongoDB(解压版)
目录 0.需求说明1.环境检查2.下载软件2.1.下载MongoDB服务端2.2.下载MongoDB连接工具(可略过)2.3.检查上传或下载的安装包 3.安装MongoDB3.1.编辑系统服务3.2.启动服务3.3.客户端连接验证3.3.1.创建管理员用户 4.远程访问4.1.开启远程访问4.2.开放防火墙 0.需求说明 问&#x…...
计算机毕业设计Python考研院校推荐系统 考研分数线预测 考研推荐系统 考研可视化(代码+LW文档+PPT+讲解视频)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
Python 爬虫框架对比与推荐
Python 爬虫框架对比与推荐 Python 爬虫框架对比与推荐1. Scrapy1.1 框架介绍1.2 优点1.3 缺点1.4 适用场景 2. PySpider2.1 框架介绍2.2 优点2.3 缺点2.4 适用场景 3. Selenium3.1 框架介绍3.2 优点3.3 缺点3.4 适用场景 4. BeautifulSoup Requests(自定义方案&am…...
本地DeepSeek模型GGUF文件转换为PyTorch格式
接前文,我们在本地Windows系统上,基于GGUF文件部署了DeepSeek模型(DeepSeek-R1-Distill-Qwen-1.5B.gguf版本),但是GGUF是已经量化的版本,我们除了对其进行微调之外,无法对其训练,那么还有没有其他办法对本地的GGUF部署的DeepSeek模型进行训练呢?今天我们就反其道而行之…...
自动化测试框架搭建-单次接口执行-三部曲
目的 判断接口返回值和提前设置的预期是否一致,从而判断本次测试是否通过 代码步骤设计 第一步:前端调用后端已经写好的POST接口,并传递参数 第二步:后端接收到参数,组装并请求指定接口,保存返回 第三…...
SAP F1搜索帮助 添加自定义功能按钮
最近deepseek 比较火,好多伙伴把deep seek 调用集成到SAP 系统,集成需要方便的去查询问题,方便一点就是添加在F1搜索帮助的地方,看到有朋友问看自定义按钮怎么添加在F1的工具栏,跟踪了下代码,尝试了下&…...
Webpack,Vite打包的理解
Webpack 和 Vite 都是现代前端开发中常用的构建工具,用于打包和优化项目代码。尽管它们的目标相似,但在设计理念、工作方式和适用场景上存在显著差异。 Webpack Webpack 是一个模块打包工具,主要用于将多个模块(如 JavaScript、…...
ollama部署大模型,本地调用
Ollama是一个强大的大型语言模型平台,它允许用户轻松地下载、安装和运行各种大型语言模型。在本文中,我将指导你如何在你的本地机器上部署Ollama,并展示如何使用Python进行简单的API调用以访问这些模型。 最近很多人在学习大模型的时候&…...
【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析⑩】
ISO 14229-1:2023 UDS诊断【ECU复位0x11服务】_TestCase10 作者:车端域控测试工程师 更新日期:2025年02月18日 关键词:UDS诊断协议、ECU复位服务、0x11服务、ISO 14229-1:2023 TC11-010测试用例 用例ID测试场景验证要点参考条款预期结果TC…...
opencv实时二维码识别的一种实现与思路分享
在嵌入式平台上比如 rk3568 这种弱鸡的平台,要做到实时视频处理就非常鸡肋,不像英伟达那种 deepstrem 什么的。 开始的时候,我们使用python 下的 pyzbar + opencv opencv 读取摄像头的数据然后每帧送到 pyzbar 二维码识别函数里面进行处理,然后打印出识别的数字。结果,非常…...
【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析⑫】
ISO 14229-1:2023 UDS诊断【ECU复位0x11服务】_TestCase12 作者:车端域控测试工程师 更新日期:2025年02月18日 关键词:UDS诊断协议、ECU复位服务、0x11服务、ISO 14229-1:2023 TC11-012测试用例 用例ID测试场景验证要点参考条款预期结果TC…...
Jenkins同一个项目不同分支指定不同JAVA环境
背景 一些系统应用,会为了适配不同的平台,导致不同的分支下用的是不同的gradle,导致需要不同的JAVA环境来编译,比如a分支需要使用JAVA11, b分支使用JAVA17。 但是jenkins上,一般都是Global Tool Configuration 全局所有环境公用一个JAVA_HOME。 尝试过用 Build 的Execut…...
小爱音箱连接电脑外放之后,浏览器网页视频暂停播放后,音箱整体没声音问题解决
背景 22年买的小爱音箱增强版play,小爱音箱连接电脑外放之后,浏览器网页视频暂停播放后,音箱整体没声音(一边打着游戏,一边听歌,一边放视频,视频一暂停,什么声音都没了,…...
AIGC(生成式AI)试用 21 -- Python调用deepseek API
1. 安装openai pip3 install openai########################## Collecting openaiUsing cached openai-1.61.1-py3-none-any.whl.metadata (27 kB) Collecting anyio<5,>3.5.0 (from openai)Using cached anyio-4.8.0-py3-none-any.whl.metadata (4.6 kB) Collecting d…...
使用linux脚本部署discuz博客(详细注释版)
使用脚本部署一个discuzz项目 1.显示当前环境状态 防火墙状态 selinux状态 httpd状态 由上可知,虚拟机已处于最初始状态 2.脚本编写 #!/bin/bash #这是一个通过脚本来部署discuzz博客 firewalld关闭 systemctl stop firewalld if [ $? -eq 0 ];then echo "…...
Kafka的生产者和消费者模型
Kafka的生产者和消费者模型是一种消息传递模式,以下是该模型的详细描述: 一、生产者(Producer) 定义:生产者是消息的生产者,它将消息发布到Kafka的主题(Topic)中。 功能࿱…...
调用openssl实现加解密算法
由于工作中涉及到加解密,包括Hash(SHA256)算法、HMAC_SHA256 算法、ECDH算法、ECC签名算法、AES/CBC 128算法一共涉及5类算法,笔者通过查询发现openssl库以上算法都支持,索性借助openssl库实现上述5类算法。笔者用的op…...
【Python项目】信息安全领域中语义搜索引擎系统
【Python项目】信息安全领域中语义搜索引擎系统 技术简介:采用Python技术、MYSQL数据库等实现。 系统简介:系统主要是围绕着语义搜索展开的,要将输入的文字在爬取数据时能够通过深层次的内涵理解,来更好的查找到与之相关的精准信息…...
快速排序_912. 排序数组(10中排序算法)
快速排序_912. 排序数组(10中排序算法) 1 快速排序(重点)报错代码超时代码修改官方题解快速排序 1:基本快速排序快速排序 2:双指针(指针对撞)快速排序快速排序 3:三指针快…...
BS5852英国家具防火安全条款主要包括哪几个方面呢?
什么是BS5852检测? BS5852是英国针对家用家具的强制性安全要求,主要测试家具在受到燃烧香烟和火柴等火源时的可燃性。这个标准通常分为四个部分进行测试,但实际应用中主要测试第一部分和第二部分,包括烟头测试和利用乙炔火焰模拟…...
高考或者单招考试需要考物理这科目
问题:帮忙搜索一下以上学校哪些高考或者单招考试需要考物理这科目的 回答: 根据目前获取的资料,明确提及高考或单招考试需考物理的学校为湖南工业职业技术学院,在部分专业单招时要求选考物理;其他学校暂未发现明确提…...
基于vue3实现的课堂点名程序
设计思路 采用vue3实现的课堂点名程序,模拟课堂座位布局,点击开始点名按钮后,一朵鲜花在座位间传递,直到点击结束点名按钮,鲜花停留的座位被点名。 课堂点名 座位组件 seat.vue <script setup>//组合式APIimpo…...
压力传感器
压力传感器是一种用于测量气体或液体压力的设备,广泛应用于工业控制、汽车电子、医疗设备、航空航天等领域。以下是关于压力传感器的详细介绍: 一、压力传感器的分类 1. 按测量原理分类 - 压阻式压力传感器: - 原理:利用压…...
Django REST Framework (DRF) 中用于构建 API 视图类解析
Django REST Framework (DRF) 提供了丰富的视图类,用于构建 API 视图。这些视图类可以分为以下几类: 1. 基础视图类 这些是 DRF 中最基础的视图类,通常用于实现自定义逻辑。 常用类 APIView: 最基本的视图类,所有其…...
DeepSeek介绍[Cache-Through、Cache-Around、Cache-Behind、Cache-Asid]
Cache-Through、Cache-Around、Cache-Behind和Cache-Aside是几种常见的缓存策略,每种策略有其独特的工作机制和应用场景。以下是对这些缓存模式的详细介绍: 1. Cache-Through 工作原理: 读操作:应用程序首先向缓存层请求数据。…...
React 前端框架介绍
什么是 React? React 是一个由 Facebook 开发并维护的开源 JavaScript 库,用于构建用户界面。它主要用于创建交互式用户界Face(UI),尤其是当数据变化时需要更新部分视图时非常有效。React 的核心思想是组件化和声明性…...
自制简单的图片查看器(python)
图片格式:支持常见的图片格式(JPG、PNG、BMP、GIF)。 import os import tkinter as tk from tkinter import filedialog, messagebox from PIL import Image, ImageTkclass ImageViewer:def __init__(self, root):self.root rootself.root.…...
基于Electron+Vue3创建桌面应用
Electron 是一个开源框架,基于 Chromium 和 Node.js,用于开发跨平台桌面应用程序。它允许开发者使用 HTML、CSS 和 JavaScript 等 Web 技术构建原生桌面应用,支持 Windows、macOS 和 Linux。Electron 以其开发便捷性、强大的功能和丰富的生态系统而广泛应用于工具类应用、媒…...
Redis实战-扩展Redis
扩展Redis 1、扩展读性能2、扩展写性能和内存容量3、扩展复杂的查询3.1 扩展联合查询3.2 扩展分片排序 如有侵权,请联系~ 如有错误,也欢迎批评指正~ 本篇文章大部分是来自学习《Redis实战》的笔记 1、扩展读性能 单台Redis服务器…...
Vue 前端开发中的路由知识:从入门到精通
文章目录 引言1. Vue Router 简介1.1 安装 Vue Router1.2 配置 Vue Router1.3 在 Vue 实例中使用 Vue Router 2. 路由的基本用法2.1 路由映射2.2 路由视图2.3 路由链接 3. 动态路由3.1 动态路径参数3.2 访问动态参数3.3 响应路由参数的变化 4. 嵌套路由4.1 定义嵌套路由4.2 渲染…...
为AI聊天工具添加一个知识系统 之109 详细设计之50 三性三量三境
本文要点 纵观整个讨论过程 最初我提“相得益彰的三性(三性) 相提并论的三者(三量) 相映成趣的三化(三境)” “ 确定 今天的讨论题-- “我”的知识树:相得益彰的三性(即 三性&…...