Java基础中的一些知识点(二)

Java基础知识

  • Java容器集合类
  • Java常用设计模式

1、Java容器集合类

Java容器类主要可从两大类讨论,一类是Collection集合类,还有一类是Map类。第一类是通过Collection接口来实现的,第二类是通过Map接口来实现的。

  • Collection接口
    List、Set、Queue均是在Collection接口下继承(此处并未实现)的接口。
    (1)实现List接口的类有ArrayList、LinkedList、Vector。特点是有序但允许有重复元素。其中的LinkedList类是ArrayList的子类。

     ArrayList: 适用于随机访问的集合,类似于顺序表。
     LinkedList:适用于经常进行插、删、移操作的集合,类似于链表。
     Vector:能够实现同步,经常用到的是它的子类Stack,即我们常用的栈,功能类似与数组。
     **ArrayList每次扩容是以“50%+1”的形式,例原有12,则扩容后有19,而Vector每次扩容是以“100%”的形式,例原有12,则扩容后有24。
    

    (2)实现Set接口的类有HashSet、LinkedHashSet、TreeSet等。特点是无序且不允许有重复元素。

      HashSet:底层是通过哈希函数实现的。
      LinkedHashSet:底层是通过链表和哈希函数共同实现的。
    

TreesSet:通过传递实现Comparator接口的比较器对象构建TreeSet对象,使其具有排序功能,构造器源码如下:

public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<E,Object>(comparator));
    }

(3)实现Queue接口的类有PriorityQueue,即我们常用的队列。

  • Map接口

    Java中的Map接口主要是以键值对的形式存储元素。实现Map接口的类主要有HashMap、LinkedHashMap、TreeMap、HashTable。

    (1)HashMap类

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

从源代码也可以看出HashMap不仅实现了Map接口,还继承了AbstractMap类。其数据结构可以用“链表散列”来概括,即整体上可以看成一个table数组,而数组中的每个元素被称为Entry节点,其可以连接成一条链,其中存放了哈希值相同的具体元素值(键值对)。以下为Entry的底层代码。Entry节点包含了键key、值value、指向下一个节点next,以及hash值。

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        .......
    }

影响HashMap性能的关键因素是初始容量和加载因子。HashMap类有三种构造函数:

  • HashMap():默认构造函数。
  • HashMap(int initialCapacity): 带指定初始容量的构造函数。
  • HashMap(int initialCapacity, float loadFactor):带指定初始容量和加载因子的构造函数。

HashMap的默认初始容量为16,每次以一倍的量扩容,默认加载因子为0.75。
(2)LinkedHashMap类
HashMap类无法保证元素的插入顺序,而LinkedHashMap可以保证元素的插入顺序,它是HashMap的子类。底层实现类似于HashMap,不再赘述。

(3)HashTable类

public class Hashtable<K,V>  
    extends Dictionary<K,V>  
    implements Map<K,V>, Cloneable, java.io.Serializable  

从源码可以看出HashTable类除了实现了Map接口,还继承了Dictionary类。Dictionary类是所有可将键映射到对应值的类抽象父类。

HashTable主要采用“拉链法”实现哈希表,其中有几个比较重要的参数:table数组、count、threshold、loadFactor、modCount。

  • table: 是一个数组类型,用Entry[]表示,里面装着Entry节点,代表了键值对。
  • count:是HashTable的大小,表示其所含Entry键值对的数量。
  • threshold:是HashTable的阈值,主要用来判断是否需要调整HashTable的容量,其值为容量与加载因子的乘积。
  • loadFactor: 加载因子。
  • modCount:用来实现“fail-fast”机制,即在并发集合中,对其进行迭代操作时,一旦遇到不安全情况(有其他线程参与修改数据等),立马抛出异常,停止操作。

HashTable类有四种常见形式的构造函数:

  • Hashtable():默认构造函数。
  • Hashtable(int initialCapacity):带指定初始容量的构造函数。
  • Hashtable(int initialCapacity, float loadFactor):带指定初始容量和加载因子的构造函数。
  • Hashtable(Map< ?extends K, ? extends V> t): 构造一个与给定的 Map 具有相同映射关系的新哈希表。

HashTable的默认初始容量是11,每次以一倍加1的量扩容,默认加载因子为0.75。

(4)ConcurrentHashMap类(补充)

该类是在HashMap和HashTable的基础上的改进类。主要有三个部分组成:ConcurrentHashMap(整个Hash表)、Segment段(子Hash表)和HashEntry(节点)。
说到ConcurrentHashMap类,就要提到线程同步的特性,在实现Map接口的这些类中,只有HashTable和ConcurrentHashMap这两个类是线程安全的。而这两个类实现线程安全的方式又有所不同。HashTable类在操作时是锁定整张哈希表,使得其他线程无法操作哈希表的任何部分;而ConcurrentHashMap类则是采用分段锁技术,只对要操作的段进行加锁,此时其他线程依旧可以操作哈希表的未加锁段,并且读操作不需要加锁,只是对删除和插入操作进行加锁。

