Java重新出发--Java学习笔记(二)--数据结构

既然是重新出发, 那一定要更好的出发,我觉得需要对数据结构做一个回顾了。

JAVA中的数据类型

java是强类型语言,每一个变量都需要声明它的类型。
数据类型可以分为两个部分

基本类型

4种整形、2种浮点类型、1种用于表示Unicode编码的字符单元的字符类型char和1种用于表示真值的boolean类型。
每个基本类型都有一个对应的引用类型,称作装箱基本类型(boxed primitive)。
装箱基本类中对应于int、double、boolean的是Integer、Double和Boolean。

引用类型

String和List

Java中的特例

JAVA虽然是面向对象的语言,但是在JAVA8中有8种数据类型不是对象。之所以这样设计,是因为基本数据效率更高。为了深入了解这其中的缘由,有必要去了解一下创建对象的过程。

创建对象的过程

JAVA将内存分为四块区域,堆、栈、静态区和常量区。

  • 堆:在RAM中,用于存放java对象
  • 栈: 也在RAM中,用于存放引用
  • 静态区:位于RAM中,被static修饰的变量存放在静态区。
  • 常量区: 常量存在的地方,因为常量永远不会被改变,因此放在ROM中会非常安全。

比如现在创建一个Person对象,实例化一个Person类:

Person p = new Person()

首先,会在堆中开辟一个新空间来存放这个新对象,然后在创建一个引用p放在栈中,这个引用指向堆中的这个新对象,换句话说这个引用是存放这个Person对象的地址。
这样,通过访问p我们也就间接的选到了这个Person.

接下里,我们也许又会再一次new一个Person对象p2

Person p2 = new Person()

这条代码的意思是又创建了一个新的引用也是指向这个对象Person的地址,这个时候如果通过改变Person对象的状态,也会改变p的结果。因为它们指向同一个对象。(String是特例,以后单独笔记)

引用的单独存在是没有意义的,因为它没有一个指向的地址,也就无对象可操作。所以可以只创建一个引用,但是在使用它的时候,必须为它赋值。
一个对象可以有很多个引用,但是一个引用只能对应一个对象。

在java里,‘=’不能被看做是一个赋值语句,它做的只是将一个对象的地址传给左边的引用,使得左边的引用指向了右边的引用所指向的对象。虽然java表面上看已经不存在指针的概念,但是它的引用实质上就是一个指针。所以‘=’不应该被翻译成赋值语句,因为它执行的并不是赋值过程,而是一个传递地址的过程。

特例:基本数据类型

特例存在的意义:使用new创建的对象存在于堆中,而频繁的使用new创建对象尤其是简单的小的变量其实是非常不明智的,因为堆的空间是有限的,如果频繁的操作会导致不可想象的错误。
所以针对这些简单的类型,java采用了c和c++相同的方法,也就是不使用new来创建对象,另外就是创建一个并非是引用的‘自动’的常量。这个变量直接存储‘值’并置于常量区中,因此更加高效。

举个栗子🌰:

int i =2;
int j =2;

需要知道的是,在常量区中,相同的常量只会存在一个,当执行第一行代码时,会先查找常量区中是否有2,没有则开辟一个空间来存放2,然后在栈中存入一个变量i,用来指向这个常量。
执行到第二行,查找发现2已存在,所以就无需开辟新空间,直接在栈中保存一个新变量j,让j也指向2.
(这里我的理解是,堆中存放的对象的引用里存放的事这个对象的地址,而基本数据类型的引用是存放在常量区的这个常量值)

JAVA与此同时也为每一个基本数据类型提供了对应的包装类,使得我们也可以使用new操作符来创建我们想要的变量。

Integer i = new Integer(1);
Integer j = new Integer(2);

使用new操作符每一次都会在堆中开辟新的空间,所以这里i和j指向的是不同的内存地址。

深入了解Integer

再举一个栗子先:

public static void main(String[] args){
    Integer a = 5;
    Integer b = 5;
    System.out.println(a == b);

    Integer c = 500;
    Integer d = 500;
    System.out.println(c == d);
    
    Integer i = new Integer(value:5);
    Integer j = new Integer(value:5);    
    System.out.println(i == j);
}

第一个返回true是很正常的,因为指向的是同一个地址,第二个返回false有点费解下面详说,第三个返回false是因为使用了new关键词来开辟了新的空间,i和j两个对象分别指向堆区中的两块内存空间。

关于第二个问题,可以跟踪一下Integer的源码。

private static class IntegerCache{
    static final int low = -128;
    static final int hight;
    static final Integer cache[];

    static{
        int h = 127;
        String integerCacheHighPropValue = sun.misc.VM.getSavedProperty(s:"java.lang.Integer.IntegerCache.high");
        if(integerCacheHighPropValue != null){
            try{
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i,127);
                h = Math.min(i,Integer.MAX_VALUE - (-low)-1)
            }catch(NumberFormatException nfe){

            }
        }
        high = h;
        cache = new Integer[(high - low)+1];
        int j = low;
        for(int k = 0;k<cache.length;k++)
             cache[k] = new Integer(j++);
    }
    assert IntegerCache.high >= 127;
}

说实话我并没有完全搞懂这段代码的意思,但是这是JAVA的一个缓存机制。Integer类的内部类缓存了-128到127的所有数字。(当然Integer类的缓存上限是可以通过修改系统来更改的,了解就好不必深究。)

