Advertisement

C#软件工程师、 .NET、 上位机软件开发工程师秋招面经八股汇总 及心得

阅读量:

博主在七月至十月底期间共投出了142家企业。成功完成流程的企业有9家。获得了7份Offer。目前已与三方达成合作签约。满意签约的企业共有多少呢?我们来总结一下过去几个月中遇到的一些常见问题,并希望能够为各位读者提供参考和帮助。

我的八股多源自个人简历中的相关项目经验。例如我在温室监控系统中应用了TCP协议,在此过程中遇到了诸多挑战与解决方案。因此面试官往往会围绕这一技术进行提问。为了使面试更加高效,在项目经验中应突出掌握的技术点,并为项目的实施准备相应的优化策略如网络组网策略、数据库优化方法(硬件性能提升与软件逻辑改进)等。当面试官询问技术选择的原因时就可以结合多个方案的优势进行阐述说明如选择某项技术可能是因为其成本低或易于上手等原则。这样的场景屡见不鲜

针对于整个系统的优化考虑:

1.首先是考虑系统的并发压力:主要是硬件终端与上位机之间的传输

每个温室配备有21个采集配置(其中包括8个温度监测配置(分别测量环境空气湿度土壤)8个湿度配置(与温度监测同步实施)以及5个光强配置(包括四个角落位置及中心位置))和9个控制配置

在客户端和服务端之间的连接中考虑了管理员数量及指令下发需求后决定采用TCP协议(对比分析:为何选择基于序列号的可靠数据传输机制?已掌握其工作原理),通过设置一个监听线程来接收客户端的连接请求实现了单个服务器向多个客户端提供成对多式的通信接口(确保每次下指令操作都互不干扰以避免资源竞争)

提升对读写数据库的操作效率(这一问题实际上并不过分突出,在实际情况中也是情有可原的),在企业数据管理领域中占据重要地位(处理并发能力与软件性能调优):

在遭遇高并发压力时

对此我从架构和代码两个方面提出优化方案:

架构层面:

一、基于业务系统的多个数据库机器优化方案划分工作展开。首先按照节点的功能特点对数据库进行分类处理,并采取采集信息与控制信息分别存储的方式,在一定程度上缓解了对单个数据库机器的压力。

二、读写分离策略下进行架构设计,在为每个数据库配置一个From存储节点的基础上实现数据同步复制机制,在确保主存储节点与配置好的From存储节点间的数据一致性的同时,则可以通过将系统的增量更新直接发送至主存储节点并在配置好的From存储节点上执行查询操作来实现对负载压力的有效分担这样一来就能有效缓解原来由于负载过重导致的性能瓶颈问题

三、数据分离存储策略。该策略通过数据分离存储的方式实现高并发处理能力的提升。具体来说,在这种策略下,我们首先将原始数据库拆分为多个独立的数据库,并将每个数据库分配相应表的数据片段;然后通过多主数据库集群来承受高并发读取负载;最后这种方法有助于进一步缓解系统压力

代码方面:

在insert语句中处理多个值时,在单个行内将多个values一次性插入会带来更高的效率(速度极快),但需要注意其潜在的竞态条件问题(由于基于特定的数据结构特性)。此外,在这种情况下仅需一个接收线程即可完成所有操作。

二、停止自动生成提交指令,并创建一个任务列表。当该列表中的任务数量达到某个特定数值(例如50)时,请手动触发提交流程。

然而,在实际操作中,为了提高效率只需整合一个温室的数据(包括温室id以及其它相关信息)形成一条记录进行批量写入。

在最大承载量方面缺乏精确的数据支撑,在过去的某个月里,在十个温室中每隔五分钟就记录了一条数据,在此期间总共采集到了大约十万条数据记录之后就没有继续监控

计算数据库大小:

单条数据大小:110+421+8=102个字节

温室id、21个采集节点、9个控制节点、时间

TINYINT (TINYINT类型占用1个字节),用于标识温室ID及控制节点(其中开关仅用01表示),数据库中不支持布尔类型)、float型 (占4个字节)、用于存储时间信息的大atetime型 (包含21个采集节点)、每个节点配置8字节

