Java 8特性
1. Java8的新特性
1.1. Lambda表达式和函数式接口
最简单的Lambda表达式可以用逗号分隔的参数列表、->符号和功能语句块来表示。示例如下:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
请注意到编译器会根据上下文来推测参数的类型,或者你也可以显示地指定参数类型,只需要将类型包在括号里。举个例子:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Lambda表达式可能会引用类的成员或者局部变量会被隐式地转变成final类型,下面两种写法的效果是一样的:
String separator = ",";
//separator = ";;";//该行编译时会报错:从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
和
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
Lambda表达式可能会有返回值,编译器会根据上下文推断返回值的类型。如果lambda的语句块只有一行,不需要return关键字。下面两个写法是等价的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
Java语言的设计者们思考了很多如何让现有的功能和lambda表达式友好兼容。于是就有了函数接口这个概念。函数接口是一种只有一个方法的接口,函数接口可以隐式地转换成lambda表达式。
java.lang.Runnable 和java.util.concurrent.Callable是函数接口两个最好的例子。但是在实践中,函数接口是非常脆弱的,只要有人在接口里添加多一个方法,那么这个接口就不是函数接口了,就会导致编译失败。Java 8提供了一个特殊的注解@FunctionalInterface来克服上面提到的脆弱性并且显示地表明函数接口的目的(java里所有现存的接口都已经加上了@FunctionalInterface)。让我们看看一个简单的函数接口定义:
@FunctionalInterface
public interface Functional {
void method();
}
我们要记住默认的方法和静态方法(下一节会具体解释)不会违反函数接口的约定,例子如下:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
函数式接口的重要属性是:我们能够使用lambda实例化它们。下面是实例化Runnable函数式接口的一个例子。
Runnable r = () ->{ System.out.println("Running!"); }
新版本向 java.util.function包中添加了很多新的函数式接口。下面是一些例子:
Function<T, R>——将T作为输入,返回R作为输出
Predicate<T>——将T作为输入,返回一个布尔值作为输出
Consumer<T>——将T作为输入,不返回任何内容
Supplier<T>——没有输入,返回T
BinaryOperator<T>——将两个T作为输入,返回一个T作为输出
1.2. 接口的默认方法和静态方法
Java 8增加了两个新的概念在接口声明的时候:默认和静态方法。默认方法允许我们在接口里添加新的方法,而不会破坏实现这个接口的已有类的兼容性,也就是说不会强迫实现接口的类实现默认方法。
默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供一个默认的方法实现,所有这个接口的实现类都会通过继承得到这个方法(如果有需要也可以重写这个方法),让我们来看看下面的例子:
private interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
} private static class DefaultableImpl implements Defaulable { } private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
接口Defaulable使用default关键字声明了一个默认方法notRequired(),类DefaultableImpl实现了Defaulable接口,没有对默认方法做任何修改。另外一个类OverridableImpl重写类默认实现,提供了自己的实现方法。注意,接口不能为Object类中的任何方法提供默认的实现。
Java 8 的另外一个有意思的新特性是接口里可以声明静态方法,并且可以实现。例子如下:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
下面是把接口的静态方法和默认方法放在一起的示例(::new 是构造方法引用,后面会有详细描述):
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}
控制台的输出如下:
Default implementation
Overridden implementation
1.3. 方法引用
方法引用提供了一个很有用的语义来直接访问类或者实例的已经存在的方法或者构造方法。结合Lambda表达式,方法引用使语法结构紧凑简明。不需要复杂的引用。
下面我们用Car 这个类来做示例,Car这个类有不同的方法定义。让我们来看看java 8支持的4种方法引用。
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
} public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
} public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
} public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一种方法引用是构造方法引用,语法是:Class::new ,对于泛型来说语法是:Class<T >::new,请注意构造方法没有参数:
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
第二种方法引用是静态方法引用,语法是:Class::static_method请注意这个静态方法只支持一个类型为Car的参数。
cars.forEach( Car::collide );
第三种方法引用是类实例的方法引用,语法是:Class::method请注意方法没有参数。
cars.forEach( Car::repair );
最后一种方法引用是引用特殊类的方法,语法是:instance::method,请注意只接受Car类型的一个参数。
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
运行这些例子我们将会在控制台得到如下信息(Car的实例可能会不一样):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
2. Java 8 库的新特性
2.1. Optional
著名的NullPointerException 是引起系统失败最常见的原因。很久以前Google Guava项目引入了Optional作为解决空指针异常的一种方式,不赞成代码被null检查的代码污染,期望程序员写整洁的代码。受Google Guava的鼓励,Optional 现在是Java 8库的一部分。
Optional 只是一个容器,它可以保存一些类型的值或者null。它提供很多有用的方法,所以没有理由显式地检查null。
让我们看看两个Optional 用法的小例子:一个是允许为空的值,另外一个是不允许为空的值。
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
如果Optional实例有非空的值,方法 isPresent() 返回true否则返回false。方法orElseGet提供了回退机制,当Optional的值为空时接受一个方法返回默认值。map()方法转化Optional当前的值并且返回一个新的Optional实例。orElse方法和orElseGet类似,但是它不接受一个方法,而是接受一个默认值。上面代码运行结果如下:
Full Name is set? false
Full Name: [none]
Hey Stranger!
让我们大概看看另外一个例子。
Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
输出如下:
First Name is set? true
First Name: Tom
Hey Tom!
2.2. Stream
新增加的Stream API (java.util.stream)引入了在Java里可以工作的函数式编程。这是目前为止对java库最大的一次功能添加,希望程序员通过编写有效、整洁和简明的代码,能够大大提高生产率。
list转map
常用方式
public Map<Long, String> getIdNameMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername));
}
收集成实体本身map
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account));
}
account -> account是一个返回本身的lambda表达式,其实还可以使用Function接口中的一个默认方法代替,使整个方法更简洁优雅:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity()));
}
重复key的情况
代码如下:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity()));
}
这个方法可能报错(java.lang.IllegalStateException: Duplicate key),因为name是有可能重复的。toMap有个重载方法,可以传入一个合并的函数来解决key冲突问题:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2));
}
这里只是简单的使用后者覆盖前者来解决key重复问题。
指定具体收集的map
toMap还有另一个重载方法,可以指定一个Map的具体实现,来收集数据:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new));
}
Stream API让集合处理简化了很多(我们后面会看到不仅限于Java集合类)。让我们从一个简单的类Task开始来看看Stream的用法。
public class Streams {
private enum Status {
OPEN, CLOSED
}; private static final class Task {
private final Status status;
private final Integer points; Task( final Status status, final Integer points ) {
this.status = status;
this.points = points;
} public Integer getPoints() {
return points;
} public Status getStatus() {
return status;
} @Override
public String toString() {
return String.format( "[%s, %d]", status, points );
}
}
}
Task类有一个分数的概念(或者说是伪复杂度),其次是还有一个值可以为OPEN或CLOSED的状态.让我们引入一个Task的小集合作为演示例子:
final Collection< Task > tasks = Arrays.asList(
new Task( Status.OPEN, 5 ),
new Task( Status.OPEN, 13 ),
new Task( Status.CLOSED, 8 )
);
第一个问题是所有的开放的Task的点数是多少?在java 8 之前,通常的做法是用foreach迭代。但是Java8里头我们会用Stream。Stream是多个元素的序列,支持串行和并行操作。
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
.stream()
.filter( task -> task.getStatus() == Status.OPEN )
.mapToInt( Task::getPoints )
.sum();
System.out.println( "Total points: " + totalPointsOfOpenTasks );
控制台的输出将会是:
Total points: 18
上面代码执行的流程是这样的,首先Task集合会被转化为Stream表示,然后filter操作会过滤掉所有关闭的Task,接下来使用Task::getPoints 方法取得每个Task实例的点数,mapToInt方法会把Task Stream转换成Integer Stream,最后使用Sum方法将所有的点数加起来得到最终的结果。
在我们看下一个例子之前,我们要记住一些关于Stream的说明。Stream操作被分为中间操作和终点操作。
中间操作返回一个新的Stream。这些中间操作是延迟的,执行一个中间操作比如filter实际上不会真的做过滤操作,而是创建一个新的Stream,当这个新的Stream被遍历的时候,它里头会包含有原来Stream里符合过滤条件的元素。
终点操作比如说forEach或者sum会遍历Stream从而产生最终结果或附带结果。终点操作执行完之后,Stream管道就被消费完了,不再可用。在几乎所有的情况下,终点操作都是即时完成对数据的遍历操作。
Stream的另外一个价值是Stream创造性地支持并行处理。让我们看看下面这个例子,这个例子把所有task的点数加起来。
// Calculate total points of all tasks
final double totalPoints = tasks
.stream()
.parallel()
.map( task -> task.getPoints() ) // or map( Task::getPoints )
.reduce( 0, Integer::sum );
System.out.println( "Total points (all tasks): " + totalPoints );
这个例子跟上面那个非常像,除了这个例子里使用了parallel()方法 并且计算最终结果的时候使用了reduce方法。
输出如下:
Total points (all tasks): 26.0
经常会有这个一个需求:我们需要按照某种准则来对集合中的元素进行分组。Stream也可以处理这样的需求,下面是一个例子:
// Group tasks by their status
final Map< Status, List< Task > > map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
控制台的输出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
按照某种准则来对集合中的元素进行分组并统计每组个数:
// Group tasks by their status
final Map< Status, Long > map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus, Collectors.counting()) );
System.out.println( map );
按照某种准则来对集合中的元素进行分组并统计每组里某个字段的平均值:
// Group tasks by their status
final Map< Status, Long > map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus, Collectors.averagingInt(Task::getPoints)) );
System.out.println( map );
让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。
// Calculate the weight of each tasks (as percent of total points)
final Collection< String > result = tasks
.stream() // Stream< String >
.mapToInt( Task::getPoints ) // IntStream
.asLongStream() // LongStream
.mapToDouble( points -> points / totalPoints ) // DoubleStream
.boxed() // Stream< Double >
.mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
.mapToObj( percentage -> percentage + "%" ) // Stream< String>
.collect( Collectors.toList() ); // List< String >
System.out.println( result );
控制台输出如下:
[19%, 50%, 30%]
最后,就像前面提到的,Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。
final Path path = new File( filename ).toPath();
try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
Stream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。
流可以是无限的、有状态的,可以是顺序的,也可以是并行的。在使用流的时候,你首先需要从一些来源中获取一个流,执行一个或者多个中间操作,然后执行一个最终操作。中间操作包括filter、map、flatMap、peel、distinct、sorted、limit和substream。终止操作包括forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和findAny。 java.util.stream.Collectors是一个非常有用的实用类。该类实现了很多归约操作,例如将流转换成集合和聚合元素。
Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能:
首先我们创建一个没有重复元素的大表:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
然后我们计算一下排序这个Stream要耗时多久,
串行排序:
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// 串行耗时: 899 ms
并行排序:
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
// 并行排序耗时: 472 ms
上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。
2.3. 日期时间API(JSR310)
新的java.time包包含了所有关于日期、时间、日期时间、时区、Instant(跟日期类似但精确到纳秒)、duration(持续时间)和时钟操作的类。设计这些API的时候很认真地考虑了这些类的不变性(从java.util.Calendar吸取的痛苦教训)。如果需要修改时间对象,会返回一个新的实例。
- Clock
Clock使用时区来访问当前的instant, date和time。Clock类可以替换 System.currentTimeMillis() 和 TimeZone.getDefault().
- LocalDate
LocalDate只保存有ISO-8601日期系统的日期部分,有时区信息
- LocalTime
LocalTime只保存ISO-8601日期系统的时间部分,没有时区信息。
- LocalDateTime
LocalDateTime类合并了LocalDate和LocalTime,它保存有ISO-8601日期系统的日期和时间,但是没有时区信息。
- ZonedDateTime
如果您需要一个类持有日期时间和时区信息,可以使用ZonedDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。
- Duration
Duration持有的时间精确到纳秒。它让我们很容易计算两个日期中间的差异。
2.4. Base64
对Base64的支持最终成了Java 8标准库的一部分,非常简单易用:
package com.javacodegeeks.java8.base64; import java.nio.charset.StandardCharsets;
import java.util.Base64; public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64
.getEncoder()
.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
System.out.println( encoded );
final String decoded = new String(
Base64.getDecoder().decode( encoded ),
StandardCharsets.UTF_8 );
System.out.println( decoded );
}
}
控制台输出的编码和解码的字符串:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
新的Base64API也支持URL和MINE的编码解码:
(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder()).
2.5. 并行数组
Java 8新增加了很多方法支持并行的数组处理。最重要的大概是parallelSort()这个方法显著地使排序在多核计算机上速度加快。下面的小例子演示了这个新的方法(parallelXXX)的行为。
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom; public class ParallelArrays {
public static void main( String[] args ) {
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong,
index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
}
}
这一小段代码使用parallelSetAll() 方法填充这个长度是2000的数组,然后使用parallelSort() 排序。这个程序输出了排序前和排序后的10个数字来验证数组真的已经被排序了。示例可能的输出如下(请注意这些数字是随机产生的)
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
2.6. 并发
在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)。
新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。
在java.util.concurrent.atomic包中还增加了下面这些类:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
3. 新的工具
3.1. 类依赖分析工具:jdeps
Jdeps是一个功能强大的命令行工具,它可以帮我们显示出包层级或者类层级java类文件的依赖关系。它接受class文件、目录、jar文件作为输入,默认情况下,jdeps会输出到控制台。
作为例子,让我们看看现在很流行的Spring框架的库的依赖关系报告。为了让报告短一些,我们只分析一个jar: org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar 这个命令输出内容很多,我们只看其中的一部分,这些依赖关系根绝包来分组,如果依赖关系在classpath里找不到,就会显示not found.
4. JVM的新特性
JVM内存永久区已经被metaspace替换(JEP 122)。JVM参数 -XX:PermSize 和 –XX:MaxPermSize被XX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替。
5. 参考资料
http://ifeve.com/java-8-features-tutorial/
https://blog.chou.it/2014/03/java-8-new-features/
http://www.infoq.com/cn/news/2013/08/everything-about-java-8#
http://www.importnew.com/17313.html
Java 8特性的更多相关文章
- 主流的单元测试工具之-JAVA新特性-Annotation 写作者:组长 梁伟龙
1:什么是Annotation?Annotation,即“@xxx”(如@Before,@After,@Test(timeout=xxx),@ignore),这个单词一般是翻译成元数据,是JAVA的一 ...
- Java 三大特性——封装、继承、多态
一.封装 封装,实际就是把属于同一类事物的共性(包括属性与方法)归到一个类中,以方便使用. 概念:在面向对象程式设计方法中,封装(英语:Encapsulation)是指,一种将抽象性函式接口的实作细节 ...
- paip。java 高级特性 类默认方法,匿名方法+多方法连续调用, 常量类型
paip.java 高级特性 类默认方法,匿名方法+多方法连续调用, 常量类型 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http ...
- Hi java新特性
java新特性 1995.5.23 java语言 1996 jdk1.0 250个类在API 主要用在桌面型应用程序1997 jdk1.1 500 图形用户界面编程1998 jdk1.2 2300 J ...
- JAVA三大特性之多态
面向对象的三大特性:封装.继承.多态.从一定角度来看,封装和继承几乎都是为多态而准备的.这是我们最后一个概念,也是最重要的知识点. 多态的定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据 ...
- Java高级特性之泛型
首先我们先提出两个问题: 什么是泛型? 为什么要使用泛型?我们先来看看第一个问题什么是泛型.如果你对Java三大特性中的多态性理解的比较透彻的话,泛型就比较好理解了.多态性表示一个对象具备多种状态.比 ...
- JAVA三大特性之二——继承
很多人在学习了JAVA以后,都会了解这个概念,而且继承也会在以后的开发中经常用到,但对于JAVA的继承特性,很多人都了解的不够深入,不够完整,当然这其中包括我,所以我就想抽点时间来整理一下JAVA继承 ...
- JAVA三大特性之一——封装
自学java已经有一段时间了,但是感觉对于很多知识点还是有必要总结和整理一下,下面我就来说一下我对JAVA三大特性之一——封装特性的认识和理解. 封装,从字面意思可以看出来,就是包装,也就是把我们写好 ...
- JAVA基础第二章-java三大特性:封装、继承、多态
业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...
- Java三大特性
Java 三大特性,算是Java独特的表现,提到Java 的三大特性, 我们都会想到封装, 继承和多态 这是我们Java 最重要的特性. 封装(Encapsulation) : 封装:是指隐藏对象的属 ...
随机推荐
- 最大开源代码sourceforge 简介 及视音频方面常用的开源代码
所有的音视频凯源代码在这里:http://sourceforge.net/directory/audio-video/os:windows/,你可以下载分析,视频不懂请发邮件给我,帮你分析. 0.视频 ...
- (转)Inno Setup入门(七)——提供安装语言选项
本文转载自:http://blog.csdn.net/yushanddddfenghailin/article/details/17250803 Inno Setup安装目录下有一个Languages ...
- DataTable 树形构造加全部
DataTable dtGx = new DataTable(); dtGx = SqlHelper.SqlGetDataTable(StrSql, "tbUserGx"); th ...
- ROS HTB限速失败原因分析和需注意事项
要想做限速,必须要知道以下几点: 首先要知道自己要限制什么的速度,谁的速度,于是需要用的标记,即Mangle. 其次要知道怎么限速,是限制上传,还是下载? 最后要知道所做的限速是否成功,即需要知道如何 ...
- Sql--------服务器的数据库表数据插入到本地数据库
本地语句:::insert into 表名(列名) SELECT * FROM OPENDATASOURCE('SQLOLEDB', 'Data Source=127.0.0.1;User ID=sa ...
- C++类够做函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式.例如: class CExample{ public: int a; float b; C ...
- Oracle数据库学习笔记(一)
Oracle的体系结构大体上分为两部分:Instance(实例)和Database(数据库). Instance(实例) :在Oracle Instance中主要包含了SGA以及一些进程(例如:P ...
- Oracle block 格式
Oracle block 格式 信息参考: http://www.ixora.com.au/ 特别感谢 overtime 大哥对我的无私的帮助和对我一直鼓励支持我的网友这些资料是没得到oracle ...
- socket-简单实现
server--------------#!/usr/bin/env python # encoding: utf-8 # Date: 2018/6/7 from socket import * s ...
- SSMS安装英文版后无法修改为中文
SSMS的UI语言和所安装的Visual Studio的语言是相关的,你这种情况应该是第一次安装的时候安装了英文版的visual studio isolated shell,在卸载的时候你没有卸载这个 ...