Java深入集合--linkedHashMap

原文:http://greemranqq.iteye.com/blog/1931880

LinkedHashMap 源码介绍

 

一、介绍:

     LinkedHashMap 和hashMap 功能类似,都是维护的键值对集合,连遍历 以及方法都类似,唯一的区别在于

hashMap  里面的元素是根据hash值来决定存放位置的,是无序的,而LinkedHashMap 维护的是一个按顺序存放的双向链表,是有序的。

      所谓的双向链表其实是链表的一种。链表:相当于元素 A->B->C ,也就是我可以通过A 找到B ,从而找到C,可以单向移动。而双向链表:A<->B<->C ,也就是说我可以从B  找到 A 和 C,可以双向移动。

 

二、源码分析:

       

Java代码 复制代码  收藏代码
  1. public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>  
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

    我们可以看到 这东西是继承的HashMap 的,说明拥有hashMap 的功能,那么我们具体来看看 不同在哪里呢?

 

   1. 构造函数:

    

Java代码 复制代码  收藏代码
  1. public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {  
  2.         super(initialCapacity, loadFactor);  
  3.         this.accessOrder = accessOrder;  
  4. }  
  5.   
  6.  public LinkedHashMap(int initialCapacity, float loadFactor) {  
  7.         super(initialCapacity, loadFactor);  
  8.         accessOrder = false;  
  9. }  
  10.   
  11.  public LinkedHashMap(int initialCapacity) {  
  12.     super(initialCapacity);  
  13.         accessOrder = false;  
  14. }  
  15. 我们看到3个构造函数,其实都是访问的super(..),也就是hashMap 的构造,区别就在accessOrder   
  16. 来看看它是什么?  
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
}

 public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
}

 public LinkedHashMap(int initialCapacity) {
	super(initialCapacity);
        accessOrder = false;
}
我们看到3个构造函数,其实都是访问的super(..),也就是hashMap 的构造,区别就在accessOrder 
来看看它是什么?

 

 

Java代码 复制代码  收藏代码
  1.  //The iteration ordering method for this linked hash map: <tt>true</tt>  
  2.  //for access-order, <tt>false</tt> for insertion-order.  
  3. // 简单说就是这个用来控制元素的顺序,  
  4. // true: 是访问的顺序,也就是谁最先访问,就排在第一位  
  5. // false:存放顺序,就是你put 元素的时候的顺序  
  6. private final boolean accessOrder;  
 
     //The iteration ordering method for this linked hash map: <tt>true</tt>
     //for access-order, <tt>false</tt> for insertion-order.
    // 简单说就是这个用来控制元素的顺序,
    // true: 是访问的顺序,也就是谁最先访问,就排在第一位
    // false:存放顺序,就是你put 元素的时候的顺序
    private final boolean accessOrder;

  这里稍后再看怎么用的,我们先来分析核心内部类 Entry 类,这几乎是所有map 都需要的东西。

 

Java代码 复制代码  收藏代码
  1. private static class Entry<K,V> extends HashMap.Entry<K,V> {  
  2.        // These fields comprise the doubly linked list used for iteration.  
  3.        // 这里我们看到,Entry<K,V>  类里面多了了两个属性,专门来方便我们进行链表的前后移动        // 的  
  4.        Entry<K,V> before, after;  
  5.        // 这里调用了hashMap 的entry 构造,说明还是用的HashMap 的Entry  
  6. Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {  
  7.            super(hash, key, value, next);  
  8.        }  
  9.       // ..剩下的稍后说  
 private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        // 这里我们看到,Entry<K,V>  类里面多了了两个属性,专门来方便我们进行链表的前后移动        // 的
        Entry<K,V> before, after;
        // 这里调用了hashMap 的entry 构造,说明还是用的HashMap 的Entry
	Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }
       // ..剩下的稍后说
}

   

 

 

Java代码 复制代码  收藏代码
  1. 继续来看这些属性怎么利用起来的。对于集合,我们肯定要关注他的 存放元素 和 取元素的方法啦:  
  2. 当我们找遍整个类的时候发现,没有找到put 方法- -。但是LinkedHashMap 肯定是可以调用put的,因为继承了hashMap,  
  3. 那我们把 hashMap 的put方法先取出来吧  
继续来看这些属性怎么利用起来的。对于集合,我们肯定要关注他的 存放元素 和 取元素的方法啦:
当我们找遍整个类的时候发现,没有找到put 方法- -。但是LinkedHashMap 肯定是可以调用put的,因为继承了hashMap,
那我们把 hashMap 的put方法先取出来吧

 