每隔五分钟,在十个温室中各采集一次数据。持续一个月的时间段内运行后统计数据显示:预计生成约十万条数据记录。这些数据占用存储空间约10MB(查看当前数据库表容量显示为10MB)。

以下问题都是我在面试过程中遇到的八股,供大家参考:

一、区分值引用与地址引用时需注意它们的具体区别是什么(如区分值类型与引用类型)。并简称为传递值与访问地址。

基本类型的变量被系统分配到内存中的栈区进行数据存储;而引用型变量则被分配到内存中的堆区并为每个对象分配唯一地址。

2.值类型存取速度快,引用类型存取速度慢。

3.值类型的变量用于存储实际的数据实例。引用类型的变量则用于存储内存堆中数据的指针或引用信息。

4.值类型继承自System.ValueType,引用类型继承自System.Object

5.栈的内存分配是自动释放;而堆在.NET中会有GC来释放

值类型的变量直接存储实际数据;引用类型的变量则被存储了数据的地址(即对象本身的引用)。

第7条 值类型变量将数值直接存放在栈内存中;引用型变量存储的是目标内存地址,并将其放置于栈内存位置;其中的实际数据则存储于基内存空间。

需要注意的是,在内存中,堆与堆栈虽然功能不同且存储位置各异。其中,在内存中通常将动态数据如字符串存储在堆中;而固定长度的数据如整数类型(每个int占4个字节)则常存放在栈中。当将一个值型变量赋给另一个值型变量时,在栈中会保留两个完全相同的拷贝;而将一个引用型变量赋给另一个引用型变量,则会在栈中保留对同一个对象的两个引用副本(即在同一块内存区域上的两个引用)。操作时若仅处理独立的值型变量,则各自行为互不影响;但若处理同一对象的引用型变量,则任何操作都会对该对象的所有引用产生影响(因为它们共享相同的内存地址)。

二、C#中抽象类(abstract)和接口(interface)的相同点与区别

相同点:

1、都可以被继承

2、都不能被实例化

3、都可以包含方法声明

4、派生类必须实现未实现的方法

区别:

抽象基类支持定义字段、属性以及方法的实现。接口则仅限于定义属性、索引器、事件以及方法声明,并不允许包含字段。

2、抽象类是一个未实现的功能模块,在设计上需要进行详细划分;而接口则是一种明确的操作规范。微软的自定义接口通常会在功能名称后附加"able"字段,并将此视为操作规范中的一类'我能做什么'的行为规范。

3、接口可以被多重实现,抽象类只能被单一继承

抽象类主要定义在一系列紧密联系的类之间,并非所有接口都是较为松散的关系但多数实现了某一特定功能。

5、抽象类基于一系列相关对象提取而来, 体现了事物间的共同特征;接口为满足不同系统间调用需求而建立的功能标准, 揭示了一切系统的功能交互模式

6、接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法

7、接口可以用于支持回调,而继承并不具备这个特点

  1. 抽象类的具体方法默认被声明为纯虚函数;然而,在针对接口而言,在实现该接口的类中,默认情况下这些接口函数是非虚函数;当然您也可以选择将这些接口函数声明为纯虚函数。

如果一个抽象类实现了接口,则可以在其子类中具体实施这些接口的方法,并不需要在这些子类中去详细描述这些接口的具体功能。

三、Static关键字

static 是一种静态修饰符,在编程中被用作定义那些不依赖于具体实例属性的行为。当使用 static 关键字进行修饰时,默认情况下该属性或方法将被绑定到整个类而不是具体的任一实例上。被 static 修饰的对象可能同时属于多个实例(特别是当这些实例共享同一份代码资源时)。尽管 static 定义了一个独立于任何实例的行为模式(即它不会受到当前特定对象生命周期的影响),但其实在这种情况下它仍然会被每个实例所继承并引用到相关的操作中去。这些 static 属性有时也被称为静态属性。

特点

1. 被 static 修饰的属于类,不属于对象

2. 优先于对象存在

3. 静态成员可以作为共享的数据源 (来自同一类的对象都可以共享这个数据)