(5)TreeMap类
TreeMap类底层是通过红黑树算法实现的,具有排序功能。这里需要讲解一些TreeMap的两个主要操作put()和deleteEntry()。

  • put()操作
    添加元素操作可以概括为两个步骤,一个构建排序二叉树,二是建立平衡二叉树。
    1、从根节点开始检索,不断拿当前节点与新增节点进行对比,若新增节点值较大,向右子树遍历,反之向左子树遍历。
    2、循环递归第一步,直到检索出合适的叶子节点为止。
    3、将新增节点与第二步中检索出的节点进行比较,若新增节点值较大,则将其添加为右子节点,反之,为左子节点。
    4、之后观察该排序二叉树是否符合平衡二叉树的标准,不符合将其调整为平衡二叉树。

  • deleteEntry()操作
    删除红黑树中节点的操作,要分几种情况:

    1、要删除无子节点的节点:直接将其父节点的对应子节点指针置为NULL。
    2、要删除有一个子节点的节点: 用子节点替代该删除节点的位置,然后删去子节点。
    3、要删除有两个子节点的节点: 这种情况较复杂,要记住一个原则“找到一个子C节点来替代待删除节点P的位置,然后删除节点C,并调整红黑树”。

此处讲到了红黑树,那么温故一下红黑树的五个重要特点:

1、红黑树中的节点只有红色和黑色两种。
2、红黑树的根节点是黑色的。
3、每个叶子节点都是黑色的。
4、如果一个节点是红色的,那么它的子节点一定是黑色的。即在任意一条根节点到叶子节点的路径上不存在相邻的红色节点。
5、从任意一个节点到其叶子节点的所有路径上都含有同样数目的黑色节点。

2、Java常用设计模式

也许你觉得本篇博文跳跃有点大,但是不管啦,反正都是Java的基础知识点,想到哪儿就记录到哪儿,至少这样证明了曾经温习过,难道不是吗,嘻嘻~~

下面介绍我们经常用到的几种Java设计模式。

  • 工厂模式

工厂模式,字如其意,可以用“产品+工厂”的形式表达,其意图在于定义一个用来创建对象的接口,让子类决定实例化那个类。属于创建型模式,两个主要角色即工厂和产品,主要过程分为四步:

1、定义抽象产品接口

interface IA{}  //定义一个抽象产品接口

2、 定义具体的产品子类

//A产品有A1、A2、A3...
class A1 implements IA{}   
class A2 implements IA{}   
class A3 implements IA{}   
...

3、定义抽象工厂类

//抽象工厂类,声明了生产产品的抽象函数
abstract class B{
   abstract IA create();
}

4、定义具体工厂子类

//B工厂有B1、B2、B3...
class B1 extends B{
   IA create(){ return new A1();}
}
class B2 extends B{
   IA create(){ return new A2();}
}
class B3 extends B{
   IA create(){ return new A3();}
}
...
  • 代理模式

代理模式属于结构性模式,旨在给某一个对象提供一个代理对象,并用代理对象控制原对象的引用,主要涉及的角色有抽象主题ISubject、具体主题RealSubject、代理对象Proxy。主要过程分为三步。
1、定义抽象主题接口ISubject

//抽象主题
interface ISubject{
   abstract void Request();
 }

2、定义实际主题类

class RealSubject implements ISubject{
   void Request(){}
}

3、定义代理

//此处定义的是动态代理的Proxy(下文会讲到)
class Proxy implements ISubject{
  public RealSubject realsubject;//定义了一个实际主题类对象
  void PreRequest(){}
  void Request(){ realsubject.Request();} //调用实际主题对象的Request方法
  void PostRequest(){}
 }

代理模式分为静态代理和动态代理两种。

(1) 静态代理模式的特点是一个主题类对应一个代理类,代理类在程序运行前已经编译好。
(2) 动态代理模式的特点是多个主题类对应一个代理类,Proxy类中的前处理PreRequest和后处理PostRequest函数是一样的,只有Request函数不同, 可以大大降低程序的规模。 主要应用有JDK动态代理、cglib动态代理等。

  • 命令模式

    命令模式属于行为型模式,旨在将发送命令和接收处理命令对象分离开。主要角色有四种:抽象命令者ICommand,具体命令发送者Commander、命令接收者Receiver、命令请求者Invoker。过程如下:

1、定义抽象命令者

interface ICommand{
   abstract void execute();
}

2、定义具体命令发送者

class Commander implements ICommand{
  public Receiver receiver;//定义了一个命令接接收者类对象
  void execute(){receiver.action()};//调用命令接接收者类对象的action函数
}

