当前位置: 首页 > news >正文

诚客网站建设怎么做家政的网站

诚客网站建设,怎么做家政的网站,怎样进入谷歌网站,重庆今天新闻发布会直播JDK、JRE、JVM 三者之间的关系#xff1f;JDK#xff08;Java Development Kit#xff09;#xff1a;是Java开发工具包#xff0c;是整个Java的核心#xff0c;包括了Java运行环境JRE、Java工具和Java基础类库。它能够创建和编译程序。JRE#xff08;Java Runtime Envi…JDK、JRE、JVM 三者之间的关系JDKJava Development Kit是Java开发工具包是整个Java的核心包括了Java运行环境JRE、Java工具和Java基础类库。它能够创建和编译程序。JREJava Runtime Environment是Java的运行环境它是运行已编译Java程序所需的所有内容的集合包括 Java 虚拟机JVMJava核心类库java命令和其他的一些基础构件。JVMJava Virtual Machine是运行Java字节码的虚拟机是整个Java实现跨平台的最核心的部分能够运行以Java语言写作的软件程序。所有的Java程序会首先被编译为 .class 的类文件这种类文件可以在虚拟机上执行。什么是字节码?采用字节码的好处是什么?JVM 可以理解的代码就叫做字节码即扩展名为 .class 的文件它不面向任何特定的处理器只面向虚拟机。由于字节码并不针对一种特定的机器因此Java程序无须重新编译便可在多种不同操作系统的计算机上运行。为什么Java代码可以实现一次编写、到处运行JVMJava虚拟机是Java跨平台的关键。在程序运行前Java源代码.java需要经过编译器编译成字节码.class。在程序运行时JVM负责将字节码翻译成特定平台下的机器码并运行也就是说只要在不同的平台上安装对应的JVM就可以运行字节码文件。同一份Java源代码在不同的平台上运行它不需要做任何的改变并且只需要编译一次。而编译好的字节码是通过JVM这个中间的“桥梁”实现跨平台的JVM是与平台相关的软件它能将统一的字节码翻译成该平台的机器码。注意1编译的结果是生成字节码、不是机器码字节码不能直接运行必须通过JVM翻译成机器码才能运行2跨平台的是Java程序、而不是JVMJVM是用C/C开发的软件不同平台下需要安装不同版本的JVM。为什么说 Java 语言“编译与解释并存”高级编程语言按照程序的执行方式分为两种1编译型编译型语言会通过编译器将源代码一次性翻译成可被该平台执行的机器码。常见的编译性语言有 C、C、Go等。2解释型解释型语言会通过解释器一句一句的将代码解释为机器代码后再执行。常见的解释性语言有 Python、JavaScript、PHP等等。Java语言既具有编译型语言的特征也具有解释型语言的特征。因为 Java程序要经过先编译后解释两个步骤由Java编写的程序需要先经过编译步骤生成字节码.class文件到执行期时再将字节码直译来解释执行。一个Java文件里可以有多个类吗不含内部类一个java文件里可以有多个类但最多只能有一个被public修饰的类如果这个java文件中包含public修饰的类则这个类的名称必须和java文件名一致。什么是面向对象面向对象和面向过程的区别面向对象的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。它从现实世界中客观存在的事物出发来构造软件系统强调直接以现实世界中的事物为中心来思考并根据这些事物的本质特点把它们抽象地表示为系统中的类作为系统的基本构成单元这使得软件系统的组件可以直接映像到客观世界并保持客观世界中事物及其相互关系的本来面貌。区别1编程思路不同面向过程以实现功能的函数开发为主而面向对象要首先抽象出类、属性及其方法然后通过实例化类、执行方法来完成功能。2封装性都具有封装性但是面向过程封装的是功能而面向对象封装的是数据和功能。3面向对象具有继承性和多态性而面向过程没有继承性和多态性。面向对象的三大特性1封装将对象的成员变量和实现细节隐藏起来不允许外部直接访问然后通过一些公用方法来控制对这些成员变量进行安全的访问和操作。2继承继承是从已有的类中得到继承信息后创建新类的过程。提供继承信息的类被称为父类得到继承信息的类被称为子类当子类继承父类后将直接获得父类的属性和方法。3多态分为编译时多态方法重载和运行时多态方法重写。多态指的是子类对象可以直接赋给父类变量但运行时依然表现出子类的行为特征这意味着相同类型的变量在执行同一个方法时可能会表现出多种不同的行为特征。要实现多态需要做两件事一是子类继承父类并重写父类中的方法二是用父类型引用子类型对象。封装的目的是什么为什么要有封装封装是面向对象编程语言对客观世界的模拟在客观世界里对象的状态信息都被隐藏在对象内部外界无法直接操作和修改。对一个类或对象实现良好的封装可以隐藏类的实现细节让使用者只能通过事先预定的方法来访问数据从而可以在该方法里加入控制逻辑限制对成员变量的不合理访问可进行数据检查从而有利于保证对象信息的完整性便于修改提高代码的可维护性。Java的访问权限Java语言为我们提供了三种访问修饰符即private、protected、public在使用这些修饰符修饰目标时一共可以形成四种访问权限即private、default、protected、public注意在不加任何修饰符时为default访问权限。1在修饰类时该类只有两种访问权限对应的访问权限的含义如下default该类可以被同一包下其他的类访问public该类可以被任意包下任意的类所访问。2在修饰成员变量/成员方法时该成员的四种访问权限的含义如下private该成员可以被该类内部成员访问default该成员可以被该类内部成员访问也可以被同一包下其他的类访问protected该成员可以被该类内部成员访问也可以被同一包下其他的类访问还可以被它的子类访问public该成员可以被任意包下任意类的成员进行访问。Java的数据类型Java数据类型包括基本数据类型和引用数据类型两大类。基本数据类型有8个分别是整数类型byte/short/int/long、浮点类型float/double、字符类型char、布尔类型boolean。在这8个基本类型当中除了布尔类型之外的其他7个类型都可以看做是数字类型它们相互之间可以进行类型转换。引用类型就是对一个对象的引用根据引用对象类型的不同可以将引用类型分为3类即数组、类、接口类型。引用类型本质上就是通过指针指向堆中对象所持有的内存空间只是Java语言不再沿用指针这个说法而已。byte1字节8位数据范围是 -2^7 ~ 2^7-1。short2字节16位数据范围是 -2^15 ~ 2^15-1。int4字节32位数据范围是 -2^31 ~ 2^31-1。long8字节64位数据范围是 -2^63 ~ 2^63-1。float4字节32位数据范围大约是 -3.4*10^38 ~ 3.4*10^38。double8字节64位数据范围大约是 -1.8*10^308 ~ 1.8*10^308。char2字节16位数据范围是 \u0000 ~ \uffff。为啥要有包装类基本类型和包装类型的区别Java语言是面向对象的语言其设计理念是“一切皆对象”。但8种基本数据类型却出现了例外它们不具备对象的特性。正是为了解决这个问题Java为每个基本数据类型都定义了一个对应的引用类型这就是包装类。区别1成员变量包装类型不赋值就是null而基本类型有默认值且不是null。2包装类型可用于泛型而基本类型不可以。3基本数据类型的局部变量存放在Java虚拟机栈中的局部变量表中基本数据类型的成员变量未被static 修饰存放在 Java 虚拟机的堆中。包装类型属于对象类型存在于堆中。4相比于对象类型基本数据类型占用的空间非常小。装箱和拆箱的区别自动装箱可以把一个基本类型的数据直接赋值给对应的包装类型自动拆箱可以把一个包装类型的对象直接赋值给对应的基本类型通过自动装箱、自动拆箱功能可以大大简化基本类型变量和包装类对象之间的转换过程。如何对Integer和Double类型判断相等Integer、Double不能直接进行比较因为它们是不同的数据类型。整数、浮点类型的包装类都继承于Number类型所以可以将Integer、Double先转为转换为相同的基本数据类型如double然后使用进行比较。Integer和int的区别1int 是 Java 的八种基本数据类型之一而Integer是int的包装类2int型变量的默认值是0Integer 变量的默认值是null3Integer变量必须实例化后才可以使用而int不需要。注意①Integer变量和int变量比较时只要两个变量的值是相等的则结果为true。因为包装类Integer和基本数据类型int类型进行比较时Integer会自动拆箱为int类型然后进行比较实际上就是两个int型变量在进行比较②由于Integer变量实际上是对一个Integer对象的引用所以两个通过new生成的Integer变量永远是不相等的因为其内存地址是不同的③非new生成的Integer变量和new Integer()生成的变量进行比较时结果为false。因为非new生成的Integer变量指向的是Java常量池中的对象而new Integer()生成的变量指向堆中新建的对象两者在内存中的地址不同。重载和重写的区别重载重载发生在同一个类中若多个方法之间方法名相同、参数列表不同则它们构成重载的关系。重载与方法的返回值以及访问修饰符无关即重载的方法不能根据返回类型进行区分。重写重写发生在子类与父类之间若子类方法想要和父类方法构成重写关系则它的方法名、参数列表必须与父类方法相同。另外返回值要小于等于父类方法抛出的异常要小于等于父类方法访问修饰符则要大于等于父类方法。若父类方法的访问修饰符为private则子类不能对其重写。构造方法有哪些特点构造方法能不能重写构造方法特点如下1名字与类名相同2没有返回值3生成类的对象时自动执行无需调用主要作用是完成对象的初始化工作。构造方法不能重写。因为构造方法需要和类保持同名而重写的要求是子类方法要和父类方法保持同名。如果允许重写构造方法的话那么子类中将会存在与类名不同的构造方法这与构造方法的要求是矛盾的。如果一个类没有声明构造方法该程序能正确执行吗?如果一个类没有声明构造方法也可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法无论是否有参Java就不会再添加默认的无参数的构造方法了对象实体与对象引用有何不同对象的相等和引用相等的区别new创建对象实例对象实例在堆内存中对象引用指向对象实例对象引用存放在栈内存中。对象的相等一般比较的是内存中存放的内容是否相等引用相等一般比较的是他们指向的内存地址是否相等。成员变量和局部变量的区别成员变量1成员变量是在类的范围里定义的变量2成员变量有默认初始值3未被static修饰的成员变量也叫实例变量它存储于对象所在的堆内存中生命周期与对象相同4被static修饰的成员变量也叫类变量它存储于方法区中生命周期与当前类相同。局部变量1局部变量是在方法里定义的变量3局部变量没有默认初始值3局部变量存储于栈内存中作用的范围结束变量空间会自动的释放。静态变量和实例变量的区别静态变量是被static修饰的变量也称为类变量当创建了类的多个对象时多个对象共享同一个静态变量当修改其中一个对象的静态变量时其他对象的静态变量也被修改。静态变量随着类的加载而加载要早于对象的创建可以通过“类.静态变量”的方式调用。由于类只加载一次则静态变量在内存中也只会存在一份在方法区的静态域中。实例变量未被static修饰的成员变量也叫实例变量它存储于对象所在的堆内存中生命周期与对象相同需要先创建对象然后通过对象才能访问到它。当创建了类的多个对象时每个对象独立的拥有一套类中的非静态属性当修改其中一个对象的非静态属性时不会导致其他对象中同样的属性被修改。static关键字在Java类里只能包含成员变量、方法、构造器、初始化块、内部类包括接口、枚举5种成员static可以修饰类中的所有成员以static修饰的成员就是类成员类成员属于整个类而不属于单个对象。被static修饰的变量称为静态变量被static修饰的方法称为静态方法在静态方法中只能调用静态的方法或属性不能访问实例成员。static修饰的成员可以通过类名访问也可以通过对象名访问。static修饰的类可以被继承。final关键字1final修饰类该类不能被继承2final修饰方法该方法不能被重写3final修饰变量该变量为常量final修饰变量时可以赋值的位置有显示初始化、代码块内初始化、构造方法内初始化。abstract关键字abstract修饰类1抽象类不能实例化不能创造该类的对象2抽象类中可以没有抽象方法3一般都会提供抽象类的子类通过实例化子类来调用抽象类中的结构abstarct修饰方法1抽象方法只有方法的声明没有方法体2包含抽象方法的类一定是抽象类(3)若子类重写了父类的所有抽象方法则可以实例化子类如果子类没有重写父类的所有抽象方法则需要将子类声明为抽象类。接口1接口内只能定义全局常量public static final、抽象方法public abstact、静态方法public static、默认方法public default 2接口内不能定义构造器因此接口不可以实例化给接口创建对象3接口通过类实现implements的方式来使用如果实现类实现了接口中所有的方法则此实现类可以实例化。4一个类可以实现多个接口该类需要实现所有接口中的所有方法接口和抽象类有什么共同点和区别相同①都不能被实例化②都可以包含抽象方法③都可以有默认方法。不同①接口里只能包含抽象方法、静态方法、默认方法和私有方法不能为普通方法提供方法实现抽象类则可以包含普通方法②接口里只能定义静态常量不能定义普通成员变量抽象类里则既可以定义普通成员变量也可以定义静态常量③接口里不包含构造器抽象类里可以包含构造器抽象类里的构造器并不是用于创建对象而是让其子类调用这些构造器来完成属于抽象类的初始化操作④接口里不能包含初始化块抽象类可以包含初始化块⑤一个类最多只能有一个直接父类包括抽象类但一个类可以直接实现多个接口通过实现多个接口可以弥补Java单继承的不足。谈谈你对面向接口编程的理解?接口体现的是规范和实现分离充分利用接口可以极好地降低程序各模块之间的耦合从而提高系统的可扩展性和可维护性。hashCode()和equals()hashCode()的作用是获取哈希码int整数哈希码的作用是确定该对象在哈希表中的索引位置。equals()是用于比较两个对象是否相等。如果两个对象的hashCode值相等那这两个对象不一定相等哈希碰撞同样的hashCode有多个对象继续使用 equals()来判断是否真的相同如果两个对象的hashCode值相等并且equals()方法也返回true我们才认为这两个对象相等如果两个对象的hashCode值不相等我们就可以直接认为这两个对象不相等。两个相等的对象的hashCode值一定相等。为什么重写 equals() 时必须重写 hashCode() 方法因为两个相等的对象的hashCode值一定相等也就是说如果equals()方法判断两个对象是相等的那这两个对象的hashCode值也要相等。如果重写equals()时没有重写hashCode()方法的话就可能会导致equals()方法判断是相等的两个对象hashCode值却不相等。和equals()的区别1对于基本数据类型来说 比较的是值2对于引用数据类型来说 比较的是对象的内存地址即判断它们是否为同一个对象。equals()不能用于判断基本数据类型的变量只能用来判断两个对象是否相等。1类没有重写equals()方法通过equals()比较该类的两个对象时等价于通过“”比较这两个对象即比较两个对象的内存地址是否相同。2类重写了equals()方法比较两个对象中的内容是否相等如String。String、StringBuffer、StringBuilder的区别String由final修饰是不可变类不能被继承线程安全。StringBuffer可变的字符序列、线程安全。StringBuilder可变的字符序列、线程不安全、效率高和StringBuilder有共同的父类AbstractStringBuilder。String为什么要设计为不可变类1由于String被广泛使用会用来存储敏感信息如果字符串是可变的容易被篡改无法保证使用字符串进行操作时它是安全的。2字符串常量池的需要字符串常量池是Java堆内存中一个特殊的存储区域, 当创建一个String对象时假如此字符串值已经存在于常量池中则不会创建一个新的对象而是引用已经存在的对象若字符串可变字符串常量池失去意义。3字符串不变性保证了hash码的唯一性使得类似HashMapHashSet等容器才能实现相应的缓存功能。由于String的不可变避免重复计算hashcode只要使用缓存的hashcode即可大大提高了在散列集合中使用String对象的性能。String字符串修改实现的底层原理当用String类型来对字符串进行修改时其实现方法是首先创建一个 StringBuilder其次调用StringBuilder的append()方法最后调用StringBuilder的toString()方法把结果返回。String str abc和new String(abc)的区别String str abcJVM 会首先检查字符串常量池中是否已经存在该字符串对象如果已经存在那么就不会再创建了直接返回该字符串在字符串常量池中的内存地址将其引用赋值给变量如果字符串常量池中没有该字符串那么就会在字符串常量池中创建该字符串对象然后再返回。new String(abc)JVM会首先检查字符串常量池中是否已经存在该字符串如果已经存在则不会在字符串常量池中再创建了如果不存在则就会在字符串常量池中创建该字符串对象然后再到堆内存中再创建一份字符串对象把字符串常量池中的字符串内容拷贝到内存中的字符串对象中然后返回堆内存中该字符串的内存地址。如何处理异常在Java中处理异常的语句由try、catch、finally三部分组成。使用try将可能出现异常的代码包裹起来在执行过程中一旦出现异常就会生成一个对应异常类的对象根据此对象的类型去catch中进行匹配。一旦try中的异常对象匹配到某一个catch时就进入到catch中进行异常处理。处理完成后就跳出当前的try-catch结构继续执行后面的代码。像数据库连接、输入输出流、网络变成Socket等资源JVM不能自动回收我们需要手动进行资源释放此时的资源释放需要声明在finally中。finally中声明的是一定会被执行的代码即使catch中又出现了异常、try中有return语句或catch中有return语句等情况。Java也允许程序主动抛出异常。当业务代码中判断某项错误的条件成立时可以使用throw关键字向外抛出异常。如果当前方法不知道该如何处理这个异常指明执行此方法时可能会出现的异常类型一旦方法体执行时出现异常会在异常代码处生成一个异常类对象此对象满足throws后的异常类型时就会被抛出后面的代码不会继续执行则该异常将交给JVM处理。finally一定会被执行吗finally中声明的是一定会被执行的代码即使catch中又出现了异常、try中有return语句或catch中有return语句等情况。当在try块或catch块中遇到return语句时finally语句块将在方法返回之前被执行。finally可以有return吗在通常情况下不要在finally块中使用return、throw等导致方法终止的语句一旦在finally块中使用了return、throw语句将会导致try块、catch块中的return、throw语句失效。Exception和Error有什么区别Error类和Exception类的父类都是Throwable类。主要区别如下Exception程序本身可以处理的异常可以通过catch来进行捕获。Exception 又可以分为 Checked Exception受检查异常必须处理和 Unchecked Exception 不受检查异常可以不处理。ErrorError属于程序无法处理的错误例如Java虚拟机运行错误、虚拟机内存不够错误、类定义错误等。这些异常发生时JVM一般会选择线程终止。throw和throws的区别1throw在方法体内部表示抛出异常由方法体内部的语句处理throw 是具体向外抛出异常的动作所以它抛出的是一个异常实例2throws在方法声明后面表示如果抛出异常由该方法的调用者来进行异常的处理表示出现异常的可能性并不一定会发生这种异常。什么是泛型有什么作用Java的参数化类型被称为泛型允许程序在创建集合时指定集合元素的类型集合自动记住所有集合元素的数据类型从而无须对集合元素进行强制类型转换。泛型的类型必须是类不能是基本数据类型如果要用到基本数据类型使用包装类替换泛型擦除当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时与泛型相关的信息会被擦除掉。比如一个ListString 类型被转换为List则该List对集合元素的类型检查变成了泛型参数的上限即Object。List? super T和List? extends T有什么区别? 是类型通配符List? 可以表示各种泛型List的父类意思是元素类型未知的ListList? super T 用于设定类型通配符的下限此处? 代表一个未知的类型但它必须是T的父类型即可以接受任何T 的父类构成的 ListList? extends T 用于设定类型通配符的上限此处 ? 代表一个未知的类型但它必须是T的子类型即可以接受任何继承自T的类型的List。什么是反射每个类都有一个Class对象包含了与类有关的信息。当编译一个新类时会产生一个同名的 .class文件该文件内容保存着Class对象。类加载相当于 Class对象的加载类在第一次使用时才动态加载到 JVM 中。反射可以提供运行时的类信息并且这个类可以在运行时才加载进来甚至在编译时期该类的 .class不存在也可以加载进来。具体来说程序运行时通过反射机制我们可以通过反射获得任意一个类的Class对象并通过这个对象查看这个类的信息可以通过反射创建任意一个类的实例并访问该实例的成员可以通过反射机制生成一个类的动态代理类或动态代理对象。Java反射在实际项目中有哪些应用场景使用JDBC时如果要创建数据库的连接则需要先通过反射机制加载数据库的驱动程序多数框架都支持注解/XML配置从配置中解析出来的类是字符串需要利用反射机制实例化面向切面编程AOP的实现方案是在程序运行时创建目标对象的代理类这必须由反射机制来实现。continue、break和return的区别是什么continue指跳出当前的这一次循环继续下一次循环。break指跳出整个循环体继续执行循环下面的语句。return用于跳出所在方法结束该方法的运行。final、finally、finalize 的区别final用于声明属性、方法和类分别表示属性不可变、方法不可覆盖、被其修饰的类不可继承finally异常处理语句结构的一部分表示总是执行finallizeObject类的一个方法在垃圾回收时会调用被回收对象的finalize如何实现对象的克隆1实现Cloneable接口并重写Object类中的clone()方法2实现Serializable接口通过对象的序列化和反序列化实现克隆可以实现真正的深克隆。深克隆和浅克隆的区别1浅克隆拷贝对象和原始对象的引用类型引用同一个对象。浅克隆只是复制了对象的引用地址两个对象指向同一个内存地址所以修改其中任意的值另一个值都会随之变化。2深克隆拷贝对象和原始对象的引用类型引用不同对象。深拷贝是将对象和值都复制过来会完全复制整个对象两个对象修改其中任意的值另一个值不会改变。Java中有哪些容器集合Java容器主要包括Collection和MapCollection主要用于存放单一元素 Map主要用于存放键值对。Collection接口又有三个主要的子接口List、Set和Queue。1List是元素有序、可重复的集合主要的实现类有ArrayList、LinkedList、Vector2Set是元素无序、不可重复的集合主要的实现类有HashSetLinkedHashSetTreeSet3Queue是先进先出FIFO的队列存储的元素是有序、可重复的主要的实现类有ArrayQueue、LinkedList、PriorityQueue。Map用于存储key - value对是无序的其中key不能重复主要的实现类有HashMap、TreeMap、Hashtable、ConcurrentHashMap。Java容器集合的主要实现类有哪些ArrayList线程不安全效率高底层使用Object[ ]存储查找快增删较慢随机访问元素的复杂度是O(1)。LinkedList线程不安全底层使用双向链表存储查找慢、增删快随机查找元素的复杂度是O(n)LinkedList的节点除了存储数据还存储了两个指针比ArrayList更占内存。Vector线程安全效率低底层使用Object[ ]存储。 HashSet线程不安全基于哈希表实现不支持有序性操作可以存储nullTreeSet线程不安全基于红黑树实现支持自然排序和定制排序不可以存储nullLinkedHashSet线程不安全是HashSet的子类基于链表和哈希表实现对于频繁的遍历操作其效率大于HashSet ArrayDeque基于可变长度的数组和双指针实现不可以存储null。PriorityQueue线程不安全基于二叉堆结构实现底层使用可变长的数组来存储数据可以用它来实现优先队列。 HashMap线程不安全、效率高JDK1.7中的HashMap是基于数组链表来实现的它的底层维护一个Entry数组JDK1.8中的HashMap是基于数组链表红黑树来实现的它的底层维护一个Node数组存储的key和value可以是nullTreeMap线程不安全基于红黑树实现按照key-value对中的key对数据进行排序实现排序遍历Hashtable线程安全、效率很低底层采用数组链表实现存储的key和value不能是null。ConcurrentHashMap线程安全JDK1.7的ConcurrentHashMap 底层采用分段的数组链表实现JDK1.8采用的数组链表红黑树。LinkedHashMap作为HashMap的子类底层使用双向链表存储ArrayList实现RandomAccess接口有何作用为何LinkedList没实现这个接口RandomAccess 接口只是一个标志接口标识实现这个接口的类具有随机访问功能。ArrayList底层是数组而LinkedList底层是链表。数组天然支持随机访问时间复杂度为O(1)。链表需要遍历到特定位置才能访问特定位置的元素时间复杂度为 O(n)所以不支持快速随机访问。ArrayList实现了RandomAccess接口就表明了他具有快速随机访问功能。HashSet的实现原理HashSet是基于HashMap实现的HashSet的构造方法中会初始化一个HashMap对象所有放入HashSet中的集合元素实际上由HashMap的key来保存而HashMap的value则存储了一个PRESENT它是一个静态的Object对象。Queue与Deque的区别Queue 是单端队列只能从一端插入元素另一端删除元素实现上一般遵循 先进先出FIFO 规则。Deque 是双端队列在队列的两端均可以插入或删除元素。HashMap的put方法的执行过程1先判断数组是否为空若数组为空则进行第一次扩容2通过hash算法计算键值对在数组中的索引3如果当前位置元素为空则直接插入数据如果当前位置元素非空且key已存在则直接覆盖其value如果当前位置元素非空且key不存在则将数据链到链表末端4若链表长度达到8则将链表转换成红黑树并将数据插入树中5如果数组中元素个数size超过threshold则再次进行扩容操作。HashMap的扩容机制1数组的初始容量为16而容量是以2的次方扩充的。2数组是否需要扩充是通过负载因子判断的如果当前元素个数为数组容量的0.75时就会扩充数组。这个0.75就是默认的负载因子可由构造器传入。3为了解决碰撞数组中的元素是单向链表类型。当链表长度到达一个阈值时7或8先检测当前数组是否到达一个阈值64如果没有到达这个容量先去扩充数组否则会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时6又会将红黑树转换回单向链表提高性能。HashMap为什么线程不安全HashMap在并发执行put操作时可能会导致形成循环链表从而引起死循环。HashMap中的循环链表是如何产生的在多线程的情况下当重新调整HashMap大小的时候就会存在条件竞争因为如果两个线程都发现HashMap需要重新调整大小了它们会同时试着调整大小。在调整大小的过程中存储在链表中的元素的次序会反过来因为移动到新的bucket位置的时候HashMap并不会将元素放在链表的尾部而是放在头部这是为了避免尾部遍历。如果条件竞争发生了那么就会产生死循环了。ConcurrentHashMap的实现原理在jdk 1.7中ConcurrentHashMap是由Segment数据结构和HashEntry数组结构构成采取分段锁来保证安全性。Segment继承了ReentrantLock所以它是一种重入锁HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment 数组一个Segment里包含一个HashEntry数组Segment的结构和HashMap类似是一个数组和链表结构。JDK1.8 的实现已经摒弃了Segment的概念而是直接用 Node数组链表红黑树的数据结构来实现并发控制使用Synchronized 和CAS来操作整个看起来就像是优化过且线程安全的HashMap。ConcurrentHashMap是怎么分段分组的Segment的get操作先经过一次再散列然后使用这个散列值通过散列运算定位到Segment再通过散列算法定位到元素。get操作的高效之处在于整个get过程都不需要加锁除非读到空的值才会加锁重读。当执行put操作时首先判断是否需要扩容然后定位到添加元素的位置将其放入HashEntry数组中。插入过程会进行第一次key的hash来定位Segment的位置如果该Segment还没有初始化即通过CAS操作进行赋值然后进行第二次hash 操作找到相应的HashEntry的位置这里会利用继承过来的锁的特性在将数据插入指定的HashEntry位置时尾插法会通过继承 ReentrantLock的tryLock()方法尝试去获取锁如果获取成功就直接插入相应的位置如果已经有线程获取该Segment的锁那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁超过指定次数就挂起等待唤醒。HashTable和ConcurrentHashMap的区别HashTable 和 ConcurrentHashMap 相比效率低。 Hashtable之所以效率低主要是使用了synchronized关键字对put等操作进行加锁而synchronized关键字加锁是对整张Hash表加锁即每次锁住整张表让线程独占致使效率低下ConcurrentHashMap在对象中保存了一个Segment数组即将整个Hash表划分为多个分段而每个Segment元素即每个分段则类似于一个Hashtable这样在执行put操作时首先根据hash算法定位到元素属于哪个Segment然后对该Segment加锁即可因此ConcurrentHashMap在多线程并发编程中可是实现多线程put操作。HashMap的get方法的执行过程能否判断元素是否在map 中通过key的hash值找到在table数组中的索引处的Entry然后返回该key对应的value即可。不能判断一个key是否包含在map中因为get返回null有可能是不包含该 key也有可能该key对应的value为 null。因为HashMap中允许key为null也允许value为 null。JDK1.8之后为什么HashMap头插法改为尾插法头插法在并发下时可能形成数据环get数据时死循环而在1.8之前因为处理 hash 冲突的方式是用链表存放数据使用头插法可以提升一定效率。但是在 1.8 之后链表长度达到阈值就要考虑升级红黑树了所以哪怕进行尾插遍历次数也会很有限效率影响不大。Iterator迭代器可以遍历并选择序列中的对象并且只能单向移动而开发人员不需要了解该序列的底层结构。iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时它返回序列的第一个元素。之后使用next()获得序列中的下一个元素。 Iterator和ListIterator的区别Iterator可用来遍历Set和List集合但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历ListIterator既可以前向也可以后向。ListIterator 实现了Iterator接口并包含其他的功能比如增加元素替换元素获取前一个和后一个元素的索引等等。Java中的IO流数据输入到计算机内存的过程即输入反之输出到外部存储比如数据库文件的过程即输出。按照数据流向可以将流分为输入流和输出流其中输入流只能读取数据、不能写入数据而输出流只能写入数据、不能读取数据。按照数据类型可以将流分为字节流和字符流其中字节流操作的数据单元是8位的字节而字符流操作的数据单元是16位的字符。字符流通常处理文本文件字节流用来处理图片、视频、音频等非文本文件。InputStream字节输入流Reader字符输入流OutputStream字节输出流Writer字符输出流怎么用流打开一个大文件1使用缓冲流。缓冲流内部维护了一个缓冲区通过与缓冲区的交互减少与设备的交互次数。使用缓冲输入流时它每次会读取一批数据将缓冲区填满每次调用读取方法并不是直接从设备取值而是从缓冲区取值当缓冲区为空时它会再一次读取数据将缓冲区填满。使用缓冲输出流时每次调用写入方法并不是直接写入到设备而是写入缓冲区当缓冲区填满时它会自动写入设备。2使用NIO。NIO采用内存映射文件的方式来处理输入/输出NIO将文件或文件的一段区域映射到内存中这样就可以像访问内存一样来访问文件了通过这种方式来进行输入/输出比传统的输入/输出要快得多。Java中3种常见IO模型1BIOBIO属于同步阻塞IO模型同步阻塞 IO 模型中应用程序发起 read 调用后会一直阻塞直到内核把数据拷贝到用户空间。2NIONIO主要由ChannelBufferSelector组成。它是支持面向缓冲的基于通道的I/O操作方法。所有的IO在NIO中都从一个Channel开始数据可以从Channel读到Buffer中也可以从Buffer写到Channel中。Buffer本质上是一块可以写入数据然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象并提供了一组方法用来方便的访问该块内存。Selector允许单线程处理多个Channel只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后才会为其服务。3AIO异步 IO 是基于事件和回调机制实现的也就是应用操作之后会直接返回不会堵塞在那里当后台处理完成操作系统会通知相应的线程进行后续的操作。Java的序列化与反序列化序列化机制可以将对象转换成字节序列这些字节序列可以保存在磁盘上也可以在网络中传输并允许程序将这些字节序列再次恢复成原来的对象。其中对象的序列化Serialize是指将一个Java对象写入IO流中对象的反序列化Deserialize则是指从IO流中恢复该Java对象。若对象要支持序列化机制则它的类需要实现Serializable接口该接口是一个标记接口它没有提供任何方法只是标明该类是可以序列化的。若要实现序列化则需要使用对象流ObjectInputStream和ObjectOutputStream。其中在序列化时需要调用ObjectOutputStream对象的writeObject()方法以输出对象序列。在反序列化时需要调用ObjectInputStream对象的readObject()方法将对象序列恢复为对象。Serializable接口为什么需要定义serialVersionUID变量serialVersionUID代表序列化的版本通过定义类的序列化版本在反序列化时只要对象中所存的版本和当前类的版本一致就允许做恢复数据的操作否则将会抛出序列化版本不一致的错误。如果不定义序列化版本在反序列化时可能出现冲突的情况。增加了序列化版本之后在这种情况下则可以抛出异常以提示这种矛盾的存在提高数据的安全性。除了Java自带的序列化之外你还了解哪些序列化工具Json简单直观可读性好有jacksongsonfastjson等解析工具Protobuf用来序列化结构化数据的技术可以用来持久化数据或者序列化成网络传输的数据。更加节省空间以二进制流存储、速度更快、更加灵活。Thrift不仅仅是序列化协议而是一个RPC框架能够满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。Avro提供两种序列化格式即JSON格式或者Binary格式。并行和并发的区别1并行是指两个或者多个事件在同一时刻发生而并发是指两个或多个事件在同一时间间隔发生2并行是在不同实体上的多个事件并发是在同一实体上的多个事件线程和进程的区别进程是程序运行和资源分配的基本单位一个程序至少有一个进程一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元而多个线程共享内存资源减少切换次数从而效率更高。线程是进程的一个实体是cpu调度和分派的基本单位是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。创建线程的几种方式1. 继承Thread类1创建一个继承于Thread类的子类2重写Thread类的run()将此线程要执行的操作声明在run()中3创建子类对象4通过此对象调用start()启动当前线程调用当前线程的run()2. 实现Runnable接口1创建一个实现了Runnable接口的类2实现类去重写Runnable中的run()方法3创建实现类的对象4创建Thread类的对象将实现类的对象作为参数传递到Thread类的构造器中5通过Thread类的对象调用start()。3. 通过实现Callable接口1创建Callable接口的实现类并实现call()方法该call()方法将作为线程执行体且该call()方法有返回值。然后再创建Callable实现类的对象2使用FutureTask类来包装Callable对象FutureTask对象封装了Callable对象的call()方法的返回值3使用FutureTask对象作为Thread对象的target创建并启动新线程4调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。run()和start()有什么区别run()方法被称为线程执行体它的方法体代表了线程需要完成的任务每个线程都是通过某个特定Thread对象所对应的run()方法来完成操作的。start()方法用来启动线程。调用start()方法启动线程时系统会把该run()方法当成线程执行体来处理这时无需等待run()方法体代码执行完毕可以直接继续执行下面的代码这时此线程是处于就绪状态并没有运行。但如果直接调用线程对象的run()方法系统把线程对象当成一个普通对象而run()方法也是一个普通方法而不是线程执行体。阻塞线程的方式有哪些1线程调用sleep()方法主动放弃所占用的处理器资源2线程调用了一个阻塞式IO方法在该方法返回之前该线程被阻塞3线程试图获得一个同步监视器但该同步监视器正被其他线程所持有4线程在等待某个通知5程序调用了线程的suspend()方法将该线程挂起。线程的生命周期在线程的生命周期中它要经过新建New、就绪Ready、运行Running、阻塞Blocked和死亡Dead5种状态。当程序使用new关键字创建了一个线程之后该线程就处于新建状态此时仅仅由Java虚拟机为其分配内存并初始化其成员变量的值。当线程对象调用了start()方法之后该线程处于就绪状态处于这个状态中的线程并没有开始运行只是表示该线程可以运行了。至于该线程何时开始运行取决于JVM里线程调度器的调度。如果处于就绪状态的线程获得了CPU开始执行run()方法的线程执行体则该线程处于运行状态当线程数大于处理器数时会存在多个线程在同一个CPU上轮换的现象。当一个线程开始运行后它不可能一直处于运行状态线程在运行过程中需要被中断目的是使其他线程获得执行的机会线程调度的细节取决于底层平台所采用的策略。当发生如下情况时线程将会进入阻塞状态1线程调用sleep()方法主动放弃所占用的处理器资源2线程调用了一个阻塞式IO方法在该方法返回之前该线程被阻塞3线程试图获得一个同步监视器但该同步监视器正被其他线程所持有4线程在等待某个通知5程序调用了线程的suspend()方法将该线程挂起。当发生如下特定的情况时可以解除上面的阻塞让该线程重新进入就绪状态1调用sleep()方法的线程经过了指定时间2线程调用的阻塞式IO方法已经返回3线程成功地获得了试图取得的同步监视器4线程正在等待某个通知时其他线程发出了一个通知5处于挂起状态的线程被调用了resume()恢复方法。线程会以如下三种方式结束结束后就处于死亡状态1run()或call()方法执行完成线程正常结束2线程抛出一个未捕获的Exception或Error3直接调用该线程的stop()方法来结束该线程。sleep()和wait()的区别sleep()方法让正在执行的线程主动让出cpu然后cpu就可以去执行其他任务在sleep指定时间后cpu再回到该线程继续往下执行sleep方法只让出了cpu而并不会释放同步资源锁到时间自动恢复。wait()方法则是指当前线程让自己暂时退让出同步资源锁以便其他正在等待该资源的线程得到该资源进而运行只有调用了notify()方法之前调用wait()的线程才会解除wait状态可以去参与竞争同步资源锁进而得到执行。wait()方法会放弃对象锁进入等待队列。sleep()可以在任何地方使用而wait()只能在同步方法或同步代码块中使用。sleep()不会释放锁而wait()会释放锁并需要通过notify()/notifyAll()重新获取锁。如何实现子线程先执行主线程再执行启动子线程后立即调用该线程的join()方法则主线程必须等待子线程执行完成后再执行。如何实现线程同步1使用Synchronized关键字被该关键字修饰的方法或语句块会自动被加上内置锁从而实现同步。2使用wait和notifywait()方法可以让当前线程释放对象锁并进入阻塞状态notify()方法用于唤醒一个正在等待相应对象锁的线程使其进入就绪队列以便在当前线程释放锁后竞争锁进而得到CPU的执行。3ReentrantLockReentrantLock类是可重入、互斥、实现了Lock接口的锁它与使用synchronized方法和快具有相同的基本行为和语义并且扩展了其能力。4使用特殊域变量volatilevolatile关键字为域变量的访问提供了一种免锁机制使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新。5使用可重入锁实现线程同步6使用阻塞队列实现线程同步wait、notify和notifyAll()wait()、notify()、notifyAll()用来实现线程通信这三个方法都不是Thread类中所声明的方法而是Object类中声明的方法并且被final修饰无法被重写。原因是每个对象都拥有锁所以让当前线程等待某个对象的锁所以应该通过这个对象来操作。wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对象锁的线程使其进入就绪队列以便在当前线程释放锁后竞争锁进而得到CPU的执行。notifyAll()用于唤醒所有正在等待相应对象锁的线程使它们进入就绪队列以便在当前线程释放锁后竞争锁进而得到CPU的执行。每个锁对象都有两个队列一个是就绪队列一个是阻塞队列。就绪队列存储了已就绪将要竞争锁的线程阻塞队列存储了被阻塞的线程。当一个阻塞线程被唤醒后才会进入就绪队列进而等待CPU的调度。反之当一个线程被wait后就会进入阻塞队列等待被唤醒。synchronized关键字synchronized关键字解决的是多个线程之间访问资源的同步性synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。synchronized关键字最主要的三种使用方式1修饰实例方法对当前对象实例加锁进入同步代码前要获得当前对象实例的锁2修饰静态方法对当前类对象加锁进入同步代码前要获得当前类对象的锁。也就是给当前类加锁会作用于类的所有对象实例因为静态成员不属于任何一个实例对象是类成员。3修饰代码块指定加锁对象对给定对象加锁进入同步代码块前要获得给定对象的锁。和synchronized方法一样synchronized代码块也是锁定当前对象的。synchronized可以修饰静态方法但不能修饰静态代码块。synchronized与Lock的区别1synchronized是Java关键字在JVM层面实现加锁和解锁Lock是一个接口在代码层面实现加锁和解锁。2synchronized可以用在代码块上、方法上Lock只能写在代码里。3synchronized在代码执行完或出现异常时自动释放锁Lock不会自动释放锁需要在finally中显示释放锁。4synchronized会导致线程拿不到锁一直等待Lock可以设置获取锁失败的超时时间。5synchronized无法得知是否获取锁成功Lock则可以通过tryLock得知加锁是否成功。6synchronized锁可重入、不可中断、非公平Lock锁可重入、可中断、可公平/不公平并可以细分读写锁以提高效率。synchronized和ReenTrantLock的区别synchronized是关键字依赖于JVMReentrantLock是类依赖于API它提供了比synchronized更多更灵活的特性等待可中断、可实现公平锁、可实现选择性通知锁可以绑定多个条件。volatile关键字volatile关键字是用来保证有序性和可见性的。当一个变量被定义成volatile之后它将具备两项特性1保证可见性2禁止指令重排。即执行到volatile变量时其前面的所有语句都执行完后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。虽然volatile能够保证可见性但它不能保证原子性。volatile变量在各个线程的工作内存中是不存在一致性问题的但是Java里面的运算操作符并非原子操作这导致volatile变量的运算在并发下一样是不安全的。synchronized和volatile的区别1volatile本质是在告诉JVM当前变量在寄存器工作内存中的值是不确定的需要从主存中读取synchronized则是锁定当前变量只有当前线程可以访问该变量其他线程被阻塞住。2volatile仅能使用在变量级别synchronized 则可以使用在变量、方法、和类级别的。3volatile仅能实现变量的修改可见性不能保证原子性而synchronized则可以保证变量的修改可见性和原子性。4volatile不会造成线程的阻塞synchronized可能会造成线程的阻塞。5volatile 标记的变量不会被编译器优化synchronized 标记的变量可以被编译器优化。如果不使用synchronized和Lock如何保证线程安全volatilevolatile关键字为域变量的访问提供了一种免锁机制使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新因此每次使用该域就要重新计算而不是使用寄存器中的值。乐观锁和悲观锁悲观锁总是假设最坏的情况每次去拿数据的时候都认为别人会修改所以每次在拿数据的时候都会上锁这样别人想拿这个数据就会阻塞直到它拿到锁共享资源每次只给一个线程使用其它线程阻塞用完后再把资源转让给其它线程。Java中悲观锁是通过synchronized关键字或Lock接口来实现的。乐观锁总是假设最好的情况每次去拿数据的时候都认为别人不会修改所以不会上锁但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于多读的应用类型这样可以提高吞吐量。乐观锁一般会使用版本号机制或者CASCompare And Swap算法实现。同步器AQS抽象队列同步器AbstractQueuedSynchronizerAQS用来构建锁或者其他同步组件减少了各功能组件实现的代码量也解决了在实现同步器时涉及的大量细节问题使用 AQS 能简单且高效地构造出应用广泛的大量的同步器例如ReentrantLock可重入锁支持公平和非公平的方式获取锁Semaphore计数信号量;ReentrantReadWriteLock读写锁。AQS的原理是什么如果被请求的共享资源空闲则将当前请求资源的线程设置为有效的工作线程并且将共享资源设置为锁定状态。如果被请求的共享资源被占用那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制这个机制AQS是用 CLH 队列锁实现的即将暂时获取不到锁的线程加入到队列中。ThreadLocalThreadLocal是线程私有的局部变量存储容器它用来存储线程私有变量内部真正存取是一个Map。每个线程可以通过set()和get()存取变量多线程间无法访问各自的局部变量。只要线程处于活动状态它所对应的ThreadLocal实例就是可访问的线程被终止后它的所有实例将被垃圾收集。ThreadLocal存储的变量属于当前线程。ThreadLocal经典的使用场景是为每个线程分配一个JDBC连接 Connection这样就可以保证每个线程的都在各自的Connection上进行数据库的操作。另外ThreadLocal还经常用于管理Session会话将Session保存在ThreadLocal中使线程处理多次处理会话时始终是同一个Session。线程池系统启动一个新线程的成本是比较高的在这种情形下使用线程池可以很好地提高性能尤其是当程序中需要创建大量生存期很短暂的线程时更应该考虑使用线程池。线程池提供了一种限制和管理资源包括执行一个任务的方式。使用线程池的好处1降低资源消耗通过重复利用已创建的线程降低线程创建和销毁造成的消耗2提高响应速度当任务到达时任务可以不需要的等到线程创建就能立即执行3提高线程的可管理性线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。如何创建线程池1通过ThreadPoolExecutor 的构造方法实现2通过Executor 框架的工具类 Executors 来实现。线程池的工作流程1判断核心线程池是否已满没满则创建一个新的工作线程来执行任务。2判断任务队列是否已满没满则将新提交的任务添加在工作队列。3判断整个线程池是否已满没满则创建一个新的工作线程来执行任务已满则执行饱和拒绝策略。线程池都有哪些状态RUNNING能接受新提交的任务并且也能处理阻塞队列中的任务。SHUTDOWN关闭状态不再接受新提交的任务但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时调用shutdown()方法会使线程池进入到该状态。STOP不能接受新任务也不处理队列中的任务会中断正在处理任务的线程。在线程池处于RUNNING或SHUTDOWN状态时调用shutdownNow()方法会使线程池进入到该状态。TIDYING如果所有的任务都已终止了有效线程数为0线程池进入该状态后会调用terminated()方法进入TERMINATED 状态。TERMINATED在terminated()方法执行完后进入该状态默认terminated()方法中什么也没有做。线程池的拒绝策略当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize如果还有任务到来就会采取任务拒绝策略通常有以下四种策略1AbortPolicy丢弃任务并抛出异常。2DiscardPolicy丢弃任务但是不抛出异常。3DiscardOldestPolicy丢弃队列最前面的任务然后重新尝试执行任务重复该过程。4CallerRunsPolicy由调用线程处理该任务。线程池的队列大小通常怎么设置1CPU密集型任务尽量使用较小的线程池一般为CPU核心数1。2IO密集型任务可以使用稍大的线程池一般为2*CPU核心数。63混合型任务可以将任务分成IO密集型和CPU密集型任务然后分别用不同的线程池去处理。只要分完之后两个任务的执行时间相差不大那么就会比串行执行来的高效。线程池有哪些参数各个参数的作用是什么1corePoolSize核心工作线程数当向线程池提交一个任务时若线程池已创建的线程数小于corePoolSize即便此时存在空闲线程也会通过创建一个新线程来执行该任务直到已创建的线程数大于或等于corePoolSize。2maximumPoolSize最大线程数线程池所允许的最大线程个数。当队列满了且已创建的线程数小于maximumPoolSize则线程池会创建新的线程来执行任务。另外对于无界队列可忽略该参数。3keepAliveTime多余线程存活时间当线程池中线程数大于核心线程数时线程的空闲时间如果超过线程存活时间那么这个线程就会被销毁直到线程池中的线程数小于等于核心线程数。4TimeUnit线程活动保持时间的单位5workQueue队列用于传输和保存等待执行任务的阻塞队列。6threadFactory线程创建工厂用于创建新线程。7handler拒绝策略当线程池和队列都满了再加入线程会执行此策略。
http://www.dnsts.com.cn/news/254372.html