public static void main(String[] args){
    Integer a = 127;
    Integer b = 127;
    System.out.println(a == b);
    Integer a = 128;
    Integer b = 128;
    System.out.println(a == b);
}

为什么要引入缓存机制

这就回到了为什么要引入基础类型这个特例的问题上了。
这里我留作一个待学习点,以待补充。

另一个特例:String

String是一个特殊的类,它被final修饰符所修饰,是一个不可改变的类。java源码中基本类型的各个包装类也都被final所修饰。这里以String为例。
举一个栗子:

public void testString(){
    String s1 = "abc";
    String s2 = s1;
    String s3 = "abc";
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s2 == s3);
    s1 = "bcd";
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s2 == s3);
}

执行第一句:常量区开辟空间存放“abc”,s1存放于栈中指向“abc”
执行第二句:s2也指向于“abc”
执行第三句:直接指向“abc”
正因为三个变量都指向于同一个内存地址,结果都为true.
当s1发生变化,新开辟空间存放“bcd”,但是其它变量没有改变指向。

再举一个例子:

public void testString2(){
    String s1 = new String("abc");
    String s2 = s1;
    String s3 = new String("abc");

    System.out.println(s1 == s2);//true
    System.out.println(s1 == s3);//false
    System.out.println(s2 == s3);//false
    s1 = "abc";
    System.out.println(s1 == s2);//false
    System.out.println(s1 == s3);//false
    System.out.println(s2 == s3);//false
}

执行第一句:在堆里分配空间存放String对象,在常量区开辟空间存放“abc”,String对象指向常量,s1指向该对象
执行第二句:s2也指向上一步new出来的string对象
执行第三句:同执行一,s3指向新string对象,但是new出来的新对象也是指向常量区的该值。
当s1重新赋一个“abc”时,结果却为false了。

这就是String的特殊之处。每一次new出来的string不再是常量,而是一个String对象,因为String类是不可修改的,所以String对象也是不可修改的。

java是值传递还是引用传递的呢?

java是值传递。

public void test(){
    int a =2;
    int b =3;
    swap(a,b);
    System.out.print(a);
    System.out.print(b);
}
public void swap (int a ,int b){
    int temp = a;
    a = b;
    b = a;
}

我们在嗲用一个需要传递参数的函数时,传给函数的参数并不是参数本身,
我们可以理解为是一个复制。函数使用这个复制来做一些操作,但是并不改变这个数据本身。
传递进去的参数称为形参,而实参并没有发生变化。

可以再看一个复杂一点的例子:

public void testPersonTwo(){
    Person p1 = new Person();
    p1.setAge(10);
    
    change(p1);
    System.out.println();//结果是10
}

private void change(Person p){
    p = new Person();
    p.setAge(20);
}

可以看到,我们把p1传进去,它并没有被替换成新的对象。因为change函数操作的不是p1这个引用本身,而是这个引用的一个副本。
你依然可以理解为,主函数将p1复制了一份然后变成了chagne函数的形参,最终指向新Person对象的是那个副本引用,而实参p1并没有改变。

再看一个例子:

public void testPersonThree(){
    Person p1 = new Person();
    p1.setAge(10);
    
    changeAge(p1);
    
    System.out.println(p1.getAge);//结果是20
}

private void changeAge(Person p){
    p.setAge(20);
}

简单说明一下,java的传值过程,其实传的是副本,不管是变量还是引用。可以通过引用改变值,但是不能通过副本改变变量。

浮点类型

浮点类型用于表示有小数部分的数值。在Java中有两种浮点类型,一个是4字节的float,一个是8字节的double。我们平时用来编写程序用来表示增长率、物品重量等方面也非常有用。不过,在使用浮点类型时,也需要留意一些问题。

浮点类型只是近似的存储

请问一个问题:0.1+0.2等于多少?是0.3吗?
我们可以看一下java给出的答案

public class Tester{
    public static void main(String[] args){
        double d1 = 0.1;
        double d2 = 0.2;
        System.out.print(d1+d2);
    }
}
//输出0.30000000000000004

结果似乎有些令人惊讶,这么简单的算术竟然也会算错。

其实,这并不是计算错误,这只是浮点数类型存储的问题。计算机使用二进制来存储数据,而二进制无法准确的表示分数 1/10 ,就像使用十进制时,无法准确地表示 1/3 一样。

数量级差很大的浮点运算

当浮点数值的数量级相差很大的时候,运算又会有什么问题呢?

public class Tester{
    public static void main(String[] args){
        float f1 = 30000;
        float f2 = f1 + 1;
        System.out.print(f1+"--"f2);
        System.out.print(f1<f2);

        float f3 = 30000000;
        float f4 = f1 + 1;
        System.out.print(f1+"--"f2);
        System.out.print(f1<f2);
    }
}
//输出 30000 30001.0
//true
//3.0E7  3.0E7   false

又发生了预期外的结果。从输出结果来看,f3竟然和f4是相等的,也就是意味着对f3+1并没有改变f3的值。

这同样是因为浮点数的存储造成的,二进制所能表示的两个相邻的浮点值之间存在一定的空隙。浮点值越大,这个间隙也会越大。当浮点值大道一定程度的时,如果对浮点值的改变很小(例如上面的30000000+1),就不足以使浮点值发生改变。就好比蒸发掉大海中的一滴水,大海还是大海,几乎不存在变化。
如果想要准确的存储,就去使用BigDecimal吧,有必要了解的可以去自行百度,这里就不做过多介绍了,已经是Java封装好的类库了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容