Java LinkedHashMap

LinkedHashMap HashMap 是无序的,HashMap 在 put 的时候是根据 key 的 hashcode 进行 hash 然后放入对应的地方。所以在按照一定顺序 put 进 HashMap 中,然后遍历出 HashMap 的顺序跟 put 的顺序不同. JAVA 在 JDK1.4 以后提供了 LinkedHashMap 来实现有序的 HashMap! LinkedHashMap 是 HashMap 的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用 LinkedHashMap。LinkedHashMap 是 Map 接口的哈希表和链表数组实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {} LinkedHashMap 采用的 hash 算法和 HashMap 相同,但是它重新定义了数组中保存的元素 Entry,该 Entry 除了保存当前对象的引用外,还保存了其上一个元素 before 和下一个元素 after 的引用,从而在哈希表的基础上又构成了双向链表。迭代顺序可以是插入顺序或者是访问顺序。 /** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * 如果为true,则按照访问顺序;如果为false,则按照插入顺序。 */ private final boolean accessOrder; /** * 双向链表的表头元素。 */ private transient Entry<K,V> header; /** * LinkedHashMap的Entry元素。 * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; …… } 根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用 get 方法)的链表。默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。 ...

2017-06-19 · 3 min · Cong Chan

Java LinkedHashMap和LRUcache

LRU缓存 用缓存来存放之前读取过的数据,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力会有很大提高。但是,当我们读取的个数特别大的时候,我们不可能把所有已经读取的数据都放在缓存里,毕竟内存大小是一定的,所以一般把最近常读取的放在缓存里。 LRU(Least Recently Used)缓存利用了这样的一种思想, 把最新读取的数据放在最前面,缓存中存储的是读取最频繁的数据,以能够提高系统的性能。 LinkedHashMap实现LRU LinkedHashMap支持按照访问顺序的存储,也就是说,最近读取的会放在最前面,最不常读取的会放在最后。其次,LinkedHashMap 有一个方法用于判断是否需要移除最不常读取的数,原始方法默认不需要移除,所以,LRU 需要 override 这个方法,使得当缓存里存放的数据个数超过规定个数后,就把最不常用的移除掉。 import java.util.LinkedHashMap; import java.util.Collection; import java.util.Map; import java.util.ArrayList; /** * An LRU cache, based on <code>LinkedHashMap</code>. * * <p> * This cache has a fixed maximum number of elements (<code>cacheSize</code>). * If the cache is full and another entry is added, the LRU (least recently * used) entry is dropped. * * <p> * This class is thread-safe. All methods of this class are synchronized. * * <p> * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> * Multi-licensed: EPL / LGPL / GPL / AL / BSD. */ public class LRUCache<K, V> { private static final float hashTableLoadFactor = 0.75f; private LinkedHashMap<K, V> map; private int cacheSize; /** * Creates a new LRU cache. 在该方法中,new LinkedHashMap<K,V>(hashTableCapacity, * hashTableLoadFactor, true)中,true代表使用访问顺序 * * @param cacheSize * the maximum number of entries that will be kept in this cache. */ public LRUCache(int cacheSize) { this.cacheSize = cacheSize; int hashTableCapacity = (int) Math .ceil(cacheSize / hashTableLoadFactor) + 1; map = new LinkedHashMap<K, V>(hashTableCapacity, hashTableLoadFactor, true) { // (an anonymous inner class) private static final long serialVersionUID = 1; @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > LRUCache.this.cacheSize; } }; } /** * Retrieves an entry from the cache.<br> * The retrieved entry becomes the MRU (most recently used) entry. * * @param key * the key whose associated value is to be returned. * @return the value associated to this key, or null if no value with this * key exists in the cache. */ public synchronized V get(K key) { return map.get(key); } /** * Adds an entry to this cache. The new entry becomes the MRU (most recently * used) entry. If an entry with the specified key already exists in the * cache, it is replaced by the new entry. If the cache is full, the LRU * (least recently used) entry is removed from the cache. * * @param key * the key with which the specified value is to be associated. * @param value * a value to be associated with the specified key. */ public synchronized void put(K key, V value) { map.put(key, value); } /** * Clears the cache. */ public synchronized void clear() { map.clear(); } /** * Returns the number of used entries in the cache. * * @return the number of entries currently in the cache. */ public synchronized int usedEntries() { return map.size(); } /** * Returns a <code>Collection</code> that contains a copy of all cache * entries. * * @return a <code>Collection</code> with a copy of the cache content. */ public synchronized Collection<Map.Entry<K, V>> getAll() { return new ArrayList<Map.Entry<K, V>>(map.entrySet()); } // Test routine for the LRUCache class. public static void main(String[] args) { LRUCache<String, String> c = new LRUCache<String, String>(3); c.put("1", "one"); // 1 c.put("2", "two"); // 2 1 c.put("3", "three"); // 3 2 1 c.put("4", "four"); // 4 3 2 if (c.get("2") == null) throw new Error(); // 2 4 3 c.put("5", "five"); // 5 2 4 c.put("4", "second four"); // 4 5 2 // Verify cache content. if (c.usedEntries() != 3) throw new Error(); if (!c.get("4").equals("second four")) throw new Error(); if (!c.get("5").equals("five")) throw new Error(); if (!c.get("2").equals("two")) throw new Error(); // List cache content. for (Map.Entry<String, String> e : c.getAll()) System.out.println(e.getKey() + " : " + e.getValue()); } }

2017-06-19 · 3 min · Cong Chan

Java 垃圾回收机制

在Java中,JVM负责内存动态分配和垃圾回收的问题。Java的对象必须要有引用才能被使用,也就是说如果要操作对象,必须通过引用来进行。如果一个对象唯一的引用变量死了(随着堆栈块一起解散),对象就会被认定为可被垃圾回收(Garbage Collection)的。没有被引用的对象,是没有存在意义的,因为没有人知道它的地址,无法调用它,它的存在只会浪费空间。 目前内存的动态分配和内存回收技术已经相当成熟,但还是需要了解GC和内存分配,这样当需要排查各种内存溢出、内存泄漏问题时,当GC成为系统达到更高并发量的瓶颈时,需要对这些自动化的技术实施必要的监控和调节。 判断对象是否可回收 最简单的方法是通过引用计数(reference counting)来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,那么这个对象就被判定为可被回收的对象了。这种方式实现简单,而且效率较高,但是它无法解决对象间循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。 主流的商用程序语言(Java,C#,Lisp)的主流实现中,使用可达性分析(reachability analysis)来判定对象是否存活。 使用一些列GC Root对象作为起始点,从这些节点开始往下沿着引用链搜索,如果GC Root到某个对象无法通过任何引用链项链,则该对象会被标记一次, 并且进行一次筛选, 筛选的条件是此对象是否有必要执行finalize()方法. 当对象没有覆盖finalize()方法, 或者该方法已经被JVM调用过, JVM都会视之为没有必要执行finalize()。 如果该对象被判定为有必要执行finalize()方法, 那么这个对象会被放置在F-Queue队列中, 并在稍后由一个由JVM自动建立的, 低优先级的Finalizer线程去执行. 执行只是触发该方法, 但不会等待它结束, 因为可能会有执行缓慢或者死循环等特殊情况 稍后GC会动F-Queue里的对象进行第二次标记, 如果对象要在finalize()中避免被消灭, 只需要重新与引用链上的任何一个对象建立关联即可(finalize()的优先级较低), 这样第二次标记时它将不会被考虑. 否则就只能被回收. 要注意, 任何一个对象的finalize()只会被系统自动调用一次, 下次再GC时不会再执行, 也就是只有一次自救机会. 在Java中,可作为GC Roots的对象包括: 虚拟机栈(栈帧中的本地变量表)中引用的对象。 方法区中类静态属性引用的对象 方法区中常量引用的对象 本地方法栈中native方法引用的对象 引用 引用分为强引用,软引用,弱引用,虚引用。根据不同引用,有不同的GC回收策略。 强引用:类似Object obj = new Object();这种, 垃圾回收器永远不会回收他们 软引用:非必须引用,内存溢出之前进行回收. 如果这次回收后内存还不足,才会抛出内存溢出异常 Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; sf.get();//有时候会返回null 软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。 弱引用:非必须引用, 强度比软引用更弱. 当GC工作时, 无论当前内存是否足够, 都会回收掉只有弱引用关联的对象. ...

2017-06-19 · 1 min · Cong Chan

Java Exceptions

Exception-handling 假如调用了一个不是自己写的方法, 该方法执行某些有风险的任务(当然,自己写的也可能会有风险),可能会在运行期间出状况,那么就必须认识到该方法是有风险的, 要写出可以在发生状况时做出应对的代码. 当程序出现错误时,假如继续运行下去已经没有意义(或者根本不可能继续),那么我们就想要中断正常的控制流程 - throws an exception。 Throwing Exceptions 比如当想从某ArrayMap中提取某个不存在的键值时, java自动抛出一个implicit exception $ java ExceptionDemo Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1 at ArrayMap.get(ArrayMap.java:38) at ExceptionDemo.main(ExceptionDemo.java:6) 如果想让自己的程序抛出更详细的信息, 可以在程序中加入explicit exception public V get(K key) { intlocation = findKey(key); if(location < 0) { throw newIllegalArgumentException("Key " + key + " does not exist in map."\); } return values[findKey(key)]; } $java ExceptionDemo Exception in thread "main" java.lang.IllegalArgumentException: Key yolp does not exist in map. at ArrayMap.get(ArrayMap.java:40) at ExceptionDemo.main(ExceptionDemo.java:6) Catch Exceptions 单纯 throw exception 会导致代码崩溃。但是通过 try - catch “捕捉”异常(RuntimeException 是 Java object), 可以防止程序崩溃。 ...

2017-05-29 · 3 min · Cong Chan

Java Immutability

An immutable data type is a data type whose instances cannot change in any observable way after instantiation. 比如String是immutable, Array是mutable. Immutable 的类型: String, Integer, Double, Color, Vector, Transaction, Point2D. Mutable: StringBuilder, Stack, Counter, Java array. public final class Vector { private final int N; private final double[] data; public Vector(double[] data) { this.N = data.length; this.data = new double[N]; for (int i = 0; i < N; i++) { // defensive copy of mutable instance variables this.data[i] = data[i]; } } ... } 如何防止变量在第一次赋值后被更改: ...

2017-05-29 · 1 min · Cong Chan

Java 堆栈

认识栈和堆的概念, 有助于了解变量的有效范围(scope),对象的建立,内存管理,线程(thread)和异常处理。 对象生存在可垃圾回收的堆(heap)上面,方法调用(方法的参数,局部变量的引用)的生存空间(stack)。 当JVM启动时,从底层操作系统取得一块内存, 以此区段来执行Java程序。 实例变量和局部变量 实例变量声明在类方法之外:实例变量保存在所属的对象中,位于堆上。如果实例变量是个对对象的引用,则引用和对象都在堆上。 public class Duck { int size; Mouth mouth; } 当新建一个Duck()时, Java必须在堆上帮它找位置, 保证空间足够存放该对象所有实例变量. 对于primitive数据类型的实例变量, Java会根据类型的大小为它们留下堆空间, int 32, long 64 .... 如果实例变量是个对象, 也就是Duck对象带有引用变量mouth, Java会在堆上留给Duck的专属空间只包含该引用变量, 不包含引用的那个对象. 被引用的对象, 只有实际实例化后, 才会在堆上占有一席之地mouth = new Mouth(), 该空间并不属于Duck. 局部变量声明在方法或方法的参数上:它们是暂时的,且生命周期只限于被放在栈上的这段时间(也就是方法调用至执行完毕位止)。所有局部变量存在栈上相对应的堆栈块中,故又称为栈变量。引用对象的局部变量(只是对对象的引用)和primitive数据类型变量也都放在栈上。 public void foo(int x) { int i = x + 3; } 不管是实例变量还是局部变量, 它们指向的对象本身在堆上。 栈 程序对方法的调用,以带有方法的状态的堆栈块的形式,不断堆在栈上面,当前被执行的方法处于栈顶。执行完毕的方法会出栈。递归调用太深层,会导致栈空间耗尽。 如果方法中有局部变量引用对象,那么该局部变量也是存在于栈中. 但被引用的对象还是运行在堆上面. 堆 变量的生命周期 局部变量只会存活在声明该变量的方法中, 其余方法无法接触到 实例变量的寿命与所属对象相同. 如果所属对象还活着, 则实例变量也会活着. public class Life { int size; // size 在类中一直可以用 public void setSize(int s) { size = s; } // 但 s 仅限于setSize中, 且在方法结束后消失 } 所以这里存在生命周期life和范围scope的概念差别 ...

2017-05-29 · 1 min · Cong Chan

Java 封装, 包, JAR, 权限控制

Encapsulation 封装是面向对象编程的基本原则之一,也是程序员处理复杂性一个方法。管理复杂性是编写大型程序时必须面对的主要挑战之一。 对抗复杂性的一些工具包括: Hierarchical abstraction: 创建一个个具有明确的 abstraction barriers 的抽象层 Abstraction Barriers:使用private, 保证对象内部不能被查看, 确保底层的复杂性不会暴露给外部世界。 “Design for change” (D. Parnas) Organize program around objects. Let objects decide how things are done. Hide information others don’t need. 大概的想法都是 - 程序应该被构建成模块化,可互换的片段,可以在不破坏系统的情况下进行交换。 封装就是构建在这种对外部隐藏信息的概念上。以细胞为类比:细胞内部可能非常复杂,由染色体,线粒体,核糖体等组成,但它完全被封装在一个单一模块中 - 抽象了内部的复杂性。 Module: A set of methods that work together as a whole to perform some task or set of related tasks. Encapsulated: A module is said to be encapsulated if its implementation is completely hidden, and it can be accessed only through a documented interface. ...

2017-05-29 · 2 min · Cong Chan

Java 抽象数据类型

Abstract Data Types (ADTS) ADTS 是由其行为属性定义的抽象类型, 跟如何实现无关. 堆栈 Stacks 和队列 Queues 是两种类似的线性集合。堆栈是后进先出的ADT:元素总是从数据结构的一端添加或删除。队列是先进先出的ADT. 二者都支持以下操作: push(): 加入 peek(): 返回下一个 poll(): 返回下一个并删除 Java的Deque(double ended queue, “deck”) 接口融合了堆栈和队列, 支持两端的元素插入和移除. ArrayDeque和LinkedListDeque都是实现deque这个接口,deque只是罗列了一些 methods,也即是一种合约,保证会实现的行为。而这些方法的具体实现则是由ArrayDeque和LinkedListDeque完成。从概念上讲,deque就是一种抽象的数据类型,只说会有什么行为,但不体现这些行为的具体实现方式,所以是抽象的。 优先级队列 priority queue 的每个元素都有一个与之关联的优先级,以决定从队列中元素操作的顺序。 三种方式实现Stack的push(Item x): 继承模式:extends LinkedList<Item>以使用其方法: public class ExtensionStack<Item> extends LinkedList<Item> { public void push(Item x) { add(x); } } 委托模式Delegation, 生成Linked List并调用其方法来达到目的 public class DelegationStack<Item> { private LinkedList<Item> L = new LinkedList<Item>(); public void push(Item x) { L.add(x); } } 类似委托模式, 只是这里可以利用任何实现了List接口的类, 如Linked List, ArrayList, 等等 public class StackAdapter<Item> { private List L; public StackAdapter(List<Item> worker) { L = worker; } public void push(Item x) { L.add(x); } } Delegation vs Extension: Extension 一般是基于对父类有比较清楚的了解认知下才会使用。此外,扩展基本上等于在说明正在扩展的类与被扩展类是相似的。如果两个类无法看做是同属的, 那么就用委托模式。 ...

2017-05-29 · 2 min · Cong Chan

Java 格式

Java格式化指令" 跟在%后面的都是格式化指令. % [argument number] [flags] [width] [.precision] type []内都是选择性的参数, 且必须按照顺序. argument number 当要格式化的参数超过一个, 这里指定是哪一个 flags 特定类型的特定选项, 如数字是否要加逗号或正负号 width 最小的字符数. 但不是总数, 输出可以超过此宽度, 若不足则会主动补零 .precision 精确度 type 类型标识 d decimal, 十进制整数 f 浮点数 x hexadecimal, 16进制 c character 如format("%, 6.1f", 33.000); 如果有多个待输出的参数, 可以把新的参数加到后面, 并对应两个不同的格式化设定, 也就是两个%格式指令 String s = String.format("%,.2f out of %,d", 999, 1000) 可变参数列表 可以看到格式化的参数似乎可以不断添加,如果用重载来实现会显得不现实。为了应对格式化的API,Java支持可变参数列表 varable argument list (vararg). 日期 日期格式化的类型是用t开头的两个字符表示 %tc 完整的日期与时间 Sun Nov 03 14:52:41 2018 %tr 只有时间 03:01:47 PM 周月日 Sunday, November 03, 通过组合而来 ...

2017-05-29 · 1 min · Cong Chan

Java 比较对象大小

对物品排序首先需要比较各个物品的大小, 这个大小的定义既可以是按照"自然顺序", 也可以是其他指定的特殊规则. Comparable Java的对象不能直接使用>, <, =进行比较. 在Python或C++中,当应用于不同对象类型时,比较运算符可以重新定义,但Java不支持。但可以借用接口继承,Java提供了一个Comparable接口,包含一个compareTo方法, 以保证任何实现该接口的类可以和其他同类做比较: /** Return negative if this < o. Return 0 if this equals o. Return positive if this > o. */ public interface Comparable<T> { public int compareTo(T obj); } 当有class需要与其他class比较时, 就实现这个接口: public class Dog implements Comparable<Dog> { ... public int compareTo(Dog uddaDog) { return this.size - uddaDog.size; } } Comparable定义了类用于比较的自然顺序(Natural order), 返回的是三种结果负整数, 0, 正整数, 分别对应小于, 等于和大于. Insertion.sort()不需要知道要排序的数组类型, 因为它直接调用数组成员自带的compareTo方法. Java的Integer, Double, String, Date, File数据类型都扩展了Comparable接口。 ...

2017-05-29 · 2 min · Cong Chan