四、什么是面向对象以及多态的实现,虚方法实现多态?

基于核心理念的方法主要是将事物转化为具有明确属性与行为的对象化处理方式。基于这种方法的思想模式,在程序设计中更能贴近于实际生活的情境处理需求。从整体上讲,在软件开发中基于过程的方法其实质是将各个过程归约为类,并通过封装功能实现操作的便捷性即为基于对象的技术(万物皆物象)。

基于核心理念的方法主要是将事物转化为具有明确属性与行为的对象化处理方式。
基于这种方法的思想模式,
在程序设计中更能贴近于实际生活的情境处理需求。
从整体上讲,
在软件开发中基于过程的方法其实质是将各个过程归约为类,
并通过封装功能实现操作的便捷性即为基于对象的技术(万物皆物象)。

三大特点:封装,继承,多态

多态实现:

举个例子:

Animal eat...

Dog eat...

WolfDog eat...

如前述示例中所示,在示例中 class Dog 被定义为其 parent 类 Animal 的子类型,并对其 eat() 方法进行了重构。随后 class WolfDog 作为 Dog 的子类型也被定义出来,并对其 eat() 方法再次进行了重构以确保良好的多态性表现。值得注意的是不论其层次结构多么复杂 子类型都能够对其 parent 类型已有的重构方法继续执行进一步的重构操作 即若 parent 类型中的 eat() 方法被标记为 override修饰 若子类型对该 eat() 方法进行继承 则同样可以将其标记为 override修饰 这种多态性正是通过多层次 inheritance 机制得以实现的 若要阻止这种重构行为 只需在其定义时使用 sealed 关键词即可(seal:密封 意味着该类型或方法将无法被进一步扩展或修改)

理解多态的两大原则:里氏替换原则、开放封闭原则

里氏替换原则(Liskov Substitution Principle):派生类(子类)对象能够替代其基类(超类)对象在特定情境下的使用。通俗而言,“子类即为父类”,即"子型继承父型"。具体来说,“父型是子型的基础”,即"男人是人";而"子型不是父型的全部"则是"人不一定是男人"!因此,在需要使用父型对象时应提供子型对象,则在需要使用子型对象时提供父型对象则是不可行的!

遵循开放与关闭的原则(Open Closed Principle),在软件设计中应当注意两点:其一是在实现过程中应当避免对现有封装方式进行修改;其二则是要尽量减小组件之间的依赖关系以降低系统的耦合度。具体而言这一原则主要体现在以下两个方面:一是对系统进行扩展时应当能够灵活地对其进行适应;二是系统一旦设计完成则不应对其结构进行任何改动以保证系统的稳定性和独立性

虚方法实现多态:(virtual)

该虚拟方法能够支持多态性;即使子类未对...进行重写...时,则可通过传统的继承机制直接调用父类的虚方法。

五、C#中 private、 protected、 public、 internal 修饰符的访问权限

private字段、方法或属性仅限于类内部可见。
protected字段、方法或属性可以在当前类及其继承类中被访问。
public字段、方法或属性对所有外部实体都是公开透明的。
internal字段、方法或属性仅限于同一个命名空间内的其他组件可见。

六、用过啥集合吗?说说看

答:ArrayList用过,因为它元素类型没有限制

array和arrayList的区别?

当Array被声明之后, 其长度固定不变, 可以通过数组下标对指定位置的元素进行变更. ArrayList基于数组实现, 但其长度是灵活变化的, 可以通过添加和删除来改变元素数量.

七、委托和事务的区别

概念

在变量中传递该方法,并采用函数形式进行操作;它是一个类对象,并属于引用类型的对象。

2、事件:功能被限制的一个委托变量。它的类型是委托类型。

3、委托的三种形式

3.1、delegate: 四步(声明,实例化,注册方法,调用)

3.2、Action:添加的方法不能有返回值

3.3、Func: 添加的方法要有返回值

3.4、lamda表达式:方法只使用一次,没有多次使用的话使用

