精华内容
下载资源
问答
  • Java四种引用类型

    千次阅读 2019-05-12 19:01:34
    文章目录引用类型简介对象可达性判断GC Roots对象可达性判断可达性状态类型可达性判断规则状态转换Reference类定义状态变化四种引用类型实现强引用FinalReference软引用SoftReference弱引用WeakReference虚引用...

    引用类型简介

    Java开发者肯定都很熟悉java中的4种引用类型,它们从强到弱分别是:

    引用类型 对象是否可引用 回收时间 使用场景
    强引用 可以 从不回收 普遍对象的状态
    软引用 可以 内存不足时 内存敏感的高速缓存
    弱引用 可以 下一次GC 对象缓存
    虚引用 不可以 下一次GC,不影响对象生命周期 必须和引用队列(ReferenceQueue)一起使用,一般用于追踪垃圾收集器的回收动作。相比对象的finalize方法,虚引用的方式更加灵活和安全。

    但可能大多数对4种引用类型的理解都只停留在概念的记忆上,而并不了解四种引用的实现原理。下面我们结合源代码了解下四种引用的实现原理。当然在了解四种引用的实现原理之前,首先得了解下JVM中实现对象可达性判断的原理。

    对象可达性判断

    目前,大多数JVM都是使用可达性分析算法来判断对象的是否可达。可达性分析算法以GC Roots对象作为起始点进行搜索。当一个对象与GC Roots对象没有任何引用链相连时(也即引用有向图中从GC Roots对象到这个对象是不连通的),则表明该对象是不可用的(不可用的对象不一定被判定为可以回收的对象)。当对象与GC Roots对象有引用链相连时,则需要根据引用链的类型来判断对象是否可达。

    不可用的对象不一定被判定为可以回收的对象:判定对象为”死亡”至少需要经历两次标记的过程。第一次标记:对象可达性分析,如果发现对象没有与GC Roots相连接的引用链,且对象需要执行finalize方法,将会被加入F-Queue队列中。第二次标记:由一个优先级低的Finalizer线程去取F-Queue队列的对象,“尝试执行”对象的finalize方法。

    JVM会保证触发满足条件的对象的finalize方法,但是并不承诺会等待方法执行结束。finalize方法是对象逃脱死亡命运的最后一次机会。

    GC Roots对象

    GC Roots对象包含以下四类:

    • 虚拟机栈(栈桢中的本地变量表)中的引用的对象;
    • 方法区中的类静态属性引用的对象;
    • 方法区中的常量引用的对象;
    • 本地方法栈中JNI(Native方法)引用的对象;

    可达性判断

    可达性状态类型

    Java有5种类型的可达性状态:

    • 强可达(Strongly Reachable):与GC Roots对象之间有强引用相连通,则为强可达的;
    • 软可达(Soft Reachable):与GC Roots对象之间没有强引用相连通,但有软引用相连通,则为软可达的;
    • 弱可达(Weak Reachable):与GC Roots对象之间没有强引用或软引用相连通,但有弱引用相连通,则为弱可达的;
    • 虚可达(Phantom Reachable):与GC Roots对象之间没有强引用、软引用或弱引用相连通,然后该对象finalize方法已执行,并且有虚引用相连通,则为虚可达的;
    • 不可达(Unreachable):如果对象finalize方法已执行并且没有任何引用相连通,则对象是不可达的,可以被回收。

    可达性判断规则

    从GC Roots对象到一个对象的引用链可能存在多条,那么此时会依据两个原则来判断对象的可达性:

    • 单个引用链中,以最弱的引用类型为准;
    • 多引用链联合看时,以最强的引用类型为准;

    可达性判断

    首先,单个引用链中,以最弱的引用类型为准:则GC Roots->Obj1->Obj4是软引用连通的,GC Roots->Obj2->Obj4是弱引用连通的,GC Roots->Obj3->Obj5是弱引用连通的。然后多引用链联合看时,以最强的引用类型为准:则GC Roots到Obj4对象的引用联合来看是弱引用连通的。

    状态转换

    对象可达性状态是随着程序运行而不断变化的,对象可达性状态转换图可参考下图。

    • 对象创建后一般是强可达的。
    • 当GC Roots对象到该对象的强引用被清除后:如果剩余引用链最高为软引用,则状态转换为软可达的;反之如果最高为弱引用,则状态转换为弱可达的,反之则把对象标记为可执行finalize方法状态。
    • 当软可达对象重新被强引用连接时,则转换为强可达状态;当软可达对象的软引用被清除后,如果剩余引用链最高为弱引用,则状态转换为弱可达;反之则把对象标记为可执行finalize方法状态。
    • 当弱可达对象重新被强引用或者软引用连接时,则可转换为强可达或者软可达;当弱可达对象的弱引用被清除后,则把对象标记为可执行finalize方法状态。
    • 可对象被标记为可执行finalize方法状态,如果对象finalize从未被执行,则执行finalize方法,并标记对象的finalize方法已经被执行(在finalize方法可能会重新生成强/软/弱引用等,对象状态会重新转换为强/软/弱可达,不过并不推荐这么做,因为可能会导致对象状态紊乱,无法被正常回收);反之当对象有虚引用连接时,则转换为虚可达状态,否则转换为不可达状态。
    • 虚可达对象在垃圾回收后状态转换为不可达(不能通过虚引用获取对象引用,所以对象状态不会再转换为强/软/弱可达);

    可达性状态转换图

    Reference

    Reference类是所有引用类型的基类,定义了reference对象的通用操作,用来保存对象引用及引用的内部状态。Reference抽象类初始化时,会启动一个ReferenceHandler线程。Reference的referent被回收前,垃圾回收器会把reference添加到pending这个链表里(如果注册了ReferenceQueue),然后ReferenceHandler线程不断的读取pending中的reference,把它加入到对应的ReferenceQueue中(如果Reference是Cleaner类的实例,即虚引用对象,则调用其注册的预处理钩子方法)。

    ReferenceQueue提供了两个静态字段NULL,ENQUEUED。这两个字段的主要功能:NULL是当我们构造Reference实例时queue传入null时,会默认使用NULL,这样在enqueue时判断queue是否为NULL,如果为NULL直接返回,入队失败。ENQUEUED的作用是防止重复入队,reference后会把其queue字段赋值为ENQUEUED,当再次入队时会直接返回失败。

    类定义

    Reference对象的基类。该类定义了reference对象的通用操作。因为reference对象是和垃圾回收器密切配合实现的,因此该类不能直接进行子类化。

    public abstract class Reference<T> {
    	// 用于保存对象的引用,GC会特别对待该变量
        private T referent; 
    	// 如果注册了ReferenceQueue(需要通知机制),用来保存对象引用的队列,
        volatile ReferenceQueue<? super T> queue;
    
        // 保存需要由ReferenceHandler处理的引用
        volatile Reference next;
    
    	// 被JVM使用,保存需要被JVM处理的下一个引用
        transient private Reference<T> discovered;
    
        // 同步锁,用于同步pending队列的进队和出队
        static private class Lock { }
        private static Lock lock = new Lock();
    
        // 一个PENDING队列,配合上述next一起使用,实现类单向循环链表的操作
        private static Reference<Object> pending = null;
    
    	// 高优先级线程,用于将pending队列里面的Reference实例依次添加到不同的ReferenceQueue中
    	private static class ReferenceHandler extends Thread {...}
    	...
    }
    

    状态变化

    一个对象引用有四种内部状态:

    • Active: 新创建的实例的状态,当对象引用被垃圾回收器回收前:如果Reference注册了ReferenceQueue,则会切换为Pending,并且Reference会加入pending链表中,如果没有注册ReferenceQueue,会切换为Inactive。
    • Pending: 在pending链表中的Reference的状态,这些Reference等待被ReferenceHandler内部线程加入ReferenceQueue中。
    • Enqueued: 在ReferenceQueue队列中的Reference的状态,如果Reference从队列中移除,会进入Inactive状态。未注册ReferenceQueue的实例不会到达该状态。
    • Inactive: Reference的最终状态,该状态不会再改变。

    其状态转换图如下:

    Reference内部状态转换图

    四种引用类型实现

    不同引用类型的UML类图如下:

    Reference UML类图

    强引用FinalReference

    对象新建后默认为强引用类型的,是普遍对象引用的类型。查看FinalReference在JDK中的源码发现其只有一个空实现,这也说明强引用是“默认引用类型”。

    /**
     * Final references, used to implement finalization
     */
    class FinalReference<T> extends Reference<T> {
        public FinalReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    

    软引用SoftReference

    软引用是用来描述一些“还有用但是非必须”的对象。软引用的回收策略在不同的JVM实现会略有不同,JVM不仅仅只会考虑当前内存情况,还会考虑软引用所指向的referent最近的使用情况和创建时间来综合决定是否回收该referent。软引用保存了两个变量:

    • timestamp:每次调用get方法都会更新时间戳。JVM可以利用该字段来选择要清除的软引用,但不是必须要这样做。
    • clock:时间锁,由垃圾收集器更新。

    因此,任何GC都可以使用这些字段并定义清除软引用的策略,例如:最后清除最近创建的或最近使用的软引用。

    /**
     * 软引用对象由垃圾收集器根据内存需要决定是否清除。软引用经常用于实现内存敏感的缓存。
     *
     * 假如垃圾收集器在某个时间确定对象是软可达的,此时它可以选择原子地清除
     * 指向该对象的所有软引用,以及从该对象通过强引用链连接的其他软可达对象的所有软引用。
     * 与时同时或者之后的某个时间,它会将注册了reference queues的新清除的软引用加入队列。
     *
     * 在虚拟机抛出OutOfMemoryError异常之前,将保证清除对软可达对象的所有软引用。
     * 不过,并没有对清除软引用的时间以及清除顺序施加强制约束。
     * 但是,鼓励虚拟机实现偏向不清除最近创建或最近使用的软引用。
     *
     * 该类的直接实例可用于实现简单的缓存。
     * 该类或其派生子类也可用于更大的数据结构以实现更复杂的高速缓存。
     * 只要软引用的引用对象还是强可达的,即还在实际使用中,软引用就不会被清除。
     * 因此,复杂的高速缓存可以通过持有对最近使用缓存对象的强引用来防止其被清除,
     * 而不常使用的剩余缓存对象由垃圾收集器决定是否清除。
     */
    public class SoftReference<T> extends Reference<T> {
    
    	// 时间锁,由垃圾收集器更新。
        static private long clock;
    
    	// 每次调用get方法都会更新该时间戳。JVM可能会在选择要清除的软引用时使用该字段,
    	// 但这不是强制必须的。
        private long timestamp;
    	
    	// 返回对象的引用。如果该引用对象已经被程序或者垃圾收集器清除,则返回null。
    	// 把最近一次垃圾回收时间赋值给timestamp
        public T get() {
            T o = super.get();
            if (o != null && this.timestamp != clock)
                this.timestamp = clock;
            return o;
        }
    
    }
    

    弱引用WeakReference

    当一个对象没有被强引用或者软引用连接,但被弱引用连接时,则处于弱可达状态。只要发生GC,弱可达的对象就会被清除,同时会把弱引用加入到注册的引用队列中(如果存在的话)。弱引用对GC几乎是没有影响的,它不影响对应的referent被终结(finalized)和回收(reclaimed)。因此,弱引用最常用于实现规范化映射(canonicalizing mappings),例如哈希表,如果它们在程序中未被引用,则其键和值将从映射中删除。

    /**
     * 弱引用对象不能阻止自身的引用被回收。
     * 弱引用常用于实现规范化映射(对象实例可以在程序的多个地方同时使用)。
     *
     * 假如垃圾收集器在某个时间点确定对象是弱可达的。那时它将原子地清除对该对象的所有弱引用
     * 以及该引用通过强引用或者软引用连接的所有其他弱可达对象的所有弱引用。
     * 同时,它将表明前面所指的所有弱可达对象都可以执行finalize方法。
     * 与此同时或之后某一个时间,它将注册了reference queues的那些新清除弱引用加入队列。
     */
    public class WeakReference<T> extends Reference<T> {
    
    	// 创建没有注册ReferenceQueue的弱引用
        public WeakReference(T referent) {
            super(referent);
        }
    
    	// 创建注册了ReferenceQueue的弱引用
        public WeakReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    

    虚引用PhantomReference

    虚引用是所有引用类型中最弱的一种。一个对象是否关联到虚引用,完全不会影响该对象的生命周期,也无法通过虚引用来获取一个对象的实例。为对象设置一个虚引用的唯一目的是:能在此对象被垃圾收集器回收的时候收到一个系统通知,它就是利用ReferenceQueue实现的。当referent被gc回收时,JVM自动把虚引用对象本身加入到ReferenceQueue中,表明该reference指向的referent被回收。然后可以通过去queue中取到reference,可以通过这个来做额外的清理工作。可以用虚引用代替对象finalize方法来实现资源释放,这样更加灵活和安全。

    /**
     * 虚引用对象在被垃圾收集器检查到后加入reference queues队列,否则会被回收。
     * 虚引用最常用于实现比Java finalization机制更灵活的安排额外的清理工作。
     *
     * 如果垃圾收集器在某个时间点确定虚引用对象是虚可达的,那么在那个时间或之后某个时间它会将引用加入reference queues队列。
     *
     * 为了确保可回收对象保持不变,虚引用的引用无法使用:虚引用对象的get方法始终返回null。
     *
     * 与软引用和弱引用不同,当虚引用加入reference queues队列后垃圾收集器不会被自动清除。
     * 只通过虚引用可达的对象将保持不变,直到所有此类引用都被清除或自已变为不可达。
     */
    public class PhantomReference<T> extends Reference<T> {
    
        // 由于不能通过虚引用访问对象,因此此方法始终返回null。
        public T get() {
            return null;
        }
    
        // 使用空ReferenceQueue队列创建一个虚引用没有意义:它的get方法总是返回null,
    	// 并且由于它没有注册队列,所以也不会被加入队列有任何清理前的预处理操作。
        public PhantomReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }
    }
    
    展开全文
  • java基本类型与引用类型

    万次阅读 多人点赞 2018-01-04 17:30:32
    java基本类型与引用类型 目录 java基本类型与引用类型 目录 一基本数据类型 二引用类型 三基本类型与引用类型的区别 默认值 内存分配 自动装箱自动拆箱 自动装箱拆箱带来的问题 程序的性能 空指针异常 ...

    java基本类型与引用类型


    目录

    一、基本数据类型

    java中一共分为8种基本数据类型:byte、short、int、long、float、double、char、boolean,其中byte、short、int、long是整型。float、double是浮点型,char是字符型,boolean是布尔型。

    二、引用类型

    java为每种基本类型都提供了对应的封装类型,分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean。引用类型是一种对象类型,它的值是指向内存空间的引用,就是地址。

    三、基本类型与引用类型的区别

    1.默认值

    整型byte、short、int、long的默认值都为0,浮点型float、double的默认值为0.0,boolean默认值为false,char默认值为空。对应的包装类型默认值都为null。

    2.内存分配

    基本数据类型的变量是存储在栈内存中,而引用类型变量存储在栈内存中,保存的是实际对象在堆内存中的地址,实际对象中保存这内容。

    3.自动装箱、自动拆箱

    Java从jdk1.5开始引入自动装箱和拆箱,使得基本数据类型与引用类型之间相互转换变得简单。

    自动装箱: java自动将原始类型转化为引用类型的过程,自动装箱时编译器会调用valueOf方法,将原始类型转化为对象类型。

    自动拆箱: java自动将引用类型转化为原始类型的过程,自动拆箱时编译器会调用intValue(),doubleValue()这类的方法将对象转换成原始类型值。

    自动装箱主要发生在两种情况:一种是赋值时,一种是方法调用时。
    a.赋值

    Integer a = 3; //自动装箱
    int b = a; //自动拆箱

    b.方法调用

    public Integer query(Integer a){
       return a;
    }
    query(3); //自动装箱
    int result = query(3); //自动拆箱

    4.自动装箱、拆箱带来的问题

    1.程序的性能

    由于装箱会隐式地创建对象创建,因此千万不要在一个循环中进行自动装箱的操作,下面就是一个循环中进行自动装箱的例子,会额外创建多余的对象,增加GC的压力,影响程序的性能:

    Integer sum = 0;
     for(int i=0; i<1000; i++){
       sum+=i;
    }

    2.空指针异常

    注意拆箱过程中可能产生的空指针异常,一个简单的例子:

    Object obj = null;
    int i = (Integer)obj;

    3.对象相等比较时

    先来看一个常见的例子:

    Integer a = 120;
    int b= 120;
    Integer c = 120;
    Integer d = new Integer(120);
    System.out.println(a == b);   //true    t1
    System.out.println(a == c);   //true    t2
    System.out.println(a == d);   //false   t3
    
    Integer e = 128;
    Integer f = 128;
    System.out.println(e == f);   //false    t4

    返回结果是不是出乎大家的意料,解释一下每种结果的原因:
    我们先反编译一下生成字节码:

    Integer a = Integer.valueOf(120);
    int b = 120;
    Integer c = Integer.valueOf(120);
    Integer d = new Integer(120);
    System.out.println(a.intValue() == b);
    System.out.println(a == c);
    System.out.println(a == d);
    
    Integer e = Integer.valueOf(127);
    Integer f = Integer.valueOf(127);
    System.out.println(e == f);
    
    Integer e1 = Integer.valueOf(128);
    Integer f1 = Integer.valueOf(128);
    System.out.println(e1 == f1);

    可以看到变量a、c在初始化的时候编译器调用了valueOf进行自动装箱,在a==b时对变量a调用了intValue()方法进行了自动拆箱操作,这就很好解释t1~t4的结果了。

    t1产生的原因是编译器编译时会调用intValue()自动的将a进行了拆箱,结果肯定是true;
    t2跟t4的结果比较难理解:这是因为初始化时,编译器会调用装箱类的valueOf()方法,查看jdk的源码:

    public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    发现jdk对-128~127之间的值做了缓存,对于-128~127之间的值会取缓存中的引用,通过缓存经常请求的值而显著提高空间和时间性能。
    这就能解释t2结果返回true,而t4由于128不在缓存区间内,编译器调用valueOf方法会重新创建新的对象,两个不同的对象返回false。

    t3结果无论如何都不会相等的,因为new Integer(120)构造器会创建新的对象。

    Byte、Short、Integer、Long、Char这几个装箱类的valueOf()方法都会做缓存,而Float、Double则不会,原因也很简单,因为byte、Short、integer、long、char在某个范围内的整数个数是有限的,但是float、double这两个浮点数却不是。

    展开全文
  • C#详解值类型和引用类型区别

    万次阅读 多人点赞 2016-04-20 17:59:42
    首先,什么是值类型,什么是引用类型? 在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。 值类型(value type):byte,short,int,long,float,double,decimal,...

    首先,什么是值类型,什么是引用类型?

    在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。

    值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。值类型变量声明后,不管是否已经赋值,编译器为其分配内存。


            引用类型(reference type):string 和 class统称为引用类型。当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。


    值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进程堆中分配(动态分配)。

            下面的例子说明值类型和引用类型的区别,代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Parameters
    {
        class Program
        {
            static void Main(string[] args)
            {
                Dowork();
            }
    
            static void Dowork()
            {
                int i = 0;  // int 是值类型
                Console.WriteLine(i);   // i = 0
                Pass.value(i);          // 值类型使用的是 i 的副本,i不变
                Console.WriteLine(i);   // i = 0
    
                WrappendInt wi = new WrappendInt(); // 创建类 WrappendInt 的另外一个实例
                Console.WriteLine(wi.Number);   // 0 // 被默认构造器初始化为 0
                Pass.Reference(wi);     // 调用方法,wi 和 param 将引用同一个对象
                Console.WriteLine(wi.Number);   // 42
            }
        }
    
        class Pass
        {
            public static void value(int param)
            {
                param = 42; // 赋值操作使用的是值类型参数的一个副本,原始参数不受影响
            }
    
            public static void Reference(WrappendInt param) // 创建类 WrappendInt 的一个实例
            {
                param.Number = 42;  // 此参数是引用类型的参数
            }
        }
    
        class WrappendInt   // 类是引用类型
        {
            public int Number;
        }
    }

    输出结果为:

    0

    0

    0

    42


    从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。这两种类型存储在内存的不同地方。在C#中,我们必须在设计类型的时候就决定类型实例的行为。这种决定非常重要,用《CLR via C#》作者Jeffrey Richter的话来说,“不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference between reference types and value types will introduce subtle bugs and performance issues into their code.)”。这就要求我们正确理解和使用值类型和引用类型。

    1. 通用类型系统

    C#中,变量是值还是引用仅取决于其数据类型。

    C#的基本数据类型都以平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义了可以在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言都最终被编译为IL,即编译为基于CTS类型的代码。

    例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:

    • 确保IL上的强制类型安全;
    • 实现了不同.NET语言的互操作性;
    • 所有的数据类型都是对象。它们可以有方法,属性,等。例如:
    int i;
    i
     = 1
    ;
    string
     s;
    s
     = i.ToString();

    MSDN的这张图说明了CTS中各个类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。

    c#中引用类型和值类型的区别

    2. 值类型

    C#的所有值类型均隐式派生自System.ValueType:

    • 结构体:struct(直接派生于System.ValueType);
      • 数值类型:
        • 整型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char);
        • 浮点型:float(System.Single),double(System.Double);
        • 用于财务计算的高精度decimal型:decimal(System.Decimal)。
      • bool型:bool(System.Boolean的别名);
      • 用户定义的结构体(派生于System.ValueType)。
    • 枚举:enum(派生于System.Enum);
    • 可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。

    每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:

    int i = new int();

    等价于:

    Int32 i = new Int32();

    等价于:

    int i = 0;

    等价于:

    Int32 i = 0;

    使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了i。MSDN上有完整的默认值表

    所有的值类型都是密封(seal)的,所以无法派生出新的值类型。

    值得注意的是,引 用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

    可以用Type.IsValueType属性来判断一个类型是否为值类型:

    TestType testType = new TestType ();
    if
     (testTypetype.GetType().IsValueType)
    {
         Console.WriteLine(
    "{0} is value type."
    , testType.ToString());
    }

    3. 引用类型

    C#有以下一些引用类型:

    • 数组(派生于System.Array)
    • 用户用定义的以下类型:
      • 类:class(派生于System.Object);
      • 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);
      • 委托:delegate(派生于System.Delegate)。
    • object(System.Object的别名);
    • 字符串:string(System.String的别名)。

    可以看出:

    • 引用类型与值类型相同的是,结构体也可以实现接口;
    • 引用类型可以派生出新的类型,而值类型不能;
    • 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型);
    • 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。

    对于最后一条,经常混淆的是string。我曾经在一本书的一个早期版本上看到String变量比string变量效率高;我还经常听说String是引用类型,string是值类型,等等。例如:

    string s1 = "Hello, ";
    string s2 = "world!"
    ;
    string s3 = s1 + s2;//s3 is "Hello, world!"

    这确实看起来像一个值类型的赋值。再如:

    string s1 = "a";
    string s2 = s1
    ;
    s1
     = "b";//s2 is still "a"

    改变s1的值对s2没有影响。这更使string看起来像值类型。实际上,这是运算符重载的结果,当s1被改变时,.NET在托管堆上为s1重新分配了内存。这样的目的,是为了将做为引用类型的string实现为通常语义下的字符串。

    4. 值类型和引用类型在内存中的部署

    经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。

    MSDN上说:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:

    object reference = new object();

    关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。

    再来看值类型。《C#语言规范》上的措辞是“结构体不要求在堆上分配内存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“结构体在栈上分配内存”。这不免容易让人感到困惑:值类型究竟部署在什么地方?

    4.1 数组

    考虑数组:

    int[] reference = new int[100];

    根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。

    而int数组的元素都是int,根据定义,int是值类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的值类型元素究竟位于栈还是堆?

    如果用WinDbg去看reference[i]在内存中的具体位置,就会发现它们并不在栈上,而是在托管堆上。

    实际上,对于数组:

    TestType[] testTypes = new TestType[100];

    如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。

    如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。

    4.2 类型嵌套

    更容易让人困惑的是引用类型包含值类型,以及值类型包含引用类型的情况:

    public class ReferenceTypeClass
    {
        
    private int
     _valueTypeField;
        
    public
     ReferenceTypeClass()
         {
             _valueTypeField
     = 0
    ;
         }
        
    public void
     Method()
         {
            
    int valueTypeLocalVariable = 0
    ;
         }
    }
    ReferenceTypeClass referenceTypeClassInstance
     = new ReferenceTypeClass();//Where is _valueTypeField?

    referenceTypeClassInstance.Method();//Where is valueTypeLocalVariable?

    public struct ValueTypeStruct
    {
        
    private object
     _referenceTypeField;
        
    public void
     Method()
         {
             _referenceTypeField
     = new object
    ();
            
    object referenceTypeLocalVariable = new object
    ();
         }
    }
    ValueTypeStruct valueTypeStructInstance
     = new
     ValueTypeStruct();
    valueTypeStructInstance.Method();
    //Where is _referenceTypeField?And where is referenceTypeLocalVariable?

    单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块扔到栈上的。但是字段_referenceTypeField是引用类型,局部变量referenceTypeLocalVarible也是引用类型。

    referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,似乎应该整块部署在托管堆上。但字段_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟是在栈上还是在托管堆上?

    规律是:

    • 引用类型部署在托管堆上;
    • 值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

    我们来分析一下上面的代码。对于引用类型实例,即referenceTypeClassInstance:

    • 从上下文看,referenceTypeClassInstance是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;
    • 值类型字段_valueTypeField属于引用类型实例referenceTypeClassInstance的一部分,所以跟随引用类型实例referenceTypeClassInstance部署在托管堆上(有点类似于数组的情形);
    • valueTypeLocalVariable是值类型局部变量,所以部署在栈上。

    而对于值类型实例,即valueTypeStruct:

    • 根据上下文,值类型实例valueTypeStructInstance本身是一个局部变量而不是字段,所以位于栈上;
    • 其引用类型字段_referenceTypeField不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是valueTypeStruct的一部分,位于栈);
    • 其引用类型局部变量referenceTypeLocalVariable显然部署在托管堆上,并被一个位于栈的引用所持有。

    所以,简单地说“值类型存储在栈上,引用类型存储在托管堆上”是不对的。必须具体情况具体分析。


    5. 辨明值类型和引用类型的使用场合


    在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
    SomeType[] oneTypes = new SomeType[100];
    如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。
    一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:
    该类型的主要职责用于数据存储。 
    该类型的共有接口完全由一些数据成员存取属性定义。 
    该类型永远不可能有子类。 
    该类型不具有多态行为。


    5. 值类型和引用类型的区别(小结)


    相同点:
    引用类型可以实现接口,值类型当中的结构体也可以实现接口;
    引用类型和值类型都继承自System.Object类。

    1)范围方面
    C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
    C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

    2)内存分配方面:

    数组的元素不管是引用类型还是值类型,都存储在托管堆上。

    引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)

    3)适用场合

    值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

    • 引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;
    • 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如   int? a = null;  );
    • 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。


    值得注意的是,引 用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。




    展开全文
  • 用typeof确定一个值是哪种基本类型 引用类型: 引用类型的值是对象,保存在堆内存中 引用类型的变量实际上是一个指针,它保存在栈中,指向堆内存中的对象 复制引用类型变量实际是复制该指针,所以他们都
    基本类型(5个):
    undefined  null  boolean  number  string
    基本类型保存在内存中的栈中,大小固定,复制其变量时会创建这个值的一个副本

    typeof确定一个值是哪种基本类型


    引用类型:
    引用类型的值是对象,保存在堆内存中
    引用类型的变量实际上是一个指针,它保存在栈中,指向堆内存中的对象
    复制引用类型变量实际是复制该指针,所以他们都指向同一个对象
    instanceof确定一个值是哪种引用类型

    引用类型包括:object  array  date  regexp  function


    基本包装类型(是基本也是引用,所以叫做基本包装类型):
    var s="sss";
    var sub=s.substring(2);
    s是基本类型值却有方法?
    实际上后台已经自动完成了一系列处理,当第二行访问s时,后台会完成下列处理:
    1,创建一个string类型的实例
    2,在实例上调用指定方法
    3,销毁这个实例
    实际上就相当于执行了下列过程:
    var s=new String("sss");
    var sub=s.substring(2);
    s=null;
    上面过程适用于String Boolean Number
    展开全文
  • 值类型与引用类型

    千次阅读 2016-03-02 14:39:17
    1. 值类型和引用类型的区别? 2. 结构和类的区别? 3. delegate是引用类型还是值类型?enum、int[]和string呢? 4. 堆和栈的区别? 5. 什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗? 6.“结构”...
  • Java原始类型和引用类型

    千次阅读 2016-06-05 16:30:47
    在Java中提供了两数据类型:原始类型和引用类型。 本文主要针对这两类型进行说明,便于在实际开发过程中正确选择合适的数据类型。 1. 原始类型  Java中的原始类型是不同于类的基本数据类型,包括如下8...
  • Java 基本数据类型与引用类型 变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。 因此,通过...
  • golang值类型与引用类型的区别

    千次阅读 2020-01-10 20:51:44
    1、golang值类型与引用类型的区别 1.1 本质存储区别 值类型与引用类型的区别主要在于: 值类型的变量: 存储当前类型的存储的数据。值类型包括数值类型、bool、string、数组、struct结构体 引用类型的变量:存储...
  • Swift 值类型和引用类型

    千次阅读 2018-03-13 09:28:21
    Swift中有两类型:值类型(Value Type),引用类型(Reference Type)。1. 值类型在Swift中,所有的基本类型:整型(Int)、浮点型(Float)、布尔类型(Boolean)、字符串类型(String)、数组(Array)、字典...
  • Java有 5种引用类型(对象类型):类 接口 数组 枚举 标注 引用类型:底层结构和基本类型差别较大 JVM的内存空间: (1). Heap 堆空间:分配对象 new Student() (2). Stack 栈空间:临时变量 Student stu (3)...
  • Java 基本类型与引用类型

    千次阅读 2019-03-27 20:55:51
    Java 基本类型与引用类型 一、基本数据类型 java 中一共分为 8 基本数据类型:byte、short、int、long、float、double、char、boolean, 其中 byte、short、int、long 是整型。float、double 是浮点型,char 是...
  • Java 数据类型(基本数据类型引用数据类型)

    千次阅读 多人点赞 2013-12-11 23:40:11
    Java 数据类型(基本数据类型引用数据类型) 简单的介绍
  • 基本类型和引用类型区别

    千次阅读 2018-06-01 14:30:53
    相应的,变量也有两类型:基本类型和引用类型。 基本类型的变量保存原始值,即它代表的值就是数值本身; 而引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象...
  • 根据这篇文章可以理解一下java中的基本数据类型与引用数据类型的区别,写的很好!javascript中基本数据类型和引用数据... 当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值...
  • 理解Java自定义类作为数据类型——引用数据类型

    千次阅读 多人点赞 2020-01-11 20:01:48
    前言:Java的数据类型按照类型可分为基本数据类型(byte、short、int、long、double、float、char、boolean)和引用数据类型(类(class)、接口(interface)、数组(array)),8基本数据类型相信只要接触过Java就...
  • java中四种引用类型

    千次阅读 2015-08-27 11:42:50
    今天看代码,里面有一个类java.lang.ref.SoftReference把小弟弄神了,试想一下,接触java已经有3年了哇,连lang包下面的类都不了解,怎么混。后来在网上查资料,感觉收获颇多,现记录如下。对象的强、软、弱和虚引用...
  • js 基本类型 引用类型 简单赋值 对象引用 底层原理分析  本文转载自 http://segmentfault.com/a/1190000002789651 原作者版权所有,... ...ECMAScirpt 变量有两不同的数据类型:基本类型,引用类型。也有其他
  • Java引用类型有哪些

    万次阅读 2019-07-05 16:48:32
    其中,引用数据类型在存储堆中对需要引用的对象进行引用,引用是Java面向对象的一个特点,在Java入门中,我们会接触到四Java的引用类型,接下来就说说这四Java引用类型有哪些吧: 1、Java中有引用?...
  • Java引用类型

    千次阅读 2016-03-16 19:12:55
    博主最近在整理Java集合框架时,在整理到WeakHashMap的时候,觉得有必要先阐述一下Java的引用类型,故此先整理的这篇文章,希望各位多提提意见。   闲话不多说,直接进入主题。Java中提供了4个级别的引用:强引用...
  • JavaScript数据类型,包括原始类型和引用类型有哪些? 原始类型有五个 Number类型 专门保存数字的类型,可用于进行数学计算等的数值 String类型 专门用来保存字符串的类型;” “,用来存储字符串类型的文本. ...
  • C# 值类型与引用类型

    千次阅读 2018-07-19 14:31:09
    我们知道,C#中的每一类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。 引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System....
  • JavaScript中的值类型与引用类型

    千次阅读 2013-01-10 12:15:19
    一、值类型和引用类型话题 随着部分有大型面向对象语言基础朋友的介入,...就讲讲JavaScript中的两变量类型:即值类型和引用类型,这通常又会让你联想到“堆栈”,另外还有“引用地址”或“指针”相关概念,有过J
  • String是值类型还是引用类型(C#)

    千次阅读 2015-08-28 14:22:25
    MSDN 中明确指出 String 是引用类型不是值类型,但 String 表面上用起来却像是值类型,这又是什么原因呢?
  •  早在介绍JS的数据类型的时候就提到过基本类型和引用类型,不过在说两类型之前,我们先来了解一下变量的 值的类型。在ECMAScript中,变量可以存在两类型的值,即原始值和引用值。  (1)原始值  存储在栈中...
  • Java引用类型的强制类型转换

    千次阅读 2018-07-22 22:30:10
    正如有时候需要将浮点型的... 编写Java程序时,引用类型只能调用声明该变量的类型的方法,也就是编译时类型的方法,不允许调用运行时类型所定义的方法,即使它实际所引用的对象包含该方法。解释一下就是我们说的...
  • 1、基本类型和引用类型基本的数据类型有5个:undefined,boolean,number,string,nulltypeof null; //"object" typeof undefined; //"undefined" typeof 1; //"number" typeof false //"boolean" typeof "1" //...
  • C# 值类型传参、引用类型传参

    千次阅读 多人点赞 2014-04-09 11:23:10
    而其本身类型有值类型与引用类型下面分类详细说明。 二、按值传参 1.值类型 值类型存储在栈中,故按值传递时只是简单传递一份复制数据,接受参数的函数操作的只是一个副本,对原数据未做任何修改。 2.引用...
  • Javascript中的基本类型和引用类型

    千次阅读 2016-06-30 21:46:27
    一、基本类型和引用类型...引用类型值:保存在内存中,js不允许直接访问内存位置,因此时操作引用而不是实际对象 二、如何检测数据类型 1.基本数据类型的检测:使用typeof var s = “AAA”; alert(typeof s);
  • c#中的值类型和引用类型

    千次阅读 2019-02-03 15:43:04
    值类型和引用类型,是c#比较基础,但是也必须掌握的知识点,但是也不是那么轻易就能掌握,今天让我们一起来看看。 &amp;nbsp; 典型类型 首先我们看看这两不同的类型有哪些比较典型的代表。 &amp;nbsp; ...
  • 我们都知道,c#的两大数据类型分别为值类型(int,float,double,char,DataTime)和引用类型(类、托管、数组和接口)。很多人或许闭着眼睛都能说出值类型包括简单类型、结构体类型和枚举类型,引用类型包括自定义类、数组...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 954,175
精华内容 381,670
关键字:

下面哪种类型不是引用类型