3、定义命令接收者类

class Receiver{
  void action(){};
}

4、定义命令请求者类

//命令请求者,负责具体命令的管理和维护
class Invoker{
   public ICommand command;//定义了一个抽象命令者对象
   void executeCommand(){command.execute();}//调用抽象命令者的execute函数
}
  • 装饰者模式

装饰者模式属于结构性模式,用于动态增加对象的额外职责。主要角色有抽象构件角色Component、抽象装饰角色Decorator、具体构件角色ConcreteComponent、具体装饰角色ConcreteDecorator。具体过程如下:
1、定义抽象构件接口

interface Component{
    abstract void log();
} 

2、定义具体构件类(可有很多)

class ConcreteComponent implements Component{
     void log(){}
}

3、 定义抽象装饰接口

interface Decorator{
   public Component component;//持有接口Component 的应用
   abstract void log();
}

4、定义具体装饰类(可有很多)

class ConCreteDecorator implements Decorator{
    void log(){}
}
  • **代理模式和装饰者模式的区别(注意点)

    1、代理模式中代理对象和真实对象之间的关系在编译期间就已经确定了(特指静态代理);而装饰者模式构件和装饰对象的联系在运行递归时才会被构造。
    2、代理模式主要用于控制对象的访问;装饰者模式则是用于为对象动态得添加功能方法。
    3、代理模式在代理类中创建对象实例;装饰者模式则是将构件对象作为参数传递给装饰者的构造器。

  • 桥接模式

    桥接模式属于结构型模式,旨在将抽象部分与实现部分进行分离。主要思想有两个方面:一、强调“包含”代替“继承”的观点;二、利用反射技术,在抽象事物类的构造函数中,用类名的“String类型”代替对象类型。该模型由于本人接触不多,所以具体的过程在此处不细讲,留到之后的博文中。

  • 单例模式

单例模式在面试中被问到的比较多,所以此处详细介绍下。单例模式的宗旨是确保某个类至始至终只有一个实例对象,属于创建型模式。关于其写法有多种方式,大体可以分成懒汉式、饱汉式、枚举式、静态内部类式。

1、懒汉式
主要思想就是需要调用时再对某个类进行实例化,而不是编译时就完成实例操作。

//懒汉式(非线程安全)
//只能通过getInstance()对Singleton进行实例化
public class Singleton{
   private Singleton(){}  //私有构造函数,防止类被随意实例化
   private static Singleton single=null;
   public static Singleton getInstance(){
        if(single==null)
            single=new Singleton();
         return single;
   }    
} 
//很明显上述方法无法工作于多线程环境下
//懒汉式(线程安全)
//只需要将上述方法中的getInstance()方法做简单修改,加synchronized 修饰词
public static synchronized Singleton getInstance(){
   if(single==null)
     single=new Singleton();
    return single;
}
//此方法的缺点是效率低,由于每次调用对象都要加锁,使得其他线程被阻塞。
//懒汉式(双重校验锁)
//加synchronized、volatile 修饰词
public class Singleton{
   private Singleton(){}  //私有构造函数,防止类被随意实例化
   private volatile static Singleton single;
   public static Singleton getInstance(){
        if(single==null){
           synochronized(Singleton.this){
              if(single==null)
                 single=new Singleton();
           }
        }
        return single;
   }    
} 
//用volatile修饰Singleton对象是为了防止在getInstance()中指令重排。

2、饱汉式
主要思想是一开始就将类实例化,以供调用时使用。

//饱汉式(原始版本)
public class Singleton{
    private Singleton(){}
    private static Singleton single=new Singleton();

    public static Singleton getInstance(){
        return single; 
    }
}
//此方法single在类装载时就会被实例化
//饱汉式(改编版本)
public class Singleton{
    private Singleton(){}
    private Singleton single=null;
    static{
       single=new Singleton();
    }

    public static Singleton getInstance(){
        return this.single; 
    }
}
//实际上该方法类似于上一种,对象在类加载时便已经被实例化。

3、枚举式

枚举式是有Effective Java的作者提出的可避免多线程同步问题。但个人感觉这也失去了类原本的类型,变为了枚举类型。主要代码如下:

//枚举式
public enum Singleton{
  SINGLE;
  public void method(){}
}

4、静态内部类式

静态内部类方法主要是建立一个内部类来实例化类对象。只有在调用getInstance()时,才会显性加载内部类,并实例化Singleton对象。

//静态内部类方式
public class Singleton{
  private Singleton(){}
  private static class SingletonHolder{
      private static final Singleton SINGLE=new Singleton();
  }
  public static Singleton getInstance(){
    return SingletonHolder.SINGLE;
 } 
}

Ps: 博主是新手,对一些点的理解有限,如果亲爱的读者你有任何建议请不吝赐教,谢啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值