相关文章:

  • 医院网站信息化有哪些建设规范长沙seo公司排名
  • 单页网站制作系统南安市城乡住房建设局网站
  • php网站 源码天津网站开发网站
  • 网站需要做404页面吗一张图看懂企业所得税
  • 企业网站建设中有哪几个重要点注册公司费用会计分录
  • 深圳模板建站多少钱程序员做外包网站
  • 培训课程网站那些网站专门做棋牌推广的
  • 天津做个网站需要多少钱中国建筑报道网
  • 莆田企业自助建站网站色调搭配
  • 万网建设网站的步骤湖南常德红网百姓呼声
  • 宁夏建设工程质量安全监督总网站北京网站设计公司哪家公司好
  • python一句做网站小企业网站建设的连接方式
  • 网站开发天晟合益想自己做网站吗
  • 邢台开发区建设小学官方网站专业建设网站哪家好
  • 适合网站开发的python专业网站设计建设公司
  • 黑色系 网站云南做网站哪家好
  • 全国妇联官方网站儿童之家建设建一个c2c网站要多少钱
  • 美食网站中饮食资讯该怎么做制作图片的电脑软件
  • 做盈利的设计素材网站有前途全部浏览器下载大全
  • 镇江做网站哪家公司好南做网站
  • wordpress网站制作教程视频电商平台网站建设
  • 做设计用的常用网站学seo如何入门
  • 怎么做自适应的网站会员播放系统插件wordpress
  • 做汉字网站的外国人永湖网站建设
  • 云课堂哪个网站做的好网站文件验证
  • 专业的设计网站有哪些wordpress新建页面显示数据
  • 百度站长平台电脑版食品商标出售网
  • 国外做仿牌网站做新房网站怎么弄
  • 南京网站建设 个人天水做网站电话
  • 西部数码个人网站铜陵58同城做网站