• 本文将告诉你学习Java需要达到的30个目标,学习过程中可能遇到的问题,及学习路线。希望能够对你的学习有所帮助。对比一下自己,你已经掌握了这30条中的多少条了呢? 路线 Java发展到现在,按应用来分主要分为三大...

     

     

    java详细路线:

     

    原文出自点击打开链接

    本文将告诉你学习Java需要达到的30个目标,学习过程中可能遇到的问题,及学习路线。希望能够对你的学习有所帮助。对比一下自己,你已经掌握了这30条中的多少条了呢?

    路线

    Java发展到现在,按应用来分主要分为三大块:J2SE,J2ME和J2EE。

    这三块相互补充,应用范围不同。

    J2SE就是Java2的标准版,主要用于桌面应用软件的编程;

    J2ME主要应用于嵌入是系统开发,如手机和PDA的编程;

    J2EE是Java2的企业版,主要用于分布式的网络程序的开发,如电子商务网站和ERP系统。

    先学习j2se

    要学习j2ee就要先学习j2se,刚开始学习j2se先建议不要使用IDE,然后渐渐的过渡到使用IDE开发,毕竟用它方便嘛。学习j2se推荐两本书,《java2核心技术一二卷》,《java编程思想》,《java模式》。其中《java编程思想》要研读,精读。这一段时间是基本功学习,时间会很长,也可能很短,这要看学习者自身水平而定。

    不要被IDE纠缠

    在学习java和j2ee过程中,你会遇到五花八门的IDE,不要被他们迷惑,学JAVA的时候,要学语言本身的东西,不要太在意IDE的附加功能,JAVA编程在不同IDE之间的转换是很容易的,过于的在意IDE的功能反而容易耽误对语言本身的理解。目前流行的IDE有jbuilder,eclipse和eclipse的加强版WSAD。用好其中一个就可以了,推荐从eclipse入手j2ee。因为Jbuilder更适合于写j2se程序。

    选择和学习服务器使用配置

    当你有了j2se和IDE的经验时,可以开始j2ee的学习了,web服务器:tomcat,勿庸置疑,tomcat为学习web服务首选。而应用服务器目前主要有三个:jboss、weblogic、websphere。有很多项目开始采用jboss,并且有大量的公司开始做websphere或weblogic向jboss应用服务器的移植(节省成本),这里要说的是,学习tomcat和jboss我认为是首选,也是最容易上手的。学习服务器使用配置最好去询问有经验的人(有条件的话),因为他们或许一句话就能解决问题,你自己上网摸索可能要一两天(我就干过这种傻事),我们应该把主要时间放在学习原理和理论上,一项特定技术的使用永远代替不了一个人的知识和学问。

    学习web知识

    如果你是在做电子商务网站等时,你可能要充当几个角色,这是你还要学习:

    html,可能要用到vscode或者webstorm或者sublime或者等IDE。

    Javascript,学会简单的数据校验,数据联动显示等等。

    J2eeAPI学习

    学习j2eeAPI和学习服务器应该是一个迭代的过程。

    先学习jsp和servlet编程,这方面的书很多,我建立看oreilly公司的两本《jsp设计》和《java servlet编程》,oreilly出的书总是那本优秀,不得不佩服。

    学习jdbc数据库编程,j2ee项目大多都是MIS系统,访问数据库是核心。这本应属于j2se学习中,这里拿出来强调一下。

    学习jndi api,它和学习ejb可以结合起来。

    学习ejb api,推荐书《精通ejb》

    经过上面的这些的学习,大概可以对付一般的应用了。

    有人说跟着sun公司的《j2ee tutorial》一路学下来,当然也可以。

    学习ejb设计模式和看代码(最重要)

    设计模式是练内功,其重要性可以这么说吧,如果你不会用设计模式的话,你将写出一堆使用了ejb的垃圾,有慢又是一堆bug,其结果不如不用ejb实现(ejb不等于j2ee)

    无论学习什么语言,都应该看大量代码,你看的代码量不到一定数量,是学不好j2ee的。

    目前有很多开源的工程可以作为教材:

    jive论坛

    petstore sun公司

    dune sun公司

    等等,研读一个,并把它用到自己的工程中来。

    J2ee其他学习

    当你渐渐对j2ee了解到一定深度时,你要开始关注当前领域中的一些技术变化,J2ee是一块百家争鸣的领域,大家都在这里提出自己的解决方案,例如structs,hiberate,ofbiz等等,学习这些东西要你的项目和目标而定,预先补充一下未尝不可,但不用涉及太深,毕竟学习原理和理论是最最重要的事。

    目前常见j2eeAPI

    JavaServer Pages(JSP)技术1.2

    Java Servlet技术2.3

    JDBC API 2.0

    Java XML处理API(JAXP)1.1

    Enterprise JavaBeans技术2.0

    Java消息服务(JMS)1.0

    Java命名目录接口(JNDI)1.2

    Java事务API(JTA) 1.0

    JavaMail API 1.2

    JavaBeans激活架构(JAF)1.0

    J2EE连接器体系结构(JCA)1.0

    Java认证和授权服务(JAAS)1.0

    学习上面的某些API要以你的项目而定,了解所有他们总之是有好处的。

    上面印证了大家说的一句话,java语言本身不难学,但是技术太多,所以学java很费劲。回想一下,基本上每个初学者,在刚学习java的时候可能都会问别人这么一句话,你怎么知道的哪个方法(api)在哪个包里的?呵呵,无他,唯手熟尔。

    1 基础是王道。我们的基础要扎实扎实再扎实。

    以上面的整个流程来看java的技术分支很多,要想完全掌握是绝对不可能的。我们只有从中精通1到2个部分。但是java也是有通性的,所谓万变不离其宗。java的所有编程思路都是“面向对象”的编程。所以大家在往更高境界发展以前一定要打好基础,这样不管以后是jree还是j3d都有应刃而解的感觉。在这里强烈推荐“java编程思想”.

    2 所谓打好基础并不是说要熟悉所有的java代码。我说的意思是要了解java的结构。class,methode,object,各种套用import,extend 让自己在结构上对java有个立体而且整体的了解即刻。其实java的学习不用固执于对代码的熟悉,1来java本身带有很多demo,java2d

    的所有问题几乎都有demo的样例。2来java是开放代码,即使没有demo网络上也有很多高手把自己的代码分享。所以不要怕没有参考,参考是到处都有的。

    3 最后还有1点经验和大家分享,对sun的api一定要学会活用,不论是学习还是作为参考api都有很大的帮助,在完全了解java的结构的基础上,不论什么方法都是可以通过api来找到的.所以不要怕找不到方法,了解结构,了解api就能找到方法。

    重点

    精通:能够掌握此技术的85%技术要点以上,使用此技术时间超过两年,并使用此技术成功实施5个以上的项目。能使用此技术优化性能或代码,做到最大可能的重用。

    熟练:能够掌握此技术的60%技术要点以上,使用此技术时间超过一年,并使用此技术成功实施3个以上的项目。能使用此技术实现软件需求并有经验的积累在实现之前能做优化设计尽可能的实现模块或代码的重用。

    熟悉:能够掌握此技术的50%技术要点以上,使用此技术时间超过半年上,并使用此技术成功实施1个以上的项目。能使用此技术实现软件需求。

    了解:可以在实际需要时参考技术文档或帮助文件满足你的需要,基本知道此项技术在你运用是所起的作用,能够调用或者使用其根据规定提供给你的调用方式。

    二:基本要求

    1:html 掌握程度:熟练。原因:不会html你可能写JSP?

    2:javascript/jscript:掌握程度:熟悉。原因:client端的数据校验、一些页面处理需要你使用脚本。

    3:CSS 掌握程度:熟悉。原因:实现页面风格的统一通常会使用css去实现。

    4:java基础编程 掌握程度:熟练。原因:不会java你能写JSP?开玩笑吧。还有你必须非常熟悉以下几个包java.lang;java.io;java.sql;java.util;java.text;javax.sevrlet;javax.servlet.http; javax.mail;等。

    5:sql 掌握程度:熟练。原因:如果你不使用数据库的话你也许不需要掌握sql。同时你必须对以下几种数据库中的一种以上的sql比较熟悉。Oracle,DB2,Mysql,Postgresql.

    6:xml 掌握程度:了解 原因:AppServer的配置一般是使用XML来实现的。

    7:ejb 掌握程度:了解 原因:很多项目中商业逻辑是由ejb来实现的,所以呢„„

    8:以下几种AppServer(engnier) 你需要了解一个以上。

    a:)Tomcat b:)WebLogic c:)WebSphere d:)JRun e:)Resin 原因:你的jsp跑在什么上面啊!

    三:选择要求(因项目而定)

    1:LDAP 掌握程度:了解 原因:LADP越来越多的运用在权限控制上面。

    2:Struts 掌握程度:熟练 原因:如果符合MVC设计通常会使用Struts实现C。

    3:Xsp 掌握程度:根据需要而定很多时候是不使用的,但在不需要使用ejb但jsp+servlet+bean实现不了的时候Xsp是一个非常不错的选择。

    4:Linux 掌握程度:熟悉 原因:如果你的运用跑在Linux/Unix上你最少要知道rm ,mv,cp,vi,tar gzip/gunzip 是用来做什么的吧。

    四:工具的使用 1:UltraEdit(EditPlus)+jakarta-ant+jakarta-log4j; 2:Jubilder4-6 3:Visual Age For Java 4:VCafe

    以上的工具你选择你自己熟悉的吧。不过强烈建议你用log4j做调试工具。

    五:成长之路

    1:html 学习时间,如果你的智商在80以上,15天时间应该够用了。至少你能手写出一个页面来。

    2:jacascript/jscript学习时间,这真的不好说,比较深奥的东西,够用的话一个礼拜可以学写皮毛。

    3:css 学习时间,三天的时间你应该知道如何使用css了,不要求你写,一般是美工来写css。

    4:java 学习时间,天才也的三个月吧。慢满学吧。如果要精通,那我不知道需要多少时间了。用来写

    jsp,四个月应该够了。

    5:sql 学习时间,只需要知道insert ,delete ,update ,select,create/drop table的话一天你应该知道了。

    6:xml 学习时间,我不知道我还没有学会呢。呵呵。不过我知道DTD是用来做什么的。

    7:ejb 学习时间,基本的调用看3天你会调用了。不过是建立在你学会java的基础上的。

    8:熟悉AppServer,Tomcat四天你可以掌握安装,配置。把jsp跑起来了。如果是WebLogic也够了,但要使用ejb那不关你的事情吧。SA做什么去了。

    9:熟悉Linux那可得需要不少时间。慢慢看man吧。

    10:Struts如果需要你再学习。

    目标

    1.你需要精通面向对象分析与设计(OOA/OOD)、涉及模式(GOF,J2EEDP)以及综合模式。你应该十分了解UML,尤其是class,object,interaction以及statediagrams。

    2. 你需要学习JAVA语言的基础知识以及它的核心类库(collections,serialization,streams, networking, multithreading,reflection,event,handling,NIO,localization,以及其他)。

    3.你应该了解JVM,classloaders,classreflect,以及垃圾回收的基本工作机制等。你应该有能力反编译一个类文件并且明白一些基本的汇编指令。

    4. 如果你将要写客户端程序,你需要学习WEB的小应用程序(applet),必需掌握GUI设计的思想和方法,以及桌面程序的SWING,AWT, SWT。你还应该对UI部件的JAVABEAN组件模式有所了解。JAVABEANS也被应用在JSP中以把业务逻辑从表现层中分离出来。

    5.你需要学习java数据库技术,如JDBCAPI并且会使用至少一种persistence/ORM构架,例如Hibernate,JDO, CocoBase,TopLink,InsideLiberator(国产JDO红工厂软件)或者iBatis。

    6.你还应该了解对象关系的阻抗失配的含义,以及它是如何影响业务对象的与关系型数据库的交互,和它的运行结果,还需要掌握不同的数据库产品运茫 热?oracle,mysql,mssqlserver。

    7.你需要学习JAVA的沙盒安全模式(classloaders,bytecodeverification,managers,policyandpermissions,

    codesigning, digitalsignatures,cryptography,certification,Kerberos,以及其他)还有不同的安全/认证 API,例如JAAS(JavaAuthenticationandAuthorizationService),JCE (JavaCryptographyExtension),JSSE(JavaSecureSocketExtension),以及JGSS (JavaGeneralSecurityService)。

    8.你需要学习Servlets,JSP,以及JSTL(StandardTagLibraries)和可以选择的第三方TagLibraries。

    9.你需要熟悉主流的网页框架,例如JSF,Struts,Tapestry,Cocoon,WebWork,以及他们下面的涉及模式,如MVC/MODEL2。

    10.你需要学习如何使用及管理WEB服务器,例如tomcat,resin,Jrun,并且知道如何在其基础上扩展和维护WEB程序。

    11.你需要学习分布式对象以及远程API,例如RMI和RMI/IIOP。

    12.你需要掌握各种流行中间件技术标准和与java结合实现,比如Tuxedo、CROBA,当然也包括javaEE本身。

    13.你需要学习最少一种的XMLAPI,例如JAXP(JavaAPIforXMLProcessing),JDOM(JavaforXMLDocumentObjectModel),DOM4J,或JAXR(JavaAPIforXMLRegistries)。

    14. 你应该学习如何利用JAVAAPI和工具来构建WebService。例如JAX-RPC(JavaAPIforXML/RPC),SAAJ (SOAPwithAttachmentsAPIforJava),JAXB(JavaArchitectureforXMLBinding),JAXM (JavaAPIforXMLMessaging), JAXR(JavaAPIforXMLRegistries),或者JWSDP(JavaWebServicesDeveloperPack)。

    15.你需要学习一门轻量级应用程序框架,例如Spring,PicoContainer,Avalon,以及它们的IoC/DI风格(setter,constructor,interfaceinjection)。

    16. 你需要熟悉不同的J2EE技术,例如JNDI(JavaNamingandDirectoryInterface),JMS (JavaMessageService),JTA/JTS(JavaTransactionAPI/JavaTransactionService), JMX (JavaManagementeXtensions),以及JavaMail。

    17.你需要学习企业级 JavaBeans(EJB)以及它们的不同组件模式:Stateless/StatefulSessionBeans,EntityBeans(包含 Bean- ManagedPersistence[BMP]或者Container-ManagedPersistence[CMP]和它的EJB-QL),或者 Message-DrivenBeans(MDB)。

    18.你需要学习如何管理与配置一个J2EE应用程序服务器,如WebLogic,JBoss等,并且利用它的附加服务,例如簇类,连接池以及分布式处理支援。你还需要了解如何在它上面封装和配置应用程序并且能够监控、调整它的性能。

    19.你需要熟悉面向方面的程序设计以及面向属性的程序设计(这两个都被很容易混淆的缩

    写为AOP),以及他们的主流JAVA规格和执行。例如AspectJ和AspectWerkz。

    20. 你需要熟悉对不同有用的API和frame work等来为你服务。例如Log4J(logging/tracing),Quartz (scheduling),JGroups(networkgroupcommunication),JCache (distributedcaching), Lucene(full-textsearch),JakartaCommons等等。

    21.如果你将要对接或者正和旧的系统或者本地平台,你需要学习JNI (JavaNativeInterface) and JCA (JavaConnectorArchitecture)。

    22.你需要熟悉JINI技术以及与它相关的分布式系统,比如掌握CROBA。

    23.你需要JavaCommunityProcess(JCP)以及他的不同JavaSpecificationRequests(JSRs),例如Portlets(168),JOLAP(69),DataMiningAPI(73),等等。

    24.你应该熟练掌握一种JAVAIDE例如sunOne,netBeans,IntelliJIDEA或者Eclipse。(有些人更喜欢VI或EMACS来编写文件。随便你用什么了:)

    25.JAVA(精确的说是有些配置)是冗长的,它需要很多的人工代码(例如EJB),所以你需要熟悉代码生成工具,例如XDoclet。

    26.你需要熟悉一种单元测试体系(JNunit),并且学习不同的生成、部署工具(Ant,Maven)。

    27.你需要熟悉一些在JAVA开发中经常用到的软件工程过程。例如RUP(RationalUnifiedProcess)andAgilemethodologies。

    28.你需要能够深入了解加熟练操作和配置不同的操作系统,比如GNU/linux,sunsolaris,macOS等,做为跨平台软件的开发者。

    29.你还需要紧跟java发展的步伐,比如现在可以深入的学习javaME,以及各种java新规范,技术的运用,如新起的web富客户端技术。

    30.你必需要对opensource有所了解,因为至少java的很多技术直接是靠开源来驱动发展的,如java3D技术。(BlogJava-Topquan's Blog)

    原文出自点击打开链接

    当然学习了基础知识,也少不了了解一些数据结构与算法

    数据结构是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作。算法是为求解一个问题需要遵循的、被清楚指定的简单指令的集合。下面是自己整理的常用数据结构与算法相关内容,如有错误,欢迎指出。

    为了便于描述,文中涉及到的代码部分都是用Java语言编写的,其实Java本身对常见的几种数据结构,线性表、栈、队列等都提供了较好的实现,就是我们经常用到的Java集合框架,有需要的可以阅读这篇文章。Java - 集合框架完全解析

    一、线性表
      1.数组实现
      2.链表
    二、栈与队列
    三、树与二叉树
      1.树
      2.二叉树基本概念
      3.二叉查找树
      4.平衡二叉树
      5.红黑树
    四、图
    五、总结
    

    一、线性表

    线性表是最常用且最简单的一种数据结构,它是n个数据元素的有限序列。

    实现线性表的方式一般有两种,一种是使用数组存储线性表的元素,即用一组连续的存储单元依次存储线性表的数据元素。另一种是使用链表存储线性表的元素,即用一组任意的存储单元存储线性表的数据元素(存储单元可以是连续的,也可以是不连续的)。

    数组实现

    数组是一种大小固定的数据结构,对线性表的所有操作都可以通过数组来实现。虽然数组一旦创建之后,它的大小就无法改变了,但是当数组不能再存储线性表中的新元素时,我们可以创建一个新的大的数组来替换当前数组。这样就可以使用数组实现动态的数据结构。

    • 代码1 创建一个更大的数组来替换当前数组
    int[] oldArray = new int[10];
            
    int[] newArray = new int[20];
            
    for (int i = 0; i < oldArray.length; i++) {
        newArray[i] = oldArray[i];
    }
    
    // 也可以使用System.arraycopy方法来实现数组间的复制     
    // System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            
    oldArray = newArray;
    
    • 代码2 在数组位置index上添加元素e
    //oldArray 表示当前存储元素的数组
    //size 表示当前元素个数
    public void add(int index, int e) {
    
        if (index > size || index < 0) {
            System.out.println("位置不合法...");
        }
    
        //如果数组已经满了 就扩容
        if (size >= oldArray.length) {
            // 扩容函数可参考代码1
        }
    
        for (int i = size - 1; i >= index; i--) {
            oldArray[i + 1] = oldArray[i];
        }
    
        //将数组elementData从位置index的所有元素往后移一位
        // System.arraycopy(oldArray, index, oldArray, index + 1,size - index);
    
        oldArray[index] = e;
    
        size++;
    }
    

    上面简单写出了数组实现线性表的两个典型函数,具体我们可以参考Java里面的ArrayList集合类的源码。数组实现的线性表优点在于可以通过下标来访问或者修改元素,比较高效,主要缺点在于插入和删除的花费开销较大,比如当在第一个位置前插入一个元素,那么首先要把所有的元素往后移动一个位置。为了提高在任意位置添加或者删除元素的效率,可以采用链式结构来实现线性表。

    链表

    链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点组成,这些节点不必在内存中相连。每个节点由数据部分Data和链部分Next,Next指向下一个节点,这样当添加或者删除时,只需要改变相关节点的Next的指向,效率很高。

    单链表的结构

    下面主要用代码来展示链表的一些基本操作,需要注意的是,这里主要是以单链表为例,暂时不考虑双链表和循环链表。

    • 代码3 链表的节点
    class Node<E> {
    
        E item;
        Node<E> next;
        
        //构造函数
        Node(E element) {
           this.item = element;
           this.next = null;
       }
    }
    
    • 代码4 定义好节点后,使用前一般是对头节点和尾节点进行初始化
    //头节点和尾节点都为空 链表为空
    Node<E> head = null;
    Node<E> tail = null;
    
    • 代码5 空链表创建一个新节点
    //创建一个新的节点 并让head指向此节点
    head = new Node("nodedata1");
    
    //让尾节点也指向此节点
    tail = head;
    
    • 代码6 链表追加一个节点
    //创建新节点 同时和最后一个节点连接起来
    tail.next = new Node("node1data2");
    
    //尾节点指向新的节点
    tail = tail.next;
    
    • 代码7 顺序遍历链表
    Node<String> current = head;
    while (current != null) {
        System.out.println(current.item);
        current = current.next;
    }
    
    • 代码8 倒序遍历链表
    static void printListRev(Node<String> head) {
    //倒序遍历链表主要用了递归的思想
        if (head != null) {
            printListRev(head.next);
            System.out.println(head.item);
        }
    }
    
    • 代码 单链表反转
    //单链表反转 主要是逐一改变两个节点间的链接关系来完成
    static Node<String> revList(Node<String> head) {
    
        if (head == null) {
            return null;
        }
    
        Node<String> nodeResult = null;
    
        Node<String> nodePre = null;
        Node<String> current = head;
    
        while (current != null) {
    
            Node<String> nodeNext = current.next;
    
            if (nodeNext == null) {
                nodeResult = current;
            }
    
            current.next = nodePre;
            nodePre = current;
            current = nodeNext;
        }
    
        return nodeResult;
    }
    

    上面的几段代码主要展示了链表的几个基本操作,还有很多像获取指定元素,移除元素等操作大家可以自己完成,写这些代码的时候一定要理清节点之间关系,这样才不容易出错。

    链表的实现还有其它的方式,常见的有循环单链表,双向链表,循环双向链表。 循环单链表 主要是链表的最后一个节点指向第一个节点,整体构成一个链环。 双向链表 主要是节点中包含两个指针部分,一个指向前驱元,一个指向后继元,JDK中LinkedList集合类的实现就是双向链表。** 循环双向链表** 是最后一个节点指向第一个节点。

    二、栈与队列

    栈和队列也是比较常见的数据结构,它们是比较特殊的线性表,因为对于栈来说,访问、插入和删除元素只能在栈顶进行,对于队列来说,元素只能从队列尾插入,从队列头访问和删除。

    栈是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫作栈顶,对栈的基本操作有push(进栈)和pop(出栈),前者相当于插入,后者相当于删除最后一个元素。栈有时又叫作LIFO(Last In First Out)表,即后进先出。

    栈的模型

    下面我们看一道经典题目,加深对栈的理解。

    关于栈的一道经典题目

    上图中的答案是C,其中的原理可以好好想一想。

    因为栈也是一个表,所以任何实现表的方法都能实现栈。我们打开JDK中的类Stack的源码,可以看到它就是继承类Vector的。当然,Stack是Java2前的容器类,现在我们可以使用LinkedList来进行栈的所有操作。

    队列

    队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

    队列示意图

    我们可以使用链表来实现队列,下面代码简单展示了利用LinkedList来实现队列类。

    • 代码9 简单实现队列类
    public class MyQueue<E> {
    
        private LinkedList<E> list = new LinkedList<>();
    
        // 入队
        public void enqueue(E e) {
            list.addLast(e);
        }
    
        // 出队
        public E dequeue() {
            return list.removeFirst();
        }
    }
    

    普通的队列是一种先进先出的数据结构,而优先队列中,元素都被赋予优先级。当访问元素的时候,具有最高优先级的元素最先被删除。优先队列在生活中的应用还是比较多的,比如医院的急症室为病人赋予优先级,具有最高优先级的病人最先得到治疗。在Java集合框架中,类PriorityQueue就是优先队列的实现类,具体大家可以去阅读源码。

    三、树与二叉树

    树型结构是一类非常重要的非线性数据结构,其中以树和二叉树最为常用。在介绍二叉树之前,我们先简单了解一下树的相关内容。

    ** 树 是由n(n>=1)个有限节点组成一个具有层次关系的集合。它具有以下特点:每个节点有零个或多个子节点;没有父节点的节点称为节点;每一个非根节点有且只有一个 父节点 **;除了根节点外,每个子节点可以分为多个不相交的子树。

    树的结构

    二叉树基本概念

    • 定义

    二叉树是每个节点最多有两棵子树的树结构。通常子树被称作“左子树”和“右子树”。二叉树常被用于实现二叉查找树和二叉堆。

    • 相关性质

    二叉树的每个结点至多只有2棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。

    二叉树的第i层至多有2(i-1)个结点;深度为k的二叉树至多有2k-1个结点。

    一棵深度为k,且有2^k-1个节点的二叉树称之为** 满二叉树 **;

    深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中,序号为1至n的节点对应时,称之为** 完全二叉树 **。

    • 三种遍历方法

    在二叉树的一些应用中,常常要求在树中查找具有某种特征的节点,或者对树中全部节点进行某种处理,这就涉及到二叉树的遍历。二叉树主要是由3个基本单元组成,根节点、左子树和右子树。如果限定先左后右,那么根据这三个部分遍历的顺序不同,可以分为先序遍历、中序遍历和后续遍历三种。

    (1) 先序遍历 若二叉树为空,则空操作,否则先访问根节点,再先序遍历左子树,最后先序遍历右子树。 (2) 中序遍历 若二叉树为空,则空操作,否则先中序遍历左子树,再访问根节点,最后中序遍历右子树。(3) 后序遍历 若二叉树为空,则空操作,否则先后序遍历左子树访问根节点,再后序遍历右子树,最后访问根节点。

    给定二叉树写出三种遍历结果

    • 树和二叉树的区别

    (1) 二叉树每个节点最多有2个子节点,树则无限制。 (2) 二叉树中节点的子树分为左子树和右子树,即使某节点只有一棵子树,也要指明该子树是左子树还是右子树,即二叉树是有序的。 (3) 树决不能为空,它至少有一个节点,而一棵二叉树可以是空的。

    上面我们主要对二叉树的相关概念进行了介绍,下面我们将从二叉查找树开始,介绍二叉树的几种常见类型,同时将之前的理论部分用代码实现出来。

    二叉查找树

    • 定义

    二叉查找树就是二叉排序树,也叫二叉搜索树。二叉查找树或者是一棵空树,或者是具有下列性质的二叉树: (1) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;(2) 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;(3) 左、右子树也分别为二叉排序树;(4) 没有键值相等的结点。

    典型的二叉查找树的构建过程

    • 性能分析

    对于二叉查找树来说,当给定值相同但顺序不同时,所构建的二叉查找树形态是不同的,下面看一个例子。

    不同形态平衡二叉树的ASL不同

    可以看到,含有n个节点的二叉查找树的平均查找长度和树的形态有关。最坏情况下,当先后插入的关键字有序时,构成的二叉查找树蜕变为单支树,树的深度为n,其平均查找长度(n+1)/2(和顺序查找相同),最好的情况是二叉查找树的形态和折半查找的判定树相同,其平均查找长度和log2(n)成正比。平均情况下,二叉查找树的平均查找长度和logn是等数量级的,所以为了获得更好的性能,通常在二叉查找树的构建过程需要进行“平衡化处理”,之后我们将介绍平衡二叉树和红黑树,这些均可以使查找树的高度为O(log(n))。

    • 代码10 二叉树的节点
    
    class TreeNode<E> {
    
        E element;
        TreeNode<E> left;
        TreeNode<E> right;
    
        public TreeNode(E e) {
            element = e;
        }
    }
    

    二叉查找树的三种遍历都可以直接用递归的方法来实现:

    • 代码12 先序遍历
    protected void preorder(TreeNode<E> root) {
    
        if (root == null)
            return;
    
        System.out.println(root.element + " ");
    
        preorder(root.left);
    
        preorder(root.right);
    }
    
    • 代码13 中序遍历
    protected void inorder(TreeNode<E> root) {
    
        if (root == null)
            return;
    
        inorder(root.left);
    
        System.out.println(root.element + " ");
    
        inorder(root.right);
    }
    
    • 代码14 后序遍历
    protected void postorder(TreeNode<E> root) {
    
        if (root == null)
            return;
    
        postorder(root.left);
    
        postorder(root.right);
    
        System.out.println(root.element + " ");
    }
    
    • 代码15 二叉查找树的简单实现
    /**
     * @author JackalTsc
     */
    public class MyBinSearchTree<E extends Comparable<E>> {
    
        // 根
        private TreeNode<E> root;
    
        // 默认构造函数
        public MyBinSearchTree() {
        }
    
        // 二叉查找树的搜索
        public boolean search(E e) {
    
            TreeNode<E> current = root;
    
            while (current != null) {
    
                if (e.compareTo(current.element) < 0) {
                    current = current.left;
                } else if (e.compareTo(current.element) > 0) {
                    current = current.right;
                } else {
                    return true;
                }
            }
    
            return false;
        }
    
        // 二叉查找树的插入
        public boolean insert(E e) {
    
            // 如果之前是空二叉树 插入的元素就作为根节点
            if (root == null) {
                root = createNewNode(e);
            } else {
                // 否则就从根节点开始遍历 直到找到合适的父节点
                TreeNode<E> parent = null;
                TreeNode<E> current = root;
                while (current != null) {
                    if (e.compareTo(current.element) < 0) {
                        parent = current;
                        current = current.left;
                    } else if (e.compareTo(current.element) > 0) {
                        parent = current;
                        current = current.right;
                    } else {
                        return false;
                    }
                }
                // 插入
                if (e.compareTo(parent.element) < 0) {
                    parent.left = createNewNode(e);
                } else {
                    parent.right = createNewNode(e);
                }
            }
            return true;
        }
    
        // 创建新的节点
        protected TreeNode<E> createNewNode(E e) {
            return new TreeNode(e);
        }
    
    }
    
    // 二叉树的节点
    class TreeNode<E extends Comparable<E>> {
    
        E element;
        TreeNode<E> left;
        TreeNode<E> right;
    
        public TreeNode(E e) {
            element = e;
        }
    }
    
    

    上面的代码15主要展示了一个自己实现的简单的二叉查找树,其中包括了几个常见的操作,当然更多的操作还是需要大家自己去完成。因为在二叉查找树中删除节点的操作比较复杂,所以下面我详细介绍一下这里。

    • 二叉查找树中删除节点分析

    要在二叉查找树中删除一个元素,首先需要定位包含该元素的节点,以及它的父节点。假设current指向二叉查找树中包含该元素的节点,而parent指向current节点的父节点,current节点可能是parent节点的左孩子,也可能是右孩子。这里需要考虑两种情况:

    1. current节点没有左孩子,那么只需要将patent节点和current节点的右孩子相连。
    2. current节点有一个左孩子,假设rightMost指向包含current节点的左子树中最大元素的节点,而parentOfRightMost指向rightMost节点的父节点。那么先使用rightMost节点中的元素值替换current节点中的元素值,将parentOfRightMost节点和rightMost节点的左孩子相连,然后删除rightMost节点。
        // 二叉搜索树删除节点
        public boolean delete(E e) {
    
            TreeNode<E> parent = null;
            TreeNode<E> current = root;
    
            // 找到要删除的节点的位置
            while (current != null) {
                if (e.compareTo(current.element) < 0) {
                    parent = current;
                    current = current.left;
                } else if (e.compareTo(current.element) > 0) {
                    parent = current;
                    current = current.right;
                } else {
                    break;
                }
            }
    
            // 没找到要删除的节点
            if (current == null) {
                return false;
            }
    
            // 考虑第一种情况
            if (current.left == null) {
                if (parent == null) {
                    root = current.right;
                } else {
                    if (e.compareTo(parent.element) < 0) {
                        parent.left = current.right;
                    } else {
                        parent.right = current.right;
                    }
                }
            } else { // 考虑第二种情况
                TreeNode<E> parentOfRightMost = current;
                TreeNode<E> rightMost = current.left;
                // 找到左子树中最大的元素节点
                while (rightMost.right != null) {
                    parentOfRightMost = rightMost;
                    rightMost = rightMost.right;
                }
    
                // 替换
                current.element = rightMost.element;
    
                // parentOfRightMost和rightMost左孩子相连
                if (parentOfRightMost.right == rightMost) {
                    parentOfRightMost.right = rightMost.left;
                } else {
                    parentOfRightMost.left = rightMost.left;
                }
            }
    
            return true;
        }
    

    平衡二叉树

    平衡二叉树又称AVL树,它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。

    平衡二叉树

    AVL树是最先发明的自平衡二叉查找树算法。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

    红黑树

    红黑树是平衡二叉树的一种,它保证在最坏情况下基本动态集合操作的事件复杂度为O(log n)。红黑树和平衡二叉树区别如下:(1) 红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。(2) 平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。点击查看更多

    四、图

    • 简介

    图是一种较线性表和树更为复杂的数据结构,在线性表中,数据元素之间仅有线性关系,在树形结构中,数据元素之间有着明显的层次关系,而在图形结构中,节点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。图的应用相当广泛,特别是近年来的迅速发展,已经渗入到诸如语言学、逻辑学、物理、化学、电讯工程、计算机科学以及数学的其他分支中。

    • 相关阅读

    因为图这部分的内容还是比较多的,这里就不详细介绍了,有需要的可以自己搜索相关资料。

    (1) 《百度百科对图的介绍》
    (2) 《数据结构之图(存储结构、遍历)》

    这篇文章是常见数据结构与算法整理总结的下篇,上一篇主要是对常见的数据结构进行集中总结,这篇主要是总结一些常见的算法相关内容,文章中如有错误,欢迎指出。

    一、概述
    二、查找算法
    三、排序算法
    四、其它算法
    五、常见算法题
    六、总结
    

    一、概述

    以前看到这样一句话,语言只是工具,算法才是程序设计的灵魂。的确,算法在计算机科学中的地位真的很重要,在很多大公司的笔试面试中,算法掌握程度的考察都占据了很大一部分。不管是为了面试还是自身编程能力的提升,花时间去研究常见的算法还是很有必要的。下面是自己对于算法这部分的学习总结。

    算法简介

    算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。对于同一个问题的解决,可能会存在着不同的算法,为了衡量一个算法的优劣,提出了空间复杂度与时间复杂度这两个概念。

    时间复杂度

    一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间度量记为 ** T(n) = O(f(n)) **,它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。这里需要重点理解这个增长率。

    举个例子,看下面3个代码:
    
    1、{++x;}
    
    2、for(i = 1; i <= n; i++) { ++x; }
    
    3、for(j = 1; j <= n; j++) 
            for(j = 1; j <= n; j++) 
                 { ++x; }
    
    上述含有 ++x 操作的语句的频度分别为1 、n 、n^2,
    
    假设问题的规模扩大了n倍,3个代码的增长率分别是1 、n 、n^2
    
    它们的时间复杂度分别为O(1)、O(n )、O(n^2)
    

    空间复杂度

    空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。

    二、查找算法

    查找和排序是最基础也是最重要的两类算法,熟练地掌握这两类算法,并能对这些算法的性能进行分析很重要,这两类算法中主要包括二分查找、快速排序、归并排序等等。

    顺序查找

    顺序查找又称线性查找。它的过程为:从查找表的最后一个元素开始逐个与给定关键字比较,若某个记录的关键字和给定值比较相等,则查找成功,否则,若直至第一个记录,其关键字和给定值比较都不等,则表明表中没有所查记录查找不成功,它的缺点是效率低下。

    二分查找

    • 简介

    二分查找又称折半查找,对于有序表来说,它的优点是比较次数少,查找速度快,平均性能好。

    二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x。

    二分查找的时间复杂度为O(logn)

    • 实现
    //给定有序查找表array 二分查找给定的值data
    //查找成功返回下标 查找失败返回-1
    
    static int funBinSearch(int[] array, int data) {
    
        int low = 0;
        int high = array.length - 1;
    
        while (low <= high) {
    
            int mid = (low + high) / 2;
    
            if (data == array[mid]) {
                return mid;
            } else if (data < array[mid]) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }
    

    三、排序算法

    排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。下面主要对一些常见的排序算法做介绍,并分析它们的时空复杂度。

    常见排序算法

    常见排序算法性能比较:

    图片来自网络

    上面这张表中有稳定性这一项,排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前和排序后他们的相对位置不发生变化。

    下面从冒泡排序开始逐一介绍。

    冒泡排序

    • 简介

    冒泡排序的基本思想是:设排序序列的记录个数为n,进行n-1次遍历,每次遍历从开始位置依次往后比较前后相邻元素,这样较大的元素往后移,n-1次遍历结束后,序列有序。

    例如,对序列(3,2,1,5)进行排序的过程是:共进行3次遍历,第1次遍历时先比较3和2,交换,继续比较3和1,交换,再比较3和5,不交换,这样第1次遍历结束,最大值5在最后的位置,得到序列(2,1,3,5)。第2次遍历时先比较2和1,交换,继续比较2和3,不交换,第2次遍历结束时次大值3在倒数第2的位置,得到序列(1,2,3,5),第3次遍历时,先比较1和2,不交换,得到最终有序序列(1,2,3,5)。

    需要注意的是,如果在某次遍历中没有发生交换,那么就不必进行下次遍历,因为序列已经有序。

    • 实现
    // 冒泡排序 注意 flag 的作用
    static void funBubbleSort(int[] array) {
    
        boolean flag = true;
    
        for (int i = 0; i < array.length - 1 && flag; i++) {
    
            flag = false;
    
            for (int j = 0; j < array.length - 1 - i; j++) {
    
                if (array[j] > array[j + 1]) {
    
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
    
                    flag = true;
                }
            }
        }
    
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
    
    • 分析

    最佳情况下冒泡排序只需一次遍历就能确定数组已经排好序,不需要进行下一次遍历,所以最佳情况下,时间复杂度为** O(n) **。

    最坏情况下冒泡排序需要n-1次遍历,第一次遍历需要比较n-1次,第二次遍历需要n-2次,...,最后一次需要比较1次,最差情况下时间复杂度为** O(n^2) **。

    简单选择排序

    • 简介

    简单选择排序的思想是:设排序序列的记录个数为n,进行n-1次选择,每次在n-i+1(i = 1,2,...,n-1)个记录中选择关键字最小的记录作为有效序列中的第i个记录。

    例如,排序序列(3,2,1,5)的过程是,进行3次选择,第1次选择在4个记录中选择最小的值为1,放在第1个位置,得到序列(1,3,2,5),第2次选择从位置1开始的3个元素中选择最小的值2放在第2个位置,得到有序序列(1,2,3,5),第3次选择因为最小的值3已经在第3个位置不需要操作,最后得到有序序列(1,2,3,5)。

    • 实现
    static void funSelectionSort(int[] array) {
    
        for (int i = 0; i < array.length - 1; i++) {
    
            int mink = i;
    
                // 每次从未排序数组中找到最小值的坐标
            for (int j = i + 1; j < array.length; j++) {
    
                if (array[j] < array[mink]) {
                    mink = j;
                }
            }
    
            // 将最小值放在最前面
            if (mink != i) {
                int temp = array[mink];
                array[mink] = array[i];
                array[i] = temp;
            }
        }
    
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
    }
    
    • 分析

    简单选择排序过程中需要进行的比较次数与初始状态下待排序的记录序列的排列情况** 无关。当i=1时,需进行n-1次比较;当i=2时,需进行n-2次比较;依次类推,共需要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,即进行比较操作的时间复杂度为 O(n^2) ,进行移动操作的时间复杂度为 O(n) 。总的时间复杂度为 O(n^2) **。

    最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。最坏情况下,即待排序记录初始状态是按第一条记录最大,之后的记录从小到大顺序排列,则需要移动记录的次数最多为3(n-1)。

    简单选择排序是不稳定排序。

    直接插入排序

    • 简介

    直接插入的思想是:是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。

    例如,排序序列(3,2,1,5)的过程是,初始时有序序列为(3),然后从位置1开始,先访问到2,将2插入到3前面,得到有序序列(2,3),之后访问1,找到合适的插入位置后得到有序序列(1,2,3),最后访问5,得到最终有序序列(1,2,3,5).

    • 实现
    static void funDInsertSort(int[] array) {
    
        int j;
    
        for (int i = 1; i < array.length; i++) {
    
            int temp = array[i];
    
            j = i - 1;
    
            while (j > -1 && temp < array[j]) {
    
                array[j + 1] = array[j];
    
                j--;
            }
    
            array[j + 1] = temp;
    
        }
    
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
    }
    
    • 分析

    最好情况下,当待排序序列中记录已经有序时,则需要n-1次比较,不需要移动,时间复杂度为** O(n) 。最差情况下,当待排序序列中所有记录正好逆序时,则比较次数和移动次数都达到最大值,时间复杂度为 O(n^2) 。平均情况下,时间复杂度为 O(n^2) **。

    希尔排序

    希尔排序又称“缩小增量排序”,它是基于直接插入排序的以下两点性质而提出的一种改进:(1) 直接插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。(2) 直接插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。点击查看更多关于希尔排序的内容

    归并排序

    • 简介

    归并排序是分治法的一个典型应用,它的主要思想是:将待排序序列分为两部分,对每部分递归地应用归并排序,在两部分都排好序后进行合并。

    例如,排序序列(3,2,8,6,7,9,1,5)的过程是,先将序列分为两部分,(3,2,8,6)和(7,9,1,5),然后对两部分分别应用归并排序,第1部分(3,2,8,6),第2部分(7,9,1,5),对两个部分分别进行归并排序,第1部分继续分为(3,2)和(8,6),(3,2)继续分为(3)和(2),(8,6)继续分为(8)和(6),之后进行合并得到(2,3),(6,8),再合并得到(2,3,6,8),第2部分进行归并排序得到(1,5,7,9),最后合并两部分得到(1,2,3,5,6,7,8,9)。

    • 实现
        //归并排序
        static void funMergeSort(int[] array) {
    
            if (array.length > 1) {
    
                int length1 = array.length / 2;
                int[] array1 = new int[length1];
                System.arraycopy(array, 0, array1, 0, length1);
                funMergeSort(array1);
    
                int length2 = array.length - length1;
                int[] array2 = new int[length2];
                System.arraycopy(array, length1, array2, 0, length2);
                funMergeSort(array2);
    
                int[] datas = merge(array1, array2);
                System.arraycopy(datas, 0, array, 0, array.length);
            }
    
        }
    
        //合并两个数组
        static int[] merge(int[] list1, int[] list2) {
    
            int[] list3 = new int[list1.length + list2.length];
    
            int count1 = 0;
            int count2 = 0;
            int count3 = 0;
    
            while (count1 < list1.length && count2 < list2.length) {
    
                if (list1[count1] < list2[count2]) {
                    list3[count3++] = list1[count1++];
                } else {
                    list3[count3++] = list2[count2++];
                }
            }
    
            while (count1 < list1.length) {
                list3[count3++] = list1[count1++];
            }
    
            while (count2 < list2.length) {
                list3[count3++] = list2[count2++];
            }
    
            return list3;
        }
    
    • 分析

    归并排序的时间复杂度为O(nlogn),它是一种稳定的排序,java.util.Arrays类中的sort方法就是使用归并排序的变体来实现的。

    快速排序

    • 简介

    快速排序的主要思想是:在待排序的序列中选择一个称为主元的元素,将数组分为两部分,使得第一部分中的所有元素都小于或等于主元,而第二部分中的所有元素都大于主元,然后对两部分递归地应用快速排序算法。

    • 实现
    // 快速排序
    static void funQuickSort(int[] mdata, int start, int end) {
        if (end > start) {
            int pivotIndex = quickSortPartition(mdata, start, end);
            funQuickSort(mdata, start, pivotIndex - 1);
            funQuickSort(mdata, pivotIndex + 1, end);
        }
    }
    
    // 快速排序前的划分
    static int quickSortPartition(int[] list, int first, int last) {
    
        int pivot = list[first];
        int low = first + 1;
        int high = last;
    
        while (high > low) {
    
            while (low <= high && list[low] <= pivot) {
                low++;
            }
    
            while (low <= high && list[high] > pivot) {
                high--;
            }
    
            if (high > low) {
                int temp = list[high];
                list[high] = list[low];
                list[low] = temp;
            }
        }
    
        while (high > first && list[high] >= pivot) {
            high--;
        }
    
        if (pivot > list[high]) {
            list[first] = list[high];
            list[high] = pivot;
            return high;
        } else {
            return first;
        }
    }
    
    • 分析

    在快速排序算法中,比较关键的一个部分是主元的选择。在最差情况下,划分由n个元素构成的数组需要进行n次比较和n次移动,因此划分需要的时间是O(n)。在最差情况下,每次主元会将数组划分为一个大的子数组和一个空数组,这个大的子数组的规模是在上次划分的子数组的规模上减1,这样在最差情况下算法需要(n-1)+(n-2)+...+1= ** O(n^2) **时间。

    最佳情况下,每次主元将数组划分为规模大致相等的两部分,时间复杂度为** O(nlogn) **。

    堆排序

    • 简介

    在介绍堆排序之前首先需要了解堆的定义,n个关键字序列K1,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):(1) ki <= k(2i)且 ki <= k(2i+1) (1 ≤ i≤ n/2),当然,这是小根堆,大根堆则换成>=号。

    如果将上面满足堆性质的序列看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有的非终端节点的值均不大于(或不小于)其左右孩子节点的值。

    堆排序的主要思想是:给定一个待排序序列,首先经过一次调整,将序列构建成一个大顶堆,此时第一个元素是最大的元素,将其和序列的最后一个元素交换,然后对前n-1个元素调整为大顶堆,再将其第一个元素和末尾元素交换,这样最后即可得到有序序列。

    • 实现
    //堆排序
    public class TestHeapSort {
    
        public static void main(String[] args) {
            int arr[] = { 5, 6, 1, 0, 2, 9 };
            heapsort(arr, 6);
            System.out.println(Arrays.toString(arr));
        }
    
        static void heapsort(int arr[], int n) {
    
            // 先建大顶堆
            for (int i = n / 2 - 1; i >= 0; i--) {
                heapAdjust(arr, i, n);
            }
    
            for (int i = 0; i < n - 1; i++) {
                swap(arr, 0, n - i - 1);
                heapAdjust(arr, 0, n - i - 1);
            }
        }
    
        // 交换两个数
        static void swap(int arr[], int low, int high) {
            int temp = arr[low];
            arr[low] = arr[high];
            arr[high] = temp;
        }
    
        // 调整堆
        static void heapAdjust(int arr[], int index, int n) {
    
            int temp = arr[index];
    
            int child = 0;
    
            while (index * 2 + 1 < n) {
                            
                child = index * 2 + 1;
                            
                // child为左右孩子中较大的那个
                if (child != n - 1 && arr[child] < arr[child + 1]) {
                    child++;
                }
                // 如果指定节点大于较大的孩子 不需要调整
                if (temp > arr[child]) {
                    break;
                } else {
                    // 否则继续往下判断孩子的孩子 直到找到合适的位置
                    arr[index] = arr[child];
                    index = child;
                }
            }
    
            arr[index] = temp;
        }
    }
    
    
    • 分析

    由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。堆排序时间复杂度也为O(nlogn),空间复杂度为O(1)。它是不稳定的排序方法。与快排和归并排序相比,堆排序在最差情况下的时间复杂度优于快排,空间效率高于归并排序。

    四、其它算法

    在上面的篇幅中,主要是对查找和常见的几种排序算法作了介绍,这些内容都是基础的但是必须掌握的内容,尤其是二分查找、快排、堆排、归并排序这几个更是面试高频考察点。(这里不禁想起百度一面的时候让我写二分查找和堆排序,二分查找还行,然而堆排序当时一脸懵逼...)下面主要是介绍一些常见的其它算法。

    递归

    • 简介

    在平常解决一些编程或者做一些算法题的时候,经常会用到递归。程序调用自身的编程技巧称为递归。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。上面介绍的快速排序和归并排序都用到了递归的思想。

    • 经典例子

    斐波那契数列,又称黄金分割数列、因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*)。

    //斐波那契数列 递归实现
    static long funFib(long index) {
    
        if (index == 0) {
            return 0;
        } else if (index == 1) {
            return 1;
        } else {
            return funFib(index - 1) + funFib(index - 2);
        }
    }
    

    上面代码是斐波那契数列的递归实现,然而我们不难得到它的时间复杂度是O(2^n),递归有时候可以很方便地解决一些问题,但是它也会带来一些效率上的问题。下面的代码是求斐波那契数列的另一种方式,效率比递归方法的效率高。

    static long funFib2(long index) {
    
        long f0 = 0;
        long f1 = 1;
        long f2 = 1;
    
        if (index == 0) {
            return f0;
        } else if (index == 1) {
            return f1;
        } else if (index == 2) {
            return f2;
        }
    
        for (int i = 3; i <= index; i++) {
            f0 = f1;
            f1 = f2;
            f2 = f0 + f1;
        }
    
        return f2;
    }
    

    分治算法

    分治算法的思想是将待解决的问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后合并这些子问题的解来建立最终的解。分治算法中关键地一步其实就是递归地求解子问题。关于分治算法的一个典型例子就是上面介绍的归并排序。查看更多关于分治算法的内容

    动态规划

    动态规划与分治方法相似,都是通过组合子问题的解来求解待解决的问题。但是,分治算法将问题划分为互不相交的子问题,递归地求解子问题,再将它们的解组合起来,而动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题。动态规划方法通常用来求解最优化问题。查看更多关于动态规划的内容

    动态规划典型的一个例子是最长公共子序列问题。

    常见的算法还有很多,比如贪心算法,回溯算法等等,这里都不再详细介绍,想要熟练掌握,还是要靠刷题,刷题,刷题,然后总结。

    五、常见算法题

    下面是一些常见的算法题汇总。

    不使用临时变量交换两个数

    static void funSwapTwo(int a, int b) {
    
        a = a ^ b;
        b = b ^ a;
        a = a ^ b;
    
        System.out.println(a + " " + b);
    }
    

    判断一个数是否为素数

    static boolean funIsPrime(int m) {
    
        boolean flag = true;
    
        if (m == 1) {
            flag = false;
        } else {
    
            for (int i = 2; i <= Math.sqrt(m); i++) {
                if (m % i == 0) {
                    flag = false;
                    break;
                }
            }
        }
    
        return flag;
    }
    

    其它算法题

    1、15道使用频率极高的基础算法题
    2、二叉树相关算法题
    3、链表相关算法题
    4、字符串相关算法问题

    数据结构与算法原文出自简书尘语凡心常见数据结构与算法整理总结

    当然作为后端也要掌握一些架构师技术图谱

    展开全文
  • javaSE学习方法心得

    2014-08-29 16:03:43
     从4月末到现在一直在断断续续地学习javaSE,到目前为止已经过了3个多月的时间,可是还没有把javaSE学习完,速度实在是慢成狗。但是也算是自己一直在摸索着适合自己的自学java编程语言的方法,正是因为没有指引,都...

     

        从4月末到现在一直在断断续续地学习javaSE,到目前为止已经过了3个多月的时间,可是还没有把javaSE学习完,速度实在是慢成狗。但是也算是自己一直在摸索着适合自己的自学java编程语言的方法,正是因为没有指引,都是自己摸索出来的,所以慢点也是合情合理的,但是我有点太慢了。

        刚开始学是看的马士兵的视频,但是马士兵讲课都喜欢结合JVM来讲,一涉及到JVM我听着就费劲,理解起来有些困难,后来看推荐又开始看韩顺平的视频,韩顺平果然像大家说的比较啰嗦,而且他的javaSE的视频感觉很多,每集都将近两个小时,我也实在是很难听下去。

        后来偶然在知乎看见一个人问自学java找工作的问题,联系到了这个作者,向他咨询了下javaSE学习的方法,然后就按照他给的路线图学了。其实他给我的路线图挺好的,但是我当时忘记了一点,虽然好,但是不一定适合我,我当时只想着按部就班地学,忘记了针对自己的实际情况做改进。比如说,这个人推荐我学javaSE的时候看《thinking in java》,这点就极其不适合我,第一点,我这个人看理科类的书籍基本都是又慢又看不进去;第二点,英文版的书,我看起来还是很吃力的,虽然单词都可以查字典,但是合成句子了句子一长就不知所云了。

        遵照这个人的建议我开始听毕向东的javaSE视频,毕向东的视频听起来的确比马士兵和韩顺平的舒服,但是我犯了一个比较大的错误,就是忘记了规划看视频学习的进度,我当时就是直接在网上搜到毕向东的javaSE视频就开始学,而没有了解一下详细的细节资料,比如直到前天我才知道原来毕向东的视频是有25天和35天两版的,我一直在听的是25天的这一版,但是我居然听了三个月,直到前天我才终于知道这些视频前面的数字代表的是什么意思。正是因为不了解这些细节,我根本不知道我看毕向东的视频合理的情况应该一天看多少,有时候我一天看一大堆,看的昏头昏脑基本都没什么印象,有时候好几天不看或者一天看一点点,导致我的进度混乱而且缓慢,而且因为下载的问题,有时候看的顺序也看错了,导致我看的糊里糊涂的。

        在学习过程中,我还了解了一下关于javaSE的资格考试,基本上针对的就是OCJP了,原来计划想考这个考试来着,在某宝上花了60买了试题,大概看了看,在百度文库下载了一些有讲解的OCJP试题,我都看了,但是看试题库英文版的试题我还是不知道为什么应该选这个答案。这个考试目前先放一放吧,等什么时候我准备好了再去考吧,反正也不着急。

        前几天在网上看的关于自学java的一个帖子,应该是尚学堂的老师写的,好像还是2007年的老文章了,不过道理感觉还是很好的,给我印象最深的就是有时候不要去抠细节,特别是真的不理解的地方就先背下来,以后学的多了肯定会理解的,先把框架都搭好,然后再往里一点点添砖加瓦,这点还是很对的。

        学习中买了两本书,一本是《JAVA核心技术卷一》,一本是《疯狂java》,其实这两本书都很好,但是还是我看不进去理科类书籍的问题,我看的还是乱七八糟的,都没什么印象,而且我建议除了字典意外书还是不要做的太厚了,需要的话就做成分册也好,像《疯狂java》这种书,拿着、携带都不方便,而且一看那么厚就有一种让人敬而远之的感觉。上面这两本书都是理论性的书籍,因为一直都缺乏练习,感觉也很不好,所以又买了一本朱福喜的《面向对象与java程序设计习题与解析》,做个很多里面的题,感觉还是很多提高的,而且给我的经验是:其实自己敲程序练习远比看视频看书都重要。

        接下来的学习结合之前的经验,我的规划是:看视频要规划好每天学习的量,做好计划和安排,了解当初学这些视频的学生他们当时的进度是怎样的,结合他们的进度好好安排自己的进度。其次是当天学习的内容当天都要做针对这一部分的练习题,敲这一部分的程序才行。最后一点就是要活用搜索了,其实好像专业人士推荐用谷歌,但是鉴于天朝制度以及英文困难问题,所以还是先用百度吧,现在感觉网上的只是比书上更全更灵活,想知道什么一搜就可以了,而且不罗嗦,都是脱水版干货。

     

    原链接:http://www.aichengxu.com/article/Java/32539_2.html

    展开全文
  • 2019-6-21 [JavaSE] 背景.特点.安装.DOS.环境变量.工作原理.进制转换 2019-6-22 [JavaSE] 注释.变量.数据类型.整数.浮点数.字符.布尔.类型转化.运算符 2019-6-24 [JavaSE] 算术运算符 关系运算符 逻辑赋值运算符 ...

    知识点:

    第一周:

    2019-6-21 [JavaSE] 背景.特点.安装.DOS.环境变量.工作原理.进制转换

    2019-6-22 [JavaSE] 注释.变量.数据类型.整数.浮点数.字符.布尔.类型转化.运算符

    2019-6-24 [JavaSE] 算术运算符 关系运算符 逻辑赋值运算符 条件运算符 位运算符 运算符优先级 控制台输入 流程控制语句 分支结构

    2019-6-25 [JavaSE] 多重if.switch分支.while循环.do-while.for.循环之间的区别

    2019-6-27 [JavaSE] 跳转break、跳转continue.循环嵌套.数组.基本和增强for.[算法:冒泡.选择排序.插入排序]

    第二周:

    2019-6-28 [JavaSE] [算法:二分查找].复制.Arrys的常用方法.[项目案例:图书管理系统]

    2019-6-29 [JavaSE] 二维数组.不规则数组.面向对象.类的应用.方法的定义与调用(实例).[编程:杨辉三角形]

    2019-7-1 [JavaSE] 方法调用 成员与局部变量 方法参数传递 引用参数 可变参数 命令行传参

    2019-7-2 [JavaSE] 递归 封装 重载 构造 与普通方法和访问器的区别 构造块 成员变量初始化 构造器和访问器

    2019-7-4 [JavaSE] 程序包 访问修饰符 static修饰符与static块运行原理 [项目案例:图书管理系统2.0]

    2019-7-5 [JavaSE] Math随机数 Random 继承的语法和好处 里氏替换原则 方法重写 super 父类 子类 继承传递性 Object类

    第三周:

    2019-7-6 [JavaSE] Object类 equals() finalize() toString() final 实现关系 抽象类 接口语法与特点

    2019-7-8 [JavaSE] 多态及应用 instanceof运算符 类型转换 UML-泛化 实现 依赖 关联 字符串比较 API合集 compareTo() 源码

    2019-7-9 [JavaSE] String ,StringBuffer,StringBuilder比较 装箱和拆箱 日期类 正则

    2019-7-11 [JavaSE] 成员内部类 静态内部类 局部内部类 匿名内部类 函数式接口 Lambda表达式 异常处理 try-catch

    2019-7-12 [JavaSE] 泛型类 通配符 类型擦除 泛型接口 比较器 Comparable Comparator Enum枚举

    第四周:

    2019-7-13 [JavaSE] 集合 Collection List 集合遍历Iterator 底层技术[数组 链表] 单向双向 区别 ArrayList源码分析 链表的结点源码 add源码

    2019-7-15 [JavaSE] SET:HashSet LinkedHahSet TreeSet 队列 Collection Map 文件\

    2019-7-16 [JavaSE] 目录 字节流 文件流 转换字符流 缓冲流 数据流 对象流 字符流 节点流 自动资源释放

    2019-7-18 [JavaSE] 线程-新建/就绪/运行/阻塞/死亡 主线程 子线程-三种运行 线程优先级 中断-sleep/join/yield 同步 锁 synchronized/lock

    2019-7-19 [JavaSE] 单例多线程 死锁 wait与sleep 生产消费者模式 守护线程 网络 协议 IP 查询IP 域名 端口 URL查询 数据传输实例

    2019-7-20 [JavaSE] 反射的属性/方法/构造 装饰者模式 简单工厂

    练习题:

    第一周:

    2019-6-26 [JavaSE] For循环 4个练习题

    2019-6-26 [JavaSE] While循环 4个练习题

    第二周:

    2019-7-4 [JavaSE] 对象 5个练习题

    2019-7-4 [JavaSE] 访问权限 3个练习题

    2019-7-5 [JavaSE] 继承 3个练习题

    2019-7-7 [JavaSE] 抽象 2个练习题 接口 2个练习题

    第三周:

    2019-7-10 [JavaSE] 字符串 5个练习题 时间 2个练习题

    2019-7-11 [JavaSE] 内部类 4个练习题

    2019-7-12 [JavaSE] 泛型 枚举 排序 各一个练习题

    第四周:

    2019-7-14 [JavaSE] List接口 Set接口 练习题各一个

    2019-7-15 [JavaSE] 模拟队列 HashMap 练习题各1个

    2019-7-17 [JavaSE] 文件字节流 练习题2个

    总结

    [JavaSE] 阶段总结

    写在最后

    大数据中java是基础中的基础,这一部分的知识点不算多,所以一定要打好基础,让我们继续前进吧

    展开全文
  • JavaSE学习之路

    2010-04-26 16:11:00
    我们知道Java有三个方向,JavaSE,JavaME,JavaEE(老程序员可能会叫J2EE等,不过SUN已经改名了)。JavaME主要是嵌入式与游戏开发。那我们先看JavaSE,因为只有学好了JavaSE才可以学JavaEE,所以说JavaSE是JavaEE的根基...

    我们知道Java有三个方向,JavaSE,JavaME,JavaEE(老程序员可能会叫J2EE等,不过SUN已经改名了)。JavaME主要是嵌入式与游戏开发。那我们先看JavaSE,因为只有学好了JavaSE才可以学JavaEE,所以说JavaSEJavaEE的根基。

    Java的历史当然得了解,但除去与别人讨论卖弄工作基本用不到,您可以暂时放放,那天累了调剂学习。那能知道他的历史呢?google,baidu搜吧!以后要养成个意识,凡是不了解想明白的东西就上google,baidu,不仅仅限于编程知识。

    下面我们详细看JavaSE学习路线:

    1.Java跨平台原理,JDK的部署与HelloWorld。您得知道Java跨平台原理,知道怎么配置环境变量以及编写一个HelloWorld程序。

    2.程序的基本语法,比如什么是常量变量流程控制循环等任何一门编程语言都必备的知识,再学习下冒泡啊递归啊等算法,然后给数组排个序,打印个或实心或空心三角形等这些玩意。如果您学过C,C++这会非常轻松。

    3.学习JavaOOP,这是重点也是难点。你们要想听一遍课就熟练应用这些知识基本是不可能的。一个优秀的老师能把这些给你讲清楚就已经非常了不起了,同样一个优秀的学生能把这些概念背下来在看到类似的代码知道原因也非常了不起。至于创造,建议先别要求那么高。先把OOP众多概念背下来,以后JavaEE的学习中,在项目代码中再体会理解。所以这部分重点是理解加背概念。下面是平时讲课顺序

    a)   学习类和对象的知识知道什么是类与对象以及在java中如何创建类与对象。

    b)   包的知识

    c)   java中数据类型的分类(值类型,引用类型)

    d)   声明对象与创建对象

    e)   java中方法参数的两种传递方式。

    f)   构造方法

    g)   继承以及继承的特点(thissuper关键字)

    h)   封装,包括访问修饰符

    i)   抽象类,接口

    j)   多态(方法重载与方法重写),知道抽象类接口都是为多态服务

    k)   staticfinal关键字

    l)   用以上的知识做一个面向对象的某某管理系统

    4.异常包括分类以及五个关键字try catch finally throw throws

    5.集合框架

    6.到这如果想做个有成就感的程序可以直接学习JDBC,当然这需要您数据库的知识的支持(比如能建库建表,会增删改查的sql语句),然后可以做个控制台版本的某某管理软件。当然您这个系统一定要使用vo类,要使用DAO模式,最好能加上接口编写两个DAO实现,StatementPreparedStatement各一个,在此基础上更深刻的理解OOP编程。

    7.设计模式的知识这时您也可以学习一些,了解工厂,单态这两种设计模式,然后使用它重构您上面做好的管理系统。

    (到这里,如果您急的找工作就业,也算小有所成,可以做个合格的小师弟了,然后向大师兄努力,学习JavaWeb;如果您不急着就业,可以把基本功练的更扎实些,可以接着学习下面三部分的知识)

    8.多线程编程

    9.网络编程

    10.Java中的I/O操作

    (以上三部分知识有个共同的特点是相对于前面七点在工作项目代码中使用很少,但理论还是相当重要,比如作为程序员您总得知道进程线程的区别联系吧,而且好多工具的底层就用到这些知识,比如JavaWeb部分我们使用的WebServer一定是个多线程的容器,它要与客户端通信一定需要网络编程,要处理用户的请求数据给客户端相应数据一定需要流来读写,所以这部分知识必须学,只不过您可以先暂时放放以后再学)

    11.AWTSWING/SWT

    有会问java是不是不能进行C/S结构程序开发,当然可以了。Java开发C/S 结构程序可以使用两个技术,一个是AWT/SWING,这个是SUN搞出来的,比如Java以前非常火的一个IDE工具JBuilder就是用它开发的;还有一个是SWT,这个最早是由IBM发明的,用的语言还是JAVA,但API已经与SWING不同了,比如现在非常好用的Eclipse就是用它开发的。

    java开发C/S程序相对于winform除了能夸平台并没有什么优势可言,比如复杂的技术,比如没有很好的开发工具支持快速开发(jb还可以eclipse生成的代码太乱了),比如运行超多内存的占用,所以现在使用Java开发C/S程序的少之有少,企业的招聘岗位少之有少,那就不讲不学了吧!但您搞软件开发总得写过些C/S程序吧,总得知道C/S下如何做权限,如何共享数据这些基本知识吧,而且AWT/SWING中用到了很多优秀OO设计思想,更高兴的是学习完这可以把我们前面做的控制台版本的管理系统挂上形成一个可给用户交互使用的系统,所以您有时间,学吧,急着找工作,就别学了(如果找工作顺利,那以后估计永远也不会学了。)。

    12.Java的新特性

    以前喜欢说JDK1.5的新特性,现在Java7也出来了,也不算啥新特性,这部分知识包括:static import,泛型,注解,可变参数等,比如泛型应该在讲解集合框架中说明,这部分也应该学习,但注解建议了解。

    13.反射

    反射的知识也属于JavaSE的内容,也建议暂时不要学习,等到高级阶段,自己有一些代码经验积累后再详细学习,反射的代码虽然工作项目代码使用较少,但很多框架或者你搞底层开发也一定是用得到的。

    总结:以上的学习路线虽然写了13条,但如果您想速成的话先学习前面七条就可以了,或者从另外一个角度说,掌握了前面七条暂时可以像更高的阶段JavaWeb进发了!

     

    展开全文
  • 我的javaSE学习笔记

    2019-07-18 04:01:06
    layout: post title: “我的JAVASE自学笔记” date: 2019-05-18 20:23:25 +0800...学习书籍:《疯狂JAVA讲义》李刚 搭建命令行编译环境 下载软件jdk jdk是开发java要用到的工具,里面包括 ①jre (java ru...

    layout: post
    title: “我的JAVASE自学笔记”
    date: 2019-05-18 20:23:25 +0800

    我的JAVASE自学笔记

    作者:吴甜甜

    个人博客网站: wutiantian.github.io
    微信公众号: 吴甜甜的博客


    基础知识

    学习书籍:《疯狂JAVA讲义》李刚

    搭建命令行编译环境

    • 下载软件jdk

    jdk是开发java要用到的工具,里面包括 ①jre (java runtime environment) ②一些基础类文件

    对于①:里面有java虚拟机(最终编写的java代码真正被执行的地方)即可。
    对于②:做个比喻,如果你想盖房子,那这些基础类文件就是砖块。我们的程序基本上要全部依靠这些基础类文件去实现。

    • 环境变量的系统变量配置

    a、 ”Path” 环境变量,点击编辑,新增一个值,值为jdk的bin目录路径

    告诉计算机去哪找javac.exe 或者java.exe这个程序。计算机会遍历path环境变量的所有值.

    b、 新建一个名为 ”classpath” 的环境变量,并把值 设为”.”

    注意: 一定要是英文输入法,半角下的 ”.” 在windows系统下是当前目录的意思 , 告诉javac.exe 或者java.exe 怎么理解后面的参数.

    举个例子 : javac HelloWorld.java 这段命令。

    ①Path告诉计算机去jdk的bin目录下寻找”javac.exe”这个程序.

    ②classpath就告诉计算在当前文件夹寻找 Hello.java 这个文件。”.” 意味表示当前目录。所以我们需要使用cd命令进入到你创建文件的目录下。

    编译运行第一个java程序

    目标:第一个java程序是输出一行英文: ” Hello World!”.

    ①在合适的位置建立一个txt文本文件.并将后缀改为.java. 并将这个文件改名为HelloWorld.java并且记住这个文件所在的路径.

    ②编写一段HelloWorld的代码,注意课上所说的缩进,括号成对,输入法保持在英文 等习惯,这个时候不用知道这段代码为什么长这样.

    class HelloWorld{  
    	public static void main(String[] args){  
    		System.out.println("Hello World!");	  
    	}  
    	
    }
    

    ③按ctrl+s键保存一下写的代码.
    ④开始按钮搜索框,输入 ”cmd” 右击管理员权限,打开命令行,然后利用 cd 命令进入第一步创建的文件的目录下:

    a. javac命令 编译源文件成class文件.

    javac命令格式: javac+空格+文件名+”.java”
    注意:与“文件名”要一致。

    b. java命令执行class文件.

    java 命令格式: java + 空格+文件名

    注意:与文件的class名要一致。

    一个文件java程序里可以写多个class类,但编译后的文件有对应class类的多个执行文件,需要执行其中哪个编译后文件由类名决定。

    多个class名可以与文件名不一致。注意运行时的名字不要弄错。

    例如:
    文件名Entrance.java
    
    class Entrance1{
    	public static void main(String[] args){
    		System.out.println("HelloWorld!");
    	}
    }
    class A{}
    class B{}
    执行:javac Entrance.java//编译一个文件
    生成3个文件:Entrance1.class、A.class、B.class
    

    变量

    • 申请变量的完整格式

    对于计算机来说,申请一个变量会发生三件事:
    ①计算机会申请多少空间去存储这个变量的值
    ②计算机需要知道这块空间的一个凭证
    ③计算机要知道这个变量的值是什么。

    int age=18;

    对于上面这句完整的申请变量的语句,一定会告诉计算机①②③。
    那对于①来说,变量类型 int就告诉计算机会分配32 位(4个字节)空间来存储申请的变量的值
    那对于②来说,变量名字 age 就是可以访问那块内存的起始地址
    那对于③来说,变量的值 18 就告诉计算机那块内存存储的值是18.

    • 命名规定

    1.应该以字母、下划线或者美元符号开头
    2.变量名没有长度限制
    3.变量名对大小写敏感
    4.变量名不能是java的关键字(e.g: class public void等等)

    • 驼峰命名(规范)

    1.变量名应该用有意义的英文单词
    2.变量名字若只有一个英文单词,则所有的字母需要小写
    3.变量名字若由多个英文单词组成,则从第二个单词开始首字母大写

    类名

    • 写法:首字母大写

    • 一个类只能有一个public类class名,并且需要与文件名相同。(可以有其他非public的class类名)

    • 类与类之间的交流,多态性找到其他不同的类是核心。

    进制转换

    计算机的存储单位关系如下:
    1 MB=1024KB=10241000 Byte(字节)=10241000*8 Bit
    1 Byte(字节)=8 Bit(位)
    Bit(位)就是计算机最小的存储单位,叫做位。
    对于计算机来说,所有数据都是通过二进制存储的。计算机的每一个位只能表示 0或者1

    • 由大进制转小进制

    配凑法

    将十进制数字100 转换成 8bit二进制数字
    步骤:
    a. log2100=6.64 从而选取结果6,得到第一个数字 01000000=64 【拆取最大】

    b. 100-64=36 log236=5.17 从而选取结果5,得到第二个数字 00100000=32 【拼凑】

    c. 100-64-32=4 log24=2 从而选取结果2得到第三个数字00000100=4 并且余数为0

    d. 将这几个数字相加便得到01100100

    将下列十进制数字转换成4位7进制数字

    402
    步骤:
    a.402拆取,402=343+59, 最大值为7^3为1000
    b.59=49=10,49=7^2为 0100,10为 0013
    c.将3个二进制数相加,1000、0100、0013即为1113

    • 由小进制转大进制

    进制为底数的指数和

    将二进制数字01100100转换成十进制数
    结果:027+1*26+125+0*24+023+1*22+021+0*20=100

    原码和补码

    三者关系:十进制<->原码<->补码

    原码:是一种二进制表示法,分为符号位 和 数值域。

    符号位为0表示正数,符号位为1表示负数。数值域表示该数的大小(绝对值)。

    注意:符号位总是位于最左边,即最高位,除了符号位,剩下的所有位都被称为数值域。

    注意:用原码表示数字0的时候,有两种方法: 例如在用8bit(位),即8位二进制数存储数字时:0000 0000 和 1000 0000 都代表十进制数字0。

    所以原码和十进制不是一一对应。

    8bit转换:

    • 原码转换成十进制数

    ①00000101转换为十进制数的过程:120+021+122+023=+5
    ②10000101转换为十进制数的过程:120+021+122+023=5 但是最高位是1,所以这个原码10000101代表的十进制就是-5.

    • 十进制数转换成原码

    ①98用我们之前的方法写成二进制是1100010
    所以三个二进制数相加得到:1000 000+0100000+0000010=1100010
    所以这个时候我们补上符号位最左边,因为98是正数所以我们补上一个0在最左边得到0110 0010

    补码: 另外一种二进制表示法。范围:-128~+127=256 个

    • 补码和原码符号位一样,当一个原码为正整数时,补码和原码的数值域一样。

    原码转换为补码:
    0000 1110(原码):因为符号位上是0,所以是正整数,从而补码等于原码,所以补码也为 0000 1110.

    • 当原码为负整数时,将原码的数值域取反并且加1就是补码的数值域。
      (计算机存储数字时都是以补码的形式存储的!!!!)

    1000 1110(原码):符号位是1,所以是负整数,从而补码的数值域和原码不同,符号位依然为1,数值位取反,并且加1.最终10001110(原码)转换为补码为 1111 0010

    补码和十进制是一一对应。

    • 补码转换成原码:

    将补码看成原码,再次求补码即可。

    注意:在用补码表示十进制数字时,0只有一种表示方法:例如在8bit(位),即8位二进制数存储数字时:只有0000 0000 代表十进制数字0。

    Java基本数据类型

    • 数值类型根据表示范围大小排序
      1.整型排序: byte<short<int<long
      2.浮点排序: float<double
      3.所有排序: byte<short<int<long<float<double

    • 有什么用?
      数值型变量赋值:

    1 . 超过数的表示范围会报错!

    2 . 对于整数,java默认是int,对于小数java默认是double(例子:3是int,0.3是double)

    3 . 当我们在变量赋值的时候,大范围赋给小范围会报错,小范围赋给大范围没有问题!
    (小范围大范围,实际上小的会被转成大的)
    举例: double a=1; 等价于double a=1.0d;

    4 . 当对数字进行四则运算时,会选取最大范围的作为结果(例子:(3L+3)是long;0.3f+3是float)

    5 . byteshort比较特殊,当用int赋值给byteshort时,只要整数的值位于byte或者short本身的***范围内***,就不会报错(例子: byte=127正确; byte=128报错)

    6 . 如果你希望忽略精度丢失这种错误(一般都是大转小),可以采取强转的策略.但是注意精度丢失后的问题:
    举例: int a= (int)3.5;System.out.println(a);—>打印的结果是3

    • 编码集
      ascii标准编码集介绍:
      对每一个字符``,给定一个8位二进制数字与之对应,并且这个二进制数字(最高位固定位0,使用后面7位),然后这些二级制数字与字符的对应表```就叫做ascii标准编码集。

    注意:Java在存储char类型字符的时候并没有采用ascii标准编码集,而是采用unicode编码集。unicode编码集是一种十六位的二进制编码集
    例子: char c=97;等价于char c=’a’;

    运算符

    • 算数运算符

    1 . 常规运算符(+ - * / %)
    加减乘除求余四则运算,注意参考 java基本数据类型

    2 .自增运算符 (i++ ; ++i) 和 自减运算符( --i ; i–)
    我们采用 i++ 和 ++i 给大家讲解,自减运算符遵从同等的原理。

    例子:
    int i=1;
    i++; 整个式子的返回值为i的原值,即为1.
    ++i; 整个式子的返回值为i增加后的值,即为2.
    那对于i本身的值来说,无论是i++或是++i,只要经过自增运算符后,i的值都会增加1。

    所以当我们讨论i本身的值的时候 i++ 和++i 没有区别(都为一个值)。

    但是讨论整个式子的返回值时,区别如上!

    /**1.无论是a或者b,如果讨论```i本身的值```,那a和b都会让i本身的值增加1.  
     *2.如果讨论的是自增运算符作为一个``整体的``返回值,那就有区别了  
     *i++,作为一个整体的返回值是i的原值。  
     *++i,作为一个整体的返回值是i增加1后的值  
     *分析四块b=(a++)+(++a)+(a++)+(++a);    
     *0,2,2,4,则b为8  
     */  
    public class Entrance {  
    public static void main(String[] args) {  
    	int a=0;  
    	int b=(a++)+(++a)+(a++)+(++a);  
    	System.out.println(b);  
    }  
    }  
    
    • 关系运算符 ( > < >= <= ==)

    一般使用关系运算符返回一个布尔类型的值
    例子: boolean a=3>1;

    • 逻辑运算符 (! & | ^ && ||)

    1 .:加在一个布尔类型表达式前,表示取反。

    2 .&: 加在两个布尔类型表达式中间(返回值是布尔型)表示””的含义:只有两个布尔值都为true的情况下才为true. 若第一个布尔表达式为错误依然会执行第二个布尔表达式。也可以加在整型中间(返回值是整型),表示位与运算。

    //b=(a++>0)&(a++>0)
     * 第一块为0,第二块为2,左右两边都会运算,比较笨
     * 所以a为2,b为false
     * 加在两个布尔类型表达式中间(返回值是布尔型)表示”与”的含义:
     * 只有两个布尔值都为true的情况下才为true. 若第一个布尔表达式为错误,
     * 依然会执行第二个布尔表达式。也可以加在整型中间(返回值是整型),表示位与运算。
     */
    
    
    package firstDay;
    
    public abstract class ZiZeng {
    public static void main(String[] args) {
       int a=0;
       boolean b=(a++>0)&(a++>0);
       		System.out.println(a);
       		System.out.println(b);
    }
    }
    

    3 . |: 加在两个布尔类型表达式中间表示””的含义:只要两个布尔值有一个为true那么整个式子就是true. 若第一个布尔表达式为正确依然会执行第二个布尔表达式。也可以加在整型中间(返回值是整型),表示位或运算。

    4 .^: 加在两个布尔类型表达式中间,表示”异或”的含义:只要两个布尔值不同,那么整个式子就是true.

    1. &&: 加在两个布尔类型表达式中间,表示”短路与”的含义:只有两个布尔值都为true的情况下才为true. 若第一个布尔表达式为错误不会执行第二个布尔表达式。
    • b=(a++>0)&&(a++>0);
    • 短路与,聪明!只计算左边一个自加
    • 所以a为1,b为false

    6 . ||: 加在两个布尔类型表达式中间,表示”短路或”的含义:只要两个布尔值有一个为true那么整个式子就是true. 但是若第一个布尔表达式为正确,不会执行第二个布尔表达式。

    • 赋值运算符 =

    从右向左

    • 扩展赋值运算符 (+= -= *= /=)
      先赋值原值,再进行计算。不可直接:a+=5;
      例子: int a=5; a+=3; 等价于 a=a+3;

    控制语句

    • if else 结构

    嵌套结构,有有重合区间范围

    ①第一种结构(最完整结构)如下:

    if(布尔型表达式){  
    }  
    else if(布尔型表达式){  
    }  
    else if(布尔型表达式){  
    }  
    else{  
    }  
    注意: 其中的else if 的数量没有限制!!
    

    例如:

    	int grade=95;  
    		if (grade>90 ) {               //有重合区间范围  
    			System.out.println("优秀");//>90  
    		}else if(grade>80){  
    			System.out.println("良好");//<=90  >80,注意情况划分与排斥  
    		}else if(grade>70){  
    			System.out.println("合格");//<=80  >70  
    		}else{  
    			System.out.println("不合格");//<=70  
    

    如果不是最完整结构,可以逐渐去除,从而得到以下结构:

    ②第二种结构

    if(布尔型表达式){
    }
    else if(布尔型表达式){
    }
    else if(布尔型表达式){
    }
    

    相比较最完整的结构,很明显少了else的处理。

    ③第三种结构

    if(布尔型表达式){
    }
    else{
    
    }
    

    相比较最完整的结构,很明显少了else if的处理。只要不符合if情况就进else,并没有情况比较划分。

    ④第四种结构

    if(布尔型表达式){
    }
    

    相比较最完整的结构,很明显少了else if和 else的处理。不满足if条件就不进行操作。

    • switch结构

    申请变量语句;

    switch(变量名){  
    case xx:  
    break;  
    
    case xx:  
    break;  
    
    case xx:  
    break;  
    
    case xx:  
    break;  
    
    default:  
    break;  
    }  
    

    1 .对于基本数据类型来说,switch后面的变量类型都是范围小于等于int的.
    可以:byte,short,char,int. (因为char 是采用unicode字符集存储字符的,所以是16位的,小于int的范围).
    不可以:long,float,double,boolean.
    2 . case 相当于入口,一旦其中一个case匹配进入,那么后面的case就不起作用, 直到遇到break退出
    3 . 当没有case匹配时,会进入default。

    public static void main(String[] args) {
    	char grade='a';
    	switch(grade) {              //switch后面的变量类型都是范围小于等于int
    		case 'a':
    			System.out.println("优秀 ");
    			break;
    		case 'b':
    			System.out.println("良好");
    			break;
    		case 'c':
    			System.out.println("及格");
    			break;
    		default:
    			System.out.println("不及格");
    			break;//加不加都可以,已经到程序末尾。为了美观,可以加上
    

    循环结构

    注意细节的先后

    • for循环
    for (int i = 0; i < 10; i++) {
    	System.out.println(i);
    	System.out.println("循环结束");
    

    结果:9 循环结束
    执行顺序i = 0; i < 10;println(i);i++
    注意:for循环第一次是先进入循环体打印,再执行自增运算

    • while循环

      int i=0;
      while(i<10) {
        i++;
        System.out.println(i);
        System.out.println("循环结束");
        //结果:10	循环结束
        //执行顺序i = 0; i < 10;i++;println(i);```先自增,再打印```
      

    (这部分代码和上面的for循环一模一样,但如果将i++移到循环体的第一行,则就不和上面的for循环一样了)

    • break关键字
      定义:break关键字通常用来表示跳出当前循环.

    • 嵌套循环

    第一段代码:

    int i=0;  
    while(i<5){  
    System.out.println(“-”+i);  
    i++;  
    int j=0;  
    while(j<5){  
      System.out.println(j);  
      j++:  
    }  
    

    上面代码的输出结果为: -001234-101234-201234-301234-401234

    第二段代码:

    int i=0;  
    while(i<5){  
    System.out.println(“-”+i);  
    i++;  
    int j=0;  
    while(j<5){  
      System.out.println(j);  
      j++:  
      if(j==3){  
       break;  
    }  
    }  
    }  
    

    上面代码的输出结果为: -0012-1012-2012-3012-4012
    分析: break关键字只是每次跳出了第二层循环,使得j的3和4的值每次都没法输出。

    • 写出嵌套执行顺序
    3	int i=0;
    4   while(i<2){     
    5		System.out.println("1");
    6		int j=0;
    7		while(j<3){
    8			System.out.println("1");
    9			j++;
    10		}
    11		i++;
    12	}
    13	System.out.println("end");
    

    嵌套执行行号顺序:3456789 789 789 7 11 456 789 789 789 7 11 4 13

    • 带break的嵌套循环
    3	int i=0;
    4   while(i<2){     
    5		System.out.println("1");
    6		int j=0;
    7		while(j<5){
    8			System.out.println("1");
    9			j++;
    10			if(j==3){
    11				break;
    12			}
    13		}
    14		i++;
    15	}
    16	System.out.println("end");
    
    

    嵌套执行行号顺序:345678910 78910 78910 11 14 4 5678910 78910 78910 11 14 4

    • 复杂嵌套

    利用三层嵌套循环+控制语句(i,j,k),打印出
    00011012013
    1001101201301
    2001101201301
    3001101201301
    40011012013
    (注意:这些数字没有规律,只要你能用三层嵌套循环+控制语句输出这些数字即可。这些数字是连在一起的,你们使用System.out.print()这个方法就好,我回车是为了大家看起来方便)
    /分析:从第一列知i<5,从第一行知在01重复,所以k<2。所以i<5,j<4,k<2,刨去j=3时的2个空值/

    public static void main(String [] args){
    	for(int i=0;i<5;i++){
    		System.out.print(i);
    		for(int j=0;j<4;j++){
    			System.out.print(j);
    			if( (i==0&&j==3)||(i==4&&j==3) ){
    		    	break;
    			 }
    			 for(int k=0;k<2;k++){
    			 	System.out.print(k);
    			 }
    		 } 
    	 }
    }
    

    面向对象

    方法

    • 方法的调用

    对于java来说,只要满足下列条件之一就叫做不同的方法:
    1.名字不同
    2.参数列表不同
    (注:在这又没有和返回值没有任何关系!!!!java要求同一个类中不能定义相同方法!!)

    方法调用的四类总结:
    1.左边是引用,右边是对象。Person p1=new Person;
    2.左边是引用,右边是引用。Person p2=p1;//把符号右边引用所指向的对象赋给等号左边的引用。
    3.参数列表是引用,传进来是对象。test(new Person());
    4.参数列表是引用,传进来是引用。test(p1)

    • 方法参数中类型的转换

    基本数据类型中的小范围转大范围。

    1模板中参数列表中的大范围参数,在调用的时候可以使用小范围参数且小范围参数会被转换
    2.反之则不成立!

    • this关键字真正的用法

    this指向调用它的对象

    ①当前对象的凭证
    格式
    a. this.xx (xx代表成员变量)
    b. this.xx() (xx代表成员方法)
    注意: a中的xx为成员变量, b中的xx为成员方法

    ②调用其他的构造方法(只能在构造方法的第一行出现)
    格式: this+()
    注意:()里面为参数传递!

    数组

    • 一维数组的申请方式

    数组的长度是固定的,如果越界则会有异常产生。并且数组所开辟的内存是连续的,访问速度特别快

    ①先申明,再赋值(最麻烦)

    a.基本数据类型:

    int[] intArray=new int[3];  
    intArray[0]=0;  
    intArray[1]=1;   
    intArray[2]=2;  
    

    b.引用数据类型:

    Person[] personArray=new Person[3];  
    personArray[0]=new Person();  
    personArray[1]=new Person();  
    personArray[2]=new Person();  
    

    区别:如果是一个基本类型的数组,int[] intArray=new int[3],那么intArray[0]=intArray[1]=intArray[2]=0,那如果是Person[] personArray=new Person[3],则personArray[0]=personArray[1]=personArray[2]=null).

    关于默认赋值和null的讨论
    1.默认值的不同境遇
    ①int a;
    ②Student student;
    对于①首先大家可以看到的是int是一个基本数据类型,但没有初始化,但因为是基本数据类型, java会给a一个默认的0。
    对于②来说,student这个凭证没有任何权限,并且Student 并不是基本数据类型,没有默认赋值,所以student就等于null。
    2.null造成的结果
    假设Student 有一个成员变量,grade。则正确的访问是 Student student=new Student();
    student.grade去访问,因为这个时候student已经有了一块内存的凭证。但是如果仅仅是Student student; student并没有被赋予任何内存的凭证。所以这个时候如果调用student.grade就一定会报错。具体就是空指针的错误,会造成程序崩溃。

    ②申明和赋值一起进行(较麻烦)

    int[] intArray=new int[]{0,1,2};
    Person[] personArray=new Person[]{new Person(),new Person(),new Person()};

    ③申明和赋值一起进行(最简单)

    int[] intArray={0,1,2};
    Person[] personArray={new Person(),new Person(),new Person()};

    • 一维数组的遍历方式

    1.普通的循环结构进行循环
    ①for循环

    for(int i=0;i<intArray.length;i++){  
    	System.out.println(intArray[i]);  
    }  
    

    ②while循环

    int i=0;
    while(i<intArray.length){  
    System.out.println(intArray[i]);  
    i++;  
    }  
    

    2.for each循环

    int[] intArray=new int[3]; intArray[0]=0, intArray[1]=1; intArray[2]=2;  
    for(int unit:intArray){  
    System.out.println(**unit**);  
    }  
    

    (这个过程中不能对数组进行赋值!!能进行的仅仅是访问!!)

    • 二维数组的定义

    ①先申明,再赋值(最麻烦版本)

    
    int[][] intArray=new int[2][3];  
    intArray[0][0]=0; intArray[0][1]=1; intArray[0][2]=2;  
    intArray[1][0]=1; intArray[1][1]=1; intArray[1][2]=2;  
    
    Person[][] personArray=new Person[2][3];…  
    

    注意,这并不要求每个子数组的大小是一样的:

    int[][] intArray=new int[2][];  //创建两行数组
    intArray[0]=new int[2];  //第一行数组元素有2个
    intArray[1]=new int[3];  //第二行数组元素有3个
    intArray[0][0]=0;intArray[0][1]=1;  
    intArray[1][0]=0;intArray[1][1]=1; intArray[1][2]=2;  
    
    

    ②申明和赋值一起(较麻烦版本)

    int[][] intArray=new int[][]{ {0,1} , {0,1,2} };
    Person[][] …

    ③申明和赋值一起(最简单版本)

    int[] intArray= { {0,1},{0,1,2} };
    Person[][]…

    • 二维数组的遍历

    1.普通的循环结构进行循环

    数组按行,从左向右执行输出

    ①for循环

    for(int i=0;i<intArray.length;i++){   
    	for(int j=0;j<intArray[i].length,j++){  
    		System.out.println(intArray[i][j]);  
    }  
    }  
    

    ②while循环

    int i=0;  
    while(i<intArray.length){  
    	int j=0;  
    	while(j<intArray[i].length){  
    System.out.println(intArray[i][j]);  
    j++;  
    }  
    i++;  
    
    }
    

    2.for each循环

    for(int[] item:intArray){  
    for(int unit:item){  
    System.out.println(unit);  
    }  
    }  
    (这个过程中不能对数组进行赋值!!能进行的仅仅是访问!!)  
    
    • 二位数组还能怎么赋值?

    1.把二维数组的每一行再看成一个一维数组:

    int[][] a=new int[2][];
    a[0]=new int[]{0,1};
    a[1]=new int[]{1,2,3};
    

    2.数组内存示意图
    注意课上的示意图演示.
    代码举例子:

    int[] a=new int[3];  
    a[0]=0;  
    a[1]=1;  
    a[2]=3;  
    int[] b=a;  
    a[0]=4;   
    

    问b[0]是多少?
    是4.把b=a是共享内存的。

    Person[] a=new Person[3];
    Person[] b=a;
    a[0]=new Person();
    b[0]=new Person(19);
    System.out.println(a[0].age);//19。自从b=a```共享内存```后,不管a又new新对象,b都能一行代码改变掉!
    System.out.println(b[0].age);//19
    
    int[] a=new int[3];    
    int[] b=new int[3];  
    a[0]=0;  
    a[1]=1;  
    a[2]=3;  
    b[0]=a[0];    
    a[0]=4;  
    

    问b[0]是多少?
    是0。把索引值赋给b[0],只和当前状况有关。

    继承

    • 继承的特点

    一个直接父类

    首先,在现实世界中,一个父亲可以有多个孩子。但是每个孩子只能有一个父亲。所以在java中,也遵循同样的关系。就是一个类可以有很多子类,但是只能有一个直接父类。继承通过java的关键字 extends实现。

    举例:
    ①class Son extends Father{
    }
    ②class Daughter extends Father{
    }
    通过①,我们可以说Son成为了Father的子类,Father成为了Son的父类.
    通过②,我们可以说Daughter成为了Father的子类,Father成为了Daughter的父类.
    (总结: 这时候Father有两个子类,而对于Son或者Daugher来说,它们各自只有一个直接父类)

    • 继承的作用

    一个java类的基本结构,主要包括三个方面:①成员变量 ②成员方法 ③构造方法
    ①成员变量的继承:抛开权限这个话题来说,一个子类会继承父类所有的成员变量
    ②成员方法的继承:抛开权限这个话题来说,一个子类会继承父类所有的成员方法
    ③构造方法:
    需要满足条件:子类构造方法必须调用父类的构造方法。并且要在子类构造方法的第一行调用
    格式为super(参数列表).

    a.自己在每个子类的构造方法中老老实实地显式调用父类构造方法

    例如:

    
    class Father{
    	Father(){
    
    	}
    }
    
    class Son extends{
    	son(){
    		super();
    	}
    }
    
    

    b. 没有显式调用父类构造方法,则编译器会在每个子类的构造函数第一行为我们添加super(),即替我们添加了调用父类的无参构造方法。如果自己调用的话则不会为我们做任何事情。
    但如果有参则自动添加会有问题。例如:

    
    class Father{
    	Father(int a){
    
    	}
    }
    
    class Son extends{
    	son(){
    		super(a);
    	}
    }
    
    

    c. 使用this关键字在某个构造方法的第一行调用了其他构造方法,而其他构造方法调用了父类构造方法,那也是可以的。

    
    class Father{
    	Father(int a){
    
    	}
    }
    
    class Son extends{
    	son(){
    	this(3);//此第一行调用另一个super方法,替调用父类,所以两个方法都满足了。
    }
    	son(){
    		super();
    	}
    }
    
    
    • 继承的代码实质内容

    继承的代码实质内容是super

    例如:

    class A{
    	int age=20;
    
    	void test(){
    		System.out.println(age);
    	}
    
    }
    
    class B extend A{
    	int age=22;
    }
    
    那么实质B类中的语句是
    
    class B extend A{
    	int age=22;
    	void test(){
    		super.test();
    	}
    }
    
    
    • 子类可以在继承的基础上继续添加成员变量和成员方法

    • 重写

    缘由:继承的内容不好,需要更改成员变量与方法。

    ①重写的作用:重写完以后,子类就不会访问到父类的成员方法或者成员变量

    ②方法的重写:如果子类中所写的方法,方法的名字,方法的参数列表,方法的返回值类型都相同,则叫做对父类的方法进行重写。

    如果方法的名字或者,方法的参数列表有一个不一样,则我们所做的实际上是继续添加成员方法。

    ③成员变量的重写:只要子类成员变量的与父类成员变量的名字相同,则就叫做重写了父类的成员变量。
    复写父类的成员变量的时候可以替换父类中成员变量的类型。

    • super关键字总结

    ①用在子类构造方法中,表示对父类构造方法的调用,格式:
    super+(参数列表);

    ②用在子类的成员方法中,表示对父类成员方法的调用,格式:
    super.方法名字(参数列表);

    向上转型、向下转型(多态)

    一个类只有一个直接父类,但是会有很多间接的父类!

    • 向上转型的定义

    用父类的(包括直接父类和间接父类)凭证去指向子类的对象实例时,就叫做向上转型.

    例子:

    class A{}			
    
    class B extends A{}
    

    那B类是子类,A类是父类.

    对于B类,如果我们想使用B类. 则正常的实例化过程如下:
    B b=new B();
    那向上转型表示如下:
    B b=new B();
    A a=b;
    也可以一步到位:
    A a=new B();

    • 向上转型的特点

    ①对于成员方法
    去父类寻找这个方法(再次强调是通过方法名称和参数列表)
    a.若这个方法父类不存在,则不能通过编译。
    b.若这个方法父类存在,则访问的是子类的方法

    ②对于成员变量
    a. 父类不存在这个成员变量,则不能通过编译.
    b. 父类存在这个成员变量,仅仅能访问到父类的成员变量!

    例如:仅仅能访问到父类的成员变量
    
    class A{
    	int age=20;//父类不存在这个成员变量,则不能通过编译
    	void test(){  
    		System.out.println(age);
    	}
    }
    
    
    
    class B extends A{
    	int age1=100;//向上转型访问不了子类变量
    	
    }
    
    class Entrance{
    	public static void main(String[] args){
    		A a=new B();
    		a.age1=101;//提示报错:找不到变量agel
    	}
    }
    
    

    ③方法参数列表中的向上转型

    父类不存在这个成员变量,则不能通过编译,仅仅能访问到父类的成员变量!
    (若存在方法的重载,并且重载后的方法参数列表都是父类,则选取结构图中最近的方法调用)

    例如:列表中参数是父类,但使用哪个方法取决于传入对象

    class A{
    	int age=20;//父类不存在这个成员变量,则不能通过编译
    	void test(){  
    		System.out.println("test---A");
    	}
    }
    
    class B extends A{
    	int age1=100;//向上转型访问不了子类变量
    	void test(){  
    	System.out.println("test---B");
    	}
    }
    
    class Entrance{
    	public static void main(String[] args){
    		B b=new B();//调用时,可以是```B类型参数对象,向上转型A会调用B方法```
    		test(b);
    	}
    	
    	static void test(A a){	//模板的参数列表可以是父类型
    	a.test();//编译器自动做向上转型A a=b;
    	a.age;
    	}
    }
    

    例如:A–>B–>C

    
    class Entrance{  
    	public static void main(String[] args){  
    		B b=new C();  
    		test(b);//分步转,C转B再转A
    
    		C c=new C();
    		test(c);//一步C转A
    	}
    	static void test(A a){
    		a.test();
    	}
    }
    
    

    写代码测试向上转型的特点:包括成员方法的二个特点和成员变量的二个特点

    A是B的子类。注意这些特点有些需要子类复写父类的成员变量

    class B{
    int age=1;
    void test1(){
    System.out.println(age);
    }
    
    void test2(){
    System.out.println(“test2---B”);
    
    }
    
    
    }
    
    
    class A extends B{
    int age=2;
    int height=180;
    void test2(){
    System.out.println(“test2---A”);
    }
    
    void test3(){
    System.out.println(“specialA”);
    
    }
    }
    

    主方法中:
    B b=new A();//向上转型
    1 . b.test3();///编译不通过,因为B类中没有test3方法
    2 . b.test1();//打印的结果是1.因为子类会通过super.test1()将代码传递给B类大环境执行
    3 . b.test2();//打印的结果是”test2—A”
    4 .System.out.println(b.height);//编译不通过,因为B类不存在height这个成员变量
    5 .System.out.println(b.age); //打印结果是1.访问到的是B类的成员变量

    (成员方法的两条实际上展开是三条,成员变量是两条,所以加起来我写了5行代码)

    • 向下转型定义

    子类的凭证去指向父类的对象时,就叫做向下转型.
    class A{} class B extends A{} class C extends A{}

    那B类是子类,A类是父类.
    对于B类,如果我们想使用B类. 则正常的实例化过程如下:
    B b=new B();
    那向下转型表示如下:
    B b=new B();
    A a=b; //子类B向上转型成A类
    B b=(B)a; //把父类A再向下转型成子B类

    • 为什么有些时候要使用向下转型

    1.为了找到子类特有的成员变量或成员方法

    2.为了使用子类的成员变量的值

    注意:向下转型可能会产生运行时的错误:

    C c=new C();
    A a=c;		//A类指向C
    B b=(B)a;
    

    一般向下转型正确的话:
    ①一定经历了向上转型
    ②转型到的可以是对象的真实类型或者对象的父类(一个对象真正的类型是它自己本身的类)

    • 向下转型的特点

    ①可能会产生运行时的错误(编译不会报错)

    ②如果没有报错,对于转型后的访问和正常申明后(可能也涉及到向上转型)的访问没有区别

    例如:

    A a=new C();
    B b=(B)a;//a强转(B)是不能省略的,否则编译器报错
    

    和一步向上转B b=new C();没有区别。

    ③向下转型并不一定会转到真正的类型上,如果仍然转到的是一个父类。则遵从我们上面所说的向上转型的特点.

    例如:

    • 避免向下转型报错的办法

    编译的“两个”角度

    1.如果没有强转的小括号,左边的类型要是右边类型的本身或者父类

    例如:Person p=new Student();//完全OK
    例如:A a1=b;//要求左边的类型是右边类型的父类或者本身的类。

    如果有强转的小括号,左边的类型要是右边类型的子类

    例如:  
    Student st=new Student;    
    Person p=st;   
    
    例如:
    
    A a2=(A)b;//左边的类型和右边票的类型具有继承关系或者本身的类。
    

    下游不能指向上游,所以b前要强制转换(A) 。不强制的话,编译会错。

    是否通过编译,只看类型,不看对象的指向问题。
    强制转换的括号内容,为编译而生,看代码时不用理解它的作用。

    使用instanceof关键字

    ①关键字介绍:判断 对象 是不是 该类的子类或者本身。

    举例: a instance of A 即a是否是A类本身或者是A类的子类.

    ②具体使用:利用instanceof 判断后,进行向下转型从而使用子类特有的方法.

    例如:

    static void test(A a){
    	if(a instance of B){
    		B b=(B)a;
    	}
    }
    
    
    • 多态性

    ①多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,这就是多态性

    ②多态的存在有三个前提:
    1.要有继承关系
    2.子类要重写父类的方法
    3.父类引用指向子类对象

    “两点”本质与核心:
    1.找到执行哪些方法,由引用类型决定
    引用可能指向很多类型对象。但引用是Person类型,它就一定只能调用Person类方法。
    2.具体执行什么,要看指向对象类型
    调用Person类方法,具体会执行什么,要看指向对象类型。

    static关键字

    • 静态成员变量和成员方法

    ①定义: 被static关键字修饰了成员变量或成员方法

    ②作用

    a.第一步是调用构造函数实例化生成对象,类中调用普通成员变量或者成员方法。

    b.如果有静态成员变量或者成员方法,则可以 直接用: 类名+”.”)的方式直接调用.

    ③注意事项

    a.静态成员方法中不能直接使用 普通成员变量 和 普通成员方法,因为普通成员变量和普通成员方法的访问需要实例化

    例如:
    
    class A{
    	int age=10;		//普通变量
    	static void test(){
    		A a=new A();	//调用普通变量需要实例化
    		System.out.println(a.age);
    	}
    }
    
    class Person{
    	int age-10;
    	static void test(){
    		//System.out.println(age);//不能直接使用 普通成员变量
    		System.out.println(new Person().age);//只要告诉哪个对象的,就可以直接访问
    }
    

    b.实例化后,通过 凭证+”.”的方式也可以访问到静态成员变量和静态成员方法。

    通过“类名+.”的方法,不可以 间接访问普通成员方法。
    例如:
    class A{
    	int age=10;		
    	static void test(){
    
    		A a=new A();//间接访问实例化的普通成员方法
    		a.test1();//普通变量
    
    		//test1();//编译不通过,间接访问未实例化的普通成员方法
    
    	}
    	void test1(){
    
    	}
    }
    
    “类名+.”与`凭证+”.”的方法访问都可以
    Person.test();//“类名+.”的方法
    new Person().test();//`凭证+”.”的方法
    Person p=new Person();
    p.test();//`凭证+”.”的方法
    

    c.但请注意静态成员变量一旦改变,则所有方式访问到的值都会全部改变

    例如:
    
    class A{
    	static int age=10;	
    }
    
    主函数
    A a1=new A();
    A a2=new A();
    A age=11;//这里,不管是A、a1、a2赋值都会指向这个数
    System.out.println(a1.age);
    System.out.println(a2.age);
    System.out.println(A.age);
    

    静态代码块和普通代码块

    ①定义

    a为静态代码块, b为普通代码块

    a: static{                                         
    		System.out.println(“我是静态代码块”);
    }
    
    b: {
    		System.out.println(“我是普通代码代码块”);
    }
    

    ②实例化过程:执行顺序:

    父类静态代码块->子类静态代码块->父类普通代码块->父类构造方法->子类普通代码块->子类构造方法

    代码
    class Person{
    	static{
    		System.out.println("我是Person的静态代码块");
    	}
    	{
    		System.out.println("我是Person的普通代码块");
    	}
    	static int ageStatic=11;
    	A a=new A();//若写在普通代码块后面,插入时也应该在普通代码块后面
    	Person(){
    		System.out.println("我是Person的构造函数");
    	}
    	void test(){}
    	static void testStatic(){}
    	public static void main(String[] args){
    		Person p=new Person();
    	}
    }
    class A{
    	A(){
    		System.out.println("我是A的构造函数");
    	}
    }
    
    实际运行代码顺序
    class Person{
    	static{
    		System.out.println("我是Person的静态代码块");//第一步
    	}
    	{
    		System.out.println("我是Person的普通代码块");
    	}
    	static int ageStatic=11;
    
    	Person(){
    		System.out.println("我是Person的普通代码块");//第三步,插入
    		A a=new A();//第四步,插入
    		System.out.println("我是A的构造函数");//第四步,插入
    		System.out.println("我是Person的构造函数");//第五步
    	}
    	void test(){}
    	static void testStatic(){}
    	public static void main(String[] args){
    		Person p=new Person();//第二步
    	}
    }
    
    
    例如,继承的顺序
    class Person extend A{
    	static{
    		System.out.println("我是Person的静态代码块");
    	}
    	B b=new B();
    	{
    		System.out.println("我是Person的普通代码块");
    	}
    		static int ageStatic=11;//自动执行
    		Person(){
    			//系统自动添加super();
    			//插入B b=new B();
    			//插入System.out.println("我是Person的普通代码块");
    		System.out.println("我是Person的构造函数");
    	}
    
    
    
    	void test(){}
    	static void testStatic(){}
    	public static void main(String[] args){
    		Person p=new Person();
    	}
    }
    class A{
    	A(){System.out.println("我是A的构造函数");}
    }
    
    class B{
    	B(){System.out.println("我是B的构造函数");}
    }
    
    顺序为
    
    class Person extend A{
    	static{			 //第一步
    		System.out.println("我是Person的静态代码块");
    	}
    
    		static int ageStatic=11;//自动执行
    		Person(){         //第二步
    		super();//系统自动添加
    		System.out.println("我是A的构造函数");//执行class A
    		B b=new B();//插入
    		System.out.println("我是B的构造函数");//执行class B
    		System.out.println("我是Person的普通代码块");
    		System.out.println("我是Person的普通代码块");
    		System.out.println("我是Person的构造函数");
    
    例如,继承的顺序
    class Person extend A{
    	static{
    		System.out.println("我是Person的静态代码块");
    	}
    	A a=new A();
    	{
    		System.out.println("我是Person的普通代码块");
    	}
    		static int ageStatic=11;//自动执行
    		Person(){
    			//系统自动添加super();
    		A a=new A();
    		System.out.println("我是Person的普通代码块");
    		System.out.println("我是Person的构造函数");
    	}
    
    
    
    	void test(){}
    	static void testStatic(){}
    	public static void main(String[] args){
    		Person p=new Person();
    	}
    }
    class A extend B{
    	A(){System.out.println("我是A的构造函数");}
    }
    
    class B{
    	B(){System.out.println("我是B的构造函数");}
    }
    
    执行顺序
    
    
    	static{
    		System.out.println("我是Person的静态代码块");
    	}
    
    		static int ageStatic=11;//自动执行
    		Person(){
    		super();
    		A a=new A();
    		System.out.println("我是B的构造函数");
    		System.out.println("我是A的构造函数");
    
    		A a=new A();
    		System.out.println("我是B的构造函数");
    		System.out.println("我是A的构造函数");
    		
    		System.out.println("我是Person的普通代码块");
    		System.out.println("我是Person的构造函数");
    
    
    

    final关键字

    ①申明一个常量:

    e.g: final int a=3;

    ②申明一个不可以被继承的类:

    final class……

    ③申明一个不可以被重写的方法

    final void eat(){}

    抽象类

    ①定义:拥有抽象方法的类就是抽象类,并不是只有抽象方法,抽象类必须使用abstract关键字声明。(有其他方法也行,并不是只有抽象方法。)

    没有方法体的方法就是抽象方法,必须要使用abstract关键字修饰,否则会报错。

    ②与普通类的比较:普通类是一个完善的功能类,可以直接产生实例化对象,而抽象类是指想因为拥有方法体为空的抽象方法,并不能直接产生实例化对象

    ③使用:

    a. 定义一个子类继承抽象类, 重写抽象类之中的全部抽象方法

    abstract Printer{
    	abstract void print();
    
    }
    
    class HuipuPrinter extends Printer{		//子类与父类
    	void print(){
    		System.out.println("惠普式打印");
    	}
    }
    

    b.如果子类没有全部重写父类的抽象方法,则必须将子类也必须定义为为abstract类

    abstract Printer{
    	abstract void print();
    	abstract void color();//未重写该方法
    }
    
    abstract class HuipuPrinter extends Printer{	//有未重写方法仍需定义为abstract类
    	void print(){
    		System.out.println("惠普式打印");
    	}
    }
    

    c. 可以直接访问静态变量和静态方法

    abstract class Printer{
    	static int age=1; 
    	static void test(){
    		System.out.println("1");
    	}
    	abstract void print();
    	abstract void color();//未重写该方法
    }
    
    class Entrance{
    	public static void main(String[] args){
    		Printer.test();
    	}
    
    
    

    d. 可以添加普通成员变量和普通成员方法,但是不可以直接访问,因为要求实例化

    abstract class Printer{
    	int age=10;//不妨碍定义普通成员变量
    	abstract void print();
    	abstract void color();//未重写该方法
    
    	void test(){
    
    	}
    }
    
    abstract class HuipuPrinter extends Printer{	//有未重写方法仍需定义为abstract类
    	void print(){
    		System.out.println("惠普式打印");
    	}
    }
    
    	void color(){}
    
    class Entrance{
    	public static void main(String[] args){
    		HuipuPrinter a=new HuipuPrinter();//不可以直接访问,要求实例化
    		a.print();
    		a.age=100;
    	}
    
    

    e.不能用final修饰抽象类或者抽象方法

    因为final修饰会导致无法复写抽象类或者抽象方法

    • 抽象类中的向上转型

    当子类继承抽象类的时候,则抽象类成为父类,这个时候利用向上转型可以产生多态

    接口

    ①全局常量

    举例:

    static finalint age=18;

    特点:

    a. 访问方式可以是类名+”.”或者是凭证+”.”(static特点)

    b. a中所有方式访问到的值都相等(static特点)

    c. 这个值不会改变(final特点)

    ②定义: 只由抽象方法,全局常量或者完整静态方法组成的特殊类。

    定义任何一个变量,编译器自动加上public static final修饰成份
    定义任何一个方法,编译器自动加上public abstract修饰成份
    
    interface Person{
    	static final int age=20;	//编译器也会帮加上成public static final int age=20;
    	int height=100;	//若定义普通成员变量,编译器也会帮加上成public static final int height=100;
    	void test();	//编译器也会帮加上成public abstract void test();
    	static void test1(){
    
    	}
    }
    

    ③与普通类的比较:普通类是一个完善的功能类,可以直接产生实例化对象,而接口只拥有抽象方法和全局变量或者完整静态方法,不可以直接产生实例化对象

    ④使用:

    a. 定义一个子类实现接口, 覆写接口中的所有抽象方法

    class Entrance{
    	public static void main(String[] args){
    		Student st=new Student();
    	}
    }
    
    
    	class Student implements Person{		//implements对应interface使用
    	public void test(){	//默认权限是小于public的,所以必须让权限大于public
    											//子类在复写时不能低于父类权限
    	}
    
    
    interface Person{	//父类Person
    	public static final int age=20;//接口中定义变量和方法时,默认修饰符为public static final 和public abstract
    
    	public abstract void test();//父类
    }
    

    b.如果子类没有覆写接口中的所有抽象方法,则必须将子类定义为abstract类

    例如: abstract class Student implements Person

    c.不能用final修饰

    final修饰会导致子类无法使用它

    d.在接口中定义变量和方法时,默认修饰符为public static finalpublic abstract

    e. 可以直接访问全局常量和静态方法

    ⑤接口和抽象类的比较

    不同点:
    a.抽象类可以编写普通成员变量普通成员方法,接口则不可以,接口只能有抽象方法和全局常量和完整静态方法

    b.抽象类需要继承,而继承需要遵守单继承的特点,而接口需要实现,实现没有这个规定。所以一个类只可以继承一个抽象类,却可以实现很多接口。用逗号分隔。

    例如:abstract class Student implements Person,A//复写Person与A方法可以实例化东西了

    c.对于不同的设计模式,需要在接口和抽象类中作出选择。

    相同点:
    a.若想实例化抽象类或是接口,则需要子类去继承或者实现,然后重写其中所有的抽象方法

    b. 无论是继承还是实现,抽象类或是接口都会成为父类

    c. 因为抽象类或是接口都可以成为父类,一定记住向上转型对抽象类或是接口也适用!!!

    抽象类:写都有的功能
    接口:写不是每个都有的功能,可供复写
    
    
    interface Alarm{			//不是每个门都有的功能则定义成接口
    	abstract void alarm();
    }
    abstract class Door{	//每个门都有的功能
    	void open(){}		//每个门都一样的功能写成成员方法
    	void close(){}
    }
    
    abstract void voice();//每个门都一样的功能但实现起来不一样,就定义成抽象方法
    
    class MuDoor extends Door{
    
    }
    class TieDoor extends Door{
    
    }
    
    class TongDoor extends Door implements Alarm{}
    
    

    回调

    ①定义:当一个类的一个对象完成某件事以后,这个对象通知另外类的一个对象开始执行另一件事.

    ②代码举例:

    class HairTeacher{
    
    void makeHandsome(){
    
    }
    
    }
    
    class HairWasher{
    HairTeacher hairTeacher ;
    
    void setHairTeacher(HairTeacher hairTeacher){	
    this. hairTeacher = hairTeacher;
    }
    
    	void washHair(){
    		洗完了;
    		hairTeacher.makeHandsome ();
    	}
    }
    
    
    class Entrance{
    	public static void main(String [] args){
    		HairTeacher hairTeacher =new HairTeacher ();//开辟内存
    		HairWasher hairWasher=new HairWasher();
    		hairWasher. setHairTeacher(hairTeacher);//洗发妹妹心里装着理发师
    		hairWasher.washHair();//洗发妹妹通知理发师工作
    	}
    }
    
    • 回调真正写法

    考虑下面这个问题:
    如果洗发小妹妹不仅仅要通知理发师,还可能要通知化妆师或服装师,应该怎么办?
    可以让洗发小妹妹的心里装着理发师,化妆师,服装师的父类。比如就是美学师,那然后让理发师,化妆师,服装师继承美学师.

    class BeautyTeacher{	//父类美学
    
    	void makeHandsome(){
    		美学法变帅;
    	}
    
    }
    
    class HairTeacher extends BeautyTeacher{
    	void makeHandsome(){
    		头发法变帅;
    	}
    
    }
    
    class DressTeacher extends BeautyTeacher{
    void makeHandsome(){
    		衣服法变帅;
    	}
    
    }
    
    class FaceTeacher extends BeautyTeacher{	
    
    void makeHandsome(){
    		粉底法变帅;
    	}
    
    }
    
    
    
    
    class HairWasher{
    BeautyTeacher beautyTeacher;//洗发师心中装着父类美学师
    
    void setBeautyTeacher (BeautyTeacher beautyTeacher){
    this. beautyTeacher = beautyTeacher;
    }
    
    
    	void washHair(){
    		洗完了;
    		beautyTeacher.makeHandsome();
    	}
    }
    
    
    
    
    
    class Entrance{
    	public static void main(String[] args){
    	① HairTeacher hairTeacher=new HairTeacher();
    	 	HairWasher hairWasher=new HairWasher();
    hairWasher. setBeautyTeacher(hairTeacher);
    		hairWasher.washHair();
    
    	② DressTeacher dressTeacher=new DressTeacher();
    	 	HairWasher hairWasher=new HairWasher();
    hairWasher. setBeautyTeacher(dressTeacher);
    		hairWasher.washHair();
    
    ③ FaceTeacher faceTeacher=new FaceTeacher ();
    	 	HairWasher hairWasher=new HairWasher();
    hairWasher. setBeautyTeacher(faceTeacher);
    hairWasher.washHair();
    	}
    
    }
    

    注意:实际上并不需要美学师里面的变帅方法,因为每个具体的类(例如发型师,服装师,造型师)会复写这个方法,所以如果我们把美学师变成一个抽象类,变帅方法变成抽象方法,如下:

    abstract class BeautyTeacher{
    	abstract void makeHandsome();
    
    
    }
    

    那这样做还不是完美的,因为如果用抽象类,那每个具体的类(例如发型师,服装师,造型师)必须继承美学师,为了一个洗头小妹的通知作用,浪费了宝贵的继承机会,所以最好的写法是将美学师变成一个接口,让每个具体的类(例如发型师,服装师,造型师)去实现美学师,如下:

    interface BeautyTeacher{
    	abstract void makeHandsome();
    }
    
    class HairTeacher implements BeautyTeacher{
    	void makeHandsome(){
    		头发法变帅;
    	}
    
    
    
    }
    
    class DressTeacher implements BeautyTeacher{
    void makeHandsome(){
    		衣服法变帅;
    	}
    
    }
    
    class FaceTeacher implements BeautyTeacher{	
    
    void makeHandsome(){
    		粉底法变帅;
    	}
    
    }
    
    

    package关键字

    • 作用:package关键字用来定向输出编译后的class文件

    ①对于每个源文件(.java结尾)来说,你可以编写任意数目的类。所以一个.java文件可以产生很多.class文件。

    (注意:每个源文件只能有一个public的类,并且public类名要和文件名保持一致。)

    ②当我们想要让一个源文件产生的.class文件位于一个目标文件夹时,需要使用package关键字

    ③这时候为了使package关键字生效,我们需要使用javac+空格+ -d+.+空格+文件名字+.java的方式编译(多了-d . 新建文件夹名,所有编译后的文件都放到新建文件夹里才行)

    ④为了访问一个类(例如拿到一个凭证或者调用构造方法实例化),若两个class文件不位于同一个包中,需要使用包名+类名的方式访问(所有的访问都是基于类名的,源文件名称并不重要)

    import关键字

    • import关键字导入类,用来简化全路径访问

    由于package关键字的存在导致很多class文件位于不同的包中,若访问不同包的类时,我们需要使用完整的访问方式,即包名+类名的方式访问,那import关键字可以让我们的写法更简单. (注意课上举的例子,使用完import关键字后,就可以省略前面的包名直接以类名方式去访问)

    • 意义
      为了省略包名
    Entrance.java
    
    package b;
    import a.b.Person;	//导入a文件夹下的b文件夹下的Person类
    class Entrance{
    	public static void main(String[] args){
    		Person1 p=new Person1();
    	}
    }
    
    Person.java
    
    package a.b;		//a文件夹下的b文件夹
    public class Person{}
    
    

    访问修饰符

    四种权限

    修饰词 本类 同一个包的类 继承类 其他类
    private J X X X
    无(默认) J J X X
    protected J J J X
    public J J J J

    内部类

    内部类主要可以分为四种:成员内部类,静态内部类,匿名内部类,局部内部类。

    • 成员内部类

    ①定义: 把一个类当做普通成员变量那样定义。

    class OuterClass{
    	int a=0;
    	class InnerClass{
    	int b=1;
    
    	void test(){			//成员内部类
    	System.out.println(a);	//内部类的内部可以直接访问外部成员变量
    //访问外部成员变量一定是内部类中有张票赋予外部类权限
    //(OuterClass.this.a)中系统自动加的OuterClass.this指向外部类的调起内部类void test()的对象所依靠的OuterClass对象
    }
    }
    }
    //主方法中:
    	OuterClass out=new OuterClass();//依赖外部类的对象访问内部类
    	OuterClass.InnerClass inner=out.new InnerClass();//见使用第a、b条
    

    ②成员内部类的使用:

    a. 内部类的票的类型有固定格式为 外部类名称+”.”+内部类名称,构造方法new +内部类类名+”()”;

    b. 如果要生成内部类的实例,需要依靠外部类的实例.

    c. 内部类会持有外部类类型的一张票( 注意内存示意图 ) ,可以利用所持有的这张外部类类型的票直接访问外部类成员变量成员方法(包括静态成员变量和成员方法)

    d. 外部类不可以直接访问到内部类的任何成员变量和成员方法

    class OuterClass{
    	int ageOuter=1;
    
    	void testOuter(){
    		OuterClass.Inner asd=this.new.InnerClass();//asd访问ageInner
    		System.out.println(ageInner);
    	}
    	class InnerClass{
    		int ageInner=2;
    
    		void testInner(){		
    	System.out.println(ageOuter);
    
    

    e. 注意内部类中两种不同的this所指向的内存区别

    f. 实际的编译会生成两个class文件,分别为OuterClass.class和OuterrClass$InnerClass.class

    g. 内部类里面不能定义静态的成员变量或者成员方法
    (完全掌握的标准 是你可以将内部类提出来写 并且达到内部类的效果,这里需要的基础是对this关键字作为凭证的用法完全掌握)

    • 静态内部类

    ①定义: 把一个类当做静态成员变量那样定义。

    class OuterClass{
    	static int a=0;
    void test(){
    	System.out.println(OutClass.InnerClass.b);
    }
    static class InnerClass{
    		static int b=1;
    void test(){
    System.out.println(a);
    }
    }
    }
    

    ②静态内部类的使用:

    a.内部类的票的类型有固定格式为外部类名称+”.”+内部类名称

    b.如果要生成内部类的实例,不需要依靠外部类的实例

    c. 内部类只可以直接访问到外部类的静态成员变量静态成员方法

    d. 外部类不可以直接访问到内部类的任何成员变量和成员方法(包括静态)

    d. 实际的编译会生成两个class文件,分别为OuterClass.class和OuterrClass$InnerClass.class

    • 匿名内部类

    ①出现了一个实例,这个实例并不属于所编写的任何模板,并且都伴随着向上转型的过程

    ②代码举例:

    ③在①中出现的实例若出现在成员方法中或是成员变量中,则这个实例实际上可以转换成成员内部类的一个实例,则这个实例拥有一个外部类实例的权限,具有成员内部类所有特点。如果出现在静态方法中静态成员变量中,则这个实例实际上可以转换成静态内部类的一个实例,则具有静态内部类的所有特点。

    • 局部类

    异常

    运行时的异常特点:若程序不幸在运行时抛出了异常,则后面的代码都不会得到执行

    异常层次结构 二级 三级 四级
    Error VirtualMachineError
    Throwable AWTError
    Exception RuntimeException ArithmeticException
    NullPointerException
    lndexOutOfBoundException
    IOException EOFException
    FileNotFoundException
    • 抛出Exception

    无论是哪种exception,我们都可以在方法体使用代码抛出,使用throw关键字.

    例如: throw new IOException(); throw new RuntimeException();

    ①对于IOException我们抛出后,编译前必须处理.

    a.处理方法1: 甩锅,使用throws关键字跟在方法的后面表示对异常向上抛出:

    void setAge(int age) throws IOException{	//甩锅
    If(age<0){
      throw new IOException();//甩锅
    }
    }
    

    b.处理方法2: 使用try catch结构处理。 真正处理:

    void setAge(int age){
    If(age<0){
      try{
    throw new IOException();
    }catch(Exception e){
    
    }finally{
    }
    }
    }
    

    ②对于RuntimeException抛出后,不需要在编译前处理默认做“甩锅”处理

    void setAge(int age) {
    If(age<0){
      throw new RuntimeException();//RuntimeException抛出后,不需要在编译前处理
    }}
    
    • 方法调用的层级关系

    首先,方法调用的层级顺序。

    如果①调用了②,②调用了③,③调用了④。。。

    依次类推,则这些方法会存在上下级关系。

    • 对于异常的处理

    ①throws关键字向上级抛出注意上下级关系),如果抛到主线程程序就会崩溃.

    ②使用try catch finally 结构捕获异常

    a.标准结构:

    try{
    
    }catch(Exception e){
    }finallly{
    }
    

    采用标准结构,将可能发生异常的代码放在try内部,若有exception抛出,则进入catch代码,finally内部的代码无论如何都会被执行

    b.非标准结构:

    try{
    
    }catch(EOFException e){
    
    }catch(FileNotFoundException e){
    
    }catch(Exception e){
    
    }finally{
    }
    注意:catch的参数范围要是从小到大的。
    
    try{
    
    }finally{
    
    }
    
    

    注意: 利用finally代码块的代码无论如何都会运行的特点。 通常用在关闭IO流,防止代码发生异常后,代码停止运行然后导致的IO流关不掉

    c.执行顺序

    注意try catch finally 的执行顺序,主要是finally内部的代码一定会被执行。

    • 运行阶段异常的特点

    ①如果是运行阶段,对于两种异常,都需要在某个方法的调用使用try catch finally结构处理,否则一直往上级抛出,抛到主线程程序就会出问题。(在未学习多线程的情况下,默认只有主线程)

    ②对于IOException因为编译要求我们处理异常,如果不使用try catch finally处理,则一定会显式得使用throws甩锅从而通过编译。

    RuntimeException编译前不需要写额外代码处理异常,但是实际上运行的时候如果一直没有try catch finally结构去处理,最终会一层层抛出直到主线程,然后程序就会崩溃。(在未学习多线程的情况下,默认只有主线程)

    字节流

    • 概念

    1.Java对数据的操作是通过流的方式

    2.Java用于操作流的类都在IO包

    3.流按流向分为两种:输入流,输出流

    4.流按操作类型分为两种:

    a.字节流: 可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的

    b.字符流: 只能操作纯字符数据,比较方便。

    • IO流常用父类

    1.字节流的抽象父类: InputStream和OutputStream

    2.字符流的抽象父类:Reader 和 Writer

    (注意:使用前,导入IO包中的类,使用时,进行IO异常处理,使用后,释放资源)

    类型 字节流 字符流
    操作 纯文本,网络数据,图片 纯文本
    输入流 lnputStream Reader
    输出流 OutputStream Writer

    字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点

    所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列

    字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

    字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的 但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的 。

    字节流和字符流的操作过程:

     以文件操作为例,主要的操作流程如下:
    
    1 使用File类打开一个文件
    
    2 通过字节流或字符流的子类,指定输出的位置
    
    3 进行读/写操作
    
    4 关闭输入/输出
    
     IO操作属于资源操作,一定要记得关闭 
    
    • 子类FileInputStream的使用

    read() 方法一次读取一个字节并且返回值是一个int

    FileInputStream fis = new FileInputStream("aaa.txt");	
    int b;										
    while((b = fis.read()) != -1) {						
    System.out.println((char)b);	//32位强转为8位,然后去字符集里找对应符号打印				
    }
    fis.close();		
    

    为什么返回一个int:
    因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111,那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完.

    • FileOutputStream的使用
    FileOutputStream fos = new FileOutputStream("bbb.txt");	
    fos.write(97);								
    fos.write(98); 
    fos.write(99);
    fos.close();
    (write方法参数列表是一个int,但是会将前面的24位去掉,所以结果是写出一个byte)
    
    • 拷贝一个文件

    1.效率最低的方法:

    FileInputStream fis = new FileInputStream("computer.jpg");	
    FileOutputStream fos = new FileOutputStream("copy.jpg");
    int b;
    while((b = fis.read()) != -1) {
    fos.write(b);
    }
    fis.close();
    fos.close();
    

    2.改进:
    思路:一次读取一个文件的所有字节,然后一次写出,代码如下:

    FileInputStream fis = new FileInputStream("致青春.mp3");
    FileOutputStream fos = new FileOutputStream("copy.mp3");
    byte[] arr = new byte[fis.available()];			//	
    fis.read(arr);											
    fos.write(arr);											
    fis.close();
    fos.close();
    (由于java虚拟机分配内存的问题,这种方法不推荐)
    

    3.再改进(小数组方法)

    思路:一次读取一定长度的数组.

    FileInputStream fis = new FileInputStream("致青春.mp3");
    FileOutputStream fos = new FileOutputStream("copy.mp3");
    int len;
    byte[] arr = new byte[1024 * 8];	//读1024 * 8长度				
    while((len = fis.read(arr)) != -1) {
    fos.write(arr, 0, len);							
    }
    		
    fis.close();
    fos.close();
    

    4.用jdk给封装的类

    a.BufferedInputStream:

    • 内置了一个缓冲区(数组)
    • 会一次性从文件中读取8192个, 存在缓冲区中
    • 直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个

    b.BufferedOutputStream

    • BufferedOutputStream也内置了一个缓冲区(数组)
    • BufferedInputStream会一个字节一个字节将自己的数据赋给BufferedOutputStream的缓冲区(发生在内存中,速度很快)
    • 直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
    FileInputStream fis = new FileInputStream("致青春.mp3");			
    BufferedInputStream bis = new BufferedInputStream(fis);		
    FileOutputStream fos = new FileOutputStream("copy.mp3");		
    BufferedOutputStream bos = new BufferedOutputStream(fos);		
    int b;
    while((b = bis.read()) != -1) {		
    bos.write(b);
    }
    bis.close();						
    bos.close();
    

    注意:小数组的读写和带Buffered的读取哪个更快?
    a. 定义小数组如果是8192个字节大小和Buffered比较的话,定义小数组会略胜一筹,因为读和写操作的是同一个数组
    b.Buffered操作的是两个数组

    • 利用try-finally结构处理IO流中可能产生的异常
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
    	fis = new FileInputStream("aaa.txt");
    	fos = new FileOutputStream("bbb.txt");
    	int b;
    	while((b = fis.read()) != -1) {
    		fos.write(b);
    }
    } finally {
    	try {
    		if(fis != null)
    			fis.close();
    		}finally {
    			if(fos != null)
    				fos.close();
    		}	
    }
    

    字符流

    • 概念

    a.字符流是可以直接读写字符的 IO 流

    b.字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.

    • 顶层的抽象类为Reader和Writer.

    四个具体的子类:

    ①FileReader和 InputStreamReader

    ②FileWriter和 OutStreamWriter

    • FileReader的使用

    a. FileReader类的read()方法可以按照字符大小读取

    代码如下:
    FileReader fr = new FileReader("aaa.txt");				
    int ch;	
    while((ch = fr.read()) != -1) {			//读一个字节,在高位添24个0				
    System.out.println((char)ch);//抛弃了16个0,变成char的16位二进制						
    }
    fr.close();											
    
    • FileWriter的使用

    FileWriter类的write()方法可以自动把字符转为字节写出.

    代码如下:
    FileWriter fw = new FileWriter("aaa.txt");
    fw.write(‘我’);
    fw.close();
    
    • 利用输入字符流和输出字符流拷贝文本文件

    ①最低效

    FileReader fr = new FileReader("a.txt");
    FileWriter fw = new FileWriter("b.txt");
    int ch;
    while((ch = fr.read()) != -1) {
    fw.write(ch);
    }	
    fr.close();
    fw.close();
    

    思考题:在拷贝文本文件的时候,用字节流还是字符流?

    • 字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.
      思考题:那什么时候使用字符流?
    • 程序需要读取一段文本并展示
    • 读取的时候是按照字符的大小读取的,不会出现半个中文
      思考题: 字符流是否可以拷贝非纯文本的文件
      不可以。因为在读的时候会将字节转换为字符,在转换过程中,可能找不到对应的字符。

    ②改进之----自定义字符数组的拷贝

    FileReader fr = new FileReader("aaa.txt");			
    FileWriter fw = new FileWriter("bbb.txt");		
    int len;
    char[] arr = new char[1024*8];						
    while((len = fr.read(arr)) != -1) {			
    fw.write(arr, 0, len);				
    }			
    fr.close();										
    fw.close();	
    
    ③改进之---使用jdk自带的
    BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));	
    BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt"));
    int ch;				
    while((ch = br.read()) != -1) {		
    bw.write(ch);						
    }
    		
    br.close();							
    bw.close();  
    
    五.利用InputStreamReader和OutputStreamWriter完成指定的码表读写字符) 
    a.FileReader是使用默认码表(gbk)读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader.
    b.FileWriter是使用默认码表(gbk)写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter.
    BufferedReader  br = new BufferedReader(new InputStreamReader(new FileInputStream("UTF-8.txt"), "UTF-8"));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("GBK.txt"), "GBK"));
    int ch;
    while((ch = br.read()) != -1) {
    
    	bw.write(ch);
    }		
    br.close();
    bw.close();
    

    泛型

    目的:提高代码重用性,用通用的数据类型Object来实现。

    • 泛型类

    需求: 如果我们需要写一个类,这个类里面有一个成员方法:

    1.这个方法在调用的时候可以传入任何类型的对象

    2.这个成员方法的返回值是它本身

    e.g:
    class Person {
    	public Object test(Object obj){		//传入任何类型的对象
    return obj;		//返回值是它本身
    	}
    
    
    }
    

    那在主方法里调用的时候:

    Person p=new Person();
    Object o1=p.test(“asdasdasd”);
    String a1=(String)o1;	//必须进行"向下转型"
    System.out.println(a1.charAt(2));	//才能使用"特殊的方法"
    

    但是如果使用泛型类,可以这么写:

    class Person<T>{		//泛型用一个通用数据类型T来代替Object
    	public T test(T o){
    	
    }
    }
    

    那我们可以这么调用:

    Person<String> p=new Person<String>();
    String o1=p.test(“asdasdasd”);
    System.out.println(o1.charAt(2));
    

    特点:

    1.在实例化的过程中指定泛型参数

    2.指定完泛型参数后,如果传入其他类型的参数编译就会报错

    3.方法在返回的时候不需要我们向下转型

    4.如果实例化过程中没有指定泛型参数,则泛型参数相当于Object

    注意: 泛型参数的范围只可以通过extends限定泛型参数的一个上界

    例如:
    class Person<T extends A>{
    }
    
    class A{
    
    
    }
    class B extends A{
    
    
    }
    
    • 泛型类的继承关系

    泛型类并不是协变的。意思是说,泛型类的参数具有继承关系,但是泛型类本身没有任何关系。

    需求:
    写一个方法   参数列表  可以接受的参数为ArrayList,并且对ArrayList中的泛型参数有一定要----例如是Person+Person的子类。(Student类是继承Person类的)
    
    错误代码:
    void test(ArrayList<Person> o){
    
    }
    
    错误, 因为ArrayList<Person>和ArrayList<Student>并没有任何关系。
    

    所以如果要满足上述的需求则可以使用通配符,具体代码如下:

    void test(ArrayList<? extends Person> o){
    ①
    
    
    }
    

    那如果是要求泛型参数是是某个类+某个类的父类呢?

    void test(ArrayList<? super Person> o){
    ②
    
    
    }
    

    对于①来说,我们基本上什么–都不能add,只能add null。
    对于②来说,我们可以add Person或者Person的子类。

    注意: 我们也可以利用extends 和 super 来确定泛型参数的边界

    • 泛型方法

    如果希望并不是实例化过程告诉我们泛型参数,而是 方法调用过程传的参数 告诉我们 返回值得具体类型,从而避免向下转型,则可以使用泛型方法:

    原来使用泛型类的参数定义的代码如下:

    T test(T t){		//定义泛型参数成员变量
    	return t;		//返回值也可使用
    }
    
    <E> E test(E t)//参数列表中变量类型是E
    
    }
    
    

    泛型方法使用场景:

    1.静态方法不能使用类的泛型参数,因为不需要实例化过程

    2.如果这个方法比较多变,则尽量使用泛型方法

    • 泛型的类型擦除

    由于数组是协变的,泛型的类型擦除的影响,导致禁止生成具有泛型类类型数组.

    Pair<Double> cell=new Pair<>();
    cell.setValue(4.5);
    Pair<String>[] arr1=new Pairt<>[10];
    Object[] arr2=arr1;
    arr2[0]=cell;
    String s=arr1[0].getValue();
    
    
    class Pair<T> {  
        private T value;  
        public T getValue() {  
            return value;  
        }  
        public void setValue(T value) {  
            this.value = value;  
        }  
    }  
    

    集合

    List

    • 集合由来

    a.数组长度是固定的,当添加的元素超过了数组的长度时需要对数组重新定义,太麻烦,。

    b.java内部给我们提供了集合类,能存储任意对象,长度是可以改变的,随着元素的增加而增加。

    • 集合和数组的区别
      区别1 :
    • 数组既可以存储基本数据类型,又可以存储引用数据类型
    • 集合只能存储引用数据类型(对象)
    • 集合中也可以存储基本数据类型,但是在存储的时候会自动装箱变成对象
      区别2:
    • 数组长度是固定的,不能自动增长
    • 集合的长度的是可变的,可以根据元素的增加而增长
    • 使用场景
    • 如果元素个数是固定的推荐用数组
    • 如果元素个数不是固定的推荐用集合
    • 集合继承体系图

    注意: 以ArrayList为例进行演示,LinkedList用法完全一致,只是使用场景不一样.

    boolean add(E e)
    		boolean remove(Object o)
    		void clear()
    E get(int index)
    		boolean contains(Object o)
    		boolean isEmpty()
    		int size()	  
    		
    		boolean addAll(Collection c)
    		boolean removeAll(Collection c)
    		boolean containsAll(Collection c)
    		boolean retainAll(Collection c)
    
    • 集合的遍历

    ①转化为数组进行遍历

    代码如下:
    ArrayList coll = new ArrayList();
    coll.add(new Student("张三",23));		//可无限添加
    coll.add(new Student("李四",24));
    coll.add(new Student("王五",25));
    coll.add(new Student("赵六",26));
    			
    Object[] arr = coll.toArray();				
    for (int i = 0; i < arr.length; i++) {
    Student s = (Student)arr[i];			
    System.out.println(s.getName() + "," + s.getAge());
    }
    

    ②迭代器遍历

    代码如下:
    ArrayList c = new ArrayList();
    c.add("a");
    c.add("b");
    c.add("c");
    c.add("d");
    Iterator it = c.iterator();				
    while(it.hasNext()) {				
    	System.out.println(it.next());
    }
    

    (注意如果需要更改list的结构,要调用迭代器的方法,否则会抛出ConcurrentModificaitionException)

    ③for-each遍历

    内部的实现也是迭代器,所以也请注意ConcurrentModificationException

    ④利用索引和普通循环结构进行遍历

    • ArrayList和LinkedList场景使用区别

    LinkedList经常用在增删操作较多而查询操作很少的情况下;
    ArrayList经常用在增删操作较少而查询操作很多的情况下。

    Set

    • HashSet的使用
    1. HashSet存储字符串并遍历
    	HashSet<String> hs = new HashSet<>();
    	boolean b1 = hs.add("a");
    	boolean b2 = hs.add("a");			//判断b1,b2两个字符串在字面上是否相等			
    	System.out.println(b1);
    	System.out.println(b2);
    	for(String s : hs) {
    		System.out.println(s);
    	}
    

    注意String的写法,如果是new String(“a”),则结果依然是都存进去了

    HashSet存储自定义对象保证元素唯一性

    			HashSet<Person> hs = new HashSet<>();
    			hs.add(new Person("张三", 23));
    			hs.add(new Person("张三", 23));
    			hs.add(new Person("李四", 23));
    			hs.add(new Person("李四", 23));
    			hs.add(new Person("王五", 23));
    			hs.add(new Person("赵六", 23));
    
    • HashSet原理

    a.我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数

    b.当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象

    (1)如果没有哈希值相同的对象就直接存入集合

    (2))如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

    推断:

    1. hashCode()方法返回的值不同,则一定不是同一个对象
    2. hashCode()方法返回的值相同,则不一定是同一个对象
    3. equals方法返回值不同,是不同对象,返回值相同是同一个对象。
      (前提是这些方法都没有被复写继承的是Object的方法)
    • Treeset的使用

    存储Person对象,画图演示: TreeSet使用Comparable存储自定义对象并遍历(按照年龄大小)

    代码如下:
    public int compareTo(Object o) {
    Person p=(Pesron)o;
            int length = this.name.length() - o.name.length();              
            return length== 0 ? this.age - o.age : length;                       
    }
    
    

    (上面的代码可以理解为建树的过程)

    案例演示TreeSet使用Comparator 存储自定义对象并遍历(按照年龄大小)

    代码如下:
    class A implements Comparator{
    public int compare(Object s1, Object s2) {    
    	Person p1=(Person)s1;   
    Person p2=(Person)s2;      
              return p1.age-p2.age;
        }
    }
    (上面的代码可以理解为建树的过程)
    
            TreeSet<String> ts = new TreeSet<>(new A());       
            ts.add("aaaaaaaa");
            ts.add("z");
            ts.add("wc");
            ts.add("nba");
            ts.add("cba");
          
            System.out.println(ts);
    
    • 两种方式的区别

    TreeSet构造函数什么都不传,但是Person类要实现Comparable接口

    TreeSet构造函数需要传一个写有标准的类的对象,但是Person类不要实现 Comparable接口

    TreeSet如果传入Comparator,Person类又实现了Comparable接口,就优先按照Comparator

    Map

    • HashMap的使用

    a:添加功能
    V put(K key,V value):添加元素。
    如果键是第一次存储,就直接存储元素,返回null
    如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值

    b:删除功能
    void clear():移除所有的键值对元素
    V remove(Object key):根据键删除键值对元素,并把值返回
    c:判断功能
    boolean containsKey(Object key):判断集合是否包含指定的键
    boolean containsValue(Object value):判断集合是否包含指定的值
    boolean isEmpty():判断集合是否为空

    d:获取功能
    Set<Map.Entry<K,V>> entrySet():
    V get(Object key):根据键获取值
    Set keySet():获取集合中所有键的集合
    Collection values():获取集合中所有值的集合

    e:长度功能
    int size():返回集合中的键值对的个数

    • HashMap的遍历

    第一种思路:获取所有键的集合,遍历键的集合,获取到每一个键, 根据键找值。

    代码如下:
    			HashMap<String, Integer> hm = new HashMap<>();
    			hm.put("张三", 23);
    			hm.put("李四", 24);
    			hm.put("王五", 25);
    			hm.put("赵六", 26);
    			Set<String> keySet = hm.keySet();						
    Iterator<String> it =keySet.iterator();	
    			while(it.hasNext()) {						
    				String key = it.next();					
    				Integer value = hm.get(key);			
    				System.out.println(key + "=" + value);	
    			}
    

    第二种思路:键值对对象找键和值思路,获取所有键值对对象的集合,遍历键值对对象的集合,获取到每一个键值对对象,根据键值对对象找键和值。

    代码如下:	
    HashMap<String, Integer> hm = new HashMap<>();
    hm.put("张三", 23);
    hm.put("李四", 24);
    hm.put("王五", 25);
    hm.put("赵六", 26);
    Set<Map.Entry<String, Integer>> entrySet = hm.entrySet();	
    Iterator<Entry<String, Integer>> it = entrySet.iterator();
    while(it.hasNext()) {
    Map.Entry<String, Integer> en = it.next();	
    String key = en.getKey();								
    	Integer value = en.getValue();							
    	System.out.println(key + "=" + value);
    }
    

    线程

    • 多线程和多进程的区别

    多进程是指操作系统能同时运行多个任务(程序)。

    多线程是指在同一程序中有多个顺序流在执行。

    -多线程的创建方式

    1.继承的方式:通过继承Thread并且重写run方法.

    代码如下:
    class MyThread extends Thread{
    	public void run(){
    }
    }
    主方法中先生成对象然后启动线程:
    MyThread myThread=new MyThread();
    myThread.start();
    

    2.通过实现Runnable接口: 重写run方法并将生成的对象通过Thread的构造方法传给相应的thread对象.

    代码如下:
    class MyRunnable implements Runnable{
    	public void run(){
    }
    }
    
    主方法中:
    MyRunnable myRunnabble=new MyRunnable();
    Thread a=new Thread(myRunnabble);
    a.start();
    
    • 直接调用线程的run()方法和调用start()方法的区别

    1.如果直接调用 run 方法则相当于在主线程中调用一个普通对象的普通成员方法,并不会开启线程

    2.调用 start 方法则会让线程进入就绪状态

    • synchronized关键字

    同步。对象锁和类锁的使用,判断代码会不会交错,运行会不会发生一些情况。

    1.对象锁

    a.当使用对象锁的时候,注意要是相同的对象,并且当有线程正在访问对象锁内部的代码的时候,其他线程无法访问。(注意无法访问的范围)。

    b.但是并不影响没有使用对象锁的部分的代码的运行。

    对象锁分为两类一个叫做synchronized代码块(圆括号内是普通类的对象),另外一个是sybchronized修饰普通成员方法。它们二者其实可以通过this关键字进项转化。

    2.类锁

    a. 当使用类锁的时候,只要是同一个类的对象.当有线程正在访问类锁内部的代码的时候,其他线程无法访问。(注意无法访问的范围)

    b. 但是并不影响没有使用类锁的部分的代码的运

    对象锁分为两类一个叫做synchronized代码块(圆括号内是class对象),另外一个是sybchronized修饰静态成员方法。它们二者其实可以通过class对象进项转化。

    注意: 类锁和对象锁之间没有关系。相比之下,对象锁要难一些,要判断是否是一个对象。

    • sleep方法
      会让当前线程进入阻塞状态,但是不会释放锁.

    (注意: 会丢弃当前剩余的时间片,立马进入阻塞状态)

    • yield方法
      会让当前线程暂停执行,进入就绪状态

    (注意: 会丢弃当前剩余的时间片,立马回到调度中心)

    • join方法
      会让产生线程进入阻塞状态,直到相应线程执行完毕。不是静态方法,需要用线程去调用。

    (注意: 会丢弃当前剩余的时间片,立马进入阻塞状态)

    • wait和notify/notifyAll方法

    • 线程间的通讯。

    一个线程必须在拿到一个对象锁的情况下调用这个对象的wait()方法进入休眠的状态并且释放锁,当另一个线程也拿到这个锁时可以调用这个对象的notify/notifyAll让一个或者全部休眠线程回到就绪状态(调度中心),然后如果休眠的线程拿到锁和时间片将会从上次休眠的代码处继续执行。注意压缩包中的示例代码。

    展开全文
  • 学习路线篇:如何快速高效学习javaSE学习视频和书籍推荐】写在前面的话:视频推荐书籍推荐:总结 写在前面的话: 本文章适用于想要学习java基础知识,且想知道如何快速高效学习的同学,我会在这里分享自己学习的...
  • 一进程和线程  (1)进程和线程的基本概念  进程:程序(任务)的执行过程;持有资源(共享内存,共享文件)和线程。  线程:是一个程序内部的顺序控制流。 ... 我们生活中的进程例子很多,一个进程就相当于一个你在...
  • 开始学习JavaSE了,记录下自己学习的进程。   学习大纲   1 开发环境配置 day01 配置 2 基础语法(基本差不多,看一下就好) day02 命名规则,进制转换 day03 运算符,if语句 day04 switch,for,while,...
  • JavaSE 学习经验汇总

    2018-12-14 09:42:19
    学习Java的内存分配机制和内存泄漏问题 Java Platform Standard Edition 面向对象概念的理解、Java基本语法的学习,Java桌面图形界面应用程序的开发,掌握常用的Java API等(关键是要学会怎样查阅)。 重点:...
  • JavaSE学习心得

    2019-06-24 22:09:31
    时常看到一些人说掌握了Java,但是让他们用Java做一个实际的项目可能又困难重重,在这里,笔者根据自己的一点理解斗胆提出自己的一些对掌握Java这个说法的标准,当然对于新手,也可以提供一个需要学习哪些内容的参考...
  • 本章是对JavaSE学习过程的记录,字符String类和日期Calendar类方面的学习。 String简介+代码练习 对于String学习,主要 1.有怎么定义String,主要有两种方式。 2.定义方式的区别,区别equals和==区别 3.charAt方法,...
  • 如何学习JAVASE

    2017-06-09 21:25:30
    1.实践很重要,多写代码 2.有空多看别人写的代码,站在巨人的肩膀上才能...Java 是典型的服务器端的语言,所以不要在SWING 和AWT上浪费你宝贵的时间 1.集合框架 2. OOD 万物皆对象,好好理解,深入理解 3. 泛型
  • 转自刘红旺 java学习网 http://www.easyitedu.com/ 10-4-22Java学习之...所以第三部分我会分为三章来讲解:JavaSE学习路线图,JavaSE学习方法,JavaSE学习心得。第一章:JavaSE学习路线图 我们知道Java有三个方向,J
  • JavaSE综合项目演练

    2019-05-26 15:59:53
    光阴似箭日月如梭,大家学习已经有了一段时间了,转眼间,从刚开始如何配置JDK已经到了现在快学完网络编程了。学了这么多,眼看就要进入下一个阶段了,数据库编程了,那么在进入下个阶段前,我们来完成一个综合性...
  • JavaSE复习的学习感言

    2019-10-31 12:02:22
    序言: 当然,基础就像冰冻三尺,也绝非一日之寒,慢慢学下去,持续的把java基础拿出来翻一翻,在多刷点题,这些东西才是做项目的基础,才是最重要的。 下面我也给出一些注意事项: 重点: ...
  • JavaSE :标准版 面向桌面型应用(Windows下的应用程序)的Java平台。提供完整的Java核心API,也叫J2SE. JavaEE:企业版 开发企业环境下的应用程序。该技术体系中包含的技术 Servlet Jsp。主要针对Web应用程序开发。...
  • Smecking的javase学习笔记,欢迎查看
  • JAVASE学习总结

    2014-06-17 13:23:54
    JAVASE笔记大杂烩
  • JavaSE 方法重载的总结: 我认为类好比一块庄稼地,而具体是一块什么地就是类的实例化对象。我把这块地实例化为一块水稻地。而水稻有自己的属性,比如它的高低,产量,以及成熟时间等等,而它的方法有浇水,施肥,...
  • JAVASE

    2018-10-06 16:04:48
    JAVASE day01 1.什么是软件(software)? (1)对软件的定义及认识 软件:是指一系列按照特定顺序组织的计算机数据与指令的集合。 数据:能够被计算机所识别的都是数据 avi mp3 txt png。 指令:计算机的操作步骤。 ...
1 2 3 4 5 ... 20
收藏数 11,447
精华内容 4,578