Java代码 复制代码  收藏代码
  1. public V put(K key, V value) {  
  2.        if (key == null)  
  3.            return putForNullKey(value);  
  4.        int hash = hash(key.hashCode());  
  5.        // 这里都是调用 hashMap 计算位置什么的,前面hashMap 已经分析过啦  
  6.        int i = indexFor(hash, table.length);  
  7.        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  8.            Object k;  
  9.            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  10.                V oldValue = e.value;  
  11.                e.value = value;  
  12.                // 这里被重写了 A  
  13.                e.recordAccess(this);  
  14.                return oldValue;  
  15.            }  
  16.        }  
  17.   
  18.        modCount++;  
  19.        // 这里被重写了 B   
  20.        addEntry(hash, key, value, i);  
  21.        return null;  
  22.    }  
 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        // 这里都是调用 hashMap 计算位置什么的,前面hashMap 已经分析过啦
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                // 这里被重写了 A
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        // 这里被重写了 B 
        addEntry(hash, key, value, i);
        return null;
    }

 

 

  从上面看出和hashMap 没啥区别,关键就看重写的方法啦,特别是e.recordAccess 这个方法 在hashMap     介绍中,不是提到不知道干什么的嘛,这里详细来看看。

    

Java代码 复制代码  收藏代码
  1. private static class Entry<K,V> extends HashMap.Entry<K,V> {  
  2.     /** 
  3.          * This method is invoked by the superclass whenever the value 
  4.          * of a pre-existing entry is read by Map.get or modified by Map.set. 
  5.          * If the enclosing Map is access-ordered, it moves the entry 
  6.          * to the end of the list; otherwise, it does nothing. 
  7.          */  
  8.         void recordAccess(HashMap<K,V> m) {  
  9.             LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
  10.             // 如果参数为true,也就是根据访问顺序  
  11.             if (lm.accessOrder) {  
  12.                 // 迭代控制的变量  
  13.                 lm.modCount++;  
  14.                 remove();  
  15.                 addBefore(lm.header);  
  16.             }  
  17.         }  
  18.       
  19.     // 这里我们发现 before和after 虽然定义了属性,是哪儿赋值的呢?为什么不为null呢?  
  20.         // 请回到 构造函数,我们发现有一个init()方法,以前是没用的,这里也进行了重写,请看         // 后面init 方法  
  21.         private void remove() {  
  22.             // 先移除当前这个空节点  
  23.             before.after = after;  
  24.             after.before = before;  
  25.         }  
  26.           
  27.     /** 
  28.          * Inserts this entry before the specified existing entry in the list. 
  29.          */  
  30.         private void addBefore(Entry<K,V> existingEntry) {  
  31.             // 然后将这个空节点 赋值  
  32.             after  = existingEntry;  
  33.             // 当前节点 ,赋值于标志位的前节点  
  34.             before = existingEntry.before;  
  35.             // 然后将复制后的节点的 两个属性,继续赋值为空,等待另一个节点的插入  
  36.             before.after = this;  
  37.             after.before = this;  
  38.         }  
  39.         // 整个  
  40.   
  41. }  
private static class Entry<K,V> extends HashMap.Entry<K,V> {
	/**
         * This method is invoked by the superclass whenever the value
         * of a pre-existing entry is read by Map.get or modified by Map.set.
         * If the enclosing Map is access-ordered, it moves the entry
         * to the end of the list; otherwise, it does nothing.
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            // 如果参数为true,也就是根据访问顺序
            if (lm.accessOrder) {
                // 迭代控制的变量
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
	
	// 这里我们发现 before和after 虽然定义了属性,是哪儿赋值的呢?为什么不为null呢?
        // 请回到 构造函数,我们发现有一个init()方法,以前是没用的,这里也进行了重写,请看         // 后面init 方法
        private void remove() {
            // 先移除当前这个空节点
            before.after = after;
            after.before = before;
        }
        
	/**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry<K,V> existingEntry) {
            // 然后将这个空节点 赋值
            after  = existingEntry;
            // 当前节点 ,赋值于标志位的前节点
            before = existingEntry.before;
            // 然后将复制后的节点的 两个属性,继续赋值为空,等待另一个节点的插入
            before.after = this;
            after.before = this;
        }
        // 整个

}

 

  init() 方法:

 

Java代码 复制代码  收藏代码
  1.   /** 
  2.    * The head of the doubly linked list. 
  3.    */  
  4. private transient Entry<K,V> header;  
  5. void init() {  
  6.       // 这里默链表 表头是header,并让hash值为-1,其他都为null,仅仅是作为一个标志位  
  7.       // 初始化这个节点  
  8.       header = new Entry<K,V>(-1nullnullnull);  
  9.       // 赋值在这里,默认这是起点,相当于还没有其他元素  
  10.       header.before = header.after = header;  
  11.   }  
    /**
     * The head of the doubly linked list.
     */
  private transient Entry<K,V> header;
  void init() {
        // 这里默链表 表头是header,并让hash值为-1,其他都为null,仅仅是作为一个标志位
        // 初始化这个节点
        header = new Entry<K,V>(-1, null, null, null);
        // 赋值在这里,默认这是起点,相当于还没有其他元素
        header.before = header.after = header;
    }

 

