JAVA基础03-Object类,常用类,基本的数据结构, Collection常用体系,泛型-泛型通配符

1.object类

1.概述

java.lang.object类是java语言中的根类,即所有类的超类(基类)他描述的所有的方法子类都可以使用,在对象实例化的时候最终找到的类就是object

如果一个类没有特别指定父类,那么默认则继承自object类例如

public class Test01 /*extends object*/ {}

object 类当中包含方法有11个

  • public String tostring(); 返回该对象的字符串表示
  • public boolean equals(Object obj):指示其他某个实例,是否与此实例相等

2.toString方法

1.摘要

介绍:

toString方法返回该对象的字符串表示, 其实该字符串内容就是对象的类型+@+内存地址值.

由于toString方法返回的结果是内存地址, 我们经常按照对象的属性得到相应的字符串表现形式, 因此我们需要重写他.

2.覆盖重写

如果不希望使用toString方法的默认行为则可以对它进行重写 , 例:

public class Person {
    String name;
    int    age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3.equals方法

1.摘要

equals() 方法, 判断其他某个对象是否和子对象相等.

调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的

  1. 默认地址比较

如果没有覆盖重写equals 方法, 那么Object类中默认进行== 运算符的对象进行比较, 只要不是同一个对象结果必为false.

  1. 对象内容比较

如果希望进行对象的内容比较, 所有或者指定的部分成员变量相同就判定两个对象相同, 则可以重写equals方法

public class Person {
    String name;
    int    age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }
}

4.Object类

在jdk7 中添加了一个Object工具类, 他提供了一些方法来操作对象, 他由一些静态的实例方法组成, 这些方法是null-save(空指针安全的)或者null-tolerant(容忍空指针的),用于计算对象的hashcode,返回对象的字符串表示形式, 计较两个对象.

  • public static boolean equals(Object o , Object b ) 判断两个对象是否相等,

  • 源码

      public static boolean equals(Object a, Object b) {
          return (a == b) || (a != null && a.equals(b));
      }
    

2.常用类

1.System类

java.lang.System类中提供了大量的静态方法,可以获取与系统香瓜你的信息或系统级操作, 在System类的API文档中常用的方法有:

  • public static long currentTimeMillis():返回以毫秒为单位的当前时间.
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):将数组中指定的数据拷贝到另一个数组中

1.1,currentTimeMillis 方法

currentTimeMillis 方法就是获取当前系统时间与1970年01月01日00:00点之间的毫秒差值

public static void main(String[] args) {
        long l = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
        long l1 = System.currentTimeMillis();
        System.out.println("耗时:"+(l1-l));  //耗时:256 毫秒
    }

1.2.arraycopy方法

将数组中的指定数据拷贝到另一个数组中,

数组的拷贝动作是系统级的, 性能很高,其中有五个参数:

参数名称 参数类型 参数含义
src Object 原数组
srcPos int 原数组索引其起始位置
dest Object 目标数组
destPos int 目标数组索引起始位置
length int 复制元素的个数

public static void main(String[] args) {
        int[] arr01 = {1,2,3,4,5};
        int[] arr02 = {6,7,8,9,10};
        System.arraycopy(arr01,0,arr02,0,2);//将arr01的索引为0出开始拷贝到arr02从索引为0出粘贴, 拷贝出的数量为2
        System.out.println(Arrays.toString(arr02));//[1, 2, 8, 9, 10]
    }

2.StringBuilder类

2.1 字符串拼接问题

由于strign类的对象内容是不可变的,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象:例如

public static void main(String[] args) {
        String str01 = "Hello";
        System.out.println(str01.hashCode());//69609650
        str01 += "world";
        System.out.println(str01.hashCode());//468881952
        System.out.println(str01);//Helloworld
    }

在api中对string类的描述是:java中所有的自付出那蚊子(例如:“abc”)都被时限为此实例.字符后才能不变,他们的值在创建后不能被更改,字符串缓冲区支持可变字符串, 因为String对象是不可能变得 ,他们可以被共享.

上面代码可以用下图表示:

由图片可知, 如果对字符串进行拼接操作,每次拼接都会在内存中开辟空间,创建一个新的string对象, 既耗时又消耗空间,为了解决这种问题, 可以使用java.lang.StringBuilder类

2.2 StringBuilder

javaAPI:StringBulider又称为可变字符序列, 他是一个类似String的字符串缓冲区, 通过某些方法调用可以改变该序列的长度和内容,

StringBuilder相当于一个容器, 可以装很多的字符串, 并且能够对字符串进行相应的操作,它的内部拥有一个数组来存放字符字符串内容, 进行字符串拼接时, 直接在数组中插入新的内容,StringBuilder会自动维护数组的扩容,

2.2.1构造方法

常用的构造方法有两个:

  • public StringBuilder():构造一个空的StirngBuilder容器.
  • public StringBulider(String str):构造一个空的StirngBuilder容器,并将字符串添加进去

2.2.2 常用方法

StringBuilder常用方法有两个:

  • public StringBuilder append():添加任意类型数据的字符串形式,并返回当前对象自身.

  • public StringBulider toString(): 将当前StringBuilder 对象转换为String对象.

    public static void main(String[] args) {
    //创建对象
    StringBuilder stringBuilder = new StringBuilder();
    //添加任意类型数据
    stringBuilder.append(“hello”);
    System.out.println(stringBuilder.hashCode());//1163157884
    stringBuilder.append(“world”);
    System.out.println(stringBuilder.hashCode());//1163157884
    stringBuilder.append(1);
    stringBuilder.append(true);
    stringBuilder.append(1.1f);
    System.out.println(stringBuilder.toString());
    //上面写法可以写成
    System.out.println(stringBuilder.append(“hello”).append(“world”).append(1).append(true).append(1.1f).toString());
    }

3.包装类

3.1 概述

