zoukankan      html  css  js  c++  java
  • .NET面试题解析(02)-拆箱与装箱

      常见面试题目:

    1.什么是拆箱和装箱?

    2.什么是箱子?

    3.箱子放在哪里?

    4.装箱和拆箱有什么性能影响?

    5.如何避免隐身装箱?

    6.箱子的基本结构?

    7.装箱的过程?

    8.拆箱的过程?

    9.下面这段代码输出什么?共发生多少次装箱?多少次拆箱?

    int i = 5;
    object obj = i;
    IFormattable ftt = i;
    Console.WriteLine(System.Object.ReferenceEquals(i, obj));
    Console.WriteLine(System.Object.ReferenceEquals(i, ftt));
    Console.WriteLine(System.Object.ReferenceEquals(ftt, obj));
    Console.WriteLine(System.Object.ReferenceEquals(i, (int)obj));
    Console.WriteLine(System.Object.ReferenceEquals(i, (int)ftt));

      深入浅出装箱与拆箱

    有拆必有装,有装必有拆。

    在上一文中我们提到,所有值类型都是继承自System.ValueType,而System.ValueType又是来自何方呢,不难发现System.ValueType继承自System.Object。因此Object是.NET中的万物之源,几乎所有类型都来自她,这是装箱与拆箱的基础。

    特别注意的是,本文与上一文有直接关联,需要先了解上一文中值类型与引用类型的原理,才可以更好理解本文的内容。

    微笑 基本概念

    拆箱与装箱就是值类型与引用类型的转换,她是值类型和引用类型之间的桥梁,他们可以相互转换的一个基本前提就是上面所说的:Object是.NET中的万物之源

    先看看一个小小的实例代码:

                int x = 1023;
                object o = x; //装箱
                int y = (int) o; //拆箱

    装箱:值类型转换为引用对象,一般是转换为System.Object类型或值类型实现的接口引用类型;

    拆箱:引用类型转换为值类型,注意,这里的引用类型只能是被装箱的引用类型对象;

    由于值类型和引用类型在内存分配的不同,从内存执行角度看,拆箱与装箱就势必存在内存的分配与数据的拷贝等操作,这也是装箱与拆箱性能影响的根源。

    大笑 装箱的过程

    int x = 1023;
    object o = x; //装箱

    装箱就是把值类型转换为引用类型,具体过程:

    • 1.在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(引用类型的标配:TypeHandle和同步索引块);
    • 2.将值类型的字段值(x=1023)拷贝新分配的内存中;
    • 3.返回新引用对象的地址(给引用变量object o)

    image

    如上图所示,装箱后内存有两个对象:一个是值类型变量x,另一个就是新引用对象o。装箱对应的IL指令为box,上面装箱的IL代码如下图:

    image

    大笑 拆箱的过程

    int x = 1023;
    object o = x; //装箱
    int y = (int) o; //拆箱

    明白了装箱,拆箱就是装箱相反的过程,简单的说是把装箱后的引用类型转换为值类型。具体过程:

    • 1.检查实例对象(object o)是否有效,如是否为null,其装箱的类型与拆箱的类型(int)是否一致,如检测不合法,抛出异常;
    • 2.指针返回,就是获取装箱对象(object o)中值类型字段值的地址;
    • 3.字段拷贝,把装箱对象(object o)中值类型字段值拷贝到栈上,意思就是创建一个新的值类型变量来存储拆箱后的值;

    image

    如上图所示,拆箱后,得到一个新的值类型变量y,拆箱对应的IL指令为unbox,拆箱的IL代码如下:

    image

    吐舌笑脸 装箱与拆箱总结及性能

    装的的什么?拆的又是什么?什么是箱子?

    通过上面深入了解了装箱与拆箱的原理,不难理解,只有值类型可以装箱,拆的就是装箱后的引用对象,箱子就是一个存放了值类型字段的引用对象实例,箱子存储在托管堆上。只有值类型才有装箱、拆箱两个状态,而引用类型一直都在箱子里

    关于性能

    之所以关注装箱与拆箱,主要原因就是他们的性能问题,而且在日常编码中,经常有装箱与拆箱的操作,而且这些装箱与拆箱的操作往往是在不经意时发生。一般来说,装箱的性能开销更大,这不难理解,因为引用对象的分配更加复杂,成本也更高,值类型分配在栈上,分配和释放的效率都很高。装箱过程是需要创建一个新的引用类型对象实例,拆箱过程需要创建一个值类型字段,开销更低。

    为了尽量避免这种性能损失,尽量使用泛型,在代码编写中也尽量避免隐式装箱。

    什么是隐式装箱?如何避免?

    就是不经意的代码导致多次重复的装箱操作,看看代码就好理解了

    int x = 100;
    ArrayList arr = new ArrayList(3);
    arr.Add(x);
    arr.Add(x);
    arr.Add(x);

    这段代码共有多少次装箱呢?看看Add方法的定义:

    image

    再看看IL代码,可以准确的得到装箱的次数:

    image

    显示装箱可以避免隐式装箱,下面修改后的代码就只有一次装箱了。

    int x = 100;
    ArrayList arr = new ArrayList(3);
    object o = x;
    arr.Add(o);
    arr.Add(o);
    arr.Add(o);

      题目答案解析:

    1.什么是拆箱和装箱?

    装箱就是值类型转换为引用类型,拆箱就是引用类型(被装箱的对象)转换为值类型。

    2.什么是箱子?

    就是引用类型对象。

    3.箱子放在哪里?

    托管堆上。

    4.装箱和拆箱有什么性能影响?

    装箱和拆箱都涉及到内存的分配和对象的创建,有较大的性能影响。

    5.如何避免隐身装箱?

    编码中,多使用泛型、显示装箱。

    6.箱子的基本结构?

    上面说了,箱子就是一个引用类型对象,因此她的结构,主要包含两部分:

    • 值类型字段值;
    • 引用类型的标准配置,引用对象的额外空间:TypeHandle和同步索引块,关于这两个概念在本系列后面的文章会深入探讨。

    7.装箱的过程?

    • 1.在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(引用类型的标配:TypeHandle和同步索引块);
    • 2.将值类型的字段值(x=1023)拷贝新分配的内存中;
    • 3.返回新引用对象的地址(给引用变量object o)

    8.拆箱的过程?

    • 1.检查实例对象(object o)是否有效,如是否为null,其装箱的类型与拆箱的类型(int)是否一致,如检测不合法,抛出异常;
    • 2.指针返回,就是获取装箱对象(object o)中值类型字段值的地址;
    • 3.字段拷贝,把装箱对象(object o)中值类型字段值拷贝到栈上,意思就是创建一个新的值类型变量来存储拆箱后的值;

    9.下面这段代码输出什么?共发生多少次装箱?多少次拆箱?

    int i = 5;
    object obj = i;
    IFormattable ftt = i;
    Console.WriteLine(System.Object.ReferenceEquals(i, obj));
    Console.WriteLine(System.Object.ReferenceEquals(i, ftt));
    Console.WriteLine(System.Object.ReferenceEquals(ftt, obj));
    Console.WriteLine(System.Object.ReferenceEquals(i, (int)obj));
    Console.WriteLine(System.Object.ReferenceEquals(i, (int)ftt));

    上面代码输出如下,至于发生多少次装箱多少次拆箱,你猜?

    False
    False
    False
    False
    False
  • 相关阅读:
    454 Authentication failed, please open smtp flag first!
    zabbix 调用的发邮件脚本
    Apache Shiro 标签方式授权
    Realm [realm.ShiroDbRealm@15408475] does not support authentication token
    简单的zabbix agent自动安装脚本
    创建IPC端口失败:拒绝访问
    如何对报表的参数控件赋值
    如何对报表的参数控件赋值
    Socket 通信原理(Android客户端和服务器以TCP&&UDP方式互通)
    mysql 执行计划走分区
  • 原文地址:https://www.cnblogs.com/ljdong7/p/12014526.html
Copyright © 2011-2022 走看看