   可能上面的文字描述很难理解,我们假设:这个双链表结构相当于 一条一节一节连起来的项链,当然每一节肯定有链接前before 和 after 这样的连接位置(属性),中间才是宝石(数据)。然后,一般项链都有一个明显的位置,方便你去下来的(header),那里其实是用来首位相连的,当我们觉得长度不够,需要添加一个宝石的时候,首先会从那里打开,也就是执行(remove)方法,然后把你需要的宝石连接在最后的位置(after  = existingEntry,before = existingEntry.before;);,从程序上来说 就是把那个header 的位置,原先连接器的先取掉掉,那个位置就从新连接新的元素。

    

继续讲解put 方法,继续往下看 还有一个addEntry(hash, key, value, i)方法,也进行了重写

 

Java代码 复制代码  收藏代码
  1.    void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.        // 存放元素  
  3.        createEntry(hash, key, value, bucketIndex);  
  4.   
  5.        // Remove eldest entry if instructed, else grow capacity if appropriate  
  6.        Entry<K,V> eldest = header.after;  
  7.        // 这里始终返回的是false,也就是说 方便我们扩展,重写的- -!  
  8.        if (removeEldestEntry(eldest)) {  
  9.            removeEntryForKey(eldest.key);  
  10.        } else {  
  11.            if (size >= threshold)  
  12.                resize(2 * table.length);  
  13.        }  
  14. }  
  15. void createEntry(int hash, K key, V value, int bucketIndex) {  
  16.        // 通过key 和长度 计算出的位置,去获得那个元素,可能为空  
  17.        HashMap.Entry<K,V> old = table[bucketIndex];  
  18.        // 然后在这个位置创建一个新元素,并让next 指向old  
  19. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
  20.        // 把这个元素放进数组的这个位置  
  21.        table[bucketIndex] = e;  
  22.        // 然后把header 的after before属性,和元素节点从新连接起来  
  23.        // 元素就在header 之前了,也就是可以保证最先访问(这里通过Set 集合遍历顺序是反的- -!)  
  24.        e.addBefore(header);  
  25.        size++;  
  26.    }  
    void addEntry(int hash, K key, V value, int bucketIndex) {
        // 存放元素
        createEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        // 这里始终返回的是false,也就是说 方便我们扩展,重写的- -!
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
 }
 void createEntry(int hash, K key, V value, int bucketIndex) {
        // 通过key 和长度 计算出的位置,去获得那个元素,可能为空
        HashMap.Entry<K,V> old = table[bucketIndex];
        // 然后在这个位置创建一个新元素,并让next 指向old
	Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
        // 把这个元素放进数组的这个位置
        table[bucketIndex] = e;
        // 然后把header 的after before属性,和元素节点从新连接起来
        // 元素就在header 之前了,也就是可以保证最先访问(这里通过Set 集合遍历顺序是反的- -!)
        e.addBefore(header);
        size++;
    }

 

   上面可以看出,linkedHashMap,元素默认是放在链表前,也就是根据存放顺序放的,而实际情况还是用的hashMap 里面的table 数组,元素位置是随机存放的,只是linkedHashMap扩展,加入了属性,对每个元素存放的位置进行了像链表结构那样的链接。那么当我们设置accessOrder=true 的时候如何才控制根据访问顺序进行排列呢?首先请看get 方法:

