Java笔记(四)常用基础类

常用基础类

一)String

String内部用一个字符数组表示字符串:

private final char value[];

注意:Java9对此做了优化,采用byte[],如果字符都是ASCII字符,它就可以使用一个字节表示一个字符。

String有的两个构造方法,可以根据参数创建String变量:

//根据参数创建一个新的char数组,
//并使用新的char数组实现String的大部分方法
public String(char value[])
public String(char value[], int offset, int count)

编码相关:

返回字符串给定编码的字节表示:

public byte[] getBytes() //使用系统默认编码
public byte[] getBytes(String charsetName)
public byte[] getBytes(Charset charset)

根据字节和编码创建字符串的构造方法:

public String(byte bytes[], int offset, int length, String charsetName)
public String(byte bytes[], Charset charset)

不可变的String:

String定义为不可变的程序可以更简单、安全、容易理解。

常量字符串:

Java中的字符串常量就是String对象,可以调用String的方法。

在内存中它们被放入一个共享的地方,称为字符串常量池,它保存

所有的常量字符串,每个常量只保存一份,被所有使用者共享。

当通过常量的形式使用字符串的时候,就是使用常量池中的String类型对象。

        String a = "apple";
        String b = "apple";
        System.out.println(a == b); //true

        //上面的代码实际上相当于
        String c = new String(new char[]{'a', 'p', 'p', 'l', 'e'});
        String e = c;
        String d = c;
        //实际上只有一个String对象,三个变量都指向这个变量
        System.out.println(e == d); //true

        //注意:如果不是通过常量赋值,而是通过new创建,就会不一样
        String f = new String("apple");
        String g = new String("apple");
        System.out.println(f == g);//false 此时比较的是引用地址当然false

因为,String的构造方法:

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

hash是String的一个实例变量,表示缓存的hashCode值

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

 

String与正则:

public String[] split(String regex)
public boolean matches(String regex)
public String replaceFirst(String regex, String replacement)
public String replaceAll(String regex, String replacement)

二)StringBuilder

StringBuffer是线程安全的而StringBuilder不是,但是注意线程安全是有成本的。

1.基本实现原理

StringBuilder父类AbstractStringBuilder封装了一个字符数组:

char value[];

与String不同的是,它不是final的可以被修改,它有一个实例变量表示数组中已经使用的字符个数:

int count;

抽象类的构造方法:

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

StringBuilder的构造方法:

public StringBuilder() {
    super(16);
}
    public StringBuilder(int capacity) {
        super(capacity);
    }
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

append方法:

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
       //确保数组的长度足以容纳新添加的字符
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    private void ensureCapacityInternal(int minimumCapacity) {
        // 如果数组长度小于需要的长度,则进行扩展
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

注意:为了避免每次调用append方法就进行内存分配,指数扩展策略。

在不知道最终需要多长(不知道下次append需要多长)的情况下

,指数扩展策略广泛应用于各种计算机内存分配相关的计算中。

toString方法:

     public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

String的 + 和 += 运算符:

当String使用这两个运算符的时候,Java编译器一般会生成StringBuilder,

并调用append方法。例如:

String hello = "hello";
hello+=",world";
System.out.println(hello);
//编译器会转换为:
StringBuilder hello = new StringBuilder("hello");
hello.append(",world");
System.out.println(hello.toString());

既然编译器调用了StringBuilder的append方法,那还有什么必要直接使用StringBuilder呢?

因为在复杂的情况下编译器可能会生成过多的StringBuilder。

        String hello = "hello";
        for(int i=0;i<3;i++){
            hello+=",world";
        }
        System.out.println(hello);

        //编译器转换:
        /*String hello = "hello";
        for(int i=0;i<3;i++){
            StringBuilder sb = new StringBuilder(hello);
            sb.append(",world");
            hello = sb.toString();
        }
        System.out.println(hello);*/

三)Arrays

数组的优点是比容器的效率更高。

Arrays类包含包含对数组操作的静态方法。

1.toString

输出一个数组的字符串形式,有9个重载方法:

public static String toString(int[] a)
public static String toString(Object[] a)

2.排序

sort()方法:

//每总基本类型都有
public static void sort(int[] a)
public static void sort(double[] a)
//引用类型:但对象要求实现Comparable接口
public static void sort(Object[] a)
public static void sort(Object[] a, int fromIndex, int toIndex)
//或者
public static <T> void sort(T[] a, Comparator<? super T> c)
public static <T> void sort(T[] a, int fromIndex, int toIndex,Comparator<? super T> c)

3.查找

可在已排序的数组中进行二分查找(从中间开始找......):