java的数据类型基本有两种, 基本类型与引用类型, 基本类型的话效率比较高 , 但是我们一般会使用引用类型, 这样我们创建对象可以进行更多的操作, 我们也可以把基本类型转换成引用类型, 就可以使用基本类型的包装类;

基本类型 对应包装类 java.lang包中
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

3.2 拆箱与装箱

基本类型与引用类型之间转换的过程,我们称之为"装箱"与"拆箱"

  • 装箱 :从基本类型转换为引用类型
  • 拆箱 :从引用类型转换为基本类型.

基本类型->包装类;

Integer i  = new Integer(4);//使用构造器方法
Integer i1 = Integer.valueOf(4);//使用包装类的valueOf() 方法

包装类->基本类型

int i2 = i.intvalue();

因为我们平时经常基本类型与包装类之间转换 ,从 jkd1.5开始, 基本类型与包装类之间不用手动进行拆装箱, 可以自动完成

Integer i = 8 ;
int a = i + 8;

4.基本类型与字符转之间的转换

4.1 基本类型转换为引用类型;

有三种方式:

  1. 基本类型直接+"" 例如: 3+"";
  2. 调用类的串转换方法:X.toString();
  3. 使用String的方法:String.valueOf(X);

4.2 String 类型转换为基本类型

除了Character 类之外, 其他所有的包装类都具有parseXxx 静态方法可以将字符串转换为基本数据类型;

  • public static byte parseByte(String s ) :将字符串转换为对应的byte类型
  • public static short parseShort(String s ):将字符串转换为对应的 shord 类型
  • public static int parseInt(String s ):将字符串转换为对应的int 类型
  • public static long parseLong(String s ):将字符串转换为对应的 long 类型
  • public static float parseFloat(Stirng s):将字符串转换为对应的 float 类型
  • public static double parseDouble (String s);将字符串转换为对应的 double 类型
  • public static boolean parseDoolean (String s ):将字符串转换为对应的 boolean 类型
    //举个例子:
    public static void main(String[] args) {
    String s = “true00000”;
    boolean b = Boolean.parseBoolean(s);
    System.out.println(b);//false
    if (b) {
    System.out.println(“222”);
    }
    String s1 = “12324”;
    int i = Integer.parseInt(s1);
    System.out.println(i);//12324
    }

如果无法正确转换 会抛出

java.lang.NumberFormatException.异常

5.日期时间类

5.1 Date类

5.1.1 概述

java.util.Date 类 表示指定的瞬间,精确到毫秒

jdk文档 介绍:一个大约一毫秒值的薄包装,允许JDBC将其标识为SQL DATE值。 毫秒值表示1970年1月1日00:00:00.000 GMT之后的毫秒数。

为了符合SQL DATE ,由java.sql.Date实例包装的毫秒值必须通过在实例关联的特定时区中将小时,分钟,秒和毫秒设置为零来“归一化”。

构造方法 : 例

Date(int year, int month, int day) 已弃用 而是使用构造Date(long date)
Date(long date) 使用给定的毫秒时间值构造一个 Date对象。

Date 拥有多个构造方法, 只是大部分已经过时, 但是其中有未过时的函数可以吧毫秒值转换成日期

  • public Date():分配Date对象并初始化此对象, 以表示分配他的时间(精确到毫秒)
  • public Date(long date):分配Date对象并初始化此对象, 以表示自从标准时间(成为"历元(epoch)", 即使1970年01月01日00:00:00 GMT) 以来的毫秒数.

tips: 由于中国处于东八区(GMT+08:00)是比世界协调时间/格林尼治时间(GMT)快8小时的时区,当格林尼治标准时间为0:00时,东八区的标准时间为08:00。

使用无参构造器,可以自动设置当前系统时间的毫秒时刻; 指定long类型的构造参数, 可以定义毫秒时刻,例如:

public static void main(String[] args) {
        System.out.println(new Date());//Mon Jun 29 16:06:19 CST 2020
        System.out.println(new Date(0L));//Thu Jan 01 08:00:00 CST 1970  
    }//在使用println方法时,会自动调用Date类中的toString方法。Date类对Object类中的toString方法进行
//了覆盖重写,所以结果为指定格式的字符串。

5.1.2 常用方法

Date类中有很多方法已经过时, 常用的方法有:

  • public long getTime(): 把日期对象转换成对象的时间毫秒值

5.2 DateFormat类

java.text.DateFormat: 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。

  • 格式化: 按照指定的格式, 从Date对象转换为String对象.
  • 解析: 按照指定的格式,从String 转换成Date对象.

5.2.1 构造方法

由于DataFormat 为抽象类 , 不能被实例化, 所以我们经常使用他的子类,java.text.SimpleDateFormat:这个类需要一个格式来指定格式化或解析的标准, 构造方法为:

  • public SimpleDateFormat(String pattern):用于给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat.参数patterm 是一个字符串, 代表日期时间的定义格式;

5.2.2 格式规则

表示字母(区分大小写) 含义 例子
Y
y 年 1997;07
M 月 7月;8月
m 分
D
d 日
H 时
h
S
s 秒
W
w
K
k
Z
z
F
E
G 文本 AD
X
L
a
u

具体介绍参考JDK文档

5.2.3 常用方法