Java代码 复制代码  收藏代码
  1. }    // 也进行了重写  
  2. public V get(Object key) {  
  3.     // 调用父类的get方法  
  4.     Entry<K,V> e = (Entry<K,V>)getEntry(key);  
  5.     if (e == null)  
  6.         return null;  
  7.     // 这里还是调用的刚才的方法,把当前元素放在header 之前,也就完成了 根据访问顺序排序  
  8.     e.recordAccess(this);  
  9.     return e.value;  
  10. }  
    }    // 也进行了重写
    public V get(Object key) {
        // 调用父类的get方法
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        // 这里还是调用的刚才的方法,把当前元素放在header 之前,也就完成了 根据访问顺序排序
        e.recordAccess(this);
        return e.value;
    }

 

上面对该集合实现双链表结构的原理 以及代码大概讲述了一下,可以去看看图例更加清晰,下面继续看看一些方法。

 

Java代码 复制代码  收藏代码
  1. 1.keySet(),entrySet(),values方法:  
  2.  这里还是调用的父类的,但是它重写了几个方法:  
  3.  // 这是重写的  
  4.  Iterator<K> newKeyIterator()   { return new KeyIterator();   }  
  5.  Iterator<V> newValueIterator() { return new ValueIterator(); }  
  6.  terator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }  
  7.  // 这里返回值方式类似,都是通过 nextEntry()返回不同的内容,但是继承对象变成了LinkedHashIterator,不是原来的  // HashIterator  
  8.  private class KeyIterator extends LinkedHashIterator<K> {  
  9. public K next() { return nextEntry().getKey(); }  
  10.   }  
  11.   // 看看区别吧  
  12.   private abstract class LinkedHashIterator<T> implements Iterator<T> {  
  13.        // 后一个节点  
  14. Entry<K,V> nextEntry    = header.after;  
  15.        // 最后返回的节点  
  16. Entry<K,V> lastReturned = null;  
  17.   
  18. // 迭代的那个变量控制  
  19. int expectedModCount = modCount;  
  20.        // 如果下一个元素师头元素,说明已经到底呢,没有其他元素了  
  21.        public boolean hasNext() {  
  22.            return nextEntry != header;  
  23. }  
  24.       // 获得下一个元素  
  25.       Entry<K,V> nextEntry() {  
  26.            // 这里就不明白了,当排序参数设置为true 是,有lm.modCount ++,这里进行.next 迭代的时候,老报错  
  27.            // 不明白的意义了  
  28.     if (modCount != expectedModCount)  
  29.     throw new ConcurrentModificationException();  
  30.            // 这里元素迭代完了, 直接抛异常- -,不懂为什么这样设计  
  31.            if (nextEntry == header)  
  32.                throw new NoSuchElementException();  
  33.            // 返回下一个元素,并且将nextEntry 指向下下一个元素  
  34.            Entry<K,V> e = lastReturned = nextEntry;  
  35.            nextEntry = e.after;  
  36.            return e;  
  37. }  
  38.        // 删除  
  39.        public void remove() {  
  40.            // 这里同上  
  41.     if (lastReturned == null)  
  42.     throw new IllegalStateException();  
  43.     if (modCount != expectedModCount)  
  44.     throw new ConcurrentModificationException();  
  45.     // 这里看出lastReturned 作用就是记录遍历的当前位置,方便删除  
  46.            // 这里直接调用removeEntryForKey 方法就好了,这个- - 看着不爽,反正又不要返回值  
  47.            LinkedHashMap.this.remove(lastReturned.key);  
  48.            lastReturned = null;  
  49.            expectedModCount = modCount;  
  50. }  
  51.   }  
 1.keySet(),entrySet(),values方法:
  这里还是调用的父类的,但是它重写了几个方法:
  // 这是重写的
  Iterator<K> newKeyIterator()   { return new KeyIterator();   }
  Iterator<V> newValueIterator() { return new ValueIterator(); }
  terator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }
  // 这里返回值方式类似,都是通过 nextEntry()返回不同的内容,但是继承对象变成了LinkedHashIterator,不是原来的  // HashIterator
  private class KeyIterator extends LinkedHashIterator<K> {
	public K next() { return nextEntry().getKey(); }
   }
   // 看看区别吧
   private abstract class LinkedHashIterator<T> implements Iterator<T> {
        // 后一个节点
	Entry<K,V> nextEntry    = header.after;
        // 最后返回的节点
	Entry<K,V> lastReturned = null;

	// 迭代的那个变量控制
	int expectedModCount = modCount;
        // 如果下一个元素师头元素,说明已经到底呢,没有其他元素了
        public boolean hasNext() {
            return nextEntry != header;
	}
       // 获得下一个元素
       Entry<K,V> nextEntry() {
            // 这里就不明白了,当排序参数设置为true 是,有lm.modCount ++,这里进行.next 迭代的时候,老报错
            // 不明白的意义了
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
            // 这里元素迭代完了, 直接抛异常- -,不懂为什么这样设计
            if (nextEntry == header)
                throw new NoSuchElementException();
            // 返回下一个元素,并且将nextEntry 指向下下一个元素
            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
	}
        // 删除
        public void remove() {
            // 这里同上
	    if (lastReturned == null)
		throw new IllegalStateException();
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
	    // 这里看出lastReturned 作用就是记录遍历的当前位置,方便删除
            // 这里直接调用removeEntryForKey 方法就好了,这个- - 看着不爽,反正又不要返回值
            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
	}
   }

 

 