public static int binarySearch(int[] a, int key)
public static int binarySearch(int[] a, int fromIndex, int toIndex, int key)
public static int binarySearch(Object[] a, Object key)
//自定义比较器,需要和排序时的比较器一致
public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c)

5.多维数组

本质:数组中的每个元素是另一个数组,层层嵌套。

创建多维数组:

int[][][] arr = new int[10][10][10];
int[][] arr = new int[2][];
arr[0] = new int[3];
arr[1] = new int[5];

Arrays数组中的方法都有针对多维数组的方法:

public static String deepToString(Object[] a)
public static boolean deepEquals(Object[] a1, Object[] a2)
public static int deepHashCode(Object a[])

四)日期和时间

由于旧的API被广泛使用,这里介绍Java8之前的日期和时间API

1.基本概念

1)时区

全球一共24个时区,英国格林尼治是0时区,北京是东八区,

0时区也被称为GMT+0时间,GMT是格林尼治标准时间,北京时间就是GMT+8:00。

2)时刻和纪元

所有计算机内部都用一个整数表示时刻,这个整数是距离格林尼治标准时间1970年1月1日0时0分

的毫秒数。这个时间也被称为Epoch Time(纪元时).这个整数表示的是一个时刻,与时区无关,世界

上各个地方都是同一时刻,但各个地区对这一时刻的解读(如年月日时分)可能是不一样的。

2.时间和日期API(Java8以前的)

Date: 表示时刻,即绝对时间,与年月日无关。

Calender:表示年历,Calender是一个抽象类,其中表示公历的子类为Gregorian-Calender。

DateForm:表示格式化,能够将日期和时间与字符串间进行转换,该类也是一个抽象类,最

常用的子类是SimpleDateForm。

TimeZone:表示时区。

Locale:表示国家(地区)和语言。

1)Date

 内部用fast变量表示时刻:

private transient long fastTime;//表示距离纪元时的毫秒数

有两个构造方法:

    public Date() {
        this(System.currentTimeMillis());
    }
    public Date(long date) {
        fastTime = date;
    }

Date中的大部分方法都过时,其中没有过时的有:

public long getTime()//返回毫秒数
public boolean equals(Object obj)//主要是比较内部毫秒数是否相同
public int compareTo(Date anotherDate)//与其他毫秒数进行比较
public boolean before(Date when)
public boolean after(Date when)
public int hashCode()

2)TimeZone

        TimeZone defaultTimeZon = TimeZone.getDefault();
        System.out.println(defaultTimeZon.getID()); //Asia/Shanghai

        TimeZone usTimeZone = TimeZone.getTimeZone("US/Eastern");
        System.out.println(usTimeZone.getID()); //US/Eastern
        TimeZone unknownTimeZone = TimeZone.getTimeZone("GMT+09:00");
        System.out.println(unknownTimeZone.getID()); //GMT+09:00

3)Locale

它主要有两个参数:一个是国家或地区,一个是语言

Locale类中定义了一些静态变量,表示常见的Locale,例如:

        System.out.println(Locale.US); //en_US
        System.out.println(Locale.ENGLISH); //en
        System.out.println(Locale.TAIWAN); //zh_TW
        System.out.println(Locale.CHINESE); //zh
        System.out.println(Locale.CHINA); //zh_CN
        System.out.println(Locale.SIMPLIFIED_CHINESE); //zh_CN

4)Calendar

该类是日期和时间操作的主要类,它表示与TimeZome和Locale相关的日历信息。

Calendar内部也有一个表示时刻的毫秒数:

protected long time;

还有一个数组表示日历中各个字段的值

protected int fields[];//这个数组长度为17,保存一个日期中各个字段的值

Calendar定义了一些静态变量,表示这些字段如:

Calendar.YEAR表示年

Calender是抽象类,不能直接创建实例:

public static Calendar getInstance()
public static Calendar getInstance(TimeZone zone, Locale aLocale)
//根据提供的TimeZone和Locale创建Calender子类对象

内部,Calendar会将表示时刻的毫秒数,按照TimeZone和Locale对应的年历,

计算各个日历字段的值,存放在fields数组中。

        Calendar calendar = Calendar.getInstance(Locale.CHINA);
        System.out.println("YEAR: " + calendar.get(Calendar.YEAR)); //2018
        System.out.println("MONTH: " + calendar.get(Calendar.MONTH)); //10
        System.out.println("DAY: " + calendar.get(Calendar.DAY_OF_MONTH)); //13
        System.out.println("Day of week: " + calendar.get(Calendar.DAY_OF_WEEK)); //3

Calendar支持Date或者毫秒数设置时间:

public final void setTime(Date date)
public void setTimeInMillis(long millis)