DateFormat类的常用方法有:

  • public String format(Date date):将Date对象格式化为字符串;

  • public Date parse(String source):将字符串转换为Date 对象;

    public static void main(String[] args) throws ParseException {
    //将当前日期转换为字符串
    SimpleDateFormat sl = new SimpleDateFormat(“yyyy-MM-dd”);
    String format = sl.format(new Date());
    System.out.println(format);
    //将当前时间转换为日期类型
    SimpleDateFormat sl1 = new SimpleDateFormat(“yyyyMMddhhmmss");
    Date parse = sl1.parse("1997
    1130231032”);
    //将日期类型转化为Stirng
    String format1 = sl1.format(parse);
    System.out.println(format1);
    }

5.3Calender类

5.3.1概念

java.util.Calendar 是日历类, 在Date之后出现, 替换掉许多Date的方法, 该类将所有可能用到的时间信息封装为静态成员变量,日历类就是方便获取各个时间属性的.

5.3.2 获取方式

Calendar 为抽象类, 不能直接创建, 而是通过静态方法创建,返回子类对象

Calendar 静态方法

  • public static Calendar getInstance():使用默认失去和语言环境获得一个日历

    Calendar calendar = Calendar.getInstance();

5.3.3 常用方法

常用的方法有:

  • public int get(int field):返回给定日期字段的值;
  • public void set (int field , int value):将制定的日期字段设置为给定值.
  • public abstract void add(int field , int amount):根据日历的规则, 为给定的日历字段添加或减去指定的时间量.
  • public Date getTime():返回一个标识此Calendar 时间值(从历元到现在的毫秒的偏移量)的Date对象.

Calendar类中提供很多成员常量, 代表给定的日期字段:

常用:

字段值 含义
YEAR 年
MONTH 月(从0开始,可以+1使用)
DAY_OF_MONTH 用中的第几天(几号)
HOUR 时(12小时)
HOUR_OF_DAY 时(24小时)
MINUTE 分
SECOND 秒
DAY_OF_WEEK 周中的天(周几,周日为1, 可以用-1使用)

JDK提供:

Modifier and Type Field and Description
static int ALL_STYLES getDisplayNames的样式说明符, 表示所有样式的名称,如“1月”和“1月”。
static int AM AM_PM字段的值表示从午夜到中午之前的一天中的一段时间。
static int AM_PM 对于现场数 get和 set指示是否 HOUR是前或中午之后。
static int APRIL MONTH字段的价值 指示了格里高利和朱利安日历中的第四个月。
protected boolean areFieldsSet 如果 fields[]与当前设置的时间同步,则为真。
static int AUGUST MONTH领域的价值 指示了公历和朱利安日历中的第八个月。
static int DATE get和 set字段编号表示该月的日期。
static int DAY_OF_MONTH get字段编号和 set本月的日期。
static int DAY_OF_WEEK get字段编号和 set表示一周中的日期。
static int DAY_OF_WEEK_IN_MONTH get字段编号和 set当月的 set几的序号。
static int DAY_OF_YEAR get和 set字段编号, set本年度的日数。
static int DECEMBER MONTH字段的值表示公历和朱利安日历中的第十二个月。
static int DST_OFFSET get和 set字段编号 get夏令时偏移量(以毫秒为单位)。
static int ERA get和 set字段号表示时代,例如在儒略历中的AD或BC。
static int FEBRUARY MONTH字段的价值表示今年第二个月在公历和朱利安日历。
static int FIELD_COUNT get和 set的不同字段的数量。
protected int[] fields 该日历的当前设置时间的日历字段值。
static int FRIDAY DAY_OF_WEEK字段的值表示周五。
static int HOUR get和 set字段编号, get上午或下午的小时。
static int HOUR_OF_DAY get字段编号和 set当天的小时数。
protected boolean[] isSet 说明是否设置日历的指定日历字段的标志。
protected boolean isTimeSet 如果那么那么 time的值是有效的。
static int JANUARY MONTH字段的价值表示今年首次在公历和朱利安日历。
static int JULY MONTH字段的值代表了 公历和朱利安日历中的第七个月。
static int JUNE MONTH字段的价值 指示了公历和朱利安日历中的第六个月。
static int LONG getDisplayName和 getDisplayNames相当于 LONG_FORMAT的样式说明 符 。
static int LONG_FORMAT getDisplayName和 getDisplayNames的样式说明 符 , 表示用于格式的长名称。
static int LONG_STANDALONE 一个 getDisplayName和 getDisplayNames的样式说明 符 , 表示一个独立使用的长名称,例如月份名称作为日历头。
static int MARCH MONTH字段的值代表了 公历和朱利安日历中的第三个月。
static int MAY MONTH领域的价值 指示了公历和朱利安日历中的第五个月。
static int MILLISECOND get和 set字段号表示 get内的 set数。
static int MINUTE get和 set字段编号表示小时内的分钟。
static int MONDAY DAY_OF_WEEK字段的值表示星期一。
static int MONTH get和 set字段号表示月份。
static int NARROW_FORMAT getDisplayName和 getDisplayNames的样式说明 符 , 表示用于格式的窄名称。
static int NARROW_STANDALONE getDisplayName和 getDisplayNames的样式说明 符 独立地表示一个狭义的名称。
static int NOVEMBER MONTH领域的价值 指示了公历和朱利安日历中的第十一个月。
static int OCTOBER MONTH字段的价值表示在公历和朱利安日历中的一年中的第十个月。
static int PM AM_PM字段的值表示从中午到午夜之前的一天中的一段时间。
static int SATURDAY DAY_OF_WEEK字段的值表示星期六。
static int SECOND get和 set字段编号表示分钟内的第二个。
static int SEPTEMBER MONTH字段的值代表了 公历和朱利安日历中的第九个月。
static int SHORT getDisplayName和 getDisplayNames的样式说明 符 , 相当于 SHORT_FORMAT 。
static int SHORT_FORMAT getDisplayName和 getDisplayNames的样式说明 符 , 表示用于格式的短名称。
static int SHORT_STANDALONE 一个用于 getDisplayName和 getDisplayNames的样式说明 符 , 表示一个简单的名称,例如一个月缩写作为日历头。
static int SUNDAY DAY_OF_WEEK字段的值表示星期天。
static int THURSDAY DAY_OF_WEEK字段的值表示星期四。
protected long time 这个日历的当前设定时间,以1970年1月1日,格林尼治标准时间0:00:00之后的毫秒表示。
static int TUESDAY DAY_OF_WEEK字段的值表示周二。
static int UNDECIMBER MONTH字段的值表示一年的第十三个月。
static int WEDNESDAY DAY_OF_WEEK字段的值表示周三。
static int WEEK_OF_MONTH get和 set字段编号, set当月的周数。
static int WEEK_OF_YEAR get和 set字段编号, set本年度的周数。
static int YEAR get现场编号和 set表示年份。
static int ZONE_OFFSET get和 set字段编号, get GMT以毫秒为 get的原始偏移量。

public static void main(String[] args) {
        //获取Calendar对象
        Calendar cal = Calendar.getInstance();
        //获取年
        int i = cal.get(Calendar.YEAR);
        System.out.println(i);//2020
        //设置年
        cal.set(Calendar.YEAR,2023);
        int i1 = cal.get(Calendar.YEAR);
        System.out.println(i1);//2023
        //将年份修改为2003年
        cal.add(Calendar.YEAR,-20);
        int i2 = cal.get(Calendar.YEAR);
        System.out.println(i2);//2003
        SimpleDateFormat sl = new SimpleDateFormat("yyyy-MM-dd");
        String format = sl.format(cal.getTime());
        System.out.println(format);//2003-06-29
    }

西方星期的开始为周日,中国为周一。

在Calendar类中,月份的表示是以0-11代表1-12月。

日期是有大小关系的,时间靠后,时间越大。

6.BigDecimal类

6.1BigDecimal 类概述

java.math.BigDecimal类: 他可以标识一个不可变的, 任意精度的有符号10进制数,同时他也提供了对这种数据运算的一些方法, 以及各种舍入模式, 它尤其可以避免基本数据类型进行浮点运算时损失精度的问题,

public static void main(String[] args) {
        float f1 = 0.1f;
        double f2 = 0.05;
        System.out.println(f1+f2);//0.1500000014901161
    }

6.2 BigDecimal 使用

  • 构造方法:
    1. public BigDecimal(Double d):将double 转换成BigDecimal [不建议]
    2. public BigDecimal(String s):将String 转换为BigDecimal [建议]
    3. BigDecimal(BigInteger val) 将 BigInteger转换成 BigDecimal 。
      BigDecimal(BigInteger unscaledVal, int scale) 将BigInteger的 BigInteger值和 int等级转换为 BigDecimal 。
      BigDecimal(BigInteger unscaledVal, int scale, MathContext mc) 将 BigInteger未缩放值和 int扩展转换为 BigDecimal ,根据上下文设置进行舍入。
      BigDecimal(BigInteger val, MathContext mc) 根据上下文设置将 BigInteger转换为 BigDecimal舍入。
      BigDecimal(char[] in) 一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造。
      BigDecimal(char[] in, int offset, int len) 一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造,同时允许一个子阵列被指定。
      BigDecimal(char[] in, int offset, int len, MathContext mc) 一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造,同时允许指定一个子阵列和用根据上下文设置进行舍入。
      BigDecimal(char[] in, MathContext mc) 一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受相同的字符序列作为 BigDecimal(String)构造与根据上下文设置进行舍入。
      BigDecimal(double val) 将 double转换为 BigDecimal ,这是 double的二进制浮点值的精确十进制表示。
      BigDecimal(double val, MathContext mc) 将 double转换为 BigDecimal ,根据上下文设置进行舍入。
      BigDecimal(int val) 将 int成 BigDecimal 。
      BigDecimal(int val, MathContext mc) 将 int转换为 BigDecimal ,根据上下文设置进行舍入。
      BigDecimal(long val) 将 long成 BigDecimal 。
      BigDecimal(long val, MathContext mc) 将 long转换为 BigDecimal ,根据上下文设置进行舍入。
      BigDecimal(String val) 将BigDecimal的字符串表示 BigDecimal转换为 BigDecimal 。
      BigDecimal(String val, MathContext mc) 一个转换的字符串表示 BigDecimal成 BigDecimal ,接受相同的字符串作为 BigDecimal(String)构造,利用根据上下文设置进行舍入。
  • 常用方法:
    1. public BigDecimal add(BigDecimal augend):与参数相加.
    2. public BigDecimal subract(BigDecimal subtranhend):与参数相减;
    3. public BigDecimal multiply(BigDecimal multiplicand):与参数相乘
    4. public BigDecimal divide(BigDecimal divisor): 与参数做除法, 除不尽会抛出异常
    5. `public BigDecimal divide(BigDecimal divisor, int scale ,int roundingMode):与参数做除法;
      • 参数1:除数;

      • 参数2:小数点后保留的位数;

      • 参数2:舍入模式;

        public static void main(String[] args) {
        BigDecimal bd1 = new BigDecimal(“1000”);
        BigDecimal bd2= new BigDecimal(“3”);
        System.out.println(bd1.add(bd2));//加法
        System.out.println(bd1.subtract(bd2));//减法
        System.out.println(bd1.multiply(bd2));//乘法
        //bd3/bd4 保留五位小数,2舍 3入
        BigDecimal divide = bd1.divide(bd2, 5, 2);
        System.out.println(divide);
        }

3.常见数据结构

3.1数据结构介绍

数据结构的含义即是 : 数据用什么样的方式排列在一起

3.2常见数据结构

常用的数据结构分为: 栈, 队列, 数组, 链表, 红黑树, 其余不一一列举

3.2.1 栈

  • 栈(stack): 又称为堆栈 ,他是运算受限的线性代表, 其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加,查找, 删除等操作.

采用该结构的集合, 对元素的存取有一下特点

  • 先进后出: 存进去的元素,要在他后面的元素依次取出后, 才能取出该元素.就好比子弹夹,往里面压入子弹, 射击的时候最上面的先击发;
  • 栈的入口和出口都是在栈的最顶端位置
  • 压栈 : 就是存入元素, 即把元素存储到栈的顶端位置, 栈中已有元素依次向栈底移动一个位置.
  • 弹栈: 就是取出元素,即把栈的顶端位置元素取出, 栈中已有元素依次向栈的顶端移动一个位置.

3.2.2 队列

  • 队列(queue) ,简称队, 他从堆栈一样, 也是一种运算受限的线性表, 其限制是仅允许在表的一端进行插入, 而在表的另一端进行删除.

采用该结构的集合,对元素的存取有如下特点:

  • 先进先出:存进去的元素,要在他的前面的元素依次取出后, 他能求出该元素; 比如显示生活中的隧道;
  • 队列的入口出口,各占一半,

3.2.3 数组

  • 数组(array) 是有序的元素序列, 数组是在内存中开辟一端连续的空间, 并在此空间存放元素; 酒店,每个房间都有房间号;可以根据房间号查看每个房间是否有客人;

采用该结构的集合,对元素有一下特点

  • 查找元素快: 通过索引,可以快速访问指定位置的元素
  • 增删元素比较慢 :
    • 指定索引位置增加元素: 需要创建一个新数组,将指定新元素存储在指定索引位置 , 再把原数组根据索引,复制到新数组对应的索引位置;

    • 指定索引位置删除元素: 需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置, 原数组中指定索引位置元素不复制到新数组,

3.2.4 链表

  • 链表(linked list) , 由一些列节点node(链表中每一个元素成为一个节点)组成, 节点可以在运行时动态生成,每个节点包括两部分:一个时存储数据元素的数据域, 另一个是存储下一个节点地址的指针域.链表结构有单向链表和双向链表,
    • 单向链表

采用该结构的集合,对元素的存储有一下特点:

  • 多个节点之间,通过地址进行连接, 例如自行车链条
  • 增删元素比较快

3.2.5 红黑树

  • 二叉树(binary tree) , 是每个节点不超过2的有序树(tree).

就是类似于我们现实中的数目结构, 只不过每个树枝最多有两个叉, 顶上的叫根节点, 两边被称为"左子树" 和"右子树"

红黑树本质就是一颗二叉查找树,将节点插入后, 该树仍然是一颗二叉查找树

红黑树可以通过红色节点和黑色节点尽可能的保证二叉树的平衡, 从而提高效率, 查找速度恐怖!

具体什么是红黑树, 是很大一门学问, 需要另起文章,一一讲解;

4.Collection-集合框架

  • 集合的概述:
    • 集合: 集合使java中提供的一种容器, 可以用来存储多个数据.
    • 集合与数组的区别:
      • 数组长度是固定的, 集合的长度是可变的.
      • 数组中存储的是同一种元素, 可以存储任意类型的数据, 集合中存储的都是引用数据类型,如果想存储基本数据类型需要存储对应的包装类
  • 集合的继承体系
    Collection:单列集合的根接口,用于存储一系列符合某种规则的元素, 他有两个重要的子接口, 分别是java.util.List和java.util.Set其中, List的特点是有序,元素可重复, Set的特点是元素不可重复.List接口的主要实现类有java.util.ArrayList和java.util.LinkedList,Set接口的主要实现类有java.util.HashSet和’LinedHashSet.

上面这张图包括我们经常使用到的集合框,并不是说仅仅只有这些

集合本身就是一个工具, 它存放在java.util包中,在Collection接口中定义了单例集合框架中共性的部分

  • Collection 常用的功能;
    Collection ,是所有单列集合的父接口, 因此在Collection 中定义了单列集合(List/Set)通用的一些方法, 这些方法可以用于操作所有的单列集合
    • public boolean add(E e): 把给定的对象添加到当前集合中.
    • public void clear():清空所有的元素.
    • public boolean remove(E e) : 把给定的对象在当前集合中删除.
    • public boolean contains(Object obj):判断当前集合中是否包含给定的对象.
    • public boolean isEmpty(): 判断当前集合是否为空.
    • public int size():返回当前集合中元素的个数.
    • public Object[] toArray: 把集合中的元素,存储到数组中
      常用的方法基本就以上这么多,还有很多方法可以查看java API 文档

Iterator 迭代器,增强for

  • Iterator 接口
    在程序开发中,经常需要遍历集合中所有元素, 针对这种需求,JDK专门提供了一个接口
    java.util.Iterator
    想要遍历Collection集合, 那么就要获取该集合迭代器完成迭代操作,
    • pubic Iterator iterator():获取集合对应的迭代器, 用来遍历集合中的元素的
    • 迭代 : 即Collection 集合元素通用的获取方式, 在取元素之前要先判断集合里面有没有元素, 如果有则取出来,继续判断有的话再取出, 直到把集合中元素全部取出来, 这种方式专业术语叫做迭代
  • Iterator 接口的常用方法如下:
    • public E next() :返回迭代的下一个元素.

    • public boolean hasNext(): 如果有下一个元素可以迭代, 返回true.

      public static void main(String[] args) {
      ArrayList arr = new ArrayList<>();
      arr.add(“test01”);
      arr.add(“test02”);
      arr.add(“test03”);
      arr.add(“test04”);
      //获取集合的迭代器
      Iterator iterator = arr.iterator();
      //判断是否有下一个元素
      while (iterator.hasNext()) {
      String next = iterator.next();
      System.out.print(next+"\t");//test01 test02 test03 test04
      }
      }

PS:

  1. 在进行集合元素获取时, 如果集合中已经没有元素了,还继续调用迭代器的next() 方法, 将会抛出异常:java.util.NoSuchElementException 没有元素异常.

  2. 在进行集合元素获取时, 如果天剑或移除集合中的元素, 将无法继续迭代,将会抛出
    ConcurrentModificationException并发修改异常

  • 迭代器的实现原理
    当遍历集合时,首先通过调用集合的iterator()方法获取迭代器对象, 然后使用hasNest()方法判断集合中是否有下一个元素,如果存在, 则调用next()方法取出,否则证明已经全部取出, 停止循环遍历
    Iterator 迭代器对象在遍历集合时, 内部采用指针的方式来跟踪集合中的元素;

    在调用iterator 的next()方法之前,迭代器的索引位于第一个元素之前, 不指向任何元素, 当第一次调用迭代器的next()方法之后,迭代器的索引为向后移动一位, 指向第一个元素并返回, 以此类推,知道hasNext()方法返回false; 终止遍历

  • 增强for
    也称为foreach 循环, 是jkd5之后, 出现的一个高级for循环 , 主要用来遍历元素,内部其实是一个Iterator迭代器, 所以在遍历的时候不能对元素进行增删操作;
    格式:
    for(元素类型 变量名 : 集合或者数组)
    {}
    它用于遍历Collection 和数组, 通常只进行遍历,不要在遍历过程中对集合中的元素进行增删操作;
    public static void main(String[] args) {
    int[] arr = {1, 3, 5, 7, 8};
    for (int a : arr) {
    System.out.print(a+"\t");//1 3 5 7 8
    }
    System.out.println();
    ArrayList arrayList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
    arrayList.add(i + “”);
    }
    for (String s : arrayList) {
    System.out.print(s+"\t");
    //0 1 2 3 4 5 6 7 8 9
    }
    }
    PS:
    foreach 循环必须有被遍历的目标, 目标只能是Collection 或者数组;
    仅仅可以遍历不能增删元素否则会抛出ConcurrentModifcationException并发异常

4.1 List

4.1.1 List介绍

java.util.List接口实现Collection接口, 是单列结合的一个重要分支, 一般将实现List接口的对象成为List集合, 在List集合中允许出现重复的元素, 所有的元素是以一种线性方式进行存储的, 在程序中可以通过索引来访问集合中指定的元素, List集合是有序的, 即元素的存入顺序和取出顺序一致

List接口特点:

  1. 他是一个元素存取有序的集合, 例如,存入的是 1,2,3 那么集合中元素的存储顺序就是1,2,3
  2. 他是一个带有索引的集合, 通过索引就可以精确的操作集合中的元素(与数组的索引一样).
    
  3. 集合中可以有重复的元素, 通过元素的equals 方法, 来比较是否为重复元素.
    

4.1.2 List 接口中常用方法

List作为Collection 子接口, 不但继承了Collection 接口中的全部方法, 而且还增加了一些根据元素索引来操作集合的特有方法:

  • public void add(int index ,E element):将指定的元素,添加到该集合中的指定位置上
  • public E get(int index):返回集合中指定位置的元素.
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素.
  • pubic E set(int index ,E element): 用指定的元素替换集合中指定位置的元素, 返回替换之前的元素

4.1.3 ArrayList 集合

java.util.ArrayList集合数据的存储结构是数组结构, 元素增删慢, 查找快, 因为日常操作都是查询数据,遍历数据,所以ArrayList 经常使用;

4.1.4 LinkedList 集合

java.util.LinkedList 集合数据存储结构是链表结构,方便元素添加,删除的集合;

LinkList 是一个双向链表结构

LindedList 提供了大量首尾操作的方法:

  • public void addFirst(E e): 将指定的元素插入此列表的开头
  • public void addLast(E e):将指定的元素插入此列表的结尾
  • public E getFirst():返回此列表开头第一个的元素
  • public E getLast():返回此列表结尾最后一个元素
  • public E removeFirst():移除并返回此列表的第一个元素
  • public E removeLast(): 移除并返回此列表的最后一个元素
  • public E pop():从此列表锁表示的堆栈处弹出一个元素
  • public void push(E e):将元素推入此列表所表示的堆栈.
  • public boolean isEmpty():如果列表不包含元素则返回true.

LinkedList是List的子类, List中的方法LinkedList 中都可以使用 , LinkedList 可以被当做堆栈, 队列来使用;

public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("str01");
        linkedList.add("str02");
        linkedList.add("str03");
        linkedList.add("str04");
        System.out.println(linkedList.toString());
        String first = linkedList.getFirst();
        System.out.println(first);
        String last = linkedList.getLast();
        System.out.println(last);
        String s = linkedList.removeFirst();
        System.out.println(s);
        String s1 = linkedList.removeLast();
        System.out.println(s1);
        System.out.println(linkedList.toString());
        linkedList.addFirst("str01");
        linkedList.addLast("str04");
        System.out.println("========================");
        System.out.println(linkedList.toString());
        boolean empty = linkedList.isEmpty();
        System.out.println(empty);
        System.out.println("========================");
        String pop = linkedList.pop();
        System.out.println(pop);
        System.out.println(linkedList.toString());
        linkedList.push("test999");
        System.out.println(linkedList.toString());
        /*
        [str01, str02, str03, str04]
        str01
        str04
        str01
        str04
        [str02, str03]
        ========================
        [str01, str02, str03, str04]
        false
        ========================
        str01
        [str02, str03, str04]
        [test999, str02, str03, str04]
         */
    }

4.2 Set

java.util.Set接口和java.util.List接口一样都是Collection的子接口与, 他与Collection 接口中的方法基本一致, 并没有对Collection 接口进行功能上的扩充 , 只是比Collection接口更加的严格, 与List接口不同的是, Set 接口中的元素是无序的, 并且都会以某种规则保证存入的元素不出现重复;Set集合有多个子类, 主要的两个为java.util.hashSet,java.util.LinkedHashSet这两个集合,

取出元素的方式可以采用: 迭代器, 增强for

4.2.1HashSet集合

java.util.HashSet 是Set接口的一个实现类, 他所存储的元素是不可重复的, 并且元素都是无序的(存取顺序无法保证)java.util.HashSet 底层是一个java.util.HashMap 支持,

HashSet 是根据对象的哈希值来确定元素在集合中的存储位置, 因此查找性能比较好, 保证元素唯一性的方式采用 hashcode 和equals方法

public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("str1");
        hashSet.add("str1");
        hashSet.add(new String("str1"));
        for (String s : hashSet) {
            System.out.println(s);
        }
        /*
        * str1
        * str11
        * */
    }//不同对象, 数据相同也不会储存

4.2.2 HashSet集合存储数据的结构(哈希表)

哈希表是什么?

在jkd1.8之前, 哈希表底层采用数组+链表来实现, 即使用数组处理冲突, 同一hash 值的链表都存储在一个数组里, 但是当位于一个桶中的元素较多, 即hash值相同的元素较多是,通过key 值依次查找效率比较低,在jdk1.8时哈希表存储数据采用数组+链表+红黑树实现,当链表长度超过阈值(8)时, 将链表转换为红黑树,来降低查找时间.

HashSet唯一性通过hashCode和equals 方法来决定的, 如果往集合中添加自定义对象, 要保证其唯一性,就必须重写hashCode和equals 方法简历属于当前对象的比较方式

4.2.3 HashSet 存储自定义类型元素

public class Student {
    String name ;
    String sex ;

    public Student(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) &&
                Objects.equals(sex, student.sex);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, sex);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}



public class Test_Student {
    public static void main(String[] args) {
        HashSet<Student> hashSet = new HashSet<>();
        hashSet.add(new Student("小红", "23"));
        hashSet.add(new Student("小红", "23"));
        hashSet.add(new Student("小红", "23"));
        hashSet.add(new Student("小红", "23"));
        for (Student student1 : hashSet) {
            System.out.println(student1);
            //Student{name='小红', sex='23'}
        }
    }
}

4.2.4 LinkedHashSet

HashSet保证元素的唯一性, 但是无法保证放入元素的顺序, 如果要保证顺序的话,在HashSet下面有一个子接口java.util.LinkedHashSet , 他是链表和哈希表组合的一种数据存储结构.

 public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<String>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        set.add("ddd");
        set.add("ddd");
        for (String s : set) {
            System.out.println(s);
        }
      /*aaa
        bbb
        ccc
        ddd*/
    }

4.3 Map

4.3.1 什么是Map

>现实生活中,我们每个人都有每个人的身份证号, 像这中一一对象的关系叫做映射, java提供了专门的集合类来存放这些具有映射关系的元素,`java.util.Map` 接口, `Map`和`Collection`存储数据的方式不同, Map是双列的集合结构
  • collection集合中元素是独立存在的
  • Map 集合中元素是成对出现的, 每个元素都由两个部分组成, 通过键可以找到对应的值
  • 'Collection 集合成为单列集合, Map集合称为多列集合(每个键都是唯一的,值可以重复).

4.3.2 Map的常用子类

  • HashMap<K,V> : 存储数据采用的哈希表结构, 元素的存取顺序不能保证一致 ,由于要保证键的唯一性,不可重复, 需要重写键的hashcode() 和equals() 方法,
  • LinkedHashMap: HashMap 下的子类, 存储数据采用哈希表+链表结构, 通过链表结构可以保证元素存取顺序一致, 通过哈希表结构可以保证键的唯一,不重复, 需要重写键的 hashcode() 和 equals()方法
  • Map 集合中都有两个泛型变量 <K,V> , 在定义时 ,两个泛型变量类型可以相同也可以不同, 一般都是这样定义Map<String,Object> 为了方便使用;

具体每个集合的底层以后一一研究 这里就不做赘述, 没和集合底层都是一门学问;

4.3.3 Map 的常用方法

Map 接口中定义了很多种方法, 比较常用的有:

  • public v put(k key ,v value): 把指定的键与指定的值添加到Map中 , 若Map中没有这个K则返回null, 如果有的话就返回原先的值,把新值插进去;

  • public v remove(Object key): 把指定的键所对应的键值在集合中删除, 返回被删除的值

  • public v get(Object key): 根据指定的键, 在Map 集合中获取对应的值

  • public Set keySet(): 获取Map集合中所有的键, 存储到Set 集合中.

  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

  • public boolean containKey(Object key) :判断该集合中是否有此键。

    public static void main(String[] args) {
    HashMap<String, Object> hashMap = new HashMap<>();
    hashMap.put(“1”, “test01”);
    hashMap.put(“2”, “test02”);
    hashMap.put(“3”, “test03”);
    hashMap.put(“4”, “test04”);
    System.out.println(hashMap);//{1=test01, 2=test02, 3=test03, 4=test04}
    Object remove = hashMap.remove(“1”);//test01
    System.out.println(remove);
    System.out.println(hashMap);//{2=test02, 3=test03, 4=test04}
    hashMap.put(“1”, “test01”);
    System.out.println(hashMap);//{1=test01, 2=test02, 3=test03, 4=test04}
    Object o = hashMap.get(“1”);
    System.out.println(o);//test01
    Set set = hashMap.keySet();
    System.out.println(set);//[1, 2, 3, 4]
    Set<Map.Entry<String, Object>> entries = hashMap.entrySet();
    System.out.println(entries);//[1=test01, 2=test02, 3=test03, 4=test04]
    boolean b = hashMap.containsKey(“1”);
    System.out.println(b);//true
    }

4.3.4 Map的遍历方式

Map的遍历, 因为键值唯一的,我们可以用keySet() 方法获取到所有的键的set集合,然后遍历set集合根据get()方法将key传进去, 取出值

public static void main(String[] args) {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("1", "test01");
        hashMap.put("2", "test02");
        hashMap.put("3", "test03");
        Set<String> set = hashMap.keySet();
        for (String s : set) {
            String ss = (String) hashMap.get(s);
            System.out.println(ss);
        }
        /*test01
          test02
          test03*/
    }

还有一种就是用entrySet()方法, 获取Set集合, 通过遍历set 集合获取每一个entry对象, 在通过entry 的getKey(), 和 getValue()方法来获取键和值;

public static void main(String[] args) {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("1", "test01");
        hashMap.put("2", "test02");
        hashMap.put("3", "test03");
        Set<Map.Entry<String, Object>> entries = hashMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object value = entry.getValue();
            System.out.println(key+"==="+value);
        }
        /*1===test01
        2===test02
        3===test03*/
    }

4.3.5 LinkedHashMap

hashMap的查询速度很快, 可以保证成对元素的唯一性, 但是无法保证元素存放的顺序, 要想保证元素的顺序, 就得使用HashMap下的子类, LikendHashMap 他的底层是采用链表加哈希表组合的数据结构

public static void main(String[] args) {
        LinkedHashMap<String, Object> linkedHashMap = new LinkedHashMap<>();
        for (int i = 1; i <= 5; i++) {
            linkedHashMap.put("" + i + "", "Test" + i);
        }
        Set<Map.Entry<String, Object>> entries = linkedHashMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            System.out.println(entry.getKey()+"->"+entry.getValue());
        }
          /*1->Test1
            2->Test2
            3->Test3
            4->Test4
            5->Test5*/
    }

4.4 collections工具类

  • java.util.Collections 是集合的工具类,主要用来操作集合

  • public static void shuffle(List<?> list) : 打乱集合顺序.

    • public static void main(String[] args) {
              ArrayList<Integer> integers = new ArrayList<>();
              integers.add(11);
              integers.add(22);
              integers.add(33);
              integers.add(44);
              System.out.println(integers);//[11, 22, 33, 44]
              Collections.shuffle(integers);
              System.out.println(integers);//[11, 33, 22, 44]
          }
      
  • public static void sort(List list):将集合中的元素按照默认规则排序

    • public static void main(String[] args) {
              ArrayList<Integer> integers = new ArrayList<>();
              integers.add(1);
              integers.add(97);
              integers.add(43);
              integers.add(22);
              System.out.println(integers);//[1, 97, 43, 22]
              Collections.sort(integers);
              System.out.println(integers);//[1, 22, 43, 97]
          }
      
  • public static void sort(List list , Comparator<? super T>):将集合中元素按照指定规则排序.
    public class Student {
    private String name;
    private int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Strdent{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
     public static void main(String[] args) {
            ArrayList<Student> objects = new ArrayList<>();
            objects.add(new Student("小王",66));
            objects.add(new Student("小红", 1));
            objects.add(new Student("小狗", 9));
            objects.add(new Student("小猪", 5));
            System.out.println(objects);
            //[Student{name='小王', age=66}, Student{name='小红', age=1}, Student{name='小狗', age=9}, Student{name='小猪', age=5}]
            Collections.sort(objects, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o2.getAge() -o1.getAge() ;
                    //前减后 由小到大, 后减前 由大到小
                }
            });
            System.out.println(objects);
            //[Student{name='小王', age=66}, Student{name='小狗', age=9}, Student{name='小猪', age=5}, Student{name='小红', age=1}]
        }
    

5.泛型, 泛型通配符

5.1 泛型(Geneic)

在JDK1.5之前, 我们定义集合的时候 是可以随意往里面赋值的, 但是取出的时候我们无法确定是什么类型, 有时候就会导致异常的发生java.lang.ClassCasetException, 类型转换异常, 但是才JDK1.5 之后引入了泛型,就不会导致这种异常的发生,因为我们实例集合的时候已经指定了类型,这样的话如果存入的数据类型匹配的话就会导致编译报错

泛型的定义与使用;

可以定义泛型类, 方法, 接口,

  • 定义含有泛型类

    public class Test01 {
    public void add(E e){
    }
    }

在实例化类的时候在能确定泛型的类型;

public class Test {
    public static void main(String[] args) {
        Test01<String> stringTest01 = new Test01<>();
        stringTest01.add("d");
    }
}
  • 定义含有泛型方法

    //修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}

    public class Test02 {
    public E query(E e){
    return e;
    }
    }

在调用方法的时候才能确定泛型的类型

Test02 test02 = new Test02();
        test02.query("fd");
  • 定义含有泛型接口

    public interface Test03 {
    void add(E e);
    E query();
    }

调用时指明类型

public class Test implements Test03<String> {
    public static void main(String[] args) {}
    @Override
    public void add(String o) { }
    @Override
    public String query() { return null; }
}

没有创建时不能确定的类型叫做泛型

public class Test01<E> implements Test03<E>{
    public void  add(E e){ }
    @Override
    public E query() { return null; }
}

5.2 泛型通配符

当使用泛型类或者接口是, 传递的数据中,泛型类型不能确定, 可以通过通配符<?> 表示 , 使用通配符后, 只能使用Object中的共性方法 , 集合中元素自身的方法无法使用,

5.2.1 通配符的基本使用

泛型通配符:不知道该用什么类型接收的时候可以使用泛型通配符? 表示未知通配符.此时只能接收数据, 不能我那个集合中存入数据.

public static void main(String[] args) {
        LinkedHashMap<Object, Object> objectObjectLinkedHashMap = new LinkedHashMap<>();
        get(objectObjectLinkedHashMap);
    }
    public static void get(Map< ?,? > collection){
    }

泛型不存在继承关系

如上图, 这种实例定义泛型定义String 类型, 但是接收用Object 的话就编译报错

5.2.2 通配符高级使用–受限泛型

java中泛型存在上限和下限

泛型的上限

  • 格式 类型<? extends 类> 对象名称
  • 含义: 只能接收该类型及其子类

泛型的下限

  • 格式:类型<? super 类> 对象名称

  • 含义:只能接收本类及其父类

    public class Test01 {
    public static void main(String[] args) {
    HashSet objects = new HashSet();
    HashSet integers = new HashSet();
    HashSet strings = new HashSet();
    HashSet numbers = new HashSet();
    //getElements(objects); 会编译报错, 因为 getElements 接收的只能是Number类 及其他的子类
    //getElements(strings); 同上
    getElements(numbers);
    getElements(integers);
    //getElements1(integers); 编译报错, getElements1 泛型只能接收Number 类型的及其的他父类型
    //getElements1(strings);
    getElements1(numbers);
    getElements1(objects);
    }

      public static void getElements(Collection<? extends  Number> collection) { }
      public static void getElements1(Collection<? super Number> collection) { }
    

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值