Java代码 复制代码  收藏代码
  1. 2.containsKey() 这里是访问hashMap 的方法,这里就不说了。  
  2.    containsValue() 进行了重写  
  3.   
  4.    public boolean containsValue(Object value) {  
  5.         // Overridden to take advantage of faster iterator  
  6.         // 这里仅仅是通过链表的两个属性进行遍历,hashMap 是通过table 数组进行遍历,效果差不        // 多  
  7.         if (value==null) {  
  8.             for (Entry e = header.after; e != header; e = e.after)  
  9.                 if (e.value==null)  
  10.                     return true;  
  11.         } else {  
  12.             for (Entry e = header.after; e != header; e = e.after)  
  13.                 if (value.equals(e.value))  
  14.                     return true;  
  15.         }  
  16.         return false;  
2.containsKey() 这里是访问hashMap 的方法,这里就不说了。
   containsValue() 进行了重写

   public boolean containsValue(Object value) {
        // Overridden to take advantage of faster iterator
        // 这里仅仅是通过链表的两个属性进行遍历,hashMap 是通过table 数组进行遍历,效果差不        // 多
        if (value==null) {
            for (Entry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (Entry e = header.after; e != header; e = e.after)
                if (value.equals(e.value))
                    return true;
        }
        return false;

 

最近我不能直接在iteye 进行编辑,不知道为什么,老是卡死,都是记事本写了,复制过来,没图片和格式,望见谅!

总结:1.linkedhashMap 是继承于hashMap 也就是拥有了他一切功能

           2.他是双链表结构,好处可以存取顺序

           3.可以进行扩展,用来对那些最近访问元素的优先获得权

           4.存放效率,如果构造参数设置为true ,由于要维护链表结构,效率比hashMap 低一点,但是默认是放在最后,能直接从header 进行操作,效率其实没多大影响。

           5.get 元素类似。如果构造参数为true ,需要进行链表的操作,效率低于hashMap,否则效率一样。

           6.通过Iterator.keySet().iterator() 这样迭代,数据3000000 的情况,数据默认一个数字,linkedhashMap 慢几十毫秒,基本没影响。如果构造参数为true ,则linkedHashMap 迭代异常。

             

Java代码 复制代码  收藏代码
  1.        // 我内存不够,i大了要内存溢出  
  2.     public static  void test(){  
  3.     LinkedHashMap p = new  LinkedHashMap(3000000,0.75f,false);  
  4.     for(int i =0;i<3000000;i++){  
  5.         p.put(i, 2);  
  6.     }  
  7.     long a = System.currentTimeMillis();  
  8.     Iterator it1 = p.keySet().iterator();  
  9.     while(it1.hasNext()){  
  10.         Object o = it1.next();  
  11.         p.get(o);  
  12.     }  
  13.     long b = System.currentTimeMillis();  
  14.     System.out.println(b-a);// 250 毫秒  
  15.       
  16. }  
  17. public static void test2(){  
  18.     Map m = new HashMap(3000000);  
  19.     for(int i =0;i<3000000;i++){  
  20.         m.put(i, 2);  
  21.     }  
  22.     long a = System.currentTimeMillis();  
  23.     Iterator it1 = m.keySet().iterator();  
  24.     while(it1.hasNext()){  
  25.         Object o = it1.next();  
  26.         m.get(o);  
  27.     }  
  28.     long b = System.currentTimeMillis();  
  29.     System.out.println(b-a);// 210 毫秒  
  30. }  

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值