也可根据年月日设置:

public final void set(int year, int month, int date)
public final void set(int year, int month, int date,int hourOfDay, int minute, int second)
public void set(int field, int value)

直接增加或者减少时间:

public void add(int field, int amount)//amount正数表示增加,负数表示减少

Calendar之间也可以进行比较:

public boolean equals(Object obj)
public int compareTo(Calendar anotherCalendar)
public boolean after(Object when)
public boolean before(Object when)

5)DateForm

两个主要方法:

public final String format(Date date)
public Date parse(String source)

DateForm定义了4个静态变量,表示4种风格,不同的风格输出详尽程度不一样。

DateFrom是抽象类,用工厂方法创建实例:

public final static DateFormat getDateTimeInstance()
public final static DateFormat getDateInstance()
public final static DateFormat getTimeInstance()

 重载方法接收日期及时间风格和Locale为参数:

DateFormat getDateTimeInstance(int dateStyle, int timeStyle)
DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
        Calendar calendar = Calendar.getInstance();
        calendar.set(2018, 11, 15, 8, 22);
        System.out.println(DateFormat.getDateTimeInstance(DateFormat.LONG,
                DateFormat.SHORT, Locale.CHINESE).format(calendar.getTime())); //2018年12月15日 上午8:22

DateForm设置TimeZone:

public void setTimeZone(TimeZone zone)

6)SimpleDateForm

该类是DateForm的子类,与父类的主要不同是,它可以接收一个自定义模式作为参数

 

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日  E HH时mm分ss秒");
        System.out.println("Now is : " + format.format(calendar.getTime()));
        //Now is : 2018年11月14日  星期三 09时31分29秒

 

其中E表示星期,hh也可表示小时,不过是12小时制的。

3.Java8之前时间和日期API的局限性

1)Date种过时的方法

2)Calendar操作繁琐

API设计失败,要写很多代码,难以计算两个日期之间,比如有几天几个月等等。

3)DateForm的线程安全问题

五)随机

1.Math.random

该静态方法生成一个0到1的随机数(duoble类型),但不包括1和0.

        for (int i = 2; i >= 0; i--) {
            System.out.println(Math.random());
            /*0.2031695860558771
            0.7189188014572521
            0.686824037093907 */ //每次随机都不一样哦      
        }

该方法的实现:

 

public static double random() {
    Random rnd = randomNumberGenerator;
    if (rnd == null) rnd = initRNG();
    return rnd.nextDouble();
}

 

2.Random类

该类提供了丰富的随机方法:

        Random random = new Random();
        System.out.println(random.nextInt());
        System.out.println(random.nextInt(100));
        //-809244560
        //87

nextInt()产生一个随机的int,可能为正数,也可能为负数,nextInt(100)产生随机int,范围为0-100

除了默认的构造方法还有一个接收long类型的种子参数的构造方法:

public Random(long seed)

种子决定了随机产生的序列,种子相同,产生的随机数序列就是相同的。

        Random random = new Random(20160824);
        for (int i = 0; i < 5; i++) {
            System.out.println(random.nextInt(100));
        }

结果为:69  13  13  94  50

这个程序无论执行多少遍,在哪儿执行,结果都是一样的。

在Random类中还可用synchronized public void setSeed(long seed) 方法指定种子。

指定种子是为了实现可重复随机。

 

如果Random使用默认的构造函数,不传入种子,他会自动生成一个种子,

这个种子数是真正的随机数。

3.随机数的应用

1)随机密码

 

    //生成6位数字随机密码
    public static String randomPassWord() {
        char[] chars = new char[6];
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            chars[i] = (char)(random.nextInt(10) + '0');
        }
        return new String(chars);
    }

 

生成8位随机验证码,可能包含数字、特殊字符、字母

    private static final String SPECIAL_CHARS = "!@#$%^&*-=_+";
    private static char nextChar(Random rnd) {
        switch (rnd.nextInt(4)) {
            case 0:
                return (char) ('a' + rnd.nextInt(26));
            case 1:
                return (char) ('A' + rnd.nextInt(26));
            case 2:
                return (char) ('0' + rnd.nextInt(10));
            default:
                return SPECIAL_CHARS.charAt(rnd.nextInt(SPECIAL_CHARS.length()));
        }
    }
    //生成8位随机密码
    public static String randomPassWord() {
        char[] chars = new char[8];
        Random random = new Random();
        for (int i = 0; i < 8; i++) {
            chars[i] = nextChar(random);
        }
        return new String(chars);
    }
posted @ 2018-11-14 13:56  Shadowplay  阅读(689)  评论(0编辑  收藏  举报