事件的声明只需在委托前添加一个event关键字;即使你尝试定义一个public类成员(尽管你可能会尝试定义一个public字段或方法),但一旦使用了event关键字后编译器会将其声明指定为private类型,并自动添加一组addremove的方法(其中add对应+=操作符、``remove```对应-=操作符)。这样一来,则意味着事件绑定只能通过+=或-=来进行增删操作;而普通的字段或方法则可以通过=赋值,并且也可以使用+=、-=来进行绑定(据我所知(或说法),这并不适用)。

2.委托可以在外部被其他对象调用同时具有可返回值的能力(具体而言是返回最后一个注册方法所生成的结果)。这种可选性使得我们在需要时能够灵活地进行操作。而事件仅限于在声明事件的类内部进行操作无法在外部进行交互。我们可以通过这一特性来实现观察者模式。大致如此

八节 进程与线 thread 的区别在于其功能定位的不同:line thread 是调度的基本单元,在内存层面实现资源分配与请求处理;process 则代表系统资源拥有者,在物理层完成资源获取与设备连接等任务。在 line thread 环境下如何确保多 line thread 安全?在线 thread 中 sleep() 和 wait() 方法有何不同?

线程与进程的比较如下:

1.进程是资源(包括内存、打开的文件等)分配的单位,线程是CPU调度的单位;

进程具备一个完整的资源平台;线程仅独立拥有必要的资源,例如寄存器与栈。

3.除了就绪、阻塞、执行之外,线程还具有状态间的切换关系

4.线程能减少并发执行的时间和空间开销;

使用多线程的原因主要有以下几点(优点):

增加CPU内核的数量 现代计算机 processor 的性能提升方式 已经从追求更高的 clock rate 转向关注增加计算资源的数量 从而导致 processor 的核心数量将不断增加 因此 计算资源的数量将不断增加 利用这些计算资源能够显著提升程序运行效率 而通过采用多线程技术 可以使计算逻辑得以分散至多个 processing 单元 这种安排不仅能够大幅缩短处理所需的时间 而且随着更多 processing 单元 的加入 将进一步提升系统的整体效能

2. 更短的响应时间 我们经常为处理复杂业务而编写的代码较为复杂,若采用多线程技术,则可将数据不具强一致性操作分配给其他线程执行(也可通过消息队列实现)。例如上传图片、发送邮件或生成订单等操作,由不同的线程独立完成,从而显著提升了用户体验。

确保多线程环境的安全性:可能存在的安全隐患在于当多个程序单元(如函数或过程)仅进行读取而不进行修改时,在某些特殊情况下可能导致数据竞争性破坏;而当多个程序单元同时试图对该数据结构进行读取或修改时,则必须采取相应的同步机制以避免潜在的冲突问题。解决方法是采用加锁机制将多个程序单元的访问进行严格控制。

线程的 sleep() 方法和 wait() 方法有以下几点区别:

(1)sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法。

该方法不会释放锁。而wait方法将释放,并将在等待队列中被加入。

(3) sleep() 函数不受 synchronized construct 的影响, 而 wait() 函数 必须依靠 synchronized 机制运作.

在调用 sleep() 方法后,线程无需再次被唤醒。当进入休眠状态后立即开始阻塞,并且该线程的状态始终保持监控中:一旦预设的等待时间结束,则会自动重启。而 wait() 方法则需定期被重新唤醒以避免断开。

The wait method, along with notify and notifyAll, must be used within synchronized control methods or blocks. In contrast, the sleep method can be employed anywhere.

为实现睡眠状态必须捕获指定的不可 throwable RuntimeExceptions,在Java N+1版本中也是如此;然而,在某些早期版本中曾存在误解认为leep方法不需要捕捉这些异常(但之前在网络上有很多观点声称leep方法不需要抛出异常的观点是错误的)。实际上,在N+1版本中leep、leep同步块以及与之相关的同步操作符仍需捕捉指定的不可 throwable RuntimeExceptions;而 notify与 notifyAll 方法确实无需抛出此类异常

(7). sleep被视为Thread类的一个静态方法。其功能则是让相关线程在指定时间暂停执行,并在该时间点恢复继续工作。等待操作(wait)则是一个Object的方法。当调用wait方法时,则会暂时挂起当前线程。

九、Runnable和Callable的区别

相同点

1、两者都是接口;(废话)

2、两者都可用来编写多线程程序;

3、两者都需要调用Thread.start()启动线程;

不同点

两者的显著区别在于:能够通过Callable接口的任务线程实现执行结果的返回;而Runnable接口的任务线程无法实现这一功能。

Callable接口的call()方法能够抛出异常;而Runnable接口的run()方法仅能在内部被处理,并无法向上层抛出

十、设计模式了解吗?就你常用的举两个例子。(单例和工厂最好举例子)

单例模式规定每个类只能有一个实例存在,并且通过将构造函数声明为只能在类内部使用以实现这一点。这种设计模式的主要优点包括:当需要频繁地创建和销毁对象但无法优化性能时能够有效解决问题;当对象产生需要较多资源如读取配置文件或其他依赖对象时可以通过在启用时一次性生成对象并在内存中永久保留的方式来提高效率;通过确保只有一个实例能够有效避免资源被多重占用从而降低潜在的风险;此外还可以通过设置全局访问点来统一管理和共享资源的访问权限以提升系统的可维护性与效率等优势。然而该模式也存在一些明显的缺点:首先由于其特性限制使得扩展系统变得困难;其次对于并行开发环境中的测试工作并不友好因为在某些情况下可能导致测试无法顺利进行;另外该模式还可能违背单一职责原则导致一个类的功能过于分散难以单独处理特定问题等挑战。

该网站实时在线人数统计采用全局计数器的方式;任何在同一时间访问该网站的用户都会得到相同的在线人数统计结果;为了达到这一目标;该计数器必须是全局唯一的;这正是单例模式的理想应用场景;当然这里不包括分布式场景;因为计数是存在内存中的;并且还要保证线程安全;

在开发过程中经常会遇到一些与环境相关的配置文件需求,例如用于短信通知的配置信息或与邮件相关的设置信息等.例如,在项目中我们通常会使用properties类型的配置文件来完成这些需求.以读取一个properties文件来进行配置为例,如果项目中采用Spring框架,可以通过@PropertySource注解来实现其默认的单例模式功能.如果不选择使用单例模式的话,每次都需要创建新的对象,并导致读取配置文件的操作被重复执行,这将严重影响系统的性能表现.而如果采用单例模式设计,则可以在初始化时一次性读取所有必要的配置信息,从而避免重复操作带来的性能消耗.

定义一个用于生成产品对象的公共接口,在子类中完成相关功能(避免直接暴露具体实现细节)。该接口返回的对象共享相同的基类或接口(确保继承关系明确)。相比简单的工厂模式而言,则更加灵活多样:不再提供一个统一的工厂类来创建所有的对象(而是根据不同的需求分别设计不同的工厂方案)。每个类型都有专门负责其生成的工厂方案(每个对象都有一个与之对应的工厂)。相比传统的简单工厂模式,则更加注重责任分离:提供一种组织相关类型及其生成方式的方法(抽象 factory 模式)。这种设计使得不同类型的实例化工作能够独立管理(无须指定它们具体的类)。

十一、几种常见排序(问思想,部分公司要手写)

1.冒泡排序(二者比较,大的下沉,小的上浮)

主要思路:

1.比较相邻的元素。如果第一个比第二个大,就交换它们两个。

依次处理从首到尾的所有相邻元素,在此处分开展示相同的操作步骤

3.针对多有的元素重复以上的步骤,除了最后一个。

时间复杂度O(n^2), 稳定

2.选择排序法(先找出数组中的最小值,并将其与第一个位置上的元素进行交换;接着,在剩下的元素中继续寻找最小值,并将其与下一个位置上的元素进行交换;依此类推)

主要思路:

1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

接下来从剩余未排序元素中继续定位最小(大)元素,并将其归位放置到未排序区间的开头。

3.重复第二步,直到所有元素均排序完毕。

时间复杂度O(n^2), 不稳定

3.插入排序(从后面未排序的数插入到前面已经排好的数组当中去)

主要思路:

插入排序是一种基础而常用的排序算法;为了实现它;我们需要将给定数组划分为两个互不重叠的部分:有序序列而非无序序列;然后依次与有序序列中的数值进行比较;找到合适的位置后将其插入进去以完成排列操作

1.从第一个元素开始,该元素可以认为已经被排序

2.取下一个元素temp,从已排序的元素序列从后往前扫描

3.如果该元素大于temp,则将该元素移到下一位

4.重复步骤3,直到找到已排序元素中小于等于temp的元素

5.将temp置于该元素之后;若排序后的所有元素均大于temp,则将temp置于索引0处。

6.重复步骤2~5

时间复杂度O(n^2), 稳定

4.希尔排序

属于插值得到的一种高级排序算法它采用一个不断递减的间隔序列将无序数据不断分割并重新应用插入排序的方法也就是我们常说的'缩小间隔排序'或'递减间隔排序'其时间复杂度在O(n1.3至n1.5)之间表现优于传统直接插入法(O(n²))需要注意的一点是最终间隔必须设置为1值得注意的是尽管元素跳跃式移动但希尔算法不具备稳定性

基本思想:

首先选择一个增量值h,并以该增量值h作为数据分类标准来对原始数据进行分组;
对各组已分类的数据依次进行有序排列以完成插入排序;
接下来降低增量值并将其最低设为1后再次重复上述插入排序的操作。

5.归并排序

基本思想:

归并排序(MERGE-SORT)是一种经典的排序方法,它主要依据归并操作实现了一种高效的排序过程

分治法(Divide-and-Conquer)是一种经典的算法设计范例。通过合并已排序的子序列来构建一个整体有序序列。

完全递增的系列;亦即先依次实现每个子系列的严格递增性。进而实现各子系列间的顺序关系。若将两对已排序的数据集合整合为一个整体数据集

一个有序表,称为二路归并(分两组,每组再细分两组……)。

时间复杂度O(nlogn),稳定

6.快速排序(选择数组中最左侧的一个元素作为基准值;然后将所有小于基准值的数放置于基准值左侧的位置;将所有大于基准值的数放置于基准值右侧的位置;接着依次从两端各取一个元素进行相同的操作;直到每边只剩下一个元素为止)

基本思想:

快速排序算法的核心思想基于分治法。首先从待排序的数列中选择一个元素作为轴值(基准数)pivot element;然后利用该轴值将整个数列划分为左右两部分,并分别对这两部分进行同样的操作;最终合并各部分得到有序的结果。

根据基准数将数列进行分区,小于基准数的放左边,大于基准数的放右边;

重复分区操作,知道各区间只有一个数为止。

平均时间复杂度O(nlogn),最差为O(n^2),不稳定

十二、图的遍历(浅显的答的容易,有深度的答不上来,我太菜了)

1.DFS深度优先:

类似于二叉树的前序遍历算法,在这种情况下从根节点出发一路向左,并依次访问其所有子节点;当发现还有未被访问到的邻接节点时,则需要逐步回退到未被访问到的父节点,并继续查找是否有遗漏的子节点;不断重复这一过程直至所有节点都被访问完毕;这样通过迭代的方式就可以完成深度优先搜索(DFS)而不使用递归方法

2. BFS广度优先:(遍历节点和其连通节点,依次向下遍历,队列)

将起始节点加入到队列中;随后,在该节点被取出时,请立即将其所有邻接点也加入到队列中;持续地重复这一过程直至队列为空时;在此期间,请将实际问题作为一部分纳入出队进队这一活动之中以实现算法目的

十三、String、StringBuffer、StringBuilder之间区别

1.三者在执行速度方面的比较: StringBuilder >StringBuffer > String

当一个String变量被赋值或初始化后就无法再对其进行修改(因为String类是final类型的),除非重新赋以新的值这时系统将为新内容分配一个新的内存地址以存储该字符串数据。相比之下StringBuilder类则通过append和insert等方法对现有对象所占用的内存空间内执行字符操作从而避免了每次重新创建对象所带来的资源浪费。因此建议在需要频繁对字符串进行修改的情况下(如频繁追加字符),应先创建一个StringBuilder对象完成所有修改操作后再将其转换为最终的String类型这样处理效率会显著提高。

StringBuffer其实是StringBuilder的包装类(wrapper),类似于基本数据类型中的int对应Integer的关系。
StringBuffer具有缓存机制,在声明一个字符串仅用于接收传入参数并进行业务处理时,若使用多个StringBuffer对象,则会浪费内存资源。
建议直接使用字符串而非StringBuffer来提高效率并减少内存消耗。

2. 在字符串拼接时,String 对象的速度并不会比 StringBuffer对象慢。

JVM对String对象进行字符串拼接的行为实质上等同于将其转换为StringBuilder对象后再进行操作的过程。因此,在这种情况下...速度不会低于对应的StringBuilder操作速度;然而,在以下涉及字符串对象生成的具体场景中,则可观察到一个显著的优势:使用String类完成相关操作时其效率明显高于采用StringBuilder的方式。

String S1 = “This is only a” + “ simple” + “ test”;

StringBuffer Sb = new instance of StringBuilder;

你可能会感到震惊地发现,在生成 String S1 对象的过程中速度异常迅速;然而与此同时, 使用 StringBuffer 并不占优, 它的速度完全不占优, 根本就是没优势. 其实这背后有其玄机, 在 JVM 的视角中

String S1 = “This is only a” + “ simple” + “test”;

其实就是:

String S1 = “This is only a simple test”;

因为这种情况通常只需要花费较短的时间就能完成任务。然而,在这种情况下需要注意的一点是:如果所处理的字符串是从另一个String对象复制过来的(即不是新的独立字符串实例),那么这种方法的速度就会明显降低;比如:

String S2 = “This is only a”;

String S3 = “ simple”;

String S4 = “ test”;

String S1 = S2 +S3 + S4;

这时候 JVM 会规规矩矩的按照原来的方式去做

3. 在线程安全方面不同

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

三者使用总结:对于仅进行少量修改的数据来说,在单线程环境下处理大数据量的字符串应该采用StringBuilder;而在多线脚本环境中处理大数据量的字符串则应该采用StringBuffer

十四、TCP三次握手,TCP传输中有个粘包现象了解过吗?你怎么解决的?

三次握手

三次握手的基本要素

acks, one of the fields in the TCP header used to indicate whether the acknowledgment number is valid. When the acknowledgment number is valid, it is set to 1. (Acknowledgment number: used to notify the sender that all data received up to that point has been successfully received.)

SYN,同步序列号,如果TCP建立连接成功则置1

FIN,在发送端完成了发送任务位的操作之后,在TCP完成了数据传输的任务之后,在想要断开连接后,在该位置标记为1。

详细过程:

1.(同步通信方式)假定服务端向客户端发送信息

服务端向客户端发送一个含有SYN的数据段给客户端,并请求连接

客户端收到服务端收到请求后,用含有ACK和SYN的数据段响应服务端

当服务端接收到客户端发送的数据段时,在随后发送一个ACK报文以确认接收到的数据段后,从而完成会话建立的过程

注:握手过程通过协议内部机制完成;无需出现在上位机代码中;通过理解这一过程即可掌握。

2.传输数据

建立连接后可以通过通信信道来进行数据传输的任务。
需要注意的是,在TCP协议中数据采用字节流的形式存在。
其中的数据需将字符集转化为相应的字节数组,并且在接收端能够实现相应的解码以恢复原始信息内容。
通常情况下,在字符集与目标编码体系之间进行编码映射,在接收端则会执行相应的解码操作以恢复原始信息内容。

3.断开连接

断开连接主要地涉及到关闭读写流的操作。具体而言,则取决于服务端与客户端之间的操作优先级;此外,在某些情况下还需要特别的异常处理机制。

数据传输中的延迟或丢包现象通常发生在流量较大的网络环境中;而由于其无连接的特性,在UDP数据链路层中不存在数据分组的累积现象(参考Windows 网络编程)。

1 发送端需要等缓冲区满才发送出去,造成粘包

2 接收方不及时接收缓冲区的包,造成多个包接收

解决办法:

为了避免粘包现象的发生, 可采取以下具体措施. 首先, 对于发送方导致的粘包现象, 可通过编程设置实现其预防. 具体而言, TCP协议提供了"push"操作指令用于强制发送立即数据(TCP协议字段中有6个标志位, 如ACK, FIN, SYN均为常用标志位, push标志位较为少见). 当TCP软件接收到该操作指令后, 即立即将本段数据发送出去, 无需等待发送缓冲区满才开始传输; 其次, 对于接收方引发的粘包问题, 则可通过优化程序设计、精简接收进程的工作量以及提升接收进程的优先级等手段加以处理(Thread.CurrentThread.priority = ThreadPriority.Highest;); 最后, 由接收方主动控制的方式是将一包数据按照其结构字段进行拆分接收后再统一合并处理以避免出现粘包现象.

如前所述的这三种方法均存在各自的缺陷。第一种编程设置方案尽管能够有效抑制发送方产生的粘包现象(即防止因发送方操作导致的数据包延迟或堆积),但却牺牲了优化算法的作用(即关闭了优化算法的相关功能),从而降低了网络整体的数据传输效率(即应用系统的运行速度受到影响)。这种做法一般不被推荐采用(即不建议采用)。第二种方案仅能降低粘包发生的概率(即减少因数据传输中断导致的数据丢失风险),但完全避免粘包事件是不可能的(即无法100%防止数据丢失)。因此,在数据传输频率较高的情况下(特别是在高吞吐量的应用场景下),或由于网络突发状况可能导致某个时间段内数据包到达接收端的速度较快(即可能出现接收到大量数据的情况),此时接收端仍有可能来不及接收全部数据(从而导致部分数据丢失)。第三种方案虽然能够在一定程度上防止粘包现象的发生(确保存储在服务器端的数据完整性),但也导致应用系统的运行效率相对较低(影响系统响应速度和处理能力)。因此,在对实时性要求较高的应用场景中尤其不适合采用这种方案

十五、ADO.NET五大对象及用法

1.Connection对象

Connection对象就像在战争中负责通信的士兵一样,在战争开始前需要先建立连接以确保司令部与各个作战部队之间的通讯线路畅通无阻。随后通过通讯线路实现命令的接收和发布过程。这里的司令部相当于服务器的作用而各个作战部队则如同独立的应用程序。

2.Command对象

进行一系列基础操作指令(如:增减修改),即涉及T-SQL语句的操作过程。Command对象包含若干关键功能:例如完成增减修改操作后会返回其影响的记录数量等信息。查询功能主要包含两种类型:一种是调用ExecuteReader()方法以获取数据集;另一种则是ExecuteScale()函数用于获取首行首列数据等信息。

3.DataAdapter对象

通过DataAdapter获取本地数据库中的记录,并将其添加到本地数据集。同时利用DataAdapter将反向更新的数据传输至服务器。在DataAdapter中存在四个关键命令对象:SelectOperation(查询)、InsertOperation(插入)、UpdateOperation(更新)以及DeleteOperation(删除)。其中优先采用的是SelectOperation这一功能模块,在Fill方法的作用下,在选型操作的基础上完成本地数据库与远程服务器之间的数据同步过程。

4.DataReader对象

当只需要按顺序进行数据读取而不涉及其他操作时

5.DataSet对象

该 DataSet 对象相当于本地存储的一个数据库系统,在线获取的数据可以在本地保存下来。其主要功能不仅在于存储多个 Table 数据的同时还可以通过 Adapter 对象获取诸如主键这样的元数据信息,并记录各数据表之间的关联关系。在我们利用 DataSet 读取数据时,在线连接状态已经不再重要了,在线连接状态不再影响我们的操作流程。而在 ADO.NET 库体系中, DataSet 对象扮演着核心角色,在我们程序运行过程中起到关键作用

另外因为公司业务也会问一些专业性的小问题,就不一一例举了。

全部评论 (0)

还没有任何评论哟~